about summary refs log tree commit diff stats
path: root/adapter/protocol
diff options
context:
space:
mode:
Diffstat (limited to 'adapter/protocol')
-rw-r--r--adapter/protocol/gopher.nim113
1 files changed, 113 insertions, 0 deletions
diff --git a/adapter/protocol/gopher.nim b/adapter/protocol/gopher.nim
new file mode 100644
index 00000000..cf3038f7
--- /dev/null
+++ b/adapter/protocol/gopher.nim
@@ -0,0 +1,113 @@
+import std/envvars
+import std/options
+
+import bindings/curl
+import loader/connecterror
+import loader/curlwrap
+import loader/request
+import types/opt
+import types/url
+import utils/twtstr
+
+include ../gophertypes
+
+type GopherHandle = ref object
+  curl: CURL
+  t: GopherType
+  statusline: bool
+
+func gopherType(c: char): GopherType =
+  return case c
+  of '0': TEXT_FILE
+  of '1': DIRECTORY
+  of '3': ERROR
+  of '5': DOS_BINARY
+  of '7': SEARCH
+  of 'm': MESSAGE
+  of 's': SOUND
+  of 'g': GIF
+  of 'h': HTML
+  of 'i': INFO
+  of 'I': IMAGE
+  of '9': BINARY
+  of 'p': PNG
+  else: UNKNOWN
+
+proc onStatusLine(op: GopherHandle) =
+  var status: clong
+  op.curl.getinfo(CURLINFO_RESPONSE_CODE, addr status)
+  stdout.write("Status: " & $status & "\n")
+  let s = case op.t
+  of DIRECTORY, SEARCH: "Content-Type: text/gopher\n"
+  of HTML: "Content-Type: text/html\n"
+  of GIF: "Content-Type: image/gif\n"
+  of PNG: "Content-Type: image/png\n"
+  of TEXT_FILE, ERROR: "Content-Type: text/plain\n"
+  else: ""
+  stdout.write(s & "\n")
+
+proc loadSearch(op: GopherHandle, surl: string) =
+  stdout.write("""
+Content-Type: text/html
+
+<!DOCTYPE HTML>
+<HTML>
+<HEAD>
+<BASE HREF="""" & surl & """">
+</HEAD>
+<BODY>
+<H1>Search """ & htmlEscape(surl) & """</H1>
+<FORM>
+<INPUT TYPE=SEARCH NAME="NAME">
+</FORM>
+</BODY>
+</HTML>
+""")
+
+# From the documentation: size is always 1.
+proc curlWriteBody(p: cstring, size, nmemb: csize_t, userdata: pointer):
+    csize_t {.cdecl.} =
+  let op = cast[GopherHandle](userdata)
+  if not op.statusline:
+    op.statusline = true
+    op.onStatusLine()
+  return csize_t(stdout.writeBuffer(p, int(nmemb)))
+
+proc main() =
+  let curl = curl_easy_init()
+  doAssert curl != nil
+  if getEnv("REQUEST_METHOD") != "GET":
+    stdout.write("Cha-Control: ConnectionError " & $int(ERROR_INVALID_METHOD))
+    return
+  var url = newURL(getEnv("QUERY_STRING")).get
+  var path = url.pathname
+  if path.len < 1:
+    path &= '/'
+  if path.len < 2:
+    path &= '1'
+    url = newURL(url)
+    url.setPathname(path)
+  let op = GopherHandle(
+    curl: curl,
+    t: gopherType(path[1])
+  )
+  if op.t == SEARCH:
+    if url.query.isNone:
+      op.loadSearch(url.serialize())
+      return
+    else:
+      url.query = some(url.query.get.after('='))
+  let surl = url.serialize()
+  #TODO avoid re-parsing
+  curl.setopt(CURLOPT_URL, surl)
+  curl.setopt(CURLOPT_WRITEDATA, op)
+  curl.setopt(CURLOPT_WRITEFUNCTION, curlWriteBody)
+  let proxy = getEnv("ALL_PROXY")
+  if proxy != "":
+    curl.setopt(CURLOPT_PROXY, proxy)
+  let res = curl_easy_perform(curl)
+  if res != CURLE_OK and not op.statusline:
+    stdout.write("Cha-Control: ConnectionError " & $int(res) & "\n")
+  curl_easy_cleanup(curl)
+
+main()