about summary refs log tree commit diff stats
path: root/src/io
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2022-09-12 00:30:21 +0200
committerbptato <nincsnevem662@gmail.com>2022-09-12 00:30:21 +0200
commit51ea622d58bfca19212fac1800cfb033bb85ec39 (patch)
treeb75891690f67b190c60584751f2a30c96f342fdc /src/io
parente38402dfa1bbc33db6b9d9736517eb45533d595c (diff)
downloadchawan-51ea622d58bfca19212fac1800cfb033bb85ec39.tar.gz
Add JS binding generation
Diffstat (limited to 'src/io')
-rw-r--r--src/io/buffer.nim47
-rw-r--r--src/io/lineedit.nim6
-rw-r--r--src/io/loader.nim83
-rw-r--r--src/io/process.nim22
-rw-r--r--src/io/socketstream.nim35
-rw-r--r--src/io/term.nim30
6 files changed, 131 insertions, 92 deletions
diff --git a/src/io/buffer.nim b/src/io/buffer.nim
index 52a55819..8b1268ef 100644
--- a/src/io/buffer.nim
+++ b/src/io/buffer.nim
@@ -931,12 +931,12 @@ proc displayStatusMessage*(buffer: Buffer) =
   print(SGR())
 
 # https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#constructing-the-form-data-set
-proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encoding: string = ""): Table[string, string] =
+proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encoding: string = ""): seq[tuple[name, value: string]] =
   if form.constructingentrylist:
     return
   form.constructingentrylist = true
 
-  var entrylist: Table[string, string]
+  var entrylist: seq[tuple[name, value: string]]
   for field in form.controls:
     if field.findAncestor({TAG_DATALIST}) != nil or
         field.attrb("disabled") or
@@ -950,8 +950,8 @@ proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encodin
           field.attr("name") & '.'
         else:
           ""
-        entrylist[name & 'x'] = $field.xcoord
-        entrylist[name & 'y'] = $field.ycoord
+        entrylist.add((name & 'x', $field.xcoord))
+        entrylist.add((name & 'y', $field.ycoord))
         continue
 
     #TODO custom elements
@@ -965,13 +965,13 @@ proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encodin
       let field = HTMLSelectElement(field)
       for option in field.options:
         if option.selected or option.disabled:
-          entrylist[name] = option.value
+          entrylist.add((name, option.value))
     elif field.tagType == TAG_INPUT and HTMLInputElement(field).inputType in {INPUT_CHECKBOX, INPUT_RADIO}:
       let value = if field.attr("value") != "":
         field.attr("value")
       else:
         "on"
-      entrylist[name] = value
+      entrylist.add((name, value))
     elif field.tagType == TAG_INPUT and HTMLInputElement(field).inputType == INPUT_FILE:
       #TODO file
       discard
@@ -980,10 +980,10 @@ proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encodin
         encoding
       else:
         "UTF-8"
-      entrylist[name] = charset
+      entrylist.add((name, charset))
     else:
       if field.tagType == TAG_INPUT:
-        entrylist[name] = HTMLInputElement(field).value
+        entrylist.add((name, HTMLInputElement(field).value))
       else:
         assert false
     if field.tagType == TAG_TEXTAREA or
