cgi.nim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2012 Andreas Rumpf
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## This module implements helper procs for CGI applications. Example:
  10. ##
  11. ## .. code-block:: Nim
  12. ##
  13. ## import strtabs, cgi
  14. ##
  15. ## # Fill the values when debugging:
  16. ## when debug:
  17. ## setTestData("name", "Klaus", "password", "123456")
  18. ## # read the data into `myData`
  19. ## var myData = readData()
  20. ## # check that the data's variable names are "name" or "password"
  21. ## validateData(myData, "name", "password")
  22. ## # start generating content:
  23. ## writeContentType()
  24. ## # generate content:
  25. ## write(stdout, "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01//EN\">\n")
  26. ## write(stdout, "<html><head><title>Test</title></head><body>\n")
  27. ## writeLine(stdout, "your name: " & myData["name"])
  28. ## writeLine(stdout, "your password: " & myData["password"])
  29. ## writeLine(stdout, "</body></html>")
  30. import strutils, os, strtabs, cookies, uri
  31. export uri.encodeUrl, uri.decodeUrl
  32. proc handleHexChar(c: char, x: var int) {.inline.} =
  33. case c
  34. of '0'..'9': x = (x shl 4) or (ord(c) - ord('0'))
  35. of 'a'..'f': x = (x shl 4) or (ord(c) - ord('a') + 10)
  36. of 'A'..'F': x = (x shl 4) or (ord(c) - ord('A') + 10)
  37. else: assert(false)
  38. proc addXmlChar(dest: var string, c: char) {.inline.} =
  39. case c
  40. of '&': add(dest, "&amp;")
  41. of '<': add(dest, "&lt;")
  42. of '>': add(dest, "&gt;")
  43. of '\"': add(dest, "&quot;")
  44. else: add(dest, c)
  45. proc xmlEncode*(s: string): string =
  46. ## Encodes a value to be XML safe:
  47. ## * ``"`` is replaced by ``&quot;``
  48. ## * ``<`` is replaced by ``&lt;``
  49. ## * ``>`` is replaced by ``&gt;``
  50. ## * ``&`` is replaced by ``&amp;``
  51. ## * every other character is carried over.
  52. result = newStringOfCap(s.len + s.len shr 2)
  53. for i in 0..len(s)-1: addXmlChar(result, s[i])
  54. type
  55. CgiError* = object of IOError ## exception that is raised if a CGI error occurs
  56. RequestMethod* = enum ## the used request method
  57. methodNone, ## no REQUEST_METHOD environment variable
  58. methodPost, ## query uses the POST method
  59. methodGet ## query uses the GET method
  60. proc cgiError*(msg: string) {.noreturn.} =
  61. ## raises an ECgi exception with message `msg`.
  62. var e: ref CgiError
  63. new(e)
  64. e.msg = msg
  65. raise e
  66. proc getEncodedData(allowedMethods: set[RequestMethod]): string =
  67. case getEnv("REQUEST_METHOD").string
  68. of "POST":
  69. if methodPost notin allowedMethods:
  70. cgiError("'REQUEST_METHOD' 'POST' is not supported")
  71. var L = parseInt(getEnv("CONTENT_LENGTH").string)
  72. result = newString(L)
  73. if readBuffer(stdin, addr(result[0]), L) != L:
  74. cgiError("cannot read from stdin")
  75. of "GET":
  76. if methodGet notin allowedMethods:
  77. cgiError("'REQUEST_METHOD' 'GET' is not supported")
  78. result = getEnv("QUERY_STRING").string
  79. else:
  80. if methodNone notin allowedMethods:
  81. cgiError("'REQUEST_METHOD' must be 'POST' or 'GET'")
  82. iterator decodeData*(data: string): tuple[key, value: TaintedString] =
  83. ## Reads and decodes CGI data and yields the (name, value) pairs the
  84. ## data consists of.
  85. var i = 0
  86. var name = ""
  87. var value = ""
  88. # decode everything in one pass:
  89. while i < data.len:
  90. setLen(name, 0) # reuse memory
  91. while i < data.len:
  92. case data[i]
  93. of '%':
  94. var x = 0
  95. handleHexChar(data[i+1], x)
  96. handleHexChar(data[i+2], x)
  97. inc(i, 2)
  98. add(name, chr(x))
  99. of '+': add(name, ' ')
  100. of '=', '&': break
  101. else: add(name, data[i])
  102. inc(i)
  103. if i >= data.len or data[i] != '=': cgiError("'=' expected")
  104. inc(i) # skip '='
  105. setLen(value, 0) # reuse memory
  106. while i < data.len:
  107. case data[i]
  108. of '%':
  109. var x = 0
  110. if i+2 < data.len:
  111. handleHexChar(data[i+1], x)
  112. handleHexChar(data[i+2], x)
  113. inc(i, 2)
  114. add(value, chr(x))
  115. of '+': add(value, ' ')
  116. of '&', '\0': break
  117. else: add(value, data[i])
  118. inc(i)
  119. yield (name.TaintedString, value.TaintedString)
  120. if i < data.len:
  121. if data[i] == '&': inc(i)
  122. else: cgiError("'&' expected")
  123. iterator decodeData*(allowedMethods: set[RequestMethod] =
  124. {methodNone, methodPost, methodGet}): tuple[key, value: TaintedString] =
  125. ## Reads and decodes CGI data and yields the (name, value) pairs the
  126. ## data consists of. If the client does not use a method listed in the
  127. ## `allowedMethods` set, an `ECgi` exception is raised.
  128. let data = getEncodedData(allowedMethods)
  129. for key, value in decodeData(data):
  130. yield (key, value)
  131. proc readData*(allowedMethods: set[RequestMethod] =
  132. {methodNone, methodPost, methodGet}): StringTableRef =
  133. ## Read CGI data. If the client does not use a method listed in the
  134. ## `allowedMethods` set, an `ECgi` exception is raised.
  135. result = newStringTable()
  136. for name, value in decodeData(allowedMethods):
  137. result[name.string] = value.string
  138. proc readData*(data: string): StringTableRef =
  139. ## Read CGI data from a string.
  140. result = newStringTable()
  141. for name, value in decodeData(data):
  142. result[name.string] = value.string
  143. proc validateData*(data: StringTableRef, validKeys: varargs[string]) =
  144. ## validates data; raises `ECgi` if this fails. This checks that each variable
  145. ## name of the CGI `data` occurs in the `validKeys` array.
  146. for key, val in pairs(data):
  147. if find(validKeys, key) < 0:
  148. cgiError("unknown variable name: " & key)
  149. proc getContentLength*(): string =
  150. ## returns contents of the ``CONTENT_LENGTH`` environment variable
  151. return getEnv("CONTENT_LENGTH").string
  152. proc getContentType*(): string =
  153. ## returns contents of the ``CONTENT_TYPE`` environment variable
  154. return getEnv("CONTENT_Type").string
  155. proc getDocumentRoot*(): string =
  156. ## returns contents of the ``DOCUMENT_ROOT`` environment variable
  157. return getEnv("DOCUMENT_ROOT").string
  158. proc getGatewayInterface*(): string =
  159. ## returns contents of the ``GATEWAY_INTERFACE`` environment variable
  160. return getEnv("GATEWAY_INTERFACE").string
  161. proc getHttpAccept*(): string =
  162. ## returns contents of the ``HTTP_ACCEPT`` environment variable
  163. return getEnv("HTTP_ACCEPT").string
  164. proc getHttpAcceptCharset*(): string =
  165. ## returns contents of the ``HTTP_ACCEPT_CHARSET`` environment variable
  166. return getEnv("HTTP_ACCEPT_CHARSET").string
  167. proc getHttpAcceptEncoding*(): string =
  168. ## returns contents of the ``HTTP_ACCEPT_ENCODING`` environment variable
  169. return getEnv("HTTP_ACCEPT_ENCODING").string
  170. proc getHttpAcceptLanguage*(): string =
  171. ## returns contents of the ``HTTP_ACCEPT_LANGUAGE`` environment variable
  172. return getEnv("HTTP_ACCEPT_LANGUAGE").string
  173. proc getHttpConnection*(): string =
  174. ## returns contents of the ``HTTP_CONNECTION`` environment variable
  175. return getEnv("HTTP_CONNECTION").string
  176. proc getHttpCookie*(): string =
  177. ## returns contents of the ``HTTP_COOKIE`` environment variable
  178. return getEnv("HTTP_COOKIE").string
  179. proc getHttpHost*(): string =
  180. ## returns contents of the ``HTTP_HOST`` environment variable
  181. return getEnv("HTTP_HOST").string
  182. proc getHttpReferer*(): string =
  183. ## returns contents of the ``HTTP_REFERER`` environment variable
  184. return getEnv("HTTP_REFERER").string
  185. proc getHttpUserAgent*(): string =
  186. ## returns contents of the ``HTTP_USER_AGENT`` environment variable
  187. return getEnv("HTTP_USER_AGENT").string
  188. proc getPathInfo*(): string =
  189. ## returns contents of the ``PATH_INFO`` environment variable
  190. return getEnv("PATH_INFO").string
  191. proc getPathTranslated*(): string =
  192. ## returns contents of the ``PATH_TRANSLATED`` environment variable
  193. return getEnv("PATH_TRANSLATED").string
  194. proc getQueryString*(): string =
  195. ## returns contents of the ``QUERY_STRING`` environment variable
  196. return getEnv("QUERY_STRING").string
  197. proc getRemoteAddr*(): string =
  198. ## returns contents of the ``REMOTE_ADDR`` environment variable
  199. return getEnv("REMOTE_ADDR").string
  200. proc getRemoteHost*(): string =
  201. ## returns contents of the ``REMOTE_HOST`` environment variable
  202. return getEnv("REMOTE_HOST").string
  203. proc getRemoteIdent*(): string =
  204. ## returns contents of the ``REMOTE_IDENT`` environment variable
  205. return getEnv("REMOTE_IDENT").string
  206. proc getRemotePort*(): string =
  207. ## returns contents of the ``REMOTE_PORT`` environment variable
  208. return getEnv("REMOTE_PORT").string
  209. proc getRemoteUser*(): string =
  210. ## returns contents of the ``REMOTE_USER`` environment variable
  211. return getEnv("REMOTE_USER").string
  212. proc getRequestMethod*(): string =
  213. ## returns contents of the ``REQUEST_METHOD`` environment variable
  214. return getEnv("REQUEST_METHOD").string
  215. proc getRequestURI*(): string =
  216. ## returns contents of the ``REQUEST_URI`` environment variable
  217. return getEnv("REQUEST_URI").string
  218. proc getScriptFilename*(): string =
  219. ## returns contents of the ``SCRIPT_FILENAME`` environment variable
  220. return getEnv("SCRIPT_FILENAME").string
  221. proc getScriptName*(): string =
  222. ## returns contents of the ``SCRIPT_NAME`` environment variable
  223. return getEnv("SCRIPT_NAME").string
  224. proc getServerAddr*(): string =
  225. ## returns contents of the ``SERVER_ADDR`` environment variable
  226. return getEnv("SERVER_ADDR").string
  227. proc getServerAdmin*(): string =
  228. ## returns contents of the ``SERVER_ADMIN`` environment variable
  229. return getEnv("SERVER_ADMIN").string
  230. proc getServerName*(): string =
  231. ## returns contents of the ``SERVER_NAME`` environment variable
  232. return getEnv("SERVER_NAME").string
  233. proc getServerPort*(): string =
  234. ## returns contents of the ``SERVER_PORT`` environment variable
  235. return getEnv("SERVER_PORT").string
  236. proc getServerProtocol*(): string =
  237. ## returns contents of the ``SERVER_PROTOCOL`` environment variable
  238. return getEnv("SERVER_PROTOCOL").string
  239. proc getServerSignature*(): string =
  240. ## returns contents of the ``SERVER_SIGNATURE`` environment variable
  241. return getEnv("SERVER_SIGNATURE").string
  242. proc getServerSoftware*(): string =
  243. ## returns contents of the ``SERVER_SOFTWARE`` environment variable
  244. return getEnv("SERVER_SOFTWARE").string
  245. proc setTestData*(keysvalues: varargs[string]) =
  246. ## fills the appropriate environment variables to test your CGI application.
  247. ## This can only simulate the 'GET' request method. `keysvalues` should
  248. ## provide embedded (name, value)-pairs. Example:
  249. ##
  250. ## .. code-block:: Nim
  251. ## setTestData("name", "Hanz", "password", "12345")
  252. putEnv("REQUEST_METHOD", "GET")
  253. var i = 0
  254. var query = ""
  255. while i < keysvalues.len:
  256. add(query, encodeUrl(keysvalues[i]))
  257. add(query, '=')
  258. add(query, encodeUrl(keysvalues[i+1]))
  259. add(query, '&')
  260. inc(i, 2)
  261. putEnv("QUERY_STRING", query)
  262. proc writeContentType*() =
  263. ## call this before starting to send your HTML data to `stdout`. This
  264. ## implements this part of the CGI protocol:
  265. ##
  266. ## .. code-block:: Nim
  267. ## write(stdout, "Content-type: text/html\n\n")
  268. write(stdout, "Content-type: text/html\n\n")
  269. proc resetForStacktrace() =
  270. stdout.write """<!--: spam
  271. Content-Type: text/html
  272. <body bgcolor=#f0f0f8><font color=#f0f0f8 size=-5> -->
  273. <body bgcolor=#f0f0f8><font color=#f0f0f8 size=-5> --> -->
  274. </font> </font> </font> </script> </object> </blockquote> </pre>
  275. </table> </table> </table> </table> </table> </font> </font> </font>
  276. """
  277. proc writeErrorMessage*(data: string) =
  278. ## Tries to reset browser state and writes `data` to stdout in
  279. ## <plaintext> tag.
  280. resetForStacktrace()
  281. # We use <plaintext> here, instead of escaping, so stacktrace can
  282. # be understood by human looking at source.
  283. stdout.write("<plaintext>\n")
  284. stdout.write(data)
  285. proc setStackTraceStdout*() =
  286. ## Makes Nim output stacktraces to stdout, instead of server log.
  287. errorMessageWriter = writeErrorMessage
  288. proc setStackTraceNewLine*() {.deprecated.} =
  289. ## Makes Nim output stacktraces to stdout, instead of server log.
  290. ## Depracated alias for setStackTraceStdout.
  291. setStackTraceStdout()
  292. proc setCookie*(name, value: string) =
  293. ## Sets a cookie.
  294. write(stdout, "Set-Cookie: ", name, "=", value, "\n")
  295. var
  296. gcookies {.threadvar.}: StringTableRef
  297. proc getCookie*(name: string): TaintedString =
  298. ## Gets a cookie. If no cookie of `name` exists, "" is returned.
  299. if gcookies == nil: gcookies = parseCookies(getHttpCookie())
  300. result = TaintedString(gcookies.getOrDefault(name))
  301. proc existsCookie*(name: string): bool =
  302. ## Checks if a cookie of `name` exists.
  303. if gcookies == nil: gcookies = parseCookies(getHttpCookie())
  304. result = hasKey(gcookies, name)