diff --git a/extensions/matrix/src/matrix/sdk/transport.test.ts b/extensions/matrix/src/matrix/sdk/transport.test.ts index 6936196ab41c..5ac0031ff038 100644 --- a/extensions/matrix/src/matrix/sdk/transport.test.ts +++ b/extensions/matrix/src/matrix/sdk/transport.test.ts @@ -29,6 +29,71 @@ describe("performMatrixRequest", () => { clearTestUndiciRuntimeDepsOverride(); }); + it.each([ + { + name: "a root homeserver", + homeserverPath: "", + expectedPath: "/_matrix/client/v3/account/whoami", + }, + { + name: "a proxy prefix without a trailing slash", + homeserverPath: "/matrix-proxy", + expectedPath: "/matrix-proxy/_matrix/client/v3/account/whoami", + }, + { + name: "a proxy prefix with a trailing slash", + homeserverPath: "/matrix-proxy/", + expectedPath: "/matrix-proxy/_matrix/client/v3/account/whoami", + }, + { + name: "an encoded nested proxy prefix", + homeserverPath: "/proxy%20base/tenant/", + expectedPath: "/proxy%20base/tenant/_matrix/client/v3/account/whoami", + }, + ])( + "preserves $name through the real HTTP transport", + async ({ homeserverPath, expectedPath }) => { + const requests: Array<{ url: string | undefined; authorization: string | undefined }> = []; + const server = http.createServer((request, response) => { + requests.push({ + url: request.url, + authorization: request.headers.authorization, + }); + response.writeHead(200, { "content-type": "application/json" }); + response.end(JSON.stringify({ user_id: "@bot:example.org" })); + }); + await new Promise((resolve) => { + server.listen(0, "127.0.0.1", resolve); + }); + const { port } = server.address() as { port: number }; + + try { + const result = await performMatrixRequest({ + homeserver: `http://127.0.0.1:${port}${homeserverPath}`, + accessToken: "test-token", + method: "GET", + endpoint: "/_matrix/client/v3/account/whoami", + qs: { via: "proxy path" }, + timeoutMs: 5000, + ssrfPolicy: { allowPrivateNetwork: true }, + }); + + expect(result.response.status).toBe(200); + expect(JSON.parse(result.text)).toEqual({ user_id: "@bot:example.org" }); + expect(requests).toEqual([ + { + url: `${expectedPath}?via=proxy+path`, + authorization: "Bearer test-token", + }, + ]); + } finally { + await new Promise((resolve) => { + server.close(() => resolve()); + }); + } + }, + ); + it("rejects oversized raw responses before buffering the whole body", async () => { const cancel = vi.fn(); const stream = new ReadableStream({ cancel }); diff --git a/extensions/matrix/src/matrix/sdk/transport.ts b/extensions/matrix/src/matrix/sdk/transport.ts index 185aebc9e8bd..416641e564b4 100644 --- a/extensions/matrix/src/matrix/sdk/transport.ts +++ b/extensions/matrix/src/matrix/sdk/transport.ts @@ -328,7 +328,7 @@ export async function performMatrixRequest(params: { const baseUrl = isAbsoluteEndpoint ? new URL(params.endpoint) - : new URL(normalizeEndpoint(params.endpoint), params.homeserver); + : new URL(`${params.homeserver.replace(/\/+$/u, "")}${normalizeEndpoint(params.endpoint)}`); applyQuery(baseUrl, params.qs); const headers = new Headers();