about summary refs log tree commit diff stats
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/config/mailcap.nim7
-rw-r--r--src/io/posixstream.nim71
-rw-r--r--src/io/socketstream.nim91
-rw-r--r--src/loader/loader.nim12
-rw-r--r--src/loader/request.nim62
-rw-r--r--src/local/client.nim45
-rw-r--r--src/local/container.nim1
-rw-r--r--src/local/pager.nim2
-rw-r--r--src/render/rendertext.nim11
-rw-r--r--src/server/buffer.nim46
-rw-r--r--src/server/forkserver.nim6
-rw-r--r--src/types/buffersource.nim11
-rw-r--r--src/types/formdata.nim5
13 files changed, 127 insertions, 243 deletions
diff --git a/src/config/mailcap.nim b/src/config/mailcap.nim
index fc75086d..0caf7f17 100644
--- a/src/config/mailcap.nim
+++ b/src/config/mailcap.nim
@@ -216,11 +216,10 @@ proc quoteFile(file: string, qs: QuoteState): string =
       elif qs == QS_NORMAL:
         s &= '\\'
       # double-quoted: append normally
-    of '_', '.', ':', '/':
+    of AsciiAlphaNumeric, '_', '.', ':', '/':
       discard # no need to quote
-    else:
-      if c notin AsciiAlpha and qs == QS_NORMAL:
-        s &= '\\'
+    elif qs == QS_NORMAL:
+      s &= '\\'
     s &= c
   return s
 
diff --git a/src/io/posixstream.nim b/src/io/posixstream.nim
index 3aa14191..683d72b8 100644
--- a/src/io/posixstream.nim
+++ b/src/io/posixstream.nim
@@ -6,6 +6,7 @@ type
   PosixStream* = ref object of Stream
     fd*: cint
     isend*: bool
+    blocking*: bool
 
   ErrorAgain* = object of IOError
   ErrorBadFD* = object of IOError
@@ -33,32 +34,6 @@ proc raisePosixIOError*() =
   else:
     raise newException(IOError, $strerror(errno))
 
-proc psClose(s: Stream) =
-  let s = cast[PosixStream](s)
-  discard close(s.fd)
-
-proc psReadData(s: Stream, buffer: pointer, len: int): int =
-  assert len != 0
-  let s = cast[PosixStream](s)
-  let wasend = s.isend
-  let buffer = cast[ptr UncheckedArray[uint8]](buffer)
-  while result < len:
-    let n = read(s.fd, addr buffer[result], len - result)
-    if n < 0:
-      if result == 0:
-        result = n
-      break
-    elif n == 0:
-      s.isend = true
-      break
-    result += n
-  if result == 0:
-    if wasend:
-      raise newException(EOFError, "eof")
-    s.isend = true
-  if result == -1:
-    raisePosixIOError()
-
 method recvData*(s: PosixStream, buffer: pointer, len: int): int {.base.} =
   let n = read(s.fd, buffer, len)
   if n < 0:
@@ -69,6 +44,12 @@ method recvData*(s: PosixStream, buffer: pointer, len: int): int {.base.} =
     s.isend = true
   return n
 
+proc recvData*(s: PosixStream, buffer: var openArray[uint8]): int {.inline.} =
+  return s.recvData(addr buffer[0], buffer.len)
+
+proc recvData*(s: PosixStream, buffer: var openArray[char]): int {.inline.} =
+  return s.recvData(addr buffer[0], buffer.len)
+
 method sendData*(s: PosixStream, buffer: pointer, len: int): int {.base.} =
   #TODO use sendData instead
   let n = write(s.fd, buffer, len)
@@ -77,30 +58,42 @@ method sendData*(s: PosixStream, buffer: pointer, len: int): int {.base.} =
   return n
 
 method setBlocking*(s: PosixStream, blocking: bool) {.base.} =
+  s.blocking = blocking
   let ofl = fcntl(s.fd, F_GETFL, 0)
   if blocking:
     discard fcntl(s.fd, F_SETFL, ofl and not O_NONBLOCK)
   else:
     discard fcntl(s.fd, F_SETFL, ofl or O_NONBLOCK)
 
+method sclose*(s: PosixStream) {.base.} =
+  discard close(s.fd)
+
+proc psClose(s: Stream) =
+  PosixStream(s).sclose()
+
+proc psReadData(s: Stream, buffer: pointer, len: int): int =
+  let s = PosixStream(s)
+  assert len != 0 and s.blocking
+  return s.recvData(buffer, len)
+
 proc psWriteData(s: Stream, buffer: pointer, len: int) =
