summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJeff Sharkey <jsharkey@android.com>2018-08-10 11:43:47 -0600
committerJeff Sharkey <jsharkey@google.com>2018-08-10 21:49:05 +0000
commit6d489a5f574013d22fe16af00464cc1e9eaf0616 (patch)
tree60e3027e68228cbd312d7d1a82e3146dcba72127
parentb2db30a21eaac0ebc033bc5a824b146633bedda0 (diff)
parentb760a3bb46b86a820303cb11bb506cd01c777a4d (diff)
downloadandroid_packages_providers_DownloadProvider-6d489a5f574013d22fe16af00464cc1e9eaf0616.tar.gz
android_packages_providers_DownloadProvider-6d489a5f574013d22fe16af00464cc1e9eaf0616.tar.bz2
android_packages_providers_DownloadProvider-6d489a5f574013d22fe16af00464cc1e9eaf0616.zip
Merge commit 'b760a3bb' into aug10d-nyc-mr2-dev
Bug: 111085900 Change-Id: Ie12be47f760402566801eb42f5defa0611a51ca2
-rw-r--r--src/com/android/providers/downloads/DownloadProvider.java195
1 files changed, 93 insertions, 102 deletions
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
index 7f242137..121ace46 100644
--- a/src/com/android/providers/downloads/DownloadProvider.java
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -20,7 +20,9 @@ import static android.provider.BaseColumns._ID;
import static android.provider.Downloads.Impl.COLUMN_DESTINATION;
import static android.provider.Downloads.Impl.COLUMN_MEDIA_SCANNED;
import static android.provider.Downloads.Impl.COLUMN_MIME_TYPE;
+import static android.provider.Downloads.Impl.COLUMN_OTHER_UID;
import static android.provider.Downloads.Impl.DESTINATION_NON_DOWNLOADMANAGER_DOWNLOAD;
+import static android.provider.Downloads.Impl.PERMISSION_ACCESS_ALL;
import static android.provider.Downloads.Impl._DATA;
import android.app.AppOpsManager;
@@ -42,6 +44,7 @@ import android.database.DatabaseUtils;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
+import android.database.sqlite.SQLiteQueryBuilder;
import android.net.Uri;
import android.os.Binder;
import android.os.ParcelFileDescriptor;
@@ -96,12 +99,14 @@ public final class DownloadProvider extends ContentProvider {
private static final int MY_DOWNLOADS = 1;
/** URI matcher constant for the URI of an individual download belonging to the calling UID */
private static final int MY_DOWNLOADS_ID = 2;
+ /** URI matcher constant for the URI of a download's request headers */
+ private static final int MY_DOWNLOADS_ID_HEADERS = 3;
/** URI matcher constant for the URI of all downloads in the system */
- private static final int ALL_DOWNLOADS = 3;
+ private static final int ALL_DOWNLOADS = 4;
/** URI matcher constant for the URI of an individual download */
- private static final int ALL_DOWNLOADS_ID = 4;
+ private static final int ALL_DOWNLOADS_ID = 5;
/** URI matcher constant for the URI of a download's request headers */
- private static final int REQUEST_HEADERS_URI = 5;
+ private static final int ALL_DOWNLOADS_ID_HEADERS = 6;
static {
sURIMatcher.addURI("downloads", "my_downloads", MY_DOWNLOADS);
sURIMatcher.addURI("downloads", "my_downloads/#", MY_DOWNLOADS_ID);
@@ -109,16 +114,16 @@ public final class DownloadProvider extends ContentProvider {
sURIMatcher.addURI("downloads", "all_downloads/#", ALL_DOWNLOADS_ID);
sURIMatcher.addURI("downloads",
"my_downloads/#/" + Downloads.Impl.RequestHeaders.URI_SEGMENT,
- REQUEST_HEADERS_URI);
+ MY_DOWNLOADS_ID_HEADERS);
sURIMatcher.addURI("downloads",
"all_downloads/#/" + Downloads.Impl.RequestHeaders.URI_SEGMENT,
- REQUEST_HEADERS_URI);
+ ALL_DOWNLOADS_ID_HEADERS);
// temporary, for backwards compatibility
sURIMatcher.addURI("downloads", "download", MY_DOWNLOADS);
sURIMatcher.addURI("downloads", "download/#", MY_DOWNLOADS_ID);
sURIMatcher.addURI("downloads",
"download/#/" + Downloads.Impl.RequestHeaders.URI_SEGMENT,
- REQUEST_HEADERS_URI);
+ MY_DOWNLOADS_ID_HEADERS);
}
/** Different base URIs that could be used to access an individual download */
@@ -181,43 +186,6 @@ public final class DownloadProvider extends ContentProvider {
private int mDefContainerUid = -1;
/**
- * This class encapsulates a SQL where clause and its parameters. It makes it possible for
- * shared methods (like {@link DownloadProvider#getWhereClause(Uri, String, String[], int)})
- * to return both pieces of information, and provides some utility logic to ease piece-by-piece
- * construction of selections.
- */
- private static class SqlSelection {
- public StringBuilder mWhereClause = new StringBuilder();
- public List<String> mParameters = new ArrayList<String>();
-
- public <T> void appendClause(String newClause, final T... parameters) {
- if (newClause == null || newClause.isEmpty()) {
- return;
- }
- if (mWhereClause.length() != 0) {
- mWhereClause.append(" AND ");
- }
- mWhereClause.append("(");
- mWhereClause.append(newClause);
- mWhereClause.append(")");
- if (parameters != null) {
- for (Object parameter : parameters) {
- mParameters.add(parameter.toString());
- }
- }
- }
-
- public String getSelection() {
- return mWhereClause.toString();
- }
-
- public String[] getParameters() {
- String[] array = new String[mParameters.size()];
- return mParameters.toArray(array);
- }
- }
-
- /**
* Creates and updated database on demand when opening it.
* Helper class to create database the first time the provider is
* initialized and upgrade it when a new version of the provider needs
@@ -938,15 +906,23 @@ public final class DownloadProvider extends ContentProvider {
throw new IllegalArgumentException("Unknown URI: " + uri);
}
- if (match == REQUEST_HEADERS_URI) {
+ if (match == MY_DOWNLOADS_ID_HEADERS || match == ALL_DOWNLOADS_ID_HEADERS) {
if (projection != null || selection != null || sort != null) {
throw new UnsupportedOperationException("Request header queries do not support "
+ "projections, selections or sorting");
}
- return queryRequestHeaders(db, uri);
- }
- SqlSelection fullSelection = getWhereClause(uri, selection, selectionArgs, match);
+ // Headers are only available to callers with full access.
+ getContext().enforceCallingOrSelfPermission(
+ Downloads.Impl.PERMISSION_ACCESS_ALL, Constants.TAG);
+
+ final SQLiteQueryBuilder qb = getQueryBuilder(uri, match);
+ projection = new String[] {
+ Downloads.Impl.RequestHeaders.COLUMN_HEADER,
+ Downloads.Impl.RequestHeaders.COLUMN_VALUE
+ };
+ return qb.query(db, projection, null, null, null, null, null);
+ }
if (shouldRestrictVisibility()) {
if (projection == null) {
@@ -974,8 +950,8 @@ public final class DownloadProvider extends ContentProvider {
logVerboseQueryInfo(projection, selection, selectionArgs, sort, db);
}
- Cursor ret = db.query(DB_TABLE, projection, fullSelection.getSelection(),
- fullSelection.getParameters(), null, null, sort);
+ final SQLiteQueryBuilder qb = getQueryBuilder(uri, match);
+ final Cursor ret = qb.query(db, projection, selection, selectionArgs, null, null, sort);
if (ret != null) {
ret.setNotificationUri(getContext().getContentResolver(), uri);
@@ -1061,35 +1037,6 @@ public final class DownloadProvider extends ContentProvider {
}
/**
- * Handle a query for the custom request headers registered for a download.
- */
- private Cursor queryRequestHeaders(SQLiteDatabase db, Uri uri) {
- String where = Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + "="
- + getDownloadIdFromUri(uri);
- String[] projection = new String[] {Downloads.Impl.RequestHeaders.COLUMN_HEADER,
- Downloads.Impl.RequestHeaders.COLUMN_VALUE};
- return db.query(Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE, projection, where,
- null, null, null, null);
- }
-
- /**
- * Delete request headers for downloads matching the given query.
- */
- private void deleteRequestHeaders(SQLiteDatabase db, String where, String[] whereArgs) {
- String[] projection = new String[] {Downloads.Impl._ID};
- Cursor cursor = db.query(DB_TABLE, projection, where, whereArgs, null, null, null, null);
- try {
- for (cursor.moveToFirst(); !cursor.isAfterLast(); cursor.moveToNext()) {
- long id = cursor.getLong(0);
- String idWhere = Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + "=" + id;
- db.delete(Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE, idWhere, null);
- }
- } finally {
- cursor.close();
- }
- }
-
- /**
* @return true if we should restrict the columns readable by this caller
*/
private boolean shouldRestrictVisibility() {
@@ -1170,13 +1117,12 @@ public final class DownloadProvider extends ContentProvider {
break;
}
- final SqlSelection selection = getWhereClause(uri, where, whereArgs, match);
- count = db.update(DB_TABLE, filteredValues, selection.getSelection(),
- selection.getParameters());
+ final SQLiteQueryBuilder qb = getQueryBuilder(uri, match);
+ count = qb.update(db, filteredValues, where, whereArgs);
if (updateSchedule || isCompleting) {
final long token = Binder.clearCallingIdentity();
- try (Cursor cursor = db.query(DB_TABLE, null, selection.getSelection(),
- selection.getParameters(), null, null, null)) {
+ try (Cursor cursor = qb.query(db, null, where, whereArgs,
+ null, null, null)) {
final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver,
cursor);
final DownloadInfo info = new DownloadInfo(context);
@@ -1222,21 +1168,64 @@ public final class DownloadProvider extends ContentProvider {
}
}
- private SqlSelection getWhereClause(final Uri uri, final String where, final String[] whereArgs,
- int uriMatch) {
- SqlSelection selection = new SqlSelection();
- selection.appendClause(where, whereArgs);
- if (uriMatch == MY_DOWNLOADS_ID || uriMatch == ALL_DOWNLOADS_ID) {
- selection.appendClause(Downloads.Impl._ID + " = ?", getDownloadIdFromUri(uri));
+ /**
+ * Create a query builder that filters access to the underlying database
+ * based on both the requested {@link Uri} and permissions of the caller.
+ */
+ private SQLiteQueryBuilder getQueryBuilder(final Uri uri, int match) {
+ final String table;
+ final StringBuilder where = new StringBuilder();
+ switch (match) {
+ // The "my_downloads" view normally limits the caller to operating
+ // on downloads that they either directly own, or have been given
+ // indirect ownership of via OTHER_UID.
+ case MY_DOWNLOADS_ID:
+ appendWhereExpression(where, _ID + "=" + getDownloadIdFromUri(uri));
+ // fall-through
+ case MY_DOWNLOADS:
+ table = DB_TABLE;
+ if (getContext().checkCallingOrSelfPermission(
+ PERMISSION_ACCESS_ALL) != PackageManager.PERMISSION_GRANTED) {
+ appendWhereExpression(where, Constants.UID + "=" + Binder.getCallingUid()
+ + " OR " + COLUMN_OTHER_UID + "=" + Binder.getCallingUid());
+ }
+ break;
+
+ // The "all_downloads" view is already limited via <path-permission>
+ // to only callers holding the ACCESS_ALL_DOWNLOADS permission, but
+ // access may also be delegated via Uri permission grants.
+ case ALL_DOWNLOADS_ID:
+ appendWhereExpression(where, _ID + "=" + getDownloadIdFromUri(uri));
+ // fall-through
+ case ALL_DOWNLOADS:
+ table = DB_TABLE;
+ break;
+
+ // Headers are limited to callers holding the ACCESS_ALL_DOWNLOADS
+ // permission, since they're only needed for executing downloads.
+ case MY_DOWNLOADS_ID_HEADERS:
+ case ALL_DOWNLOADS_ID_HEADERS:
+ table = Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE;
+ appendWhereExpression(where, Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + "="
+ + getDownloadIdFromUri(uri));
+ break;
+
+ default:
+ throw new UnsupportedOperationException("Unknown URI: " + uri);
}
- if ((uriMatch == MY_DOWNLOADS || uriMatch == MY_DOWNLOADS_ID)
- && getContext().checkCallingOrSelfPermission(Downloads.Impl.PERMISSION_ACCESS_ALL)
- != PackageManager.PERMISSION_GRANTED) {
- selection.appendClause(
- Constants.UID + "= ? OR " + Downloads.Impl.COLUMN_OTHER_UID + "= ?",
- Binder.getCallingUid(), Binder.getCallingUid());
+
+ final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
+ qb.setStrict(true);
+ qb.setTables(table);
+ qb.appendWhere(where);
+ return qb;
+ }
+
+ private static void appendWhereExpression(StringBuilder sb, String expression) {
+ if (sb.length() > 0) {
+ sb.append(" AND ");
}
- return selection;
+ sb.append('(').append(expression).append(')');
}
/**
@@ -1260,11 +1249,8 @@ public final class DownloadProvider extends ContentProvider {
case MY_DOWNLOADS_ID:
case ALL_DOWNLOADS:
case ALL_DOWNLOADS_ID:
- final SqlSelection selection = getWhereClause(uri, where, whereArgs, match);
- deleteRequestHeaders(db, selection.getSelection(), selection.getParameters());
-
- try (Cursor cursor = db.query(DB_TABLE, null, selection.getSelection(),
- selection.getParameters(), null, null, null)) {
+ final SQLiteQueryBuilder qb = getQueryBuilder(uri, match);
+ try (Cursor cursor = qb.query(db, null, where, whereArgs, null, null, null)) {
final DownloadInfo.Reader reader = new DownloadInfo.Reader(resolver, cursor);
final DownloadInfo info = new DownloadInfo(context);
while (cursor.moveToNext()) {
@@ -1304,10 +1290,15 @@ public final class DownloadProvider extends ContentProvider {
if (!Downloads.Impl.isStatusCompleted(info.mStatus)) {
info.sendIntentIfRequested();
}
+
+ // Delete any headers for this download
+ db.delete(Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE,
+ Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + "=?",
+ new String[] { Long.toString(info.mId) });
}
}
- count = db.delete(DB_TABLE, selection.getSelection(), selection.getParameters());
+ count = qb.delete(db, where, whereArgs);
break;
default: