diff --git a/jslib/angular/src/services/jslib-services.module.ts b/jslib/angular/src/services/jslib-services.module.ts index 8cdc4a6e..eb09f5a3 100644 --- a/jslib/angular/src/services/jslib-services.module.ts +++ b/jslib/angular/src/services/jslib-services.module.ts @@ -8,7 +8,6 @@ import { CryptoService as CryptoServiceAbstraction } from "@/jslib/common/src/ab import { CryptoFunctionService as CryptoFunctionServiceAbstraction } from "@/jslib/common/src/abstractions/cryptoFunction.service"; import { EnvironmentService as EnvironmentServiceAbstraction } from "@/jslib/common/src/abstractions/environment.service"; import { EventService as EventServiceAbstraction } from "@/jslib/common/src/abstractions/event.service"; -import { FileUploadService as FileUploadServiceAbstraction } from "@/jslib/common/src/abstractions/fileUpload.service"; import { I18nService as I18nServiceAbstraction } from "@/jslib/common/src/abstractions/i18n.service"; import { KeyConnectorService as KeyConnectorServiceAbstraction } from "@/jslib/common/src/abstractions/keyConnector.service"; import { LogService } from "@/jslib/common/src/abstractions/log.service"; @@ -37,7 +36,6 @@ import { ConsoleLogService } from "@/jslib/common/src/services/consoleLog.servic import { CryptoService } from "@/jslib/common/src/services/crypto.service"; import { EnvironmentService } from "@/jslib/common/src/services/environment.service"; import { EventService } from "@/jslib/common/src/services/event.service"; -import { FileUploadService } from "@/jslib/common/src/services/fileUpload.service"; import { KeyConnectorService } from "@/jslib/common/src/services/keyConnector.service"; import { NotificationsService } from "@/jslib/common/src/services/notifications.service"; import { OrganizationService } from "@/jslib/common/src/services/organization.service"; @@ -142,11 +140,6 @@ import { ValidationService } from "./validation.service"; AppIdServiceAbstraction, ], }, - { - provide: FileUploadServiceAbstraction, - useClass: FileUploadService, - deps: [LogService, ApiServiceAbstraction], - }, { provide: SyncServiceAbstraction, useFactory: ( diff --git a/jslib/common/src/abstractions/fileUpload.service.ts b/jslib/common/src/abstractions/fileUpload.service.ts deleted file mode 100644 index 6918347e..00000000 --- a/jslib/common/src/abstractions/fileUpload.service.ts +++ /dev/null @@ -1,18 +0,0 @@ -import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -import { EncString } from "../models/domain/encString"; -import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse"; -import { SendFileUploadDataResponse } from "../models/response/sendFileUploadDataResponse"; - -export abstract class FileUploadService { - uploadSendFile: ( - uploadData: SendFileUploadDataResponse, - fileName: EncString, - encryptedFileData: EncArrayBuffer, - ) => Promise; - uploadCipherAttachment: ( - admin: boolean, - uploadData: AttachmentUploadDataResponse, - fileName: EncString, - encryptedFileData: EncArrayBuffer, - ) => Promise; -} diff --git a/jslib/common/src/services/azureFileUpload.service.ts b/jslib/common/src/services/azureFileUpload.service.ts deleted file mode 100644 index bcfcda17..00000000 --- a/jslib/common/src/services/azureFileUpload.service.ts +++ /dev/null @@ -1,214 +0,0 @@ -import { LogService } from "../abstractions/log.service"; -import { Utils } from "../misc/utils"; -import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; - -const MAX_SINGLE_BLOB_UPLOAD_SIZE = 256 * 1024 * 1024; // 256 MiB -const MAX_BLOCKS_PER_BLOB = 50000; - -export class AzureFileUploadService { - constructor(private logService: LogService) {} - - async upload(url: string, data: EncArrayBuffer, renewalCallback: () => Promise) { - if (data.buffer.byteLength <= MAX_SINGLE_BLOB_UPLOAD_SIZE) { - return await this.azureUploadBlob(url, data); - } else { - return await this.azureUploadBlocks(url, data, renewalCallback); - } - } - private async azureUploadBlob(url: string, data: EncArrayBuffer) { - const urlObject = Utils.getUrl(url); - const headers = new Headers({ - "x-ms-date": new Date().toUTCString(), - "x-ms-version": urlObject.searchParams.get("sv"), - "Content-Length": data.buffer.byteLength.toString(), - "x-ms-blob-type": "BlockBlob", - }); - - const request = new Request(url, { - body: data.buffer, - cache: "no-store", - method: "PUT", - headers: headers, - }); - - const blobResponse = await fetch(request); - - if (blobResponse.status !== 201) { - throw new Error(`Failed to create Azure blob: ${blobResponse.status}`); - } - } - private async azureUploadBlocks( - url: string, - data: EncArrayBuffer, - renewalCallback: () => Promise, - ) { - const baseUrl = Utils.getUrl(url); - const blockSize = this.getMaxBlockSize(baseUrl.searchParams.get("sv")); - let blockIndex = 0; - const numBlocks = Math.ceil(data.buffer.byteLength / blockSize); - const blocksStaged: string[] = []; - - if (numBlocks > MAX_BLOCKS_PER_BLOB) { - throw new Error( - `Cannot upload file, exceeds maximum size of ${blockSize * MAX_BLOCKS_PER_BLOB}`, - ); - } - - // eslint-disable-next-line - try { - while (blockIndex < numBlocks) { - url = await this.renewUrlIfNecessary(url, renewalCallback); - const blockUrl = Utils.getUrl(url); - const blockId = this.encodedBlockId(blockIndex); - blockUrl.searchParams.append("comp", "block"); - blockUrl.searchParams.append("blockid", blockId); - const start = blockIndex * blockSize; - const blockData = data.buffer.slice(start, start + blockSize); - const blockHeaders = new Headers({ - "x-ms-date": new Date().toUTCString(), - "x-ms-version": blockUrl.searchParams.get("sv"), - "Content-Length": blockData.byteLength.toString(), - }); - - const blockRequest = new Request(blockUrl.toString(), { - body: blockData, - cache: "no-store", - method: "PUT", - headers: blockHeaders, - }); - - const blockResponse = await fetch(blockRequest); - - if (blockResponse.status !== 201) { - const message = `Unsuccessful block PUT. Received status ${blockResponse.status}`; - this.logService.error(message + "\n" + (await blockResponse.json())); - throw new Error(message); - } - - blocksStaged.push(blockId); - blockIndex++; - } - - url = await this.renewUrlIfNecessary(url, renewalCallback); - const blockListUrl = Utils.getUrl(url); - const blockListXml = this.blockListXml(blocksStaged); - blockListUrl.searchParams.append("comp", "blocklist"); - const headers = new Headers({ - "x-ms-date": new Date().toUTCString(), - "x-ms-version": blockListUrl.searchParams.get("sv"), - "Content-Length": blockListXml.length.toString(), - }); - - const request = new Request(blockListUrl.toString(), { - body: blockListXml, - cache: "no-store", - method: "PUT", - headers: headers, - }); - - const response = await fetch(request); - - if (response.status !== 201) { - const message = `Unsuccessful block list PUT. Received status ${response.status}`; - this.logService.error(message + "\n" + (await response.json())); - throw new Error(message); - } - } catch (e) { - throw e; - } - } - - private async renewUrlIfNecessary( - url: string, - renewalCallback: () => Promise, - ): Promise { - const urlObject = Utils.getUrl(url); - const expiry = new Date(urlObject.searchParams.get("se") ?? ""); - - if (isNaN(expiry.getTime())) { - expiry.setTime(Date.now() + 3600000); - } - - if (expiry.getTime() < Date.now() + 1000) { - return await renewalCallback(); - } - return url; - } - - private encodedBlockId(blockIndex: number) { - // Encoded blockId max size is 64, so pre-encoding max size is 48 - const utfBlockId = ( - "000000000000000000000000000000000000000000000000" + blockIndex.toString() - ).slice(-48); - return Utils.fromUtf8ToB64(utfBlockId); - } - - private blockListXml(blockIdList: string[]) { - let xml = ''; - blockIdList.forEach((blockId) => { - xml += `${blockId}`; - }); - xml += ""; - return xml; - } - - private getMaxBlockSize(version: string) { - if (Version.compare(version, "2019-12-12") >= 0) { - return 4000 * 1024 * 1024; // 4000 MiB - } else if (Version.compare(version, "2016-05-31") >= 0) { - return 100 * 1024 * 1024; // 100 MiB - } else { - return 4 * 1024 * 1024; // 4 MiB - } - } -} - -class Version { - /** - * Compares two Azure Versions against each other - * @param a Version to compare - * @param b Version to compare - * @returns a number less than zero if b is newer than a, 0 if equal, - * and greater than zero if a is newer than b - */ - static compare(a: Required | string, b: Required | string) { - if (typeof a === "string") { - a = new Version(a); - } - - if (typeof b === "string") { - b = new Version(b); - } - - return a.year !== b.year - ? a.year - b.year - : a.month !== b.month - ? a.month - b.month - : a.day !== b.day - ? a.day - b.day - : 0; - } - year = 0; - month = 0; - day = 0; - - constructor(version: string) { - try { - const parts = version.split("-").map((v) => Number.parseInt(v, 10)); - this.year = parts[0]; - this.month = parts[1]; - this.day = parts[2]; - } catch { - // Ignore error - } - } - /** - * Compares two Azure Versions against each other - * @param compareTo Version to compare against - * @returns a number less than zero if compareTo is newer, 0 if equal, - * and greater than zero if this is greater than compareTo - */ - compare(compareTo: Required | string) { - return Version.compare(this, compareTo); - } -} diff --git a/jslib/common/src/services/bitwardenFileUpload.service.ts b/jslib/common/src/services/bitwardenFileUpload.service.ts deleted file mode 100644 index 0347a902..00000000 --- a/jslib/common/src/services/bitwardenFileUpload.service.ts +++ /dev/null @@ -1,34 +0,0 @@ -import { ApiService } from "../abstractions/api.service"; -import { Utils } from "../misc/utils"; -import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; - -export class BitwardenFileUploadService { - constructor(private apiService: ApiService) {} - - async upload( - encryptedFileName: string, - encryptedFileData: EncArrayBuffer, - apiCall: (fd: FormData) => Promise, - ) { - const fd = new FormData(); - try { - const blob = new Blob([encryptedFileData.buffer], { type: "application/octet-stream" }); - fd.append("data", blob, encryptedFileName); - } catch (e) { - if (Utils.isNode && !Utils.isBrowser) { - fd.append( - "data", - Buffer.from(encryptedFileData.buffer) as any, - { - filepath: encryptedFileName, - contentType: "application/octet-stream", - } as any, - ); - } else { - throw e; - } - } - - await apiCall(fd); - } -} diff --git a/jslib/common/src/services/fileUpload.service.ts b/jslib/common/src/services/fileUpload.service.ts deleted file mode 100644 index aafe4178..00000000 --- a/jslib/common/src/services/fileUpload.service.ts +++ /dev/null @@ -1,111 +0,0 @@ -import { ApiService } from "../abstractions/api.service"; -import { FileUploadService as FileUploadServiceAbstraction } from "../abstractions/fileUpload.service"; -import { LogService } from "../abstractions/log.service"; -import { FileUploadType } from "../enums/fileUploadType"; -import { EncArrayBuffer } from "../models/domain/encArrayBuffer"; -import { EncString } from "../models/domain/encString"; -import { AttachmentUploadDataResponse } from "../models/response/attachmentUploadDataResponse"; -import { SendFileUploadDataResponse } from "../models/response/sendFileUploadDataResponse"; - -import { AzureFileUploadService } from "./azureFileUpload.service"; -import { BitwardenFileUploadService } from "./bitwardenFileUpload.service"; - -export class FileUploadService implements FileUploadServiceAbstraction { - private azureFileUploadService: AzureFileUploadService; - private bitwardenFileUploadService: BitwardenFileUploadService; - - constructor( - private logService: LogService, - private apiService: ApiService, - ) { - this.azureFileUploadService = new AzureFileUploadService(logService); - this.bitwardenFileUploadService = new BitwardenFileUploadService(apiService); - } - - async uploadSendFile( - uploadData: SendFileUploadDataResponse, - fileName: EncString, - encryptedFileData: EncArrayBuffer, - ) { - try { - switch (uploadData.fileUploadType) { - case FileUploadType.Direct: - await this.bitwardenFileUploadService.upload( - fileName.encryptedString, - encryptedFileData, - (fd) => - this.apiService.postSendFile( - uploadData.sendResponse.id, - uploadData.sendResponse.file.id, - fd, - ), - ); - break; - case FileUploadType.Azure: { - const renewalCallback = async () => { - const renewalResponse = await this.apiService.renewSendFileUploadUrl( - uploadData.sendResponse.id, - uploadData.sendResponse.file.id, - ); - return renewalResponse.url; - }; - await this.azureFileUploadService.upload( - uploadData.url, - encryptedFileData, - renewalCallback, - ); - break; - } - default: - throw new Error("Unknown file upload type"); - } - } catch (e) { - await this.apiService.deleteSend(uploadData.sendResponse.id); - throw e; - } - } - - async uploadCipherAttachment( - admin: boolean, - uploadData: AttachmentUploadDataResponse, - encryptedFileName: EncString, - encryptedFileData: EncArrayBuffer, - ) { - const response = admin ? uploadData.cipherMiniResponse : uploadData.cipherResponse; - try { - switch (uploadData.fileUploadType) { - case FileUploadType.Direct: - await this.bitwardenFileUploadService.upload( - encryptedFileName.encryptedString, - encryptedFileData, - (fd) => this.apiService.postAttachmentFile(response.id, uploadData.attachmentId, fd), - ); - break; - case FileUploadType.Azure: { - const renewalCallback = async () => { - const renewalResponse = await this.apiService.renewAttachmentUploadUrl( - response.id, - uploadData.attachmentId, - ); - return renewalResponse.url; - }; - await this.azureFileUploadService.upload( - uploadData.url, - encryptedFileData, - renewalCallback, - ); - break; - } - default: - throw new Error("Unknown file upload type."); - } - } catch (e) { - if (admin) { - await this.apiService.deleteCipherAttachmentAdmin(response.id, uploadData.attachmentId); - } else { - await this.apiService.deleteCipherAttachment(response.id, uploadData.attachmentId); - } - throw e; - } - } -} diff --git a/src/bwdc.ts b/src/bwdc.ts index cc06628a..afc4573a 100644 --- a/src/bwdc.ts +++ b/src/bwdc.ts @@ -11,7 +11,6 @@ import { AppIdService } from "@/jslib/common/src/services/appId.service"; import { ContainerService } from "@/jslib/common/src/services/container.service"; import { CryptoService } from "@/jslib/common/src/services/crypto.service"; import { EnvironmentService } from "@/jslib/common/src/services/environment.service"; -import { FileUploadService } from "@/jslib/common/src/services/fileUpload.service"; import { KeyConnectorService } from "@/jslib/common/src/services/keyConnector.service"; import { NoopMessagingService } from "@/jslib/common/src/services/noopMessaging.service"; import { OrganizationService } from "@/jslib/common/src/services/organization.service"; @@ -55,7 +54,6 @@ export class Main { containerService: ContainerService; cryptoFunctionService: NodeCryptoFunctionService; authService: AuthService; - fileUploadService: FileUploadService; searchService: SearchService; settingsService: SettingsService; syncService: SyncService; @@ -199,8 +197,6 @@ export class Main { this.settingsService = new SettingsService(this.stateService); - this.fileUploadService = new FileUploadService(this.logService, this.apiService); - this.searchService = new SearchService(this.logService, this.i18nService); this.program = new Program(this);