Repository navigation
fix: truncate long image filenames when saving to prevent ENAMETOOLONG - #1324
Open
ibrahim-iqbal wants to merge 2 commits into
Open
ibrahim-iqbal wants to merge 2 commits into
ibrahim-iqbal wants to merge 2 commits into
Conversation
URLUtil.guessFileName() can produce a filename longer than the 255-byte
limit ext4/f2fs enforces on Android, so saving an image with a long
title or URL-derived name crashes with
java.io.FileNotFoundException: open failed: ENAMETOOLONG
on ContentResolver.openFileDescriptor. Sanitize the name to at most
200 chars while preserving the file extension so the resulting file
still opens as an image.
Fixes ReadYouApp#1315.
4 tasks done
conradlyn
added a commit
to conradlyn/ReadYou
that referenced
this pull request
Sep 24, 2026
Cherry-picked from upstream open PRs. All of them touch files this fork has never modified, so they apply cleanly and will keep merging cleanly if upstream lands them later. ReadYouApp#1322 browser-like User-Agent to bypass WAF bot blocking; BestIconFinder is now failure-tolerant, tries https for bare domains, and caps icon candidates at 4; searchFeed reports HTTP status in its errors ReadYouApp#1323 proper charset detection when parsing full content (HTTP header -> BOM -> <meta> -> UTF-8), replacing the old peek+Jsoup ReadYouApp#1324 truncate over-long image filenames to avoid ENAMETOOLONG ReadYouApp#1320 reset the intent package when opening a link fails ReadYouApp#1317 keep the trailing slash when saving a greader/FreshRSS server URL Deviation on purpose: ReadYouApp#1322 also shipped two tests that perform real network requests to phoronix.com. Those are dropped -- a unit test that depends on a third party's availability and anti-bot policy would make CI flaky. The other tests from both PRs (4 charset cases, 3 UA cases, 1 local RSS parse) are kept.
ENAMETOOLONG is a limit on encoded bytes (NAME_MAX = 255 on ext4/f2fs), not on Kotlin/UTF-16 character count. A filename guessed from feed content can be in any script, so the original .take(200) could still leave a filename well over 255 bytes for CJK, Cyrillic, emoji, etc. — the exact crash this PR set out to prevent, just for non-ASCII names. Walk the string one whole code point at a time (never split a surrogate pair) and stop once the UTF-8 byte budget is used up. Added AndroidImageDownloaderTest covering ASCII, multi-byte, emoji and no-extension cases; there was no existing test for this class.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #1315.
`URLUtil.guessFileName()` derives the save-target name from the URL when the response doesn't carry an unambiguous `Content-Disposition`. For image URLs with long titles / hashed paths, that name can exceed the 255-byte filename limit ext4/f2fs enforces on Android. `ContentResolver.openFileDescriptor` then blows up with:
```
java.io.FileNotFoundException: open failed: ENAMETOOLONG (File name too long)
at me.ash.reader.infrastructure.android.AndroidImageDownloader.downloadImage2.invokeSuspend(AndroidImageDownloader.kt:181)
```
The tap-to-save flow is a 100% crash on any image whose guessed name goes past the limit — reproducer in #1315.
Added a small `sanitizeFileName` helper that caps the name at 200 chars (headroom under the 255-byte limit for typical single-byte scripts) while preserving the file extension so the saved file still opens as an image. Applied at the single call site right after `URLUtil.guessFileName` so both the Android Q+ MediaStore path and the pre-Q direct-file path get the sanitized name.
Test plan
Would need a device to verify end-to-end, which I don't have set up for this repo. Logically covered:
Happy to add a unit test if you'd like — didn't see an existing test file for AndroidImageDownloader in the app module, so didn't want to seed a new one without your steer.