From 1e15a322a53b135c909b9f8e9c26b9497dc0f0a9 Mon Sep 17 00:00:00 2001 From: David Baker Date: Wed, 30 Jul 2025 13:30:18 +0100 Subject: [PATCH] Fix downloaded attachments not being decrypted (#30433) * Fix downloaded attachments not being decrypted Fixes https://github.com/element-hq/element-web/issues/30339 * Import order --- src/hooks/useDownloadMedia.ts | 18 ++++++---- .../views/elements/ImageView-test.tsx | 35 +++++++++++++++++++ 2 files changed, 46 insertions(+), 7 deletions(-) diff --git a/src/hooks/useDownloadMedia.ts b/src/hooks/useDownloadMedia.ts index 74328ac7ca..eb0af954e0 100644 --- a/src/hooks/useDownloadMedia.ts +++ b/src/hooks/useDownloadMedia.ts @@ -59,15 +59,19 @@ export function useDownloadMedia(url: string, fileName?: string, mxEvent?: Matri return downloadBlob(blobRef.current); } - const res = await fetch(url); - if (!res.ok) { - throw parseErrorResponse(res, await res.text()); + // We must download via the mediaEventHelper if given as the file may need decryption. + if (mediaEventHelper) { + blobRef.current = await mediaEventHelper.sourceBlob.value; + } else { + const res = await fetch(url); + if (!res.ok) { + throw parseErrorResponse(res, await res.text()); + } + + blobRef.current = await res.blob(); } - const blob = await res.blob(); - blobRef.current = blob; - - await downloadBlob(blob); + await downloadBlob(blobRef.current); } catch (e) { showError(e); } diff --git a/test/unit-tests/components/views/elements/ImageView-test.tsx b/test/unit-tests/components/views/elements/ImageView-test.tsx index b0e9338f69..6537a3948a 100644 --- a/test/unit-tests/components/views/elements/ImageView-test.tsx +++ b/test/unit-tests/components/views/elements/ImageView-test.tsx @@ -10,11 +10,13 @@ import React from "react"; import { mocked } from "jest-mock"; import { render, fireEvent, waitFor } from "jest-matrix-react"; import fetchMock from "fetch-mock-jest"; +import { MatrixEvent } from "matrix-js-sdk/src/matrix"; import ImageView from "../../../../../src/components/views/elements/ImageView"; import { FileDownloader } from "../../../../../src/utils/FileDownloader"; import Modal from "../../../../../src/Modal"; import ErrorDialog from "../../../../../src/components/views/dialogs/ErrorDialog"; +import { stubClient } from "../../../../test-utils"; jest.mock("../../../../../src/utils/FileDownloader"); @@ -44,6 +46,39 @@ describe("", () => { expect(fetchMock).toHaveFetched("https://example.com/image.png"); }); + it("should use event as download source if given", async () => { + stubClient(); + + const event = new MatrixEvent({ + event_id: "$eventId", + type: "m.image", + content: { + body: "fromEvent.png", + url: "mxc://test.dummy/fromEvent.png", + file_name: "filename.png", + }, + origin_server_ts: new Date(2000, 0, 1, 0, 0, 0, 0).getTime(), + }); + + fetchMock.get("http://this.is.a.url/test.dummy/fromEvent.png", "TESTFILE"); + const { getByRole } = render( + , + ); + fireEvent.click(getByRole("button", { name: "Download" })); + await waitFor(() => + expect(mocked(FileDownloader).mock.instances[0].download).toHaveBeenCalledWith({ + blob: expect.anything(), + name: "fromEvent.png", + }), + ); + expect(fetchMock).toHaveFetched("http://this.is.a.url/test.dummy/fromEvent.png"); + }); + it("should start download on Ctrl+S", async () => { fetchMock.get("https://example.com/image.png", "TESTFILE");