From d0c11cd3afa8b41fee0fc2aaf5bd3bcd776bef0a Mon Sep 17 00:00:00 2001
From: DJ Mountney <david@twkie.net>
Date: Sat, 17 Feb 2024 10:49:02 -0800
Subject: [PATCH] Converts html special characters in ofx values to plaintext
 (#2364)

* Converts html special characters in ofx values to plaintext

- Apply during ofx/qfx import
---
 .../loot-core/src/mocks/files/html-vals.qfx   | 17 ++++++
 .../__snapshots__/parse-file.test.ts.snap     | 55 +++++++++++++++++++
 .../loot-core/src/server/accounts/ofx2json.ts | 14 ++++-
 .../src/server/accounts/parse-file.test.ts    | 13 +++++
 upcoming-release-notes/2364.md                |  6 ++
 5 files changed, 103 insertions(+), 2 deletions(-)
 create mode 100644 packages/loot-core/src/mocks/files/html-vals.qfx
 create mode 100644 upcoming-release-notes/2364.md

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 000000000..0e3b2a5a2
--- /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 &amp; POWER AUTHORITY;Electronic Funds Transfer</STMTTRN>
+<STMTTRN><TRNTYPE>DEBIT<DTPOSTED>20230301120000.000[-5:EST]<TRNAMT>-1.00<FITID>23060061032549554596530000<NAME>TPAY &amp;lt;DEFTPYMT&amp;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 72b569a02..5e325d19b 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 91e1f50b4..b5ad38faa 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(/&amp;/g, '&') // ampersands
+    .replace(/&#038;/g, '&') // other ampersands
+    .replace(/&lt;/g, '<') // lessthan
+    .replace(/&gt;/g, '>') // greaterthan
+    .replace(/&#39;/g, "'") // eslint-disable-line rulesdir/typography
+    .replace(/&quot;/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 95bcf6fff..edd472d2a 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 000000000..92b4ecb80
--- /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.
-- 
GitLab