1 | 'use strict'
|
2 |
|
3 | const proxyAddr = require('proxy-addr')
|
4 | const semver = require('semver')
|
5 | const {
|
6 | FSTDEP005,
|
7 | FSTDEP012,
|
8 | FSTDEP015,
|
9 | FSTDEP016,
|
10 | FSTDEP017,
|
11 | FSTDEP018
|
12 | } = require('./warnings')
|
13 | const {
|
14 | kHasBeenDecorated,
|
15 | kSchemaBody,
|
16 | kSchemaHeaders,
|
17 | kSchemaParams,
|
18 | kSchemaQuerystring,
|
19 | kSchemaController,
|
20 | kOptions,
|
21 | kRequestCacheValidateFns,
|
22 | kRouteContext,
|
23 | kPublicRouteContext,
|
24 | kRequestOriginalUrl
|
25 | } = require('./symbols')
|
26 | const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors')
|
27 |
|
28 | const HTTP_PART_SYMBOL_MAP = {
|
29 | body: kSchemaBody,
|
30 | headers: kSchemaHeaders,
|
31 | params: kSchemaParams,
|
32 | querystring: kSchemaQuerystring,
|
33 | query: kSchemaQuerystring
|
34 | }
|
35 |
|
36 | function Request (id, params, req, query, log, context) {
|
37 | this.id = id
|
38 | this[kRouteContext] = context
|
39 | this.params = params
|
40 | this.raw = req
|
41 | this.query = query
|
42 | this.log = log
|
43 | this.body = undefined
|
44 | }
|
45 | Request.props = []
|
46 |
|
47 | function getTrustProxyFn (tp) {
|
48 | if (typeof tp === 'function') {
|
49 | return tp
|
50 | }
|
51 | if (tp === true) {
|
52 |
|
53 | return function () { return true }
|
54 | }
|
55 | if (typeof tp === 'number') {
|
56 |
|
57 | return function (a, i) { return i < tp }
|
58 | }
|
59 | if (typeof tp === 'string') {
|
60 |
|
61 | const values = tp.split(',').map(it => it.trim())
|
62 | return proxyAddr.compile(values)
|
63 | }
|
64 | return proxyAddr.compile(tp)
|
65 | }
|
66 |
|
67 | function buildRequest (R, trustProxy) {
|
68 | if (trustProxy) {
|
69 | return buildRequestWithTrustProxy(R, trustProxy)
|
70 | }
|
71 |
|
72 | return buildRegularRequest(R)
|
73 | }
|
74 |
|
75 | function buildRegularRequest (R) {
|
76 | const props = R.props.slice()
|
77 | function _Request (id, params, req, query, log, context) {
|
78 | this.id = id
|
79 | this[kRouteContext] = context
|
80 | this.params = params
|
81 | this.raw = req
|
82 | this.query = query
|
83 | this.log = log
|
84 | this.body = undefined
|
85 |
|
86 |
|
87 | var prop
|
88 |
|
89 | for (var i = 0; i < props.length; i++) {
|
90 | prop = props[i]
|
91 | this[prop.key] = prop.value
|
92 | }
|
93 | }
|
94 | Object.setPrototypeOf(_Request.prototype, R.prototype)
|
95 | Object.setPrototypeOf(_Request, R)
|
96 | _Request.props = props
|
97 | _Request.parent = R
|
98 |
|
99 | return _Request
|
100 | }
|
101 |
|
102 | function getLastEntryInMultiHeaderValue (headerValue) {
|
103 |
|
104 | const lastIndex = headerValue.lastIndexOf(',')
|
105 | return lastIndex === -1 ? headerValue.trim() : headerValue.slice(lastIndex + 1).trim()
|
106 | }
|
107 |
|
108 | function buildRequestWithTrustProxy (R, trustProxy) {
|
109 | const _Request = buildRegularRequest(R)
|
110 | const proxyFn = getTrustProxyFn(trustProxy)
|
111 |
|
112 |
|
113 | _Request[kHasBeenDecorated] = true
|
114 |
|
115 | Object.defineProperties(_Request.prototype, {
|
116 | ip: {
|
117 | get () {
|
118 | return proxyAddr(this.raw, proxyFn)
|
119 | }
|
120 | },
|
121 | ips: {
|
122 | get () {
|
123 | return proxyAddr.all(this.raw, proxyFn)
|
124 | }
|
125 | },
|
126 | hostname: {
|
127 | get () {
|
128 | if (this.ip !== undefined && this.headers['x-forwarded-host']) {
|
129 | return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-host'])
|
130 | }
|
131 | return this.headers.host || this.headers[':authority']
|
132 | }
|
133 | },
|
134 | protocol: {
|
135 | get () {
|
136 | if (this.headers['x-forwarded-proto']) {
|
137 | return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-proto'])
|
138 | }
|
139 | if (this.socket) {
|
140 | return this.socket.encrypted ? 'https' : 'http'
|
141 | }
|
142 | }
|
143 | }
|
144 | })
|
145 |
|
146 | return _Request
|
147 | }
|
148 |
|
149 | Object.defineProperties(Request.prototype, {
|
150 | server: {
|
151 | get () {
|
152 | return this[kRouteContext].server
|
153 | }
|
154 | },
|
155 | url: {
|
156 | get () {
|
157 | return this.raw.url
|
158 | }
|
159 | },
|
160 | originalUrl: {
|
161 | get () {
|
162 |
|
163 | if (!this[kRequestOriginalUrl]) {
|
164 | this[kRequestOriginalUrl] = this.raw.originalUrl || this.raw.url
|
165 | }
|
166 | return this[kRequestOriginalUrl]
|
167 | }
|
168 | },
|
169 | method: {
|
170 | get () {
|
171 | return this.raw.method
|
172 | }
|
173 | },
|
174 | context: {
|
175 | get () {
|
176 | FSTDEP012()
|
177 | return this[kRouteContext]
|
178 | }
|
179 | },
|
180 | routerPath: {
|
181 | get () {
|
182 | FSTDEP017()
|
183 | return this[kRouteContext].config?.url
|
184 | }
|
185 | },
|
186 | routeOptions: {
|
187 | get () {
|
188 | const context = this[kRouteContext]
|
189 | const routeLimit = context._parserOptions.limit
|
190 | const serverLimit = context.server.initialConfig.bodyLimit
|
191 | const version = context.server.hasConstraintStrategy('version') ? this.raw.headers['accept-version'] : undefined
|
192 | const options = {
|
193 | method: context.config?.method,
|
194 | url: context.config?.url,
|
195 | bodyLimit: (routeLimit || serverLimit),
|
196 | attachValidation: context.attachValidation,
|
197 | logLevel: context.logLevel,
|
198 | exposeHeadRoute: context.exposeHeadRoute,
|
199 | prefixTrailingSlash: context.prefixTrailingSlash,
|
200 | handler: context.handler,
|
201 | version
|
202 | }
|
203 |
|
204 | Object.defineProperties(options, {
|
205 | config: {
|
206 | get: () => context.config
|
207 | },
|
208 | schema: {
|
209 | get: () => context.schema
|
210 | }
|
211 | })
|
212 |
|
213 | return Object.freeze(options)
|
214 | }
|
215 | },
|
216 | routerMethod: {
|
217 | get () {
|
218 | FSTDEP018()
|
219 | return this[kRouteContext].config?.method
|
220 | }
|
221 | },
|
222 | routeConfig: {
|
223 | get () {
|
224 | FSTDEP016()
|
225 | return this[kRouteContext][kPublicRouteContext]?.config
|
226 | }
|
227 | },
|
228 | routeSchema: {
|
229 | get () {
|
230 | FSTDEP015()
|
231 | return this[kRouteContext][kPublicRouteContext].schema
|
232 | }
|
233 | },
|
234 | is404: {
|
235 | get () {
|
236 | return this[kRouteContext].config?.url === undefined
|
237 | }
|
238 | },
|
239 | connection: {
|
240 | get () {
|
241 |
|
242 | if (semver.gte(process.versions.node, '13.0.0')) {
|
243 | FSTDEP005()
|
244 | }
|
245 | return this.raw.connection
|
246 | }
|
247 | },
|
248 | socket: {
|
249 | get () {
|
250 | return this.raw.socket
|
251 | }
|
252 | },
|
253 | ip: {
|
254 | get () {
|
255 | if (this.socket) {
|
256 | return this.socket.remoteAddress
|
257 | }
|
258 | }
|
259 | },
|
260 | hostname: {
|
261 | get () {
|
262 | return this.raw.headers.host || this.raw.headers[':authority']
|
263 | }
|
264 | },
|
265 | protocol: {
|
266 | get () {
|
267 | if (this.socket) {
|
268 | return this.socket.encrypted ? 'https' : 'http'
|
269 | }
|
270 | }
|
271 | },
|
272 | headers: {
|
273 | get () {
|
274 | if (this.additionalHeaders) {
|
275 | return Object.assign({}, this.raw.headers, this.additionalHeaders)
|
276 | }
|
277 | return this.raw.headers
|
278 | },
|
279 | set (headers) {
|
280 | this.additionalHeaders = headers
|
281 | }
|
282 | },
|
283 | getValidationFunction: {
|
284 | value: function (httpPartOrSchema) {
|
285 | if (typeof httpPartOrSchema === 'string') {
|
286 | const symbol = HTTP_PART_SYMBOL_MAP[httpPartOrSchema]
|
287 | return this[kRouteContext][symbol]
|
288 | } else if (typeof httpPartOrSchema === 'object') {
|
289 | return this[kRouteContext][kRequestCacheValidateFns]?.get(httpPartOrSchema)
|
290 | }
|
291 | }
|
292 | },
|
293 | compileValidationSchema: {
|
294 | value: function (schema, httpPart = null) {
|
295 | const { method, url } = this
|
296 |
|
297 | if (this[kRouteContext][kRequestCacheValidateFns]?.has(schema)) {
|
298 | return this[kRouteContext][kRequestCacheValidateFns].get(schema)
|
299 | }
|
300 |
|
301 | const validatorCompiler = this[kRouteContext].validatorCompiler ||
|
302 | this.server[kSchemaController].validatorCompiler ||
|
303 | (
|
304 |
|
305 |
|
306 | this.server[kSchemaController].setupValidator(this.server[kOptions]) ||
|
307 | this.server[kSchemaController].validatorCompiler
|
308 | )
|
309 |
|
310 | const validateFn = validatorCompiler({
|
311 | schema,
|
312 | method,
|
313 | url,
|
314 | httpPart
|
315 | })
|
316 |
|
317 |
|
318 |
|
319 |
|
320 |
|
321 |
|
322 | if (this[kRouteContext][kRequestCacheValidateFns] == null) {
|
323 | this[kRouteContext][kRequestCacheValidateFns] = new WeakMap()
|
324 | }
|
325 |
|
326 | this[kRouteContext][kRequestCacheValidateFns].set(schema, validateFn)
|
327 |
|
328 | return validateFn
|
329 | }
|
330 | },
|
331 | validateInput: {
|
332 | value: function (input, schema, httpPart) {
|
333 | httpPart = typeof schema === 'string' ? schema : httpPart
|
334 |
|
335 | const symbol = (httpPart != null && typeof httpPart === 'string') && HTTP_PART_SYMBOL_MAP[httpPart]
|
336 | let validate
|
337 |
|
338 | if (symbol) {
|
339 |
|
340 | validate = this[kRouteContext][symbol]
|
341 | }
|
342 |
|
343 |
|
344 | if (validate == null && (schema == null ||
|
345 | typeof schema !== 'object' ||
|
346 | Array.isArray(schema))
|
347 | ) {
|
348 | throw new FST_ERR_REQ_INVALID_VALIDATION_INVOCATION(httpPart)
|
349 | }
|
350 |
|
351 | if (validate == null) {
|
352 | if (this[kRouteContext][kRequestCacheValidateFns]?.has(schema)) {
|
353 | validate = this[kRouteContext][kRequestCacheValidateFns].get(schema)
|
354 | } else {
|
355 |
|
356 | validate = this.compileValidationSchema(schema, httpPart)
|
357 | }
|
358 | }
|
359 |
|
360 | return validate(input)
|
361 | }
|
362 | }
|
363 | })
|
364 |
|
365 | module.exports = Request
|
366 | module.exports.buildRequest = buildRequest
|