Skip to content

Commit 09c83fe

Browse files
committed
initates
1 parent e08d770 commit 09c83fe

File tree

6 files changed

+629
-0
lines changed

6 files changed

+629
-0
lines changed

.travis.yml

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
language: c
2+
3+
cache: ccache
4+
cache:
5+
directories:
6+
- .cache
7+
8+
matrix:
9+
include:
10+
# Build and test against the master (stable) and devel branches of Nim
11+
- os: linux
12+
env: CHANNEL=stable
13+
compiler: gcc
14+
15+
# On OSX we only test against clang (gcc is mapped to clang by default)
16+
- os: osx
17+
env: CHANNEL=stable
18+
compiler: clang
19+
20+
- os: windows
21+
env: CHANNEL=stable
22+
compiler: gcc
23+
24+
25+
allow_failures:
26+
# Ignore failures when building against the devel Nim branch
27+
# Also ignore OSX, due to very long build queue
28+
- os: osx
29+
fast_finish: true
30+
31+
# BEGIN: Assuming you rely on external dependencies
32+
# addons: # This will only be executed on Linux
33+
# apt:
34+
# packages:
35+
# - libzip-dev
36+
37+
# before_install:
38+
# # If you want to install an OSX Homebrew dependency
39+
# - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew update ; fi
40+
# - if [[ "$TRAVIS_OS_NAME" == "osx" ]]; then brew install libzip; fi
41+
# ## END: Assuming you rely on external dependencies
42+
43+
install:
44+
- export CHOOSENIM_NO_ANALYTICS=1
45+
- curl https://nim-lang.org/choosenim/init.sh -sSf > init.sh
46+
- sh init.sh -y
47+
- export PATH=~/.nimble/bin:$PATH
48+
- echo "export PATH=~/.nimble/bin:$PATH" >> ~/.profile
49+
- choosenim $CHANNEL
50+
51+
script:
52+
- nimble install -y
53+
- nimble tests

cookies.nimble

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Package
2+
3+
version = "0.1.0"
4+
author = "flywind"
5+
description = "HTTP Cookies for Nim."
6+
license = "Apache-2.0"
7+
srcDir = "src"
8+
9+
10+
11+
# Dependencies
12+
13+
requires "nim >= 1.2.0"
14+
15+
task tests, "Run all tests":
16+
exec "testament r test.nim"

src/cookies.nim

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
import cookies/cookies
2+
3+
export cookies

