1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-20 10:13:31 +00:00

Platform/pm 19/platform team file moves (#5460)

* Rename service-factory folder

* Move cryptographic service factories

* Move crypto models

* Move crypto services

* Move domain base class

* Platform code owners

* Move desktop log services

* Move log files

* Establish component library ownership

* Move background listeners

* Move background background

* Move localization to Platform

* Move browser alarms to Platform

* Move browser state to Platform

* Move CLI state to Platform

* Move Desktop native concerns to Platform

* Move flag and misc to Platform

* Lint fixes

* Move electron state to platform

* Move web state to Platform

* Move lib state to Platform

* Fix broken tests

* Rename interface to idiomatic TS

* `npm run prettier` 🤖

* Resolve review feedback

* Set platform as owners of web core and shared

* Expand moved services

* Fix test types

---------

Co-authored-by: Hinton <hinton@users.noreply.github.com>
This commit is contained in:
Matt Gibson
2023-06-06 15:34:53 -05:00
committed by GitHub
parent ce4fc31efd
commit 78248db590
869 changed files with 2840 additions and 2746 deletions

View File

@@ -0,0 +1,214 @@
import { LogService } from "../../abstractions/log.service";
import { Utils } from "../../misc/utils";
import { EncArrayBuffer } from "../../models/domain/enc-array-buffer";
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<string>) {
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<string>
) {
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<string>
): Promise<string> {
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 = '<?xml version="1.0" encoding="utf-8"?><BlockList>';
blockIdList.forEach((blockId) => {
xml += `<Latest>${blockId}</Latest>`;
});
xml += "</BlockList>";
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<Version> | string, b: Required<Version> | 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<Version> | string) {
return Version.compare(this, compareTo);
}
}

View File

@@ -0,0 +1,31 @@
import { Utils } from "../../misc/utils";
import { EncArrayBuffer } from "../../models/domain/enc-array-buffer";
export class BitwardenFileUploadService {
async upload(
encryptedFileName: string,
encryptedFileData: EncArrayBuffer,
apiCall: (fd: FormData) => Promise<any>
) {
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);
}
}

View File

@@ -0,0 +1,53 @@
import { FileUploadType } from "../../../enums";
import {
FileUploadApiMethods,
FileUploadService as FileUploadServiceAbstraction,
} from "../../abstractions/file-upload/file-upload.service";
import { LogService } from "../../abstractions/log.service";
import { EncArrayBuffer } from "../../models/domain/enc-array-buffer";
import { EncString } from "../../models/domain/enc-string";
import { AzureFileUploadService } from "./azure-file-upload.service";
import { BitwardenFileUploadService } from "./bitwarden-file-upload.service";
export class FileUploadService implements FileUploadServiceAbstraction {
private azureFileUploadService: AzureFileUploadService;
private bitwardenFileUploadService: BitwardenFileUploadService;
constructor(protected logService: LogService) {
this.azureFileUploadService = new AzureFileUploadService(logService);
this.bitwardenFileUploadService = new BitwardenFileUploadService();
}
async upload(
uploadData: { url: string; fileUploadType: FileUploadType },
fileName: EncString,
encryptedFileData: EncArrayBuffer,
fileUploadMethods: FileUploadApiMethods
) {
try {
switch (uploadData.fileUploadType) {
case FileUploadType.Direct:
await this.bitwardenFileUploadService.upload(
fileName.encryptedString,
encryptedFileData,
(fd) => fileUploadMethods.postDirect(fd)
);
break;
case FileUploadType.Azure: {
await this.azureFileUploadService.upload(
uploadData.url,
encryptedFileData,
fileUploadMethods.renewFileUploadUrl
);
break;
}
default:
throw new Error("Unknown file upload type");
}
} catch (e) {
await fileUploadMethods.rollback();
throw e;
}
}
}