-  #TODO use sendData instead
-  let s = cast[PosixStream](s)
-  let res = write(s.fd, buffer, len)
-  if res == -1:
-    raisePosixIOError()
+  let s = PosixStream(s)
+  assert len != 0 and s.blocking
+  discard s.sendData(buffer, len)
 
 proc psAtEnd(s: Stream): bool =
-  return cast[PosixStream](s).isend
+  return PosixStream(s).isend
+
+proc addStreamIface*(ps: PosixStream) =
+  ps.closeImpl = cast[typeof(ps.closeImpl)](psClose)
+  ps.readDataImpl = cast[typeof(ps.readDataImpl)](psReadData)
+  ps.writeDataImpl = cast[typeof(ps.writeDataImpl)](psWriteData)
+  ps.atEndImpl = psAtEnd
 
 proc newPosixStream*(fd: FileHandle): PosixStream =
-  return PosixStream(
-    fd: fd,
-    closeImpl: psClose,
-    readDataImpl: psReadData,
-    writeDataImpl: psWriteData,
-    atEndImpl: psAtEnd
-  )
+  let ps = PosixStream(fd: fd, blocking: true)
+  ps.addStreamIface()
+  return ps
 
 proc newPosixStream*(path: string, flags, mode: cint): PosixStream =
   let fd = open(cstring(path), flags, mode)
diff --git a/src/io/socketstream.nim b/src/io/socketstream.nim
index dd391f21..99f7c2c9 100644
--- a/src/io/socketstream.nim
+++ b/src/io/socketstream.nim
@@ -1,7 +1,6 @@
 import std/nativesockets
 import std/net
 import std/os
-import std/streams
 
 when defined(posix):
   import std/posix
@@ -11,43 +10,6 @@ import io/serversocket
 
 type SocketStream* = ref object of PosixStream
   source*: Socket
-  blk*: bool
-
-proc sockReadData(s: Stream, buffer: pointer, len: int): int =
-  assert len != 0
-  let s = SocketStream(s)
-  let wasend = s.isend
-  let buffer = cast[ptr UncheckedArray[uint8]](buffer)
-  if s.blk:
-    while result < len:
-      let n = s.source.recv(addr buffer[result], len - result)
-      if n < 0:
-        if result == 0:
-          result = n
-        break
-      elif n == 0:
-        s.isend = true
-        break
-      result += n
-  else:
-    result = s.source.recv(buffer, len)
-  if result == 0:
-    if wasend:
-      raise newException(EOFError, "eof")
-    s.isend = true
-  if result < 0:
-    raisePosixIOError()
-  elif result == 0:
-    s.isend = true
-
-proc sockWriteData(s: Stream, buffer: pointer, len: int) =
-  var i = 0
-  let buffer = cast[ptr UncheckedArray[uint8]](buffer)
-  while i < len:
-    let n = SocketStream(s).source.send(addr buffer[i], len - i)
-    if n < 0:
-      raisePosixIOError()
-    i += n
 
 method recvData*(s: SocketStream, buffer: pointer, len: int): int =
   let n = s.source.recv(buffer, len)
@@ -65,49 +27,34 @@ method sendData*(s: SocketStream, buffer: pointer, len: int): int =
     raisePosixIOError()
   return n
 
-proc sockAtEnd(s: Stream): bool =
-  SocketStream(s).isend
-
-proc sockClose(s: Stream) = {.cast(tags: []).}: #...sigh
-  let s = SocketStream(s)
-  s.source.close()
-
 {.compile: "sendfd.c".}
-proc sendfd(sock: SocketHandle, fd: cint): int {.importc.}
+proc sendfd(sock, fd: cint): int {.importc.}
 
-# See https://stackoverflow.com/a/4491203
 proc sendFileHandle*(s: SocketStream, fd: FileHandle) =
   assert not s.source.hasDataBuffered
-  let n = sendfd(s.source.getFd(), cint(fd))
+  let n = sendfd(s.fd, cint(fd))
   if n < 0:
     raisePosixIOError()
   assert n == 1 # we send a single nul byte as buf
 
 {.compile: "recvfd.c".}
