diff --git a/libs/importer/src/importers/msecure-csv-importer.spec.ts b/libs/importer/src/importers/msecure-csv-importer.spec.ts index 83e35802fac..3cf7cc713a8 100644 --- a/libs/importer/src/importers/msecure-csv-importer.spec.ts +++ b/libs/importer/src/importers/msecure-csv-importer.spec.ts @@ -8,6 +8,24 @@ describe("MSecureCsvImporter.parse", () => { importer = new MSecureCsvImporter(); }); + it("should correctly parse legacy formatted cards", async () => { + const mockCsvData = + `aWeirdOldStyleCard|1032,Credit Card,,Security code 1234,Card Number|12|5555 4444 3333 2222,Expiration Date|11|04/0029,Name on Card|9|Obi Wan Kenobi,Security Code|9|444,`.trim(); + const result = await importer.parse(mockCsvData); + + expect(result.success).toBe(true); + expect(result.ciphers.length).toBe(1); + const cipher = result.ciphers[0]; + expect(cipher.name).toBe("aWeirdOldStyleCard"); + expect(cipher.type).toBe(CipherType.Card); + expect(cipher.card.number).toBe("5555 4444 3333 2222"); + expect(cipher.card.expiration).toBe("04 / 2029"); + expect(cipher.card.code).toBe("444"); + expect(cipher.card.cardholderName).toBe("Obi Wan Kenobi"); + expect(cipher.notes).toBe("Security code 1234"); + expect(cipher.card.brand).toBe(""); + }); + it("should correctly parse credit card entries as Secret Notes", async () => { const mockCsvData = `myCreditCard|155089404,Credit Card,,,Card Number|12|41111111111111111,Expiration Date|11|05/2026,Security Code|9|123,Name on Card|0|John Doe,PIN|9|1234,Issuing Bank|0|Visa,Phone Number|4|,Billing Address|0|,`.trim(); diff --git a/libs/importer/src/importers/msecure-csv-importer.ts b/libs/importer/src/importers/msecure-csv-importer.ts index 322764fa8dc..e78c715976f 100644 --- a/libs/importer/src/importers/msecure-csv-importer.ts +++ b/libs/importer/src/importers/msecure-csv-importer.ts @@ -43,23 +43,34 @@ export class MSecureCsvImporter extends BaseImporter implements Importer { ).split("/"); cipher.card.expMonth = month.trim(); cipher.card.expYear = year.trim(); - cipher.card.code = this.getValueOrDefault(this.splitValueRetainingLastPart(value[6])); - cipher.card.cardholderName = this.getValueOrDefault( - this.splitValueRetainingLastPart(value[7]), + const securityCodeRegex = RegExp("^Security Code\\|\\d*\\|"); + const securityCodeEntry = value.find((entry: string) => securityCodeRegex.test(entry)); + cipher.card.code = this.getValueOrDefault( + this.splitValueRetainingLastPart(securityCodeEntry), ); - cipher.card.brand = this.getValueOrDefault(this.splitValueRetainingLastPart(value[9])); - cipher.notes = - this.getValueOrDefault(value[8].split("|")[0]) + - ": " + - this.getValueOrDefault(this.splitValueRetainingLastPart(value[8]), "") + - "\n" + - this.getValueOrDefault(value[10].split("|")[0]) + - ": " + - this.getValueOrDefault(this.splitValueRetainingLastPart(value[10]), "") + - "\n" + - this.getValueOrDefault(value[11].split("|")[0]) + - ": " + - this.getValueOrDefault(this.splitValueRetainingLastPart(value[11]), ""); + + const cardNameRegex = RegExp("^Name on Card\\|\\d*\\|"); + const nameOnCardEntry = value.find((entry: string) => entry.match(cardNameRegex)); + cipher.card.cardholderName = this.getValueOrDefault( + this.splitValueRetainingLastPart(nameOnCardEntry), + ); + + cipher.card.brand = this.getValueOrDefault(this.splitValueRetainingLastPart(value[9]), ""); + + const noteRegex = RegExp("\\|\\d*\\|"); + const rawNotes = value + .slice(2) + .filter((entry: string) => !this.isNullOrWhitespace(entry) && !noteRegex.test(entry)); + const noteIndexes = [8, 10, 11]; + const indexedNotes = noteIndexes + .filter((idx) => value[idx] && noteRegex.test(value[idx])) + .map((idx) => value[idx]) + .map((val) => { + const key = val.split("|")[0]; + const value = this.getValueOrDefault(this.splitValueRetainingLastPart(val), ""); + return `${key}: ${value}`; + }); + cipher.notes = [...rawNotes, ...indexedNotes].join("\n"); } else if (value.length > 3) { cipher.type = CipherType.SecureNote; cipher.secureNote = new SecureNoteView(); @@ -95,6 +106,6 @@ export class MSecureCsvImporter extends BaseImporter implements Importer { // like "Password|8|myPassword", we want to keep the "myPassword" but also ensure that if // the value contains any "|" it works fine private splitValueRetainingLastPart(value: string) { - return value.split("|").slice(0, 2).concat(value.split("|").slice(2).join("|")).pop(); + return value && value.split("|").slice(0, 2).concat(value.split("|").slice(2).join("|")).pop(); } }