#
#
# 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:
##
## ```Nim
## import std/[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 std/[strutils, os, strtabs, cookies, uri]
export uri.encodeUrl, uri.decodeUrl
when defined(nimPreviewSlimSystem):
import std/syncio
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 a `CgiError` exception with message `msg`.
raise newException(CgiError, msg)
proc getEncodedData(allowedMethods: set[RequestMethod]): string =
case getEnv("REQUEST_METHOD")
of "POST":
if methodPost notin allowedMethods:
cgiError("'REQUEST_METHOD' 'POST' is not supported")
var L = parseInt(getEnv("CONTENT_LENGTH"))
if L == 0:
return ""
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")
else:
if methodNone notin allowedMethods:
cgiError("'REQUEST_METHOD' must be 'POST' or 'GET'")
iterator decodeData*(data: string): tuple[key, value: string] =
## Reads and decodes CGI data and yields the (name, value) pairs the
## data consists of.
for (key, value) in uri.decodeQuery(data):
yield (key, value)
iterator decodeData*(allowedMethods: set[RequestMethod] =
{methodNone, methodPost, methodGet}): tuple[key, value: string] =
## 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, a `CgiError` exception is raised.
let data = getEncodedData(allowedMethods)
for (key, value) in uri.decodeQuery(data):
yield (key, value)
proc readData*(allowedMethods: set[RequestMethod] =
{methodNone, methodPost, methodGet}): StringTableRef =
## Reads CGI data. If the client does not use a method listed in the
## `allowedMethods` set, a `CgiError` exception is raised.
result = newStringTable()
for name, value in decodeData(allowedMethods):
result[name] = value
proc readData*(data: string): StringTableRef =
## Reads CGI data from a string.
result = newStringTable()
for name, value in decodeData(data):
result[name] = value
proc validateData*(data: StringTableRef, validKeys: varargs[string]) =
## Validates data; raises `CgiError` 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: 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:
## ```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*() =
## Calls this before starting to send your HTML data to `stdout`. This
## implements this part of the CGI protocol:
## ```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 setCookie*(name, value: string) =
## Sets a cookie.
write(stdout, "Set-Cookie: ", name, "=", value, "\n")
var
gcookies {.threadvar.}: StringTableRef
proc getCookie*(name: string): string =
## Gets a cookie. If no cookie of `name` exists, "" is returned.
if gcookies == nil: gcookies = parseCookies(getHttpCookie())
result = 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)