summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorKevin F. Haggerty <haggertk@lineageos.org>2019-11-14 13:52:43 (GMT)
committerKevin F. Haggerty <haggertk@lineageos.org>2019-11-14 13:52:43 (GMT)
commit4cf46a15e8245923296d51c39684dcb2dc628d15 (patch)
tree21356b0a1c958d85bac7978fade9b89913918705
parent4af620f06f1356cf76287345a5cac62ef2337936 (diff)
parent1730e9b26321596c6c47de00cca3b88b9948d38e (diff)
downloadandroid_packages_providers_DownloadProvider-lineage-15.1.zip
android_packages_providers_DownloadProvider-lineage-15.1.tar.gz
android_packages_providers_DownloadProvider-lineage-15.1.tar.bz2
Merge tag 'android-8.1.0_r70' into staging/lineage-15.1_merge-android-9.0.0_r70lineage-15.1
Android 8.1.0 release 70 * tag 'android-8.1.0_r70': RESTRICT AUTOMERGE Enable stricter SQLiteQueryBuilder options. Merge commit '6d489a5f' into aug10d-oc-dev Remove "public" download feature. DO NOT MERGE: Update DownloadProvider for correct meteredness Change-Id: I71689859bcb2bfc3f5cfb0c6178362dc4d1bdb48
-rw-r--r--src/com/android/providers/downloads/DownloadProvider.java189
-rw-r--r--src/com/android/providers/downloads/Helpers.java263
2 files changed, 107 insertions, 345 deletions
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
index ceb17da..55a87a2 100644
--- a/src/com/android/providers/downloads/DownloadProvider.java
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -55,13 +55,13 @@ import android.provider.Downloads;
import android.provider.OpenableColumns;
import android.text.TextUtils;
import android.text.format.DateUtils;
+import android.util.ArrayMap;
import android.util.Log;
import com.android.internal.util.IndentingPrintWriter;
import libcore.io.IoUtils;
-import com.google.android.collect.Maps;
import com.google.common.annotations.VisibleForTesting;
import java.io.File;
@@ -70,11 +70,7 @@ import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
-import java.util.Arrays;
-import java.util.HashMap;
-import java.util.HashSet;
import java.util.Iterator;
-import java.util.List;
import java.util.Map;
/**
@@ -134,48 +130,107 @@ public final class DownloadProvider extends ContentProvider {
Downloads.Impl.ALL_DOWNLOADS_CONTENT_URI,
};
- private static final String[] sAppReadableColumnsArray = new String[] {
- Downloads.Impl._ID,
- Downloads.Impl.COLUMN_APP_DATA,
- Downloads.Impl._DATA,
- Downloads.Impl.COLUMN_MIME_TYPE,
- Downloads.Impl.COLUMN_VISIBILITY,
- Downloads.Impl.COLUMN_DESTINATION,
- Downloads.Impl.COLUMN_CONTROL,
- Downloads.Impl.COLUMN_STATUS,
- Downloads.Impl.COLUMN_LAST_MODIFICATION,
- Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE,
- Downloads.Impl.COLUMN_NOTIFICATION_CLASS,
- Downloads.Impl.COLUMN_TOTAL_BYTES,
- Downloads.Impl.COLUMN_CURRENT_BYTES,
- Downloads.Impl.COLUMN_TITLE,
- Downloads.Impl.COLUMN_DESCRIPTION,
- Downloads.Impl.COLUMN_URI,
- Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI,
- Downloads.Impl.COLUMN_FILE_NAME_HINT,
- Downloads.Impl.COLUMN_MEDIAPROVIDER_URI,
- Downloads.Impl.COLUMN_DELETED,
- OpenableColumns.DISPLAY_NAME,
- OpenableColumns.SIZE,
- };
+ private static void addMapping(Map<String, String> map, String column) {
+ if (!map.containsKey(column)) {
+ map.put(column, column);
+ }
+ }
- private static final HashSet<String> sAppReadableColumnsSet;
- private static final HashMap<String, String> sColumnsMap;
+ private static void addMapping(Map<String, String> map, String column, String rawColumn) {
+ if (!map.containsKey(column)) {
+ map.put(column, rawColumn + " AS " + column);
+ }
+ }
+ private static final Map<String, String> sDownloadsMap = new ArrayMap<>();
static {
- sAppReadableColumnsSet = new HashSet<String>();
- for (int i = 0; i < sAppReadableColumnsArray.length; ++i) {
- sAppReadableColumnsSet.add(sAppReadableColumnsArray[i]);
- }
+ final Map<String, String> map = sDownloadsMap;
+
+ // Columns defined by public API
+ addMapping(map, DownloadManager.COLUMN_ID,
+ Downloads.Impl._ID);
+ addMapping(map, DownloadManager.COLUMN_LOCAL_FILENAME,
+ Downloads.Impl._DATA);
+ addMapping(map, DownloadManager.COLUMN_MEDIAPROVIDER_URI);
+ addMapping(map, DownloadManager.COLUMN_DESTINATION);
+ addMapping(map, DownloadManager.COLUMN_TITLE);
+ addMapping(map, DownloadManager.COLUMN_DESCRIPTION);
+ addMapping(map, DownloadManager.COLUMN_URI);
+ addMapping(map, DownloadManager.COLUMN_STATUS);
+ addMapping(map, DownloadManager.COLUMN_FILE_NAME_HINT);
+ addMapping(map, DownloadManager.COLUMN_MEDIA_TYPE,
+ Downloads.Impl.COLUMN_MIME_TYPE);
+ addMapping(map, DownloadManager.COLUMN_TOTAL_SIZE_BYTES,
+ Downloads.Impl.COLUMN_TOTAL_BYTES);
+ addMapping(map, DownloadManager.COLUMN_LAST_MODIFIED_TIMESTAMP,
+ Downloads.Impl.COLUMN_LAST_MODIFICATION);
+ addMapping(map, DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR,
+ Downloads.Impl.COLUMN_CURRENT_BYTES);
+ addMapping(map, DownloadManager.COLUMN_ALLOW_WRITE);
+ addMapping(map, DownloadManager.COLUMN_LOCAL_URI,
+ "'placeholder'");
+ addMapping(map, DownloadManager.COLUMN_REASON,
+ "'placeholder'");
+
+ // Columns defined by OpenableColumns
+ addMapping(map, OpenableColumns.DISPLAY_NAME,
+ Downloads.Impl.COLUMN_TITLE);
+ addMapping(map, OpenableColumns.SIZE,
+ Downloads.Impl.COLUMN_TOTAL_BYTES);
+
+ // Allow references to all other columns to support DownloadInfo.Reader;
+ // we're already using SQLiteQueryBuilder to block access to other rows
+ // that don't belong to the calling UID.
+ addMapping(map, Downloads.Impl._ID);
+ addMapping(map, Downloads.Impl._DATA);
+ addMapping(map, Downloads.Impl.COLUMN_ALLOWED_NETWORK_TYPES);
+ addMapping(map, Downloads.Impl.COLUMN_ALLOW_METERED);
+ addMapping(map, Downloads.Impl.COLUMN_ALLOW_ROAMING);
+ addMapping(map, Downloads.Impl.COLUMN_ALLOW_WRITE);
+ addMapping(map, Downloads.Impl.COLUMN_APP_DATA);
+ addMapping(map, Downloads.Impl.COLUMN_BYPASS_RECOMMENDED_SIZE_LIMIT);
+ addMapping(map, Downloads.Impl.COLUMN_CONTROL);
+ addMapping(map, Downloads.Impl.COLUMN_COOKIE_DATA);
+ addMapping(map, Downloads.Impl.COLUMN_CURRENT_BYTES);
+ addMapping(map, Downloads.Impl.COLUMN_DELETED);
+ addMapping(map, Downloads.Impl.COLUMN_DESCRIPTION);
+ addMapping(map, Downloads.Impl.COLUMN_DESTINATION);
+ addMapping(map, Downloads.Impl.COLUMN_ERROR_MSG);
+ addMapping(map, Downloads.Impl.COLUMN_FAILED_CONNECTIONS);
+ addMapping(map, Downloads.Impl.COLUMN_FILE_NAME_HINT);
+ addMapping(map, Downloads.Impl.COLUMN_FLAGS);
+ addMapping(map, Downloads.Impl.COLUMN_IS_PUBLIC_API);
+ addMapping(map, Downloads.Impl.COLUMN_IS_VISIBLE_IN_DOWNLOADS_UI);
+ addMapping(map, Downloads.Impl.COLUMN_LAST_MODIFICATION);
+ addMapping(map, Downloads.Impl.COLUMN_MEDIAPROVIDER_URI);
+ addMapping(map, Downloads.Impl.COLUMN_MEDIA_SCANNED);
+ addMapping(map, Downloads.Impl.COLUMN_MIME_TYPE);
+ addMapping(map, Downloads.Impl.COLUMN_NO_INTEGRITY);
+ addMapping(map, Downloads.Impl.COLUMN_NOTIFICATION_CLASS);
+ addMapping(map, Downloads.Impl.COLUMN_NOTIFICATION_EXTRAS);
+ addMapping(map, Downloads.Impl.COLUMN_NOTIFICATION_PACKAGE);
+ addMapping(map, Downloads.Impl.COLUMN_OTHER_UID);
+ addMapping(map, Downloads.Impl.COLUMN_REFERER);
+ addMapping(map, Downloads.Impl.COLUMN_STATUS);
+ addMapping(map, Downloads.Impl.COLUMN_TITLE);
+ addMapping(map, Downloads.Impl.COLUMN_TOTAL_BYTES);
+ addMapping(map, Downloads.Impl.COLUMN_URI);
+ addMapping(map, Downloads.Impl.COLUMN_USER_AGENT);
+ addMapping(map, Downloads.Impl.COLUMN_VISIBILITY);
+
+ addMapping(map, Constants.ETAG);
+ addMapping(map, Constants.RETRY_AFTER_X_REDIRECT_COUNT);
+ addMapping(map, Constants.UID);
+ }
- sColumnsMap = Maps.newHashMap();
- sColumnsMap.put(OpenableColumns.DISPLAY_NAME,
- Downloads.Impl.COLUMN_TITLE + " AS " + OpenableColumns.DISPLAY_NAME);
- sColumnsMap.put(OpenableColumns.SIZE,
- Downloads.Impl.COLUMN_TOTAL_BYTES + " AS " + OpenableColumns.SIZE);
+ private static final Map<String, String> sHeadersMap = new ArrayMap<>();
+ static {
+ final Map<String, String> map = sHeadersMap;
+ addMapping(map, "id");
+ addMapping(map, Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID);
+ addMapping(map, Downloads.Impl.RequestHeaders.COLUMN_HEADER);
+ addMapping(map, Downloads.Impl.RequestHeaders.COLUMN_VALUE);
}
- private static final List<String> downloadManagerColumnsList =
- Arrays.asList(DownloadManager.UNDERLYING_COLUMNS);
@VisibleForTesting
SystemFacade mSystemFacade;
@@ -918,28 +973,6 @@ public final class DownloadProvider extends ContentProvider {
return qb.query(db, projection, null, null, null, null, null);
}
- if (shouldRestrictVisibility()) {
- if (projection == null) {
- projection = sAppReadableColumnsArray.clone();
- } else {
- // check the validity of the columns in projection
- for (int i = 0; i < projection.length; ++i) {
- if (!sAppReadableColumnsSet.contains(projection[i]) &&
- !downloadManagerColumnsList.contains(projection[i])) {
- throw new IllegalArgumentException(
- "column " + projection[i] + " is not allowed in queries");
- }
- }
- }
-
- for (int i = 0; i < projection.length; i++) {
- final String newColumn = sColumnsMap.get(projection[i]);
- if (newColumn != null) {
- projection[i] = newColumn;
- }
- }
- }
-
if (Constants.LOGVV) {
logVerboseQueryInfo(projection, selection, selectionArgs, sort, db);
}
@@ -1031,25 +1064,11 @@ public final class DownloadProvider extends ContentProvider {
}
/**
- * @return true if we should restrict the columns readable by this caller
- */
- private boolean shouldRestrictVisibility() {
- int callingUid = Binder.getCallingUid();
- return Binder.getCallingPid() != Process.myPid() &&
- callingUid != mSystemUid &&
- callingUid != mDefContainerUid;
- }
-
- /**
* Updates a row in the database
*/
@Override
public int update(final Uri uri, final ContentValues values,
final String where, final String[] whereArgs) {
- if (shouldRestrictVisibility()) {
- Helpers.validateSelection(where, sAppReadableColumnsSet);
- }
-
final Context context = getContext();
final ContentResolver resolver = context.getContentResolver();
@@ -1169,6 +1188,8 @@ public final class DownloadProvider extends ContentProvider {
*/
private SQLiteQueryBuilder getQueryBuilder(final Uri uri, int match) {
final String table;
+ final Map<String, String> projectionMap;
+
final StringBuilder where = new StringBuilder();
switch (match) {
// The "my_downloads" view normally limits the caller to operating
@@ -1179,6 +1200,7 @@ public final class DownloadProvider extends ContentProvider {
// fall-through
case MY_DOWNLOADS:
table = DB_TABLE;
+ projectionMap = sDownloadsMap;
if (getContext().checkCallingOrSelfPermission(
PERMISSION_ACCESS_ALL) != PackageManager.PERMISSION_GRANTED) {
appendWhereExpression(where, Constants.UID + "=" + Binder.getCallingUid()
@@ -1194,6 +1216,7 @@ public final class DownloadProvider extends ContentProvider {
// fall-through
case ALL_DOWNLOADS:
table = DB_TABLE;
+ projectionMap = sDownloadsMap;
break;
// Headers are limited to callers holding the ACCESS_ALL_DOWNLOADS
@@ -1201,6 +1224,7 @@ public final class DownloadProvider extends ContentProvider {
case MY_DOWNLOADS_ID_HEADERS:
case ALL_DOWNLOADS_ID_HEADERS:
table = Downloads.Impl.RequestHeaders.HEADERS_DB_TABLE;
+ projectionMap = sHeadersMap;
appendWhereExpression(where, Downloads.Impl.RequestHeaders.COLUMN_DOWNLOAD_ID + "="
+ getDownloadIdFromUri(uri));
break;
@@ -1210,8 +1234,11 @@ public final class DownloadProvider extends ContentProvider {
}
final SQLiteQueryBuilder qb = new SQLiteQueryBuilder();
- qb.setStrict(true);
qb.setTables(table);
+ qb.setProjectionMap(projectionMap);
+ qb.setStrict(true);
+ qb.setStrictColumns(true);
+ qb.setStrictGrammar(true);
qb.appendWhere(where);
return qb;
}
@@ -1228,10 +1255,6 @@ public final class DownloadProvider extends ContentProvider {
*/
@Override
public int delete(final Uri uri, final String where, final String[] whereArgs) {
- if (shouldRestrictVisibility()) {
- Helpers.validateSelection(where, sAppReadableColumnsSet);
- }
-
final Context context = getContext();
final ContentResolver resolver = context.getContentResolver();
final JobScheduler scheduler = context.getSystemService(JobScheduler.class);
diff --git a/src/com/android/providers/downloads/Helpers.java b/src/com/android/providers/downloads/Helpers.java
index 2b55eb8..984ca79 100644
--- a/src/com/android/providers/downloads/Helpers.java
+++ b/src/com/android/providers/downloads/Helpers.java
@@ -49,7 +49,7 @@ import com.google.common.annotations.VisibleForTesting;
import java.io.File;
import java.io.IOException;
import java.util.Random;
-import java.util.Set;
+import java.util.function.BiConsumer;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@@ -568,265 +568,4 @@ public class Helpers {
throw new IllegalStateException("unexpected destination: " + destination);
}
}
-
- /**
- * Checks whether this looks like a legitimate selection parameter
- */
- public static void validateSelection(String selection, Set<String> allowedColumns) {
- try {
- if (selection == null || selection.isEmpty()) {
- return;
- }
- Lexer lexer = new Lexer(selection, allowedColumns);
- parseExpression(lexer);
- if (lexer.currentToken() != Lexer.TOKEN_END) {
- throw new IllegalArgumentException("syntax error");
- }
- } catch (RuntimeException ex) {
- if (Constants.LOGV) {
- Log.d(Constants.TAG, "invalid selection [" + selection + "] triggered " + ex);
- } else if (false) {
- Log.d(Constants.TAG, "invalid selection triggered " + ex);
- }
- throw ex;
- }
-
- }
-
- // expression <- ( expression ) | statement [AND_OR ( expression ) | statement] *
- // | statement [AND_OR expression]*
- private static void parseExpression(Lexer lexer) {
- for (;;) {
- // ( expression )
- if (lexer.currentToken() == Lexer.TOKEN_OPEN_PAREN) {
- lexer.advance();
- parseExpression(lexer);
- if (lexer.currentToken() != Lexer.TOKEN_CLOSE_PAREN) {
- throw new IllegalArgumentException("syntax error, unmatched parenthese");
- }
- lexer.advance();
- } else {
- // statement
- parseStatement(lexer);
- }
- if (lexer.currentToken() != Lexer.TOKEN_AND_OR) {
- break;
- }
- lexer.advance();
- }
- }
-
- // statement <- COLUMN COMPARE VALUE
- // | COLUMN IS NULL
- private static void parseStatement(Lexer lexer) {
- // both possibilities start with COLUMN
- if (lexer.currentToken() != Lexer.TOKEN_COLUMN) {
- throw new IllegalArgumentException("syntax error, expected column name");
- }
- lexer.advance();
-
- // statement <- COLUMN COMPARE VALUE
- if (lexer.currentToken() == Lexer.TOKEN_COMPARE) {
- lexer.advance();
- if (lexer.currentToken() != Lexer.TOKEN_VALUE) {
- throw new IllegalArgumentException("syntax error, expected quoted string");
- }
- lexer.advance();
- return;
- }
-
- // statement <- COLUMN IS NULL
- if (lexer.currentToken() == Lexer.TOKEN_IS) {
- lexer.advance();
- if (lexer.currentToken() != Lexer.TOKEN_NULL) {
- throw new IllegalArgumentException("syntax error, expected NULL");
- }
- lexer.advance();
- return;
- }
-
- // didn't get anything good after COLUMN
- throw new IllegalArgumentException("syntax error after column name");
- }
-
- /**
- * A simple lexer that recognizes the words of our restricted subset of SQL where clauses
- */
- private static class Lexer {
- public static final int TOKEN_START = 0;
- public static final int TOKEN_OPEN_PAREN = 1;
- public static final int TOKEN_CLOSE_PAREN = 2;
- public static final int TOKEN_AND_OR = 3;
- public static final int TOKEN_COLUMN = 4;
- public static final int TOKEN_COMPARE = 5;
- public static final int TOKEN_VALUE = 6;
- public static final int TOKEN_IS = 7;
- public static final int TOKEN_NULL = 8;
- public static final int TOKEN_END = 9;
-
- private final String mSelection;
- private final Set<String> mAllowedColumns;
- private int mOffset = 0;
- private int mCurrentToken = TOKEN_START;
- private final char[] mChars;
-
- public Lexer(String selection, Set<String> allowedColumns) {
- mSelection = selection;
- mAllowedColumns = allowedColumns;
- mChars = new char[mSelection.length()];
- mSelection.getChars(0, mChars.length, mChars, 0);
- advance();
- }
-
- public int currentToken() {
- return mCurrentToken;
- }
-
- public void advance() {
- char[] chars = mChars;
-
- // consume whitespace
- while (mOffset < chars.length && chars[mOffset] == ' ') {
- ++mOffset;
- }
-
- // end of input
- if (mOffset == chars.length) {
- mCurrentToken = TOKEN_END;
- return;
- }
-
- // "("
- if (chars[mOffset] == '(') {
- ++mOffset;
- mCurrentToken = TOKEN_OPEN_PAREN;
- return;
- }
-
- // ")"
- if (chars[mOffset] == ')') {
- ++mOffset;
- mCurrentToken = TOKEN_CLOSE_PAREN;
- return;
- }
-
- // "?"
- if (chars[mOffset] == '?') {
- ++mOffset;
- mCurrentToken = TOKEN_VALUE;
- return;
- }
-
- // "=" and "=="
- if (chars[mOffset] == '=') {
- ++mOffset;
- mCurrentToken = TOKEN_COMPARE;
- if (mOffset < chars.length && chars[mOffset] == '=') {
- ++mOffset;
- }
- return;
- }
-
- // ">" and ">="
- if (chars[mOffset] == '>') {
- ++mOffset;
- mCurrentToken = TOKEN_COMPARE;
- if (mOffset < chars.length && chars[mOffset] == '=') {
- ++mOffset;
- }
- return;
- }
-
- // "<", "<=" and "<>"
- if (chars[mOffset] == '<') {
- ++mOffset;
- mCurrentToken = TOKEN_COMPARE;
- if (mOffset < chars.length && (chars[mOffset] == '=' || chars[mOffset] == '>')) {
- ++mOffset;
- }
- return;
- }
-
- // "!="
- if (chars[mOffset] == '!') {
- ++mOffset;
- mCurrentToken = TOKEN_COMPARE;
- if (mOffset < chars.length && chars[mOffset] == '=') {
- ++mOffset;
- return;
- }
- throw new IllegalArgumentException("Unexpected character after !");
- }
-
- // columns and keywords
- // first look for anything that looks like an identifier or a keyword
- // and then recognize the individual words.
- // no attempt is made at discarding sequences of underscores with no alphanumeric
- // characters, even though it's not clear that they'd be legal column names.
- if (isIdentifierStart(chars[mOffset])) {
- int startOffset = mOffset;
- ++mOffset;
- while (mOffset < chars.length && isIdentifierChar(chars[mOffset])) {
- ++mOffset;
- }
- String word = mSelection.substring(startOffset, mOffset);
- if (mOffset - startOffset <= 4) {
- if (word.equals("IS")) {
- mCurrentToken = TOKEN_IS;
- return;
- }
- if (word.equals("OR") || word.equals("AND")) {
- mCurrentToken = TOKEN_AND_OR;
- return;
- }
- if (word.equals("NULL")) {
- mCurrentToken = TOKEN_NULL;
- return;
- }
- }
- if (mAllowedColumns.contains(word)) {
- mCurrentToken = TOKEN_COLUMN;
- return;
- }
- throw new IllegalArgumentException("unrecognized column or keyword: " + word);
- }
-
- // quoted strings
- if (chars[mOffset] == '\'') {
- ++mOffset;
- while (mOffset < chars.length) {
- if (chars[mOffset] == '\'') {
- if (mOffset + 1 < chars.length && chars[mOffset + 1] == '\'') {
- ++mOffset;
- } else {
- break;
- }
- }
- ++mOffset;
- }
- if (mOffset == chars.length) {
- throw new IllegalArgumentException("unterminated string");
- }
- ++mOffset;
- mCurrentToken = TOKEN_VALUE;
- return;
- }
-
- // anything we don't recognize
- throw new IllegalArgumentException("illegal character: " + chars[mOffset]);
- }
-
- private static final boolean isIdentifierStart(char c) {
- return c == '_' ||
- (c >= 'A' && c <= 'Z') ||
- (c >= 'a' && c <= 'z');
- }
-
- private static final boolean isIdentifierChar(char c) {
- return c == '_' ||
- (c >= 'A' && c <= 'Z') ||
- (c >= 'a' && c <= 'z') ||
- (c >= '0' && c <= '9');
- }
- }
}