Differentiate download and decryption errors when showing images (#9562)

This commit is contained in:
Michael Telatynski
2022-11-10 09:27:20 +00:00
committed by GitHub
parent abec724387
commit 962e8e0b23
7 changed files with 170 additions and 133 deletions

View File

@@ -39,6 +39,7 @@ import { blobIsAnimated, mayBeAnimated } from '../../../utils/Image';
import { presentableTextForFile } from "../../../utils/FileUtils";
import { createReconnectedListener } from '../../../utils/connection';
import MediaProcessingError from './shared/MediaProcessingError';
import { DecryptError, DownloadError } from "../../../utils/DecryptFile";
enum Placeholder {
NoImage,
@@ -258,7 +259,15 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
]));
} catch (error) {
if (this.unmounted) return;
logger.warn("Unable to decrypt attachment: ", error);
if (error instanceof DecryptError) {
logger.error("Unable to decrypt attachment: ", error);
} else if (error instanceof DownloadError) {
logger.error("Unable to download attachment to decrypt it: ", error);
} else {
logger.error("Error encountered when downloading encrypted attachment: ", error);
}
// Set a placeholder image when we can't decrypt the image.
this.setState({ error });
}
@@ -557,9 +566,16 @@ export default class MImageBody extends React.Component<IBodyProps, IState> {
const content = this.props.mxEvent.getContent<IMediaEventContent>();
if (this.state.error) {
let errorText = _t("Unable to show image due to error");
if (this.state.error instanceof DecryptError) {
errorText = _t("Error decrypting image");
} else if (this.state.error instanceof DownloadError) {
errorText = _t("Error downloading image");
}
return (
<MediaProcessingError className="mx_MImageBody">
{ _t("Error decrypting image") }
{ errorText }
</MediaProcessingError>
);
}

View File

@@ -2312,7 +2312,9 @@
"Decrypt %(text)s": "Decrypt %(text)s",
"Invalid file%(extra)s": "Invalid file%(extra)s",
"Image": "Image",
"Unable to show image due to error": "Unable to show image due to error",
"Error decrypting image": "Error decrypting image",
"Error downloading image": "Error downloading image",
"Show image": "Show image",
"Join the conference at the top of this room": "Join the conference at the top of this room",
"Join the conference from the room information card on the right": "Join the conference from the room information card on the right",

View File

@@ -16,11 +16,28 @@ limitations under the License.
// Pull in the encryption lib so that we can decrypt attachments.
import encrypt from 'matrix-encrypt-attachment';
import { parseErrorResponse } from 'matrix-js-sdk/src/http-api';
import { mediaFromContent } from "../customisations/Media";
import { IEncryptedFile, IMediaEventInfo } from "../customisations/models/IMediaEventContent";
import { getBlobSafeMimeType } from "./blobs";
export class DownloadError extends Error {
constructor(e) {
super(e.message);
this.name = "DownloadError";
this.stack = e.stack;
}
}
export class DecryptError extends Error {
constructor(e) {
super(e.message);
this.name = "DecryptError";
this.stack = e.stack;
}
}
/**
* Decrypt a file attached to a matrix event.
* @param {IEncryptedFile} file The encrypted file information taken from the matrix event.
@@ -30,19 +47,27 @@ import { getBlobSafeMimeType } from "./blobs";
* @param {IMediaEventInfo} info The info parameter taken from the matrix event.
* @returns {Promise<Blob>} Resolves to a Blob of the file.
*/
export function decryptFile(
export async function decryptFile(
file: IEncryptedFile,
info?: IMediaEventInfo,
): Promise<Blob> {
const media = mediaFromContent({ file });
// Download the encrypted file as an array buffer.
return media.downloadSource().then((response) => {
return response.arrayBuffer();
}).then((responseData) => {
// Decrypt the array buffer using the information taken from
// the event content.
return encrypt.decryptAttachment(responseData, file);
}).then((dataArray) => {
let responseData: ArrayBuffer;
try {
// Download the encrypted file as an array buffer.
const response = await media.downloadSource();
if (!response.ok) {
throw parseErrorResponse(response, await response.text());
}
responseData = await response.arrayBuffer();
} catch (e) {
throw new DownloadError(e);
}
try {
// Decrypt the array buffer using the information taken from the event content.
const dataArray = await encrypt.decryptAttachment(responseData, file);
// Turn the array into a Blob and give it the correct MIME-type.
// IMPORTANT: we must not allow scriptable mime-types into Blobs otherwise
@@ -53,5 +78,7 @@ export function decryptFile(
mimetype = getBlobSafeMimeType(mimetype);
return new Blob([dataArray], { type: mimetype });
});
} catch (e) {
throw new DecryptError(e);
}
}

View File

@@ -48,12 +48,10 @@ export class LazyValue<T> {
if (this.prom) return this.prom;
this.prom = this.getFn();
// Fork the promise chain to avoid accidentally making it return undefined always.
this.prom.then(v => {
return this.prom.then(v => {
this.val = v;
this.done = true;
return v;
});
return this.prom;
}
}