summary refs log tree commit diff stats
path: root/lib/base/cgi.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/base/cgi.nim')
-rw-r--r--lib/base/cgi.nim305
1 files changed, 305 insertions, 0 deletions
diff --git a/lib/base/cgi.nim b/lib/base/cgi.nim
new file mode 100644
index 000000000..3ed94c9b7
--- /dev/null
+++ b/lib/base/cgi.nim
@@ -0,0 +1,305 @@
+#
+#
+#            Nimrod's Runtime Library
+#        (c) Copyright 2008 Andreas Rumpf
+#
+#    See the file "copying.txt", included in this
+#    distribution, for details about the copyright.
+#
+
+## This module implements helper procs for CGI applictions. Example:
+## 
+## .. code-block:: Nimrod
+##
+##    import strtabs, cgi
+##
+##    # Fill the values when debugging:
+##    when debug: 
+##      setTestData("name", "Klaus", "password", "123456")
+##    # read the data into `myData`
+##    var myData = readData()
+##    # check that the data's variable names are "name" or "passwort" 
+##    validateData(myData, "name", "password")
+##    # start generating content:
+##    writeContentType()
+##    # generate content:
+##    write(stdout, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n")
+##    write(stdout, "<html><head><title>Test</title></head><body>\n")
+##    writeln(stdout, "your name: " & myData["name"])
+##    writeln(stdout, "your password: " & myData["password"])
+##    writeln(stdout, "</body></html>")
+
+import strutils, os, strtabs
+
+proc URLencode*(s: string): string =
+  ## Encodes a value to be HTTP safe: This means that characters in the set
+  ## ``{'A'..'Z', 'a'..'z', '0'..'9', '_'}`` are carried over to the result,
+  ## a space is converted to ``'+'`` and every other character is encoded as
+  ## ``'%xx'`` where ``xx`` denotes its hexadecimal value. 
+  result = ""
+  for i in 0..s.len-1:
+    case s[i]
+    of 'a'..'z', 'A'..'Z', '0'..'9', '_': add(result, s[i])
+    of ' ': add(result, '+')
+    else: 
+      add(result, '%')
+      add(result, toHex(ord(s[i]), 2))
+
+proc handleHexChar(c: char, x: var int) {.inline.} = 
+  case c
+  of '0'..'9': x = (x shl 4) or (ord(c) - ord('0'))
+  of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10)
+  of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10)
+  else: assert(false)
+
+proc URLdecode*(s: string): string = 
+  ## Decodes a value from its HTTP representation: This means that a ``'+'`` 
+  ## is converted to a space, ``'%xx'`` (where ``xx`` denotes a hexadecimal
+  ## value) is converted to the character with ordinal number ``xx``, and  
+  ## and every other character is carried over. 
+  result = ""
+  var i = 0
+  while i < s.len:
+    case s[i]
+    of '%': 
+      var x = 0
+      handleHexChar(s[i+1], x)
+      handleHexChar(s[i+2], x)
+      inc(i, 2)
+      add(result, chr(x))
+    of '+': add(result, ' ')
+    else: add(result, s[i])
+    inc(i)
+
+type
+  ECgi* = object of EIO  ## the exception that is raised, if a CGI error occurs
+  TRequestMethod* = enum ## the used request method
+    methodPost,          ## query uses the POST method
+    methodGet            ## query uses the GET method
+
+proc cgiError(msg: string) {.noreturn.} = 
+  var e: ref ECgi
+  new(e)
+  e.msg = msg
+  raise e
+
+proc readData*(allowedMethods: set[TRequestMethod] = 
+               {methodPost, methodGet}): PStringTable = 
+  ## Read CGI data. If the client does not use a method listed in the
+  ## `allowedMethods` set, an `ECgi` exception is raised.
+  result = newStringTable()
+  var enc: string # encoded data
+  case getenv("REQUEST_METHOD") 
+  of "POST": 
+    if methodPost notin allowedMethods: 
+      cgiError("'REQUEST_METHOD' 'POST' is not supported")
+    # read from stdin:
+    var L = parseInt(getenv("CONTENT_LENGTH"))
+    enc = newString(L)
+    if readBuffer(stdin, addr(enc[0]), L) != L:
+      cgiError("cannot read from stdin")
+  of "GET":
+    if methodGet notin allowedMethods: 
+      cgiError("'REQUEST_METHOD' 'GET' is not supported")
+    # read from the QUERY_STRING environment variable:
+    enc = getenv("QUERY_STRING")
+  else: cgiError("'REQUEST_METHOD' must be 'POST' or 'GET'")
+  
+  # decode everything in one pass:
+  var i = 0
+  var name = ""
+  var value = ""
+  while true:
+    setLen(name, 0) # reuse memory
+    while true:
+      case enc[i]
+      of '\0': return
+      of '%': 
+        var x = 0
+        handleHexChar(enc[i+1], x)
+        handleHexChar(enc[i+2], x)
+        inc(i, 2)
+        add(name, chr(x))
+      of '+': add(name, ' ')
+      of '=', '&': break
+      else: add(name, enc[i])
+      inc(i)
+    if enc[i] != '=': cgiError("'=' expected")
+    inc(i) # skip '='
+    setLen(value, 0) # reuse memory
+    while true:
+      case enc[i]
+      of '\0': return
+      of '%': 
+        var x = 0
+        handleHexChar(enc[i+1], x)
+        handleHexChar(enc[i+2], x)
+        inc(i, 2)
+        add(value, chr(x))
+      of '+': add(value, ' ')
+      of '=', '&': break
+      else: add(value, enc[i])
+      inc(i)
+    if enc[i] != '&': cgiError("'&' expected")
+    inc(i) # skip '='
+    result[name] = value
+
+proc validateData*(data: PStringTable, validKeys: openarray[string]) = 
+  ## validates data; raises `ECgi` if this fails. This checks that each variable
+  ## name of the CGI `data` occurs in the `validKeys` array.
+  for key, val in pairs(data):
+    if find(validKeys, key) < 0: 
+      cgiError("unknown variable name: " & key)
+
+proc getContentLength*(): string =
+  ## returns contents of the ``CONTENT_LENGTH`` environment variable
+  return getenv("CONTENT_LENGTH")
+
+proc getContentType*(): string =
+  ## returns contents of the ``CONTENT_TYPE`` environment variable
+  return getenv("CONTENT_Type")
+
+proc getDocumentRoot*(): string =
+  ## returns contents of the ``DOCUMENT_ROOT`` environment variable
+  return getenv("DOCUMENT_ROOT")
+
+proc getGatewayInterface*(): string =
+  ## returns contents of the ``GATEWAY_INTERFACE`` environment variable
+  return getenv("GATEWAY_INTERFACE")
+
+proc getHttpAccept*(): string =
+  ## returns contents of the ``HTTP_ACCEPT`` environment variable
+  return getenv("HTTP_ACCEPT")
+
+proc getHttpAcceptCharset*(): string =
+  ## returns contents of the ``HTTP_ACCEPT_CHARSET`` environment variable
+  return getenv("HTTP_ACCEPT_CHARSET")
+
+proc getHttpAcceptEncoding*(): string =
+  ## returns contents of the ``HTTP_ACCEPT_ENCODING`` environment variable
+  return getenv("HTTP_ACCEPT_ENCODING")
+
+proc getHttpAcceptLanguage*(): string =
+  ## returns contents of the ``HTTP_ACCEPT_LANGUAGE`` environment variable
+  return getenv("HTTP_ACCEPT_LANGUAGE")
+
+proc getHttpConnection*(): string =
+  ## returns contents of the ``HTTP_CONNECTION`` environment variable
+  return getenv("HTTP_CONNECTION")
+
+proc getHttpCookie*(): string =
+  ## returns contents of the ``HTTP_COOKIE`` environment variable
+  return getenv("HTTP_COOKIE")
+
+proc getHttpHost*(): string =
+  ## returns contents of the ``HTTP_HOST`` environment variable
+  return getenv("HTTP_HOST")
+
+proc getHttpReferer*(): string =
+  ## returns contents of the ``HTTP_REFERER`` environment variable
+  return getenv("HTTP_REFERER")
+
+proc getHttpUserAgent*(): string =
+  ## returns contents of the ``HTTP_USER_AGENT`` environment variable
+  return getenv("HTTP_USER_AGENT")
+
+proc getPathInfo*(): string =
+  ## returns contents of the ``PATH_INFO`` environment variable
+  return getenv("PATH_INFO")
+
+proc getPathTranslated*(): string =
+  ## returns contents of the ``PATH_TRANSLATED`` environment variable
+  return getenv("PATH_TRANSLATED")
+
+proc getQueryString*(): string =
+  ## returns contents of the ``QUERY_STRING`` environment variable
+  return getenv("QUERY_STRING")
+
+proc getRemoteAddr*(): string =
+  ## returns contents of the ``REMOTE_ADDR`` environment variable
+  return getenv("REMOTE_ADDR")
+
+proc getRemoteHost*(): string =
+  ## returns contents of the ``REMOTE_HOST`` environment variable
+  return getenv("REMOTE_HOST")
+
+proc getRemoteIdent*(): string =
+  ## returns contents of the ``REMOTE_IDENT`` environment variable
+  return getenv("REMOTE_IDENT")
+
+proc getRemotePort*(): string =
+  ## returns contents of the ``REMOTE_PORT`` environment variable
+  return getenv("REMOTE_PORT")
+
+proc getRemoteUser*(): string =
+  ## returns contents of the ``REMOTE_USER`` environment variable
+  return getenv("REMOTE_USER")
+
+proc getRequestMethod*(): string =
+  ## returns contents of the ``REQUEST_METHOD`` environment variable
+  return getenv("REQUEST_METHOD")
+
+proc getRequestURI*(): string =
+  ## returns contents of the ``REQUEST_URI`` environment variable
+  return getenv("REQUEST_URI")
+
+proc getScriptFilename*(): string =
+  ## returns contents of the ``SCRIPT_FILENAME`` environment variable
+  return getenv("SCRIPT_FILENAME")
+
+proc getScriptName*(): string =
+  ## returns contents of the ``SCRIPT_NAME`` environment variable
+  return getenv("SCRIPT_NAME")
+
+proc getServerAddr*(): string =
+  ## returns contents of the ``SERVER_ADDR`` environment variable
+  return getenv("SERVER_ADDR")
+
+proc getServerAdmin*(): string =
+  ## returns contents of the ``SERVER_ADMIN`` environment variable
+  return getenv("SERVER_ADMIN")
+
+proc getServerName*(): string =
+  ## returns contents of the ``SERVER_NAME`` environment variable
+  return getenv("SERVER_NAME")
+
+proc getServerPort*(): string =
+  ## returns contents of the ``SERVER_PORT`` environment variable
+  return getenv("SERVER_PORT")
+
+proc getServerProtocol*(): string =
+  ## returns contents of the ``SERVER_PROTOCOL`` environment variable
+  return getenv("SERVER_PROTOCOL")
+
+proc getServerSignature*(): string =
+  ## returns contents of the ``SERVER_SIGNATURE`` environment variable
+  return getenv("SERVER_SIGNATURE")
+
+proc getServerSoftware*(): string =
+  ## returns contents of the ``SERVER_SOFTWARE`` environment variable
+  return getenv("SERVER_SOFTWARE")
+
+proc setTestData*(keysvalues: openarray[string]) = 
+  ## fills the appropriate environment variables to test your CGI application.
+  ## This can only simulate the 'GET' 'REQUEST_METHOD'. `keysvalues` should
+  ## provide embedded (name, value)-pairs. Example:
+  ##
+  ## .. code-block:: Nimrod
+  ##    setTestData("name", "Hanz", "password", "12345")
+  putenv("REQUEST_METHOD", "GET")
+  var i = 0
+  var query = ""
+  while i < keysvalues.len:
+    add(query, URLencode(keysvalues[i]))
+    add(query, '=')
+    add(query, URLencode(keysvalues[i+1]))
+    add(query, '&')
+    inc(i, 2)
+  putenv("QUERY_STRING", query)
+
+proc writeContentType*() = 
+  ## call this before starting to send your HTML data to `stdout`. This
+  ## is just a shorthand for: 
+  ## .. code-block:: Nimrod
+  ##     write(stdout, "Content-type: text/html\n\n")
+  write(stdout, "Content-type: text/html\n\n")