about summary refs log tree commit diff stats
path: root/adapter
diff options
context:
space:
mode:
authorbptato <nincsnevem662@gmail.com>2023-12-12 21:36:30 +0100
committerbptato <nincsnevem662@gmail.com>2023-12-12 21:39:42 +0100
commit1df10adfe7eb20bc460eeb0e5a8e242970642c58 (patch)
treeb33ff0e21bcb4c621b959ebb1f05b6c534c9e879 /adapter
parenta6166c4dfc7b028761a884127cc78aeb0056ed8d (diff)
downloadchawan-1df10adfe7eb20bc460eeb0e5a8e242970642c58.tar.gz
Move out ftp: protocol; fix some local CGI bugs
Diffstat (limited to 'adapter')
-rw-r--r--adapter/protocol/ftp.nim193
1 files changed, 193 insertions, 0 deletions
diff --git a/adapter/protocol/ftp.nim b/adapter/protocol/ftp.nim
new file mode 100644
index 00000000..ad4a51a3
--- /dev/null
+++ b/adapter/protocol/ftp.nim
@@ -0,0 +1,193 @@
+import std/envvars
+import std/options
+import std/strutils
+
+import bindings/curl
+import loader/connecterror
+import loader/curlwrap
+import loader/dirlist
+import types/opt
+import types/url
+import utils/twtstr
+
+type FtpHandle = ref object
+  curl: CURL
+  buffer: string
+  dirmode: bool
+  base: string
+  path: string
+  statusline: bool
+
+proc curlWriteHeader(p: cstring, size: csize_t, nitems: csize_t,
+    userdata: pointer): csize_t {.cdecl.} =
+  var line = newString(nitems)
+  if nitems > 0:
+    prepareMutation(line)
+    copyMem(addr line[0], p, nitems)
+  let op = cast[FtpHandle](userdata)
+  if not op.statusline:
+    if line.startsWith("150") or line.startsWith("125"):
+      op.statusline = true
+      var status: clong
+      op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status)
+      stdout.write("Status: " & $status & "\n")
+      if op.dirmode:
+        stdout.write("Content-Type: text/html\n")
+      stdout.write("\n")
+      if op.dirmode:
+        stdout.write("""
+<HTML>
+<HEAD>
+<BASE HREF=""" & op.base & """>
+<TITLE>""" & op.path & """</TITLE>
+</HEAD>
+<BODY>
+<H1>Index of """ & htmlEscape(op.path) & """</H1>
+<PRE>
+""")
+      return nitems
+    elif line.startsWith("530"): # login incorrect
+      op.statusline = true
+      var status: clong
+      op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status)
+      # unauthorized (shim http)
+      stdout.write("""
+Status: 401
+Content-Type: text/html
+
+<HTML>
+<HEAD>
+<TITLE>Unauthorized</TITLE>
+</HEAD>
+<BODY>
+<PRE>
+""" & htmlEscape(line) & """
+</PRE>
+</BODY>
+</HTML>
+""")
+  return nitems
+
+# From the documentation: size is always 1.
+proc curlWriteBody(p: cstring, size, nmemb: csize_t, userdata: pointer):
+    csize_t {.cdecl.} =
+  let op = cast[FtpHandle](userdata)
+  if nmemb > 0:
+    if op.dirmode:
+      let i = op.buffer.len
+      op.buffer.setLen(op.buffer.len + int(nmemb))
+      prepareMutation(op.buffer)
+      copyMem(addr op.buffer[i], p, nmemb)
+    else:
+      return csize_t(stdout.writeBuffer(p, int(nmemb)))
+  return nmemb
+
+proc finish(op: FtpHandle) =
+  let op = op
+  var items: seq[DirlistItem]
+  for line in op.buffer.split('\n'):
+    if line.len == 0: continue
+    var i = 10 # permission
+    template skip_till_space =
+      while i < line.len and line[i] != ' ':
+        inc i
+    # link count
+    i = line.skipBlanks(i)
+    while i < line.len and line[i] in AsciiDigit:
+      inc i
+    # owner
+    i = line.skipBlanks(i)
+    skip_till_space
+    # group
+    i = line.skipBlanks(i)
+    while i < line.len and line[i] != ' ':
+      inc i
+    # size
+    i = line.skipBlanks(i)
+    var sizes = ""
+    while i < line.len and line[i] in AsciiDigit:
+      sizes &= line[i]
+      inc i
+    let nsize = parseInt64(sizes).get(-1)
+    # date
+    i = line.skipBlanks(i)
+    let datestarti = i
+    skip_till_space # m
+    i = line.skipBlanks(i)
+    skip_till_space # d
+    i = line.skipBlanks(i)
+    skip_till_space # y
+    let dates = line.substr(datestarti, i)
+    inc i
+    let name = line.substr(i)
+    if name == "." or name == "..": continue
+    case line[0]
+    of 'l': # link
+      let linki = name.find(" -> ")
+      let linkfrom = name.substr(0, linki - 1)
+      let linkto = name.substr(linki + 4) # you?
+      items.add(DirlistItem(
+        t: ITEM_LINK,
+        name: linkfrom,
+        modified: dates,
+        linkto: linkto
+      ))
+    of 'd': # directory
+      items.add(DirlistItem(
+        t: ITEM_DIR,
+        name: name,
+        modified: dates
+      ))
+    else: # file
+      items.add(DirlistItem(
+        t: ITEM_FILE,
+        name: name,
+        modified: dates,
+        nsize: int(nsize)
+      ))
+  stdout.write(makeDirlist(items))
+  stdout.write("\n</PRE>\n</BODY>\n</HTML>\n")
+
+proc main() =
+  let curl = curl_easy_init()
+  doAssert curl != nil
+  let surl = getEnv("QUERY_STRING")
+  let opath = getEnv("MAPPED_URI_PATH")
+  let path = percentDecode(opath)
+  # By default, cURL CWD's into relative paths, and an extra slash is
+  # necessary to specify absolute paths.
+  # This is incredibly confusing, and probably not what the user wanted.
+  # So we work around it by adding the extra slash ourselves.
+  let hackurl = newURL(surl).get
+  hackurl.setPathname('/' & opath)
+  let csurl = hackurl.serialize()
+  curl.setopt(CURLOPT_URL, csurl)
+  let dirmode = path.len > 0 and path[^1] == '/'
+  let op = FtpHandle(
+    curl: curl,
+    dirmode: dirmode
+  )
+  curl.setopt(CURLOPT_HEADERDATA, op)
+  curl.setopt(CURLOPT_HEADERFUNCTION, curlWriteHeader)
+  curl.setopt(CURLOPT_WRITEDATA, op)
+  curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody)
+  curl.setopt(CURLOPT_FTP_FILEMETHOD, CURLFTPMETHOD_SINGLECWD)
+  if dirmode:
+    op.base = surl
+    op.path = path
+  let purl = getEnv("ALL_PROXY")
+  if purl != "":
+    curl.setopt(CURLOPT_PROXY, purl)
+  if getEnv("REQUEST_METHOD") != "GET":
+    let code = $int(ERROR_INVALID_METHOD)
+    stdout.write("Cha-Control: ConnectionError " & $code & "\n")
+    return
+  let res = curl_easy_perform(curl)
+  if res != CURLE_OK:
+    if not op.statusline:
+      stdout.write("Cha-Control: ConnectionError " & $int(res) & "\n")
+  elif op.dirmode:
+    op.finish()
+  curl_easy_cleanup(curl)
+
+main()