MM-36256 avoid path traversal for Android image picker (#5432) (#5437)

(cherry picked from commit 0a4dafa127)

Co-authored-by: Elias Nahum <nahumhbl@gmail.com>
This commit is contained in:
Mattermost Build
2021-06-08 15:24:33 +02:00
committed by GitHub
parent afa18fb5d9
commit 840fda2051
2 changed files with 504 additions and 171 deletions

View File

@@ -3,11 +3,9 @@ package com.mattermost.share;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
import android.provider.OpenableColumns;
import android.content.ContentUris;
import android.content.ContentResolver;
import android.os.Environment;
import android.webkit.MimeTypeMap;
@@ -15,18 +13,18 @@ import android.util.Log;
import android.text.TextUtils;
import android.os.ParcelFileDescriptor;
import java.io.*;
import java.nio.channels.FileChannel;
import java.util.Objects;
// Class based on the steveevers DocumentHelper https://gist.github.com/steveevers/a5af24c226f44bb8fdc3
public class RealPathUtil {
public static String getRealPathFromURI(final Context context, final Uri uri) {
final boolean isKitKatOrNewer = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
// DocumentProvider
if (isKitKatOrNewer && DocumentsContract.isDocumentUri(context, uri)) {
if (DocumentsContract.isDocumentUri(context, uri)) {
// ExternalStorageProvider
if (isExternalStorageDocument(uri)) {
final String docId = DocumentsContract.getDocumentId(uri);
@@ -111,6 +109,7 @@ public class RealPathUtil {
int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
returnCursor.moveToFirst();
fileName = sanitizeFilename(returnCursor.getString(nameIndex));
returnCursor.close();
} catch (Exception e) {
// just continue to get the filename with the last segment of the path
@@ -118,7 +117,7 @@ public class RealPathUtil {
try {
if (TextUtils.isEmpty(fileName)) {
fileName = sanitizeFilename(uri.getLastPathSegment().toString().trim());
fileName = sanitizeFilename(uri.getLastPathSegment().trim());
}
@@ -127,7 +126,6 @@ public class RealPathUtil {
cacheDir.mkdirs();
}
String mimeType = getMimeType(uri.getPath());
tmpFile = new File(cacheDir, fileName);
tmpFile.createNewFile();
@@ -234,7 +232,7 @@ public class RealPathUtil {
private static void deleteRecursive(File fileOrDirectory) {
if (fileOrDirectory.isDirectory())
for (File child : fileOrDirectory.listFiles())
for (File child : Objects.requireNonNull(fileOrDirectory.listFiles()))
deleteRecursive(child);
fileOrDirectory.delete();

View File

@@ -1,5 +1,5 @@
diff --git a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java
index 48fb5c1..3872d91 100644
index 48fb5c1..5ca1e20 100644
--- a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java
+++ b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/ImagePickerModule.java
@@ -3,6 +3,7 @@ package com.imagepicker;
@@ -10,15 +10,7 @@ index 48fb5c1..3872d91 100644
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
@@ -49,6 +50,7 @@ import java.io.InputStream;
import java.io.OutputStream;
import java.lang.ref.WeakReference;
import java.util.List;
+import java.util.ArrayList;
import com.facebook.react.modules.core.PermissionListener;
import com.facebook.react.modules.core.PermissionAwareActivity;
@@ -69,6 +71,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -69,6 +70,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
public static final int REQUEST_LAUNCH_IMAGE_LIBRARY = 13002;
public static final int REQUEST_LAUNCH_VIDEO_LIBRARY = 13003;
public static final int REQUEST_LAUNCH_VIDEO_CAPTURE = 13004;
@@ -26,11 +18,14 @@ index 48fb5c1..3872d91 100644
public static final int REQUEST_PERMISSIONS_FOR_CAMERA = 14001;
public static final int REQUEST_PERMISSIONS_FOR_LIBRARY = 14002;
@@ -266,26 +269,24 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -265,27 +267,22 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
{
cameraIntent.putExtra(MediaStore.EXTRA_DURATION_LIMIT, videoDurationLimit);
}
}
+ else if (pickBoth) {
- }
- else
- {
+ } else if (pickBoth) {
+ Intent takePictureIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ this.setImageCaptureUri(takePictureIntent);
+ Intent takeVideoIntent = new Intent(MediaStore.ACTION_VIDEO_CAPTURE);
@@ -41,9 +36,7 @@ index 48fb5c1..3872d91 100644
+ cameraIntent.putExtra(Intent.EXTRA_TITLE, "Choose an action");
+ cameraIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, intentArray);
+ requestCode = REQUEST_LAUNCH_MIXED_CAPTURE;
+ }
else
{
+ } else {
requestCode = REQUEST_LAUNCH_IMAGE_CAPTURE;
cameraIntent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
@@ -66,23 +59,24 @@ index 48fb5c1..3872d91 100644
}
if (cameraIntent.resolveActivity(reactContext.getPackageManager()) == null)
@@ -350,16 +351,19 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -349,17 +346,18 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
requestCode = REQUEST_LAUNCH_VIDEO_LIBRARY;
libraryIntent = new Intent(Intent.ACTION_PICK);
libraryIntent.setType("video/*");
}
+ else if (pickBoth) {
- }
- else
- {
+ } else if (pickBoth) {
+ libraryIntent = new Intent(Intent.ACTION_GET_CONTENT);
+ libraryIntent.addCategory(Intent.CATEGORY_OPENABLE);
+ libraryIntent.setType("image/*");
+ libraryIntent.putExtra(Intent.EXTRA_MIME_TYPES, new String[] {"image/*", "video/*"});
+ requestCode = REQUEST_LAUNCH_MIXED_CAPTURE;
+ }
else
{
+ } else {
requestCode = REQUEST_LAUNCH_IMAGE_LIBRARY;
libraryIntent = new Intent(Intent.ACTION_PICK,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
-
- if (pickBoth)
- {
- libraryIntent.setType("image/* video/*");
@@ -91,7 +85,7 @@ index 48fb5c1..3872d91 100644
}
if (libraryIntent.resolveActivity(reactContext.getPackageManager()) == null)
@@ -385,75 +389,47 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -385,75 +383,42 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
}
}
@@ -113,29 +107,10 @@ index 48fb5c1..3872d91 100644
- callback = null;
- return;
- }
+ protected String getMimeType(Activity activity, Uri uri) {
+ String mimeType = null;
+ if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+ ContentResolver cr = activity.getApplicationContext().getContentResolver();
+ mimeType = cr.getType(uri);
+ } else {
+ String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri
+ .toString());
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+ fileExtension.toLowerCase());
+ }
+ return mimeType;
+ }
-
- Uri uri = null;
- switch (requestCode)
+ protected void extractImageFromResult(Activity activity, Uri uri, int requestCode) {
+ String realPath = getRealPathFromURI(uri);
+ String mime = getMimeType(activity, uri);
+ final boolean isUrl = !TextUtils.isEmpty(realPath) &&
+ Patterns.WEB_URL.matcher(realPath).matches();
+ if (realPath == null || isUrl)
{
- {
- case REQUEST_LAUNCH_IMAGE_CAPTURE:
- uri = cameraCaptureURI;
- break;
@@ -172,20 +147,35 @@ index 48fb5c1..3872d91 100644
- responseHelper.invokeResponse(callback);
- callback = null;
- return;
-
+ protected String getMimeType(Activity activity, Uri uri) {
+ String mimeType = null;
+ if (uri.getScheme().equals(ContentResolver.SCHEME_CONTENT)) {
+ ContentResolver cr = activity.getApplicationContext().getContentResolver();
+ mimeType = cr.getType(uri);
+ } else {
+ String fileExtension = MimeTypeMap.getFileExtensionFromUrl(uri.toString());
+ mimeType = MimeTypeMap.getSingleton().getMimeTypeFromExtension(
+ fileExtension.toLowerCase());
+ }
+ return mimeType;
+ }
- case REQUEST_LAUNCH_VIDEO_CAPTURE:
- final String path = getRealPathFromURI(data.getData());
- responseHelper.putString("uri", data.getData().toString());
- responseHelper.putString("path", path);
- fileScan(reactContext, path);
+ try
+ {
+ protected void extractImageFromResult(Activity activity, Uri uri, int requestCode) {
+ String realPath = getRealPathFromURI(uri);
+ String mime = getMimeType(activity, uri);
+ final boolean isUrl = !TextUtils.isEmpty(realPath) &&
+ Patterns.WEB_URL.matcher(realPath).matches();
+ if (isUrl) {
+ try {
+ File file = createFileFromURI(uri);
+ realPath = file.getAbsolutePath();
+ uri = Uri.fromFile(file);
+ }
+ catch (Exception e)
+ {
+ } catch (Exception e) {
+ // image not in cache
+ responseHelper.putString("error", "Could not read photo");
+ responseHelper.putString("uri", uri.toString());
@@ -201,7 +191,7 @@ index 48fb5c1..3872d91 100644
final ReadExifResult result = readExifInterface(responseHelper, imageConfig);
if (result.error != null)
@@ -461,6 +437,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -461,6 +426,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
removeUselessFiles(requestCode, imageConfig);
responseHelper.invokeError(callback, result.error.getMessage());
callback = null;
@@ -209,7 +199,7 @@ index 48fb5c1..3872d91 100644
return;
}
@@ -472,7 +449,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -472,7 +438,7 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
updatedResultResponse(uri, imageConfig.original.getAbsolutePath());
// don't create a new file if contraint are respected
@@ -218,7 +208,7 @@ index 48fb5c1..3872d91 100644
{
responseHelper.putInt("width", initialWidth);
responseHelper.putInt("height", initialHeight);
@@ -481,6 +458,14 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -481,6 +447,13 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
else
{
imageConfig = getResizedImage(reactContext, this.options, imageConfig, initialWidth, initialHeight, requestCode);
@@ -229,11 +219,10 @@ index 48fb5c1..3872d91 100644
+ this.options = null;
+ return;
+ }
+
if (imageConfig.resized == null)
{
removeUselessFiles(requestCode, imageConfig);
@@ -523,6 +508,64 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -523,6 +496,61 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
this.options = null;
}
@@ -269,15 +258,12 @@ index 48fb5c1..3872d91 100644
+ case REQUEST_LAUNCH_IMAGE_CAPTURE:
+ extractImageFromResult(activity, cameraCaptureURI, requestCode);
+ break;
+
+ case REQUEST_LAUNCH_IMAGE_LIBRARY:
+ extractImageFromResult(activity, data.getData(), requestCode);
+ break;
+
+ case REQUEST_LAUNCH_VIDEO_LIBRARY:
+ extractVideoFromResult(data.getData());
+ break;
+
+ case REQUEST_LAUNCH_MIXED_CAPTURE:
+ case REQUEST_LAUNCH_VIDEO_CAPTURE:
+ if (data == null || data.getData() == null) {
@@ -298,7 +284,7 @@ index 48fb5c1..3872d91 100644
public void invokeCustomButton(@NonNull final String action)
{
responseHelper.invokeCustomButton(this.callback, action);
@@ -551,7 +594,8 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -551,7 +579,8 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
{
return callback == null || (cameraCaptureURI == null && requestCode == REQUEST_LAUNCH_IMAGE_CAPTURE)
|| (requestCode != REQUEST_LAUNCH_IMAGE_CAPTURE && requestCode != REQUEST_LAUNCH_IMAGE_LIBRARY
@@ -308,7 +294,7 @@ index 48fb5c1..3872d91 100644
}
private void updatedResultResponse(@Nullable final Uri uri,
@@ -571,22 +615,23 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -571,22 +600,24 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@NonNull final Callback callback,
@NonNull final int requestCode)
{
@@ -318,21 +304,21 @@ index 48fb5c1..3872d91 100644
- .checkSelfPermission(activity, Manifest.permission.CAMERA);
-
- boolean permissionsGranted = false;
-
+ int selfCheckResult = 0;
switch (requestCode) {
case REQUEST_PERMISSIONS_FOR_LIBRARY:
- permissionsGranted = writePermission == PackageManager.PERMISSION_GRANTED;
+ selfCheckResult = ActivityCompat
+ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
break;
case REQUEST_PERMISSIONS_FOR_CAMERA:
- permissionsGranted = cameraPermission == PackageManager.PERMISSION_GRANTED && writePermission == PackageManager.PERMISSION_GRANTED;
+ selfCheckResult = ActivityCompat
+ .checkSelfPermission(activity, Manifest.permission.CAMERA);
+ .checkSelfPermission(activity, Manifest.permission.CAMERA);
+ if (selfCheckResult == PackageManager.PERMISSION_GRANTED) {
+ selfCheckResult = ActivityCompat
+ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ .checkSelfPermission(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE);
+ }
break;
}
@@ -341,42 +327,52 @@ index 48fb5c1..3872d91 100644
if (!permissionsGranted)
{
final Boolean dontAskAgain = ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.WRITE_EXTERNAL_STORAGE) && ActivityCompat.shouldShowRequestPermissionRationale(activity, Manifest.permission.CAMERA);
@@ -787,4 +832,22 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
@@ -787,4 +818,21 @@ public class ImagePickerModule extends ReactContextBaseJavaModule
videoDurationLimit = options.getInt("durationLimit");
}
}
+
+ private void setImageCaptureUri(Intent cameraIntent) {
+ final File original = createNewFile(reactContext, this.options, false);
+ imageConfig = imageConfig.withOriginalFile(original);
+ imageConfig = imageConfig.withOriginalFile(original);
+
+ if (imageConfig.original != null) {
+ cameraCaptureURI = RealPathUtil.compatUriFromFile(reactContext, imageConfig.original);
+ }else {
+ responseHelper.invokeError(callback, "Couldn't get file path for photo");
+ return;
+ }
+ if (cameraCaptureURI == null)
+ {
+ responseHelper.invokeError(callback, "Couldn't get file path for photo");
+ return;
+ }
+ cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraCaptureURI);
+ if (imageConfig.original != null) {
+ cameraCaptureURI = RealPathUtil.compatUriFromFile(reactContext, imageConfig.original);
+ } else {
+ responseHelper.invokeError(callback, "Couldn't get file path for photo");
+ return;
+ }
+ if (cameraCaptureURI == null) {
+ responseHelper.invokeError(callback, "Couldn't get file path for photo");
+ return;
+ }
+ cameraIntent.putExtra(MediaStore.EXTRA_OUTPUT, cameraCaptureURI);
+ }
}
diff --git a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java
index cc90dce..c1cc74d 100644
index cc90dce..72ddc92 100644
--- a/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java
+++ b/node_modules/react-native-image-picker/android/src/main/java/com/imagepicker/utils/RealPathUtil.java
@@ -7,16 +7,22 @@ import android.net.Uri;
import android.os.Build;
@@ -1,201 +1,270 @@
package com.imagepicker.utils;
-import android.annotation.SuppressLint;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
-import android.os.Build;
import android.provider.DocumentsContract;
import android.provider.MediaStore;
-import android.content.ContentUris;
+import android.provider.OpenableColumns;
import android.content.ContentUris;
+import android.content.ContentResolver;
import android.os.Environment;
+import android.os.ParcelFileDescriptor;
+import android.webkit.MimeTypeMap;
+import android.util.Log;
+import android.text.TextUtils;
+
+import android.os.ParcelFileDescriptor;
+
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.core.content.FileProvider;
@@ -384,93 +380,432 @@ index cc90dce..c1cc74d 100644
-import java.io.File;
+import java.io.*;
+import java.nio.channels.FileChannel;
+import java.util.Objects;
+
+// Class based on the steveevers DocumentHelper https://gist.github.com/steveevers/a5af24c226f44bb8fdc3
public class RealPathUtil {
+ public static final String CACHE_DIR_NAME = "mmShare";
+ public static final String CACHE_DIR_NAME = "mmShare";
+
public static @Nullable Uri compatUriFromFile(@NonNull final Context context,
@NonNull final File file) {
Uri result = null;
@@ -58,12 +64,7 @@ public class RealPathUtil {
}
// DownloadsProvider
else if (isDownloadsDocument(uri)) {
+ public static @Nullable
+ Uri compatUriFromFile(@NonNull final Context context,
+ @NonNull final File file) {
+ Uri result = null;
+ final String packageName = context.getApplicationContext().getPackageName();
+ final String authority = packageName + ".provider";
+ try {
+ result = FileProvider.getUriForFile(context, authority, file);
+ }
+ catch(IllegalArgumentException e) {
+ e.printStackTrace();
+ }
+ return result;
+ }
+
+ public static String getRealPathFromURI(final Context context, final Uri uri) {
+
+ // DocumentProvider
+ if (DocumentsContract.isDocumentUri(context, uri)) {
+ // ExternalStorageProvider
+ if (isExternalStorageDocument(uri)) {
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ if ("primary".equalsIgnoreCase(type)) {
+ return Environment.getExternalStorageDirectory() + "/" + split[1];
+ }
+ } else if (isDownloadsDocument(uri)) {
+ // DownloadsProvider
+
+ final String id = DocumentsContract.getDocumentId(uri);
+ if (!TextUtils.isEmpty(id)) {
+ if (id.startsWith("raw:")) {
+ return id.replaceFirst("raw:", "");
+ }
+ try {
+ return getPathFromSavingTempFile(context, uri);
+ } catch (NumberFormatException e) {
+ Log.e("ReactNative", "DownloadsProvider unexpected uri " + uri.toString());
+ return null;
+ }
+ }
+ } else if (isMediaDocument(uri)) {
+ // MediaProvider
+
+ final String docId = DocumentsContract.getDocumentId(uri);
+ final String[] split = docId.split(":");
+ final String type = split[0];
+
+ Uri contentUri = null;
+ if ("image".equals(type)) {
+ contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
+ } else if ("video".equals(type)) {
+ contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
+ } else if ("audio".equals(type)) {
+ contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
+ }
+
+ final String selection = "_id=?";
+ final String[] selectionArgs = new String[] {
+ split[1]
+ };
+
+ if (contentUri != null) {
+ return getDataColumn(context, contentUri, selection, selectionArgs);
+ } else {
+ return getPathFromSavingTempFile(context, uri);
+ }
+ }
+ }
+
+ if ("content".equalsIgnoreCase(uri.getScheme())) {
+ // MediaStore (and general)
+
+ if (isGooglePhotosUri(uri)) {
+ return uri.getLastPathSegment();
+ }
+
+ // Try save to tmp file, and return tmp file path
+ return getPathFromSavingTempFile(context, uri);
+ } else if ("file".equalsIgnoreCase(uri.getScheme())) {
+ return uri.getPath();
+ }
+
+ return null;
+ }
+
+ public static String getPathFromSavingTempFile(Context context, final Uri uri) {
+ File tmpFile;
+ String fileName = null;
+
+ if (uri == null || uri.isRelative()) {
+ return null;
+ }
+
+ // Try and get the filename from the Uri
+ try {
+ Cursor returnCursor =
+ context.getContentResolver().query(uri, null, null, null, null);
+ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ returnCursor.moveToFirst();
+ fileName = sanitizeFilename(returnCursor.getString(nameIndex));
+ returnCursor.close();
+
+ } catch (Exception e) {
+ // just continue to get the filename with the last segment of the path
+ }
+
+ try {
+ if (TextUtils.isEmpty(fileName)) {
+ fileName = sanitizeFilename(uri.getLastPathSegment().trim());
+ }
+
+
+ File cacheDir = new File(context.getCacheDir(), CACHE_DIR_NAME);
+ if (!cacheDir.exists()) {
+ cacheDir.mkdirs();
+ }
+
+ tmpFile = new File(cacheDir, fileName);
+ tmpFile.createNewFile();
+
+ ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
+
+ FileChannel src = new FileInputStream(pfd.getFileDescriptor()).getChannel();
+ FileChannel dst = new FileOutputStream(tmpFile).getChannel();
+ dst.transferFrom(src, 0, src.size());
+ src.close();
+ dst.close();
+ } catch (IOException ex) {
+ return null;
+ }
+ return tmpFile.getAbsolutePath();
+ }
+
+ public static String getDataColumn(Context context, Uri uri, String selection,
+ String[] selectionArgs) {
+
+ Cursor cursor = null;
+ final String column = "_data";
+ final String[] projection = {
+ column
+ };
+
+ try {
+ cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
+ null);
+ if (cursor != null && cursor.moveToFirst()) {
+ final int index = cursor.getColumnIndexOrThrow(column);
+ return cursor.getString(index);
+ }
+ } finally {
+ if (cursor != null)
+ cursor.close();
+ }
+ return null;
+ }
+
+
+ public static boolean isExternalStorageDocument(Uri uri) {
+ return "com.android.externalstorage.documents".equals(uri.getAuthority());
+ }
+
+ public static boolean isDownloadsDocument(Uri uri) {
+ return "com.android.providers.downloads.documents".equals(uri.getAuthority());
+ }
+
+ public static boolean isMediaDocument(Uri uri) {
+ return "com.android.providers.media.documents".equals(uri.getAuthority());
+ }
+
+ public static boolean isGooglePhotosUri(Uri uri) {
+ return "com.google.android.apps.photos.content".equals(uri.getAuthority());
+ }
+
+ public static String getExtension(String uri) {
+ if (uri == null) {
+ return null;
+ }
+
+ int dot = uri.lastIndexOf(".");
+ if (dot >= 0) {
+ return uri.substring(dot);
+ } else {
+ // No extension.
+ return "";
+ }
+ }
+
+ public static String getMimeType(File file) {
+
+ String extension = getExtension(file.getName());
+
+ if (extension.length() > 0)
+ return MimeTypeMap.getSingleton().getMimeTypeFromExtension(extension.substring(1));
+
+ return "application/octet-stream";
+ }
+
+ public static String getMimeType(String filePath) {
+ File file = new File(filePath);
+ return getMimeType(file);
+ }
+
+ public static String getMimeTypeFromUri(final Context context, final Uri uri) {
+ try {
+ ContentResolver cR = context.getContentResolver();
+ return cR.getType(uri);
+ } catch (Exception e) {
+ return "application/octet-stream";
+ }
+ }
+
+ public static void deleteTempFiles(final File dir) {
+ try {
+ if (dir.isDirectory()) {
+ deleteRecursive(dir);
+ }
+ } catch (Exception e) {
+ // do nothing
+ }
+ }
+
+ private static void deleteRecursive(File fileOrDirectory) {
+ if (fileOrDirectory.isDirectory())
+ for (File child : Objects.requireNonNull(fileOrDirectory.listFiles()))
+ deleteRecursive(child);
+
+ fileOrDirectory.delete();
+ }
+
+ private static String sanitizeFilename(String filename) {
+ if (filename == null) {
+ return null;
+ }
- public static @Nullable Uri compatUriFromFile(@NonNull final Context context,
- @NonNull final File file) {
- Uri result = null;
- if (Build.VERSION.SDK_INT < 21) {
- result = Uri.fromFile(file);
- }
- else {
- final String packageName = context.getApplicationContext().getPackageName();
- final String authority = new StringBuilder(packageName).append(".provider").toString();
- try {
- result = FileProvider.getUriForFile(context, authority, file);
- }
- catch(IllegalArgumentException e) {
- e.printStackTrace();
- }
- }
- return result;
- }
-
- @SuppressLint("NewApi")
- public static @Nullable String getRealPathFromURI(@NonNull final Context context,
- @NonNull final Uri uri) {
-
- final boolean isKitKat = Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT;
-
- // DocumentProvider
- if (isKitKat && DocumentsContract.isDocumentUri(context, uri)) {
- // ExternalStorageProvider
- if (isExternalStorageDocument(uri)) {
- final String docId = DocumentsContract.getDocumentId(uri);
- final String[] split = docId.split(":");
- final String type = split[0];
-
- if ("primary".equalsIgnoreCase(type)) {
- return Environment.getExternalStorageDirectory() + "/" + split[1];
- }
-
- // TODO handle non-primary volumes
- }
- // DownloadsProvider
- else if (isDownloadsDocument(uri)) {
-
- final String id = DocumentsContract.getDocumentId(uri);
- final Uri contentUri = ContentUris.withAppendedId(
- Uri.parse("content://downloads/public_downloads"), Long.valueOf(id));
-
- return getDataColumn(context, contentUri, null, null);
+ return getPathFromSavingTempFile(context, uri);
}
// MediaProvider
else if (isMediaDocument(uri)) {
@@ -89,7 +90,7 @@ public class RealPathUtil {
}
}
// MediaStore (and general)
- }
- // MediaProvider
- else if (isMediaDocument(uri)) {
- final String docId = DocumentsContract.getDocumentId(uri);
- final String[] split = docId.split(":");
- final String type = split[0];
-
- Uri contentUri = null;
- if ("image".equals(type)) {
- contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI;
- } else if ("video".equals(type)) {
- contentUri = MediaStore.Video.Media.EXTERNAL_CONTENT_URI;
- } else if ("audio".equals(type)) {
- contentUri = MediaStore.Audio.Media.EXTERNAL_CONTENT_URI;
- }
-
- final String selection = "_id=?";
- final String[] selectionArgs = new String[] {
- split[1]
- };
-
- return getDataColumn(context, contentUri, selection, selectionArgs);
- }
- }
- // MediaStore (and general)
- else if ("content".equalsIgnoreCase(uri.getScheme())) {
+ if ("content".equalsIgnoreCase(uri.getScheme())) {
// Return the remote address
if (isGooglePhotosUri(uri))
@@ -98,7 +99,7 @@ public class RealPathUtil {
if (isFileProviderUri(context, uri))
return getFileProviderPath(context, uri);
-
- // Return the remote address
- if (isGooglePhotosUri(uri))
- return uri.getLastPathSegment();
-
- if (isFileProviderUri(context, uri))
- return getFileProviderPath(context, uri);
-
- return getDataColumn(context, uri, null, null);
+ return getPathFromSavingTempFile(context, uri);
}
// File
else if ("file".equalsIgnoreCase(uri.getScheme())) {
@@ -108,6 +109,49 @@ public class RealPathUtil {
return null;
}
+
+ public static String getPathFromSavingTempFile(Context context, final Uri uri) {
+ File tmpFile;
+ String fileName = null;
+
+ // Try and get the filename from the Uri
+ try {
+ Cursor returnCursor =
+ context.getContentResolver().query(uri, null, null, null, null);
+ int nameIndex = returnCursor.getColumnIndex(OpenableColumns.DISPLAY_NAME);
+ returnCursor.moveToFirst();
+ fileName = returnCursor.getString(nameIndex);
+ } catch (Exception e) {
+ // just continue to get the filename with the last segment of the path
+ }
+
+ try {
+ if (fileName == null) {
+ fileName = uri.getLastPathSegment().toString().trim();
+ }
+
+
+ File cacheDir = new File(context.getCacheDir(), CACHE_DIR_NAME);
+ if (!cacheDir.exists()) {
+ cacheDir.mkdirs();
+ }
+
+ tmpFile = new File(cacheDir, fileName);
+ tmpFile.createNewFile();
+
+ ParcelFileDescriptor pfd = context.getContentResolver().openFileDescriptor(uri, "r");
+
+ FileChannel src = new FileInputStream(pfd.getFileDescriptor()).getChannel();
+ FileChannel dst = new FileOutputStream(tmpFile).getChannel();
+ dst.transferFrom(src, 0, src.size());
+ src.close();
+ dst.close();
+ } catch (IOException ex) {
+ return null;
+ }
+ return tmpFile.getAbsolutePath();
+ }
+
/**
* Get the value of the data column for this Uri. This is useful for
* MediaStore Uris, and other file-based ContentProviders.
- }
- // File
- else if ("file".equalsIgnoreCase(uri.getScheme())) {
- return uri.getPath();
- }
-
- return null;
- }
-
- /**
- * Get the value of the data column for this Uri. This is useful for
- * MediaStore Uris, and other file-based ContentProviders.
- *
- * @param context The context.
- * @param uri The Uri to query.
- * @param selection (Optional) Filter used in the query.
- * @param selectionArgs (Optional) Selection arguments used in the query.
- * @return The value of the _data column, which is typically a file path.
- */
- public static String getDataColumn(Context context, Uri uri, String selection,
- String[] selectionArgs) {
-
- Cursor cursor = null;
- final String column = "_data";
- final String[] projection = {
- column
- };
-
- try {
- cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
- null);
- if (cursor != null && cursor.moveToFirst()) {
- final int index = cursor.getColumnIndexOrThrow(column);
- return cursor.getString(index);
- }
- } finally {
- if (cursor != null)
- cursor.close();
- }
- return null;
- }
-
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is ExternalStorageProvider.
- */
- public static boolean isExternalStorageDocument(Uri uri) {
- return "com.android.externalstorage.documents".equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is DownloadsProvider.
- */
- public static boolean isDownloadsDocument(Uri uri) {
- return "com.android.providers.downloads.documents".equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is MediaProvider.
- */
- public static boolean isMediaDocument(Uri uri) {
- return "com.android.providers.media.documents".equals(uri.getAuthority());
- }
-
- /**
- * @param uri The Uri to check.
- * @return Whether the Uri authority is Google Photos.
- */
- public static boolean isGooglePhotosUri(@NonNull final Uri uri) {
- return "com.google.android.apps.photos.content".equals(uri.getAuthority());
- }
-
- /**
- * @param context The Application context
- * @param uri The Uri is checked by functions
- * @return Whether the Uri authority is FileProvider
- */
- public static boolean isFileProviderUri(@NonNull final Context context,
- @NonNull final Uri uri) {
- final String packageName = context.getPackageName();
- final String authority = new StringBuilder(packageName).append(".provider").toString();
- return authority.equals(uri.getAuthority());
- }
-
- /**
- * @param context The Application context
- * @param uri The Uri is checked by functions
- * @return File path or null if file is missing
- */
- public static @Nullable String getFileProviderPath(@NonNull final Context context,
- @NonNull final Uri uri)
- {
- final File appDir = context.getExternalFilesDir(Environment.DIRECTORY_PICTURES);
- final File file = new File(appDir, uri.getLastPathSegment());
- return file.exists() ? file.toString(): null;
- }
+ File f = new File(filename);
+ return f.getName();
+ }
}