diff --git a/packages/loot-core/src/mocks/files/html-vals.qfx b/packages/loot-core/src/mocks/files/html-vals.qfx new file mode 100644 index 0000000000000000000000000000000000000000..0e3b2a5a2e01d214f8927e38349a5665b702af5c --- /dev/null +++ b/packages/loot-core/src/mocks/files/html-vals.qfx @@ -0,0 +1,17 @@ +OFXHEADER:100 +DATA:OFXSGML +VERSION:102 +SECURITY:NONE +ENCODING:USASCII +CHARSET:1252 +COMPRESSION:NONE +OLDFILEUID:NONE +NEWFILEUID:NONE + +<OFX><SIGNONMSGSRSV1><SONRS><STATUS><CODE>0<SEVERITY>INFO<MESSAGE>OK</STATUS><DTSERVER>20231106023518<LANGUAGE>ENG<DTPROFUP>20231106023518<DTACCTUP>20231106023518<INTU.BID>00005</SONRS></SIGNONMSGSRSV1> +<BANKMSGSRSV1><STMTTRNRS><TRNUID>20231106023518<STATUS><CODE>0<SEVERITY>INFO<MESSAGE>OK</STATUS> +<STMTRS><CURDEF>CAD<BANKACCTFROM><BANKID>000000000<ACCTID>00000 00-00000<ACCTTYPE>CHECKING</BANKACCTFROM> +<BANKTRANLIST><DTSTART>20190731120000<DTEND>20231031120000 +<STMTTRN><TRNTYPE>DEBIT<DTPOSTED>20230509120000.000[-5:EST]<TRNAMT>-1.00<FITID>23129130032333396279270000<NAME>000000000000000<MEMO>PREAUTHORIZED DEBIT;B.C. HYDRO & POWER AUTHORITY;Electronic Funds Transfer</STMTTRN> +<STMTTRN><TRNTYPE>DEBIT<DTPOSTED>20230301120000.000[-5:EST]<TRNAMT>-1.00<FITID>23060061032549554596530000<NAME>TPAY &lt;DEFTPYMT&gt;<MEMO>PREAUTHORIZED DEBIT;LUXMORE REALTY PPTY MGMT;Electronic Funds Transfer</STMTTRN> +</BANKTRANLIST><LEDGERBAL><BALAMT>1111.11<DTASOF>20231106023518</LEDGERBAL><AVAILBAL><BALAMT>1111.11<DTASOF>20231106023518</AVAILBAL></STMTRS></STMTTRNRS></BANKMSGSRSV1></OFX> diff --git a/packages/loot-core/src/server/accounts/__snapshots__/parse-file.test.ts.snap b/packages/loot-core/src/server/accounts/__snapshots__/parse-file.test.ts.snap index 72b569a02516bc3b90f3ec301dd0b6b76b5dcad1..5e325d19b8e547c564e236d804b4534754d898f9 100644 --- a/packages/loot-core/src/server/accounts/__snapshots__/parse-file.test.ts.snap +++ b/packages/loot-core/src/server/accounts/__snapshots__/parse-file.test.ts.snap @@ -1,5 +1,60 @@ // Jest Snapshot v1, https://goo.gl/fbAQLP +exports[`File import handles html escaped plaintext 1`] = ` +Array [ + Object { + "acct": "one", + "amount": -100, + "category": null, + "cleared": 1, + "date": 20230509, + "description": "id2", + "error": null, + "financial_id": "23129130032333396279270000", + "id": "id4", + "imported_description": "000000000000000", + "isChild": 0, + "isParent": 0, + "location": null, + "notes": "PREAUTHORIZED DEBIT;B.C. HYDRO & POWER AUTHORITY;Electronic Funds Transfer", + "parent_id": null, + "pending": 0, + "reconciled": 0, + "schedule": null, + "sort_order": 123456789, + "starting_balance_flag": 0, + "tombstone": 0, + "transferred_id": null, + "type": null, + }, + Object { + "acct": "one", + "amount": -100, + "category": null, + "cleared": 1, + "date": 20230301, + "description": "id3", + "error": null, + "financial_id": "23060061032549554596530000", + "id": "id5", + "imported_description": "TPAY <DEFTPYMT>", + "isChild": 0, + "isParent": 0, + "location": null, + "notes": "PREAUTHORIZED DEBIT;LUXMORE REALTY PPTY MGMT;Electronic Funds Transfer", + "parent_id": null, + "pending": 0, + "reconciled": 0, + "schedule": null, + "sort_order": 123456789, + "starting_balance_flag": 0, + "tombstone": 0, + "transferred_id": null, + "type": null, + }, +] +`; + exports[`File import handles non-ASCII characters 1`] = ` Array [ Object { diff --git a/packages/loot-core/src/server/accounts/ofx2json.ts b/packages/loot-core/src/server/accounts/ofx2json.ts index 91e1f50b4e1682722d5a45f43265c07c8a2d7e83..b5ad38faa88823a9eccd6e386b05b873a2a9e04d 100644 --- a/packages/loot-core/src/server/accounts/ofx2json.ts +++ b/packages/loot-core/src/server/accounts/ofx2json.ts @@ -29,6 +29,16 @@ function sgml2Xml(sgml) { .replace(/<\/<added>(\w+?)>(<\/\1>)?/g, '</$1>'); // Remove duplicate end-tags } +function html2Plain(value) { + return value + ?.replace(/&/g, '&') // ampersands + .replace(/&/g, '&') // other ampersands + .replace(/</g, '<') // lessthan + .replace(/>/g, '>') // greaterthan + .replace(/'/g, "'") // eslint-disable-line rulesdir/typography + .replace(/"/g, '"'); // eslint-disable-line rulesdir/typography +} + async function parseXml(content) { return await parseStringPromise(content, { explicitArray: false }); } @@ -106,8 +116,8 @@ function mapOfxTransaction(stmtTrn): OFXTransaction { type: stmtTrn['TRNTYPE'], fitId: stmtTrn['FITID'], date: dayFromDate(transactionDate), - name: stmtTrn['NAME'], - memo: stmtTrn['MEMO'], + name: html2Plain(stmtTrn['NAME']), + memo: html2Plain(stmtTrn['MEMO']), }; } diff --git a/packages/loot-core/src/server/accounts/parse-file.test.ts b/packages/loot-core/src/server/accounts/parse-file.test.ts index 95bcf6fff059f56c7831e658d44c30a5b869375b..edd472d2ab0eb2c022533a19f7c25480b4519a54 100644 --- a/packages/loot-core/src/server/accounts/parse-file.test.ts +++ b/packages/loot-core/src/server/accounts/parse-file.test.ts @@ -142,4 +142,17 @@ describe('File import', () => { expect(errors.length).toBe(0); expect(await getTransactions('one')).toMatchSnapshot(); }); + + test('handles html escaped plaintext', async () => { + prefs.loadPrefs(); + await db.insertAccount({ id: 'one', name: 'one' }); + + const { errors } = await importFileWithRealTime( + 'one', + __dirname + '/../../mocks/files/html-vals.qfx', + 'yyyy-MM-dd', + ); + expect(errors.length).toBe(0); + expect(await getTransactions('one')).toMatchSnapshot(); + }); }); diff --git a/upcoming-release-notes/2364.md b/upcoming-release-notes/2364.md new file mode 100644 index 0000000000000000000000000000000000000000..92b4ecb8045c362227e896ee734632340fcba1fb --- /dev/null +++ b/upcoming-release-notes/2364.md @@ -0,0 +1,6 @@ +--- +category: Bugfix +authors: [twk3] +--- + +Convert html special characters in ofx imports to plaintext.