123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324 |
- #
- #
- # Nim's Runtime Library
- # (c) Copyright 2016 Dominik Picheta
- #
- # See the file "copying.txt", included in this
- # distribution, for details about the copyright.
- #
- ## Contains functionality shared between the ``httpclient`` and
- ## ``asynchttpserver`` modules.
- import tables, strutils, parseutils
- type
- HttpHeaders* = ref object
- table*: TableRef[string, seq[string]]
- HttpHeaderValues* = distinct seq[string]
- # The range starts at '0' so that we don't have to explicitly initialise
- # it. See: http://irclogs.nim-lang.org/19-09-2016.html#19:48:27 for context.
- HttpCode* = distinct range[0 .. 599]
- HttpVersion* = enum
- HttpVer11,
- HttpVer10
- HttpMethod* = enum ## the requested HttpMethod
- HttpHead, ## Asks for the response identical to the one that would
- ## correspond to a GET request, but without the response
- ## body.
- HttpGet, ## Retrieves the specified resource.
- HttpPost, ## Submits data to be processed to the identified
- ## resource. The data is included in the body of the
- ## request.
- HttpPut, ## Uploads a representation of the specified resource.
- HttpDelete, ## Deletes the specified resource.
- HttpTrace, ## Echoes back the received request, so that a client
- ## can see what intermediate servers are adding or
- ## changing in the request.
- HttpOptions, ## Returns the HTTP methods that the server supports
- ## for specified address.
- HttpConnect, ## Converts the request connection to a transparent
- ## TCP/IP tunnel, usually used for proxies.
- HttpPatch ## Applies partial modifications to a resource.
- const
- Http100* = HttpCode(100)
- Http101* = HttpCode(101)
- Http200* = HttpCode(200)
- Http201* = HttpCode(201)
- Http202* = HttpCode(202)
- Http203* = HttpCode(203)
- Http204* = HttpCode(204)
- Http205* = HttpCode(205)
- Http206* = HttpCode(206)
- Http300* = HttpCode(300)
- Http301* = HttpCode(301)
- Http302* = HttpCode(302)
- Http303* = HttpCode(303)
- Http304* = HttpCode(304)
- Http305* = HttpCode(305)
- Http307* = HttpCode(307)
- Http400* = HttpCode(400)
- Http401* = HttpCode(401)
- Http403* = HttpCode(403)
- Http404* = HttpCode(404)
- Http405* = HttpCode(405)
- Http406* = HttpCode(406)
- Http407* = HttpCode(407)
- Http408* = HttpCode(408)
- Http409* = HttpCode(409)
- Http410* = HttpCode(410)
- Http411* = HttpCode(411)
- Http412* = HttpCode(412)
- Http413* = HttpCode(413)
- Http414* = HttpCode(414)
- Http415* = HttpCode(415)
- Http416* = HttpCode(416)
- Http417* = HttpCode(417)
- Http418* = HttpCode(418)
- Http421* = HttpCode(421)
- Http422* = HttpCode(422)
- Http426* = HttpCode(426)
- Http428* = HttpCode(428)
- Http429* = HttpCode(429)
- Http431* = HttpCode(431)
- Http451* = HttpCode(451)
- Http500* = HttpCode(500)
- Http501* = HttpCode(501)
- Http502* = HttpCode(502)
- Http503* = HttpCode(503)
- Http504* = HttpCode(504)
- Http505* = HttpCode(505)
- const headerLimit* = 10_000
- proc newHttpHeaders*(): HttpHeaders =
- new result
- result.table = newTable[string, seq[string]]()
- proc newHttpHeaders*(keyValuePairs:
- openarray[tuple[key: string, val: string]]): HttpHeaders =
- var pairs: seq[tuple[key: string, val: seq[string]]] = @[]
- for pair in keyValuePairs:
- pairs.add((pair.key.toLowerAscii(), @[pair.val]))
- new result
- result.table = newTable[string, seq[string]](pairs)
- proc `$`*(headers: HttpHeaders): string =
- return $headers.table
- proc clear*(headers: HttpHeaders) =
- headers.table.clear()
- proc `[]`*(headers: HttpHeaders, key: string): HttpHeaderValues =
- ## Returns the values associated with the given ``key``. If the returned
- ## values are passed to a procedure expecting a ``string``, the first
- ## value is automatically picked. If there are
- ## no values associated with the key, an exception is raised.
- ##
- ## To access multiple values of a key, use the overloaded ``[]`` below or
- ## to get all of them access the ``table`` field directly.
- return headers.table[key.toLowerAscii].HttpHeaderValues
- converter toString*(values: HttpHeaderValues): string =
- return seq[string](values)[0]
- proc `[]`*(headers: HttpHeaders, key: string, i: int): string =
- ## Returns the ``i``'th value associated with the given key. If there are
- ## no values associated with the key or the ``i``'th value doesn't exist,
- ## an exception is raised.
- return headers.table[key.toLowerAscii][i]
- proc `[]=`*(headers: HttpHeaders, key, value: string) =
- ## Sets the header entries associated with ``key`` to the specified value.
- ## Replaces any existing values.
- headers.table[key.toLowerAscii] = @[value]
- proc `[]=`*(headers: HttpHeaders, key: string, value: seq[string]) =
- ## Sets the header entries associated with ``key`` to the specified list of
- ## values.
- ## Replaces any existing values.
- headers.table[key.toLowerAscii] = value
- proc add*(headers: HttpHeaders, key, value: string) =
- ## Adds the specified value to the specified key. Appends to any existing
- ## values associated with the key.
- if not headers.table.hasKey(key.toLowerAscii):
- headers.table[key.toLowerAscii] = @[value]
- else:
- headers.table[key.toLowerAscii].add(value)
- proc del*(headers: HttpHeaders, key: string) =
- ## Delete the header entries associated with ``key``
- headers.table.del(key.toLowerAscii)
- iterator pairs*(headers: HttpHeaders): tuple[key, value: string] =
- ## Yields each key, value pair.
- for k, v in headers.table:
- for value in v:
- yield (k, value)
- proc contains*(values: HttpHeaderValues, value: string): bool =
- ## Determines if ``value`` is one of the values inside ``values``. Comparison
- ## is performed without case sensitivity.
- for val in seq[string](values):
- if val.toLowerAscii == value.toLowerAscii: return true
- proc hasKey*(headers: HttpHeaders, key: string): bool =
- return headers.table.hasKey(key.toLowerAscii())
- proc getOrDefault*(headers: HttpHeaders, key: string,
- default = @[""].HttpHeaderValues): HttpHeaderValues =
- ## Returns the values associated with the given ``key``. If there are no
- ## values associated with the key, then ``default`` is returned.
- if headers.hasKey(key):
- return headers[key]
- else:
- return default
- proc len*(headers: HttpHeaders): int = return headers.table.len
- proc parseList(line: string, list: var seq[string], start: int): int =
- var i = 0
- var current = ""
- while start+i < line.len and line[start + i] notin {'\c', '\l'}:
- i += line.skipWhitespace(start + i)
- i += line.parseUntil(current, {'\c', '\l', ','}, start + i)
- list.add(current)
- if start+i < line.len and line[start + i] == ',':
- i.inc # Skip ,
- current.setLen(0)
- proc parseHeader*(line: string): tuple[key: string, value: seq[string]] =
- ## Parses a single raw header HTTP line into key value pairs.
- ##
- ## Used by ``asynchttpserver`` and ``httpclient`` internally and should not
- ## be used by you.
- result.value = @[]
- var i = 0
- i = line.parseUntil(result.key, ':')
- inc(i) # skip :
- if i < len(line):
- i += parseList(line, result.value, i)
- elif result.key.len > 0:
- result.value = @[""]
- else:
- result.value = @[]
- proc `==`*(protocol: tuple[orig: string, major, minor: int],
- ver: HttpVersion): bool =
- let major =
- case ver
- of HttpVer11, HttpVer10: 1
- let minor =
- case ver
- of HttpVer11: 1
- of HttpVer10: 0
- result = protocol.major == major and protocol.minor == minor
- proc contains*(methods: set[HttpMethod], x: string): bool =
- return parseEnum[HttpMethod](x) in methods
- proc `$`*(code: HttpCode): string =
- ## Converts the specified ``HttpCode`` into a HTTP status.
- ##
- ## For example:
- ##
- ## .. code-block:: nim
- ## doAssert($Http404 == "404 Not Found")
- case code.int
- of 100: "100 Continue"
- of 101: "101 Switching Protocols"
- of 200: "200 OK"
- of 201: "201 Created"
- of 202: "202 Accepted"
- of 203: "203 Non-Authoritative Information"
- of 204: "204 No Content"
- of 205: "205 Reset Content"
- of 206: "206 Partial Content"
- of 300: "300 Multiple Choices"
- of 301: "301 Moved Permanently"
- of 302: "302 Found"
- of 303: "303 See Other"
- of 304: "304 Not Modified"
- of 305: "305 Use Proxy"
- of 307: "307 Temporary Redirect"
- of 400: "400 Bad Request"
- of 401: "401 Unauthorized"
- of 403: "403 Forbidden"
- of 404: "404 Not Found"
- of 405: "405 Method Not Allowed"
- of 406: "406 Not Acceptable"
- of 407: "407 Proxy Authentication Required"
- of 408: "408 Request Timeout"
- of 409: "409 Conflict"
- of 410: "410 Gone"
- of 411: "411 Length Required"
- of 412: "412 Precondition Failed"
- of 413: "413 Request Entity Too Large"
- of 414: "414 Request-URI Too Long"
- of 415: "415 Unsupported Media Type"
- of 416: "416 Requested Range Not Satisfiable"
- of 417: "417 Expectation Failed"
- of 418: "418 I'm a teapot"
- of 421: "421 Misdirected Request"
- of 422: "422 Unprocessable Entity"
- of 426: "426 Upgrade Required"
- of 428: "428 Precondition Required"
- of 429: "429 Too Many Requests"
- of 431: "431 Request Header Fields Too Large"
- of 451: "451 Unavailable For Legal Reasons"
- of 500: "500 Internal Server Error"
- of 501: "501 Not Implemented"
- of 502: "502 Bad Gateway"
- of 503: "503 Service Unavailable"
- of 504: "504 Gateway Timeout"
- of 505: "505 HTTP Version Not Supported"
- else: $(int(code))
- proc `==`*(a, b: HttpCode): bool {.borrow.}
- proc `==`*(rawCode: string, code: HttpCode): bool =
- return cmpIgnoreCase(rawCode, $code) == 0
- proc is2xx*(code: HttpCode): bool =
- ## Determines whether ``code`` is a 2xx HTTP status code.
- return code.int in {200 .. 299}
- proc is3xx*(code: HttpCode): bool =
- ## Determines whether ``code`` is a 3xx HTTP status code.
- return code.int in {300 .. 399}
- proc is4xx*(code: HttpCode): bool =
- ## Determines whether ``code`` is a 4xx HTTP status code.
- return code.int in {400 .. 499}
- proc is5xx*(code: HttpCode): bool =
- ## Determines whether ``code`` is a 5xx HTTP status code.
- return code.int in {500 .. 599}
- proc `$`*(httpMethod: HttpMethod): string =
- return (system.`$`(httpMethod))[4 .. ^1].toUpperAscii()
- when isMainModule:
- var test = newHttpHeaders()
- test["Connection"] = @["Upgrade", "Close"]
- doAssert test["Connection", 0] == "Upgrade"
- doAssert test["Connection", 1] == "Close"
- test.add("Connection", "Test")
- doAssert test["Connection", 2] == "Test"
- doAssert "upgrade" in test["Connection"]
- # Bug #5344.
- doAssert parseHeader("foobar: ") == ("foobar", @[""])
- let (key, value) = parseHeader("foobar: ")
- test = newHttpHeaders()
- test[key] = value
- doAssert test["foobar"] == ""
- doAssert parseHeader("foobar:") == ("foobar", @[""])
|