summary refs log tree commit diff stats
path: root/lib/pure/cgi.nim
blob: e8977b80b56ae22cb2a636be0639eef4a8059920 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .sa { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Affix */
.highlight .sb { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Backtick */
.highlight .sc { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Char */
.highlight .dl { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Delimiter */
.highlight .sd { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Doc */
.highlight .s2 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Double */
.highlight .se { color: #0044dd; background-color: #fff0f0 } /* Literal.String.Escape */
.highlight .sh { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Heredoc */
.highlight .si { color: #3333bb; background-color: #fff0f0 } /* Literal.String.Interpol */
.highlight .sx { color: #22bb22; background-color: #f0fff0 } /* Literal.String.Other */
.highlight .sr { color: #008800; background-color: #fff0ff } /* Literal.String.Regex */
.highlight .s1 { color: #dd2200; background-color: #fff0f0 } /* Literal.String.Single */
.highlight .ss { color: #aa6600; background-color: #fff0f0 } /* Literal.String.Symbol */
.highlight .bp { color: #003388 } /* Name.Builtin.Pseudo */
.highlight .fm { color: #0066bb; font-weight: bold } /* Name.Function.Magic */
.highlight .vc { color: #336699 } /* Name.Variable.Class */
.highlight .vg { color: #dd7700 } /* Name.Variable.Global */
.highlight .vi { color: #3333bb } /* Name.Variable.Instance */
.highlight .vm { color: #336699 } /* Name.Variable.Magic */
.highlight .il { color: #0000DD; font-weight: bold } /* Literal.Number.Integer.Long */
discard """
  targets: "cpp"
  action: compile
"""

import tables, lists

type
  ListTable[K, V] = object
    table: Table[K, DoublyLinkedNode[V]]

proc initListTable*[K, V](initialSize = 64): ListTable[K, V] =
  result.table = initTable[K, DoublyLinkedNode[V]]()

proc `[]=`*[K, V](t: var ListTable[K, V], key: K, val: V) =
  t.table[key].value = val

type
  SomeObj = object
  OtherObj = object

proc main() =
  var someTable = initListTable[int, SomeObj]()
  var otherTable = initListTable[int, OtherObj]()

  someTable[1] = SomeObj()
  otherTable[42] = OtherObj()

main()
>362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400
#
#
#            Nim's Runtime Library
#        (c) Copyright 2012 Andreas Rumpf
#
#    See the file "copying.txt", included in this
#    distribution, for details about the copyright.
#

## This module implements helper procs for CGI applications. Example:
##
## .. code-block:: Nim
##
##    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 "password"
##    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, cookies

proc encodeUrl*(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 = newStringOfCap(s.len + s.len shr 2) # assume 12% non-alnum-chars
  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 decodeUrl*(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 = newString(s.len)
  var i = 0
  var j = 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)
      result[j] = chr(x)
    of '+': result[j] = ' '
    else: result[j] = s[i]
    inc(i)
    inc(j)
  setLen(result, j)

{.deprecated: [URLDecode: decodeUrl, URLEncode: encodeUrl].}

proc addXmlChar(dest: var string, c: char) {.inline.} =
  case c
  of '&': add(dest, "&amp;")
  of '<': add(dest, "&lt;")
  of '>': add(dest, "&gt;")
  of '\"': add(dest, "&quot;")
  else: add(dest, c)

proc xmlEncode*(s: string): string =
  ## Encodes a value to be XML safe:
  ## * ``"`` is replaced by ``&quot;``
  ## * ``<`` is replaced by ``&lt;``
  ## * ``>`` is replaced by ``&gt;``
  ## * ``&`` is replaced by ``&amp;``
  ## * every other character is carried over.
  result = newStringOfCap(s.len + s.len shr 2)
  for i in 0..len(s)-1: addXmlChar(result, s[i])

type
  CgiError* = object of IOError  ## exception that is raised if a CGI error occurs
  RequestMethod* = enum  ## the used request method
    methodNone,          ## no REQUEST_METHOD environment variable
    methodPost,          ## query uses the POST method
    methodGet            ## query uses the GET method

{.deprecated: [TRequestMethod: RequestMethod, ECgi: CgiError,
  XMLencode: xmlEncode].}

proc cgiError*(msg: string) {.noreturn.} =
  ## raises an ECgi exception with message `msg`.
  var e: ref CgiError
  new(e)
  e.msg = msg
  raise e

proc getEncodedData(allowedMethods: set[RequestMethod]): string =
  case getEnv("REQUEST_METHOD").string
  of "POST":
    if methodPost notin allowedMethods:
      cgiError("'REQUEST_METHOD' 'POST' is not supported")
    var L = parseInt(getEnv("CONTENT_LENGTH").string)
    result = newString(L)
    if readBuffer(stdin, addr(result[0]), L) != L:
      cgiError("cannot read from stdin")
  of "GET":
    if methodGet notin allowedMethods:
      cgiError("'REQUEST_METHOD' 'GET' is not supported")
    result = getEnv("QUERY_STRING").string
  else:
    if methodNone notin allowedMethods:
      cgiError("'REQUEST_METHOD' must be 'POST' or 'GET'")

iterator decodeData*(data: string): tuple[key, value: TaintedString] =
  ## Reads and decodes CGI data and yields the (name, value) pairs the
  ## data consists of.
  var i = 0
  var name = ""
  var value = ""
  # decode everything in one pass:
  while data[i] != '\0':
    setLen(name, 0) # reuse memory
    while true:
      case data[i]
      of '\0': break
      of '%':
        var x = 0
        handleHexChar(data[i+1], x)
        handleHexChar(data[i+2], x)
        inc(i, 2)
        add(name, chr(x))
      of '+': add(name, ' ')
      of '=', '&': break
      else: add(name, data[i])
      inc(i)
    if data[i] != '=': cgiError("'=' expected")
    inc(i) # skip '='
    setLen(value, 0) # reuse memory
    while true:
      case data[i]
      of '%':
        var x = 0
        handleHexChar(data[i+1], x)
        handleHexChar(data[i+2], x)
        inc(i, 2)
        add(value, chr(x))
      of '+': add(value, ' ')
      of '&', '\0': break
      else: add(value, data[i])
      inc(i)
    yield (name.TaintedString, value.TaintedString)
    if data[i] == '&': inc(i)
    elif data[i] == '\0': break
    else: cgiError("'&' expected")

iterator decodeData*(allowedMethods: set[RequestMethod] =
       {methodNone, methodPost, methodGet}): tuple[key, value: TaintedString] =
  ## Reads and decodes CGI data and yields the (name, value) pairs the
  ## data consists of. If the client does not use a method listed in the
  ## `allowedMethods` set, an `ECgi` exception is raised.
  var data = getEncodedData(allowedMethods)
  if not isNil(data):
    for key, value in decodeData(data):
      yield (key, value)

proc readData*(allowedMethods: set[RequestMethod] =
               {methodNone, methodPost, methodGet}): StringTableRef =
  ## Read CGI data. If the client does not use a method listed in the
  ## `allowedMethods` set, an `ECgi` exception is raised.
  result = newStringTable()
  for name, value in decodeData(allowedMethods):
    result[name.string] = value.string

proc validateData*(data: StringTableRef, validKeys: varargs[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").string

proc getContentType*(): string =
  ## returns contents of the ``CONTENT_TYPE`` environment variable
  return getEnv("CONTENT_Type").string

proc getDocumentRoot*(): string =
  ## returns contents of the ``DOCUMENT_ROOT`` environment variable
  return getEnv("DOCUMENT_ROOT").string

proc getGatewayInterface*(): string =
  ## returns contents of the ``GATEWAY_INTERFACE`` environment variable
  return getEnv("GATEWAY_INTERFACE").string

proc getHttpAccept*(): string =
  ## returns contents of the ``HTTP_ACCEPT`` environment variable
  return getEnv("HTTP_ACCEPT").string

proc getHttpAcceptCharset*(): string =
  ## returns contents of the ``HTTP_ACCEPT_CHARSET`` environment variable
  return getEnv("HTTP_ACCEPT_CHARSET").string

proc getHttpAcceptEncoding*(): string =
  ## returns contents of the ``HTTP_ACCEPT_ENCODING`` environment variable
  return getEnv("HTTP_ACCEPT_ENCODING").string

proc getHttpAcceptLanguage*(): string =
  ## returns contents of the ``HTTP_ACCEPT_LANGUAGE`` environment variable
  return getEnv("HTTP_ACCEPT_LANGUAGE").string

proc getHttpConnection*(): string =
  ## returns contents of the ``HTTP_CONNECTION`` environment variable
  return getEnv("HTTP_CONNECTION").string

proc getHttpCookie*(): string =
  ## returns contents of the ``HTTP_COOKIE`` environment variable
  return getEnv("HTTP_COOKIE").string

proc getHttpHost*(): string =
  ## returns contents of the ``HTTP_HOST`` environment variable
  return getEnv("HTTP_HOST").string

proc getHttpReferer*(): string =
  ## returns contents of the ``HTTP_REFERER`` environment variable
  return getEnv("HTTP_REFERER").string

proc getHttpUserAgent*(): string =
  ## returns contents of the ``HTTP_USER_AGENT`` environment variable
  return getEnv("HTTP_USER_AGENT").string

proc getPathInfo*(): string =
  ## returns contents of the ``PATH_INFO`` environment variable
  return getEnv("PATH_INFO").string

proc getPathTranslated*(): string =
  ## returns contents of the ``PATH_TRANSLATED`` environment variable
  return getEnv("PATH_TRANSLATED").string

proc getQueryString*(): string =
  ## returns contents of the ``QUERY_STRING`` environment variable
  return getEnv("QUERY_STRING").string

proc getRemoteAddr*(): string =
  ## returns contents of the ``REMOTE_ADDR`` environment variable
  return getEnv("REMOTE_ADDR").string

proc getRemoteHost*(): string =
  ## returns contents of the ``REMOTE_HOST`` environment variable
  return getEnv("REMOTE_HOST").string

proc getRemoteIdent*(): string =
  ## returns contents of the ``REMOTE_IDENT`` environment variable
  return getEnv("REMOTE_IDENT").string

proc getRemotePort*(): string =
  ## returns contents of the ``REMOTE_PORT`` environment variable
  return getEnv("REMOTE_PORT").string

proc getRemoteUser*(): string =
  ## returns contents of the ``REMOTE_USER`` environment variable
  return getEnv("REMOTE_USER").string

proc getRequestMethod*(): string =
  ## returns contents of the ``REQUEST_METHOD`` environment variable
  return getEnv("REQUEST_METHOD").string

proc getRequestURI*(): string =
  ## returns contents of the ``REQUEST_URI`` environment variable
  return getEnv("REQUEST_URI").string

proc getScriptFilename*(): string =
  ## returns contents of the ``SCRIPT_FILENAME`` environment variable
  return getEnv("SCRIPT_FILENAME").string

proc getScriptName*(): string =
  ## returns contents of the ``SCRIPT_NAME`` environment variable
  return getEnv("SCRIPT_NAME").string

proc getServerAddr*(): string =
  ## returns contents of the ``SERVER_ADDR`` environment variable
  return getEnv("SERVER_ADDR").string

proc getServerAdmin*(): string =
  ## returns contents of the ``SERVER_ADMIN`` environment variable
  return getEnv("SERVER_ADMIN").string

proc getServerName*(): string =
  ## returns contents of the ``SERVER_NAME`` environment variable
  return getEnv("SERVER_NAME").string

proc getServerPort*(): string =
  ## returns contents of the ``SERVER_PORT`` environment variable
  return getEnv("SERVER_PORT").string

proc getServerProtocol*(): string =
  ## returns contents of the ``SERVER_PROTOCOL`` environment variable
  return getEnv("SERVER_PROTOCOL").string

proc getServerSignature*(): string =
  ## returns contents of the ``SERVER_SIGNATURE`` environment variable
  return getEnv("SERVER_SIGNATURE").string

proc getServerSoftware*(): string =
  ## returns contents of the ``SERVER_SOFTWARE`` environment variable
  return getEnv("SERVER_SOFTWARE").string

proc setTestData*(keysvalues: varargs[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:: Nim
  ##    setTestData("name", "Hanz", "password", "12345")
  putEnv("REQUEST_METHOD", "GET")
  var i = 0
  var query = ""
  while i < keysvalues.len:
    add(query, encodeUrl(keysvalues[i]))
    add(query, '=')
    add(query, encodeUrl(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
  ## implements this part of the CGI protocol:
  ##
  ## .. code-block:: Nim
  ##     write(stdout, "Content-type: text/html\n\n")
  write(stdout, "Content-type: text/html\n\n")

proc resetForStacktrace() =
  stdout.write """<!--: spam
Content-Type: text/html

<body bgcolor=#f0f0f8><font color=#f0f0f8 size=-5> -->
<body bgcolor=#f0f0f8><font color=#f0f0f8 size=-5> --> -->
</font> </font> </font> </script> </object> </blockquote> </pre>
</table> </table> </table> </table> </table> </font> </font> </font>
"""

proc writeErrorMessage*(data: string) =
  ## Tries to reset browser state and writes `data` to stdout in
  ## <plaintext> tag.
  resetForStacktrace()
  # We use <plaintext> here, instead of escaping, so stacktrace can
  # be understood by human looking at source.
  stdout.write("<plaintext>\n")
  stdout.write(data)

proc setStackTraceStdout*() =
  ## Makes Nim output stacktraces to stdout, instead of server log.
  errorMessageWriter = writeErrorMessage

proc setStackTraceNewLine*() {.deprecated.} =
  ## Makes Nim output stacktraces to stdout, instead of server log.
  ## Depracated alias for setStackTraceStdout.
  setStackTraceStdout()

proc setCookie*(name, value: string) =
  ## Sets a cookie.
  write(stdout, "Set-Cookie: ", name, "=", value, "\n")

var
  gcookies {.threadvar.}: StringTableRef

proc getCookie*(name: string): TaintedString =
  ## Gets a cookie. If no cookie of `name` exists, "" is returned.
  if gcookies == nil: gcookies = parseCookies(getHttpCookie())
  result = TaintedString(gcookies[name])

proc existsCookie*(name: string): bool =
  ## Checks if a cookie of `name` exists.
  if gcookies == nil: gcookies = parseCookies(getHttpCookie())
  result = hasKey(gcookies, name)

when isMainModule:
  const test1 = "abc\L+def xyz"
  assert encodeUrl(test1) == "abc%0A%2Bdef+xyz"
  assert decodeUrl(encodeUrl(test1)) == test1