Skip to content

Commit ee8475b

Browse files
authored
fix: πŸ› request.url getter fails when using IPv6 and HTTP2 [#4560] (#4559)
* fix: πŸ› request.url getter fails when using HTTP2 and IPv6 * test: add coverage for :authority header fallback and IPv6 URL building Made-with: Cursor * perf: use regex test for IPv6 detection in _isBareIpv6 Made-with: Cursor
1 parent 5095382 commit ee8475b

File tree

2 files changed

+43
-2
lines changed

2 files changed

+43
-2
lines changed

β€Žlib/request.jsβ€Ž

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,9 @@ exports = module.exports = internals.Request = class {
184184
return url;
185185
}
186186

187-
this._url = new Url.URL(`${this._core.info.protocol}://${this.info.host || `${this._core.info.host}:${this._core.info.port}`}${source}`);
187+
const host = this.info.host || this._formatIpv6Host(this._core.info.host, this._core.info.port);
188+
189+
this._url = new Url.URL(`${this._core.info.protocol}://${host}${source}`);
188190
}
189191
else {
190192

@@ -201,6 +203,18 @@ exports = module.exports = internals.Request = class {
201203
return this._url;
202204
}
203205

206+
_isBareIpv6(host) {
207+
208+
// An IPv6 address contains at least two colons.
209+
210+
return /:[^:]*:/.test(host);
211+
}
212+
213+
_formatIpv6Host(host, port) {
214+
215+
return this._isBareIpv6(host) ? `[${host}]:${port}` : `${host}:${port}`;
216+
}
217+
204218
_normalizePath(url, options) {
205219

206220
let path = this._core.router.normalize(url.pathname);
@@ -624,7 +638,7 @@ internals.Info = class {
624638
this._request = request;
625639

626640
const req = request.raw.req;
627-
const host = req.headers.host ? req.headers.host.trim() : '';
641+
const host = (req.headers.host || req.headers[':authority'] || '').trim();
628642
const received = Date.now();
629643

630644
this.received = received;

β€Žtest/request.jsβ€Ž

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -363,6 +363,16 @@ describe('Request', () => {
363363
expect(res.payload).to.equal('shot');
364364
});
365365

366+
it('sets host info from :authority header when host header is absent', async () => {
367+
368+
const server = Hapi.server();
369+
server.route({ method: 'GET', path: '/', handler: (request) => `${request.info.host}|${request.info.hostname}` });
370+
371+
const res = await server.inject({ url: '/', headers: { host: '', ':authority': 'example.com:8080' } });
372+
expect(res.statusCode).to.equal(200);
373+
expect(res.result).to.equal('example.com:8080|example.com');
374+
});
375+
366376
it('generates unique request id', async () => {
367377

368378
const server = Hapi.server();
@@ -1765,6 +1775,23 @@ describe('Request', () => {
17651775
expect(res.statusCode).to.equal(200);
17661776
expect(res.result).to.equal('/test');
17671777
});
1778+
1779+
it('generates valid URL when server host is IPv6 and host header is absent', async () => {
1780+
1781+
const server = Hapi.server({ host: '::1' });
1782+
1783+
const handler = (request) => {
1784+
1785+
delete request.info.host;
1786+
expect(request._url).to.not.exist();
1787+
return request.url.host;
1788+
};
1789+
1790+
server.route({ path: '/test', method: 'GET', handler });
1791+
const res = await server.inject('/test');
1792+
expect(res.statusCode).to.equal(200);
1793+
expect(res.result).to.match(/^\[::1\]:\d+$/);
1794+
});
17681795
});
17691796

17701797
describe('_tap()', () => {

0 commit comments

Comments
Β (0)