Skip to content
This repository has been archived by the owner on Sep 11, 2024. It is now read-only.

Commit

Permalink
Fix svg images not correctly displaying in e2ee rooms
Browse files Browse the repository at this point in the history
- store original mimetype of blobs as well
- if they differ, use a data url instead of a blob url (data urls have a unique origin)
  • Loading branch information
justjanne committed Apr 14, 2022
1 parent 7a1a2c4 commit 6506f08
Show file tree
Hide file tree
Showing 6 changed files with 52 additions and 22 deletions.
2 changes: 1 addition & 1 deletion src/components/views/messages/DownloadActionButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export default class DownloadActionButton extends React.PureComponent<IProps, IS
return this.doDownload();
}

const blob = await this.props.mediaEventHelperGet().sourceBlob.value;
const blob = await this.props.mediaEventHelperGet().sourceBlob;
this.setState({ blob });
await this.doDownload();
};
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/messages/MAudioBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export default class MAudioBody extends React.PureComponent<IBodyProps, IState>

try {
try {
const blob = await this.props.mediaEventHelper.sourceBlob.value;
const blob = await this.props.mediaEventHelper.sourceBlob;
buffer = await blob.arrayBuffer();
} catch (e) {
this.setState({ error: e });
Expand Down
6 changes: 3 additions & 3 deletions src/components/views/messages/MFileBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -168,7 +168,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
try {
this.userDidClick = true;
this.setState({
decryptedBlob: await this.props.mediaEventHelper.sourceBlob.value,
decryptedBlob: await this.props.mediaEventHelper.sourceBlob,
});
} catch (err) {
logger.warn("Unable to decrypt attachment: ", err);
Expand All @@ -188,7 +188,7 @@ export default class MFileBody extends React.Component<IProps, IState> {
// As a button we're missing the `download` attribute for styling reasons, so
// download with the file downloader.
this.fileDownloader.download({
blob: await mediaHelper.sourceBlob.value,
blob: await mediaHelper.sourceBlob,
name: this.fileName,
});
}
Expand Down Expand Up @@ -317,7 +317,7 @@ export default class MFileBody extends React.Component<IProps, IState> {

// Start a fetch for the download
// Based upon https://stackoverflow.com/a/49500465
this.props.mediaEventHelper.sourceBlob.value.then((blob) => {
this.props.mediaEventHelper.sourceBlob.then((blob) => {
const blobUrl = URL.createObjectURL(blob);

// We have to create an anchor to download the file
Expand Down
2 changes: 1 addition & 1 deletion src/components/views/messages/MImageBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -287,7 +287,7 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {

await loadPromise;

const blob = await this.props.mediaEventHelper.sourceBlob.value;
const blob = await this.props.mediaEventHelper.sourceBlob;
if (!await blobIsAnimated(content.info.mimetype, blob)) {
isAnimated = false;
}
Expand Down
4 changes: 2 additions & 2 deletions src/components/views/messages/MVideoBody.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -147,7 +147,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
this.setState({
decryptedUrl: await this.props.mediaEventHelper.sourceUrl.value,
decryptedThumbnailUrl: thumbnailUrl,
decryptedBlob: await this.props.mediaEventHelper.sourceBlob.value,
decryptedBlob: await this.props.mediaEventHelper.sourceBlob,
});
this.props.onHeightChanged();
} else {
Expand Down Expand Up @@ -202,7 +202,7 @@ export default class MVideoBody extends React.PureComponent<IBodyProps, IState>
}
this.setState({
decryptedUrl: await this.props.mediaEventHelper.sourceUrl.value,
decryptedBlob: await this.props.mediaEventHelper.sourceBlob.value,
decryptedBlob: await this.props.mediaEventHelper.sourceBlob,
fetchingData: false,
}, () => {
if (!this.videoRef.current) return;
Expand Down
58 changes: 44 additions & 14 deletions src/utils/MediaEventHelper.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,22 +26,46 @@ import { IDestroyable } from "./IDestroyable";

// TODO: We should consider caching the blobs. https://github.com/vector-im/element-web/issues/17192

interface ITypedBlob {
mimetype: string;
data: Blob;
}

// infer type of blob from blob itself
function toTypedBlob(blob: Blob): ITypedBlob {
return {
mimetype: blob.type,
data: blob,
};
}

async function createDataUrl(blob: ITypedBlob): Promise<string> {
return `data:${blob.mimetype};base64,${await blob.data.text().then(btoa)}`;
}

export class MediaEventHelper implements IDestroyable {
// Either an HTTP or Object URL (when encrypted) to the media.
public readonly sourceUrl: LazyValue<string>;
public readonly thumbnailUrl: LazyValue<string>;

// Either the raw or decrypted (when encrypted) contents of the file.
public readonly sourceBlob: LazyValue<Blob>;
public readonly thumbnailBlob: LazyValue<Blob>;
private readonly sourceTypedBlob: LazyValue<ITypedBlob>;
private readonly thumbnailTypedBlob: LazyValue<ITypedBlob | null>;

public get sourceBlob() {
return this.sourceTypedBlob.value.then(it => it.data);
}
public get thumbnailBlob() {
return this.thumbnailTypedBlob.value.then(it => it.data);
}

public readonly media: Media;

public constructor(private event: MatrixEvent) {
this.sourceUrl = new LazyValue(this.prepareSourceUrl);
this.thumbnailUrl = new LazyValue(this.prepareThumbnailUrl);
this.sourceBlob = new LazyValue(this.fetchSource);
this.thumbnailBlob = new LazyValue(this.fetchThumbnail);
this.sourceTypedBlob = new LazyValue(this.fetchSource);
this.thumbnailTypedBlob = new LazyValue(this.fetchThumbnail);

this.media = mediaFromContent(this.event.getContent());
}
Expand All @@ -59,46 +83,52 @@ export class MediaEventHelper implements IDestroyable {

private prepareSourceUrl = async () => {
if (this.media.isEncrypted) {
const blob = await this.sourceBlob.value;
return URL.createObjectURL(blob);
const blob = await this.sourceTypedBlob.value;
if (blob.mimetype !== blob.data.type) return createDataUrl(blob);
return URL.createObjectURL(blob.data);
} else {
return this.media.srcHttp;
}
};

private prepareThumbnailUrl = async () => {
if (this.media.isEncrypted) {
const blob = await this.thumbnailBlob.value;
const blob = await this.thumbnailTypedBlob.value;
if (blob === null) return null;
return URL.createObjectURL(blob);
if (blob.mimetype !== blob.data.type) return createDataUrl(blob);
return URL.createObjectURL(blob.data);
} else {
return this.media.thumbnailHttp;
}
};

private fetchSource = () => {
private fetchSource: () => Promise<ITypedBlob> = () => {
if (this.media.isEncrypted) {
const content = this.event.getContent<IMediaEventContent>();
return decryptFile(content.file, content.info);
return decryptFile(content.file, content.info).then(data => {
return { mimetype: content.info.mimetype, data };
});
}
return this.media.downloadSource().then(r => r.blob());
return this.media.downloadSource().then(r => r.blob()).then(toTypedBlob);
};

private fetchThumbnail = () => {
private fetchThumbnail: () => Promise<ITypedBlob | null> = () => {
if (!this.media.hasThumbnail) return Promise.resolve(null);

if (this.media.isEncrypted) {
const content = this.event.getContent<IMediaEventContent>();
if (content.info?.thumbnail_file) {
return decryptFile(content.info.thumbnail_file, content.info.thumbnail_info);
return decryptFile(content.info.thumbnail_file, content.info.thumbnail_info).then(data => {
return { mimetype: content.info.thumbnail_info.mimetype, data };
});
} else {
// "Should never happen"
logger.warn("Media claims to have thumbnail and is encrypted, but no thumbnail_file found");
return Promise.resolve(null);
}
}

return fetch(this.media.thumbnailHttp).then(r => r.blob());
return fetch(this.media.thumbnailHttp).then(r => r.blob()).then(toTypedBlob);
};

public static isEligible(event: MatrixEvent): boolean {
Expand Down

0 comments on commit 6506f08

Please sign in to comment.