From ab79e414724e160e1ebf1c5d36a6325e59a0c0fa Mon Sep 17 00:00:00 2001 From: guiwenlingmu962-bot <248856456+guiwenlingmu962-bot@users.noreply.github.com> Date: Sun, 28 Jun 2026 20:57:06 +0900 Subject: [PATCH] fix: preserve HTTP proxy path delimiters --- lib/utils/resolve-file-proxy.ts | 18 +++++++++++----- tests/routes/file-proxy03.test.ts | 34 +++++++++++++++++++++++++++++++ 2 files changed, 47 insertions(+), 5 deletions(-) diff --git a/lib/utils/resolve-file-proxy.ts b/lib/utils/resolve-file-proxy.ts index 3145b4e..b7994a1 100644 --- a/lib/utils/resolve-file-proxy.ts +++ b/lib/utils/resolve-file-proxy.ts @@ -53,11 +53,7 @@ async function resolveHttpProxy( httpTargetUrl: string, relativePath: string, ): Promise { - // Ensure the URL doesn't have double slashes - const baseUrl = httpTargetUrl.endsWith("/") - ? httpTargetUrl.slice(0, -1) - : httpTargetUrl - const targetUrl = `${baseUrl}/${relativePath}` + const targetUrl = buildHttpProxyUrl(httpTargetUrl, relativePath) try { const response = await fetch(targetUrl) @@ -91,6 +87,18 @@ async function resolveHttpProxy( } } +export function buildHttpProxyUrl( + httpTargetUrl: string, + relativePath: string, +): URL { + const baseUrl = new URL(httpTargetUrl) + if (!baseUrl.pathname.endsWith("/")) { + baseUrl.pathname += "/" + } + baseUrl.pathname += relativePath + return baseUrl +} + function getContentType(fileName: string): string { const ext = fileName.split(".").pop()?.toLowerCase() const mimeTypes: Record = { diff --git a/tests/routes/file-proxy03.test.ts b/tests/routes/file-proxy03.test.ts index 0596f78..012e0d3 100644 --- a/tests/routes/file-proxy03.test.ts +++ b/tests/routes/file-proxy03.test.ts @@ -1,6 +1,18 @@ import { test, expect } from "bun:test" import { getTestServer } from "tests/fixtures/get-test-server" import { Buffer } from "node:buffer" +import { buildHttpProxyUrl } from "lib/utils/resolve-file-proxy" + +test("http proxy URL preserves special characters as path data", () => { + const targetUrl = buildHttpProxyUrl( + "https://example.com/files/static/source?token=abc", + "folder name/report?final#1.txt", + ) + + expect(targetUrl.toString()).toBe( + "https://example.com/files/static/source/folder%20name/report%3Ffinal%231.txt?token=abc", + ) +}) test("http proxy file resolution", async () => { const { axios, url } = await getTestServer() @@ -78,6 +90,28 @@ test("http proxy with query param download", async () => { expect(downloadRes.data).toBe("Query param HTTP proxy test") }) +test("http proxy downloads paths containing URL delimiters", async () => { + const { axios, url } = await getTestServer() + + await axios.post("/files/upsert", { + file_path: "/special-source/report?final#1.txt", + text_content: "Special path HTTP proxy test", + }) + + await axios.post("/file_proxies/create", { + proxy_type: "http", + http_target_url: `${url}/files/static/special-source`, + matching_pattern: "http-special/*", + }) + + const downloadRes = await axios.get("/files/download", { + params: { file_path: "/http-special/report?final#1.txt" }, + }) + + expect(downloadRes.status).toBe(200) + expect(downloadRes.data).toBe("Special path HTTP proxy test") +}) + test("http proxy binary file", async () => { const { axios, url } = await getTestServer()