summary refs log tree commit diff stats
path: root/tests/deps/jester-#head/jester/request.nim
blob: 7c6a1a96134091dee599bf1a16d51a2cff7449df (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
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
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