@@ -991,20 +991,11 @@ proc constructEntryList(form: HTMLFormElement, submitter: Element = nil, encodin
       if field.attr("dirname") != "":
         let dirname = field.attr("dirname")
         let dir = "ltr" #TODO bidi
-        entrylist[dirname] = dir
+        entrylist.add((dirname, dir))
 
   form.constructingentrylist = false
   return entrylist
 
-#https://url.spec.whatwg.org/#concept-urlencoded-serializer
-proc serializeApplicationXWWFormUrlEncoded(kvs: Table[string, string]): string =
-  for name, value in kvs:
-    if result != "":
-      result &= '&'
-    result.percentEncode(name, ApplicationXWWWFormUrlEncodedSet, true)
-    result &= '='
-    result.percentEncode(value, ApplicationXWWWFormUrlEncodedSet, true)
-
 #https://html.spec.whatwg.org/multipage/form-control-infrastructure.html#multipart/form-data-encoding-algorithm
 proc makeCRLF(s: string): string =
   result = newStringOfCap(s.len)
@@ -1022,19 +1013,19 @@ proc makeCRLF(s: string): string =
       result &= s[i]
     inc i
 
-proc serializeMultipartFormData(kvs: Table[string, string]): MimeData =
-  for name, value in kvs:
-    let name = makeCRLF(name)
-    let value = makeCRLF(value)
+proc serializeMultipartFormData(kvs: seq[(string, string)]): MimeData =
+  for it in kvs:
+    let name = makeCRLF(it[0])
+    let value = makeCRLF(it[1])
     result[name] = value
 
-proc serializePlainTextFormData(kvs: Table[string, string]): string =
-  for name, value in kvs:
+proc serializePlainTextFormData(kvs: seq[(string, string)]): string =
+  for it in kvs:
+    let (name, value) = it
     result &= name
     result &= '='
     result &= value
-    result &= '\r'
-    result &= '\n'
+    result &= "\r\n"
 
 proc submitForm(form: HTMLFormElement, submitter: Element): Option[Request] =
   let entrylist = form.constructEntryList(submitter)
@@ -1068,7 +1059,7 @@ proc submitForm(form: HTMLFormElement, submitter: Element): Option[Request] =
   #let noopener = true #TODO
 
   template mutateActionUrl() =
-    let query = serializeApplicationXWWFormUrlEncoded(entrylist)
+    let query = serializeApplicationXWWWFormUrlEncoded(entrylist)
     parsedaction.query = query.some
     return newRequest(parsedaction, httpmethod).some
 
@@ -1078,7 +1069,7 @@ proc submitForm(form: HTMLFormElement, submitter: Element): Option[Request] =
     var multipart = none(MimeData)
     case enctype
     of FORM_ENCODING_TYPE_URLENCODED:
-      body = serializeApplicationXWWFormUrlEncoded(entrylist).some
+      body = serializeApplicationXWWWFormUrlEncoded(entrylist).some
       mimeType = $enctype
     of FORM_ENCODING_TYPE_MULTIPART:
       multipart = serializeMultipartFormData(entrylist).some
diff --git a/src/io/lineedit.nim b/src/io/lineedit.nim
index 5c756625..def2feb2 100644
--- a/src/io/lineedit.nim
+++ b/src/io/lineedit.nim
@@ -4,8 +4,9 @@ import strutils
 import sequtils
 import sugar
 
-import utils/twtstr
 import config/config
+import io/term
+import utils/twtstr
 
 type LineState* = object
   news*: seq[Rune]
@@ -143,7 +144,8 @@ proc readLine(state: var LineState): bool =
     else:
       state.feedNext = false
 
-    let c = getch()
+    restoreStdin()
+    let c = stdin.readChar()
     state.s &= c
 
     var action = getLinedAction(state.s)
diff --git a/src/io/loader.nim b/src/io/loader.nim
index 31b51930..7c68258e 100644
--- a/src/io/loader.nim
+++ b/src/io/loader.nim
@@ -15,15 +15,16 @@ import options
 import streams
 import tables
 import net
-import os
 when defined(posix):
   import posix
 
 import bindings/curl
 import io/http
+import io/process
 import io/request
 import io/serialize
 import io/socketstream
+import types/mime
 import types/url
 import utils/twtstr
 
@@ -75,88 +76,80 @@ proc loadResource(loader: FileLoader, request: Request, ostream: Stream) =
     ostream.swrite(-1) # error
     ostream.flush()
 
-const SocketDirectory = "/tmp/cha/"
-const SocketPathPrefix = SocketDirectory & "cha_sock_"
-func getSocketPath(pid: Pid): string =
-  SocketPathPrefix & $pid
-
-proc runFileLoader(loader: FileLoader) =
+proc runFileLoader(loader: FileLoader, loadcb: proc()) =
   if curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK:
     raise newException(Defect, "Failed to initialize libcurl.")
-  let path = getSocketPath(getpid())
-  discard unlink(cstring(path))
-  createDir(SocketDirectory)
-  let sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP)
-  bindUnix(sock, path)
-  listen(sock)
-  stdout.write(char(0u8))
-  stdout.flushFile()
+  let ssock = initServerSocket(getpid())
+  # The server has been initialized, so the main process can resume execution.
+  loadcb()
   while true:
-    var sock2: Socket
-    sock.accept(sock2)
-    let istream = newSocketStream(sock2, nil)
-    let ostream = newSocketStream(nil, sock2)
+    let stream = ssock.acceptSocketStream()
     try:
-      let request = istream.readRequest()
+      let request = stream.readRequest()
       for k, v in loader.defaultHeaders.table:
         if k notin request.headers.table:
           request.headers.table[k] = v
