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

[fix] Force send attachment to always download and never open (#2908)

* [refactor] Introduce a file download service

* [refactor] Point platformUtilsService.saveFile() callers to fileDownloadService.download() instead

* [refactor] Remove platformUtilsService.saveFile()

* [fix] Force send attachments to always download and never open

* [fix] Remove the window property from FileDownloadRequest

* [fix] Move FileDownloadRequest to /abstractions/fileDownload

* [fix] Simplify FileDownloadRequest to a type

* [fix] Move BrowserApi.saveFile logic into BrowserFileDownloadService

* [fix] Use proper blob types for file downloads

* [fix] forceDownload -> downloadMethod on FileDownloadRequest

* [fix] Remove fileType from FileDownloadRequest

* [fix] Make fileType private
This commit is contained in:
Addison Beck
2022-06-29 14:15:29 -07:00
committed by GitHub
parent a89b745f0b
commit bb7dce031c
35 changed files with 297 additions and 155 deletions

View File

@@ -3,6 +3,7 @@ import { Directive, EventEmitter, Input, OnInit, Output } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@@ -36,7 +37,8 @@ export class AttachmentsComponent implements OnInit {
protected apiService: ApiService,
protected win: Window,
protected logService: LogService,
protected stateService: StateService
protected stateService: StateService,
protected fileDownloadService: FileDownloadService
) {}
async ngOnInit() {
@@ -171,7 +173,10 @@ export class AttachmentsComponent implements OnInit {
? attachment.key
: await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
this.fileDownloadService.download({
fileName: attachment.fileName,
blobData: decBuf,
});
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}

View File

@@ -4,6 +4,7 @@ import { FormBuilder } from "@angular/forms";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { ExportService } from "@bitwarden/common/abstractions/export.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@@ -40,7 +41,8 @@ export class ExportComponent implements OnInit {
protected win: Window,
private logService: LogService,
private userVerificationService: UserVerificationService,
private formBuilder: FormBuilder
private formBuilder: FormBuilder,
protected fileDownloadService: FileDownloadService
) {}
async ngOnInit() {
@@ -150,6 +152,10 @@ export class ExportComponent implements OnInit {
private downloadFile(csv: string): void {
const fileName = this.getFileName();
this.platformUtilsService.saveFile(this.win, csv, { type: "text/plain" }, fileName);
this.fileDownloadService.download({
fileName: fileName,
blobData: csv,
blobOptions: { type: "text/plain" },
});
}
}

View File

@@ -15,6 +15,7 @@ import { BroadcasterService } from "@bitwarden/common/abstractions/broadcaster.s
import { CipherService } from "@bitwarden/common/abstractions/cipher.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { EventService } from "@bitwarden/common/abstractions/event.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PasswordRepromptService } from "@bitwarden/common/abstractions/passwordReprompt.service";
@@ -76,7 +77,8 @@ export class ViewComponent implements OnDestroy, OnInit {
protected apiService: ApiService,
protected passwordRepromptService: PasswordRepromptService,
private logService: LogService,
protected stateService: StateService
protected stateService: StateService,
protected fileDownloadService: FileDownloadService
) {}
ngOnInit() {
@@ -373,7 +375,10 @@ export class ViewComponent implements OnDestroy, OnInit {
? attachment.key
: await this.cryptoService.getOrgKey(this.cipher.organizationId);
const decBuf = await this.cryptoService.decryptFromBytes(buf, key);
this.platformUtilsService.saveFile(this.win, decBuf, null, attachment.fileName);
this.fileDownloadService.download({
fileName: attachment.fileName,
blobData: decBuf,
});
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}

View File

@@ -0,0 +1,5 @@
import { FileDownloadRequest } from "./fileDownloadRequest";
export abstract class FileDownloadService {
download: (request: FileDownloadRequest) => void;
}

View File

@@ -0,0 +1,50 @@
import { FileDownloadRequest } from "./fileDownloadRequest";
export class FileDownloadBuilder {
get blobOptions(): any {
const options = this._request.blobOptions ?? {};
if (options.type == null) {
options.type = this.fileType;
}
return options;
}
get blob(): Blob {
if (this.blobOptions != null) {
return new Blob([this._request.blobData], this.blobOptions);
} else {
return new Blob([this._request.blobData]);
}
}
get downloadMethod(): "save" | "open" {
if (this._request.downloadMethod != null) {
return this._request.downloadMethod;
}
return this.fileType != "application/pdf" ? "save" : "open";
}
private get fileType() {
const fileNameLower = this._request.fileName.toLowerCase();
if (fileNameLower.endsWith(".pdf")) {
return "application/pdf";
} else if (fileNameLower.endsWith(".xlsx")) {
return "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
} else if (fileNameLower.endsWith(".docx")) {
return "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
} else if (fileNameLower.endsWith(".pptx")) {
return "application/vnd.openxmlformats-officedocument.presentationml.presentation";
} else if (fileNameLower.endsWith(".csv")) {
return "text/csv";
} else if (fileNameLower.endsWith(".png")) {
return "image/png";
} else if (fileNameLower.endsWith(".jpg") || fileNameLower.endsWith(".jpeg")) {
return "image/jpeg";
} else if (fileNameLower.endsWith(".gif")) {
return "image/gif";
}
return null;
}
constructor(private readonly _request: FileDownloadRequest) {}
}

View File

@@ -0,0 +1,6 @@
export type FileDownloadRequest = {
fileName: string;
blobData: BlobPart;
blobOptions?: BlobPropertyBag;
downloadMethod?: "save" | "open";
};

View File

@@ -18,7 +18,6 @@ export abstract class PlatformUtilsService {
isMacAppStore: () => boolean;
isViewOpen: () => Promise<boolean>;
launchUri: (uri: string, options?: any) => void;
saveFile: (win: Window, blobData: any, blobOptions: any, fileName: string) => void;
getApplicationVersion: () => Promise<string>;
supportsWebAuthn: (win: Window) => boolean;
supportsDuo: () => boolean;

View File

@@ -83,16 +83,6 @@ export class ElectronPlatformUtilsService implements PlatformUtilsService {
shell.openExternal(uri);
}
saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void {
const blob = new Blob([blobData], blobOptions);
const a = win.document.createElement("a");
a.href = URL.createObjectURL(blob);
a.download = fileName;
win.document.body.appendChild(a);
a.click();
win.document.body.removeChild(a);
}
getApplicationVersion(): Promise<string> {
return ipcRenderer.invoke("appVersion");
}

View File

@@ -84,10 +84,6 @@ export class CliPlatformUtilsService implements PlatformUtilsService {
}
}
saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void {
throw new Error("Not implemented.");
}
getApplicationVersion(): Promise<string> {
return Promise.resolve(this.packageJson.version);
}