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:
parent
269a458d74
commit
5571d48c32
1 changed files with 38 additions and 12 deletions
|
|
@ -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"
|
||||||
|
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue