From 0e80b6252a03a1075b9e0b0fd981182c17725f94 Mon Sep 17 00:00:00 2001 From: "jakub.walusiak" Date: Wed, 8 Mar 2023 00:44:18 +0100 Subject: [PATCH 1/4] [image_picker_android] Adjust file extension in FileUtils when it does not match its mime type. --- .../plugins/imagepicker/FileUtils.java | 21 +++++++++++---- .../plugins/imagepicker/FileUtilTest.java | 26 ++++++++++++++++--- 2 files changed, 38 insertions(+), 9 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java index 449480c19d9c..ccc8abc15680 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java @@ -45,6 +45,9 @@ class FileUtils { *

Each file is placed in its own directory to avoid conflicts according to the following * scheme: {cacheDir}/{randomUuid}/{fileName} * + *

File extension is changed to match MIME type of the file, if known. Otherwise, the extension is left + * unchanged. + * *

If the original file name is unknown, a predefined "image_picker" filename is used and the * file extension is deduced from the mime type (with fallback to ".jpg" in case of failure). */ @@ -57,9 +60,14 @@ String getPathFromUri(final Context context, final Uri uri) { // just clear the picked files after the app startup. targetDirectory.deleteOnExit(); String fileName = getImageName(context, uri); + String extension = getImageExtension(context, uri); + if (fileName == null) { Log.w("FileUtils", "Cannot get file name for " + uri); - fileName = "image_picker" + getImageExtension(context, uri); + if(extension == null) extension = ".jpg"; + fileName = "image_picker" + extension; + } else if (extension != null){ + fileName = getBaseName(fileName) + extension; } File file = new File(targetDirectory, fileName); try (OutputStream outputStream = new FileOutputStream(file)) { @@ -74,7 +82,7 @@ String getPathFromUri(final Context context, final Uri uri) { } } - /** @return extension of image with dot, or default .jpg if it none. */ + /** @return extension of image with dot, or null if it's empty. */ private static String getImageExtension(Context context, Uri uriImage) { String extension; @@ -88,12 +96,11 @@ private static String getImageExtension(Context context, Uri uriImage) { Uri.fromFile(new File(uriImage.getPath())).toString()); } } catch (Exception e) { - extension = null; + return null; } if (extension == null || extension.isEmpty()) { - //default extension for matches the previous behavior of the plugin - extension = "jpg"; + return null; } return "." + extension; @@ -121,4 +128,8 @@ private static void copy(InputStream in, OutputStream out) throws IOException { } out.flush(); } + + private static String getBaseName(String fileName) { + return fileName.split("\\.")[0]; + } } diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java index 0ea0173fa954..5dac953158b1 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java @@ -15,6 +15,7 @@ import android.database.MatrixCursor; import android.net.Uri; import android.provider.MediaStore; +import android.webkit.MimeTypeMap; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.test.core.app.ApplicationProvider; @@ -29,6 +30,7 @@ import org.robolectric.Robolectric; import org.robolectric.RobolectricTestRunner; import org.robolectric.shadows.ShadowContentResolver; +import org.robolectric.shadows.ShadowMimeTypeMap; @RunWith(RobolectricTestRunner.class) public class FileUtilTest { @@ -42,6 +44,10 @@ public void before() { context = ApplicationProvider.getApplicationContext(); shadowContentResolver = shadowOf(context.getContentResolver()); fileUtils = new FileUtils(); + ShadowMimeTypeMap mimeTypeMap = shadowOf(MimeTypeMap.getSingleton()); + mimeTypeMap.addExtensionMimeTypMapping("jpg", "image/jpeg"); + mimeTypeMap.addExtensionMimeTypMapping("png", "image/png"); + mimeTypeMap.addExtensionMimeTypMapping("webp", "image/webp"); } @Test @@ -74,15 +80,27 @@ public void FileUtil_getImageExtension() throws IOException { @Test public void FileUtil_getImageName() throws IOException { - Uri uri = Uri.parse("content://dummy/dummy.png"); + Uri uri = MockContentProvider.PNG_URI; Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); shadowContentResolver.registerInputStream( uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); String path = fileUtils.getPathFromUri(context, uri); - assertTrue(path.endsWith("dummy.png")); + assertTrue(path.endsWith("png.png")); + } + + @Test + public void FileUtil_getImageName_mismatchedType() throws IOException { + Uri uri = MockContentProvider.WEBP_URI; + Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); + shadowContentResolver.registerInputStream( + uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); + String path = fileUtils.getPathFromUri(context, uri); + assertTrue(path.endsWith("webp.webp")); } private static class MockContentProvider extends ContentProvider { + public static final Uri PNG_URI = Uri.parse("content://dummy/png.png"); + public static final Uri WEBP_URI = Uri.parse("content://dummy/webp.png"); @Override public boolean onCreate() { @@ -98,14 +116,14 @@ public Cursor query( @Nullable String[] selectionArgs, @Nullable String sortOrder) { MatrixCursor cursor = new MatrixCursor(new String[] {MediaStore.MediaColumns.DISPLAY_NAME}); - cursor.addRow(new Object[] {"dummy.png"}); + cursor.addRow(new Object[] {uri.equals(PNG_URI) ? "png.png" : "webp.png"}); return cursor; } @Nullable @Override public String getType(@NonNull Uri uri) { - return "image/png"; + return uri.equals(PNG_URI) ? "image/png" : "image/webp"; } @Nullable From a76ca859fca3558f035df945f32a244fa520bdd4 Mon Sep 17 00:00:00 2001 From: "jakub.walusiak" Date: Wed, 8 Mar 2023 00:59:30 +0100 Subject: [PATCH 2/4] [image_picker_android] Fixed formatting --- .../java/io/flutter/plugins/imagepicker/FileUtils.java | 8 ++++---- .../java/io/flutter/plugins/imagepicker/FileUtilTest.java | 2 +- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java index ccc8abc15680..ec440f552a53 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java @@ -45,8 +45,8 @@ class FileUtils { *

Each file is placed in its own directory to avoid conflicts according to the following * scheme: {cacheDir}/{randomUuid}/{fileName} * - *

File extension is changed to match MIME type of the file, if known. Otherwise, the extension is left - * unchanged. + *

File extension is changed to match MIME type of the file, if known. Otherwise, the extension + * is left unchanged. * *

If the original file name is unknown, a predefined "image_picker" filename is used and the * file extension is deduced from the mime type (with fallback to ".jpg" in case of failure). @@ -64,9 +64,9 @@ String getPathFromUri(final Context context, final Uri uri) { if (fileName == null) { Log.w("FileUtils", "Cannot get file name for " + uri); - if(extension == null) extension = ".jpg"; + if (extension == null) extension = ".jpg"; fileName = "image_picker" + extension; - } else if (extension != null){ + } else if (extension != null) { fileName = getBaseName(fileName) + extension; } File file = new File(targetDirectory, fileName); diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java index 5dac953158b1..9f0549fcfc89 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java @@ -93,7 +93,7 @@ public void FileUtil_getImageName_mismatchedType() throws IOException { Uri uri = MockContentProvider.WEBP_URI; Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); shadowContentResolver.registerInputStream( - uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); + uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); String path = fileUtils.getPathFromUri(context, uri); assertTrue(path.endsWith("webp.webp")); } From 637d240ea9c733f5cc8ffdc4d9233b5207a69685 Mon Sep 17 00:00:00 2001 From: "jakub.walusiak" Date: Sat, 11 Mar 2023 19:08:20 +0100 Subject: [PATCH 3/4] [in_app_purchases_android_platform] Add test case for unknown mime type in FileUtils. --- .../image_picker_android/CHANGELOG.md | 3 ++- .../plugins/imagepicker/FileUtils.java | 3 ++- .../plugins/imagepicker/FileUtilTest.java | 25 ++++++++++++++----- .../image_picker_android/pubspec.yaml | 2 +- 4 files changed, 24 insertions(+), 9 deletions(-) diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md index 6c3b22c77027..9cc125eb03d7 100644 --- a/packages/image_picker/image_picker_android/CHANGELOG.md +++ b/packages/image_picker/image_picker_android/CHANGELOG.md @@ -1,6 +1,7 @@ -## NEXT +## 0.8.5+10 * Aligns Dart and Flutter SDK constraints. +* Fixes case when file extension returned from the OS does not match its real mime type. ## 0.8.5+9 diff --git a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java index ec440f552a53..4ca970a3344a 100644 --- a/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java +++ b/packages/image_picker/image_picker_android/android/src/main/java/io/flutter/plugins/imagepicker/FileUtils.java @@ -130,6 +130,7 @@ private static void copy(InputStream in, OutputStream out) throws IOException { } private static String getBaseName(String fileName) { - return fileName.split("\\.")[0]; + // Basename is everything before the last '.'. + return fileName.substring(0, fileName.lastIndexOf('.')); } } diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java index 9f0549fcfc89..dad6a3e34998 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java @@ -85,7 +85,7 @@ public void FileUtil_getImageName() throws IOException { shadowContentResolver.registerInputStream( uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); String path = fileUtils.getPathFromUri(context, uri); - assertTrue(path.endsWith("png.png")); + assertTrue(path.endsWith("a.b.png")); } @Test @@ -95,12 +95,23 @@ public void FileUtil_getImageName_mismatchedType() throws IOException { shadowContentResolver.registerInputStream( uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); String path = fileUtils.getPathFromUri(context, uri); - assertTrue(path.endsWith("webp.webp")); + assertTrue(path.endsWith("c.d.webp")); + } + + @Test + public void FileUtil_getImageName_unknownType() throws IOException { + Uri uri = MockContentProvider.UNKNOWN_URI; + Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); + shadowContentResolver.registerInputStream( + uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); + String path = fileUtils.getPathFromUri(context, uri); + assertTrue(path.endsWith("e.f.g")); } private static class MockContentProvider extends ContentProvider { - public static final Uri PNG_URI = Uri.parse("content://dummy/png.png"); - public static final Uri WEBP_URI = Uri.parse("content://dummy/webp.png"); + public static final Uri PNG_URI = Uri.parse("content://dummy/a.b.png"); + public static final Uri WEBP_URI = Uri.parse("content://dummy/c.d.png"); + public static final Uri UNKNOWN_URI = Uri.parse("content://dummy/e.f.g"); @Override public boolean onCreate() { @@ -116,14 +127,16 @@ public Cursor query( @Nullable String[] selectionArgs, @Nullable String sortOrder) { MatrixCursor cursor = new MatrixCursor(new String[] {MediaStore.MediaColumns.DISPLAY_NAME}); - cursor.addRow(new Object[] {uri.equals(PNG_URI) ? "png.png" : "webp.png"}); + cursor.addRow(new Object[] {uri.getLastPathSegment()}); return cursor; } @Nullable @Override public String getType(@NonNull Uri uri) { - return uri.equals(PNG_URI) ? "image/png" : "image/webp"; + if(uri.equals(PNG_URI)) return "image/png"; + if(uri.equals(WEBP_URI)) return "image/webp"; + return null; } @Nullable diff --git a/packages/image_picker/image_picker_android/pubspec.yaml b/packages/image_picker/image_picker_android/pubspec.yaml index 55bf2296a29f..30b95c3ff175 100755 --- a/packages/image_picker/image_picker_android/pubspec.yaml +++ b/packages/image_picker/image_picker_android/pubspec.yaml @@ -3,7 +3,7 @@ description: Android implementation of the image_picker plugin. repository: https://github.com/flutter/packages/tree/main/packages/image_picker/image_picker_android issue_tracker: https://github.com/flutter/flutter/issues?q=is%3Aissue+is%3Aopen+label%3A%22p%3A+image_picker%22 -version: 0.8.5+9 +version: 0.8.5+10 environment: sdk: ">=2.17.0 <3.0.0" From 31cb6b608d6fea4c6b46556b7d1e87119107962f Mon Sep 17 00:00:00 2001 From: "jakub.walusiak" Date: Mon, 13 Mar 2023 10:23:06 +0100 Subject: [PATCH 4/4] [in_app_purchases_android_platform] Fix formatting in FileUtilTest --- .../java/io/flutter/plugins/imagepicker/FileUtilTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java index dad6a3e34998..d125b78150cc 100644 --- a/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java +++ b/packages/image_picker/image_picker_android/android/src/test/java/io/flutter/plugins/imagepicker/FileUtilTest.java @@ -103,7 +103,7 @@ public void FileUtil_getImageName_unknownType() throws IOException { Uri uri = MockContentProvider.UNKNOWN_URI; Robolectric.buildContentProvider(MockContentProvider.class).create("dummy"); shadowContentResolver.registerInputStream( - uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); + uri, new ByteArrayInputStream("imageStream".getBytes(UTF_8))); String path = fileUtils.getPathFromUri(context, uri); assertTrue(path.endsWith("e.f.g")); } @@ -134,8 +134,8 @@ public Cursor query( @Nullable @Override public String getType(@NonNull Uri uri) { - if(uri.equals(PNG_URI)) return "image/png"; - if(uri.equals(WEBP_URI)) return "image/webp"; + if (uri.equals(PNG_URI)) return "image/png"; + if (uri.equals(WEBP_URI)) return "image/webp"; return null; }