summary refs log tree commit diff stats
path: root/tests/deps/jester-#head/jester/request.nim
blob: 1b837d7287e7cd76c990ececf434336d2347a62e (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
pre { line-height: 125%; }
td.linenos .normal { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
span.linenos { color: inherit; background-color: transparent; padding-left: 5px; padding-right: 5px; }
td.linenos .special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
span.linenos.special { color: #000000; background-color: #ffffc0; padding-left: 5px; padding-right: 5px; }
.highlight .hll { background-color: #ffffcc }
.highlight .c { color: #888888 } /* Comment */
.highlight .err { color: #a61717; background-color: #e3d2d2 } /* Error */
.highlight .k { color: #008800; font-weight: bold } /* Keyword */
.highlight .ch { color: #888888 } /* Comment.Hashbang */
.highlight .cm { color: #888888 } /* Comment.Multiline */
.highlight .cp { color: #cc0000; font-weight: bold } /* Comment.Preproc */
.highlight .cpf { color: #888888 } /* Comment.PreprocFile */
.highlight .c1 { color: #888888 } /* Comment.Single */
.highlight .cs { color: #cc0000; font-weight: bold; background-color: #fff0f0 } /* Comment.Special */
.highlight .gd { color: #000000; background-color: #ffdddd } /* Generic.Deleted */
.highlight .ge { font-style: italic } /* Generic.Emph */
.highlight .ges { font-weight: bold; font-style: italic } /* Generic.EmphStrong */
.highlight .gr { color: #aa0000 } /* Generic.Error */
.highlight .gh { color: #333333 } /* Generic.Heading */
.highlight .gi { color: #000000; background-color: #ddffdd } /* Generic.Inserted */
.highlight .go { color: #888888 } /* Generic.Output */
.highlight .gp { color: #555555 } /* Generic.Prompt */
.highlight .gs { font-weight: bold } /* Generic.Strong */
.highlight .gu { color: #666666 } /* Generic.Subheading */
.highlight .gt { color: #aa0000 } /* Generic.Traceback */
.highlight .kc { color: #008800; font-weight: bold } /* Keyword.Constant */
.highlight .kd { color: #008800; font-weight: bold } /* Keyword.Declaration */
.highlight .kn { color: #008800; font-weight: bold } /* Keyword.Namespace */
.highlight .kp { color: #008800 } /* Keyword.Pseudo */
.highlight .kr { color: #008800; font-weight: bold } /* Keyword.Reserved */
.highlight .kt { color: #888888; font-weight: bold } /* Keyword.Type */
.highlight .m { color: #0000DD; font-weight: bold } /* Literal.Number */
.highlight .s { color: #dd2200; background-color: #fff0f0 } /* Literal.String */
.highlight .na { color: #336699 } /* Name.Attribute */
.highlight .nb { color: #003388 } /* Name.Builtin */
.highlight .nc { color: #bb0066; font-weight: bold } /* Name.Class */
.highlight .no { color: #003366; font-weight: bold } /* Name.Constant */
.highlight .nd { color: #555555 } /* Name.Decorator */
.highlight .ne { color: #bb0066; font-weight: bold } /* Name.Exception */
.highlight .nf { color: #0066bb; font-weight: bold } /* Name.Function */
.highlight .nl { color: #336699; font-style: italic } /* Name.Label */
.highlight .nn { color: #bb0066; font-weight: bold } /* Name.Namespace */
.highlight .py { color: #336699; font-weight: bold } /* Name.Property */
.highlight .nt { color: #bb0066; font-weight: bold } /* Name.Tag */
.highlight .nv { color: #336699 } /* Name.Variable */
.highlight .ow { color: #008800 } /* Operator.Word */
.highlight .w { color: #bbbbbb } /* Text.Whitespace */
.highlight .mb { color: #0000DD; font-weight: bold } /* Literal.Number.Bin */
.highlight .mf { color: #0000DD; font-weight: bold } /* Literal.Number.Float */
.highlight .mh { color: #0000DD; font-weight: bold } /* Literal.Number.Hex */
.highlight .mi { color: #0000DD; font-weight: bold } /* Literal.Number.Integer */
.highlight .mo { color: #0000DD; font-weight: bold } /* Literal.Number.Oct */
.highlight .
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