98 lines
3.1 KiB
TypeScript
98 lines
3.1 KiB
TypeScript
// Copyright 2021 Signal Messenger, LLC
|
||
// SPDX-License-Identifier: AGPL-3.0-only
|
||
|
||
import { omit } from 'lodash';
|
||
import { blobToArrayBuffer } from 'blob-util';
|
||
import * as log from '../logging/log';
|
||
import { getValue } from '../RemoteConfig';
|
||
|
||
import { parseIntOrThrow } from './parseIntOrThrow';
|
||
import { scaleImageToLevel } from './scaleImageToLevel';
|
||
import type { AttachmentType } from '../types/Attachment';
|
||
import { canBeTranscoded } from '../types/Attachment';
|
||
import type { LoggerType } from '../types/Logging';
|
||
import * as MIME from '../types/MIME';
|
||
import * as Errors from '../types/errors';
|
||
|
||
const MEBIBYTE = 1024 * 1024;
|
||
const DEFAULT_MAX = 100 * MEBIBYTE;
|
||
|
||
export const getMaximumAttachmentSize = (): number => {
|
||
try {
|
||
return parseIntOrThrow(
|
||
getValue('global.attachments.maxBytes'),
|
||
'preProcessAttachment/maxAttachmentSize'
|
||
);
|
||
} catch (error) {
|
||
log.warn(
|
||
'Failed to parse integer out of global.attachments.maxBytes feature flag'
|
||
);
|
||
return DEFAULT_MAX;
|
||
}
|
||
};
|
||
|
||
// Upgrade steps
|
||
// NOTE: This step strips all EXIF metadata from JPEG images as
|
||
// part of re-encoding the image:
|
||
export async function autoOrientJPEG(
|
||
attachment: AttachmentType,
|
||
{ logger }: { logger: LoggerType },
|
||
{
|
||
sendHQImages = false,
|
||
isIncoming = false,
|
||
}: {
|
||
sendHQImages?: boolean;
|
||
isIncoming?: boolean;
|
||
} = {}
|
||
): Promise<AttachmentType> {
|
||
if (isIncoming && !MIME.isJPEG(attachment.contentType)) {
|
||
return attachment;
|
||
}
|
||
|
||
if (!canBeTranscoded(attachment)) {
|
||
return attachment;
|
||
}
|
||
|
||
// If we haven't downloaded the attachment yet, we won't have the data.
|
||
// All images go through handleImageAttachment before being sent and thus have
|
||
// already been scaled to level, oriented, stripped of exif data, and saved
|
||
// in high quality format. If we want to send the image in HQ we can return
|
||
// the attachment as-is. Otherwise we'll have to further scale it down.
|
||
if (!attachment.data || sendHQImages) {
|
||
return attachment;
|
||
}
|
||
|
||
const dataBlob = new Blob([attachment.data], {
|
||
type: attachment.contentType,
|
||
});
|
||
try {
|
||
const { blob: xcodedDataBlob } = await scaleImageToLevel(
|
||
dataBlob,
|
||
attachment.contentType,
|
||
isIncoming
|
||
);
|
||
const xcodedDataArrayBuffer = await blobToArrayBuffer(xcodedDataBlob);
|
||
|
||
// IMPORTANT: We overwrite the existing `data` `Uint8Array` losing the original
|
||
// image data. Ideally, we’d preserve the original image data for users who want to
|
||
// retain it but due to reports of data loss, we don’t want to overburden IndexedDB
|
||
// by potentially doubling stored image data.
|
||
// See: https://github.com/signalapp/Signal-Desktop/issues/1589
|
||
const xcodedAttachment = {
|
||
// `digest` is no longer valid for auto-oriented image data, so we discard it:
|
||
...omit(attachment, 'digest'),
|
||
data: new Uint8Array(xcodedDataArrayBuffer),
|
||
size: xcodedDataArrayBuffer.byteLength,
|
||
};
|
||
|
||
return xcodedAttachment;
|
||
} catch (error: unknown) {
|
||
const errorString = Errors.toLogFormat(error);
|
||
logger.error(
|
||
'autoOrientJPEG: Failed to rotate/scale attachment',
|
||
errorString
|
||
);
|
||
|
||
return attachment;
|
||
}
|
||
}
|