Skip to content

[16.0][FIX] account_statement_import_ofx: handle mislabeled OFX charsets - #968

Open
renatonlima wants to merge 3 commits into
OCA:16.0from
akretion:16.0-account_statement_import_ofx-decode
Open

[16.0][FIX] account_statement_import_ofx: handle mislabeled OFX charsets#968
renatonlima wants to merge 3 commits into
OCA:16.0from
akretion:16.0-account_statement_import_ofx-decode

Conversation

@renatonlima

@renatonlima renatonlima commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary

Fix OFX imports failing with a misleading "file format is not supported" error when the file's declared charset does not match its real encoding.

Problem

ofxparse reads the charset declared in the OFX SGML header and decodes the body with a strict cp1252 codec. Several banks emit OFX files whose header declares CHARSET:1252 while the body is actually UTF-8. Decoding then fails on bytes that are undefined in cp1252 (e.g. 0x81):

import io
from ofxparse import OfxParser

with open("/path/to/Extrato_3246_020550_18-06-2026.ofx", "rb") as file:
    data = file.read()
    ofx = OfxParser.parse(io.BytesIO(data))
​

output

Traceback (most recent call last):
  File "<stdin>", line 4, in <module>
  File "/Users/renato/DEV/odoo16/odoo/lib/python3.10/site-packages/ofxparse/ofxparse.py", line 396, in parse
    ofx_file = OfxPreprocessedFile(file_handle)
  File "/Users/renato/DEV/odoo16/odoo/lib/python3.10/site-packages/ofxparse/ofxparse.py", line 160, in __init__
    ofx_string = self.fh.read()
  File "/Users/renato/.pyenv/versions/3.10.13/lib/python3.10/codecs.py", line 504, in read
    newchars, decodedbytes = self.decode(data, self.errors)
  File "/Users/renato/.pyenv/versions/3.10.13/lib/python3.10/encodings/cp1252.py", line 15, in decode
    return codecs.charmap_decode(input,errors,decoding_table)
UnicodeDecodeError: 'charmap' codec can't decode byte 0x81 in position 906: character maps to <undefined>

_check_ofx catches this exception and returns False, so the user only sees the generic dialog:

image

In the affected file the accented name ROMÁRIO is stored in UTF-8 (Á = 0xC3 0x81), so the lone 0x81 byte breaks the cp1252 decoder even though the header claims CHARSET:1252.

Solution

Decode the raw OFX bytes ourselves before handing them to ofxparse:

  1. UTF-8 first — it is self-validating, so it rarely matches non-UTF-8 data by accident.

  2. Then the charset declared in the header (cp1252 / iso-8859-1).

  3. Finally latin-1, which maps every byte value and never raises. The decoded text is re-encoded to UTF-8 and the SGML ENCODING header is normalized to UTF-8, so ofxparse decodes the body consistently regardless of the original (possibly wrong) declaration. Passing bytes rather than a text stream also prevents ofxparse from re-running its own cp1252 detection on the content.

This works across locales — Western Europe, Latin America and Asia — without corrupting characters outside latin-1 (the previous behaviour silently turned a UTF-8 or CJK text into mojibake).

Tests

Added regression tests covering four encoding scenarios, each asserting that the special characters survive the round-trip:

Test Scenario
test_ofx_encoding_latin UTF-8 body mislabeled as CHARSET:1252 (the reported bug; contains byte 0x81)
test_ofx_encoding_cp1252 Genuine cp1252 body with the Euro sign and Western accents
test_ofx_encoding_utf8_euro UTF-8 body with characters outside latin-1 (Euro sign, em dash)
test_ofx_encoding_asian UTF-8 body with CJK characters

@OCA-git-bot

Copy link
Copy Markdown
Contributor

Hi @alexis-via,
some modules you are maintaining are being modified, check this out!

@OCA-git-bot OCA-git-bot added series:16.0 mod:account_statement_import_ofx Module account_statement_import_ofx labels Jun 19, 2026
@renatonlima
renatonlima force-pushed the 16.0-account_statement_import_ofx-decode branch from acbaa6b to c7b5262 Compare June 19, 2026 01:24
@renatonlima
renatonlima marked this pull request as draft June 19, 2026 01:28
@renatonlima

Copy link
Copy Markdown
Member Author

There's an error in another module; I believe the cause is the version of the Python library ofxtools. It's likely the newest version of this library isn't compatible. I've changed the status of this PR to draft while I investigate the error in CI.

@OCA-git-bot OCA-git-bot added the mod:account_statement_import_online_ofx Module account_statement_import_online_ofx label Jun 19, 2026
ofxparse trusts the charset declared in the OFX SGML header (e.g.
CHARSET:1252) and decodes the body with a strict cp1252 codec. Some
banks emit files whose header declares one charset while the body is
actually encoded differently (UTF-8 is common). Decoding then raises
UnicodeDecodeError on bytes undefined in cp1252 (e.g. 0x81), and since
_check_ofx swallows the exception the import fails with the misleading
"This bank statement file format is not supported" message.

Decode the raw bytes ourselves instead: UTF-8 first (it is
self-validating, so it rarely matches non-UTF-8 data by accident), then
the charset declared in the header, and finally latin-1, which maps
every byte value and never raises. The decoded text is re-encoded to
UTF-8 and the SGML ENCODING header is normalized to UTF-8, so ofxparse
decodes the body consistently regardless of the original (possibly
wrong) declaration. Passing bytes (not a text stream) also avoids
ofxparse re-running its own cp1252 detection on the data.

This fixes imports for files produced by Brazilian banks and works
across locales (Western Europe, Latin America, Asia) without corrupting
characters outside latin-1 (e.g. the Euro sign or CJK text).

Add tests covering UTF-8 mislabeled as cp1252, genuine cp1252, UTF-8
with characters outside latin-1 (Euro sign, em dash) and CJK content.
…sts/test_import_bank_statement.py:40:58: E231 missing whitespace after ':'
@renatonlima
renatonlima force-pushed the 16.0-account_statement_import_ofx-decode branch from 52631d5 to 6a90f7b Compare June 19, 2026 18:34
@OCA-git-bot OCA-git-bot added the mod:account_statement_import_camt Module account_statement_import_camt label Jun 19, 2026
@renatonlima
renatonlima marked this pull request as ready for review June 19, 2026 21:35
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

mod:account_statement_import_camt Module account_statement_import_camt mod:account_statement_import_ofx Module account_statement_import_ofx mod:account_statement_import_online_ofx Module account_statement_import_online_ofx series:16.0

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants