diff options
Diffstat (limited to 'src')
-rw-r--r-- | src/client.nim | 9 | ||||
-rw-r--r-- | src/io/buffer.nim | 24 | ||||
-rw-r--r-- | src/io/http.nim | 52 | ||||
-rw-r--r-- | src/io/loader.nim | 125 | ||||
-rw-r--r-- | src/io/request.nim (renamed from src/io/loadertypes.nim) | 23 | ||||
-rw-r--r-- | src/io/serialize.nim | 139 |
6 files changed, 311 insertions, 61 deletions
diff --git a/src/client.nim b/src/client.nim index 9eec487d..1df1b3d6 100644 --- a/src/client.nim +++ b/src/client.nim @@ -4,7 +4,6 @@ import streams import terminal import unicode -import bindings/curl import css/sheet import config/config import io/buffer @@ -161,9 +160,9 @@ proc readPipe(client: Client, ctype: string) = var g_client: Client proc getPage(client: Client, url: Url, click = none(ClickAction)): LoadResult = let page = if click.isnone: - client.loader.getPage(url) + client.loader.getPage(newRequest(url)) else: - client.loader.getPage(url, click.get.httpmethod, click.get.mimetype, click.get.body, click.get.multipart) + client.loader.getPage(newRequest(url, click.get.httpmethod, {"Content-Type": click.get.mimetype}, click.get.body, click.get.multipart)) return page # Load url in a new buffer. @@ -413,7 +412,6 @@ proc isearchBack(client: Client) = proc quit(client: Client) = eraseScreen() print(HVP(0, 0)) - curl_global_cleanup() quit(0) proc input(client: Client) = @@ -531,9 +529,6 @@ proc inputLoop(client: Client) = client.buffer.setStatusMessage(e.msg) proc launchClient*(client: Client, pages: seq[string], ctype: string, dump: bool) = - if curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK: - eprint "Failed to initialize libcurl." - quit(1) client.userstyle = gconfig.stylesheet.parseStylesheet() if not stdin.isatty: client.readPipe(ctype) diff --git a/src/io/buffer.nim b/src/io/buffer.nim index 08feb73f..e6c290c7 100644 --- a/src/io/buffer.nim +++ b/src/io/buffer.nim @@ -14,6 +14,8 @@ import html/htmlparser import io/cell import io/lineedit import io/loader +import io/request +import io/serialize import io/term import js/regex import layout/box @@ -841,10 +843,17 @@ proc loadResources(buffer: Buffer, document: Document) = if elem.rel == "stylesheet": let url = parseUrl(elem.href, document.location.some) if url.issome: - if url.get.scheme == buffer.location.scheme: - let res = buffer.loader.getPage(url.get) - if res.s != nil and res.contenttype == "text/css": - let sheet = parseStylesheet(res.s) + let url = url.get + if url.scheme == buffer.location.scheme: + let fs = buffer.loader.getPage(newRequest(url)) + if fs.s != nil and fs.contenttype == "text/css": + var res = newStringStream() + while true: + var s: string + buffer.istream.sread(s) + if s == "": break + res.write(s) + let sheet = parseStylesheet(res) elem.sheet = sheet for child in elem.children_rev: @@ -854,8 +863,11 @@ proc load*(buffer: Buffer) = case buffer.contenttype of "text/html": if not buffer.streamclosed: - buffer.source = buffer.istream.readAll() - buffer.istream.close() + while true: + var s: string + buffer.istream.sread(s) + if s == "": break + buffer.source &= s buffer.istream = newStringStream(buffer.source) buffer.document = parseHTML5(buffer.istream) buffer.streamclosed = true diff --git a/src/io/http.nim b/src/io/http.nim index 0cba2c29..221648f3 100644 --- a/src/io/http.nim +++ b/src/io/http.nim @@ -3,15 +3,18 @@ import streams import strutils import bindings/curl -import io/loadertypes +import io/request +import io/serialize import types/url -import types/mime import utils/twtstr type HeaderResult* = ref object statusline: bool headers: HeaderList + curl: CURL + ostream: Stream + request: Request template setopt(curl: CURL, opt: CURLoption, arg: typed) = discard curl_easy_setopt(curl, opt, arg) @@ -30,12 +33,23 @@ proc curlWriteHeader(p: cstring, size: csize_t, nitems: csize_t, userdata: point let headers = cast[HeaderResult](userdata) if not headers.statusline: headers.statusline = true - return nitems #TODO handle status line? + var status: int + headers.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status) + headers.ostream.swrite(status) + return nitems let k = line.until(':') if k.len == line.len: - return nitems # empty line (last, before body) or invalid (=> error) + # empty line (last, before body) or invalid (=> error) + headers.ostream.swrite(headers.headers.getOrDefault("Content-Type").until(';')) + var urlp: cstring + headers.curl.getinfo(CURLINFO_REDIRECT_URL, addr urlp) + if urlp != nil: + headers.ostream.swrite(parseUrl($urlp, some(headers.request.url))) + else: + headers.ostream.swrite(none(Url)) + return nitems let v = line.substr(k.len + 1).strip() headers.headers.add(k, v) @@ -46,11 +60,12 @@ proc curlWriteBody(p: cstring, size: csize_t, nmemb: csize_t, userdata: pointer) for i in 0..<nmemb: s[i] = p[i] let stream = cast[Stream](userdata) - stream.write(s) - stream.flush() + if nmemb > 0: + stream.swrite(s) + stream.flush() return nmemb -proc getPageHttp*(request: Request): LoadResult = +proc loadHttp*(request: Request, ostream: Stream) = let curl = curl_easy_init() if curl == nil: return # fail @@ -58,11 +73,10 @@ proc getPageHttp*(request: Request): LoadResult = let surl = request.url.serialize() curl.setopt(CURLOPT_URL, surl) - var cs = newStringStream() - curl.setopt(CURLOPT_WRITEDATA, cs) + curl.setopt(CURLOPT_WRITEDATA, ostream) curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody) - let headerres = HeaderResult(headers: newHeaderList()) + let headerres = HeaderResult(headers: newHeaderList(), curl: curl, ostream: ostream, request: request) curl.setopt(CURLOPT_HEADERDATA, headerres) curl.setopt(CURLOPT_HEADERFUNCTION, curlWriteHeader) @@ -104,21 +118,9 @@ proc getPageHttp*(request: Request): LoadResult = let res = curl_easy_perform(curl) if res == CURLE_OK: # TODO handle errors - cs.setPosition(0) - result.s = cs - - let ct = headerres.headers.getOrDefault("Content-Type") - if ct != "": - result.contenttype = ct.until(';') - else: - result.contenttype = guessContentType(request.url.path.serialize()) - curl.getinfo(CURLINFO_RESPONSE_CODE, addr result.status) - if result.status in {301, 302, 303}: #TODO 300, 304, 307 - var urlp: cstring - curl.getinfo(CURLINFO_REDIRECT_URL, addr urlp) - if urlp != nil: - let urls = $urlp - result.redirect = parseUrl(urls, some(request.url)) + discard + ostream.swrite("") + ostream.flush() curl_easy_cleanup(curl) if mime != nil: diff --git a/src/io/loader.nim b/src/io/loader.nim index f7ac06e6..74a692fe 100644 --- a/src/io/loader.nim +++ b/src/io/loader.nim @@ -4,12 +4,14 @@ import tables when defined(posix): import posix +import bindings/curl import io/http -import io/loadertypes +import io/request +import io/serialize import types/mime import types/url -export loadertypes +export request const DefaultHeaders = { "User-Agent": "chawan", @@ -32,26 +34,113 @@ proc doFork(): Pid = quit(0) return 0 +proc loadFile(url: Url, ostream: Stream) = + when defined(windows) or defined(OS2) or defined(DOS): + let path = url.path.serialize_unicode_dos() + else: + let path = url.path.serialize_unicode() + let istream = newFileStream(path, fmRead) + ostream.swrite(if istream != nil: + 200 # ok + else: + 404 # file not found + ) + ostream.swrite(guessContentType(path)) + ostream.swrite(none(Url)) + while not istream.atEnd: + const bufferSize = 4096 + var buffer {.noinit.}: array[bufferSize, char] + while true: + let n = readData(istream, addr buffer[0], bufferSize) + if n == 0: + break + ostream.swrite(n) + ostream.writeData(addr buffer[0], n) + ostream.flush() + if n < bufferSize: + break + ostream.swrite("") + ostream.flush() + +proc loadResource(loader: FileLoader, request: Request, ostream: Stream) = + case request.url.scheme + of "file": + loadFile(request.url, ostream) + of "http", "https": + loadHttp(request, ostream) + +proc runFileLoader(loader: FileLoader) = + if curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK: + eprint "Failed to initialize libcurl." + quit(1) + let istream = newFileStream(stdin) + let ostream = newFileStream(stdout) + while true: + try: + let request = istream.readRequest() + loader.loadResource(request, ostream) + except IOError: + # End-of-file, quit. + # TODO this should be EOFError + break + istream.close() + ostream.close() + curl_global_cleanup() + quit(0) + +proc doRequest*(loader: FileLoader, request: Request): LoadResult = + if loader.istream != nil: + loader.istream.swrite(request) + loader.istream.flush() + loader.ostream.sread(result.status) + loader.ostream.sread(result.contenttype) + loader.ostream.sread(result.redirect) + result.s = loader.ostream + else: + eprint "Error: no loader process" + quit(1) + proc newFileLoader*(defaultHeaders: HeaderList): FileLoader = new(result) result.defaultHeaders = defaultHeaders + when defined(posix): + var pipefd_a: array[0..1, cint] + var pipefd_b: array[0..1, cint] + if pipe(pipefd_a) == -1: + eprint "Failed to open pipe." + quit(1) + if pipe(pipefd_b) == -1: + eprint "Failed to open pipe." + quit(1) + let pid = doFork() + if pid == 0: + # child process + let readfd = pipefd_a[0] # get read a + discard close(pipefd_a[1]) # close write a + let writefd = pipefd_b[1] # get write b + discard close(pipefd_b[0]) # close read b + discard dup2(readfd, stdin.getFileHandle()) + discard dup2(writefd, stdout.getFileHandle()) + result.runFileLoader() + else: + result.process = pid + let writefd = pipefd_a[1] # get write a + discard close(pipefd_a[0]) # close read a + let readfd = pipefd_b[0] # get read b + discard close(pipefd_b[1]) # close write b + var readf: File + var writef: File + if not open(readf, FileHandle(readfd), fmRead): + eprint "Failed to open output handle." + quit(1) + if not open(writef, FileHandle(writefd), fmWrite): + eprint "Failed to open input handle." + quit(1) + result.ostream = newFileStream(readf) + result.istream = newFileStream(writef) proc newFileLoader*(): FileLoader = newFileLoader(DefaultHeaders) -proc getPage*(loader: FileLoader, url: Url, smethod: HttpMethod = HTTP_GET, mimetype = "", body = none(string), multipart = none(MimeData)): LoadResult = - case url.scheme - of "file": - when defined(windows) or defined(OS2) or defined(DOS): - let path = url.path.serialize_unicode_dos() - else: - let path = url.path.serialize_unicode() - result.contenttype = guessContentType(path) - result.s = newFileStream(path, fmRead) - if result.s != nil: - result.status = 200 # ok - else: - result.status = 404 # file not found - of "http", "https": - let request = loader.newRequest(url, smethod, {"Content-Type": mimetype}, body, multipart) - return getPageHttp(request) +proc getPage*(loader: FileLoader, request: Request): LoadResult = + loader.doRequest(request) diff --git a/src/io/loadertypes.nim b/src/io/request.nim index 4c910b44..461a3680 100644 --- a/src/io/loadertypes.nim +++ b/src/io/request.nim @@ -10,7 +10,8 @@ type HttpMethod* = enum HTTP_POST, HTTP_PUT, HTTP_TRACE type - Request* = ref object + Request* = ref RequestObj + RequestObj* = object httpmethod*: HttpMethod url*: Url headers*: HeaderList @@ -19,6 +20,9 @@ type FileLoader* = ref object defaultHeaders*: HeaderList + process*: int + istream*: Stream + ostream*: Stream LoadResult* = object s*: Stream @@ -59,22 +63,31 @@ func newHeaderList*(table: Table[string, string]): HeaderList = else: result.table[k] = @[v] -func newRequest*(loader: FileLoader, - url: Url, +func newRequest*(url: Url, httpmethod = HTTP_GET, headers: openarray[(string, string)] = [], body = none(string), - multipart = none(MimeData)): Request = + multipart = none(MimeData), + defaultHeaders = none(HeaderList)): Request = new(result) result.httpmethod = httpmethod result.url = url - result.headers.table = loader.defaultHeaders.table + if defaultHeaders.issome: + result.headers.table = defaultHeaders.get.table for it in headers: if it[1] != "": #TODO not sure if this is a good idea, options would probably work better result.headers.table[it[0]] = @[it[1]] result.body = body result.multipart = multipart +func newRequest*(loader: FileLoader, + url: Url, + httpmethod = HTTP_GET, + headers: openarray[(string, string)] = [], + body = none(string), + multipart = none(MimeData)): Request = + newRequest(url, httpmethod, headers, body, multipart, some(loader.defaultHeaders)) + proc `[]=`*(multipart: var MimeData, k, v: string) = multipart.content.add(MimePart(name: k, content: v)) diff --git a/src/io/serialize.nim b/src/io/serialize.nim new file mode 100644 index 00000000..505eb8c9 --- /dev/null +++ b/src/io/serialize.nim @@ -0,0 +1,139 @@ +# Write data to streams. + +import options +import streams +import tables + +import io/request +import types/url + +template swrite*[T](stream: Stream, o: T) = + stream.write(o) + +proc swrite*(stream: Stream, s: string) = + stream.swrite(s.len) + stream.write(s) + +proc swrite*(stream: Stream, b: bool) = + if b: + stream.swrite(1u8) + else: + stream.swrite(0u8) + +proc swrite*(stream: Stream, url: Url) = + stream.swrite(url.serialize()) + +proc swrite*(stream: Stream, headers: HeaderList) = + stream.swrite(headers.table.len) + for k, v in headers.table: + stream.swrite(k) + stream.swrite(v.len) + for s in v: + stream.swrite(s) + +proc swrite*(stream: Stream, part: MimePart) = + stream.swrite(part.isFile) + stream.swrite(part.name) + stream.swrite(part.content) + if part.isFile: + stream.swrite(part.filename) + stream.swrite(part.contentType) + stream.swrite(part.fileSize) + stream.swrite(part.isStream) + +proc swrite*[T](stream: Stream, s: seq[T]) = + stream.swrite(s.len) + for m in s: + stream.swrite(m) + +proc swrite*[T](stream: Stream, o: Option[T]) = + if o.issome: + stream.swrite(1u8) + stream.swrite(o.get) + else: + stream.swrite(0u8) + +proc swrite*(stream: Stream, request: Request) = + stream.swrite(request.httpmethod) + stream.swrite(request.url) + stream.swrite(request.headers) + stream.swrite(request.body) + stream.swrite(request.multipart) + +template sread*[T](stream: Stream, o: T) = + stream.read(o) + +proc sread*(stream: Stream, s: var string) = + var len: int + stream.sread(len) + stream.readStr(len, s) + +proc sread*(stream: Stream, b: var bool) = + var n: uint8 + stream.sread(n) + if n == 1u8: + b = true + else: + assert n == 0u8 + b = false + +proc sread*(stream: Stream, url: var Url) = + var s: string + stream.sread(s) + url = parseUrl(s).get + +proc sread*(stream: Stream, headers: var HeaderList) = + var len: int + stream.sread(len) + for i in 0..<len: + var k: string + stream.sread(k) + var n: int + stream.sread(n) + for j in 0..<n: + var v: string + stream.sread(v) + headers.add(k, v) + +proc sread*(stream: Stream, part: var MimePart) = + var isFile: bool + stream.sread(isFile) + if isFile: + part = MimePart(isFile: true) + else: + part = MimePart(isFile: false) + stream.sread(part.name) + stream.sread(part.content) + if part.isFile: + stream.sread(part.filename) + stream.sread(part.contentType) + stream.sread(part.fileSize) + stream.sread(part.isStream) + +proc sread*[T](stream: Stream, s: var seq[T]) = + var len: int + stream.sread(len) + s.setLen(len) + for i in 0..<len: + stream.sread(s[i]) + +proc sread*[T](stream: Stream, o: var Option[T]) = + let c = uint8(stream.readChar()) + if c == 1u8: + var m: T + stream.sread(m) + o = some(m) + else: + assert c == 0u8 + o = none(T) + +proc sread*(stream: Stream, req: var RequestObj) = + stream.sread(req.httpmethod) + stream.sread(req.url) + stream.sread(req.headers) + stream.sread(req.body) + stream.sread(req.multipart) + +proc readRequest*(stream: Stream): Request = + new(result) + stream.sread(result[]) |