1
0
mirror of https://github.com/bitwarden/browser synced 2025-12-17 16:53:34 +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

@@ -1,6 +1,7 @@
import { Directive } from "@angular/core";
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";
@@ -30,7 +31,8 @@ export abstract class BaseEventsComponent {
protected i18nService: I18nService,
protected exportService: ExportService,
protected platformUtilsService: PlatformUtilsService,
protected logService: LogService
protected logService: LogService,
protected fileDownloadService: FileDownloadService
) {
const defaultDates = this.eventService.getDefaultDateFilters();
this.start = defaultDates[0];
@@ -173,6 +175,10 @@ export abstract class BaseEventsComponent {
const data = await this.exportService.getEventExport(events);
const fileName = this.exportService.getFileName(this.exportFileName, "csv");
this.platformUtilsService.saveFile(window, data, { type: "text/plain" }, fileName);
this.fileDownloadService.download({
fileName,
blobData: data,
blobOptions: { type: "text/plain" },
});
}
}

View File

@@ -4,6 +4,7 @@ import { ActivatedRoute, Router } from "@angular/router";
import { UserNamePipe } from "@bitwarden/angular/pipes/user-name.pipe";
import { ApiService } from "@bitwarden/common/abstractions/api.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 { OrganizationService } from "@bitwarden/common/abstractions/organization.service";
@@ -37,9 +38,17 @@ export class EventsComponent extends BaseEventsComponent implements OnInit {
logService: LogService,
private userNamePipe: UserNamePipe,
private organizationService: OrganizationService,
private providerService: ProviderService
private providerService: ProviderService,
fileDownloadService: FileDownloadService
) {
super(eventService, i18nService, exportService, platformUtilsService, logService);
super(
eventService,
i18nService,
exportService,
platformUtilsService,
logService,
fileDownloadService
);
}
async ngOnInit() {

View File

@@ -1,8 +1,8 @@
import { Component, EventEmitter, Input, Output } from "@angular/core";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@Component({
selector: "app-download-license",
@@ -18,7 +18,7 @@ export class DownloadLicenseComponent {
constructor(
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
private fileDownloadService: FileDownloadService,
private logService: LogService
) {}
@@ -34,12 +34,10 @@ export class DownloadLicenseComponent {
);
const license = await this.formPromise;
const licenseString = JSON.stringify(license, null, 2);
this.platformUtilsService.saveFile(
window,
licenseString,
null,
"bitwarden_organization_license.json"
);
this.fileDownloadService.download({
fileName: "bitwarden_organization_license.json",
blobData: licenseString,
});
this.onDownloaded.emit();
} catch (e) {
this.logService.error(e);

View File

@@ -5,6 +5,7 @@ import { ActivatedRoute } from "@angular/router";
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";
@@ -28,7 +29,8 @@ export class OrganizationExportComponent extends ExportComponent {
policyService: PolicyService,
logService: LogService,
userVerificationService: UserVerificationService,
formBuilder: FormBuilder
formBuilder: FormBuilder,
fileDownloadService: FileDownloadService
) {
super(
cryptoService,
@@ -39,7 +41,8 @@ export class OrganizationExportComponent extends ExportComponent {
policyService,
logService,
userVerificationService,
formBuilder
formBuilder,
fileDownloadService
);
}

View File

@@ -3,6 +3,7 @@ import { Component } 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";
@@ -29,7 +30,8 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService
logService: LogService,
fileDownloadService: FileDownloadService
) {
super(
cipherService,
@@ -38,7 +40,8 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
stateService,
platformUtilsService,
apiService,
logService
logService,
fileDownloadService
);
}

View File

@@ -4,6 +4,7 @@ import { ActivatedRoute } from "@angular/router";
import { ApiService } from "@bitwarden/common/abstractions/api.service";
import { CryptoService } from "@bitwarden/common/abstractions/crypto.service";
import { CryptoFunctionService } from "@bitwarden/common/abstractions/cryptoFunction.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService } from "@bitwarden/common/abstractions/i18n.service";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
import { SEND_KDF_ITERATIONS } from "@bitwarden/common/enums/kdfType";
@@ -44,7 +45,8 @@ export class AccessComponent implements OnInit {
private apiService: ApiService,
private platformUtilsService: PlatformUtilsService,
private route: ActivatedRoute,
private cryptoService: CryptoService
private cryptoService: CryptoService,
private fileDownloadService: FileDownloadService
) {}
get sendText() {
@@ -109,7 +111,11 @@ export class AccessComponent implements OnInit {
try {
const buf = await response.arrayBuffer();
const decBuf = await this.cryptoService.decryptFromBytes(buf, this.decKey);
this.platformUtilsService.saveFile(window, decBuf, null, this.send.file.fileName);
this.fileDownloadService.download({
fileName: this.send.file.fileName,
blobData: decBuf,
downloadMethod: "save",
});
} catch (e) {
this.platformUtilsService.showToast("error", null, this.i18nService.t("errorOccurred"));
}

View File

@@ -11,6 +11,7 @@ import {
MEMORY_STORAGE,
} from "@bitwarden/angular/services/jslib-services.module";
import { ModalService as ModalServiceAbstraction } from "@bitwarden/angular/services/modal.service";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { I18nService as I18nServiceAbstraction } from "@bitwarden/common/abstractions/i18n.service";
import { LogService } from "@bitwarden/common/abstractions/log.service";
import { MessagingService as MessagingServiceAbstraction } from "@bitwarden/common/abstractions/messaging.service";
@@ -41,6 +42,7 @@ import { InitService } from "./init.service";
import { ModalService } from "./modal.service";
import { PolicyListService } from "./policy-list.service";
import { RouterService } from "./router.service";
import { WebFileDownloadService } from "./webFileDownload.service";
@NgModule({
imports: [ToastrModule, JslibServicesModule],
@@ -114,6 +116,10 @@ import { RouterService } from "./router.service";
provide: PasswordRepromptServiceAbstraction,
useClass: PasswordRepromptService,
},
{
provide: FileDownloadService,
useClass: WebFileDownloadService,
},
HomeGuard,
],
})

View File

@@ -0,0 +1,26 @@
import { Injectable } from "@angular/core";
import { FileDownloadService } from "@bitwarden/common/abstractions/fileDownload/fileDownload.service";
import { FileDownloadBuilder } from "@bitwarden/common/abstractions/fileDownload/fileDownloadBuilder";
import { FileDownloadRequest } from "@bitwarden/common/abstractions/fileDownload/fileDownloadRequest";
import { PlatformUtilsService } from "@bitwarden/common/abstractions/platformUtils.service";
@Injectable()
export class WebFileDownloadService implements FileDownloadService {
constructor(private platformUtilsService: PlatformUtilsService) {}
download(request: FileDownloadRequest): void {
const builder = new FileDownloadBuilder(request);
const a = window.document.createElement("a");
if (builder.downloadMethod === "save") {
a.download = request.fileName;
} else if (!this.platformUtilsService.isSafari()) {
a.target = "_blank";
}
a.href = URL.createObjectURL(builder.blob);
a.style.position = "fixed";
window.document.body.appendChild(a);
a.click();
window.document.body.removeChild(a);
}
}

View File

@@ -4,6 +4,7 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/ang
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";
@@ -25,7 +26,8 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService
logService: LogService,
fileDownloadService: FileDownloadService
) {
super(
cipherService,
@@ -35,7 +37,8 @@ export class EmergencyAccessAttachmentsComponent extends BaseAttachmentsComponen
apiService,
window,
logService,
stateService
stateService,
fileDownloadService
);
}

View File

@@ -2,6 +2,7 @@ import { Component, OnInit } from "@angular/core";
import { Router } from "@angular/router";
import { ApiService } from "@bitwarden/common/abstractions/api.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";
@@ -30,7 +31,8 @@ export class UserSubscriptionComponent implements OnInit {
private platformUtilsService: PlatformUtilsService,
private i18nService: I18nService,
private router: Router,
private logService: LogService
private logService: LogService,
private fileDownloadService: FileDownloadService
) {
this.selfHosted = platformUtilsService.isSelfHost();
}
@@ -139,12 +141,10 @@ export class UserSubscriptionComponent implements OnInit {
}
const licenseString = JSON.stringify(this.sub.license, null, 2);
this.platformUtilsService.saveFile(
window,
licenseString,
null,
"bitwarden_premium_license.json"
);
this.fileDownloadService.download({
fileName: "bitwarden_premium_license.json",
blobData: licenseString,
});
}
updateLicense() {

View File

@@ -5,6 +5,7 @@ import { ExportComponent as BaseExportComponent } from "@bitwarden/angular/compo
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";
@@ -27,7 +28,8 @@ export class ExportComponent extends BaseExportComponent {
policyService: PolicyService,
logService: LogService,
userVerificationService: UserVerificationService,
formBuilder: FormBuilder
formBuilder: FormBuilder,
fileDownloadService: FileDownloadService
) {
super(
cryptoService,
@@ -39,7 +41,8 @@ export class ExportComponent extends BaseExportComponent {
window,
logService,
userVerificationService,
formBuilder
formBuilder,
fileDownloadService
);
}

View File

@@ -4,6 +4,7 @@ import { AttachmentsComponent as BaseAttachmentsComponent } from "@bitwarden/ang
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";
@@ -24,7 +25,8 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
stateService: StateService,
platformUtilsService: PlatformUtilsService,
apiService: ApiService,
logService: LogService
logService: LogService,
fileDownloadService: FileDownloadService
) {
super(
cipherService,
@@ -34,7 +36,8 @@ export class AttachmentsComponent extends BaseAttachmentsComponent {
apiService,
window,
logService,
stateService
stateService,
fileDownloadService
);
}

View File

@@ -104,54 +104,6 @@ export class WebPlatformUtilsService implements PlatformUtilsService {
document.body.removeChild(a);
}
saveFile(win: Window, blobData: any, blobOptions: any, fileName: string): void {
let blob: Blob = null;
let type: string = null;
const fileNameLower = fileName.toLowerCase();
let doDownload = true;
if (fileNameLower.endsWith(".pdf")) {
type = "application/pdf";
doDownload = false;
} else if (fileNameLower.endsWith(".xlsx")) {
type = "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet";
} else if (fileNameLower.endsWith(".docx")) {
type = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
} else if (fileNameLower.endsWith(".pptx")) {
type = "application/vnd.openxmlformats-officedocument.presentationml.presentation";
} else if (fileNameLower.endsWith(".csv")) {
type = "text/csv";
} else if (fileNameLower.endsWith(".png")) {
type = "image/png";
} else if (fileNameLower.endsWith(".jpg") || fileNameLower.endsWith(".jpeg")) {
type = "image/jpeg";
} else if (fileNameLower.endsWith(".gif")) {
type = "image/gif";
}
if (type != null) {
blobOptions = blobOptions || {};
if (blobOptions.type == null) {
blobOptions.type = type;
}
}
if (blobOptions != null) {
blob = new Blob([blobData], blobOptions);
} else {
blob = new Blob([blobData]);
}
const a = win.document.createElement("a");
if (doDownload) {
a.download = fileName;
} else if (!this.isSafari()) {
a.target = "_blank";
}
a.href = URL.createObjectURL(blob);
a.style.position = "fixed";
win.document.body.appendChild(a);
a.click();
win.document.body.removeChild(a);
}
getApplicationVersion(): Promise<string> {
return Promise.resolve(process.env.APPLICATION_VERSION || "-");
}