diff options
Diffstat (limited to 'tests/deps/jester-#head/jester')
-rw-r--r-- | tests/deps/jester-#head/jester/patterns.nim | 141 | ||||
-rw-r--r-- | tests/deps/jester-#head/jester/private/errorpages.nim | 19 | ||||
-rw-r--r-- | tests/deps/jester-#head/jester/private/utils.nim | 195 | ||||
-rw-r--r-- | tests/deps/jester-#head/jester/request.nim | 184 |
4 files changed, 539 insertions, 0 deletions
diff --git a/tests/deps/jester-#head/jester/patterns.nim b/tests/deps/jester-#head/jester/patterns.nim new file mode 100644 index 000000000..52b0d3a15 --- /dev/null +++ b/tests/deps/jester-#head/jester/patterns.nim @@ -0,0 +1,141 @@ +# Copyright (C) 2012-2018 Dominik Picheta +# MIT License - Look at license.txt for details. +import parseutils, tables +type + NodeType* = enum + NodeText, NodeField + Node* = object + typ*: NodeType + text*: string + optional*: bool + + Pattern* = seq[Node] + +#/show/@id/? +proc parsePattern*(pattern: string): Pattern = + result = @[] + template addNode(result: var Pattern, theT: NodeType, theText: string, + isOptional: bool): typed = + block: + var newNode: Node + newNode.typ = theT + newNode.text = theText + newNode.optional = isOptional + result.add(newNode) + + template `{}`(s: string, i: int): char = + if i >= len(s): + '\0' + else: + s[i] + + var i = 0 + var text = "" + while i < pattern.len(): + case pattern[i] + of '@': + # Add the stored text. + if text != "": + result.addNode(NodeText, text, false) + text = "" + # Parse named parameter. + inc(i) # Skip @ + var nparam = "" + i += pattern.parseUntil(nparam, {'/', '?'}, i) + var optional = pattern{i} == '?' + result.addNode(NodeField, nparam, optional) + if pattern{i} == '?': inc(i) # Only skip ?. / should not be skipped. + of '?': + var optionalChar = text[^1] + setLen(text, text.len-1) # Truncate ``text``. + # Add the stored text. + if text != "": + result.addNode(NodeText, text, false) + text = "" + # Add optional char. + inc(i) # Skip ? + result.addNode(NodeText, $optionalChar, true) + of '\\': + inc i # Skip \ + if pattern[i] notin {'?', '@', '\\'}: + raise newException(ValueError, + "This character does not require escaping: " & pattern[i]) + text.add(pattern{i}) + inc i # Skip ``pattern[i]`` + else: + text.add(pattern{i}) + inc(i) + + if text != "": + result.addNode(NodeText, text, false) + +proc findNextText(pattern: Pattern, i: int, toNode: var Node): bool = + ## Finds the next NodeText in the pattern, starts looking from ``i``. + result = false + for n in i..pattern.len()-1: + if pattern[n].typ == NodeText: + toNode = pattern[n] + return true + +proc check(n: Node, s: string, i: int): bool = + let cutTo = (n.text.len-1)+i + if cutTo > s.len-1: return false + return s.substr(i, cutTo) == n.text + +proc match*(pattern: Pattern, s: string): + tuple[matched: bool, params: Table[string, string]] = + var i = 0 # Location in ``s``. + + result.matched = true + result.params = initTable[string, string]() + + for ncount, node in pattern: + case node.typ + of NodeText: + if node.optional: + if check(node, s, i): + inc(i, node.text.len) # Skip over this optional character. + else: + # If it's not there, we have nothing to do. It's optional after all. + discard + else: + if check(node, s, i): + inc(i, node.text.len) # Skip over this + else: + # No match. + result.matched = false + return + of NodeField: + var nextTxtNode: Node + var stopChar = '/' + if findNextText(pattern, ncount, nextTxtNode): + stopChar = nextTxtNode.text[0] + var matchNamed = "" + i += s.parseUntil(matchNamed, stopChar, i) + result.params[node.text] = matchNamed + if matchNamed == "" and not node.optional: + result.matched = false + return + + if s.len != i: + result.matched = false + +when isMainModule: + let f = parsePattern("/show/@id/test/@show?/?") + doAssert match(f, "/show/12/test/hallo/").matched + doAssert match(f, "/show/2131726/test/jjjuuwąąss").matched + doAssert(not match(f, "/").matched) + doAssert(not match(f, "/show//test//").matched) + doAssert(match(f, "/show/asd/test//").matched) + doAssert(not match(f, "/show/asd/asd/test/jjj/").matched) + doAssert(match(f, "/show/@łę¶ŧ←/test/asd/").params["id"] == "@łę¶ŧ←") + + let f2 = parsePattern("/test42/somefile.?@ext?/?") + doAssert(match(f2, "/test42/somefile/").params["ext"] == "") + doAssert(match(f2, "/test42/somefile.txt").params["ext"] == "txt") + doAssert(match(f2, "/test42/somefile.txt/").params["ext"] == "txt") + + let f3 = parsePattern(r"/test32/\@\\\??") + doAssert(match(f3, r"/test32/@\").matched) + doAssert(not match(f3, r"/test32/@\\").matched) + doAssert(match(f3, r"/test32/@\?").matched) diff --git a/tests/deps/jester-#head/jester/private/errorpages.nim b/tests/deps/jester-#head/jester/private/errorpages.nim new file mode 100644 index 000000000..d1e695040 --- /dev/null +++ b/tests/deps/jester-#head/jester/private/errorpages.nim @@ -0,0 +1,19 @@ +# Copyright (C) 2012 Dominik Picheta +# MIT License - Look at license.txt for details. +import htmlgen +proc error*(err, jesterVer: string): string = + return html(head(title(err)), + body(h1(err), + "<hr/>", + p("Jester " & jesterVer), + style = "text-align: center;" + ), + xmlns="http://www.w3.org/1999/xhtml") + +proc routeException*(error: string, jesterVer: string): string = + return html(head(title("Jester route exception")), + body( + h1("An error has occured in one of your routes."), + p(b("Detail: "), error) + ), + xmlns="http://www.w3.org/1999/xhtml") diff --git a/tests/deps/jester-#head/jester/private/utils.nim b/tests/deps/jester-#head/jester/private/utils.nim new file mode 100644 index 000000000..4b00c52a1 --- /dev/null +++ b/tests/deps/jester-#head/jester/private/utils.nim @@ -0,0 +1,195 @@ +# Copyright (C) 2012 Dominik Picheta +# MIT License - Look at license.txt for details. +import parseutils, strtabs, strutils, tables, net, mimetypes, asyncdispatch, os +from cgi import decodeUrl + +const + useHttpBeast* = not defined(windows) and not defined(useStdLib) + +type + MultiData* = OrderedTable[string, tuple[fields: StringTableRef, body: string]] + + Settings* = ref object + staticDir*: string # By default ./public + appName*: string + mimes*: MimeDb + port*: Port + bindAddr*: string + reusePort*: bool + futureErrorHandler*: proc (fut: Future[void]) {.closure, gcsafe.} + + JesterError* = object of Exception + +proc parseUrlQuery*(query: string, result: var Table[string, string]) + {.deprecated: "use stdlib".} = + var i = 0 + i = query.skip("?") + while i < query.len()-1: + var key = "" + var val = "" + i += query.parseUntil(key, '=', i) + if query[i] != '=': + raise newException(ValueError, "Expected '=' at " & $i & + " but got: " & $query[i]) + inc(i) # Skip = + i += query.parseUntil(val, '&', i) + inc(i) # Skip & + result[decodeUrl(key)] = decodeUrl(val) + +template parseContentDisposition(): typed = + var hCount = 0 + while hCount < hValue.len()-1: + var key = "" + hCount += hValue.parseUntil(key, {';', '='}, hCount) + if hValue[hCount] == '=': + var value = hvalue.captureBetween('"', start = hCount) + hCount += value.len+2 + inc(hCount) # Skip ; + hCount += hValue.skipWhitespace(hCount) + if key == "name": name = value + newPart[0][key] = value + else: + inc(hCount) + hCount += hValue.skipWhitespace(hCount) + +proc parseMultiPart*(body: string, boundary: string): MultiData = + result = initOrderedTable[string, tuple[fields: StringTableRef, body: string]]() + var mboundary = "--" & boundary + + var i = 0 + var partsLeft = true + while partsLeft: + var firstBoundary = body.skip(mboundary, i) + if firstBoundary == 0: + raise newException(ValueError, "Expected boundary. Got: " & body.substr(i, i+25)) + i += firstBoundary + i += body.skipWhitespace(i) + + # Headers + var newPart: tuple[fields: StringTableRef, body: string] = ({:}.newStringTable, "") + var name = "" + while true: + if body[i] == '\c': + inc(i, 2) # Skip \c\L + break + var hName = "" + i += body.parseUntil(hName, ':', i) + if body[i] != ':': + raise newException(ValueError, "Expected : in headers.") + inc(i) # Skip : + i += body.skipWhitespace(i) + var hValue = "" + i += body.parseUntil(hValue, {'\c', '\L'}, i) + if toLowerAscii(hName) == "content-disposition": + parseContentDisposition() + newPart[0][hName] = hValue + i += body.skip("\c\L", i) # Skip *one* \c\L + + # Parse body. + while true: + if body[i] == '\c' and body[i+1] == '\L' and + body.skip(mboundary, i+2) != 0: + if body.skip("--", i+2+mboundary.len) != 0: + partsLeft = false + break + break + else: + newPart[1].add(body[i]) + inc(i) + i += body.skipWhitespace(i) + + result.add(name, newPart) + +proc parseMPFD*(contentType: string, body: string): MultiData = + var boundaryEqIndex = contentType.find("boundary=")+9 + var boundary = contentType.substr(boundaryEqIndex, contentType.len()-1) + return parseMultiPart(body, boundary) + +proc parseCookies*(s: string): Table[string, string] = + ## 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. + + result = initTable[string, string]() + var i = 0 + while true: + i += skipWhile(s, {' ', '\t'}, i) + var keystart = i + i += skipUntil(s, {'='}, i) + var keyend = i-1 + if i >= len(s): break + inc(i) # skip '=' + var valstart = i + i += skipUntil(s, {';'}, i) + result[substr(s, keystart, keyend)] = substr(s, valstart, i-1) + if i >= len(s): break + inc(i) # skip ';' + +type + SameSite* = enum + None, Lax, Strict + +proc makeCookie*(key, value, expires: string, domain = "", path = "", + secure = false, httpOnly = false, + sameSite = Lax): string = + result = "" + result.add key & "=" & value + if domain != "": result.add("; Domain=" & domain) + if path != "": result.add("; Path=" & path) + if expires != "": result.add("; Expires=" & expires) + if secure: result.add("; Secure") + if httpOnly: result.add("; HttpOnly") + if sameSite != None: + result.add("; SameSite=" & $sameSite) + +when not declared(tables.getOrDefault): + template getOrDefault*(tab, key): untyped = tab[key] + +when not declared(normalizePath) and not declared(normalizedPath): + proc normalizePath*(path: var string) = + ## Normalize a path. + ## + ## Consecutive directory separators are collapsed, including an initial double slash. + ## + ## On relative paths, double dot (..) sequences are collapsed if possible. + ## On absolute paths they are always collapsed. + ## + ## Warning: URL-encoded and Unicode attempts at directory traversal are not detected. + ## Triple dot is not handled. + let isAbs = isAbsolute(path) + var stack: seq[string] = @[] + for p in split(path, {DirSep}): + case p + of "", ".": + continue + of "..": + if stack.len == 0: + if isAbs: + discard # collapse all double dots on absoluta paths + else: + stack.add(p) + elif stack[^1] == "..": + stack.add(p) + else: + discard stack.pop() + else: + stack.add(p) + + if isAbs: + path = DirSep & join(stack, $DirSep) + elif stack.len > 0: + path = join(stack, $DirSep) + else: + path = "." + + proc normalizedPath*(path: string): string = + ## Returns a normalized path for the current OS. See `<#normalizePath>`_ + result = path + normalizePath(result) + +when isMainModule: + var r = {:}.newStringTable + parseUrlQuery("FirstName=Mickey", r) + echo r + diff --git a/tests/deps/jester-#head/jester/request.nim b/tests/deps/jester-#head/jester/request.nim new file mode 100644 index 000000000..1b837d728 --- /dev/null +++ b/tests/deps/jester-#head/jester/request.nim @@ -0,0 +1,184 @@ +import uri, cgi, tables, logging, strutils, re, options + +import jester/private/utils + +when useHttpBeast: + import httpbeast except Settings + import options, httpcore + + type + NativeRequest* = httpbeast.Request +else: + import asynchttpserver + + type + NativeRequest* = asynchttpserver.Request + +type + Request* = object + req: NativeRequest + patternParams: Option[Table[string, string]] + reMatches: array[MaxSubpatterns, string] + settings*: Settings + +proc body*(req: Request): string = + ## Body of the request, only for POST. + ## + ## You're probably looking for ``formData`` + ## instead. + when useHttpBeast: + req.req.body.get("") + else: + req.req.body + +proc headers*(req: Request): HttpHeaders = + ## Headers received with the request. + ## Retrieving these is case insensitive. + when useHttpBeast: + if req.req.headers.isNone: + newHttpHeaders() + else: + req.req.headers.get() + else: + req.req.headers + +proc path*(req: Request): string = + ## Path of request without the query string. + when useHttpBeast: + let p = req.req.path.get("") + let queryStart = p.find('?') + if unlikely(queryStart != -1): + return p[0 .. queryStart-1] + else: + return p + else: + let u = req.req.url + return u.path + +proc reqMethod*(req: Request): HttpMethod = + ## Request method, eg. HttpGet, HttpPost + when useHttpBeast: + req.req.httpMethod.get() + else: + req.req.reqMethod +proc reqMeth*(req: Request): HttpMethod {.deprecated.} = + req.reqMethod + +proc ip*(req: Request): string = + ## IP address of the requesting client. + when useHttpBeast: + result = req.req.ip + else: + result = req.req.hostname + + let headers = req.headers + if headers.hasKey("REMOTE_ADDR"): + result = headers["REMOTE_ADDR"] + if headers.hasKey("x-forwarded-for"): + result = headers["x-forwarded-for"] + +proc params*(req: Request): Table[string, string] = + ## Parameters from the pattern and the query string. + if req.patternParams.isSome(): + result = req.patternParams.get() + else: + result = initTable[string, string]() + + when useHttpBeast: + let query = req.req.path.get("").parseUri().query + else: + let query = req.req.url.query + + try: + for key, val in cgi.decodeData(query): + result[key] = val + except CgiError: + logging.warn("Incorrect query. Got: $1" % [query]) + + let contentType = req.headers.getOrDefault("Content-Type") + if contentType.startswith("application/x-www-form-urlencoded"): + try: + parseUrlQuery(req.body, result) + except: + logging.warn("Could not parse URL query.") + +proc formData*(req: Request): MultiData = + let contentType = req.headers.getOrDefault("Content-Type") + if contentType.startsWith("multipart/form-data"): + result = parseMPFD(contentType, req.body) + +proc matches*(req: Request): array[MaxSubpatterns, string] = + req.reMatches + +proc secure*(req: Request): bool = + if req.headers.hasKey("x-forwarded-proto"): + let proto = req.headers["x-forwarded-proto"] + case proto.toLowerAscii() + of "https": + result = true + of "http": + result = false + else: + logging.warn("Unknown x-forwarded-proto ", proto) + +proc port*(req: Request): int = + if (let p = req.headers.getOrDefault("SERVER_PORT"); p != ""): + result = p.parseInt + else: + result = if req.secure: 443 else: 80 + +proc host*(req: Request): string = + req.headers.getOrDefault("HOST") + +proc appName*(req: Request): string = + ## This is set by the user in ``run``, it is + ## overriden by the "SCRIPT_NAME" scgi + ## parameter. + req.settings.appName + +proc stripAppName(path, appName: string): string = + result = path + if appname.len > 0: + var slashAppName = appName + if slashAppName[0] != '/' and path[0] == '/': + slashAppName = '/' & slashAppName + + if path.startsWith(slashAppName): + if slashAppName.len() == path.len: + return "/" + else: + return path[slashAppName.len .. path.len-1] + else: + raise newException(ValueError, + "Expected script name at beginning of path. Got path: " & + path & " script name: " & slashAppName) + +proc pathInfo*(req: Request): string = + ## This is ``.path`` without ``.appName``. + req.path.stripAppName(req.appName) + +# TODO: Can cookie keys be duplicated? +proc cookies*(req: Request): Table[string, string] = + ## Cookies from the browser. + if (let cookie = req.headers.getOrDefault("Cookie"); cookie != ""): + result = parseCookies(cookie) + else: + result = initTable[string, string]() + +#[ Protected procs ]# + +proc initRequest*(req: NativeRequest, settings: Settings): Request {.inline.} = + Request( + req: req, + settings: settings + ) + +proc getNativeReq*(req: Request): NativeRequest = + req.req + +#[ Only to be used by our route macro. ]# +proc setPatternParams*(req: var Request, p: Table[string, string]) = + req.patternParams = some(p) + +proc setReMatches*(req: var Request, r: array[MaxSubpatterns, string]) = + req.reMatches = r |