Fix downloading files with authenticated media API (#30520)
* Fix downloading files with authenticated media API Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Update snapshot Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> * Fix test Signed-off-by: Michael Telatynski <7t3chguy@gmail.com> --------- Signed-off-by: Michael Telatynski <7t3chguy@gmail.com>
This commit is contained in:
@@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only OR GPL-3.0-only OR LicenseRef-Element-Com
|
|||||||
Please see LICENSE files in the repository root for full details.
|
Please see LICENSE files in the repository root for full details.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import { type Download, type Page } from "@playwright/test";
|
import { type Page } from "@playwright/test";
|
||||||
|
|
||||||
import { test, expect } from "../../element-web-test";
|
import { test, expect } from "../../element-web-test";
|
||||||
import { viewRoomSummaryByName } from "./utils";
|
import { viewRoomSummaryByName } from "./utils";
|
||||||
@@ -189,23 +189,13 @@ test.describe("FilePanel", () => {
|
|||||||
|
|
||||||
const link = imageBody.locator(".mx_MFileBody_download a");
|
const link = imageBody.locator(".mx_MFileBody_download a");
|
||||||
|
|
||||||
const newPagePromise = context.waitForEvent("page");
|
const downloadPromise = page.waitForEvent("download");
|
||||||
|
|
||||||
const downloadPromise = new Promise<Download>((resolve) => {
|
|
||||||
page.once("download", resolve);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Click the anchor link (not the image itself)
|
// Click the anchor link (not the image itself)
|
||||||
await link.click();
|
await link.click();
|
||||||
|
|
||||||
const newPage = await newPagePromise;
|
const download = await downloadPromise;
|
||||||
// XXX: Clicking the link opens the image in a new tab on some browsers rather than downloading
|
expect(download.suggestedFilename()).toBe("riot.png");
|
||||||
await expect(newPage)
|
|
||||||
.toHaveURL(/.+\/_matrix\/media\/\w+\/download\/localhost\/\w+/)
|
|
||||||
.catch(async () => {
|
|
||||||
const download = await downloadPromise;
|
|
||||||
expect(download.suggestedFilename()).toBe("riot.png");
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -178,7 +178,6 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
|||||||
public render(): React.ReactNode {
|
public render(): React.ReactNode {
|
||||||
const isEncrypted = this.props.mediaEventHelper?.media.isEncrypted;
|
const isEncrypted = this.props.mediaEventHelper?.media.isEncrypted;
|
||||||
const contentUrl = this.getContentUrl();
|
const contentUrl = this.getContentUrl();
|
||||||
const contentFileSize = this.content.info ? this.content.info.size : null;
|
|
||||||
const fileType = this.content.info?.mimetype ?? "application/octet-stream";
|
const fileType = this.content.info?.mimetype ?? "application/octet-stream";
|
||||||
// defaultProps breaks types on IBodyProps, so instead define the default here.
|
// defaultProps breaks types on IBodyProps, so instead define the default here.
|
||||||
const showGenericPlaceholder = this.props.showGenericPlaceholder ?? true;
|
const showGenericPlaceholder = this.props.showGenericPlaceholder ?? true;
|
||||||
@@ -285,21 +284,9 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
|||||||
target: "_blank",
|
target: "_blank",
|
||||||
rel: "noreferrer noopener",
|
rel: "noreferrer noopener",
|
||||||
|
|
||||||
// We set the href regardless of whether or not we intercept the download
|
// We cannot rely on href+download to download media due to the authenticated media API as it relies
|
||||||
// because we don't really want to convert the file to a blob eagerly, and
|
// on authentication via headers, so we'll have to download the file into memory and then download it.
|
||||||
// still want "open in new tab" and "save link as" to work.
|
onClick: (e) => {
|
||||||
href: contentUrl,
|
|
||||||
};
|
|
||||||
|
|
||||||
// Blobs can only have up to 500mb, so if the file reports as being too large then
|
|
||||||
// we won't try and convert it. Likewise, if the file size is unknown then we'll assume
|
|
||||||
// it is too big. There is the risk of the reported file size and the actual file size
|
|
||||||
// being different, however the user shouldn't normally run into this problem.
|
|
||||||
const fileTooBig = typeof contentFileSize === "number" ? contentFileSize > 524288000 : true;
|
|
||||||
|
|
||||||
if (["application/pdf"].includes(fileType) && !fileTooBig) {
|
|
||||||
// We want to force a download on this type, so use an onClick handler.
|
|
||||||
downloadProps["onClick"] = (e) => {
|
|
||||||
logger.log(`Downloading ${fileType} as blob (unencrypted)`);
|
logger.log(`Downloading ${fileType} as blob (unencrypted)`);
|
||||||
|
|
||||||
// Avoid letting the <a> do its thing
|
// Avoid letting the <a> do its thing
|
||||||
@@ -319,11 +306,8 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
|||||||
tempAnchor.click();
|
tempAnchor.click();
|
||||||
tempAnchor.remove();
|
tempAnchor.remove();
|
||||||
});
|
});
|
||||||
};
|
},
|
||||||
} else {
|
};
|
||||||
// Else we are hoping the browser will do the right thing
|
|
||||||
downloadProps["download"] = this.fileName;
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<span className="mx_MFileBody">
|
<span className="mx_MFileBody">
|
||||||
|
|||||||
@@ -12,8 +12,6 @@ exports[`<MFileBody/> should show a download button in file rendering type 1`] =
|
|||||||
class="_button_vczzf_8 _has-icon_vczzf_57"
|
class="_button_vczzf_8 _has-icon_vczzf_57"
|
||||||
data-kind="secondary"
|
data-kind="secondary"
|
||||||
data-size="sm"
|
data-size="sm"
|
||||||
download="alt for a image"
|
|
||||||
href="https://server/_matrix/media/v3/download/server/image"
|
|
||||||
rel="noreferrer noopener"
|
rel="noreferrer noopener"
|
||||||
role="link"
|
role="link"
|
||||||
tabindex="0"
|
tabindex="0"
|
||||||
|
|||||||
Reference in New Issue
Block a user