Skip to content

fix(rss): properly detect charset and decode French and non-UTF8 full content - #1323

Open
heberjeur wants to merge 1 commit into
ReadYouApp:mainfrom
heberjeur:fix/issue-1094-french-encoding
Open

heberjeur wants to merge 1 commit into
ReadYouApp:mainfrom
heberjeur:fix/issue-1094-french-encoding

Conversation

@heberjeur

Copy link
Copy Markdown

What & Why

Fixes #1094

When fetching and parsing full article content (e.g. from developpez.com or other sites declaring ISO-8859-1 / Windows-1252):

  • French characters with accents (é, è, à, ç, ê, î, ô, ù, etc.) were displayed as corrupted replacement symbols (`` or ?).
  • Example: "Perplexity a lanc son nouvel abonnement Max, factur 200 dollars..."

Root Cause

  1. HTTP Content-Type Header with Comma: Some legacy/PHP servers return Content-Type: text/html, charset=iso-8859-1 with a comma rather than a semicolon. OkHttp's MediaType.parse() fails on this format and returns null for responseBody.contentType()?.charset().
  2. Missing HTML5 <meta charset="..."> Detection: Only meta[http-equiv=content-type] was inspected, completely ignoring HTML5 <meta charset="iso-8859-1">.
  3. Lossy Stream Peeking: it.peek().readString(Charsets.UTF_8) converted raw ISO-8859-1 bytes (such as 0xE9 for é) directly into \uFFFD, causing irreversible character loss if meta charset wasn't found in time.

Fixes Applied

  • Robust Charset Detection (detectHtmlCharset) in RssHelper.kt:
    1. Parses charset from HTTP Content-Type header (handling both , and ; delimiters as well as quotes).
    2. Inspects HTML <head> for <meta charset="..."> (HTML5) and <meta http-equiv="content-type" content="...">.
    3. Checks standard BOM markers (UTF-8, UTF-16).
    4. Defaults safely to UTF-8.
  • Direct Byte Decoding in parseFullContent(): Reads the raw response bytes and decodes them directly with the detected charset, preventing lossy intermediate UTF-8 conversions.
  • Header Normalization in toHttpContentType(): Normalizes commas to semicolons when reading feed XML content types.
  • Unit Tests (RssHelperTest.kt): Added comprehensive tests verifying charset detection from comma-separated headers, HTML5 meta tags, and accurate decoding of French accented text.

Tested

  • Ran unit tests: ./gradlew testGithubReleaseUnitTest -> BUILD SUCCESSFUL.
  • Built release APK: ./gradlew assembleGithubRelease -> BUILD SUCCESSFUL.

@ibrahim-iqbal ibrahim-iqbal left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Solid rewrite. detectHtmlCharset runs through a proper priority order (HTTP header → BOM → HTML <meta> → UTF-8 default), which is exactly what feeds and full-content pages need, and the change from source().use { peek { … } } to a straight bytes() + String(bytes, charset) gets rid of the double-decode footgun that was in the old path.

A few small things worth thinking about:

  • Memory footprint. Switching from source() streaming to responseBody.bytes() loads the entire response into memory. For most feeds and article pages this is fine, but ReadYou's full-content extractor can hit pages in the multi-MB range on image-heavy sites. Not something to change here, but worth keeping an eye on if you ever see OOMs on low-memory devices.
  • Malformed input. String(bytes, charset) throws on illegal byte sequences for some charsets (e.g. ISO-2022-JP with a broken escape). Wrapping the final decode with CharsetDecoder.onMalformedInput(REPLACE).decode(...) would swap those failures for a ? glyph instead of a crash. Optional.
  • it.replace(',', ';') in toHttpContentType is a nice fix for servers that misspell the header separator. Might be worth a one-line comment saying so, since a future reader is going to wonder why we're rewriting commas in a Content-Type string.
  • Fixture tests. The added RssHelperTest cases cover the header + BOM + meta paths — nice. If you feel like adding one more, a test with Content-Type: text/html, charset=windows-1252 (comma variant) would lock in the replace(',', ';') behaviour so a future refactor doesn't quietly regress it.

Overall LGTM on the charset detection side; happy to defer on the memory trade-off since it clearly favours correctness for the current use case.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Parse full content failed to handle French characters (e.g. developpez.com)

2 participants