Spaces:
Runtime error
Runtime error
| const proxyAddr = require('@fastify/proxy-addr') | |
| const { | |
| kHasBeenDecorated, | |
| kSchemaBody, | |
| kSchemaHeaders, | |
| kSchemaParams, | |
| kSchemaQuerystring, | |
| kSchemaController, | |
| kOptions, | |
| kRequestCacheValidateFns, | |
| kRouteContext, | |
| kRequestOriginalUrl | |
| } = require('./symbols') | |
| const { FST_ERR_REQ_INVALID_VALIDATION_INVOCATION } = require('./errors') | |
| const HTTP_PART_SYMBOL_MAP = { | |
| body: kSchemaBody, | |
| headers: kSchemaHeaders, | |
| params: kSchemaParams, | |
| querystring: kSchemaQuerystring, | |
| query: kSchemaQuerystring | |
| } | |
| function Request (id, params, req, query, log, context) { | |
| this.id = id | |
| this[kRouteContext] = context | |
| this.params = params | |
| this.raw = req | |
| this.query = query | |
| this.log = log | |
| this.body = undefined | |
| } | |
| Request.props = [] | |
| function getTrustProxyFn (tp) { | |
| if (typeof tp === 'function') { | |
| return tp | |
| } | |
| if (tp === true) { | |
| // Support trusting everything | |
| return null | |
| } | |
| if (typeof tp === 'number') { | |
| // Support trusting hop count | |
| return function (a, i) { return i < tp } | |
| } | |
| if (typeof tp === 'string') { | |
| // Support comma-separated tps | |
| const values = tp.split(',').map(it => it.trim()) | |
| return proxyAddr.compile(values) | |
| } | |
| return proxyAddr.compile(tp) | |
| } | |
| function buildRequest (R, trustProxy) { | |
| if (trustProxy) { | |
| return buildRequestWithTrustProxy(R, trustProxy) | |
| } | |
| return buildRegularRequest(R) | |
| } | |
| function buildRegularRequest (R) { | |
| const props = R.props.slice() | |
| function _Request (id, params, req, query, log, context) { | |
| this.id = id | |
| this[kRouteContext] = context | |
| this.params = params | |
| this.raw = req | |
| this.query = query | |
| this.log = log | |
| this.body = undefined | |
| let prop | |
| for (let i = 0; i < props.length; i++) { | |
| prop = props[i] | |
| this[prop.key] = prop.value | |
| } | |
| } | |
| Object.setPrototypeOf(_Request.prototype, R.prototype) | |
| Object.setPrototypeOf(_Request, R) | |
| _Request.props = props | |
| _Request.parent = R | |
| return _Request | |
| } | |
| function getLastEntryInMultiHeaderValue (headerValue) { | |
| // we use the last one if the header is set more than once | |
| const lastIndex = headerValue.lastIndexOf(',') | |
| return lastIndex === -1 ? headerValue.trim() : headerValue.slice(lastIndex + 1).trim() | |
| } | |
| function buildRequestWithTrustProxy (R, trustProxy) { | |
| const _Request = buildRegularRequest(R) | |
| const proxyFn = getTrustProxyFn(trustProxy) | |
| // This is a more optimized version of decoration | |
| _Request[kHasBeenDecorated] = true | |
| Object.defineProperties(_Request.prototype, { | |
| ip: { | |
| get () { | |
| const addrs = proxyAddr.all(this.raw, proxyFn) | |
| return addrs[addrs.length - 1] | |
| } | |
| }, | |
| ips: { | |
| get () { | |
| return proxyAddr.all(this.raw, proxyFn) | |
| } | |
| }, | |
| host: { | |
| get () { | |
| if (this.ip !== undefined && this.headers['x-forwarded-host']) { | |
| return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-host']) | |
| } | |
| /** | |
| * The last fallback supports the following cases: | |
| * 1. http.requireHostHeader === false | |
| * 2. HTTP/1.0 without a Host Header | |
| * 3. Headers schema that may remove the Host Header | |
| */ | |
| return this.headers.host ?? this.headers[':authority'] ?? '' | |
| } | |
| }, | |
| protocol: { | |
| get () { | |
| if (this.headers['x-forwarded-proto']) { | |
| return getLastEntryInMultiHeaderValue(this.headers['x-forwarded-proto']) | |
| } | |
| if (this.socket) { | |
| return this.socket.encrypted ? 'https' : 'http' | |
| } | |
| } | |
| } | |
| }) | |
| return _Request | |
| } | |
| Object.defineProperties(Request.prototype, { | |
| server: { | |
| get () { | |
| return this[kRouteContext].server | |
| } | |
| }, | |
| url: { | |
| get () { | |
| return this.raw.url | |
| } | |
| }, | |
| originalUrl: { | |
| get () { | |
| /* istanbul ignore else */ | |
| if (!this[kRequestOriginalUrl]) { | |
| this[kRequestOriginalUrl] = this.raw.originalUrl || this.raw.url | |
| } | |
| return this[kRequestOriginalUrl] | |
| } | |
| }, | |
| method: { | |
| get () { | |
| return this.raw.method | |
| } | |
| }, | |
| routeOptions: { | |
| get () { | |
| const context = this[kRouteContext] | |
| const routeLimit = context._parserOptions.limit | |
| const serverLimit = context.server.initialConfig.bodyLimit | |
| const version = context.server.hasConstraintStrategy('version') ? this.raw.headers['accept-version'] : undefined | |
| const options = { | |
| method: context.config?.method, | |
| url: context.config?.url, | |
| bodyLimit: (routeLimit || serverLimit), | |
| attachValidation: context.attachValidation, | |
| logLevel: context.logLevel, | |
| exposeHeadRoute: context.exposeHeadRoute, | |
| prefixTrailingSlash: context.prefixTrailingSlash, | |
| handler: context.handler, | |
| version | |
| } | |
| Object.defineProperties(options, { | |
| config: { | |
| get: () => context.config | |
| }, | |
| schema: { | |
| get: () => context.schema | |
| } | |
| }) | |
| return Object.freeze(options) | |
| } | |
| }, | |
| is404: { | |
| get () { | |
| return this[kRouteContext].config?.url === undefined | |
| } | |
| }, | |
| socket: { | |
| get () { | |
| return this.raw.socket | |
| } | |
| }, | |
| ip: { | |
| get () { | |
| if (this.socket) { | |
| return this.socket.remoteAddress | |
| } | |
| } | |
| }, | |
| host: { | |
| get () { | |
| /** | |
| * The last fallback supports the following cases: | |
| * 1. http.requireHostHeader === false | |
| * 2. HTTP/1.0 without a Host Header | |
| * 3. Headers schema that may remove the Host Header | |
| */ | |
| return this.raw.headers.host ?? this.raw.headers[':authority'] ?? '' | |
| } | |
| }, | |
| hostname: { | |
| get () { | |
| return this.host.split(':', 1)[0] | |
| } | |
| }, | |
| port: { | |
| get () { | |
| // first try taking port from host | |
| const portFromHost = parseInt(this.host.split(':').slice(-1)[0]) | |
| if (!isNaN(portFromHost)) { | |
| return portFromHost | |
| } | |
| // now fall back to port from host/:authority header | |
| const host = (this.headers.host ?? this.headers[':authority'] ?? '') | |
| const portFromHeader = parseInt(host.split(':').slice(-1)[0]) | |
| if (!isNaN(portFromHeader)) { | |
| return portFromHeader | |
| } | |
| // fall back to null | |
| return null | |
| } | |
| }, | |
| protocol: { | |
| get () { | |
| if (this.socket) { | |
| return this.socket.encrypted ? 'https' : 'http' | |
| } | |
| } | |
| }, | |
| headers: { | |
| get () { | |
| if (this.additionalHeaders) { | |
| return Object.assign({}, this.raw.headers, this.additionalHeaders) | |
| } | |
| return this.raw.headers | |
| }, | |
| set (headers) { | |
| this.additionalHeaders = headers | |
| } | |
| }, | |
| getValidationFunction: { | |
| value: function (httpPartOrSchema) { | |
| if (typeof httpPartOrSchema === 'string') { | |
| const symbol = HTTP_PART_SYMBOL_MAP[httpPartOrSchema] | |
| return this[kRouteContext][symbol] | |
| } else if (typeof httpPartOrSchema === 'object') { | |
| return this[kRouteContext][kRequestCacheValidateFns]?.get(httpPartOrSchema) | |
| } | |
| } | |
| }, | |
| compileValidationSchema: { | |
| value: function (schema, httpPart = null) { | |
| const { method, url } = this | |
| if (this[kRouteContext][kRequestCacheValidateFns]?.has(schema)) { | |
| return this[kRouteContext][kRequestCacheValidateFns].get(schema) | |
| } | |
| const validatorCompiler = this[kRouteContext].validatorCompiler || | |
| this.server[kSchemaController].validatorCompiler || | |
| ( | |
| // We compile the schemas if no custom validatorCompiler is provided | |
| // nor set | |
| this.server[kSchemaController].setupValidator(this.server[kOptions]) || | |
| this.server[kSchemaController].validatorCompiler | |
| ) | |
| const validateFn = validatorCompiler({ | |
| schema, | |
| method, | |
| url, | |
| httpPart | |
| }) | |
| // We create a WeakMap to compile the schema only once | |
| // Its done lazily to avoid add overhead by creating the WeakMap | |
| // if it is not used | |
| // TODO: Explore a central cache for all the schemas shared across | |
| // encapsulated contexts | |
| if (this[kRouteContext][kRequestCacheValidateFns] == null) { | |
| this[kRouteContext][kRequestCacheValidateFns] = new WeakMap() | |
| } | |
| this[kRouteContext][kRequestCacheValidateFns].set(schema, validateFn) | |
| return validateFn | |
| } | |
| }, | |
| validateInput: { | |
| value: function (input, schema, httpPart) { | |
| httpPart = typeof schema === 'string' ? schema : httpPart | |
| const symbol = (httpPart != null && typeof httpPart === 'string') && HTTP_PART_SYMBOL_MAP[httpPart] | |
| let validate | |
| if (symbol) { | |
| // Validate using the HTTP Request Part schema | |
| validate = this[kRouteContext][symbol] | |
| } | |
| // We cannot compile if the schema is missed | |
| if (validate == null && (schema == null || | |
| typeof schema !== 'object' || | |
| Array.isArray(schema)) | |
| ) { | |
| throw new FST_ERR_REQ_INVALID_VALIDATION_INVOCATION(httpPart) | |
| } | |
| if (validate == null) { | |
| if (this[kRouteContext][kRequestCacheValidateFns]?.has(schema)) { | |
| validate = this[kRouteContext][kRequestCacheValidateFns].get(schema) | |
| } else { | |
| // We proceed to compile if there's no validate function yet | |
| validate = this.compileValidationSchema(schema, httpPart) | |
| } | |
| } | |
| return validate(input) | |
| } | |
| } | |
| }) | |
| module.exports = Request | |
| module.exports.buildRequest = buildRequest | |