httpcore.nim 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369
  1. #
  2. #
  3. # Nim's Runtime Library
  4. # (c) Copyright 2016 Dominik Picheta
  5. #
  6. # See the file "copying.txt", included in this
  7. # distribution, for details about the copyright.
  8. #
  9. ## Contains functionality shared between the `httpclient` and
  10. ## `asynchttpserver` modules.
  11. ##
  12. ## Unstable API.
  13. import std/private/since
  14. import std/[tables, strutils, parseutils]
  15. type
  16. HttpHeaders* = ref object
  17. table*: TableRef[string, seq[string]]
  18. isTitleCase: bool
  19. HttpHeaderValues* = distinct seq[string]
  20. # The range starts at '0' so that we don't have to explicitly initialise
  21. # it. See: http://irclogs.nim-lang.org/19-09-2016.html#19:48:27 for context.
  22. HttpCode* = distinct range[0 .. 599]
  23. HttpVersion* = enum
  24. HttpVer11,
  25. HttpVer10
  26. HttpMethod* = enum ## the requested HttpMethod
  27. HttpHead = "HEAD" ## Asks for the response identical to the one that
  28. ## would correspond to a GET request, but without
  29. ## the response body.
  30. HttpGet = "GET" ## Retrieves the specified resource.
  31. HttpPost = "POST" ## Submits data to be processed to the identified
  32. ## resource. The data is included in the body of
  33. ## the request.
  34. HttpPut = "PUT" ## Uploads a representation of the specified
  35. ## resource.
  36. HttpDelete = "DELETE" ## Deletes the specified resource.
  37. HttpTrace = "TRACE" ## Echoes back the received request, so that a
  38. ## client
  39. ## can see what intermediate servers are adding or
  40. ## changing in the request.
  41. HttpOptions = "OPTIONS" ## Returns the HTTP methods that the server
  42. ## supports for specified address.
  43. HttpConnect = "CONNECT" ## Converts the request connection to a transparent
  44. ## TCP/IP tunnel, usually used for proxies.
  45. HttpPatch = "PATCH" ## Applies partial modifications to a resource.
  46. const
  47. Http100* = HttpCode(100)
  48. Http101* = HttpCode(101)
  49. Http102* = HttpCode(102) ## https://tools.ietf.org/html/rfc2518.html WebDAV
  50. Http103* = HttpCode(103) ## https://tools.ietf.org/html/rfc8297.html Early hints
  51. Http200* = HttpCode(200)
  52. Http201* = HttpCode(201)
  53. Http202* = HttpCode(202)
  54. Http203* = HttpCode(203)
  55. Http204* = HttpCode(204)
  56. Http205* = HttpCode(205)
  57. Http206* = HttpCode(206)
  58. Http207* = HttpCode(207) ## https://tools.ietf.org/html/rfc4918.html WebDAV
  59. Http208* = HttpCode(208) ## https://tools.ietf.org/html/rfc5842.html WebDAV, Section 7.1
  60. Http226* = HttpCode(226) ## https://tools.ietf.org/html/rfc3229.html Delta encoding, Section 10.4.1
  61. Http300* = HttpCode(300)
  62. Http301* = HttpCode(301)
  63. Http302* = HttpCode(302)
  64. Http303* = HttpCode(303)
  65. Http304* = HttpCode(304)
  66. Http305* = HttpCode(305)
  67. Http307* = HttpCode(307)
  68. Http308* = HttpCode(308)
  69. Http400* = HttpCode(400)
  70. Http401* = HttpCode(401)
  71. Http402* = HttpCode(402) ## https://tools.ietf.org/html/rfc7231.html Payment required, Section 6.5.2
  72. Http403* = HttpCode(403)
  73. Http404* = HttpCode(404)
  74. Http405* = HttpCode(405)
  75. Http406* = HttpCode(406)
  76. Http407* = HttpCode(407)
  77. Http408* = HttpCode(408)
  78. Http409* = HttpCode(409)
  79. Http410* = HttpCode(410)
  80. Http411* = HttpCode(411)
  81. Http412* = HttpCode(412)
  82. Http413* = HttpCode(413)
  83. Http414* = HttpCode(414)
  84. Http415* = HttpCode(415)
  85. Http416* = HttpCode(416)
  86. Http417* = HttpCode(417)
  87. Http418* = HttpCode(418)
  88. Http421* = HttpCode(421)
  89. Http422* = HttpCode(422)
  90. Http423* = HttpCode(423) ## https://tools.ietf.org/html/rfc4918.html WebDAV, Section 11.3
  91. Http424* = HttpCode(424) ## https://tools.ietf.org/html/rfc4918.html WebDAV, Section 11.3
  92. Http425* = HttpCode(425) ## https://tools.ietf.org/html/rfc8470.html Early data
  93. Http426* = HttpCode(426)
  94. Http428* = HttpCode(428)
  95. Http429* = HttpCode(429)
  96. Http431* = HttpCode(431)
  97. Http451* = HttpCode(451)
  98. Http500* = HttpCode(500)
  99. Http501* = HttpCode(501)
  100. Http502* = HttpCode(502)
  101. Http503* = HttpCode(503)
  102. Http504* = HttpCode(504)
  103. Http505* = HttpCode(505)
  104. Http506* = HttpCode(506) ## https://tools.ietf.org/html/rfc2295.html Content negotiation, Section 8.1
  105. Http507* = HttpCode(507) ## https://tools.ietf.org/html/rfc4918.html WebDAV, Section 11.5
  106. Http508* = HttpCode(508) ## https://tools.ietf.org/html/rfc5842.html WebDAV, Section 7.2
  107. Http510* = HttpCode(510) ## https://tools.ietf.org/html/rfc2774.html Extension framework, Section 7
  108. Http511* = HttpCode(511) ## https://tools.ietf.org/html/rfc6585.html Additional status code, Section 6
  109. const httpNewLine* = "\c\L"
  110. const headerLimit* = 10_000
  111. func toTitleCase(s: string): string =
  112. result = newString(len(s))
  113. var upper = true
  114. for i in 0..len(s) - 1:
  115. result[i] = if upper: toUpperAscii(s[i]) else: toLowerAscii(s[i])
  116. upper = s[i] == '-'
  117. func toCaseInsensitive*(headers: HttpHeaders, s: string): string {.inline.} =
  118. ## For internal usage only. Do not use.
  119. return if headers.isTitleCase: toTitleCase(s) else: toLowerAscii(s)
  120. func newHttpHeaders*(titleCase=false): HttpHeaders =
  121. ## Returns a new `HttpHeaders` object. if `titleCase` is set to true,
  122. ## headers are passed to the server in title case (e.g. "Content-Length")
  123. result = HttpHeaders(table: newTable[string, seq[string]](), isTitleCase: titleCase)
  124. func newHttpHeaders*(keyValuePairs:
  125. openArray[tuple[key: string, val: string]], titleCase=false): HttpHeaders =
  126. ## Returns a new `HttpHeaders` object from an array. if `titleCase` is set to true,
  127. ## headers are passed to the server in title case (e.g. "Content-Length")
  128. result = HttpHeaders(table: newTable[string, seq[string]](), isTitleCase: titleCase)
  129. for pair in keyValuePairs:
  130. let key = result.toCaseInsensitive(pair.key)
  131. {.cast(noSideEffect).}:
  132. if key in result.table:
  133. result.table[key].add(pair.val)
  134. else:
  135. result.table[key] = @[pair.val]
  136. func `$`*(headers: HttpHeaders): string {.inline.} =
  137. $headers.table
  138. proc clear*(headers: HttpHeaders) {.inline.} =
  139. headers.table.clear()
  140. func `[]`*(headers: HttpHeaders, key: string): HttpHeaderValues =
  141. ## Returns the values associated with the given `key`. If the returned
  142. ## values are passed to a procedure expecting a `string`, the first
  143. ## value is automatically picked. If there are
  144. ## no values associated with the key, an exception is raised.
  145. ##
  146. ## To access multiple values of a key, use the overloaded `[]` below or
  147. ## to get all of them access the `table` field directly.
  148. {.cast(noSideEffect).}:
  149. let tmp = headers.table[headers.toCaseInsensitive(key)]
  150. return HttpHeaderValues(tmp)
  151. converter toString*(values: HttpHeaderValues): string =
  152. return seq[string](values)[0]
  153. func `[]`*(headers: HttpHeaders, key: string, i: int): string =
  154. ## Returns the `i`'th value associated with the given key. If there are
  155. ## no values associated with the key or the `i`'th value doesn't exist,
  156. ## an exception is raised.
  157. {.cast(noSideEffect).}:
  158. return headers.table[headers.toCaseInsensitive(key)][i]
  159. proc `[]=`*(headers: HttpHeaders, key, value: string) =
  160. ## Sets the header entries associated with `key` to the specified value.
  161. ## Replaces any existing values.
  162. headers.table[headers.toCaseInsensitive(key)] = @[value]
  163. proc `[]=`*(headers: HttpHeaders, key: string, value: seq[string]) =
  164. ## Sets the header entries associated with `key` to the specified list of
  165. ## values. Replaces any existing values. If `value` is empty,
  166. ## deletes the header entries associated with `key`.
  167. if value.len > 0:
  168. headers.table[headers.toCaseInsensitive(key)] = value
  169. else:
  170. headers.table.del(headers.toCaseInsensitive(key))
  171. proc add*(headers: HttpHeaders, key, value: string) =
  172. ## Adds the specified value to the specified key. Appends to any existing
  173. ## values associated with the key.
  174. if not headers.table.hasKey(headers.toCaseInsensitive(key)):
  175. headers.table[headers.toCaseInsensitive(key)] = @[value]
  176. else:
  177. headers.table[headers.toCaseInsensitive(key)].add(value)
  178. proc del*(headers: HttpHeaders, key: string) =
  179. ## Deletes the header entries associated with `key`
  180. headers.table.del(headers.toCaseInsensitive(key))
  181. iterator pairs*(headers: HttpHeaders): tuple[key, value: string] =
  182. ## Yields each key, value pair.
  183. for k, v in headers.table:
  184. for value in v:
  185. yield (k, value)
  186. func contains*(values: HttpHeaderValues, value: string): bool =
  187. ## Determines if `value` is one of the values inside `values`. Comparison
  188. ## is performed without case sensitivity.
  189. for val in seq[string](values):
  190. if val.toLowerAscii == value.toLowerAscii: return true
  191. func hasKey*(headers: HttpHeaders, key: string): bool =
  192. return headers.table.hasKey(headers.toCaseInsensitive(key))
  193. func getOrDefault*(headers: HttpHeaders, key: string,
  194. default = @[""].HttpHeaderValues): HttpHeaderValues =
  195. ## Returns the values associated with the given `key`. If there are no
  196. ## values associated with the key, then `default` is returned.
  197. if headers.hasKey(key):
  198. return headers[key]
  199. else:
  200. return default
  201. func len*(headers: HttpHeaders): int {.inline.} = headers.table.len
  202. func parseList(line: string, list: var seq[string], start: int): int =
  203. var i = 0
  204. var current = ""
  205. while start+i < line.len and line[start + i] notin {'\c', '\l'}:
  206. i += line.skipWhitespace(start + i)
  207. i += line.parseUntil(current, {'\c', '\l', ','}, start + i)
  208. list.add(move current) # implicit current.setLen(0)
  209. if start+i < line.len and line[start + i] == ',':
  210. i.inc # Skip ,
  211. func parseHeader*(line: string): tuple[key: string, value: seq[string]] =
  212. ## Parses a single raw header HTTP line into key value pairs.
  213. ##
  214. ## Used by `asynchttpserver` and `httpclient` internally and should not
  215. ## be used by you.
  216. result.value = @[]
  217. var i = 0
  218. i = line.parseUntil(result.key, ':')
  219. inc(i) # skip :
  220. if i < len(line):
  221. if cmpIgnoreCase(result.key, "cookie") == 0:
  222. i += line.skipWhitespace(i)
  223. result.value.add line.substr(i)
  224. else:
  225. i += parseList(line, result.value, i)
  226. elif result.key.len > 0:
  227. result.value = @[""]
  228. else:
  229. result.value = @[]
  230. func `==`*(protocol: tuple[orig: string, major, minor: int],
  231. ver: HttpVersion): bool =
  232. let major =
  233. case ver
  234. of HttpVer11, HttpVer10: 1
  235. let minor =
  236. case ver
  237. of HttpVer11: 1
  238. of HttpVer10: 0
  239. result = protocol.major == major and protocol.minor == minor
  240. func contains*(methods: set[HttpMethod], x: string): bool =
  241. return parseEnum[HttpMethod](x) in methods
  242. func `$`*(code: HttpCode): string =
  243. ## Converts the specified `HttpCode` into a HTTP status.
  244. runnableExamples:
  245. doAssert($Http404 == "404 Not Found")
  246. case code.int
  247. of 100: "100 Continue"
  248. of 101: "101 Switching Protocols"
  249. of 102: "102 Processing"
  250. of 103: "103 Early Hints"
  251. of 200: "200 OK"
  252. of 201: "201 Created"
  253. of 202: "202 Accepted"
  254. of 203: "203 Non-Authoritative Information"
  255. of 204: "204 No Content"
  256. of 205: "205 Reset Content"
  257. of 206: "206 Partial Content"
  258. of 207: "207 Multi-Status"
  259. of 208: "208 Already Reported"
  260. of 226: "226 IM Used"
  261. of 300: "300 Multiple Choices"
  262. of 301: "301 Moved Permanently"
  263. of 302: "302 Found"
  264. of 303: "303 See Other"
  265. of 304: "304 Not Modified"
  266. of 305: "305 Use Proxy"
  267. of 307: "307 Temporary Redirect"
  268. of 308: "308 Permanent Redirect"
  269. of 400: "400 Bad Request"
  270. of 401: "401 Unauthorized"
  271. of 402: "402 Payment Required"
  272. of 403: "403 Forbidden"
  273. of 404: "404 Not Found"
  274. of 405: "405 Method Not Allowed"
  275. of 406: "406 Not Acceptable"
  276. of 407: "407 Proxy Authentication Required"
  277. of 408: "408 Request Timeout"
  278. of 409: "409 Conflict"
  279. of 410: "410 Gone"
  280. of 411: "411 Length Required"
  281. of 412: "412 Precondition Failed"
  282. of 413: "413 Request Entity Too Large"
  283. of 414: "414 Request-URI Too Long"
  284. of 415: "415 Unsupported Media Type"
  285. of 416: "416 Requested Range Not Satisfiable"
  286. of 417: "417 Expectation Failed"
  287. of 418: "418 I'm a teapot"
  288. of 421: "421 Misdirected Request"
  289. of 422: "422 Unprocessable Entity"
  290. of 423: "423 Locked"
  291. of 424: "424 Failed Dependency"
  292. of 425: "425 Too Early"
  293. of 426: "426 Upgrade Required"
  294. of 428: "428 Precondition Required"
  295. of 429: "429 Too Many Requests"
  296. of 431: "431 Request Header Fields Too Large"
  297. of 451: "451 Unavailable For Legal Reasons"
  298. of 500: "500 Internal Server Error"
  299. of 501: "501 Not Implemented"
  300. of 502: "502 Bad Gateway"
  301. of 503: "503 Service Unavailable"
  302. of 504: "504 Gateway Timeout"
  303. of 505: "505 HTTP Version Not Supported"
  304. of 506: "506 Variant Also Negotiates"
  305. of 507: "507 Insufficient Storage"
  306. of 508: "508 Loop Detected"
  307. of 510: "510 Not Extended"
  308. of 511: "511 Network Authentication Required"
  309. else: $(int(code))
  310. func `==`*(a, b: HttpCode): bool {.borrow.}
  311. func is1xx*(code: HttpCode): bool {.inline, since: (1, 5).} =
  312. ## Determines whether `code` is a 1xx HTTP status code.
  313. runnableExamples:
  314. doAssert is1xx(HttpCode(103))
  315. code.int in 100 .. 199
  316. func is2xx*(code: HttpCode): bool {.inline.} =
  317. ## Determines whether `code` is a 2xx HTTP status code.
  318. code.int in 200 .. 299
  319. func is3xx*(code: HttpCode): bool {.inline.} =
  320. ## Determines whether `code` is a 3xx HTTP status code.
  321. code.int in 300 .. 399
  322. func is4xx*(code: HttpCode): bool {.inline.} =
  323. ## Determines whether `code` is a 4xx HTTP status code.
  324. code.int in 400 .. 499
  325. func is5xx*(code: HttpCode): bool {.inline.} =
  326. ## Determines whether `code` is a 5xx HTTP status code.
  327. code.int in 500 .. 599