src/cookies/cookies.nim

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
## This module provides the ``Cookie`` type, which directly maps to Set-Cookie HTTP response headers,
2+
## and the ``CookieJar`` type which contains many cookies.
3+
##
4+
## Overview
5+
## ========================
6+
##
7+
## ``Cookie`` type is used to generate Set-Cookie HTTP response headers.
8+
## Server sends Set-Cookie HTTP response headers to the user agent.
9+
## So the user agent can send them back to the server later.
10+
##
11+
## ``CookieJar`` contains many cookies from the user agent.
12+
##
13+
14+
15+
import options, times, strtabs, parseutils, strutils
16+
17+
18+
type
19+
SameSite* {.pure.} = enum ## The SameSite cookie attribute.
20+
None, Lax, Strict
21+
22+
Cookie* = object ## Cookie type represents Set-Cookie HTTP response headers.
23+
name*, value*: string
24+
expires*: string
25+
maxAge*: Option[int]
26+
domain*: string
27+
path*: string
28+
secure*: bool
29+
httpOnly*: bool
30+
sameSite*: SameSite
31+
32+
CookieJar* = object ## CookieJar type is a collection of cookies.
33+
data: StringTableRef
34+
35+
MissingValueError* = object of ValueError ## Indicates an error associated with Cookie.
36+
37+
38+
proc initCookie*(name, value: string, expires = "", maxAge: Option[int] = none(int),
39+
domain = "", path = "",
40+
secure = false, httpOnly = false, sameSite = Lax): Cookie {.inline.} =
41+
## Initiates Cookie object.
42+
runnableExamples:
43+
let
44+
username = "admin"
45+
message = "ok"
46+
cookie = initCookie(username, message)
47+
48+
doAssert cookie.name == username
49+
doAssert cookie.value == message
50+
51+
result = Cookie(name: name, value: value, expires: expires,
52+
maxAge: maxAge, domain: domain, path: path,
53+
secure: secure, httpOnly: httpOnly, sameSite: sameSite)
54+
55+
proc initCookie*(name, value: string, expires: DateTime|Time,
56+
maxAge: Option[int] = none(int), domain = "", path = "", secure = false, httpOnly = false,
57+
sameSite = Lax): Cookie {.inline.} =
58+
## Initiates Cookie object.
59+
runnableExamples:
60+
import times
61+
62+
63+
let
64+
username = "admin"
65+
message = "ok"
66+
expires = now()
67+
cookie = initCookie(username, message, expires)
68+
69+
doAssert cookie.name == username
70+
doAssert cookie.value == message
71+
72+
result = initCookie(name, value, format(expires.utc,
73+
"ddd',' dd MMM yyyy HH:mm:ss 'GMT'"), maxAge, domain, path, secure,
74+
httpOnly, sameSite)
75+
76+
proc parseParams(cookie: var Cookie, key: string, value: string) {.inline.} =
77+
## Parse Cookie attributes from key-value pairs.
78+
case key.toLowerAscii
79+
of "expires":
80+
if value.len != 0:
81+
cookie.expires = value
82+
of "maxage":
83+
try:
84+
cookie.maxAge = some(parseInt(value))
85+
except ValueError:
86+
cookie.maxAge = none(int)
87+
of "domain":
88+
if value.len != 0:
89+
cookie.domain = value
90+
of "path":
91+
if value.len != 0:
92+
cookie.path = value
93+
of "secure":
94+
cookie.secure = true
95+
of "httponly":
96+
cookie.httpOnly = true
97+
of "samesite":
98+
case value.toLowerAscii
99+
of "none":
100+
cookie.sameSite = None
101+
of "strict":
102+
cookie.sameSite = Strict
103+
else:
104+
cookie.sameSite = Lax
105+
else:
106+
discard
107+
108+
proc initCookie*(text: string): Cookie {.inline.} =
109+
## Initiates Cookie object from strings.
110+
runnableExamples:
111+
doAssert initCookie("foo=bar=baz").name == "foo"
112+
doAssert initCookie("foo=bar=baz").value == "bar=baz"
113+
doAssert initCookie("foo=bar; HttpOnly").httpOnly
114+
115+
var
116+
pos = 0
117+
params: string
118+
name, value: string
119+
first = true
120+
121+
while true:
122+
pos += skipWhile(text, {' ', '\t'}, pos)
123+
pos += parseUntil(text, params, ';', pos)
124+
125+
var start = 0
126+
start += parseUntil(params, name, '=', start)
127+
inc(start) # skip '='
128+
if start < params.len:
129+
value = params[start .. ^1]
130+
else:
131+
value = ""
132+
133+
if first:
134+
if name.len == 0:
135+
raise newException(MissingValueError, "cookie name is missing!")
136+
if value.len == 0:
137+
raise newException(MissingValueError, "cookie valie is missing!")
138+
result.name = name
139+
result.value = value
140+
first = false
141+
else:
142+
parseParams(result, name, value)
143+
if pos >= text.len:
144+
break
145+
inc(pos) # skip ';
146+
147+
proc setCookie*(cookie: Cookie): string =
148+
## Stringifys Cookie object to get Set-Cookie HTTP response headers.
149+
runnableExamples:
150+
import strformat
151+
152+
153+
let
154+
username = "admin"
155+
message = "ok"
156+
cookie = initCookie(username, message)
157+
158+
doAssert setCookie(cookie) == fmt"{username}={message}; SameSite=Lax"
159+
160+
result.add cookie.name & "=" & cookie.value
161+
if cookie.domain.strip.len != 0:
162+
result.add("; Domain=" & cookie.domain)
163+
if cookie.path.strip.len != 0:
164+
result.add("; Path=" & cookie.path)
165+
if cookie.maxAge.isSome:
166+
result.add("; Max-Age=" & $cookie.maxAge.get())
167+
if cookie.expires.strip.len != 0:
168+
result.add("; Expires=" & cookie.expires)
169+
if cookie.secure:
170+
result.add("; Secure")
171+
if cookie.httpOnly:
172+
result.add("; HttpOnly")
173+
if cookie.sameSite != None:
174+
result.add("; SameSite=" & $cookie.sameSite)
175+
176+
proc `$`*(cookie: Cookie): string {.inline.} =
177+
## Stringifys Cookie object to get Set-Cookie HTTP response headers.
178+
runnableExamples:
179+
import strformat
180+
181+
182+
let
183+
username = "admin"
184+
message = "ok"
185+
cookie = initCookie(username, message)
186+
187+
doAssert $cookie == fmt"{username}={message}; SameSite=Lax"
188+
189+
setCookie(cookie)
190+
191+
proc initCookieJar*(): CookieJar {.inline.} =
192+
## Creates a new cookieJar that is empty.
193+
CookieJar(data: newStringTable(mode = modeCaseSensitive))
194+
195+
proc len*(cookieJar: CookieJar): int {.inline.} =
196+
## Returns the number of names in ``cookieJar``.
197+
cookieJar.data.len
198+
199+
proc `[]`*(cookieJar: CookieJar, name: string): string {.inline.} =
200+
## Retrieves the value at ``cookieJar[name]``.
201+
##
202+
## If ``name`` is not in ``cookieJar``, the ``KeyError`` exception is raised.
203+
cookieJar.data[name]
204+
205+
proc getOrDefault*(cookieJar: CookieJar, name: string, default = ""): string {.inline.} =
206+
## Retrieves the value at ``cookieJar[name]`` if ``name`` is in ``cookieJar``. Otherwise, the
207+
## default value is returned(default is "").
208+
cookieJar.data.getOrDefault(name, default)
209+
210+
proc hasKey*(cookieJar: CookieJar, name: string): bool {.inline.} =
211+
## Returns true if ``name`` is in the ``cookieJar``.
212+
cookieJar.data.hasKey(name)
213+
214+
proc contains*(cookieJar: CookieJar, name: string): bool {.inline.} =
215+
## Returns true if ``name`` is in the ``cookieJar``.
216+
## Alias of ``hasKey`` for use with the ``in`` operator.
217+
cookieJar.data.contains(name)
218+
219+
proc `[]=`*(cookieJar: var CookieJar, name: string, value: string) {.inline.} =
220+
## Inserts a ``(name, value)`` pair into ``cookieJar``.
221+
cookieJar.data[name] = value
222+
223+
proc parse*(cookieJar: var CookieJar, text: string) {.inline.} =
224+
## Parses CookieJar from strings.
225+
runnableExamples:
226+
var cookieJar = initCookieJar()
227+
cookieJar.parse("username=netkit; message=ok")
228+
229+
doAssert cookieJar["username"] == "netkit"
230+
doAssert cookieJar["message"] == "ok"
231+
232+
var
233+
pos = 0
234+
name, value: string
235+
while true:
236+
pos += skipWhile(text, {' ', '\t'}, pos)
237+
pos += parseUntil(text, name, '=', pos)
238+
if pos >= text.len:
239+
break
240+
inc(pos) # skip '='
241+
pos += parseUntil(text, value, ';', pos)
242+
cookieJar[name] = move(value)
243+
if pos >= text.len:
244+
break
245+
inc(pos) # skip ';'
246+
247+
iterator pairs*(cookieJar: CookieJar): tuple[name, value: string] =
248+
## Iterates over any ``(name, value)`` pair in the ``cookieJar``.
249+
for (name, value) in cookieJar.data.pairs:
250+
yield (name, value)
251+
252+
iterator keys*(cookieJar: CookieJar): string =
253+
## Iterates over any ``name`` in the ``cookieJar``.
254+
for name in cookieJar.data.keys:
255+
yield name
256+
257+
iterator values*(cookieJar: CookieJar): string =
258+
## Iterates over any ``value`` in the ``cookieJar``.
259+
for value in cookieJar.data.values:
260+
yield value
261+
262+
proc secondsForward*(seconds: Natural): DateTime =
263+
## Forward DateTime in seconds.
264+
getTime().utc + initDuration(seconds = seconds)
265+
266+
proc daysForward*(days: Natural): DateTime =
267+
## Forward DateTime in days.
268+
getTime().utc + initDuration(days = days)
269+
270+
proc timesForward*(nanoseconds, microseconds, milliseconds, seconds, minutes,
271+
hours, days, weeks: Natural = 0): DateTime =
272+
## Forward DateTime in seconds
273+
getTime().utc + initDuration(nanoseconds, microseconds, milliseconds, seconds,
274+
minutes, hours, days, weeks)

tests/config.nims

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
switch("path", "$projectDir/../src")

0 commit comments

Comments
 (0)