mirror of
https://github.com/element-hq/element-web
synced 2024-11-23 01:35:49 +03:00
Revert "remove code related to encrypted file download button in iframe" (#7957)
* Revert "remove code related to encrypted file download button in iframe (#7940)"
This reverts commit 26216ec527
.
* udpate icon import in MFileBody
Signed-off-by: Kerry Archibald <kerrya@element.io>
This commit is contained in:
parent
8f68a43ee3
commit
d304e24a45
4 changed files with 88 additions and 5 deletions
|
@ -45,6 +45,20 @@ limitations under the License.
|
|||
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 {
|
||||
cursor: pointer;
|
||||
|
||||
|
|
|
@ -30,6 +30,20 @@ import { IBodyProps } from "./IBodyProps";
|
|||
import { FileDownloader } from "../../../utils/FileDownloader";
|
||||
import TextWithTooltip from "../elements/TextWithTooltip";
|
||||
import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContext";
|
||||
import DownloadSvg from '../../../../res/img/download.svg';
|
||||
|
||||
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(DownloadSvg).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
|
||||
// we don't accidentally run those script within the same origin as the
|
||||
|
@ -61,6 +75,29 @@ import RoomContext, { TimelineRenderingType } from "../../../contexts/RoomContex
|
|||
// 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.
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
/* whether or not to show the default placeholder for the file. Defaults to true. */
|
||||
showGenericPlaceholder: boolean;
|
||||
|
@ -79,9 +116,10 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
|||
showGenericPlaceholder: true,
|
||||
};
|
||||
|
||||
private iframe: React.RefObject<HTMLIFrameElement> = createRef();
|
||||
private dummyLink: React.RefObject<HTMLAnchorElement> = createRef();
|
||||
private userDidClick = false;
|
||||
private fileDownloader: FileDownloader = new FileDownloader();
|
||||
private fileDownloader: FileDownloader = new FileDownloader(() => this.iframe.current);
|
||||
|
||||
public constructor(props: IProps) {
|
||||
super(props);
|
||||
|
@ -106,11 +144,17 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
|||
return presentableTextForFile(this.content);
|
||||
}
|
||||
|
||||
private downloadFile(fileName: string) {
|
||||
private downloadFile(fileName: string, text: string) {
|
||||
this.fileDownloader.download({
|
||||
blob: this.state.decryptedBlob,
|
||||
name: fileName,
|
||||
autoDownload: this.userDidClick,
|
||||
opts: {
|
||||
imgSrc: DOWNLOAD_ICON_URL,
|
||||
imgStyle: null,
|
||||
style: computedStyle(this.dummyLink.current),
|
||||
textContent: _t("Download %(text)s", { text }),
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -142,7 +186,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
|||
const mediaHelper = this.props.mediaEventHelper;
|
||||
if (mediaHelper?.media.isEncrypted) {
|
||||
await this.decryptFile();
|
||||
this.downloadFile(this.fileName);
|
||||
this.downloadFile(this.fileName, this.linkText);
|
||||
} else {
|
||||
// As a button we're missing the `download` attribute for styling reasons, so
|
||||
// download with the file downloader.
|
||||
|
@ -213,6 +257,8 @@ 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.
|
||||
return (
|
||||
<span className="mx_MFileBody">
|
||||
|
@ -229,6 +275,20 @@ export default class MFileBody extends React.Component<IProps, IState> {
|
|||
{ /* eslint-disable-next-line */ }
|
||||
<a ref={this.dummyLink} />
|
||||
</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> }
|
||||
</span>
|
||||
);
|
||||
|
|
|
@ -2105,9 +2105,9 @@
|
|||
"Reply": "Reply",
|
||||
"Collapse quotes │ ⇧+click": "Collapse quotes │ ⇧+click",
|
||||
"Expand quotes │ ⇧+click": "Expand quotes │ ⇧+click",
|
||||
"Download %(text)s": "Download %(text)s",
|
||||
"Error decrypting attachment": "Error decrypting attachment",
|
||||
"Decrypt %(text)s": "Decrypt %(text)s",
|
||||
"Download %(text)s": "Download %(text)s",
|
||||
"Invalid file%(extra)s": "Invalid file%(extra)s",
|
||||
"Error decrypting image": "Error decrypting image",
|
||||
"Show image": "Show image",
|
||||
|
|
|
@ -16,10 +16,18 @@ limitations under the License.
|
|||
|
||||
export type getIframeFn = () => HTMLIFrameElement; // eslint-disable-line @typescript-eslint/naming-convention
|
||||
|
||||
export const DEFAULT_STYLES = {
|
||||
imgSrc: "",
|
||||
imgStyle: null, // css props
|
||||
style: "",
|
||||
textContent: "",
|
||||
};
|
||||
|
||||
type DownloadOptions = {
|
||||
blob: Blob;
|
||||
name: string;
|
||||
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.
|
||||
|
@ -81,10 +89,11 @@ export class FileDownloader {
|
|||
return iframe;
|
||||
}
|
||||
|
||||
public async download({ blob, name, autoDownload = true }: DownloadOptions) {
|
||||
public async download({ blob, name, autoDownload = true, opts = DEFAULT_STYLES }: DownloadOptions) {
|
||||
const iframe = this.iframe; // get the iframe first just in case we need to await onload
|
||||
if (this.onLoadPromise) await this.onLoadPromise;
|
||||
iframe.contentWindow.postMessage({
|
||||
...opts,
|
||||
blob: blob,
|
||||
download: name,
|
||||
auto: autoDownload,
|
||||
|
|
Loading…
Reference in a new issue