From 212b854ea58045c4d365d20fe162caf4a07793d5 Mon Sep 17 00:00:00 2001
From: Jed Fox <git@jedfox.com>
Date: Fri, 3 Feb 2023 14:12:30 -0500
Subject: [PATCH] Allow libofx to handle decoding imported files (#591)

* Allow libofx to handle decoding imported files

* Add releve.qfx as a test file

* Remove irrelevant redacted transactions from test file

* Fix console overload from long console.warn stacks
---
 packages/loot-core/src/mocks/files/8859-1.qfx | 63 +++++++++++++++++++
 .../__snapshots__/parse-file.test.js.snap     | 29 +++++++++
 .../src/server/accounts/parse-file.js         |  2 +-
 .../src/server/accounts/parse-file.test.js    | 24 +++++++
 packages/node-libofx/ffi.js                   |  2 +-
 5 files changed, 118 insertions(+), 2 deletions(-)
 create mode 100644 packages/loot-core/src/mocks/files/8859-1.qfx

diff --git a/packages/loot-core/src/mocks/files/8859-1.qfx b/packages/loot-core/src/mocks/files/8859-1.qfx
new file mode 100644
index 000000000..8e678c9be
--- /dev/null
+++ b/packages/loot-core/src/mocks/files/8859-1.qfx
@@ -0,0 +1,63 @@
+OFXHEADER:100
+DATA:OFXSGML
+VERSION:102
+SECURITY:TYPE1
+ENCODING:USASCII
+CHARSET:8859-1
+COMPRESSION:NONE
+OLDFILEUID:NONE
+NEWFILEUID:NONE
+<OFX>
+<SIGNONMSGSRSV1>
+<SONRS>
+<STATUS>
+<CODE>0
+<SEVERITY>INFO
+<MESSAGE>OK
+</STATUS>
+<DTSERVER>20221020231556
+<USERKEY>3AAAAAAAAAAAAAA
+<INTU.BID>00999
+<LANGUAGE>FRA
+</SONRS>
+</SIGNONMSGSRSV1>
+<BANKMSGSRSV1>
+<STMTTRNRS>
+<TRNUID>AAAA-2022102023155614357
+<STATUS>
+<CODE>0
+<SEVERITY>INFO
+<MESSAGE>OK
+</STATUS>
+<STMTRS>
+<CURDEF>CAD
+<BANKACCTFROM>
+<BANKID>700012345
+<BRANCHID>0055666
+<ACCTID>999-12345-0055666-EOP
+<ACCTTYPE>CHECKING
+</BANKACCTFROM>
+<BANKTRANLIST>
+<DTSTART>20221019120000
+<DTEND>20221019120000
+<STMTTRN>
+<TRNTYPE>DEBIT
+<DTPOSTED>20221019120000
+<TRNAMT>-20.00
+<FITID>wSoKuCS77
+<NAME>Paiement facture/Carte prépayée
+<MEMO>PWW
+</STMTTRN>
+</BANKTRANLIST>
+<LEDGERBAL>
+<BALAMT>9999.99
+<DTASOF>20221020231556
+</LEDGERBAL>
+<AVAILBAL>
+<BALAMT>9999.99
+<DTASOF>20221020231556
+</AVAILBAL>
+</STMTRS>
+</STMTTRNRS>
+</BANKMSGSRSV1>
+</OFX>
diff --git a/packages/loot-core/src/server/accounts/__snapshots__/parse-file.test.js.snap b/packages/loot-core/src/server/accounts/__snapshots__/parse-file.test.js.snap
index c1300f425..5ada01f60 100644
--- a/packages/loot-core/src/server/accounts/__snapshots__/parse-file.test.js.snap
+++ b/packages/loot-core/src/server/accounts/__snapshots__/parse-file.test.js.snap
@@ -1,5 +1,34 @@
 // Jest Snapshot v1, https://goo.gl/fbAQLP
 
+exports[`File import handles non-ASCII characters 1`] = `
+Array [
+  Object {
+    "acct": "one",
+    "amount": -2000,
+    "category": null,
+    "cleared": 1,
+    "date": 20221019,
+    "description": "id2",
+    "error": null,
+    "financial_id": "wSoKuCS77",
+    "id": "id3",
+    "imported_description": "Paiement facture/Carte prépayée",
+    "isChild": 0,
+    "isParent": 0,
+    "location": null,
+    "notes": "PWW",
+    "parent_id": null,
+    "pending": 0,
+    "schedule": null,
+    "sort_order": 123456789,
+    "starting_balance_flag": 0,
+    "tombstone": 0,
+    "transferred_id": null,
+    "type": null,
+  },
+]
+`;
+
 exports[`File import ofx import works 1`] = `
 Array [
   Object {
diff --git a/packages/loot-core/src/server/accounts/parse-file.js b/packages/loot-core/src/server/accounts/parse-file.js
index 06b6f793b..8d26e09d4 100644
--- a/packages/loot-core/src/server/accounts/parse-file.js
+++ b/packages/loot-core/src/server/accounts/parse-file.js
@@ -91,7 +91,7 @@ async function parseOFX(filepath) {
   await initModule();
 
   let errors = [];
-  let contents = await fs.readFile(filepath);
+  let contents = await fs.readFile(filepath, 'binary');
 
   let data;
   try {
diff --git a/packages/loot-core/src/server/accounts/parse-file.test.js b/packages/loot-core/src/server/accounts/parse-file.test.js
index 785d2da4a..ad3593f51 100644
--- a/packages/loot-core/src/server/accounts/parse-file.test.js
+++ b/packages/loot-core/src/server/accounts/parse-file.test.js
@@ -9,6 +9,17 @@ import { reconcileTransactions } from './sync';
 
 beforeEach(global.emptyDatabase());
 
+// libofx spits out errors that contain the entire
+// source code of the file in the stack which makes
+// it hard to test.
+let old = console.warn;
+beforeAll(() => {
+  console.warn = () => {};
+});
+afterAll(() => {
+  console.warn = old;
+});
+
 async function getTransactions(accountId) {
   return db.runQuery(
     'SELECT * FROM transactions WHERE acct = ?',
@@ -99,4 +110,17 @@ describe('File import', () => {
     expect(res.errors.length).toBe(1);
     expect(res.errors[0].message).toBe('Invalid file type');
   }, 45000);
+
+  test('handles non-ASCII characters', async () => {
+    prefs.loadPrefs();
+    await db.insertAccount({ id: 'one', name: 'one' });
+
+    let { errors } = await importFileWithRealTime(
+      'one',
+      __dirname + '/../../mocks/files/8859-1.qfx',
+      'yyyy-MM-dd'
+    );
+    expect(errors.length).toBe(0);
+    expect(await getTransactions('one')).toMatchSnapshot();
+  });
 });
diff --git a/packages/node-libofx/ffi.js b/packages/node-libofx/ffi.js
index b4a49ae98..f3e9d7dfc 100644
--- a/packages/node-libofx/ffi.js
+++ b/packages/node-libofx/ffi.js
@@ -3,7 +3,7 @@ function create(libofx) {
     init: libofx.cwrap('init', null, ['number']),
     debug: libofx.cwrap('debug', null, []),
     get_new_context: libofx.cwrap('get_new_context', 'number', []),
-    parse_data: libofx.cwrap('parse_data', null, ['number', 'string']),
+    parse_data: libofx.cwrap('parse_data', null, ['number', 'array']),
 
     ofx_set_transaction_cb: libofx.cwrap('ofx_set_transaction_cb', null, [
       'number',
-- 
GitLab