From 768b55e2a384da120857d0c6b5b61fbb42ed217a Mon Sep 17 00:00:00 2001 From: Xia Chao <236466140+bun-unsafe@users.noreply.github.com> Date: Tue, 1 Sep 2026 18:55:59 +0800 Subject: [PATCH 1/2] fix: parse absolute-form request targets in request.URL href already treats GET http://host/path as a complete URL. request.URL concatenated protocol://host onto that string, producing a garbage WHATWG href. --- __tests__/request/whatwg-url.test.js | 72 +++++++++++++++++++++++++++- lib/request.js | 5 +- 2 files changed, 75 insertions(+), 2 deletions(-) diff --git a/__tests__/request/whatwg-url.test.js b/__tests__/request/whatwg-url.test.js index 05f5ffa0d..e07872da5 100644 --- a/__tests__/request/whatwg-url.test.js +++ b/__tests__/request/whatwg-url.test.js @@ -1,7 +1,8 @@ 'use strict' const { describe, it } = require('node:test') -const request = require('../../test-helpers/context').request +const context = require('../../test-helpers/context') +const request = context.request const assert = require('node:assert/strict') describe('req.URL', () => { @@ -22,4 +23,73 @@ describe('req.URL', () => { req.header.host = 'invalid host' assert.deepStrictEqual(req.URL, Object.create(null)) }) + + describe('absolute-form request target', () => { + it('should parse http absolute-form like href', () => { + const ctx = context({ + url: 'http://example.com/foo?q=1', + headers: { host: '127.0.0.1' } + }) + assert.strictEqual(ctx.href, 'http://example.com/foo?q=1') + assert.strictEqual(ctx.URL.href, 'http://example.com/foo?q=1') + assert.strictEqual(ctx.URL.pathname, '/foo') + assert.strictEqual(ctx.URL.search, '?q=1') + }) + + it('should parse https absolute-form', () => { + const ctx = context({ + url: 'https://example.com/foo?q=1', + headers: { host: '127.0.0.1' } + }) + assert.strictEqual(ctx.URL.href, 'https://example.com/foo?q=1') + assert.strictEqual(ctx.URL.protocol, 'https:') + }) + + it('should parse uppercase HTTP:// absolute-form', () => { + const ctx = context({ + url: 'HTTP://example.com/foo', + headers: { host: '127.0.0.1' } + }) + assert.strictEqual(ctx.href, 'HTTP://example.com/foo') + assert.strictEqual(ctx.URL.href, 'http://example.com/foo') + assert.strictEqual(ctx.URL.pathname, '/foo') + }) + + it('should parse absolute-form with port', () => { + const ctx = context({ + url: 'http://example.com:8080/foo?q=1', + headers: { host: '127.0.0.1' } + }) + assert.strictEqual(ctx.URL.href, 'http://example.com:8080/foo?q=1') + assert.strictEqual(ctx.URL.host, 'example.com:8080') + }) + + it('should parse absolute-form when Host matches the URL host', () => { + const ctx = context({ + url: 'http://example.com/foo?q=1', + headers: { host: 'example.com' } + }) + assert.strictEqual(ctx.URL.href, 'http://example.com/foo?q=1') + assert.strictEqual(ctx.URL.pathname, '/foo') + }) + + it('should parse absolute-form when Host is empty', () => { + const ctx = context({ + url: 'http://example.com/foo', + headers: { host: '' } + }) + assert.strictEqual(ctx.URL.href, 'http://example.com/foo') + }) + + it('should still follow originalUrl after path rewrite', () => { + const ctx = context({ + url: 'http://example.com/foo?q=1', + headers: { host: '127.0.0.1' } + }) + ctx.path = '/bar' + assert.strictEqual(ctx.url, 'http://example.com/bar?q=1') + assert.strictEqual(ctx.href, 'http://example.com/foo?q=1') + assert.strictEqual(ctx.URL.pathname, '/foo') + }) + }) }) diff --git a/lib/request.js b/lib/request.js index 23045012a..3b6828eda 100644 --- a/lib/request.js +++ b/lib/request.js @@ -298,7 +298,10 @@ module.exports = { if (!this.memoizedURL) { const originalUrl = this.originalUrl || '' // avoid undefined in template string try { - this.memoizedURL = new URL(`${this.protocol}://${this.host}${originalUrl}`) + // support: `GET http://example.com/foo` (same branch as href) + this.memoizedURL = /^https?:\/\//i.test(originalUrl) + ? new URL(originalUrl) + : new URL(`${this.protocol}://${this.host}${originalUrl}`) } catch (err) { this.memoizedURL = Object.create(null) } From 0c9866c5a2b56e79fb53a308b62ca5a0185fe109 Mon Sep 17 00:00:00 2001 From: Xia Chao <236466140+bun-unsafe@users.noreply.github.com> Date: Wed, 2 Sep 2026 13:30:44 +0800 Subject: [PATCH 2/2] style: drop redundant comment on request.URL --- lib/request.js | 1 - 1 file changed, 1 deletion(-) diff --git a/lib/request.js b/lib/request.js index 3b6828eda..db6724a64 100644 --- a/lib/request.js +++ b/lib/request.js @@ -298,7 +298,6 @@ module.exports = { if (!this.memoizedURL) { const originalUrl = this.originalUrl || '' // avoid undefined in template string try { - // support: `GET http://example.com/foo` (same branch as href) this.memoizedURL = /^https?:\/\//i.test(originalUrl) ? new URL(originalUrl) : new URL(`${this.protocol}://${this.host}${originalUrl}`)