diff --git a/src/util/url.ts b/src/util/url.ts index 2c03cc782..66597f0b0 100644 --- a/src/util/url.ts +++ b/src/util/url.ts @@ -2,6 +2,7 @@ import * as url from 'url'; import * as _ from 'lodash'; import { nthIndexOf } from './util'; +import { isIPv6Address } from './ip-utils'; import { Destination } from '../types'; // Is this URL fully qualified? @@ -92,11 +93,14 @@ export const getDestination = (protocol: string, host: string): Destination => { export const normalizeHost = (protocol: string, host: string) => { const { hostname, port } = getDestination(protocol, host); + const normalizedHostname = isIPv6Address(hostname) + ? `[${hostname}]` + : hostname; if (port === getDefaultPort(protocol)) { - return hostname; + return normalizedHostname; } else { - return `${hostname}:${port}`; + return `${normalizedHostname}:${port}`; } } diff --git a/test/integration/subscriptions/client-error-events.spec.ts b/test/integration/subscriptions/client-error-events.spec.ts index 33a4dc208..ceba46782 100644 --- a/test/integration/subscriptions/client-error-events.spec.ts +++ b/test/integration/subscriptions/client-error-events.spec.ts @@ -124,13 +124,13 @@ describe("Client error subscription", () => { let errorPromise = getDeferred(); await server.on('client-error', (e) => errorPromise.resolve(e)); - sendRawRequest(server, 'GET /abc HTTP/1.1\r\nHost: a:1:2\r\n\r\n'); + sendRawRequest(server, 'GET /abc HTTP/1.1\r\nHost: a^1:2\r\n\r\n'); let clientError = await errorPromise; expect(clientError.errorCode).to.equal("ERR_INVALID_URL"); expect(clientError.request.method).to.equal("GET"); - expect(clientError.request.url).to.equal("http://a:1:2/abc"); + expect(clientError.request.url).to.equal("http://a^1:2/abc"); const response = clientError.response as CompletedResponse; expect(response.statusCode).to.equal(400); diff --git a/test/request-utils.spec.ts b/test/request-utils.spec.ts index f0d7a0561..25090306e 100644 --- a/test/request-utils.spec.ts +++ b/test/request-utils.spec.ts @@ -1,8 +1,10 @@ import { Buffer } from 'buffer'; import * as zlib from 'zlib'; +import * as stream from 'stream'; import { expect, nodeOnly } from './test-utils'; -import { buildBodyReader } from '../src/util/request-utils'; +import { buildBodyReader, preprocessRequest } from '../src/util/request-utils'; +import { LastHopEncrypted } from '../src/util/socket-extensions'; nodeOnly(() => { describe("buildBodyReader", () => { @@ -88,4 +90,34 @@ nodeOnly(() => { }); }); -}); \ No newline at end of file + + describe("preprocessRequest", () => { + it('reconstructs valid absolute URLs from bracketed IPv6 host headers', () => { + const req = Object.assign(new stream.PassThrough(), { + method: 'GET', + url: '/api', + headers: { + host: '[::1]:8000' + }, + rawHeaders: ['Host', '[::1]:8000'], + httpVersion: '1.1', + socket: { + [LastHopEncrypted]: false + } + }) as any; + + const result = preprocessRequest(req, { + type: 'request', + serverPort: 45454, + maxBodySize: 1024 + }); + + expect(result).to.not.equal(null); + expect(req.url).to.equal('http://[::1]:8000/api'); + expect(req.destination).to.deep.equal({ + hostname: '::1', + port: 8000 + }); + }); + }); +}); diff --git a/test/url-normalization.spec.ts b/test/url-normalization.spec.ts index ba4589269..88fef2628 100644 --- a/test/url-normalization.spec.ts +++ b/test/url-normalization.spec.ts @@ -1,8 +1,42 @@ -import { normalizeUrl } from '../src/util/url'; +import { normalizeHost, normalizeUrl } from '../src/util/url'; import { expect } from "./test-utils"; describe("URL normalization for matching", () => { + describe("host normalization", () => { + it("should bracket IPv6 hosts with non-default HTTP ports", () => { + expect( + normalizeHost('http', '[::1]:8000') + ).to.equal('[::1]:8000'); + }); + + it("should bracket IPv6 hosts with non-default HTTPS ports", () => { + expect( + normalizeHost('https', '[::1]:8443') + ).to.equal('[::1]:8443'); + }); + + it("should bracket IPv6 hosts when normalizing away the default port", () => { + expect( + normalizeHost('http', '[::1]:80') + ).to.equal('[::1]'); + + expect( + normalizeHost('https', '[::1]:443') + ).to.equal('[::1]'); + }); + + it("should preserve existing formatting for IPv4 and domain hosts", () => { + expect( + normalizeHost('http', '127.0.0.1:8000') + ).to.equal('127.0.0.1:8000'); + + expect( + normalizeHost('https', 'example.com:443') + ).to.equal('example.com'); + }); + }); + it("should do nothing to fully specified URLs", () => { expect( normalizeUrl('https://example.com/abc')