summary refs log tree commit diff stats
path: root/tests/deps/jester-#head/jester
diff options
context:
space:
mode:
Diffstat (limited to 'tests/deps/jester-#head/jester')
-rw-r--r--tests/deps/jester-#head/jester/patterns.nim141
-rw-r--r--tests/deps/jester-#head/jester/private/errorpages.nim19
-rw-r--r--tests/deps/jester-#head/jester/private/utils.nim195
-rw-r--r--tests/deps/jester-#head/jester/request.nim184
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