Bug 1499618 Crash in java.lang.IllegalArgumentException: at android.database.AbstractCursor.getColumnIndexOrThrow(AbstractCursor.java) r=JanH
authorAndrei Lazar <andrei.a.lazar@softvision.ro>
Wed, 21 Nov 2018 16:33:38 +0000
changeset 505568 e1e05cf314a30fcaa2c859917dffdad32e3ae0b6
parent 505567 b9eb6110ecfc7fcd3e1479e6defcb8d759647002
child 505569 c1a7bed23922c06094d251ac6de96af92cc6d649
push id10290
push userffxbld-merge
push dateMon, 03 Dec 2018 16:23:23 +0000
treeherdermozilla-beta@700bed2445e6 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersJanH
bugs1499618
milestone65.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 1499618 Crash in java.lang.IllegalArgumentException: at android.database.AbstractCursor.getColumnIndexOrThrow(AbstractCursor.java) r=JanH Added fallback for the cases where trying to obtain a file absolute path from external storage was throwing an exception. Differential Revision: https://phabricator.services.mozilla.com/D9248
mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ContentUriUtils.java
mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
--- a/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
+++ b/mobile/android/base/java/org/mozilla/gecko/IntentHelper.java
@@ -36,17 +36,17 @@ import android.webkit.MimeTypeMap;
 import java.io.File;
 import java.net.URI;
 import java.net.URISyntaxException;
 import java.util.List;
 import java.util.Locale;
 
 import static org.mozilla.gecko.Tabs.INTENT_EXTRA_SESSION_UUID;
 import static org.mozilla.gecko.Tabs.INTENT_EXTRA_TAB_ID;
