From 449963bde1ec231b3da034f1d283d73e89b2bfdc Mon Sep 17 00:00:00 2001 From: Gil Pedersen Date: Sun, 12 Mar 2023 15:28:46 +0100 Subject: [PATCH] Make pre-routed request.route property null --- API.md | 4 +++- lib/compression.js | 2 +- lib/cors.js | 6 +++--- lib/handler.js | 6 +++--- lib/headers.js | 2 +- lib/request.js | 23 +++++++++++++---------- lib/response.js | 9 +++++---- lib/route.js | 6 +++--- lib/security.js | 2 +- lib/toolkit.js | 2 +- lib/transmit.js | 8 ++++---- lib/types/request.d.ts | 2 +- lib/validation.js | 36 +++++++++++++++++++----------------- test/core.js | 22 ++++++++++++++++++++++ test/response.js | 8 ++++---- test/types/index.ts | 2 +- 16 files changed, 85 insertions(+), 55 deletions(-) diff --git a/API.md b/API.md index 40d8ffe88..7a6dccc72 100755 --- a/API.md +++ b/API.md @@ -3619,7 +3619,7 @@ the same. The following is the complete list of steps a request can go through: - always called when `onRequest` extensions exist. - the request path and method can be modified via the [`request.setUrl()`](#request.setUrl()) and [`request.setMethod()`](#request.setMethod()) methods. Changes to the request path or method will impact how the request is routed and can be used for rewrite rules. - [`request.payload`](#request.payload) is `undefined` and can be overridden with any non-`undefined` value to bypass payload processing. - - [`request.route`](#request.route) is unassigned. + - [`request.route`](#request.route) is `null`. - [`request.url`](#request.url) can be `null` if the incoming request path is invalid. - [`request.path`](#request.path) can be an invalid path. @@ -4789,6 +4789,8 @@ The request route information object, where: - `settings` - the [route options](#route-options) object with all defaults applied. - `fingerprint` - the route internal normalized string representing the normalized path. +Returns `null` until the "onRequest" [request lifecycle](https://github.com/hapijs/hapi/blob/master/API.md#request-lifecycle) step is complete. + #### `request.server` Access: read only and the public server interface. diff --git a/lib/compression.js b/lib/compression.js index 3e4c692e4..72ee1ff41 100755 --- a/lib/compression.js +++ b/lib/compression.js @@ -114,6 +114,6 @@ exports = module.exports = internals.Compression = class { const encoder = this.encoders[encoding]; Hoek.assert(encoder !== undefined, `Unknown encoding ${encoding}`); - return encoder(request.route.settings.compression[encoding]); + return encoder(request._route.settings.compression[encoding]); } }; diff --git a/lib/cors.js b/lib/cors.js index 6600531f2..ffd4b13a0 100755 --- a/lib/cors.js +++ b/lib/cors.js @@ -158,14 +158,14 @@ internals.handler = function (request, h) { exports.headers = function (response) { const request = response.request; - const settings = request.route.settings.cors; + const settings = request._route.settings.cors; if (settings._origin !== false) { response.vary('origin'); } - if ((request.info.cors && !request.info.cors.isOriginMatch) || // After route lookup - !exports.matchOrigin(request.headers.origin, request.route.settings.cors)) { // Response from onRequest + if ((request.info.cors && !request.info.cors.isOriginMatch) || // After route lookup + !exports.matchOrigin(request.headers.origin, request._route.settings.cors)) { // Response from onRequest return; } diff --git a/lib/handler.js b/lib/handler.js index 9ae261022..2f0632037 100755 --- a/lib/handler.js +++ b/lib/handler.js @@ -28,7 +28,7 @@ exports.execute = async function (request) { // Handler - const result = await internals.handler(request, request.route.settings.handler); + const result = await internals.handler(request, request._route.settings.handler); if (result._takeover || typeof result === 'symbol') { @@ -41,8 +41,8 @@ exports.execute = async function (request) { internals.handler = async function (request, method, pre) { - const bind = request.route.settings.bind; - const realm = request.route.realm; + const bind = request._route.settings.bind; + const realm = request._route.realm; let response = await request._core.toolkit.execute(method, request, { bind, realm, continue: 'null' }); // Handler diff --git a/lib/headers.js b/lib/headers.js index 69a528e18..15310dba9 100755 --- a/lib/headers.js +++ b/lib/headers.js @@ -16,7 +16,7 @@ exports.cache = function (response) { return; } - const settings = request.route.settings.cache; + const settings = request._route.settings.cache; const policy = settings && request._route._cache && (settings._statuses.has(response.statusCode) || (response.statusCode === 304 && settings._statuses.has(200))); if (policy || diff --git a/lib/request.js b/lib/request.js index 6efaa1511..dd62cdb3a 100755 --- a/lib/request.js +++ b/lib/request.js @@ -54,7 +54,6 @@ exports = module.exports = internals.Request = class { this.preResponses = {}; // Pre response values this.raw = { req, res }; this.response = null; - this.route = this._route.public; this.query = null; this.server = server; this.state = null; @@ -121,6 +120,11 @@ exports = module.exports = internals.Request = class { return this._parseUrl(this.raw.req.url, this._core.settings.router); } + get route() { + + return this.params === null ? null : this._route.public; + } + _initializeUrl() { try { @@ -322,15 +326,14 @@ exports = module.exports = internals.Request = class { this._allowInternals) { this._route = match.route; - this.route = this._route.public; } this.params = match.params ?? {}; this.paramsArray = match.paramsArray ?? []; - if (this.route.settings.cors) { + if (this._route.settings.cors) { this.info.cors = { - isOriginMatch: Cors.matchOrigin(this.headers.origin, this.route.settings.cors) + isOriginMatch: Cors.matchOrigin(this.headers.origin, this._route.settings.cors) }; } } @@ -338,12 +341,12 @@ exports = module.exports = internals.Request = class { _setTimeouts() { if (this.raw.req.socket && - this.route.settings.timeout.socket !== undefined) { + this._route.settings.timeout.socket !== undefined) { - this.raw.req.socket.setTimeout(this.route.settings.timeout.socket || 0); // Value can be false or positive + this.raw.req.socket.setTimeout(this._route.settings.timeout.socket || 0); // Value can be false or positive } - let serverTimeout = this.route.settings.timeout.server; + let serverTimeout = this._route.settings.timeout.server; if (!serverTimeout) { return; } @@ -580,7 +583,7 @@ exports = module.exports = internals.Request = class { _log(tags, data, channel = 'internal') { if (!this._core.events.hasListeners('request') && - !this.route.settings.log.collect) { + !this._route.settings.log.collect) { return; } @@ -597,7 +600,7 @@ exports = module.exports = internals.Request = class { event = () => [this, { request: this.info.id, timestamp, tags, data: data(), channel }]; } - if (this.route.settings.log.collect) { + if (this._route.settings.log.collect) { if (typeof data === 'function') { event = event(); } @@ -735,7 +738,7 @@ internals.event = function ({ request }, event, err) { // called _reply(), in which case this call is ignored and the transmit logic is responsible for // handling the abort. - request._reply(new Boom.Boom('Request aborted', { statusCode: request.route.settings.response.disconnectStatusCode, data: request.response })); + request._reply(new Boom.Boom('Request aborted', { statusCode: request._route.settings.response.disconnectStatusCode, data: request.response })); if (request._events) { request._events.emit('disconnect'); diff --git a/lib/response.js b/lib/response.js index 2acd8320a..5cbdbcd4c 100755 --- a/lib/response.js +++ b/lib/response.js @@ -600,11 +600,12 @@ exports = module.exports = internals.Response = class { let payload = source; if (jsonify) { + const fallback = this.request._route.settings.json; const options = this.settings.stringify ?? {}; - const space = options.space ?? this.request.route.settings.json.space; - const replacer = options.replacer ?? this.request.route.settings.json.replacer; - const suffix = options.suffix ?? this.request.route.settings.json.suffix ?? ''; - const escape = this.request.route.settings.json.escape; + const space = options.space ?? fallback.space; + const replacer = options.replacer ?? fallback.replacer; + const suffix = options.suffix ?? fallback.suffix; + const escape = options.escape ?? fallback.escape; try { if (replacer || space) { diff --git a/lib/route.js b/lib/route.js index 0fb4e0d49..491b78ed4 100755 --- a/lib/route.js +++ b/lib/route.js @@ -396,7 +396,7 @@ internals.state = async function (request) { parseError.header = cookies; - return request._core.toolkit.failAction(request, request.route.settings.state.failAction, parseError, { tags: ['state', 'error'] }); + return request._core.toolkit.failAction(request, request._route.settings.state.failAction, parseError, { tags: ['state', 'error'] }); }; @@ -417,7 +417,7 @@ internals.payload = async function (request) { } try { - const { payload, mime } = await Subtext.parse(request.raw.req, request._tap(), request.route.settings.payload); + const { payload, mime } = await Subtext.parse(request.raw.req, request._tap(), request._route.settings.payload); request._isPayloadPending = !!payload?._readableState; request.mime = mime; @@ -433,7 +433,7 @@ internals.payload = async function (request) { request.mime = err.mime; request.payload = null; - return request._core.toolkit.failAction(request, request.route.settings.payload.failAction, err, { tags: ['payload', 'error'] }); + return request._core.toolkit.failAction(request, request._route.settings.payload.failAction, err, { tags: ['payload', 'error'] }); } }; diff --git a/lib/security.js b/lib/security.js index de8776114..13c1f3a01 100755 --- a/lib/security.js +++ b/lib/security.js @@ -55,7 +55,7 @@ exports.route = function (settings) { exports.headers = function (response) { - const security = response.request.route.settings.security; + const security = response.request._route.settings.security; if (security._hsts) { response._header('strict-transport-security', security._hsts, { override: false }); diff --git a/lib/toolkit.js b/lib/toolkit.js index ded406f5f..e6b1f626c 100755 --- a/lib/toolkit.js +++ b/lib/toolkit.js @@ -130,7 +130,7 @@ exports.Manager = class { throw err; } - return this.execute(failAction, request, { realm: request.route.realm, args: [options.details ?? err] }); + return this.execute(failAction, request, { realm: request._route.realm, args: [options.details ?? err] }); } }; diff --git a/lib/transmit.js b/lib/transmit.js index 7a1424153..47a2a97c9 100755 --- a/lib/transmit.js +++ b/lib/transmit.js @@ -143,7 +143,7 @@ internals.length = function (response) { if (length === 0 && !response._statusCode && response.statusCode === 200 && - request.route.settings.response.emptyStatusCode !== 200) { + request._route.settings.response.emptyStatusCode !== 200) { response.code(204); delete response.headers['content-length']; @@ -158,7 +158,7 @@ internals.range = function (response, length) { const request = response.request; if (!length || - !request.route.settings.response.ranges || + !request._route.settings.response.ranges || request.method !== 'get' || response.statusCode !== 200) { @@ -301,7 +301,7 @@ internals.end = function (env, event, err) { const origResponse = request.response; const error = err ? Boom.boomify(err) : - new Boom.Boom(`Request ${event}`, { statusCode: request.route.settings.response.disconnectStatusCode, data: origResponse }); + new Boom.Boom(`Request ${event}`, { statusCode: request._route.settings.response.disconnectStatusCode, data: origResponse }); request._setResponse(error); @@ -309,7 +309,7 @@ internals.end = function (env, event, err) { if (request.raw.res[Config.symbol]) { request.raw.res[Config.symbol].error = event ? error : - new Boom.Boom(`Response error`, { statusCode: request.route.settings.response.disconnectStatusCode, data: origResponse }); + new Boom.Boom(`Response error`, { statusCode: request._route.settings.response.disconnectStatusCode, data: origResponse }); } if (event) { diff --git a/lib/types/request.d.ts b/lib/types/request.d.ts index f0711afdd..ade71e661 100644 --- a/lib/types/request.d.ts +++ b/lib/types/request.d.ts @@ -451,7 +451,7 @@ export interface Request extends Podium { * [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-requestroute) * [See docs](https://github.com/hapijs/hapi/blob/master/API.md#-requestrouteauthaccessrequest) */ - readonly route: RequestRoute; + readonly route: RequestRoute | null; /** * Access: read only and the public server interface. diff --git a/lib/validation.js b/lib/validation.js index e94bf1349..6de00bd53 100755 --- a/lib/validation.js +++ b/lib/validation.js @@ -104,6 +104,7 @@ exports.state = function (request) { internals.input = async function (source, request) { + const settings = request._route.settings.validate; const localOptions = { context: { headers: request.headers, @@ -113,18 +114,18 @@ internals.input = async function (source, request) { state: request.state, auth: request.auth, app: { - route: request.route.settings.app, + route: request._route.settings.app, request: request.app } } }; delete localOptions.context[source]; - Hoek.merge(localOptions, request.route.settings.validate.options); + Hoek.merge(localOptions, settings.options); try { - const schema = request.route.settings.validate[source]; - const bind = request.route.settings.bind; + const schema = settings[source]; + const bind = request._route.settings.bind; var value = await (typeof schema !== 'function' ? internals.validate(request[source], schema, localOptions) : schema.call(bind, request[source], localOptions)); return; @@ -139,7 +140,7 @@ internals.input = async function (source, request) { } } - if (request.route.settings.validate.failAction === 'ignore') { + if (settings.failAction === 'ignore') { return; } @@ -155,21 +156,22 @@ internals.input = async function (source, request) { } } - if (request.route.settings.validate.errorFields) { - for (const field in request.route.settings.validate.errorFields) { - detailedError.output.payload[field] = request.route.settings.validate.errorFields[field]; + if (settings.errorFields) { + for (const field in settings.errorFields) { + detailedError.output.payload[field] = settings.errorFields[field]; } } - return request._core.toolkit.failAction(request, request.route.settings.validate.failAction, defaultError, { details: detailedError, tags: ['validation', 'error', source] }); + return request._core.toolkit.failAction(request, settings.failAction, defaultError, { details: detailedError, tags: ['validation', 'error', source] }); }; exports.response = async function (request) { - if (request.route.settings.response.sample) { + const settings = request._route.settings.response; + if (settings.sample) { const currentSample = Math.ceil(Math.random() * 100); - if (currentSample > request.route.settings.response.sample) { + if (currentSample > settings.sample) { return; } } @@ -177,14 +179,14 @@ exports.response = async function (request) { const response = request.response; const statusCode = response.isBoom ? response.output.statusCode : response.statusCode; - const statusSchema = request.route.settings.response.status[statusCode]; + const statusSchema = settings.status[statusCode]; if (statusCode >= 400 && !statusSchema) { return; // Do not validate errors by default } - const schema = statusSchema !== undefined ? statusSchema : request.route.settings.response.schema; + const schema = statusSchema !== undefined ? statusSchema : settings.schema; if (schema === null) { return; // No rules } @@ -204,14 +206,14 @@ exports.response = async function (request) { state: request.state, auth: request.auth, app: { - route: request.route.settings.app, + route: request._route.settings.app, request: request.app } } }; const source = response.isBoom ? response.output.payload : response.source; - Hoek.merge(localOptions, request.route.settings.response.options); + Hoek.merge(localOptions, settings.options); try { let value; @@ -224,7 +226,7 @@ exports.response = async function (request) { } if (value !== undefined && - request.route.settings.response.modify) { + settings.modify) { if (response.isBoom) { response.output.payload = value; @@ -235,7 +237,7 @@ exports.response = async function (request) { } } catch (err) { - return request._core.toolkit.failAction(request, request.route.settings.response.failAction, err, { tags: ['validation', 'response', 'error'] }); + return request._core.toolkit.failAction(request, settings.failAction, err, { tags: ['validation', 'response', 'error'] }); } }; diff --git a/test/core.js b/test/core.js index 58a7a18d6..442d55e9d 100755 --- a/test/core.js +++ b/test/core.js @@ -1660,6 +1660,28 @@ describe('Core', () => { describe('onRequest', () => { + it('request object has null route and params', async () => { + + const server = Hapi.server(); + + const onRequest = new Promise((resolve) => { + + server.ext('onRequest', (request, h) => { + + resolve(request); + return h.continue; + }); + }); + + const req = server.inject('/'); + const request = await onRequest; + expect(request.route).to.be.null(); + expect(request.params).to.be.null(); + + const res = await req; + expect(res.statusCode).to.equal(404); + }); + it('replies with custom response', async () => { const server = Hapi.server(); diff --git a/test/response.js b/test/response.js index 61056a1ba..1c6d22b02 100755 --- a/test/response.js +++ b/test/response.js @@ -1124,14 +1124,14 @@ describe('Response', () => { const handler = (request, h) => { - return h.response({ a: 1, b: 2, '<': '&' }).type('application/x-test').spaces(2).replacer(['a']).suffix('\n').escape(false); + return h.response({ a: '&', b: 2, '<': '&' }).type('application/x-test').spaces(2).replacer(['a']).suffix('\n').escape(true); }; const server = Hapi.server(); server.route({ method: 'GET', path: '/', handler }); const res = await server.inject('/'); - expect(res.payload).to.equal('{\n \"a\": 1\n}\n'); + expect(res.payload).to.equal('{\n \"a\": "\\u0026"\n}\n'); expect(res.headers['content-type']).to.equal('application/x-test'); }); @@ -1139,14 +1139,14 @@ describe('Response', () => { const handler = (request, h) => { - return h.response({ a: 1, b: 2, '<': '&' }).type('application/x-test').escape(false).replacer(['a']).suffix('\n').spaces(2); + return h.response({ a: '&', b: 2, '<': '&' }).type('application/x-test').escape(true).replacer(['a']).suffix('\n').spaces(2); }; const server = Hapi.server(); server.route({ method: 'GET', path: '/', handler }); const res = await server.inject('/'); - expect(res.payload).to.equal('{\n \"a\": 1\n}\n'); + expect(res.payload).to.equal('{\n \"a\": "\\u0026"\n}\n'); expect(res.headers['content-type']).to.equal('application/x-test'); }); diff --git a/test/types/index.ts b/test/types/index.ts index 3b18fe611..fb9216dfc 100644 --- a/test/types/index.ts +++ b/test/types/index.ts @@ -51,7 +51,7 @@ const route: ServerRoute = { check.type>(request.params); check.type(request.server.app.multi!); - check.type(request.route.settings.app!.prefix); + check.type(request.route!.settings.app!.prefix); return 'hello!' }