[image_picker_android] Adjust file extension in FileUtils when it does not match its mime type (#3409)
[image_picker_android] Adjust file extension in FileUtils when it does not match its mime type
diff --git a/packages/image_picker/image_picker_android/CHANGELOG.md b/packages/image_picker/image_picker_android/CHANGELOG.md
index 9591cf3..358e20c 100644
--- a/packages/image_picker/image_picker_android/CHANGELOG.md
+++ b/packages/image_picker/image_picker_android/CHANGELOG.md
@@ -1,3 +1,7 @@
+## 0.8.6+5
+
+* Fixes case when file extension returned from the OS does not match its real mime type.
+
## 0.8.6+4
* Bumps androidx.exifinterface:exifinterface from 1.3.3 to 1.3.6.
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 449480c..4ca970a 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 @@
* <p>Each file is placed in its own directory to avoid conflicts according to the following
* scheme: {cacheDir}/{randomUuid}/{fileName}
*
+ * <p>File extension is changed to match MIME type of the file, if known. Otherwise, the extension
+ * is left unchanged.
+ *
* <p>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 @@
// 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 @@
}
}
- /** @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 @@
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,9 @@
}
out.flush();
}
+
+ private static String getBaseName(String fileName) {
+ // 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 0ea0173..d125b78 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 @@
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,38 @@
@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("a.b.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("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/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() {
@@ -98,14 +127,16 @@
@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.getLastPathSegment()});
return cursor;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
- return "image/png";
+ 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 5c9be4f..41c8277 100755
--- a/packages/image_picker/image_picker_android/pubspec.yaml
+++ b/packages/image_picker/image_picker_android/pubspec.yaml
@@ -3,7 +3,7 @@
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.6+4
+version: 0.8.6+5
environment:
sdk: ">=2.17.0 <3.0.0"