-import static org.mozilla.gecko.util.FileUtils.getFilePathFromUri;
+import static org.mozilla.gecko.util.FileUtils.resolveContentUri;
 
 public final class IntentHelper implements BundleEventListener {
 
     private static final String LOGTAG = "GeckoIntentHelper";
     private static final String[] GECKO_EVENTS = {
         // Need to be on Gecko thread for synchronous callback.
         "Intent:GetHandlers",
     };
@@ -507,18 +507,21 @@ public final class IntentHelper implemen
             // Don't log the exception to prevent leaking URIs.
             Log.w(LOGTAG, "Unable to parse Intent URI - loading about:neterror");
             errorResponse.putBoolean("isFallback", false);
             callback.sendError(errorResponse);
             return;
         }
 
         if (FileUtils.isContentUri(uri)) {
-            errorResponse.putString("uri", getFilePathFromUri(getContext(), intent.getData()));
-            errorResponse.putBoolean("isFallback", true);
+            final String contentUri = resolveContentUri(getContext(), intent.getData());
+            if (!TextUtils.isEmpty(contentUri)) {
+                errorResponse.putString("uri", contentUri);
+                errorResponse.putBoolean("isFallback", true);
+            }
             callback.sendError(errorResponse);
             return;
         }
 
         // For this flow, we follow Chrome's lead:
         //   https://developer.chrome.com/multidevice/android/intents
         final String fallbackUrl = intent.getStringExtra(EXTRA_BROWSER_FALLBACK_URL);
         if (isFallbackUrlValid(fallbackUrl)) {
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ContentUriUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/ContentUriUtils.java
@@ -19,35 +19,38 @@ package org.mozilla.gecko.util;
 import android.content.ContentUris;
 import android.content.Context;
 import android.database.Cursor;
 import android.net.Uri;
 import android.os.Build;
 import android.os.Environment;
 import android.provider.DocumentsContract;
 import android.provider.MediaStore;
+import android.support.annotation.Nullable;
 import android.text.TextUtils;
 
+import java.io.File;
+
 /**
  * Based on https://github.com/iPaulPro/aFileChooser/blob/48d65e6649d4201407702b0390326ec9d5c9d17c/aFileChooser/src/com/ipaulpro/afilechooser/utils/FileUtils.java
  */
 public class ContentUriUtils {
     /**
      * Get a file path from a Uri. This will get the the path for Storage Access
      * Framework Documents, as well as the _data field for the MediaStore and
      * other file-based ContentProviders.<br>
      * <br>
      * Callers should check whether the path is local before assuming it
      * represents a local file.
      *
      * @param context The context.
      * @param uri The Uri to query.
      * @author paulburke
      */
-    public static String getPath(final Context context, final Uri uri) {
+    public static @Nullable String getOriginalFilePathFromUri(final Context context, final Uri uri) throws IllegalArgumentException {
 
         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);
@@ -114,45 +117,64 @@ public class ContentUriUtils {
         else if ("file".equalsIgnoreCase(uri.getScheme())) {
             return uri.getPath();
         }
 
         return null;
     }
 
     /**
+     * Retrieves file contents via getContentResolver().openInputStream() and stores them in a
+     * temporary file.
+     *
+     * @return The path of the temporary file, or <code>null</code> if there was an error
+     *         retrieving the file.
+     */
+    public static @Nullable String getTempFilePathFromContentUri(Context context, Uri contentUri) {
+        //copy file and send new file path
+        final String fileName = FileUtils.getFileNameFromContentUri(context, contentUri);
+        final File folder = new File(context.getCacheDir(), FileUtils.CONTENT_TEMP_DIRECTORY);
+        boolean success = true;
+        if (!folder.exists()) {
+            success = folder.mkdirs();
+        }
+
+        if (!TextUtils.isEmpty(fileName) && success) {
+                File copyFile = new File(folder.getPath(), fileName);
+                FileUtils.copy(context, contentUri, copyFile);
+                return copyFile.getAbsolutePath();
+        }
+        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.
      * @author paulburke
      */
     private static String getDataColumn(Context context, Uri uri, String selection,
-                                        String[] selectionArgs) {
+                                        String[] selectionArgs) throws IllegalArgumentException {
 
-        Cursor cursor = null;
         final String column = "_data";
         final String[] projection = {
                 column
         };
 
-        try {
-            cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
-                    null);
+        try (Cursor cursor = context.getContentResolver().query(uri, projection, selection, selectionArgs,
+                null)) {
             if (cursor != null && cursor.moveToFirst()) {
                 final int column_index = cursor.getColumnIndexOrThrow(column);
                 return cursor.getString(column_index);
             }
-        } finally {
-            if (cursor != null)
-                cursor.close();
         }
         return null;
     }
 
     /**
      * @param uri The Uri to check.
      * @return Whether the Uri authority is ExternalStorageProvider.
      * @author paulburke
--- a/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
+++ b/mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/FileUtils.java
@@ -1,16 +1,20 @@
 /* This Source Code Form is subject to the terms of the Mozilla Public
  * License, v. 2.0. If a copy of the MPL was not distributed with this file,
  * You can obtain one at http://mozilla.org/MPL/2.0/. */
 
 package org.mozilla.gecko.util;
 
+import android.content.ContentResolver;
 import android.content.Context;
+import android.database.Cursor;
 import android.net.Uri;
+import android.provider.MediaStore;
+import android.text.TextUtils;
 import android.util.Log;
 
 import java.io.File;
 import java.io.FileInputStream;
 import java.io.FileNotFoundException;
 import java.io.FileOutputStream;
 import java.io.IOException;
 import java.io.FilenameFilter;
@@ -24,23 +28,25 @@ import java.util.Random;
 import java.util.Set;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
 
 import org.json.JSONException;
 import org.json.JSONObject;
 import org.mozilla.gecko.annotation.RobocopTarget;
 
-import static org.mozilla.gecko.util.ContentUriUtils.getPath;
+import static org.mozilla.gecko.util.ContentUriUtils.getOriginalFilePathFromUri;
+import static org.mozilla.gecko.util.ContentUriUtils.getTempFilePathFromContentUri;
 
 public class FileUtils {
     private static final String LOGTAG = "GeckoFileUtils";
     private static final String FILE_SCHEME = "file";
     private static final String CONTENT_SCHEME = "content";
     private static final String FILE_ABSOLUTE_URI = FILE_SCHEME + "://%s";
+    public static final String CONTENT_TEMP_DIRECTORY = "contentUri";
 
     /*
     * A basic Filter for checking a filename and age.
     **/
     static public class NameAndAgeFilter implements FilenameFilter {
         final private String mName;
         final private double mMaxAge;
 
@@ -282,18 +288,53 @@ public class FileUtils {
         File result;
         Random random = new Random();
         do {
             result = new File(directory, prefix + random.nextInt());
         } while (!result.mkdirs());
         return result;
     }
 
-    public static String getFilePathFromUri(final Context context, final Uri uri) {
-        return String.format(FILE_ABSOLUTE_URI, getPath(context, uri));
+    public static String resolveContentUri(final Context context, final Uri uri) {
+        String path;
+        try {
+            path = getOriginalFilePathFromUri(context, uri);
+        } catch (IllegalArgumentException ex) {
+            // We cannot always successfully guess the original path of the file behind the
+            // content:// URI, so we need a fallback. This will break local subresources and
+            // relative links, but unfortunately there's nothing else we can do
+            // (see https://issuetracker.google.com/issues/77406791).
+            path = getTempFilePathFromContentUri(context, uri);
+        }
+        return !TextUtils.isEmpty(path) ? String.format(FILE_ABSOLUTE_URI, path) : path;
+    }
+
+    public static String getFileNameFromContentUri(Context context, Uri uri) {
+        final ContentResolver cr = context.getContentResolver();
+        final String[] projection = {MediaStore.MediaColumns.DISPLAY_NAME};
+        String fileName = null;
+
+        try (Cursor metaCursor = cr.query(uri, projection, null, null, null);) {
+            if (metaCursor.moveToFirst()) {
+                fileName = metaCursor.getString(0);
+            }
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
+
+        return fileName;
+    }
+
+    public static void copy(Context context, Uri srcUri, File dstFile) {
+        try (InputStream inputStream = context.getContentResolver().openInputStream(srcUri);
+             OutputStream outputStream = new FileOutputStream(dstFile)) {
+            IOUtils.copy(inputStream, outputStream);
+        } catch (Exception e) {
+            e.printStackTrace();
+        }
     }
 
     public static boolean isContentUri(Uri uri) {
         return uri != null && uri.getScheme() != null && CONTENT_SCHEME.equals(uri.getScheme());
     }
 
     public static boolean isContentUri(String sUri) {
         return sUri != null && sUri.startsWith(CONTENT_SCHEME);