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:
parent
d01ea1824b
commit
26216ec527
4 changed files with 5 additions and 87 deletions
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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>
|
||||||
);
|
);
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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,
|
||||||
|
|
Loading…
Reference in a new issue