From aeda68c64c46fd3ea28bbaa78d5c4f89b49eadd9 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Thu, 21 Jun 2018 09:04:59 -0700 Subject: Import translations. DO NOT MERGE Auto-generated-cl: translation import Bug: 64712476 Change-Id: Ibe041341d032a0eb9b0f31b74db37c2e3de38df9 --- res/values-fr/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values-fr/strings.xml b/res/values-fr/strings.xml index ed341cc5..83489be8 100644 --- a/res/values-fr/strings.xml +++ b/res/values-fr/strings.xml @@ -16,7 +16,7 @@ - "Gest. de tél." + "Gestionnaire de téléchargement" "Téléchargements" "Accéder au gestionnaire de téléchargement." "Permet à l\'application d\'accéder au gestionnaire de téléchargement et de l\'utiliser pour télécharger des fichiers. Les applications malveillantes peuvent utiliser cette fonctionnalité pour perturber les téléchargements et accéder à des informations confidentielles." -- cgit v1.2.3 From 35e123117be9ec5d61dbaea60f6eac06c0e80dc4 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Mon, 9 Jul 2018 12:16:26 -0600 Subject: Remove "public" download feature. It was never a supported API, and has been reported as causing security issues, so remove it. Bug: 111084083 Test: builds Change-Id: I26345b192ffd55216bb8c8fdb82cb5869d68d3db --- src/com/android/providers/downloads/DownloadProvider.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java index 8590df92..ef0b2883 100644 --- a/src/com/android/providers/downloads/DownloadProvider.java +++ b/src/com/android/providers/downloads/DownloadProvider.java @@ -102,11 +102,6 @@ public final class DownloadProvider extends ContentProvider { private static final int ALL_DOWNLOADS_ID = 4; /** URI matcher constant for the URI of a download's request headers */ private static final int REQUEST_HEADERS_URI = 5; - /** URI matcher constant for the public URI returned by - * {@link DownloadManager#getUriForDownloadedFile(long)} if the given downloaded file - * is publicly accessible. - */ - private static final int PUBLIC_DOWNLOAD_ID = 6; static { sURIMatcher.addURI("downloads", "my_downloads", MY_DOWNLOADS); sURIMatcher.addURI("downloads", "my_downloads/#", MY_DOWNLOADS_ID); @@ -124,9 +119,6 @@ public final class DownloadProvider extends ContentProvider { sURIMatcher.addURI("downloads", "download/#/" + Downloads.Impl.RequestHeaders.URI_SEGMENT, REQUEST_HEADERS_URI); - sURIMatcher.addURI("downloads", - Downloads.Impl.PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT + "/#", - PUBLIC_DOWNLOAD_ID); } /** Different base URIs that could be used to access an individual download */ @@ -522,8 +514,7 @@ public final class DownloadProvider extends ContentProvider { return DOWNLOAD_LIST_TYPE; } case MY_DOWNLOADS_ID: - case ALL_DOWNLOADS_ID: - case PUBLIC_DOWNLOAD_ID: { + case ALL_DOWNLOADS_ID: { // return the mimetype of this id from the database final String id = getDownloadIdFromUri(uri); final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); @@ -1224,8 +1215,7 @@ public final class DownloadProvider extends ContentProvider { int uriMatch) { SqlSelection selection = new SqlSelection(); selection.appendClause(where, whereArgs); - if (uriMatch == MY_DOWNLOADS_ID || uriMatch == ALL_DOWNLOADS_ID || - uriMatch == PUBLIC_DOWNLOAD_ID) { + if (uriMatch == MY_DOWNLOADS_ID || uriMatch == ALL_DOWNLOADS_ID) { selection.appendClause(Downloads.Impl._ID + " = ?", getDownloadIdFromUri(uri)); } if ((uriMatch == MY_DOWNLOADS || uriMatch == MY_DOWNLOADS_ID) -- cgit v1.2.3 From ce9f204ac493f000cd3020e195fd5038d0cec1e2 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Mon, 9 Jul 2018 12:16:26 -0600 Subject: Remove "public" download feature. It was never a supported API, and has been reported as causing security issues, so remove it. Bug: 111084083 Test: builds Change-Id: I26345b192ffd55216bb8c8fdb82cb5869d68d3db --- src/com/android/providers/downloads/DownloadProvider.java | 14 ++------------ 1 file changed, 2 insertions(+), 12 deletions(-) diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java index e177da17..db7db65a 100644 --- a/src/com/android/providers/downloads/DownloadProvider.java +++ b/src/com/android/providers/downloads/DownloadProvider.java @@ -105,11 +105,6 @@ public final class DownloadProvider extends ContentProvider { private static final int ALL_DOWNLOADS_ID = 4; /** URI matcher constant for the URI of a download's request headers */ private static final int REQUEST_HEADERS_URI = 5; - /** URI matcher constant for the public URI returned by - * {@link DownloadManager#getUriForDownloadedFile(long)} if the given downloaded file - * is publicly accessible. - */ - private static final int PUBLIC_DOWNLOAD_ID = 6; static { sURIMatcher.addURI("downloads", "my_downloads", MY_DOWNLOADS); sURIMatcher.addURI("downloads", "my_downloads/#", MY_DOWNLOADS_ID); @@ -127,9 +122,6 @@ public final class DownloadProvider extends ContentProvider { sURIMatcher.addURI("downloads", "download/#/" + Downloads.Impl.RequestHeaders.URI_SEGMENT, REQUEST_HEADERS_URI); - sURIMatcher.addURI("downloads", - Downloads.Impl.PUBLICLY_ACCESSIBLE_DOWNLOADS_URI_SEGMENT + "/#", - PUBLIC_DOWNLOAD_ID); } /** Different base URIs that could be used to access an individual download */ @@ -526,8 +518,7 @@ public final class DownloadProvider extends ContentProvider { return DOWNLOAD_LIST_TYPE; } case MY_DOWNLOADS_ID: - case ALL_DOWNLOADS_ID: - case PUBLIC_DOWNLOAD_ID: { + case ALL_DOWNLOADS_ID: { // return the mimetype of this id from the database final String id = getDownloadIdFromUri(uri); final SQLiteDatabase db = mOpenHelper.getReadableDatabase(); @@ -1234,8 +1225,7 @@ public final class DownloadProvider extends ContentProvider { int uriMatch) { SqlSelection selection = new SqlSelection(); selection.appendClause(where, whereArgs); - if (uriMatch == MY_DOWNLOADS_ID || uriMatch == ALL_DOWNLOADS_ID || - uriMatch == PUBLIC_DOWNLOAD_ID) { + if (uriMatch == MY_DOWNLOADS_ID || uriMatch == ALL_DOWNLOADS_ID) { selection.appendClause(Downloads.Impl._ID + " = ?", getDownloadIdFromUri(uri)); } if ((uriMatch == MY_DOWNLOADS || uriMatch == MY_DOWNLOADS_ID) -- cgit v1.2.3 From 64b55ea82b1f394369237601ae1f1c78b776aabc Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Mon, 16 Jul 2018 13:18:04 -0600 Subject: DO NOT MERGE. All untrusted selections must go through builder. When accepting untrusted selections, they must be passed directly to SQLiteQueryBuilder to ensure that setStrict() can be applied to check for malicious callers sending unbalanced parentheses. This means we can't mix local and remote selections; they always need to be kept separate. Use newly added SQLiteQueryBuilder functionality to apply strict detection to update() and delete() calls. Only allow the owner of a particular download to query the headers for that download. Only delete headers for a download once we've confirmed that caller can modify that download. Test: atest packages/providers/DownloadProvider/tests/ Test: atest cts/tests/app/src/android/app/cts/DownloadManagerTest.java Bug: 111085900 Change-Id: I9fd8e0d3cf80d7603bf0092f36fe449467090821 Merged-In: I9fd8e0d3cf80d7603bf0092f36fe449467090821 --- .../providers/downloads/DownloadProvider.java | 196 ++++++++++----------- 1 file changed, 91 insertions(+), 105 deletions(-) diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java index db7db65a..f8d5aae2 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; @@ -99,12 +101,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); @@ -112,16 +116,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 */ @@ -183,43 +187,6 @@ public final class DownloadProvider extends ContentProvider { private int mSystemUid = -1; 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 mParameters = new ArrayList(); - - public 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 @@ -933,15 +900,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) { @@ -969,11 +944,8 @@ public final class DownloadProvider extends ContentProvider { logVerboseQueryInfo(projection, selection, selectionArgs, sort, db); } - SQLiteQueryBuilder builder = new SQLiteQueryBuilder(); - builder.setTables(DB_TABLE); - builder.setStrict(true); - Cursor ret = builder.query(db, 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); @@ -1058,35 +1030,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 */ @@ -1169,13 +1112,11 @@ 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); @@ -1221,21 +1162,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 + // 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(')'); } /** @@ -1259,11 +1243,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()) { @@ -1305,10 +1286,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: -- cgit v1.2.3 From 3cb737cefc1e296e75d323b2d221ac8ceaafa6b8 Mon Sep 17 00:00:00 2001 From: Bill Yi Date: Fri, 3 Aug 2018 17:11:20 -0700 Subject: Import translations. DO NOT MERGE Auto-generated-cl: translation import Bug: 64712476 Change-Id: Ie16a2d3550ec50409c2396f439222fdec04af5e7 --- res/values-hy/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/res/values-hy/strings.xml b/res/values-hy/strings.xml index a1a48e93..506158b1 100644 --- a/res/values-hy/strings.xml +++ b/res/values-hy/strings.xml @@ -42,7 +42,7 @@ "Դուք պետք է օգտագործեք Wi-Fi՝ այս %s ներբեռնումն ավարտելու համար: \n\nՀպեք %s ՝ սկսելու այս ներբեռնումը հաջորդ անգամ, երբ միացած կլինեք որևէ Wi-Fi ցանցի:" "Հերթագրե՞լ ավելի ուշ ներբեռնելու համար:" "Այս %s ներբեռնումը հիմա սկսելը կարող է կարճեցնել ձեր մարտկոցի կյանքը և/կամ բերել ձեր բբջային տվյալների կապի ավելորդ օգտագործման, որը կարող է ուղեկցվել ծախսերով ձեր բջջային օպերատորի կողմից՝ կախված ձեր տվյալների փաթեթից:\n\n Հպեք %s ստորև` այս ներբեռնումը սկսելու հաջորդ անգամ, երբ միացած կլինեք որևէ Wi-Fi ցանցի:" - "Հերթականություն" + "Հերթագրել" "Չեղարկել" "Սկսել հիմա" -- cgit v1.2.3 From d728a508d651060676f93b252f5c62927a3728a1 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Mon, 16 Jul 2018 13:18:04 -0600 Subject: DO NOT MERGE. All untrusted selections must go through builder. When accepting untrusted selections, they must be passed directly to SQLiteQueryBuilder to ensure that setStrict() can be applied to check for malicious callers sending unbalanced parentheses. This means we can't mix local and remote selections; they always need to be kept separate. Use newly added SQLiteQueryBuilder functionality to apply strict detection to update() and delete() calls. Only allow the owner of a particular download to query the headers for that download. Only delete headers for a download once we've confirmed that caller can modify that download. Test: atest packages/providers/DownloadProvider/tests/ Test: atest cts/tests/app/src/android/app/cts/DownloadManagerTest.java Bug: 111085900 Change-Id: I9fd8e0d3cf80d7603bf0092f36fe449467090821 Merged-In: I9fd8e0d3cf80d7603bf0092f36fe449467090821 --- .../providers/downloads/DownloadProvider.java | 195 ++++++++++----------- 1 file changed, 92 insertions(+), 103 deletions(-) diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java index ef0b2883..e8240782 100644 --- a/src/com/android/providers/downloads/DownloadProvider.java +++ b/src/com/android/providers/downloads/DownloadProvider.java @@ -21,7 +21,9 @@ import static android.provider.Downloads.Impl.COLUMN_DESTINATION; import static android.provider.Downloads.Impl.COLUMN_MEDIAPROVIDER_URI; 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 */ @@ -180,43 +185,6 @@ public final class DownloadProvider extends ContentProvider { private int mSystemUid = -1; 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 mParameters = new ArrayList(); - - public 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 @@ -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); @@ -1060,35 +1036,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 */ @@ -1165,14 +1112,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) { final long token = Binder.clearCallingIdentity(); try { - try (Cursor cursor = db.query(DB_TABLE, new String[] { _ID }, - selection.getSelection(), selection.getParameters(), + try (Cursor cursor = qb.query(db, null, where, whereArgs, null, null, null)) { while (cursor.moveToNext()) { Helpers.scheduleJob(getContext(), cursor.getInt(0)); @@ -1211,21 +1156,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 + // 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(')'); } /** @@ -1246,12 +1234,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, new String[] { - _ID, _DATA, COLUMN_MEDIAPROVIDER_URI - }, 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)) { while (cursor.moveToNext()) { final long id = cursor.getLong(0); scheduler.cancel((int) id); @@ -1282,10 +1266,15 @@ public final class DownloadProvider extends ContentProvider { Binder.restoreCallingIdentity(token); } } + + // Delete any headers for this download + db.delete(Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE, + Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + "=?", + new String[] { Long.toString(id) }); } } - count = db.delete(DB_TABLE, selection.getSelection(), selection.getParameters()); + count = qb.delete(db, where, whereArgs); break; default: -- cgit v1.2.3