Add the ability to pass title case headers to an HTTP server (#13968)

* Add the ability to pass title case headers to an HTTP server

* Remove unnecessary type spec of titleCase

* Replace convert member proc by isTitleCase

Co-authored-by: wonderix <wonderix@googlemail.com>
This commit is contained in:
wonderix 2020-04-22 17:43:39 +02:00 • committed by GitHub
commit 5571d48c32
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23

View file

@ -17,6 +17,7 @@ import tables, strutils, parseutils
type type
HttpHeaders* = ref object HttpHeaders* = ref object
table*: TableRef[string, seq[string]] table*: TableRef[string, seq[string]]
isTitleCase: bool
HttpHeaderValues* = distinct seq[string] HttpHeaderValues* = distinct seq[string]
@ -100,16 +101,32 @@ const
const httpNewLine* = "\c\L" const httpNewLine* = "\c\L"
const headerLimit* = 10_000 const headerLimit* = 10_000
proc newHttpHeaders*(): HttpHeaders = proc toTitleCase(s: string): string =
result = newString(len(s))
var upper = true
for i in 0..len(s) - 1:
result[i] = if upper: toUpperAscii(s[i]) else: toLowerAscii(s[i])
upper = s[i] == '-'
proc toCaseInsensitive(headers: HttpHeaders, s: string): string {.inline.} =
return if headers.isTitleCase: toTitleCase(s) else: toLowerAscii(s)
proc newHttpHeaders*(titleCase=false): HttpHeaders =
## Returns a new ``HttpHeaders`` object. if ``titleCase`` is set to true,
## headers are passed to the server in title case (e.g. "Content-Length")
new result new result
result.table = newTable[string, seq[string]]() result.table = newTable[string, seq[string]]()
result.isTitleCase = titleCase
proc newHttpHeaders*(keyValuePairs: proc newHttpHeaders*(keyValuePairs:
openArray[tuple[key: string, val: string]]): HttpHeaders = openArray[tuple[key: string, val: string]], titleCase=false): HttpHeaders =
## Returns a new ``HttpHeaders`` object from an array. if ``titleCase`` is set to true,
## headers are passed to the server in title case (e.g. "Content-Length")
new result new result
result.table = newTable[string, seq[string]]() result.table = newTable[string, seq[string]]()
result.isTitleCase = titleCase
for pair in keyValuePairs: for pair in keyValuePairs:
let key = pair.key.toLowerAscii() let key = result.toCaseInsensitive(pair.key)
if key in result.table: if key in result.table:
result.table[key].add(pair.val) result.table[key].add(pair.val)
else: else:
@ -130,7 +147,7 @@ proc `[]`*(headers: HttpHeaders, key: string): HttpHeaderValues =
## ##
## To access multiple values of a key, use the overloaded ``[]`` below or ## To access multiple values of a key, use the overloaded ``[]`` below or
## to get all of them access the ``table`` field directly. ## to get all of them access the ``table`` field directly.
return headers.table[key.toLowerAscii].HttpHeaderValues return headers.table[headers.toCaseInsensitive(key)].HttpHeaderValues
converter toString*(values: HttpHeaderValues): string = converter toString*(values: HttpHeaderValues): string =
return seq[string](values)[0] return seq[string](values)[0]
@ -139,30 +156,30 @@ proc `[]`*(headers: HttpHeaders, key: string, i: int): string =
## Returns the ``i``'th value associated with the given key. If there are ## 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, ## no values associated with the key or the ``i``'th value doesn't exist,
## an exception is raised. ## an exception is raised.
return headers.table[key.toLowerAscii][i] return headers.table[headers.toCaseInsensitive(key)][i]
proc `[]=`*(headers: HttpHeaders, key, value: string) = proc `[]=`*(headers: HttpHeaders, key, value: string) =
## Sets the header entries associated with ``key`` to the specified value. ## Sets the header entries associated with ``key`` to the specified value.
## Replaces any existing values. ## Replaces any existing values.
headers.table[key.toLowerAscii] = @[value] headers.table[headers.toCaseInsensitive(key)] = @[value]
proc `[]=`*(headers: HttpHeaders, key: string, value: seq[string]) = proc `[]=`*(headers: HttpHeaders, key: string, value: seq[string]) =
## Sets the header entries associated with ``key`` to the specified list of ## Sets the header entries associated with ``key`` to the specified list of
## values. ## values.
## Replaces any existing values. ## Replaces any existing values.
headers.table[key.toLowerAscii] = value headers.table[headers.toCaseInsensitive(key)] = value
proc add*(headers: HttpHeaders, key, value: string) = proc add*(headers: HttpHeaders, key, value: string) =
## Adds the specified value to the specified key. Appends to any existing ## Adds the specified value to the specified key. Appends to any existing
## values associated with the key. ## values associated with the key.
if not headers.table.hasKey(key.toLowerAscii): if not headers.table.hasKey(headers.toCaseInsensitive(key)):
headers.table[key.toLowerAscii] = @[value] headers.table[headers.toCaseInsensitive(key)] = @[value]
else: else:
headers.table[key.toLowerAscii].add(value) headers.table[headers.toCaseInsensitive(key)].add(value)
proc del*(headers: HttpHeaders, key: string) = proc del*(headers: HttpHeaders, key: string) =
## Delete the header entries associated with ``key`` ## Delete the header entries associated with ``key``
headers.table.del(key.toLowerAscii) headers.table.del(headers.toCaseInsensitive(key))
iterator pairs*(headers: HttpHeaders): tuple[key, value: string] = iterator pairs*(headers: HttpHeaders): tuple[key, value: string] =
## Yields each key, value pair. ## Yields each key, value pair.
@ -177,7 +194,7 @@ proc contains*(values: HttpHeaderValues, value: string): bool =
if val.toLowerAscii == value.toLowerAscii: return true if val.toLowerAscii == value.toLowerAscii: return true
proc hasKey*(headers: HttpHeaders, key: string): bool = proc hasKey*(headers: HttpHeaders, key: string): bool =
return headers.table.hasKey(key.toLowerAscii()) return headers.table.hasKey(headers.toCaseInsensitive(key))
proc getOrDefault*(headers: HttpHeaders, key: string, proc getOrDefault*(headers: HttpHeaders, key: string,
default = @[""].HttpHeaderValues): HttpHeaderValues = default = @[""].HttpHeaderValues): HttpHeaderValues =
@ -336,3 +353,12 @@ when isMainModule:
doAssert test["foobar"] == "" doAssert test["foobar"] == ""
doAssert parseHeader("foobar:") == ("foobar", @[""]) doAssert parseHeader("foobar:") == ("foobar", @[""])
block: # test title case
var testTitleCase = newHttpHeaders(titleCase=true)
testTitleCase.add("content-length", "1")
doAssert testTitleCase.hasKey("Content-Length")
for key, val in testTitleCase:
doAssert key == "Content-Length"