Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions lib/config.js
Original file line number Diff line number Diff line change
Expand Up @@ -153,6 +153,7 @@ internals.routeBase = Joi.object({
})
.default(),
plugins: Joi.object(),
queryParser: Joi.func().allow(null).default(null),
response: Joi.object({
emptyStatusCode: Joi.valid(200, 204).default(200),
failAction: internals.failAction,
Expand Down
94 changes: 76 additions & 18 deletions lib/request.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
'use strict';

const Url = require('url');
const { URL, URLSearchParams } = require('url');

const Boom = require('boom');
const Bounce = require('bounce');
Expand Down Expand Up @@ -70,7 +70,13 @@ exports = module.exports = internals.Request = class {

// Parse request url

this.setUrl(req.url, this._core.settings.router.stripTrailingSlash);
try {
this.setUrl(req.url, this._core.settings.router.stripTrailingSlash);
}
catch (err) {
Bounce.ignore(err, 'boom');
this.url = err;
}
}

static generate(server, req, res, options) {
Expand Down Expand Up @@ -103,11 +109,31 @@ exports = module.exports = internals.Request = class {

Hoek.assert(this.params === null, 'Cannot change request URL after routing');

url = (typeof url === 'string' ? Url.parse(url, true) : Hoek.clone(url));
if (url instanceof URL) {
url = url.href;
}

Hoek.assert(typeof url === 'string', 'Url must be a string or URL object');

const parseFull = url.length === 0 || url[0] !== '/';
try {
if (parseFull) {
url = new URL(url);
}
else {
const hostname = this.info.host || `${this._core.info.host}:${this._core.info.port}`;
url = new URL(url, `${this._core.info.protocol}://${hostname}`);
}
}
catch (err) {
Bounce.ignore(err, TypeError);

throw Boom.boomify(err, { statusCode: 400 });
}

// Apply path modifications

let path = this._core.router.normalize(url.pathname || ''); // pathname excludes query
let path = this._core.router.normalize(url.pathname); // pathname excludes query

if (stripTrailingSlash &&
path.length > 1 &&
Expand All @@ -116,21 +142,14 @@ exports = module.exports = internals.Request = class {
path = path.slice(0, -1);
}

// Update derived url properties

if (path !== url.pathname) {
url.pathname = path;
url.path = url.search ? path + url.search : path;
url.href = Url.format(url);
}
url.pathname = path;

// Store request properties

this.url = url;
this.query = url.query;
this.path = url.pathname;
this.path = path;

if (url.hostname) {
if (parseFull) {
this.info.hostname = url.hostname;
this.info.host = url.host;
}
Expand Down Expand Up @@ -162,6 +181,7 @@ exports = module.exports = internals.Request = class {
}

this._lookup();
this._queryParse();
this._setTimeouts();
await this._lifecycle();
this._reply();
Expand All @@ -184,10 +204,8 @@ exports = module.exports = internals.Request = class {

// Validate path

if (!this.path ||
this.path[0] !== '/') {

throw Boom.badRequest('Invalid path');
if (this.url instanceof Error) {
throw this.url;
}
}

Expand Down Expand Up @@ -222,6 +240,46 @@ exports = module.exports = internals.Request = class {
}
}

_queryParse() {

const { queryParser } = this._route.settings;

const baseParser = (iterator) => {

const query = Object.create(null);
for (let [key, value] of iterator) {
const entry = query[key];
if (entry !== undefined) {
value = [].concat(entry, value);
}

query[key] = value;
}

return query;
};

if (queryParser) {
try {
let result = queryParser(this);

Hoek.assert(typeof result === 'object' && result !== null, 'Parsed query must be an object');

if (result instanceof URLSearchParams || result instanceof Map) {
result = baseParser(result);
}

this.query = result;
}
catch (err) {
return this._reply(err);
}
}
else {
this.query = baseParser(this.url.searchParams);
}
}

_setTimeouts() {

if (this.raw.req.socket &&
Expand Down
Loading