Use TUS for attachment uploads
This commit is contained in:
parent
4253bed0bd
commit
a992546cda
9 changed files with 312 additions and 169 deletions
|
@ -1,34 +1,46 @@
|
|||
// Copyright 2023 Signal Messenger, LLC
|
||||
// SPDX-License-Identifier: AGPL-3.0-only
|
||||
|
||||
import { createReadStream } from 'fs';
|
||||
import type {
|
||||
AttachmentWithHydratedData,
|
||||
UploadedAttachmentType,
|
||||
} from '../types/Attachment';
|
||||
import { MIMETypeToString } from '../types/MIME';
|
||||
import { padAndEncryptAttachment, getRandomBytes } from '../Crypto';
|
||||
import { getRandomBytes } from '../Crypto';
|
||||
import { strictAssert } from './assert';
|
||||
import { backupsService } from '../services/backups';
|
||||
import { tusUpload } from './uploads/tusProtocol';
|
||||
import { defaultFileReader } from './uploads/uploads';
|
||||
import type { AttachmentV3ResponseType } from '../textsecure/WebAPI';
|
||||
import {
|
||||
type EncryptedAttachmentV2,
|
||||
encryptAttachmentV2ToDisk,
|
||||
safeUnlinkSync,
|
||||
type PlaintextSourceType,
|
||||
} from '../AttachmentCrypto';
|
||||
import { missingCaseError } from './missingCaseError';
|
||||
|
||||
const CDNS_SUPPORTING_TUS = new Set([3]);
|
||||
|
||||
export async function uploadAttachment(
|
||||
attachment: AttachmentWithHydratedData
|
||||
): Promise<UploadedAttachmentType> {
|
||||
const keys = getRandomBytes(64);
|
||||
const encrypted = padAndEncryptAttachment({
|
||||
plaintext: attachment.data,
|
||||
keys,
|
||||
});
|
||||
|
||||
const { server } = window.textsecure;
|
||||
strictAssert(server, 'WebAPI must be initialized');
|
||||
|
||||
const cdnKey = await server.putEncryptedAttachment(encrypted.ciphertext);
|
||||
const size = attachment.data.byteLength;
|
||||
const keys = getRandomBytes(64);
|
||||
|
||||
const { cdnKey, cdnNumber, encrypted } = await encryptAndUploadAttachment({
|
||||
plaintext: { data: attachment.data },
|
||||
keys,
|
||||
uploadType: 'standard',
|
||||
});
|
||||
|
||||
return {
|
||||
cdnKey,
|
||||
cdnNumber: 2,
|
||||
cdnNumber,
|
||||
key: keys,
|
||||
size,
|
||||
size: attachment.data.byteLength,
|
||||
digest: encrypted.digest,
|
||||
plaintextHash: encrypted.plaintextHash,
|
||||
|
||||
|
@ -41,3 +53,91 @@ export async function uploadAttachment(
|
|||
blurHash: attachment.blurHash,
|
||||
};
|
||||
}
|
||||
|
||||
export async function encryptAndUploadAttachment({
|
||||
plaintext,
|
||||
keys,
|
||||
uploadType,
|
||||
}: {
|
||||
plaintext: PlaintextSourceType;
|
||||
keys: Uint8Array;
|
||||
uploadType: 'standard' | 'backup';
|
||||
}): Promise<{
|
||||
cdnKey: string;
|
||||
cdnNumber: number;
|
||||
encrypted: EncryptedAttachmentV2;
|
||||
}> {
|
||||
const { server } = window.textsecure;
|
||||
strictAssert(server, 'WebAPI must be initialized');
|
||||
|
||||
let uploadForm: AttachmentV3ResponseType;
|
||||
let absoluteCiphertextPath: string | undefined;
|
||||
|
||||
try {
|
||||
switch (uploadType) {
|
||||
case 'standard':
|
||||
uploadForm = await server.getAttachmentUploadForm();
|
||||
break;
|
||||
case 'backup':
|
||||
uploadForm = await server.getBackupMediaUploadForm(
|
||||
await backupsService.credentials.getHeadersForToday()
|
||||
);
|
||||
break;
|
||||
default:
|
||||
throw missingCaseError(uploadType);
|
||||
}
|
||||
|
||||
const encrypted = await encryptAttachmentV2ToDisk({
|
||||
plaintext,
|
||||
keys,
|
||||
});
|
||||
|
||||
absoluteCiphertextPath = window.Signal.Migrations.getAbsoluteAttachmentPath(
|
||||
encrypted.path
|
||||
);
|
||||
|
||||
await uploadFile({
|
||||
absoluteCiphertextPath,
|
||||
ciphertextFileSize: encrypted.ciphertextSize,
|
||||
uploadForm,
|
||||
});
|
||||
|
||||
return { cdnKey: uploadForm.key, cdnNumber: uploadForm.cdn, encrypted };
|
||||
} finally {
|
||||
if (absoluteCiphertextPath) {
|
||||
safeUnlinkSync(absoluteCiphertextPath);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export async function uploadFile({
|
||||
absoluteCiphertextPath,
|
||||
ciphertextFileSize,
|
||||
uploadForm,
|
||||
}: {
|
||||
absoluteCiphertextPath: string;
|
||||
ciphertextFileSize: number;
|
||||
uploadForm: AttachmentV3ResponseType;
|
||||
}): Promise<void> {
|
||||
const { server } = window.textsecure;
|
||||
strictAssert(server, 'WebAPI must be initialized');
|
||||
|
||||
if (CDNS_SUPPORTING_TUS.has(uploadForm.cdn)) {
|
||||
const fetchFn = server.createFetchForAttachmentUpload(uploadForm);
|
||||
await tusUpload({
|
||||
endpoint: uploadForm.signedUploadLocation,
|
||||
// the upload form headers are already included in the created fetch function
|
||||
headers: {},
|
||||
fileName: uploadForm.key,
|
||||
filePath: absoluteCiphertextPath,
|
||||
fileSize: ciphertextFileSize,
|
||||
reader: defaultFileReader,
|
||||
fetchFn,
|
||||
});
|
||||
} else {
|
||||
await server.putEncryptedAttachment(
|
||||
createReadStream(absoluteCiphertextPath),
|
||||
uploadForm
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue