disable Nimble dependencies for appveyor
This commit is contained in:
parent
bf0993c835
commit
d2208091fa
43 changed files with 27230 additions and 7 deletions
1350
tests/deps/jester-#head/jester.nim
Normal file
1350
tests/deps/jester-#head/jester.nim
Normal file
File diff suppressed because it is too large
Load diff
22
tests/deps/jester-#head/jester.nimble
Normal file
22
tests/deps/jester-#head/jester.nimble
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
# Package
|
||||
|
||||
version = "0.4.0" # Be sure to update jester.jesterVer too!
|
||||
author = "Dominik Picheta"
|
||||
description = "A sinatra-like web framework for Nim."
|
||||
license = "MIT"
|
||||
|
||||
skipFiles = @["todo.markdown"]
|
||||
skipDirs = @["tests"]
|
||||
|
||||
# Deps
|
||||
|
||||
requires "nim >= 0.18.1"
|
||||
|
||||
when not defined(windows):
|
||||
requires "httpbeast >= 0.2.0"
|
||||
|
||||
# For tests
|
||||
requires "asynctools"
|
||||
|
||||
task test, "Runs the test suite.":
|
||||
exec "nimble c -y -r tests/tester"
|
||||
141
tests/deps/jester-#head/jester/patterns.nim
Normal file
141
tests/deps/jester-#head/jester/patterns.nim
Normal file
|
|
@ -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)
|
||||
19
tests/deps/jester-#head/jester/private/errorpages.nim
Normal file
19
tests/deps/jester-#head/jester/private/errorpages.nim
Normal file
|
|
@ -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")
|
||||
195
tests/deps/jester-#head/jester/private/utils.nim
Normal file
195
tests/deps/jester-#head/jester/private/utils.nim
Normal file
|
|
@ -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
|
||||
|
||||
184
tests/deps/jester-#head/jester/request.nim
Normal file
184
tests/deps/jester-#head/jester/request.nim
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue