cgi.nim 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358
  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. if L == 0:
  73. return ""
  74. result = newString(L)
  75. if readBuffer(stdin, addr(result[0]), L) != L:
  76. cgiError("cannot read from stdin")
  77. of "GET":
  78. if methodGet notin allowedMethods:
  79. cgiError("'REQUEST_METHOD' 'GET' is not supported")
  80. result = getEnv("QUERY_STRING").string
  81. else:
  82. if methodNone notin allowedMethods:
  83. cgiError("'REQUEST_METHOD' must be 'POST' or 'GET'")
  84. iterator decodeData*(data: string): tuple[key, value: TaintedString] =
  85. ## Reads and decodes CGI data and yields the (name, value) pairs the
  86. ## data consists of.
  87. var i = 0
  88. var name = ""
  89. var value = ""
  90. # decode everything in one pass:
  91. while i < data.len:
  92. setLen(name, 0) # reuse memory
  93. while i < data.len:
  94. case data[i]
  95. of '%':
  96. var x = 0
  97. handleHexChar(data[i+1], x)
  98. handleHexChar(data[i+2], x)
  99. inc(i, 2)
  100. add(name, chr(x))
  101. of '+': add(name, ' ')
  102. of '=', '&': break
  103. else: add(name, data[i])
  104. inc(i)
  105. if i >= data.len or data[i] != '=': cgiError("'=' expected")
  106. inc(i) # skip '='
  107. setLen(value, 0) # reuse memory
  108. while i < data.len:
  109. case data[i]
  110. of '%':
  111. var x = 0
  112. if i+2 < data.len:
  113. handleHexChar(data[i+1], x)
  114. handleHexChar(data[i+2], x)
  115. inc(i, 2)
  116. add(value, chr(x))
  117. of '+': add(value, ' ')
  118. of '&', '\0': break
  119. else: add(value, data[i])
  120. inc(i)
  121. yield (name.TaintedString, value.TaintedString)
  122. if i < data.len:
  123. if data[i] == '&': inc(i)
  124. else: cgiError("'&' expected")
  125. iterator decodeData*(allowedMethods: set[RequestMethod] =
  126. {methodNone, methodPost, methodGet}): tuple[key, value: TaintedString] =
  127. ## Reads and decodes CGI data and yields the (name, value) pairs the
  128. ## data consists of. If the client does not use a method listed in the
  129. ## `allowedMethods` set, an `ECgi` exception is raised.
  130. let data = getEncodedData(allowedMethods)
  131. for key, value in decodeData(data):
  132. yield (key, value)
  133. proc readData*(allowedMethods: set[RequestMethod] =
  134. {methodNone, methodPost, methodGet}): StringTableRef =
  135. ## Read CGI data. If the client does not use a method listed in the
  136. ## `allowedMethods` set, an `ECgi` exception is raised.
  137. result = newStringTable()
  138. for name, value in decodeData(allowedMethods):
  139. result[name.string] = value.string
  140. proc readData*(data: string): StringTableRef =
  141. ## Read CGI data from a string.
  142. result = newStringTable()
  143. for name, value in decodeData(data):
  144. result[name.string] = value.string
  145. proc validateData*(data: StringTableRef, validKeys: varargs[string]) =
  146. ## validates data; raises `ECgi` if this fails. This checks that each variable
  147. ## name of the CGI `data` occurs in the `validKeys` array.
  148. for key, val in pairs(data):
  149. if find(validKeys, key) < 0:
  150. cgiError("unknown variable name: " & key)
  151. proc getContentLength*(): string =
  152. ## returns contents of the ``CONTENT_LENGTH`` environment variable
  153. return getEnv("CONTENT_LENGTH").string
  154. proc getContentType*(): string =
  155. ## returns contents of the ``CONTENT_TYPE`` environment variable
  156. return getEnv("CONTENT_Type").string
  157. proc getDocumentRoot*(): string =
  158. ## returns contents of the ``DOCUMENT_ROOT`` environment variable
  159. return getEnv("DOCUMENT_ROOT").string
  160. proc getGatewayInterface*(): string =
  161. ## returns contents of the ``GATEWAY_INTERFACE`` environment variable
  162. return getEnv("GATEWAY_INTERFACE").string
  163. proc getHttpAccept*(): string =
  164. ## returns contents of the ``HTTP_ACCEPT`` environment variable
  165. return getEnv("HTTP_ACCEPT").string
  166. proc getHttpAcceptCharset*(): string =
  167. ## returns contents of the ``HTTP_ACCEPT_CHARSET`` environment variable
  168. return getEnv("HTTP_ACCEPT_CHARSET").string
  169. proc getHttpAcceptEncoding*(): string =
  170. ## returns contents of the ``HTTP_ACCEPT_ENCODING`` environment variable
  171. return getEnv("HTTP_ACCEPT_ENCODING").string
  172. proc getHttpAcceptLanguage*(): string =
  173. ## returns contents of the ``HTTP_ACCEPT_LANGUAGE`` environment variable
  174. return getEnv("HTTP_ACCEPT_LANGUAGE").string
  175. proc getHttpConnection*(): string =
  176. ## returns contents of the ``HTTP_CONNECTION`` environment variable
  177. return getEnv("HTTP_CONNECTION").string
  178. proc getHttpCookie*(): string =
  179. ## returns contents of the ``HTTP_COOKIE`` environment variable
  180. return getEnv("HTTP_COOKIE").string
  181. proc getHttpHost*(): string =
  182. ## returns contents of the ``HTTP_HOST`` environment variable
  183. return getEnv("HTTP_HOST").string
  184. proc getHttpReferer*(): string =
  185. ## returns contents of the ``HTTP_REFERER`` environment variable
  186. return getEnv("HTTP_REFERER").string
  187. proc getHttpUserAgent*(): string =
  188. ## returns contents of the ``HTTP_USER_AGENT`` environment variable
  189. return getEnv("HTTP_USER_AGENT").string
  190. proc getPathInfo*(): string =
  191. ## returns contents of the ``PATH_INFO`` environment variable
  192. return getEnv("PATH_INFO").string
  193. proc getPathTranslated*(): string =
  194. ## returns contents of the ``PATH_TRANSLATED`` environment variable
  195. return getEnv("PATH_TRANSLATED").string
  196. proc getQueryString*(): string =
  197. ## returns contents of the ``QUERY_STRING`` environment variable
  198. return getEnv("QUERY_STRING").string
  199. proc getRemoteAddr*(): string =
  200. ## returns contents of the ``REMOTE_ADDR`` environment variable
  201. return getEnv("REMOTE_ADDR").string
  202. proc getRemoteHost*(): string =
  203. ## returns contents of the ``REMOTE_HOST`` environment variable
  204. return getEnv("REMOTE_HOST").string
  205. proc getRemoteIdent*(): string =
  206. ## returns contents of the ``REMOTE_IDENT`` environment variable
  207. return getEnv("REMOTE_IDENT").string
  208. proc getRemotePort*(): string =
  209. ## returns contents of the ``REMOTE_PORT`` environment variable
  210. return getEnv("REMOTE_PORT").string
  211. proc getRemoteUser*(): string =
  212. ## returns contents of the ``REMOTE_USER`` environment variable
  213. return getEnv("REMOTE_USER").string
  214. proc getRequestMethod*(): string =
  215. ## returns contents of the ``REQUEST_METHOD`` environment variable
  216. return getEnv("REQUEST_METHOD").string
  217. proc getRequestURI*(): string =
  218. ## returns contents of the ``REQUEST_URI`` environment variable
  219. return getEnv("REQUEST_URI").string
  220. proc getScriptFilename*(): string =
  221. ## returns contents of the ``SCRIPT_FILENAME`` environment variable
  222. return getEnv("SCRIPT_FILENAME").string
  223. proc getScriptName*(): string =
  224. ## returns contents of the ``SCRIPT_NAME`` environment variable
  225. return getEnv("SCRIPT_NAME").string
  226. proc getServerAddr*(): string =
  227. ## returns contents of the ``SERVER_ADDR`` environment variable
  228. return getEnv("SERVER_ADDR").string
  229. proc getServerAdmin*(): string =
  230. ## returns contents of the ``SERVER_ADMIN`` environment variable
  231. return getEnv("SERVER_ADMIN").string
  232. proc getServerName*(): string =
  233. ## returns contents of the ``SERVER_NAME`` environment variable
  234. return getEnv("SERVER_NAME").string
  235. proc getServerPort*(): string =
  236. ## returns contents of the ``SERVER_PORT`` environment variable
  237. return getEnv("SERVER_PORT").string
  238. proc getServerProtocol*(): string =
  239. ## returns contents of the ``SERVER_PROTOCOL`` environment variable
  240. return getEnv("SERVER_PROTOCOL").string
  241. proc getServerSignature*(): string =
  242. ## returns contents of the ``SERVER_SIGNATURE`` environment variable
  243. return getEnv("SERVER_SIGNATURE").string
  244. proc getServerSoftware*(): string =
  245. ## returns contents of the ``SERVER_SOFTWARE`` environment variable
  246. return getEnv("SERVER_SOFTWARE").string
  247. proc setTestData*(keysvalues: varargs[string]) =
  248. ## fills the appropriate environment variables to test your CGI application.
  249. ## This can only simulate the 'GET' request method. `keysvalues` should
  250. ## provide embedded (name, value)-pairs. Example:
  251. ##
  252. ## .. code-block:: Nim
  253. ## setTestData("name", "Hanz", "password", "12345")
  254. putEnv("REQUEST_METHOD", "GET")
  255. var i = 0
  256. var query = ""
  257. while i < keysvalues.len:
  258. add(query, encodeUrl(keysvalues[i]))
  259. add(query, '=')
  260. add(query, encodeUrl(keysvalues[i+1]))
  261. add(query, '&')
  262. inc(i, 2)
  263. putEnv("QUERY_STRING", query)
  264. proc writeContentType*() =
  265. ## call this before starting to send your HTML data to `stdout`. This
  266. ## implements this part of the CGI protocol:
  267. ##
  268. ## .. code-block:: Nim
  269. ## write(stdout, "Content-type: text/html\n\n")
  270. write(stdout, "Content-type: text/html\n\n")
  271. proc resetForStacktrace() =
  272. stdout.write """<!--: spam
  273. Content-Type: text/html
  274. <body bgcolor=#f0f0f8><font color=#f0f0f8 size=-5> -->
  275. <body bgcolor=#f0f0f8><font color=#f0f0f8 size=-5> --> -->
  276. </font> </font> </font> </script> </object> </blockquote> </pre>
  277. </table> </table> </table> </table> </table> </font> </font> </font>
  278. """
  279. proc writeErrorMessage*(data: string) =
  280. ## Tries to reset browser state and writes `data` to stdout in
  281. ## <plaintext> tag.
  282. resetForStacktrace()
  283. # We use <plaintext> here, instead of escaping, so stacktrace can
  284. # be understood by human looking at source.
  285. stdout.write("<plaintext>\n")
  286. stdout.write(data)
  287. proc setStackTraceStdout*() =
  288. ## Makes Nim output stacktraces to stdout, instead of server log.
  289. errorMessageWriter = writeErrorMessage
  290. proc setCookie*(name, value: string) =
  291. ## Sets a cookie.
  292. write(stdout, "Set-Cookie: ", name, "=", value, "\n")
  293. var
  294. gcookies {.threadvar.}: StringTableRef
  295. proc getCookie*(name: string): TaintedString =
  296. ## Gets a cookie. If no cookie of `name` exists, "" is returned.
  297. if gcookies == nil: gcookies = parseCookies(getHttpCookie())
  298. result = TaintedString(gcookies.getOrDefault(name))
  299. proc existsCookie*(name: string): bool =
  300. ## Checks if a cookie of `name` exists.
  301. if gcookies == nil: gcookies = parseCookies(getHttpCookie())
  302. result = hasKey(gcookies, name)