Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
a4111d9
Add common intellij paths to .gitignore
torbencarstens Sep 18, 2023
ad46aa4
Use 1.6.14 in CI instead of pre-1.6.0 git sha
torbencarstens Sep 18, 2023
08bec4a
Add 2.0.0 to test matrix
torbencarstens Sep 18, 2023
2fe8aa7
Only test nim-in-action-code/Chapter7 for nim versions < 2.0
torbencarstens Sep 18, 2023
c631652
Bump actions/checkout and iffy/install-nim
torbencarstens Sep 18, 2023
f05bd2b
Run 'git submodule update --init' for 'nimble test' to ensure that al…
torbencarstens Sep 18, 2023
88b2ace
Add 'nimble refresh' and 'nimble install' for 'nimble test' task befo…
torbencarstens Sep 18, 2023
5c9e5d3
Remove trailing whitespace
torbencarstens Sep 18, 2023
fef495a
Add support for duplicate keys in parameters by adding `paramValuesAs…
torbencarstens Sep 18, 2023
b8374b4
Adapt `params` to use `cgi.decodeData` instead of `parseUrlQuery`
torbencarstens Sep 18, 2023
f916b25
Add tests for #247
torbencarstens Sep 18, 2023
d330fad
Fix argument name to iffy/install-nim
torbencarstens Sep 18, 2023
1cce8cc
Auto accept nimble for refresh/install
torbencarstens Sep 18, 2023
944dc39
Run debug actions in CI
torbencarstens Sep 18, 2023
149d5bf
Only debug 2.0.0
torbencarstens Sep 18, 2023
88e4a29
Remove debug actions
torbencarstens Sep 18, 2023
cda4d39
Run nim2 with '--mm:refc'
torbencarstens Sep 18, 2023
85b2692
Remove nim2 from CI
torbencarstens Sep 18, 2023
84035bf
Remove debug when
torbencarstens Sep 18, 2023
44d5868
Revert "Add common intellij paths to .gitignore"
torbencarstens Sep 19, 2023
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
11 changes: 6 additions & 5 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,22 @@ jobs:
matrix:
nimversion:
- 1.4.8
- git:6b97889f44d06f66
- 1.6.14
os:
- ubuntu-latest
- macOS-latest
- windows-latest
steps:
- uses: actions/checkout@v1
- uses: actions/checkout@v4
with:
submodules: true
- uses: iffy/install-nim@v3.2.0
- uses: iffy/install-nim@v4
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
nimversion: ${{ matrix.nimversion }}
version: ${{ matrix.nimversion }}
- name: Test
run: |
nimble --version
nimble refresh -y
nimble test
nimble refresh
4 changes: 4 additions & 0 deletions jester.nimble
Original file line number Diff line number Diff line change
Expand Up @@ -16,5 +16,9 @@ when not defined(windows):
requires "httpbeast >= 0.4.0"

task test, "Runs the test suite.":
when NimMajor < 2:
exec "git submodule update --init"
exec "nimble refresh -y"
exec "nimble install -y"
exec "nimble install -y asynctools@#0e6bdc3ed5bae8c7cc9"
exec "nim c -r tests/tester"
57 changes: 46 additions & 11 deletions jester/request.nim
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import uri, cgi, tables, logging, strutils, re, options
from sequtils import map

import jester/private/utils

Expand Down Expand Up @@ -93,28 +94,62 @@ proc ip*(req: Request): string =

proc params*(req: Request): Table[string, string] =
## Parameters from the pattern and the query string.
##
## Note that this doesn't allow for duplicated keys (it simply returns the last occuring value)
## Use `paramValuesAsSeq` if you need multiple values for a key
if req.patternParams.isSome():
result = req.patternParams.get()
else:
result = initTable[string, string]()

when useHttpBeast:
let query = req.req.path.get("").parseUri().query
var queriesToDecode: seq[string] = @[]
queriesToDecode.add query(req)

let contentType = req.headers.getOrDefault("Content-Type")
if contentType.startswith("application/x-www-form-urlencoded"):
queriesToDecode.add req.body

for query in queriesToDecode:
try:
for key, val in cgi.decodeData(query):
result[key] = decodeUrl(val)
except CgiError:
logging.warn("Incorrect query. Got: $1" % [query])

proc paramValuesAsSeq*(req: Request): Table[string, seq[string]] =
## Parameters from the pattern and the query string.
##
## This allows for duplicated keys in the query (in contrast to `params`)
if req.patternParams.isSome():
let patternParams: Table[string, string] = req.patternParams.get()
var patternParamsSeq: seq[(string, string)] = @[]
for key, val in pairs(patternParams):
patternParamsSeq.add (key, val)

# We are not url-decoding the key/value for the patternParams (matches implementation in `params`
result = sequtils.map(patternParamsSeq,
proc(entry: (string, string)): (string, seq[string]) =
(entry[0], @[entry[1]])
).toTable()
else:
let query = req.req.url.query
result = initTable[string, seq[string]]()

try:
for key, val in cgi.decodeData(query):
result[key] = decodeUrl(val)
except CgiError:
logging.warn("Incorrect query. Got: $1" % [query])
var queriesToDecode: seq[string] = @[]
queriesToDecode.add query(req)

let contentType = req.headers.getOrDefault("Content-Type")
if contentType.startswith("application/x-www-form-urlencoded"):
queriesToDecode.add req.body

for query in queriesToDecode:
try:
parseUrlQuery(req.body, result)
except:
logging.warn("Could not parse URL query.")
for key, value in cgi.decodeData(query):
if result.hasKey(key):
result[key].add value
else:
result[key] = @[value]
except CgiError:
logging.warn("Incorrect query. Got: $1" % [query])

proc formData*(req: Request): MultiData =
let contentType = req.headers.getOrDefault("Content-Type")
Expand Down
41 changes: 41 additions & 0 deletions tests/issue247.nim
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
from std/cgi import decodeUrl
from std/strformat import fmt
from std/strutils import join
import jester

settings:
port = Port(5454)
bindAddr = "127.0.0.1"

proc formatParams(params: Table[string, string]): string =
result = ""
for key, value in params.pairs:
result.add fmt"{key}: {value}"

proc formatSeqParams(params: Table[string, seq[string]]): string =
result = ""
for key, values in params.pairs:
let value = values.join ","
result.add fmt"{key}: {value}"

routes:
get "/":
resp Http200
get "/params":
let params = params request
resp formatParams params
get "/params/@val%23ue":
let params = params request
resp formatParams params
post "/params/@val%23ue":
let params = params request
resp formatParams params
get "/multi":
let params = paramValuesAsSeq request
resp formatSeqParams(params)
get "/@val%23ue":
let params = paramValuesAsSeq request
resp formatSeqParams(params)
post "/@val%23ue":
let params = paramValuesAsSeq request
resp formatSeqParams(params)
113 changes: 106 additions & 7 deletions tests/tester.nim
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,7 @@ proc allTest(useStdLib: bool) =
let resp = waitFor client.get(address & "/foo/issue157")
let headers = resp.headers
check headers["Content-Type"] == "text/css"

test "resp doesn't overwrite headers":
let resp = waitFor client.get(address & "/foo/manyheaders")
let headers = resp.headers
Expand Down Expand Up @@ -271,13 +271,108 @@ proc customRouterTest(useStdLib: bool) =
let body = (waitFor resp.body)
checkpoint body
check body.startsWith("Something bad happened: Foobar")

test "redirect in error":
let resp = waitFor client.get(address & "/definitely404route")
check resp.code == Http303
check resp.headers["location"] == address & "/404"
check (waitFor resp.body) == ""

proc issue247(useStdLib: bool) =
waitFor startServer("issue247.nim", useStdLib)
var client = newAsyncHttpClient(maxRedirects = 0)

suite "issue247 useStdLib=" & $useStdLib:
test "duplicate keys in query":
let resp = waitFor client.get(address & "/multi?a=1&a=2")
check (waitFor resp.body) == "a: 1,2"

test "no duplicate keys in query":
let resp = waitFor client.get(address & "/multi?a=1")
check (waitFor resp.body) == "a: 1"

test "assure that empty values are handled":
let resp = waitFor client.get(address & "/multi?a=1&a=")
check (waitFor resp.body) == "a: 1,"

test "assure that fragment is not parsed":
let resp = waitFor client.get(address & "/multi?a=1&#a=2")
check (waitFor resp.body) == "a: 1"

test "ensure that values are url decoded per default":
let resp = waitFor client.get(address & "/multi?a=1&a=1%232")
check (waitFor resp.body) == "a: 1,1#2"

test "ensure that keys are url decoded per default":
let resp = waitFor client.get(address & "/multi?a%23b=1&a%23b=1%232")
check (waitFor resp.body) == "a#b: 1,1#2"

test "test different keys":
let resp = waitFor client.get(address & "/multi?a=1&b=2")
check (waitFor resp.body) == "b: 2a: 1"

test "ensure that path params aren't escaped":
let resp = waitFor client.get(address & "/hello%23world")
check (waitFor resp.body) == "val%23ue: hello%23world"

test "test path params and query":
let resp = waitFor client.get(address & "/hello%23world?a%23+b=1%23+b")
check (waitFor resp.body) == "a# b: 1# bval%23ue: hello%23world"

test "test percent encoded path param and query param (same key)":
let resp = waitFor client.get(address & "/hello%23world?val%23ue=1%23+b")
check (waitFor resp.body) == "val%23ue: hello%23worldval#ue: 1# b"

test "test path param, query param and x-www-form-urlencoded":
client.headers = newHttpHeaders({"Content-Type": "application/x-www-form-urlencoded"})
let resp = waitFor client.post(address & "/hello%23world?val%23ue=1%23+b", "val%23ue=1%23+b&b=2")
check (waitFor resp.body) == "val%23ue: hello%23worldb: 2val#ue: 1# b,1# b"

test "params duplicate keys in query":
let resp = waitFor client.get(address & "/params?a=1&a=2")
check (waitFor resp.body) == "a: 2"

test "params no duplicate keys in query":
let resp = waitFor client.get(address & "/params?a=1")
check (waitFor resp.body) == "a: 1"

test "params assure that empty values are handled":
let resp = waitFor client.get(address & "/params?a=1&a=")
check (waitFor resp.body) == "a: "

test "params assure that fragment is not parsed":
let resp = waitFor client.get(address & "/params?a=1&#a=2")
check (waitFor resp.body) == "a: 1"

test "params ensure that values are url decoded per default":
let resp = waitFor client.get(address & "/params?a=1&a=1%232")
check (waitFor resp.body) == "a: 1#2"

test "params ensure that keys are url decoded per default":
let resp = waitFor client.get(address & "/params?a%23b=1&a%23b=1%232")
check (waitFor resp.body) == "a#b: 1#2"

test "params test different keys":
let resp = waitFor client.get(address & "/params?a=1&b=2")
check (waitFor resp.body) == "b: 2a: 1"

test "params ensure that path params aren't escaped":
let resp = waitFor client.get(address & "/params/hello%23world")
check (waitFor resp.body) == "val%23ue: hello%23world"

test "params test path params and query":
let resp = waitFor client.get(address & "/params/hello%23world?a%23+b=1%23+b")
check (waitFor resp.body) == "a# b: 1# bval%23ue: hello%23world"

test "params test percent encoded path param and query param (same key)":
let resp = waitFor client.get(address & "/params/hello%23world?val%23ue=1%23+b")
check (waitFor resp.body) == "val#ue: 1# bval%23ue: hello%23world"

test "params test path param, query param and x-www-form-urlencoded":
client.headers = newHttpHeaders({"Content-Type": "application/x-www-form-urlencoded"})
let resp = waitFor client.post(address & "/params/hello%23world?val%23ue=1%23+b", "val%23ue=1%23+b&b=2")
check (waitFor resp.body) == "b: 2val#ue: 1# bval%23ue: hello%23world"

when isMainModule:
try:
allTest(useStdLib=false) # Test HttpBeast.
Expand All @@ -286,10 +381,14 @@ when isMainModule:
issue150(useStdLib=true)
customRouterTest(useStdLib=false)
customRouterTest(useStdLib=true)

# Verify that Nim in Action Tweeter still compiles.
test "Nim in Action - Tweeter":
let path = "tests/nim-in-action-code/Chapter7/Tweeter/src/tweeter.nim"
check execCmd("nim c --path:. " & path) == QuitSuccess
issue247(useStdLib=false)
issue247(useStdLib=true)

# nim-in-action Chapter7 is not compatible with nim >= 2.0
when NimMajor < 2:
# Verify that Nim in Action Tweeter still compiles.
test "Nim in Action - Tweeter":
let path = "tests/nim-in-action-code/Chapter7/Tweeter/src/tweeter.nim"
check execCmd("nim c --path:. " & path) == QuitSuccess
finally:
doAssert execCmd("kill -15 " & $serverProcess.processID()) == QuitSuccess