remove code related to encrypted file download button in iframe (#7940)

* remove code related to encrypted file download button in iframe

Signed-off-by: Kerry Archibald <kerrya@element.io>

* i18n

Signed-off-by: Kerry Archibald <kerrya@element.io>

* remove getIframeFn in mfilebody filedownloader

Signed-off-by: Kerry Archibald <kerrya@element.io>

* remove iframe ref too

Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
Kerry 2022-03-02 12:55:03 +01:00 committed by GitHub
parent d01ea1824b
commit 26216ec527
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
4 changed files with 5 additions and 87 deletions

View file

@ -45,20 +45,6 @@ limitations under the License.
pointer-events: none; pointer-events: none;
} }
/* Remove the border and padding for iframes for download links. */
.mx_MFileBody_download iframe {
margin: 0px;
padding: 0px;
border: none;
width: 100%;
/* Set the height of the iframe to be 1 line of text.
* Iframes don't automatically size themselves to fit their content.
* So either we have to fix the height of the iframe using CSS or
* use javascript's cross-origin postMessage API to communicate how
* big the content of the iframe is. */
height: 1.5em;
}
.mx_MFileBody_info { .mx_MFileBody_info {
cursor: pointer; cursor: pointer;

View file

@ -31,19 +31,6 @@ import { FileDownloader } from "../../../utils/FileDownloader";
import TextWithTooltip from "../elements/TextWithTooltip"; import TextWithTooltip from "../elements/TextWithTooltip";
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext"; import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
export let DOWNLOAD_ICON_URL; // cached copy of the download.svg asset for the sandboxed iframe later on
async function cacheDownloadIcon() {
if (DOWNLOAD_ICON_URL) return; // cached already
// eslint-disable-next-line @typescript-eslint/no-var-requires
const svg = await fetch(require("../../../../res/img/download.svg")).then(r => r.text());
DOWNLOAD_ICON_URL = "data:image/svg+xml;base64," + window.btoa(svg);
}
// Cache the asset immediately
// noinspection JSIgnoredPromiseFromCall
cacheDownloadIcon();
// User supplied content can contain scripts, we have to be careful that // User supplied content can contain scripts, we have to be careful that
// we don't accidentally run those script within the same origin as the // we don't accidentally run those script within the same origin as the
// client. Otherwise those scripts written by remote users can read // client. Otherwise those scripts written by remote users can read
@ -74,29 +61,6 @@ cacheDownloadIcon();
// the downside of using a sandboxed iframe is that the browers are overly // the downside of using a sandboxed iframe is that the browers are overly
// restrictive in what you are allowed to do with the generated URL. // restrictive in what you are allowed to do with the generated URL.
/**
* Get the current CSS style for a DOMElement.
* @param {HTMLElement} element The element to get the current style of.
* @return {string} The CSS style encoded as a string.
*/
export function computedStyle(element: HTMLElement) {
if (!element) {
return "";
}
const style = window.getComputedStyle(element, null);
let cssText = style.cssText;
// noinspection EqualityComparisonWithCoercionJS
if (cssText == "") {
// Firefox doesn't implement ".cssText" for computed styles.
// https://bugzilla.mozilla.org/show_bug.cgi?id=137687
for (let i = 0; i < style.length; i++) {
cssText += style[i] + ":";
cssText += style.getPropertyValue(style[i]) + ";";
}
}
return cssText;
}
interface IProps extends IBodyProps { interface IProps extends IBodyProps {
/* whether or not to show the default placeholder for the file. Defaults to true. */ /* whether or not to show the default placeholder for the file. Defaults to true. */
showGenericPlaceholder: boolean; showGenericPlaceholder: boolean;
@ -115,10 +79,9 @@ export default class MFileBody extends React.Component<IProps, IState> {
showGenericPlaceholder: true, showGenericPlaceholder: true,
}; };
private iframe: React.RefObject<HTMLIFrameElement> = createRef();
private dummyLink: React.RefObject<HTMLAnchorElement> = createRef(); private dummyLink: React.RefObject<HTMLAnchorElement> = createRef();
private userDidClick = false; private userDidClick = false;
private fileDownloader: FileDownloader = new FileDownloader(() => this.iframe.current); private fileDownloader: FileDownloader = new FileDownloader();
public constructor(props: IProps) { public constructor(props: IProps) {
super(props); super(props);
@ -143,17 +106,11 @@ export default class MFileBody extends React.Component<IProps, IState> {
return presentableTextForFile(this.content); return presentableTextForFile(this.content);
} }
private downloadFile(fileName: string, text: string) { private downloadFile(fileName: string) {
this.fileDownloader.download({ this.fileDownloader.download({
blob: this.state.decryptedBlob, blob: this.state.decryptedBlob,
name: fileName, name: fileName,
autoDownload: this.userDidClick, autoDownload: this.userDidClick,
opts: {
imgSrc: DOWNLOAD_ICON_URL,
imgStyle: null,
style: computedStyle(this.dummyLink.current),
textContent: _t("Download %(text)s", { text }),
},
}); });
} }
@ -185,7 +142,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
const mediaHelper = this.props.mediaEventHelper; const mediaHelper = this.props.mediaEventHelper;
if (mediaHelper?.media.isEncrypted) { if (mediaHelper?.media.isEncrypted) {
await this.decryptFile(); await this.decryptFile();
this.downloadFile(this.fileName, this.linkText); this.downloadFile(this.fileName);
} else { } else {
// As a button we're missing the `download` attribute for styling reasons, so // As a button we're missing the `download` attribute for styling reasons, so
// download with the file downloader. // download with the file downloader.
@ -256,8 +213,6 @@ export default class MFileBody extends React.Component<IProps, IState> {
); );
} }
const url = "usercontent/"; // XXX: this path should probably be passed from the skin
// If the attachment is encrypted then put the link inside an iframe. // If the attachment is encrypted then put the link inside an iframe.
return ( return (
<span className="mx_MFileBody"> <span className="mx_MFileBody">
@ -274,20 +229,6 @@ export default class MFileBody extends React.Component<IProps, IState> {
{ /* eslint-disable-next-line */ } { /* eslint-disable-next-line */ }
<a ref={this.dummyLink} /> <a ref={this.dummyLink} />
</div> </div>
{ /*
TODO: Move iframe (and dummy link) into FileDownloader.
We currently have it set up this way because of styles applied to the iframe
itself which cannot be easily handled/overridden by the FileDownloader. In
future, the download link may disappear entirely at which point it could also
be suitable to just remove this bit of code.
*/ }
<iframe
aria-hidden
title={presentableTextForFile(this.content, _t("Attachment"), true, true)}
src={url}
onLoad={() => this.downloadFile(this.fileName, this.linkText)}
ref={this.iframe}
sandbox="allow-scripts allow-downloads allow-downloads-without-user-activation" />
</div> } </div> }
</span> </span>
); );

View file

@ -2104,9 +2104,9 @@
"Reply": "Reply", "Reply": "Reply",
"Collapse quotes │ ⇧+click": "Collapse quotes │ ⇧+click", "Collapse quotes │ ⇧+click": "Collapse quotes │ ⇧+click",
"Expand quotes │ ⇧+click": "Expand quotes │ ⇧+click", "Expand quotes │ ⇧+click": "Expand quotes │ ⇧+click",
"Download %(text)s": "Download %(text)s",
"Error decrypting attachment": "Error decrypting attachment", "Error decrypting attachment": "Error decrypting attachment",
"Decrypt %(text)s": "Decrypt %(text)s", "Decrypt %(text)s": "Decrypt %(text)s",
"Download %(text)s": "Download %(text)s",
"Invalid file%(extra)s": "Invalid file%(extra)s", "Invalid file%(extra)s": "Invalid file%(extra)s",
"Error decrypting image": "Error decrypting image", "Error decrypting image": "Error decrypting image",
"Show image": "Show image", "Show image": "Show image",

View file

@ -16,18 +16,10 @@ limitations under the License.
export type getIframeFn = () => HTMLIFrameElement; // eslint-disable-line @typescript-eslint/naming-convention export type getIframeFn = () => HTMLIFrameElement; // eslint-disable-line @typescript-eslint/naming-convention
export const DEFAULT_STYLES = {
imgSrc: "",
imgStyle: null, // css props
style: "",
textContent: "",
};
type DownloadOptions = { type DownloadOptions = {
blob: Blob; blob: Blob;
name: string; name: string;
autoDownload?: boolean; autoDownload?: boolean;
opts?: typeof DEFAULT_STYLES;
}; };
// set up the iframe as a singleton so we don't have to figure out destruction of it down the line. // set up the iframe as a singleton so we don't have to figure out destruction of it down the line.
@ -89,11 +81,10 @@ export class FileDownloader {
return iframe; return iframe;
} }
public async download({ blob, name, autoDownload = true, opts = DEFAULT_STYLES }: DownloadOptions) { public async download({ blob, name, autoDownload = true }: DownloadOptions) {
const iframe = this.iframe; // get the iframe first just in case we need to await onload const iframe = this.iframe; // get the iframe first just in case we need to await onload
if (this.onLoadPromise) await this.onLoadPromise; if (this.onLoadPromise) await this.onLoadPromise;
iframe.contentWindow.postMessage({ iframe.contentWindow.postMessage({
...opts,
blob: blob, blob: blob,
download: name, download: name,
auto: autoDownload, auto: autoDownload,