#
#
# 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, "\n")
## write(stdout, "
Test\n")
## writeLine(stdout, "your name: " & myData["name"])
## writeLine(stdout, "your password: " & myData["password"])
## writeLine(stdout, "")
import strutils, os, strtabs, cookies, uri
export uri.encodeUrl, uri.decodeUrl
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 addXmlChar(dest: var string, c: char) {.inline.} =
case c
of '&': add(dest, "&")
of '<': add(dest, "<")
of '>': add(dest, ">")
of '\"': add(dest, """)
else: add(dest, c)
proc xmlEncode*(s: string): string =
## Encodes a value to be XML safe:
## * ``"`` is replaced by ``"``
## * ``<`` is replaced by ``<``
## * ``>`` is replaced by ``>``
## * ``&`` is replaced by ``&``
## * 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
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 i < data.len:
setLen(name, 0) # reuse memory
while i < data.len:
case data[i]
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 i >= data.len or data[i] != '=': cgiError("'=' expected")
inc(i) # skip '='
setLen(value, 0) # reuse memory
while i < data.len:
case data[i]
of '%':
var x = 0
if i+2 < data.len:
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 i < data.len:
if data[i] == '&': inc(i)
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.
let data = getEncodedData(allowedMethods)
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 readData*(data: string): StringTableRef =
## Read CGI data from a string.
result = newStringTable()
for name, value in decodeData(data):
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 """
--> -->
"""
proc writeErrorMessage*(data: string) =
## Tries to reset browser state and writes `data` to stdout in
## tag.
resetForStacktrace()
# We use here, instead of escaping, so stacktrace can
# be understood by human looking at source.
stdout.write("\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.getOrDefault(name))
proc existsCookie*(name: string): bool =
## Checks if a cookie of `name` exists.
if gcookies == nil: gcookies = parseCookies(getHttpCookie())
result = hasKey(gcookies, name)