Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

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

Merged
merged 3 commits into from
Apr 22, 2020
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 38 additions & 12 deletions lib/pure/httpcore.nim
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import tables, strutils, parseutils
type
HttpHeaders* = ref object
table*: TableRef[string, seq[string]]
isTitleCase: bool

HttpHeaderValues* = distinct seq[string]

Expand Down Expand Up @@ -100,16 +101,32 @@ const
const httpNewLine* = "\c\L"
const headerLimit* = 10_000

proc newHttpHeaders*(): HttpHeaders =
proc toTitleCase(s: string): string =
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

separate PR to add this into strutils would be nice :)

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
result.table = newTable[string, seq[string]]()
result.isTitleCase = titleCase

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
result.table = newTable[string, seq[string]]()
result.isTitleCase = titleCase
for pair in keyValuePairs:
let key = pair.key.toLowerAscii()
let key = result.toCaseInsensitive(pair.key)
if key in result.table:
result.table[key].add(pair.val)
else:
Expand All @@ -130,7 +147,7 @@ proc `[]`*(headers: HttpHeaders, key: string): HttpHeaderValues =
##
## 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
return headers.table[headers.toCaseInsensitive(key)].HttpHeaderValues

converter toString*(values: HttpHeaderValues): string =
return seq[string](values)[0]
Expand All @@ -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
## no values associated with the key or the ``i``'th value doesn't exist,
## an exception is raised.
return headers.table[key.toLowerAscii][i]
return headers.table[headers.toCaseInsensitive(key)][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]
headers.table[headers.toCaseInsensitive(key)] = @[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
headers.table[headers.toCaseInsensitive(key)] = 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]
if not headers.table.hasKey(headers.toCaseInsensitive(key)):
headers.table[headers.toCaseInsensitive(key)] = @[value]
else:
headers.table[key.toLowerAscii].add(value)
headers.table[headers.toCaseInsensitive(key)].add(value)

proc del*(headers: HttpHeaders, key: string) =
## 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] =
## Yields each key, value pair.
Expand All @@ -177,7 +194,7 @@ proc contains*(values: HttpHeaderValues, value: string): bool =
if val.toLowerAscii == value.toLowerAscii: return true

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,
default = @[""].HttpHeaderValues): HttpHeaderValues =
Expand Down Expand Up @@ -336,3 +353,12 @@ when isMainModule:
doAssert test["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"