diff --git a/packages/core/src/integrations/http/get-request-url.ts b/packages/core/src/integrations/http/get-request-url.ts index 024ac704acd1..3de4d99cf339 100644 --- a/packages/core/src/integrations/http/get-request-url.ts +++ b/packages/core/src/integrations/http/get-request-url.ts @@ -23,7 +23,11 @@ export function getRequestOptions(request: HttpClientRequest): HttpRequestOption } export function getRequestUrl(requestOptions: HttpRequestOptions): string { - return String(getRequestUrlObject(requestOptions)); + try { + return String(getRequestUrlObject(requestOptions)); + } catch { + return ''; + } } export function getRequestUrlObject(requestOptions: HttpRequestOptions): URL { @@ -37,7 +41,10 @@ export function getRequestUrlObject(requestOptions: HttpRequestOptions): URL { ? '' : `:${requestOptions.port}`; const path = requestOptions.path ? requestOptions.path : '/'; - return new URL(path, `${protocol}//${hostname}${port}`); + const base = `${protocol}//${hostname}${port}`; + // A path starting with `//` (valid for e.g. S3 object keys) would otherwise be parsed as a + // protocol-relative reference, discarding the request's origin, so we resolve it against the origin. + return new URL(path.startsWith('//') ? `${base}${path}` : path, base); } /** diff --git a/packages/core/test/lib/integrations/http/get-request-url.test.ts b/packages/core/test/lib/integrations/http/get-request-url.test.ts index 55bc1ba90f3a..92c79515f210 100644 --- a/packages/core/test/lib/integrations/http/get-request-url.test.ts +++ b/packages/core/test/lib/integrations/http/get-request-url.test.ts @@ -43,6 +43,21 @@ describe('getRequestUrl', () => { }, 'data:text/plain;hello, world!', ], + // Paths starting with `//` are valid (e.g. S3 object keys) but would otherwise be + // parsed as protocol-relative URLs. See #21627. + [ + { protocol: 'https:', hostname: 'my-bucket.s3.us-east-1.amazonaws.com', port: 443, path: '//test.html' }, + 'https://my-bucket.s3.us-east-1.amazonaws.com//test.html', + ], + [ + { + protocol: 'https:', + hostname: 'my-bucket.s3.us-east-1.amazonaws.com', + port: 443, + path: '//Trust%20scores/test.html', + }, + 'https://my-bucket.s3.us-east-1.amazonaws.com//Trust%20scores/test.html', + ], ])('works with %s', (input: HttpRequestOptions, expected: string | undefined) => { // pretend to be a client request that option-ifies to this value const clientRequest = { @@ -55,4 +70,10 @@ describe('getRequestUrl', () => { expect(String(getRequestUrl(input))).toBe(expected); expect(getRequestUrlFromClientRequest(clientRequest)).toBe(expected); }); + + it('does not throw for unparseable request options, returning an empty string', () => { + const input = { protocol: 'http:', hostname: '', port: 80, path: '//%' } as HttpRequestOptions; + expect(() => getRequestUrl(input)).not.toThrow(); + expect(getRequestUrl(input)).toBe(''); + }); });