Merge the android-9.0.0_r55 release tag
Android 9.0.0 Release 55 (6197209)

* tag 'android-9.0.0_r55':
  RESTRICT AUTOMERGE Enable stricter SQLiteQueryBuilder options.

Change-Id: Iceef038d8a39090b5ce9e741e4679e528e0409be
diff --git a/src/com/android/providers/downloads/DownloadProvider.java b/src/com/android/providers/downloads/DownloadProvider.java
index f8d5aae..78ab758 100644
--- a/src/com/android/providers/downloads/DownloadProvider.java
+++ b/src/com/android/providers/downloads/DownloadProvider.java
@@ -55,13 +55,13 @@
 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.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 @@
             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 final HashSet<String> sAppReadableColumnsSet;
-    private static final HashMap<String, String> sColumnsMap;
-
-    static {
-        sAppReadableColumnsSet = new HashSet<String>();
-        for (int i = 0; i < sAppReadableColumnsArray.length; ++i) {
-            sAppReadableColumnsSet.add(sAppReadableColumnsArray[i]);
+    private static void addMapping(Map<String, String> map, String column) {
+        if (!map.containsKey(column)) {
+            map.put(column, column);
         }
-
-        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 List<String> downloadManagerColumnsList =
-            Arrays.asList(DownloadManager.UNDERLYING_COLUMNS);
+
+    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 {
+        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);
+    }
+
+    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);
+    }
 
     @VisibleForTesting
     SystemFacade mSystemFacade;
@@ -918,28 +973,6 @@
             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 @@
     }
 
     /**
-     * @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();
 
@@ -1168,6 +1187,8 @@
      */
     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
@@ -1178,6 +1199,7 @@
                 // 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()
@@ -1193,6 +1215,7 @@
                 // fall-through
             case ALL_DOWNLOADS:
                 table = DB_TABLE;
+                projectionMap = sDownloadsMap;
                 break;
 
             // Headers are limited to callers holding the ACCESS_ALL_DOWNLOADS
@@ -1200,6 +1223,7 @@
             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;
@@ -1209,8 +1233,11 @@
         }
 
         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;
     }
@@ -1227,10 +1254,6 @@
      */
     @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 963ca9d..87cf046 100644
--- a/src/com/android/providers/downloads/Helpers.java
+++ b/src/com/android/providers/downloads/Helpers.java
@@ -50,7 +50,7 @@
 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;
 
@@ -580,265 +580,4 @@
                 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');
-        }
-    }
 }