-      loader.loadResource(request, ostream)
+      loader.loadResource(request, stream)
     except IOError:
       # End-of-file, quit.
       # TODO this should be EOFError
       break
-    close(sock2)
+    stream.close()
   curl_global_cleanup()
-  close(sock)
-  discard unlink(cstring(path))
+  ssock.close()
   quit(0)
 
 proc doRequest*(loader: FileLoader, request: Request): Response =
-  let sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP)
-  let path = getSocketPath(loader.process)
-  connectUnix(sock, path)
-  let istream = newSocketStream(nil, sock)
-  let ostream = newSocketStream(sock, nil)
-  istream.swrite(request)
-  istream.flush()
-  ostream.sread(result.res)
+  let stream = connectSocketStream(loader.process)
+  stream.swrite(request)
+  stream.flush()
+  stream.sread(result.res)
   if result.res == 0:
-    ostream.sread(result.status)
-    ostream.sread(result.headers)
+    stream.sread(result.status)
+    stream.sread(result.headers)
     if "Content-Type" in result.headers.table:
       result.contenttype = result.headers.table["Content-Type"][0].until(';')
+    else:
+      result.contenttype = guessContentType($request.url)
     if "Location" in result.headers.table:
       let location = result.headers.table["Location"][0]
       result.redirect = parseUrl(location, some(request.url))
     # Only a stream of the response body may arrive after this point.
-    result.body = ostream
+    result.body = stream
 
 proc newFileLoader*(defaultHeaders: HeaderList): FileLoader =
   new(result)
   result.defaultHeaders = defaultHeaders
   when defined(posix):
-    var pipefd_b: array[0..1, cint]
-    if pipe(pipefd_b) == -1:
+    var pipefd: array[0..1, cint]
+    if pipe(pipefd) == -1:
       raise newException(Defect, "Failed to open pipe.")
     let pid = fork()
     if pid == -1:
       raise newException(Defect, "Failed to fork network process")
     elif pid == 0:
       # child process
-      let writefd = pipefd_b[1] # get write b
-      discard close(pipefd_b[0]) # close read b
-      discard dup2(writefd, stdout.getFileHandle())
-      result.runFileLoader()
+      discard close(pipefd[0]) # close read
+      var writef: File
+      if not open(writef, FileHandle(pipefd[1]), fmWrite):
+        raise newException(Defect, "Failed to open input handle.")
+      result.runFileLoader((proc() =
+        writef.write(char(0u8))
+        writef.flushFile()
+        close(writef)
+        discard close(pipefd[1])
+      ))
     else:
       result.process = pid
-      let readfd = pipefd_b[0] # get read b
-      discard close(pipefd_b[1]) # close write b
+      let readfd = pipefd[0] # get read
+      discard close(pipefd[1]) # close write
       var readf: File
       if not open(readf, FileHandle(readfd), fmRead):
         raise newException(Defect, "Failed to open output handle.")
-      var n: uint8
-      assert newFileStream(readf).readUint8() == 0u8
+      assert readf.readChar() == char(0u8)
+      close(readf)
+      discard close(pipefd[0])
+      
 
 proc newFileLoader*(): FileLoader =
   newFileLoader(DefaultHeaders)
diff --git a/src/io/process.nim b/src/io/process.nim
index e081ecc6..ffdf5d91 100644
--- a/src/io/process.nim
+++ b/src/io/process.nim
@@ -1,6 +1,12 @@
+import net
+import os
 when defined(posix):
   import posix
 
+type ServerSocket* = object
+  sock*: Socket
+  path*: string
+
 proc doFork*(): Pid =
   result = fork()
   if result == -1:
@@ -14,3 +20,19 @@ proc doFork*(): Pid =
     quit(0)
   return 0
 
+const SocketDirectory = "/tmp/cha/"
+const SocketPathPrefix = SocketDirectory & "cha_sock_"
+func getSocketPath*(pid: Pid): string =
+  SocketPathPrefix & $pid
+
+proc initServerSocket*(pid: Pid): ServerSocket =
+  createDir(SocketDirectory)
+  result.sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP)
+  result.path = getSocketPath(getpid())
+  discard unlink(cstring(result.path))
+  bindUnix(result.sock, result.path)
+  listen(result.sock)
+
+proc close*(ssock: ServerSocket) =
+  close(ssock.sock)
+  discard unlink(cstring(ssock.path))
diff --git a/src/io/socketstream.nim b/src/io/socketstream.nim
index 73a00aa2..d7f43625 100644
--- a/src/io/socketstream.nim
+++ b/src/io/socketstream.nim
@@ -1,37 +1,50 @@
 import net
 import streams