-proc recvfd(sock: SocketHandle, fdout: ptr cint): int {.importc.}
+proc recvfd(sock: cint, fdout: ptr cint): int {.importc.}
 
 proc recvFileHandle*(s: SocketStream): FileHandle =
   assert not s.source.hasDataBuffered
   var fd: cint
-  let n = recvfd(s.source.getFd(), addr fd)
+  let n = recvfd(s.fd, addr fd)
   if n < 0:
     raisePosixIOError()
   return FileHandle(fd)
 
-func newSocketStream*(): SocketStream =
-  return SocketStream(
-    readDataImpl: cast[proc (s: Stream, buffer: pointer, bufLen: int): int
-        {.nimcall, raises: [Defect, IOError, OSError], tags: [ReadIOEffect],
-        gcsafe.}
-    ](sockReadData), # ... ???
-    writeDataImpl: sockWriteData,
-    atEndImpl: sockAtEnd,
-    closeImpl: sockClose
-  )
-
 method setBlocking*(s: SocketStream, blocking: bool) =
+  s.blocking = blocking
   s.source.getFd().setBlocking(blocking)
 
+method sclose*(s: SocketStream) =
+  s.source.close()
+
 # see serversocket.nim for an explanation
 {.compile: "connect_unix.c".}
 proc connect_unix_from_c(fd: cint, path: cstring, pathlen: cint): cint
@@ -115,8 +62,6 @@ proc connect_unix_from_c(fd: cint, path: cstring, pathlen: cint): cint
 
 proc connectSocketStream*(path: string, buffered = true, blocking = true):
     SocketStream =
-  result = newSocketStream()
-  result.blk = blocking
   let sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM,
     Protocol.IPPROTO_IP, buffered)
   if not blocking:
@@ -124,22 +69,28 @@ proc connectSocketStream*(path: string, buffered = true, blocking = true):
   if connect_unix_from_c(cint(sock.getFd()), cstring(path),
       cint(path.len)) != 0:
     raiseOSError(osLastError())
-  result.source = sock
-  result.fd = cint(sock.getFd())
+  result = SocketStream(
+    source: sock,
+    fd: cint(sock.getFd()),
+    blocking: blocking
+  )
+  result.addStreamIface()
 
 proc connectSocketStream*(pid: Pid, buffered = true, blocking = true):
     SocketStream =
   try:
-    connectSocketStream(getSocketPath(pid), buffered, blocking)
+    return connectSocketStream(getSocketPath(pid), buffered, blocking)
   except OSError:
     return nil
 
 proc acceptSocketStream*(ssock: ServerSocket, blocking = true): SocketStream =
-  result = newSocketStream()
-  result.blk = blocking
   var sock: Socket
   ssock.sock.accept(sock, inheritable = true)
-  result.source = sock
   if not blocking:
     sock.getFd().setBlocking(false)
-  result.fd = cint(sock.getFd())
+  result = SocketStream(
+    blocking: blocking,
+    source: sock,
+    fd: cint(sock.getFd())
+  )
+  result.addStreamIface()
diff --git a/src/loader/loader.nim b/src/loader/loader.nim
index 11ddefcc..f2d38d61 100644
--- a/src/loader/loader.nim
+++ b/src/loader/loader.nim
@@ -608,7 +608,7 @@ proc fetch*(loader: FileLoader, input: Request): FetchPromise =
   stream.swrite(LOAD)
   stream.swrite(input)
   stream.flush()
