You cannot select more than 25 topics
			Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
		
		
		
		
		
			
		
			
				
	
	
		
			163 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			TypeScript
		
	
			
		
		
	
	
			163 lines
		
	
	
		
			4.5 KiB
		
	
	
	
		
			TypeScript
		
	
| import * as crypto from 'crypto';
 | |
| import { Attachment } from '../../types/Attachment';
 | |
| 
 | |
| import {
 | |
|   AttachmentPointer,
 | |
|   AttachmentPointerWithUrl,
 | |
|   PreviewWithAttachmentUrl,
 | |
|   Quote,
 | |
|   QuotedAttachmentWithUrl,
 | |
| } from '../messages/outgoing/visibleMessage/VisibleMessage';
 | |
| import { FSv2 } from '../apis/file_server_api';
 | |
| import { addAttachmentPadding } from '../crypto/BufferPadding';
 | |
| import _ from 'lodash';
 | |
| 
 | |
| interface UploadParams {
 | |
|   attachment: Attachment;
 | |
|   isAvatar?: boolean;
 | |
|   isRaw?: boolean;
 | |
|   shouldPad?: boolean;
 | |
| }
 | |
| 
 | |
| export interface RawPreview {
 | |
|   url: string;
 | |
|   title?: string;
 | |
|   image: Attachment;
 | |
| }
 | |
| 
 | |
| export interface RawQuoteAttachment {
 | |
|   contentType?: string;
 | |
|   fileName?: string;
 | |
|   thumbnail?: Attachment;
 | |
| }
 | |
| 
 | |
| export interface RawQuote {
 | |
|   id: number;
 | |
|   author: string;
 | |
|   text?: string;
 | |
|   attachments?: Array<RawQuoteAttachment>;
 | |
| }
 | |
| 
 | |
| // tslint:disable-next-line: no-unnecessary-class
 | |
| export class AttachmentFsV2Utils {
 | |
|   private constructor() {}
 | |
| 
 | |
|   public static async uploadToFsV2(params: UploadParams): Promise<AttachmentPointerWithUrl> {
 | |
|     const { attachment, isRaw = false, shouldPad = false } = params;
 | |
|     if (typeof attachment !== 'object' || attachment == null) {
 | |
|       throw new Error('Invalid attachment passed.');
 | |
|     }
 | |
| 
 | |
|     if (!(attachment.data instanceof ArrayBuffer)) {
 | |
|       throw new TypeError(
 | |
|         `\`attachment.data\` must be an \`ArrayBuffer\`; got: ${typeof attachment.data}`
 | |
|       );
 | |
|     }
 | |
|     const pointer: AttachmentPointer = {
 | |
|       contentType: attachment.contentType || undefined,
 | |
|       size: attachment.size,
 | |
|       fileName: attachment.fileName,
 | |
|       flags: attachment.flags,
 | |
|       caption: attachment.caption,
 | |
|     };
 | |
| 
 | |
|     let attachmentData: ArrayBuffer;
 | |
| 
 | |
|     if (isRaw) {
 | |
|       attachmentData = attachment.data;
 | |
|     } else {
 | |
|       pointer.key = new Uint8Array(crypto.randomBytes(64));
 | |
|       const iv = new Uint8Array(crypto.randomBytes(16));
 | |
| 
 | |
|       const dataToEncrypt = !shouldPad ? attachment.data : addAttachmentPadding(attachment.data);
 | |
|       const data = await window.textsecure.crypto.encryptAttachment(
 | |
|         dataToEncrypt,
 | |
|         pointer.key.buffer,
 | |
|         iv.buffer
 | |
|       );
 | |
|       pointer.digest = new Uint8Array(data.digest);
 | |
|       attachmentData = data.ciphertext;
 | |
|     }
 | |
| 
 | |
|     // use file server v2
 | |
|     if (FSv2.useFileServerAPIV2Sending) {
 | |
|       const uploadToV2Result = await FSv2.uploadFileToFsV2(attachmentData);
 | |
|       if (uploadToV2Result) {
 | |
|         const pointerWithUrl: AttachmentPointerWithUrl = {
 | |
|           ...pointer,
 | |
|           id: uploadToV2Result.fileId,
 | |
|           url: uploadToV2Result.fileUrl,
 | |
|         };
 | |
|         return pointerWithUrl;
 | |
|       }
 | |
|       window?.log?.warn('upload to file server v2 failed');
 | |
|       throw new Error(`upload to file server v2 of ${attachment.fileName} failed`);
 | |
|     }
 | |
|     throw new Error('Only v2 fileserver upload is supported');
 | |
|   }
 | |
| 
 | |
|   public static async uploadAttachmentsToFsV2(
 | |
|     attachments: Array<Attachment>
 | |
|   ): Promise<Array<AttachmentPointerWithUrl>> {
 | |
|     const promises = (attachments || []).map(async attachment =>
 | |
|       this.uploadToFsV2({
 | |
|         attachment,
 | |
|         shouldPad: true,
 | |
|       })
 | |
|     );
 | |
| 
 | |
|     return Promise.all(promises);
 | |
|   }
 | |
| 
 | |
|   public static async uploadLinkPreviewsToFsV2(
 | |
|     previews: Array<RawPreview>
 | |
|   ): Promise<Array<PreviewWithAttachmentUrl>> {
 | |
|     const promises = (previews || []).map(async preview => {
 | |
|       // some links does not have an image associated, and it makes the whole message fail to send
 | |
|       if (!preview.image) {
 | |
|         window.log.warn('tried to upload file to fsv2 without image.. skipping');
 | |
|         return preview as any;
 | |
|       }
 | |
|       const image = await this.uploadToFsV2({
 | |
|         attachment: preview.image,
 | |
|       });
 | |
|       return {
 | |
|         ...preview,
 | |
|         image,
 | |
|       };
 | |
|     });
 | |
|     return _.compact(await Promise.all(promises));
 | |
|   }
 | |
| 
 | |
|   public static async uploadQuoteThumbnailsToFsV2(quote?: RawQuote): Promise<Quote | undefined> {
 | |
|     if (!quote) {
 | |
|       return undefined;
 | |
|     }
 | |
| 
 | |
|     const promises = (quote.attachments ?? []).map(async attachment => {
 | |
|       let thumbnail: AttachmentPointer | undefined;
 | |
|       if (attachment.thumbnail) {
 | |
|         thumbnail = await this.uploadToFsV2({
 | |
|           attachment: attachment.thumbnail,
 | |
|         });
 | |
|       }
 | |
|       if (!thumbnail) {
 | |
|         return attachment;
 | |
|       }
 | |
|       return {
 | |
|         ...attachment,
 | |
|         thumbnail,
 | |
|         url: thumbnail.url,
 | |
|         id: thumbnail.id,
 | |
|       } as QuotedAttachmentWithUrl;
 | |
|     });
 | |
| 
 | |
|     const attachments = _.compact(await Promise.all(promises));
 | |
| 
 | |
|     return {
 | |
|       ...quote,
 | |
|       attachments,
 | |
|     };
 | |
|   }
 | |
| }
 |