+when defined(posix):
+  import posix
+
+import io/process
 
 type SocketStream* = ref object of Stream
-  isource: Socket
-  osource: Socket
+  source: Socket
   isend: bool
 
 proc sockReadData(s: Stream, buffer: pointer, len: int): int =
   let s = SocketStream(s)
-  result = s.isource.recv(buffer, len)
+  result = s.source.recv(buffer, len)
   if result < 0:
     raise newException(Defect, "Failed to read data")
   elif result < len:
     s.isend = true
 
 proc sockWriteData(s: Stream, buffer: pointer, len: int) =
-  discard SocketStream(s).osource.send(buffer, len)
+  discard SocketStream(s).source.send(buffer, len)
 
 proc sockAtEnd(s: Stream): bool =
   SocketStream(s).isend
 
 proc sockClose(s: Stream) = {.cast(tags: []).}: #...sigh
   let s = SocketStream(s)
-  if s.isource != nil:
-    s.isource.close()
-  if s.osource != nil and s.isource != s.osource:
-    s.osource.close()
+  s.source.close()
 
-func newSocketStream*(isource, osource: Socket): SocketStream =
+func newSocketStream*(): SocketStream =
   new(result)
-  result.isource = isource
-  result.osource = osource
   result.readDataImpl = sockReadData
   result.writeDataImpl = sockWriteData
   result.atEndImpl = sockAtEnd
   result.closeImpl = sockClose
+
+proc connectSocketStream*(path: string): SocketStream =
+  result = newSocketStream()
+  let sock = newSocket(Domain.AF_UNIX, SockType.SOCK_STREAM, Protocol.IPPROTO_IP)
+  connectUnix(sock, path)
+  result.source = sock
+
+proc connectSocketStream*(pid: Pid): SocketStream =
+  connectSocketStream(getSocketPath(pid))
+
+proc acceptSocketStream*(ssock: ServerSocket): SocketStream =
+  result = newSocketStream()
+  var sock: Socket
+  ssock.sock.accept(sock)
+  result.source = sock
diff --git a/src/io/term.nim b/src/io/term.nim
index 914b5398..d9609715 100644
--- a/src/io/term.nim
+++ b/src/io/term.nim
@@ -1,7 +1,6 @@
 import terminal
+
 import std/exitprocs
-when defined(posix):
-  import termios
 
 type
   TermAttributes* = object
@@ -14,6 +13,9 @@ type
     height_px*: int
 
 when defined(posix):
+  import posix
+  import termios
+
   # see https://viewsourcecode.org/snaptoken/kilo/02.enteringRawMode.html
   let stdin_fileno = stdin.getFileHandle()
   var orig_termios: Termios
@@ -27,12 +29,28 @@ when defined(posix):
     raw.c_iflag = raw.c_iflag and not (BRKINT or ICRNL or INPCK or ISTRIP or IXON)
     raw.c_oflag = raw.c_oflag and not (OPOST)
     raw.c_cflag = raw.c_cflag or CS8
-    # we do not currently set ISIG, so that ctrl+c can be used to
-    # immediately return to the input loop.
-    #TODO set it once we have separated i/o from layout
-    raw.c_lflag = raw.c_lflag and not (ECHO or ICANON or IEXTEN)
+    raw.c_lflag = raw.c_lflag and not (ECHO or ICANON or ISIG or IEXTEN)
     discard tcSetAttr(stdin_fileno, TCSAFLUSH, addr raw)
 
+  var orig_flags: cint
+  var stdin_unblocked = false
+  proc unblockStdin*() =
+    orig_flags = fcntl(getFileHandle(stdin), F_GETFL, 0)
+    let flags = orig_flags or O_NONBLOCK
+    discard fcntl(getFileHandle(stdin), F_SETFL, flags)
+    stdin_unblocked = true
+
+  proc restoreStdin*() =
+    if stdin_unblocked:
+      discard fcntl(getFileHandle(stdin), F_SETFL, orig_flags)
+      stdin_unblocked = false
+else:
+  proc unblockStdin*(): cint =
+    discard
+
+  proc restoreStdin*(flags: cint) =
+    discard
+
 proc getTermAttributes*(): TermAttributes =
   if stdin.isatty():
     when defined(posix):