From 4dc6d5d8e53ae820749db3d6e364259d7ce77101 Mon Sep 17 00:00:00 2001 From: ibrahim-iqbal Date: Wed, 2 Sep 2026 18:56:53 +0530 Subject: [PATCH] fix: truncate long image filenames when saving to prevent ENAMETOOLONG 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 #1315. --- .../android/AndroidImageDownloader.kt | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/me/ash/reader/infrastructure/android/AndroidImageDownloader.kt b/app/src/main/java/me/ash/reader/infrastructure/android/AndroidImageDownloader.kt index 6e51ddfb6..3826e09ce 100644 --- a/app/src/main/java/me/ash/reader/infrastructure/android/AndroidImageDownloader.kt +++ b/app/src/main/java/me/ash/reader/infrastructure/android/AndroidImageDownloader.kt @@ -36,8 +36,10 @@ class AndroidImageDownloader @Inject constructor( Request.Builder().url(imageUrl).build().runCatching { okHttpClient.newCall(this).execute().run { - val fileName = URLUtil.guessFileName( - imageUrl, header("Content-Disposition"), body.contentType()?.toString() + val fileName = sanitizeFileName( + URLUtil.guessFileName( + imageUrl, header("Content-Disposition"), body.contentType()?.toString() + ) ) val relativePath = @@ -90,6 +92,15 @@ class AndroidImageDownloader @Inject constructor( } + private fun sanitizeFileName(fileName: String): String { + if (fileName.length <= MAX_FILENAME_LENGTH) return fileName + val dot = fileName.lastIndexOf('.') + val ext = if (dot > 0 && fileName.length - dot in 2..10) fileName.substring(dot) else "" + val base = if (ext.isEmpty()) fileName else fileName.substring(0, dot) + val maxBaseLength = (MAX_FILENAME_LENGTH - ext.length).coerceAtLeast(1) + return base.take(maxBaseLength) + ext + } + @DeprecatedSinceApi(29) private fun Response.saveImageForAndroidP( fileName: String, @@ -109,4 +120,8 @@ class AndroidImageDownloader @Inject constructor( return contentUri } + + private companion object { + const val MAX_FILENAME_LENGTH = 200 + } } \ No newline at end of file