From 84a31e925e7efce34adfbf7ee5acccae8e53586d Mon Sep 17 00:00:00 2001 From: eth-limo Date: Wed, 21 Jan 2026 12:54:12 -0500 Subject: [PATCH] chore: preserve port for Arweave gateways --- .../dweb-api-resolver/src/resolver/arweave.ts | 3 +- .../dweb-api-resolver/src/resolver/utils.ts | 40 +++++++++--- .../src/test/integration.spec.ts | 64 +++++++++++++++++++ 3 files changed, 97 insertions(+), 10 deletions(-) diff --git a/packages/dweb-api-resolver/src/resolver/arweave.ts b/packages/dweb-api-resolver/src/resolver/arweave.ts index f11028e..56812c7 100644 --- a/packages/dweb-api-resolver/src/resolver/arweave.ts +++ b/packages/dweb-api-resolver/src/resolver/arweave.ts @@ -147,8 +147,9 @@ export const arweaveUrlToSandboxSubdomain = async ( return arweave_gateway; } + // Modify hostname to add subdomain const url = new URL(arweave_gateway.toString()); - url.host = subdomain + "." + url.host; + url.hostname = subdomain + "." + url.hostname; return url; }; diff --git a/packages/dweb-api-resolver/src/resolver/utils.ts b/packages/dweb-api-resolver/src/resolver/utils.ts index 69d14ac..c4dad7b 100644 --- a/packages/dweb-api-resolver/src/resolver/utils.ts +++ b/packages/dweb-api-resolver/src/resolver/utils.ts @@ -112,17 +112,39 @@ export const recordToProxyRecord = async ( overrideCodecHeader, }; } else if (record.codec === "arweave-ns") { - const backend = new URL(arweaveConfig.getBackend()); + const backendString = arweaveConfig.getBackend(); + const backend = new URL(backendString); + + // Extract explicit port from original backend string + // Use lookahead to match port followed by /, ?, #, or end of string + let explicitPort: string | null = null; + const portMatch = backendString.match(/:(\d+)(?=\/|\?|#|$)/); + if (portMatch) { + explicitPort = portMatch[1]; + } + + const resultUrl = await arweaveUrlToSandboxSubdomain( + request, + logger, + record.DoHContentIdentifier, + backend, + ); + + // Manually construct XContentLocation to preserve explicit port + // Note: We cannot use url.port = explicitPort because the URL API + // automatically normalizes default ports (443 for https, 80 for http) + // The URL components (pathname, search, hash) are already properly encoded by the URL object + let xContentLocation: string; + if (explicitPort) { + // Preserve explicit port even if it's a default port + xContentLocation = `${resultUrl.protocol}//${resultUrl.hostname}:${explicitPort}${resultUrl.pathname}${resultUrl.search}${resultUrl.hash}`; + } else { + xContentLocation = resultUrl.toString(); + } + return { ...record, - XContentLocation: ( - await arweaveUrlToSandboxSubdomain( - request, - logger, - record.DoHContentIdentifier, - backend, - ) - ).toString(), + XContentLocation: xContentLocation, XContentPath: ensureTrailingSlash("/" + record.DoHContentIdentifier), }; } else if (record.codec === "swarm") { diff --git a/packages/dweb-api-server/src/test/integration.spec.ts b/packages/dweb-api-server/src/test/integration.spec.ts index 413774b..50f44b5 100644 --- a/packages/dweb-api-server/src/test/integration.spec.ts +++ b/packages/dweb-api-server/src/test/integration.spec.ts @@ -625,6 +625,70 @@ describe("Proxy API Integration Tests", function () { expect(res.header("x-content-path")).to.equal("/"); expect(res.header("x-content-storage-type")).to.equal("ipns-ns"); }); + + it("should preserve port in X-Content-Location for arweave with explicit port", async () => { + // Set arweave backend with explicit non-default port + harnessInput.configurationService.set((conf) => { + conf.arweave.backend = "https://arweave.net:8443"; + }); + + const { content_location, content_path, content_storage_type, res } = + await commonSetup({ + name: "makesy.eth", + type: "arweave", + contentHash: "arweave://Gum-G8CFTCIJIeDVJSxAzB9qNy2zv7SC4Cv_bgw7I3g", + additionalInfo: { + arweave: { + result: "Gum-G8CFTCIJIeDVJSxAzB9qNy2zv7SC4Cv_bgw7I3g", + query: "Gum-G8CFTCIJIeDVJSxAzB9qNy2zv7SC4Cv_bgw7I3g", + subdomain_sandbox_id: + "dlu34g6aqvgcecjb4dksklcazqpwunznwo73jaxafp7w4db3en4a", + }, + }, + options: populateDefaultOptions({}), + }); + + expect(res.statusCode).to.be.equal(200); + expect(content_location).to.be.equal( + "dlu34g6aqvgcecjb4dksklcazqpwunznwo73jaxafp7w4db3en4a.arweave.net:8443", + ); + expect(content_path).to.be.equal( + "/Gum-G8CFTCIJIeDVJSxAzB9qNy2zv7SC4Cv_bgw7I3g/", + ); + expect(content_storage_type).to.be.equal("arweave-ns"); + }); + + it("should preserve explicit default port 443 in X-Content-Location for arweave", async () => { + // Set arweave backend with explicit default port 443 + harnessInput.configurationService.set((conf) => { + conf.arweave.backend = "https://arweave.net:443"; + }); + + const { content_location, content_path, content_storage_type, res } = + await commonSetup({ + name: "makesy.eth", + type: "arweave", + contentHash: "arweave://Gum-G8CFTCIJIeDVJSxAzB9qNy2zv7SC4Cv_bgw7I3g", + additionalInfo: { + arweave: { + result: "Gum-G8CFTCIJIeDVJSxAzB9qNy2zv7SC4Cv_bgw7I3g", + query: "Gum-G8CFTCIJIeDVJSxAzB9qNy2zv7SC4Cv_bgw7I3g", + subdomain_sandbox_id: + "dlu34g6aqvgcecjb4dksklcazqpwunznwo73jaxafp7w4db3en4a", + }, + }, + options: populateDefaultOptions({}), + }); + + expect(res.statusCode).to.be.equal(200); + expect(content_location).to.be.equal( + "dlu34g6aqvgcecjb4dksklcazqpwunznwo73jaxafp7w4db3en4a.arweave.net:443", + ); + expect(content_path).to.be.equal( + "/Gum-G8CFTCIJIeDVJSxAzB9qNy2zv7SC4Cv_bgw7I3g/", + ); + expect(content_storage_type).to.be.equal("arweave-ns"); + }); }); describe("Caddy API Integration Tests", function () {