cgi.nim 11 KB

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