Skip to content

Commit fa8a55e

Browse files
authored
Merge pull request #3822 from kanongil/new-url
Use WHATWG URL for request.url
2 parents ae02555 + 582a48f commit fa8a55e

File tree

3 files changed

+223
-38
lines changed

3 files changed

+223
-38
lines changed

lib/config.js

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,7 @@ internals.routeBase = Joi.object({
153153
})
154154
.default(),
155155
plugins: Joi.object(),
156+
queryParser: Joi.func().allow(null).default(null),
156157
response: Joi.object({
157158
emptyStatusCode: Joi.valid(200, 204).default(200),
158159
failAction: internals.failAction,

lib/request.js

Lines changed: 76 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
'use strict';
22

3-
const Url = require('url');
3+
const { URL, URLSearchParams } = require('url');
44

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

7171
// Parse request url
7272

73-
this.setUrl(req.url, this._core.settings.router.stripTrailingSlash);
73+
try {
74+
this.setUrl(req.url, this._core.settings.router.stripTrailingSlash);
75+
}
76+
catch (err) {
77+
Bounce.ignore(err, 'boom');
78+
this.url = err;
79+
}
7480
}
7581

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

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

106-
url = (typeof url === 'string' ? Url.parse(url, true) : Hoek.clone(url));
112+
if (url instanceof URL) {
113+
url = url.href;
114+
}
115+
116+
Hoek.assert(typeof url === 'string', 'Url must be a string or URL object');
117+
118+
const parseFull = url.length === 0 || url[0] !== '/';
119+
try {
120+
if (parseFull) {
121+
url = new URL(url);
122+
}
123+
else {
124+
const hostname = this.info.host || `${this._core.info.host}:${this._core.info.port}`;
125+
url = new URL(url, `${this._core.info.protocol}://${hostname}`);
126+
}
127+
}
128+
catch (err) {
129+
Bounce.ignore(err, TypeError);
130+
131+
throw Boom.boomify(err, { statusCode: 400 });
132+
}
107133

108134
// Apply path modifications
109135

110-
let path = this._core.router.normalize(url.pathname || ''); // pathname excludes query
136+
let path = this._core.router.normalize(url.pathname); // pathname excludes query
111137

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

119-
// Update derived url properties
120-
121-
if (path !== url.pathname) {
122-
url.pathname = path;
123-
url.path = url.search ? path + url.search : path;
124-
url.href = Url.format(url);
125-
}
145+
url.pathname = path;
126146

127147
// Store request properties
128148

129149
this.url = url;
130-
this.query = url.query;
131-
this.path = url.pathname;
150+
this.path = path;
132151

133-
if (url.hostname) {
152+
if (parseFull) {
134153
this.info.hostname = url.hostname;
135154
this.info.host = url.host;
136155
}
@@ -162,6 +181,7 @@ exports = module.exports = internals.Request = class {
162181
}
163182

164183
this._lookup();
184+
this._queryParse();
165185
this._setTimeouts();
166186
await this._lifecycle();
167187
this._reply();
@@ -184,10 +204,8 @@ exports = module.exports = internals.Request = class {
184204

185205
// Validate path
186206

187-
if (!this.path ||
188-
this.path[0] !== '/') {
189-
190-
throw Boom.badRequest('Invalid path');
207+
if (this.url instanceof Error) {
208+
throw this.url;
191209
}
192210
}
193211

@@ -222,6 +240,46 @@ exports = module.exports = internals.Request = class {
222240
}
223241
}
224242

243+
_queryParse() {
244+
245+
const { queryParser } = this._route.settings;
246+
247+
const baseParser = (iterator) => {
248+
249+
const query = Object.create(null);
250+
for (let [key, value] of iterator) {
251+
const entry = query[key];
252+
if (entry !== undefined) {
253+
value = [].concat(entry, value);
254+
}
255+
256+
query[key] = value;
257+
}
258+
259+
return query;
260+
};
261+
262+
if (queryParser) {
263+
try {
264+
let result = queryParser(this);
265+
266+
Hoek.assert(typeof result === 'object' && result !== null, 'Parsed query must be an object');
267+
268+
if (result instanceof URLSearchParams || result instanceof Map) {
269+
result = baseParser(result);
270+
}
271+
272+
this.query = result;
273+
}
274+
catch (err) {
275+
return this._reply(err);
276+
}
277+
}
278+
else {
279+
this.query = baseParser(this.url.searchParams);
280+
}
281+
}
282+
225283
_setTimeouts() {
226284

227285
if (this.raw.req.socket &&

0 commit comments

Comments
 (0)