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

[stdlib] make cookies module modern #17116

Merged
merged 12 commits into from
Feb 24, 2021
35 changes: 22 additions & 13 deletions lib/pure/cookies.nim
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,23 @@

## This module implements helper procs for parsing Cookies.

import strtabs, times
import std/[strtabs, times, options]


type
SameSite* {.pure.} = enum ## The SameSite cookie attribute.
None, Lax, Strict

proc parseCookies*(s: string): StringTableRef =
timotheecour marked this conversation as resolved.
Show resolved Hide resolved
## parses cookies into a string table.
## Parses cookies into a string table.
##
## The proc is meant to parse the Cookie header set by a client, not the
## "Set-Cookie" header set by servers.
##
## Example:
##
## .. code-block::Nim
## doAssert parseCookies("a=1; foo=bar") == {"a": 1, "foo": "bar"}.newStringTable
runnableExamples:
import std/strtabs
let cookieJar = parseCookies("a=1; foo=bar")
assert cookieJar["a"] == "1"
assert cookieJar["foo"] == "bar"

result = newStringTable(modeCaseInsensitive)
var i = 0
Expand All @@ -39,9 +44,10 @@ proc parseCookies*(s: string): StringTableRef =

proc setCookie*(key, value: string, domain = "", path = "",
expires = "", noName = false,
secure = false, httpOnly = false): string =
secure = false, httpOnly = false,
maxAge = none(int), sameSite = SameSite.Lax): string =
ringabout marked this conversation as resolved.
Show resolved Hide resolved
## Creates a command in the format of
## ``Set-Cookie: key=value; Domain=...; ...``
## `Set-Cookie: key=value; Domain=...; ...`
result = ""
if not noName: result.add("Set-Cookie: ")
result.add key & "=" & value
Expand All @@ -50,12 +56,15 @@ proc setCookie*(key, value: string, domain = "", path = "",
if expires != "": result.add("; Expires=" & expires)
if secure: result.add("; Secure")
if httpOnly: result.add("; HttpOnly")
if maxAge.isSome: result.add("; Max-Age=" & $maxAge.get)
if sameSite != SameSite.None: result.add("; SameSite=" & $sameSite)

proc setCookie*(key, value: string, expires: DateTime|Time,
domain = "", path = "", noName = false,
secure = false, httpOnly = false): string =
secure = false, httpOnly = false,
maxAge = none(int), sameSite = SameSite.Lax): string =
## Creates a command in the format of
## ``Set-Cookie: key=value; Domain=...; ...``
return setCookie(key, value, domain, path,
## `Set-Cookie: key=value; Domain=...; ...`
Copy link
Member

Choose a reason for hiding this comment

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

pre-existing but this overload (with differently placed params) seems like a bad design, instead we should expose a proc (eg toGmtString(t: DateTime|Time) and let users call:

setCookie(..., t.toGmtString, ...)

result = setCookie(key, value, domain, path,
format(expires.utc, "ddd',' dd MMM yyyy HH:mm:ss 'GMT'"),
noname, secure, httpOnly)
noname, secure, httpOnly, maxAge, sameSite)
9 changes: 7 additions & 2 deletions tests/stdlib/tcookies.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import cookies, times, strtabs
discard """
targets: "c js"
"""


import std/[cookies, times, strtabs]

let expire = fromUnix(0) + 1.seconds

Expand All @@ -7,7 +12,7 @@ let theCookies = [
setCookie("test", "value", expire.local),
setCookie("test", "value", expire.utc)
]
let expected = "Set-Cookie: test=value; Expires=Thu, 01 Jan 1970 00:00:01 GMT"
let expected = "Set-Cookie: test=value; Expires=Thu, 01 Jan 1970 00:00:01 GMT; SameSite=Lax"
doAssert theCookies == [expected, expected, expected]

let table = parseCookies("uid=1; kp=2")
Expand Down