summary refs log tree commit diff stats
path: root/lib/pure/httpcore.nim
diff options
context:
space:
mode:
Diffstat (limited to 'lib/pure/httpcore.nim')
-rw-r--r--lib/pure/httpcore.nim190
1 files changed, 190 insertions, 0 deletions
diff --git a/lib/pure/httpcore.nim b/lib/pure/httpcore.nim
new file mode 100644
index 000000000..98c03d177
--- /dev/null
+++ b/lib/pure/httpcore.nim
@@ -0,0 +1,190 @@
+#
+#
+#            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]
+
+  HttpCode* = enum
+    Http100 = "100 Continue",
+    Http101 = "101 Switching Protocols",
+    Http200 = "200 OK",
+    Http201 = "201 Created",
+    Http202 = "202 Accepted",
+    Http204 = "204 No Content",
+    Http205 = "205 Reset Content",
+    Http206 = "206 Partial Content",
+    Http300 = "300 Multiple Choices",
+    Http301 = "301 Moved Permanently",
+    Http302 = "302 Found",
+    Http303 = "303 See Other",
+    Http304 = "304 Not Modified",
+    Http305 = "305 Use Proxy",
+    Http307 = "307 Temporary Redirect",
+    Http400 = "400 Bad Request",
+    Http401 = "401 Unauthorized",
+    Http403 = "403 Forbidden",
+    Http404 = "404 Not Found",
+    Http405 = "405 Method Not Allowed",
+    Http406 = "406 Not Acceptable",
+    Http407 = "407 Proxy Authentication Required",
+    Http408 = "408 Request Timeout",
+    Http409 = "409 Conflict",
+    Http410 = "410 Gone",
+    Http411 = "411 Length Required",
+    Http412 = "412 Precondition Failed",
+    Http413 = "413 Request Entity Too Large",
+    Http414 = "414 Request-URI Too Long",
+    Http415 = "415 Unsupported Media Type",
+    Http416 = "416 Requested Range Not Satisfiable",
+    Http417 = "417 Expectation Failed",
+    Http418 = "418 I'm a teapot",
+    Http500 = "500 Internal Server Error",
+    Http501 = "501 Not Implemented",
+    Http502 = "502 Bad Gateway",
+    Http503 = "503 Service Unavailable",
+    Http504 = "504 Gateway Timeout",
+    Http505 = "505 HTTP Version Not Supported"
+
+  HttpVersion* = enum
+    HttpVer11,
+    HttpVer10
+
+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.toLower(), @[pair.val]))
+  new result
+  result.table = newTable[string, seq[string]](pairs)
+
+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.toLower].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.toLower][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.toLower] = @[value.toLower]
+
+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.toLower] = 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.toLower):
+    headers.table[key.toLower] = @[value]
+  else:
+    headers.table[key.toLower].add(value)
+
+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.toLower == value.toLower: return true
+
+proc hasKey*(headers: HttpHeaders, key: string): bool =
+  return headers.table.hasKey(key.toLower())
+
+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 line[start + i] notin {'\c', '\l', '\0'}:
+    i += line.skipWhitespace(start + i)
+    i += line.parseUntil(current, {'\c', '\l', ','}, start + i)
+    list.add(current)
+    if 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)
+  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
+
+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"]