-  let fd = int(stream.source.getFd())
+  let fd = int(stream.fd)
   loader.registerFun(fd)
   let promise = FetchPromise()
   loader.connecting[fd] = ConnectData(
@@ -624,7 +624,7 @@ proc reconnect*(loader: FileLoader, data: ConnectData) =
   stream.swrite(LOAD)
   stream.swrite(data.request)
   stream.flush()
-  let fd = int(stream.source.getFd())
+  let fd = int(stream.fd)
   loader.registerFun(fd)
   loader.connecting[fd] = ConnectData(
     promise: data.promise,
@@ -638,7 +638,7 @@ proc switchStream*(data: var ConnectData, stream: SocketStream) =
 proc switchStream*(loader: FileLoader, data: var OngoingData,
     stream: SocketStream) =
   data.response.body = stream
-  let fd = int(stream.source.getFd())
+  let fd = int(stream.fd)
   let realCloseImpl = stream.closeImpl
   stream.closeImpl = nil
   data.response.unregisterFun = proc() =
@@ -704,7 +704,7 @@ proc onConnected*(loader: FileLoader, fd: int) =
       response: response,
       bodyRead: response.bodyRead
     )
-    stream.source.getFd().setBlocking(false)
+    stream.setBlocking(false)
     promise.resolve(JSResult[Response].ok(response))
   else:
     var msg: string
@@ -724,7 +724,7 @@ proc onRead*(loader: FileLoader, fd: int) =
       let olen = buffer[].buf.len
       try:
         buffer[].buf.setLen(olen + BufferSize)
-        let n = response.body.readData(addr buffer[].buf[olen], BufferSize)
+        let n = response.body.recvData(addr buffer[].buf[olen], BufferSize)
         buffer[].buf.setLen(olen + n)
         if n == 0:
           break
@@ -743,7 +743,7 @@ proc onError*(loader: FileLoader, fd: int) =
     when defined(debug):
       var lbuf {.noinit.}: array[BufferSize, char]
       if not response.body.atEnd():
-        let n = response.body.readData(addr lbuf[0], lbuf.len)
+        let n = response.body.recvData(addr lbuf[0], lbuf.len)
         assert n == 0
       assert response.body.atEnd()
     buffer[].bodyRead.resolve(buffer[].buf)
diff --git a/src/loader/request.nim b/src/loader/request.nim
index fcad1681..1d0e31cd 100644
--- a/src/loader/request.nim
+++ b/src/loader/request.nim
@@ -1,5 +1,4 @@
 import std/options
-import std/streams
 import std/strutils
 import std/tables
 
@@ -84,11 +83,6 @@ type
     fromcache*: bool
     clientId*: StreamId
 
-  ReadableStream* = ref object of Stream
-    isource*: Stream
-    buf: string
-    isend: bool
-
 jsDestructor(Request)
 
 proc js_url(this: Request): string {.jsfget: "url".} =
@@ -105,62 +99,6 @@ iterator pairs*(headers: Headers): (string, string) =
     for v in vs:
       yield (k, v)
 
-proc rsReadData(s: Stream, buffer: pointer, bufLen: int): int =
-  var s = ReadableStream(s)
-  if s.atEnd:
-    return 0
-  while s.buf.len < bufLen:
-    var len: int
-    s.isource.read(len)
-    if len == 0:
-      result = s.buf.len
-      copyMem(buffer, addr(s.buf[0]), result)
-      s.buf = s.buf.substr(result)
-      s.isend = true
-      return
-    var nbuf: string
-    s.isource.readStr(len, nbuf)
-    s.buf &= nbuf
-  assert s.buf.len >= bufLen
-  result = bufLen
-  copyMem(buffer, addr(s.buf[0]), result)
-  s.buf = s.buf.substr(result)
-  if s.buf.len == 0:
-    var len: int
-    s.isource.read(len)
-    if len == 0:
-      s.isend = true
-    else:
-      s.isource.readStr(len, s.buf)
-
-proc rsAtEnd(s: Stream): bool =
-  ReadableStream(s).isend
-
-proc rsClose(s: Stream) = {.cast(tags: [WriteIOEffect]).}: #TODO TODO TODO ew.
-  var s = ReadableStream(s)
-  if s.isend: return
-  s.buf = ""
-  while true:
-    var len: int
-    s.isource.read(len)
-    if len == 0:
-      s.isend = true
-      break
-    s.isource.setPosition(s.isource.getPosition() + len)
-
-proc newReadableStream*(isource: Stream): ReadableStream =
-  var len: int
-  isource.read(len)
-  result = ReadableStream(
-    isource: isource,
-    readDataImpl: rsReadData,
-    atEndImpl: rsAtEnd,
-    closeImpl: rsClose,
-    isend: len == 0
-  )
-  if len != 0:
-    isource.readStr(len, result.buf)
-
 func newRequest*(url: URL, httpMethod = HTTP_GET, headers = newHeaders(),
     body = opt(string), multipart = opt(FormData), mode = RequestMode.NO_CORS,
     credentialsMode = CredentialsMode.SAME_ORIGIN,
diff --git a/src/local/client.nim b/src/local/client.nim
index 20e1a6ef..2912b7ef 100644
--- a/src/local/client.nim
+++ b/src/local/client.nim
@@ -314,10 +314,10 @@ proc consoleBuffer(client: Client): Container {.jsfget.} =
 proc acceptBuffers(client: Client) =
   while client.pager.unreg.len > 0:
     let (pid, stream) = client.pager.unreg.pop()
-    let fd = stream.source.getFd()
-    if int(fd) in client.fdmap:
+    let fd = int(stream.fd)
+    if fd in client.fdmap:
       client.selector.unregister(fd)
-      client.fdmap.del(int(fd))
+      client.fdmap.del(fd)
     else:
       client.pager.procmap.del(pid)
     stream.close()
@@ -328,8 +328,8 @@ proc acceptBuffers(client: Client) =
       client.pager.alert("Error: failed to set up buffer")
       continue
     container.setStream(stream)
-    let fd = stream.source.getFd()
-    client.fdmap[int(fd)] = container
+    let fd = int(stream.fd)
+    client.fdmap[fd] = container
     client.selector.registerHandle(fd, {Read}, 0)
     client.pager.handleEvents(container)
     accepted.add(pid)
@@ -344,22 +344,33 @@ proc handleRead(client: Client, fd: int) =
       client.handlePagerEvents()
     )
   elif fd == client.forkserver.estream.fd:
-    var nl = false
+    const BufferSize = 4096
     const prefix = "STDERR: "
-    var s = prefix
+    var buffer {.noinit.}: array[BufferSize, char]
+    let estream = client.forkserver.estream
+    var hadlf = true
     while true:
       try:
-        let c = client.forkserver.estream.readChar()
-        if nl and s.len > prefix.len:
-          client.console.err.write(s)
-          s = prefix
-          nl = false
-        s &= c
-        nl = c == '\n'
-      except IOError:
+        let n = estream.recvData(addr buffer[0], BufferSize)
+        var i = 0
+        while i < n:
+          var j = n
+          var found = false
+          for k in i ..< n:
+            if buffer[k] == '\n':
+              j = k + 1
+              found = true
+              break
+          if hadlf:
+            client.console.err.write(prefix)
+          if j - i > 0:
+            client.console.err.writeData(addr buffer[i], j - i)
+          i = j
+          hadlf = found
+      except ErrorAgain:
         break
-    if s.len > prefix.len:
-      client.console.err.write(s)
+    if not hadlf:
+      client.console.err.write('\n')
     client.console.err.flush()
   elif fd in client.loader.connecting:
     client.loader.onConnected(fd)
diff --git a/src/local/container.nim b/src/local/container.nim
index 9ad69cc4..d6db15c0 100644
--- a/src/local/container.nim
+++ b/src/local/container.nim
@@ -20,7 +20,6 @@ import loader/request
 import local/select
 import server/buffer
 import server/forkserver
-import types/buffersource
 import types/cell
 import types/color
 import types/cookie
diff --git a/src/local/pager.nim b/src/local/pager.nim
index 3cdd0226..a6c6c9ad 100644
--- a/src/local/pager.nim
+++ b/src/local/pager.nim
@@ -31,8 +31,8 @@ import loader/loader
 import loader/request
 import local/container
 import local/select
+import server/buffer
 import server/forkserver
-import types/buffersource
 import types/cell
 import types/color
 import types/cookie
diff --git a/src/render/rendertext.nim b/src/render/rendertext.nim
index 54f2378a..56a0b2ba 100644
--- a/src/render/rendertext.nim
+++ b/src/render/rendertext.nim
@@ -23,7 +23,7 @@ type StreamRenderer* = object
 
 #TODO pass bool for whether we can rewind
 proc newStreamRenderer*(stream: Stream, charsets0: openArray[Charset]):
-    StreamRenderer =
+    ref StreamRenderer =
   var charsets = newSeq[Charset](charsets0.len)
   for i in 0 ..< charsets.len:
     charsets[i] = charsets0[charsets.high - i]
@@ -37,7 +37,7 @@ proc newStreamRenderer*(stream: Stream, charsets0: openArray[Charset]):
   let decoder = newDecoderStream(stream, cs, errormode = em)
   decoder.setInhibitCheckEnd(true)
   let encoder = newEncoderStream(decoder)
-  return StreamRenderer(
+  return (ref StreamRenderer)(
     stream: stream,
     decoder: decoder,
     encoder: encoder,
@@ -169,3 +169,10 @@ proc renderStream*(grid: var FlexibleGrid, renderer: var StreamRenderer): bool =
     grid.addLine()
   grid.renderChunk(renderer, buf)
   return true
+
+proc finishRender*(grid: var FlexibleGrid, renderer: var StreamRenderer) =
+  renderer.decoder.setInhibitCheckEnd(false)
+  let buf = renderer.decoder.readAll()
+  if grid.len == 0:
+    grid.addLine()
+  grid.renderChunk(renderer, buf)
diff --git a/src/server/buffer.nim b/src/server/buffer.nim
index cb66e9ef..3c6dfa21 100644
--- a/src/server/buffer.nim
+++ b/src/server/buffer.nim
@@ -40,7 +40,6 @@ import loader/headers
 import loader/loader
 import render/renderdocument
 import render/rendertext
-import types/buffersource
 import types/cell
 import types/color
 import types/cookie
@@ -57,8 +56,10 @@ import chakasu/charset
 import chame/tags
 
 type
-  LoadInfo* = enum
-    CONNECT, DOWNLOAD, RENDER, DONE
+  BufferSource* = object
+    contentType*: Option[string] # override
+    charset*: Charset # fallback
+    request*: Request
 
   BufferCommand* = enum
     LOAD, RENDER, WINDOW_CHANGE, FIND_ANCHOR, READ_SUCCESS, READ_CANCELED,
@@ -71,7 +72,7 @@ type
   # LOADING_PAGE: istream open
   # LOADING_RESOURCES: istream closed, resources open
   # LOADED: istream closed, resources closed
-  BufferState* = enum
+  BufferState = enum
     LOADING_PAGE, LOADING_RESOURCES, LOADED
 
   HoverType* = enum
@@ -88,12 +89,14 @@ type
     rfd: int # file descriptor of command pipe
     fd: int # file descriptor of buffer source
     url: URL # URL before readFromFd
+    pstream: SocketStream # control stream
     alive: bool
+    connected: bool
+    savetask: bool
+    ishtml: bool
+    firstBufferRead: bool
     lines: FlexibleGrid
-    rendered: bool
     source: BufferSource
-    width: int
-    height: int
     attrs: WindowAttributes
     window: Window
     document: Document
@@ -102,25 +105,20 @@ type
     istream: SocketStream
     sstream: StringStream
     available: int
-    pstream: SocketStream # pipe stream
-    srenderer: StreamRenderer
-    connected: bool
     state: BufferState
     prevnode: StyledNode
     loader: FileLoader
     config: BufferConfig
-    userstyle: CSSStylesheet
     tasks: array[BufferCommand, int] #TODO this should have arguments
-    savetask: bool
     hovertext: array[HoverType, string]
     estream: Stream # error stream
-    ishtml: bool
     ssock: ServerSocket
     factory: CAtomFactory
     uastyle: CSSStylesheet
     quirkstyle: CSSStylesheet
+    userstyle: CSSStylesheet
     htmlParser: HTML5ParserWrapper
-    firstBufferRead: bool
+    srenderer: ref StreamRenderer
 
   InterfaceOpaque = ref object
     stream: Stream
@@ -637,12 +635,10 @@ proc processData(buffer: Buffer): bool =
     buffer.document = buffer.htmlParser.builder.document
     return res
   else:
-    return buffer.lines.renderStream(buffer.srenderer)
+    return buffer.lines.renderStream(buffer.srenderer[])
 
 proc windowChange*(buffer: Buffer, attrs: WindowAttributes) {.proxy.} =
   buffer.attrs = attrs
-  buffer.width = buffer.attrs.width
-  buffer.height = buffer.attrs.height - 1
   buffer.prevstyled = nil
   if buffer.window != nil:
     buffer.window.attrs = attrs
@@ -905,10 +901,9 @@ proc clone*(buffer: Buffer, newurl: URL): Pid {.proxy.} =
       let stream = buffer.loader.tee((parentPid, fd))
       var success: bool
       stream.sread(success)
-      let sfd = int(stream.source.getFd())
       if success:
         switchStream(buffer.loader.connecting[fd], stream)
-        buffer.loader.connecting[sfd] = buffer.loader.connecting[fd]
+        buffer.loader.connecting[stream.fd] = buffer.loader.connecting[fd]
       else:
         # Unlikely, but theoretically possible: our SUSPEND connection
         # finished before the connection could have been completed.
@@ -923,10 +918,9 @@ proc clone*(buffer: Buffer, newurl: URL): Pid {.proxy.} =
       let stream = buffer.loader.tee((parentPid, fd))
       var success: bool
       stream.sread(success)
-      let sfd = int(stream.source.getFd())
       if success:
         buffer.loader.switchStream(buffer.loader.ongoing[fd], stream)
-        buffer.loader.ongoing[sfd] = buffer.loader.ongoing[fd]
+        buffer.loader.ongoing[stream.fd] = buffer.loader.ongoing[fd]
       else:
         # Already finished.
         #TODO what to do?
@@ -945,7 +939,7 @@ proc clone*(buffer: Buffer, newurl: URL): Pid {.proxy.} =
       it = 0
     let socks = ssock.acceptSocketStream()
     buffer.pstream = socks
-    buffer.rfd = int(socks.source.getFd())
+    buffer.rfd = socks.fd
     buffer.selector.registerHandle(buffer.rfd, {Read}, 0)
     return 0
   else: # parent
@@ -1094,7 +1088,7 @@ proc onload(buffer: Buffer) =
       var n = 0
       if not reprocess:
         buffer.sstream.data.prepareMutation()
-        n = buffer.istream.readData(addr buffer.sstream.data[0], BufferSize)
+        n = buffer.istream.recvData(addr buffer.sstream.data[0], BufferSize)
         if n != buffer.sstream.data.len:
           buffer.sstream.data.setLen(n)
       if n != 0 or reprocess:
@@ -1120,6 +1114,8 @@ proc onload(buffer: Buffer) =
           buffer.state = LOADED
           if buffer.document != nil: # may be nil if not buffer.ishtml
             buffer.document.readyState = READY_STATE_COMPLETE
+          if not buffer.ishtml:
+            buffer.lines.finishRender(buffer.srenderer[])
           buffer.dispatchLoadEvent()
           buffer.resolveTask(LOAD, res)
         )
@@ -1765,12 +1761,10 @@ proc launchBuffer*(config: BufferConfig, source: BufferSource,
     loader: loader,
     source: source,
     sstream: newStringStream(),
-    width: attrs.width,
-    height: attrs.height - 1,
     selector: newSelector[int](),
     estream: newFileStream(stderr),
     pstream: socks,
-    rfd: int(socks.source.getFd()),
+    rfd: socks.fd,
     ssock: ssock
   )
   gbuffer = buffer
diff --git a/src/server/forkserver.nim b/src/server/forkserver.nim
index f788aa24..6fa95ab6 100644
--- a/src/server/forkserver.nim
+++ b/src/server/forkserver.nim
@@ -13,7 +13,6 @@ import io/urlfilter
 import loader/headers
 import loader/loader
 import server/buffer
-import types/buffersource
 import types/cookie
 import types/urimethodmap
 import types/url
@@ -288,9 +287,10 @@ proc newForkServer*(): ForkServer =
       raise newException(Defect, "Failed to open output handle")
     if not open(readf, pipefd_out[0], fmRead):
       raise newException(Defect, "Failed to open input handle")
-    discard fcntl(pipefd_err[0], F_SETFL, fcntl(pipefd_err[0], F_GETFL, 0) or O_NONBLOCK)
+    let estream = newPosixStream(pipefd_err[0])
+    estream.setBlocking(false)
     return ForkServer(
       ostream: newFileStream(writef),
       istream: newFileStream(readf),
-      estream: newPosixStream(pipefd_err[0])
+      estream: estream
     )
diff --git a/src/types/buffersource.nim b/src/types/buffersource.nim
deleted file mode 100644
index 60fd7137..00000000
--- a/src/types/buffersource.nim
+++ /dev/null
@@ -1,11 +0,0 @@
-import std/options
-
-import loader/request
-
-import chakasu/charset
-
-type
-  BufferSource* = object
-    contentType*: Option[string] # override
-    charset*: Charset # fallback
-    request*: Request
diff --git a/src/types/formdata.nim b/src/types/formdata.nim
index 02a08d15..29817e54 100644
--- a/src/types/formdata.nim
+++ b/src/types/formdata.nim
@@ -79,8 +79,11 @@ proc writeEntry*(stream: Stream, entry: FormDataEntry, boundary: string) =
         var buf {.noinit.}: array[4096, uint8]
         while true:
           let n = fs.readData(addr buf[0], 4096)
+          if n == 0:
+            break
           stream.writeData(addr buf[0], n)
-          if n != 4096: break
+          if n < buf.len:
+            break
     else:
       stream.writeData(blob.buffer, int(blob.size))
     stream.write("\r\n")