From 8b2b8ea146356341b01126e20fc6715c50024e94 Mon Sep 17 00:00:00 2001 From: Pooya Parsa Date: Wed, 25 Mar 2026 20:50:33 +0100 Subject: [PATCH] fix(web-outgoing): handle object target in redirect host rewrite When `options.target` is a `ProxyTargetDetailed` object (not a string or URL), `setRedirectHostRewrite` crashed with `Invalid URL` because `new URL()` cannot parse a plain object. Construct the URL from the object's `protocol`, `host`/`hostname`, and `port` properties instead. Upstream: https://github.com/http-party/node-http-proxy/pull/1600 --- src/middleware/web-outgoing.ts | 20 +++++++++++++++++-- test/middleware/web-outgoing.test.ts | 30 ++++++++++++++++++++++++++++ 2 files changed, 48 insertions(+), 2 deletions(-) diff --git a/src/middleware/web-outgoing.ts b/src/middleware/web-outgoing.ts index 9c662f1..2625834 100644 --- a/src/middleware/web-outgoing.ts +++ b/src/middleware/web-outgoing.ts @@ -1,4 +1,5 @@ import { rewriteCookieProperty } from "../_utils.ts"; +import type { ProxyTarget, ProxyTargetDetailed } from "../types.ts"; import { type ProxyOutgoingMiddleware, defineProxyOutgoingMiddleware } from "./_utils.ts"; const redirectRegex = /^201|30([1278])$/; @@ -37,8 +38,7 @@ export const setRedirectHostRewrite = defineProxyOutgoingMiddleware( proxyRes.headers.location && redirectRegex.test(String(proxyRes.statusCode)) ) { - const target = - options.target instanceof URL ? options.target : new URL(options.target as string | URL); + const target = _toURL(options.target!); const u = new URL(proxyRes.headers.location, target); // Make sure the redirected host matches the target host before rewriting @@ -146,3 +146,19 @@ export const webOutgoingMiddleware: readonly ProxyOutgoingMiddleware[] = [ writeHeaders, writeStatusCode, ] as const; + +// --- Internal --- + +function _toURL(target: ProxyTarget): URL { + if (target instanceof URL) { + return target; + } + if (typeof target === "string") { + return new URL(target); + } + const protocol = (target as ProxyTargetDetailed).protocol || "http:"; + const host = + (target as ProxyTargetDetailed).host || (target as ProxyTargetDetailed).hostname || "localhost"; + const port = (target as ProxyTargetDetailed).port; + return new URL(`${protocol}//${host}${port ? ":" + port : ""}`); +} diff --git a/test/middleware/web-outgoing.test.ts b/test/middleware/web-outgoing.test.ts index 4706976..7596383 100644 --- a/test/middleware/web-outgoing.test.ts +++ b/test/middleware/web-outgoing.test.ts @@ -201,6 +201,36 @@ describe("middleware:web-outgoing", () => { }); }); + describe("handles object target (ProxyTargetDetailed)", () => { + it("rewrites location when target is an object with hostRewrite", () => { + ctx.options.target = { + protocol: "http:", + host: "backend.com", + hostname: "backend.com", + }; + ctx.options.hostRewrite = "ext-manual.com"; + webOutgoing.setRedirectHostRewrite( + ctx.req, + stubServerResponse(), + ctx.proxyRes, + ctx.options, + ); + expect(ctx.proxyRes.headers.location).to.eql("http://ext-manual.com/"); + }); + + it("rewrites location when target is a URL instance", () => { + ctx.options.target = new URL("http://backend.com"); + ctx.options.hostRewrite = "ext-manual.com"; + webOutgoing.setRedirectHostRewrite( + ctx.req, + stubServerResponse(), + ctx.proxyRes, + ctx.options, + ); + expect(ctx.proxyRes.headers.location).to.eql("http://ext-manual.com/"); + }); + }); + describe("rewrites location protocol with protocolRewrite", () => { beforeEach(() => { ctx.options.protocolRewrite = "https";