diff --git a/spec/unit/rendezvous/MSC4108RendezvousSession.spec.ts b/spec/unit/rendezvous/MSC4108RendezvousSession.spec.ts index a3594a31ebb..1f9360c5f74 100644 --- a/spec/unit/rendezvous/MSC4108RendezvousSession.spec.ts +++ b/spec/unit/rendezvous/MSC4108RendezvousSession.spec.ts @@ -210,6 +210,34 @@ describe("MSC4108RendezvousSession", () => { } }); + it("GET with content-type parameters, e.g. added by an intermediary proxy", async function () { + const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc4108Enabled: false }); + const transport = new MSC4108RendezvousSession({ + client, + fallbackRzServer: "https://fallbackserver/rz", + }); + { + // initial POST + fetchMock.postOnce("https://fallbackserver/rz", { + status: 201, + body: { url: "https://fallbackserver/rz/123" }, + }); + await expect(transport.send("foo=baa")).resolves.toStrictEqual(undefined); + await fetchMock.callHistory.flush(true); + } + { + // GET where the content-type has a charset parameter appended, as nginx's + // `charset` directive does — the payload must still be received + fetchMock.getOnce("https://fallbackserver/rz/123", { + status: 200, + body: "foo=baa", + headers: { "content-type": "text/plain; charset=utf-8", "etag": "aaa" }, + }); + await expect(transport.receive()).resolves.toEqual("foo=baa"); + await fetchMock.callHistory.flush(true); + } + }); + it("POST and PUTs", async function () { const client = makeMockClient({ userId: "@alice:example.com", deviceId: "DEVICEID", msc4108Enabled: false }); const transport = new MSC4108RendezvousSession({ diff --git a/src/rendezvous/transports/MSC4108RendezvousSession.ts b/src/rendezvous/transports/MSC4108RendezvousSession.ts index a906e014113..0af94e7442a 100644 --- a/src/rendezvous/transports/MSC4108RendezvousSession.ts +++ b/src/rendezvous/transports/MSC4108RendezvousSession.ts @@ -203,7 +203,10 @@ export class MSC4108RendezvousSession { // rely on server expiring the channel rather than checking ourselves const etag = poll.headers.get("etag") ?? undefined; - if (poll.headers.get("content-type") !== "text/plain") { + // Strip any parameters from the content type, e.g. a `charset` appended + // by an intermediary proxy, as only the media type itself matters. + const contentType = poll.headers.get("content-type")?.split(";", 1)[0].trim().toLowerCase(); + if (contentType !== "text/plain") { this.etag = etag; } else if (poll.status === 200) { if (!etag) {