diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts index 15791ae04fb..bd3a0984574 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.spec.ts @@ -379,6 +379,43 @@ describe("VaultExportService", () => { const attachment = await zip.file("attachments/mock-id/mock-file-name")?.async("blob"); expect(attachment).toBeDefined(); }); + + it("If a ciphers attachments filename is already present in the zipfile then append underscore and attachmentId for the new filename", async () => { + const cipherData = new CipherData(); + cipherData.id = "mock-id"; + const cipherView = new CipherView(new Cipher(cipherData)); + // Create 1st attachment with the same filename for the cipher + const attachmentView = new AttachmentView(new Attachment(new AttachmentData())); + attachmentView.id = "id-of-file-1"; + attachmentView.fileName = "mock-file-name"; + // Create 2nd attachment with the same filename for the cipher + const attachmentView2 = new AttachmentView(new Attachment(new AttachmentData())); + attachmentView2.id = "id-of-file-2"; + attachmentView2.fileName = "mock-file-name"; + cipherView.attachments = [attachmentView, attachmentView2]; + cipherService.getAllDecrypted.mockResolvedValue([cipherView]); + folderService.getAllDecryptedFromState.mockResolvedValue([]); + encryptService.decryptToBytes.mockResolvedValue(new Uint8Array(255)); + global.fetch = jest.fn(() => + Promise.resolve({ + status: 200, + arrayBuffer: () => Promise.resolve(new ArrayBuffer(255)), + }), + ) as any; + global.Request = jest.fn(() => {}) as any; + + const exportedVault = await exportService.getExport("zip"); + + expect(exportedVault.type).toBe("application/zip"); + const exportZip = exportedVault as ExportedVaultAsBlob; + const zip = await JSZip.loadAsync(exportZip.data); + const attachment = await zip.file("attachments/mock-id/mock-file-name")?.async("blob"); + expect(attachment).toBeDefined(); + const attachment2 = await zip + .file("attachments/mock-id/mock-file-name_id-of-file-2") + ?.async("blob"); + expect(attachment2).toBeDefined(); + }); }); describe("password protected export", () => { diff --git a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts index d253ae8d0b1..072b13a318c 100644 --- a/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts +++ b/libs/tools/export/vault-export/vault-export-core/src/services/individual-vault-export.service.ts @@ -117,6 +117,12 @@ export class IndividualVaultExportService for (const attachment of cipher.attachments) { const response = await this.downloadAttachment(cipher.id, attachment.id); const decBuf = await this.decryptAttachment(cipher, attachment, response); + + // To prevent files with the same name from overwriting each other, we append the attachment id to the file name, + // if a file with the same name already exists in the folder + if (cipherFolder.file(attachment.fileName) != null) { + attachment.fileName += `_${attachment.id}`; + } cipherFolder.file(attachment.fileName, decBuf); } }