about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/client.nim9
-rw-r--r--src/io/buffer.nim24
-rw-r--r--src/io/http.nim52
-rw-r--r--src/io/loader.nim125
-rw-r--r--src/io/request.nim (renamed from src/io/loadertypes.nim)23
-rw-r--r--src/io/serialize.nim139
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[])