diff options
| -rw-r--r-- | core/api/current.txt | 47 | ||||
| -rw-r--r-- | core/api/test-current.txt | 1 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteAuthorizer.java | 178 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteConnection.java | 155 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteDatabase.java | 273 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java | 9 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteDirectCursorDriver.java | 13 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteProgram.java | 15 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteQuery.java | 13 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteRawStatement.java | 7 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteSession.java | 117 | ||||
| -rw-r--r-- | core/java/android/database/sqlite/SQLiteStatement.java | 22 | ||||
| -rw-r--r-- | core/jni/android_database_SQLiteConnection.cpp | 189 |
13 files changed, 811 insertions, 228 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 6aac75087258..d0e32a7db1f0 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -14205,6 +14205,46 @@ package android.database.sqlite { ctor public SQLiteAccessPermException(String); } + public interface SQLiteAuthorizer { + method public int onAuthorize(int, @Nullable String, @Nullable String, @Nullable String, @Nullable String); + field public static final int SQLITE_ACTION_ALTER_TABLE = 26; // 0x1a + field public static final int SQLITE_ACTION_ANALYZE = 28; // 0x1c + field public static final int SQLITE_ACTION_ATTACH = 24; // 0x18 + field public static final int SQLITE_ACTION_CREATE_INDEX = 1; // 0x1 + field public static final int SQLITE_ACTION_CREATE_TABLE = 2; // 0x2 + field public static final int SQLITE_ACTION_CREATE_TEMP_INDEX = 3; // 0x3 + field public static final int SQLITE_ACTION_CREATE_TEMP_TABLE = 4; // 0x4 + field public static final int SQLITE_ACTION_CREATE_TEMP_TRIGGER = 5; // 0x5 + field public static final int SQLITE_ACTION_CREATE_TEMP_VIEW = 6; // 0x6 + field public static final int SQLITE_ACTION_CREATE_TRIGGER = 7; // 0x7 + field public static final int SQLITE_ACTION_CREATE_VIEW = 8; // 0x8 + field public static final int SQLITE_ACTION_CREATE_VTABLE = 29; // 0x1d + field public static final int SQLITE_ACTION_DELETE = 9; // 0x9 + field public static final int SQLITE_ACTION_DETACH = 25; // 0x19 + field public static final int SQLITE_ACTION_DROP_INDEX = 10; // 0xa + field public static final int SQLITE_ACTION_DROP_TABLE = 11; // 0xb + field public static final int SQLITE_ACTION_DROP_TEMP_INDEX = 12; // 0xc + field public static final int SQLITE_ACTION_DROP_TEMP_TABLE = 13; // 0xd + field public static final int SQLITE_ACTION_DROP_TEMP_TRIGGER = 14; // 0xe + field public static final int SQLITE_ACTION_DROP_TEMP_VIEW = 15; // 0xf + field public static final int SQLITE_ACTION_DROP_TRIGGER = 16; // 0x10 + field public static final int SQLITE_ACTION_DROP_VIEW = 17; // 0x11 + field public static final int SQLITE_ACTION_DROP_VTABLE = 30; // 0x1e + field public static final int SQLITE_ACTION_FUNCTION = 31; // 0x1f + field public static final int SQLITE_ACTION_INSERT = 18; // 0x12 + field public static final int SQLITE_ACTION_PRAGMA = 19; // 0x13 + field public static final int SQLITE_ACTION_READ = 20; // 0x14 + field public static final int SQLITE_ACTION_RECURSIVE = 33; // 0x21 + field public static final int SQLITE_ACTION_REINDEX = 27; // 0x1b + field public static final int SQLITE_ACTION_SAVEPOINT = 32; // 0x20 + field public static final int SQLITE_ACTION_SELECT = 21; // 0x15 + field public static final int SQLITE_ACTION_TRANSACTION = 22; // 0x16 + field public static final int SQLITE_ACTION_UPDATE = 23; // 0x17 + field public static final int SQLITE_AUTHORIZER_RESULT_DENY = 1; // 0x1 + field public static final int SQLITE_AUTHORIZER_RESULT_IGNORE = 2; // 0x2 + field public static final int SQLITE_AUTHORIZER_RESULT_OK = 0; // 0x0 + } + public class SQLiteBindOrColumnIndexOutOfRangeException extends android.database.sqlite.SQLiteException { ctor public SQLiteBindOrColumnIndexOutOfRangeException(); ctor public SQLiteBindOrColumnIndexOutOfRangeException(String); @@ -14261,6 +14301,7 @@ package android.database.sqlite { method public void beginTransactionWithListenerNonExclusive(@Nullable android.database.sqlite.SQLiteTransactionListener); method public void beginTransactionWithListenerReadOnly(@Nullable android.database.sqlite.SQLiteTransactionListener); method public android.database.sqlite.SQLiteStatement compileStatement(String) throws android.database.SQLException; + method @NonNull public android.database.sqlite.SQLiteStatement compileStatement(@NonNull String, @NonNull android.database.sqlite.SQLiteAuthorizer) throws android.database.SQLException; method @NonNull public static android.database.sqlite.SQLiteDatabase create(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory); method @NonNull public static android.database.sqlite.SQLiteDatabase createInMemory(@NonNull android.database.sqlite.SQLiteDatabase.OpenParams); method public int delete(String, String, String[]); @@ -14271,6 +14312,7 @@ package android.database.sqlite { method public void execPerConnectionSQL(@NonNull String, @Nullable Object[]) throws android.database.SQLException; method public void execSQL(String) throws android.database.SQLException; method public void execSQL(String, Object[]) throws android.database.SQLException; + method public void execSQL(@NonNull String, @Nullable Object[], @NonNull android.database.sqlite.SQLiteAuthorizer) throws android.database.SQLException; method public static String findEditTable(String); method public java.util.List<android.util.Pair<java.lang.String,java.lang.String>> getAttachedDbs(); method public long getMaximumSize(); @@ -14282,6 +14324,7 @@ package android.database.sqlite { method public long insert(String, String, android.content.ContentValues); method public long insertOrThrow(String, String, android.content.ContentValues) throws android.database.SQLException; method public long insertWithOnConflict(String, String, android.content.ContentValues, int); + method public boolean isAuthorizerSupportEnabled(); method public boolean isDatabaseIntegrityOk(); method public boolean isDbLockedByCurrentThread(); method @Deprecated public boolean isDbLockedByOtherThreads(); @@ -14308,6 +14351,7 @@ package android.database.sqlite { method public android.database.Cursor rawQuery(String, String[], android.os.CancellationSignal); method public android.database.Cursor rawQueryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, String, String[], String); method public android.database.Cursor rawQueryWithFactory(android.database.sqlite.SQLiteDatabase.CursorFactory, String, String[], String, android.os.CancellationSignal); + method @NonNull public android.database.Cursor rawQueryWithFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory, @NonNull String, @Nullable String[], @Nullable String, @Nullable android.os.CancellationSignal, @NonNull android.database.sqlite.SQLiteAuthorizer); method public static int releaseMemory(); method public long replace(String, String, android.content.ContentValues); method public long replaceOrThrow(String, String, android.content.ContentValues) throws android.database.SQLException; @@ -14324,6 +14368,7 @@ package android.database.sqlite { method public int update(String, android.content.ContentValues, String, String[]); method public int updateWithOnConflict(String, android.content.ContentValues, String, String[], int); method public void validateSql(@NonNull String, @Nullable android.os.CancellationSignal); + method public void validateSql(@NonNull String, @Nullable android.os.CancellationSignal, @NonNull android.database.sqlite.SQLiteAuthorizer); method @Deprecated public boolean yieldIfContended(); method public boolean yieldIfContendedSafely(); method public boolean yieldIfContendedSafely(long); @@ -14372,7 +14417,9 @@ package android.database.sqlite { ctor public SQLiteDatabase.OpenParams.Builder(android.database.sqlite.SQLiteDatabase.OpenParams); method @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams.Builder addOpenFlags(int); method @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams build(); + method public boolean isAuthorizerSupportEnabled(); method @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams.Builder removeOpenFlags(int); + method @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setAuthorizerSupportEnabled(boolean); method @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setCursorFactory(@Nullable android.database.sqlite.SQLiteDatabase.CursorFactory); method @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setErrorHandler(@Nullable android.database.DatabaseErrorHandler); method @Deprecated @NonNull public android.database.sqlite.SQLiteDatabase.OpenParams.Builder setIdleConnectionTimeout(@IntRange(from=0) long); diff --git a/core/api/test-current.txt b/core/api/test-current.txt index 1dfd4f9fa484..255b45e47bf6 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -1303,6 +1303,7 @@ package android.database.sqlite { public final class SQLiteDirectCursorDriver implements android.database.sqlite.SQLiteCursorDriver { ctor public SQLiteDirectCursorDriver(android.database.sqlite.SQLiteDatabase, String, String, android.os.CancellationSignal); + ctor public SQLiteDirectCursorDriver(@NonNull android.database.sqlite.SQLiteDatabase, @Nullable android.database.sqlite.SQLiteAuthorizer, @NonNull String, @Nullable String, @Nullable android.os.CancellationSignal); method public void cursorClosed(); method public void cursorDeactivated(); method public void cursorRequeried(android.database.Cursor); diff --git a/core/java/android/database/sqlite/SQLiteAuthorizer.java b/core/java/android/database/sqlite/SQLiteAuthorizer.java new file mode 100644 index 000000000000..a2bf8980e9af --- /dev/null +++ b/core/java/android/database/sqlite/SQLiteAuthorizer.java @@ -0,0 +1,178 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package android.database.sqlite; + +import android.annotation.IntDef; +import android.annotation.Nullable; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Authorizer which is consulted during compilation of a SQL statement. + * <p> + * During compilation, this callback will be invoked to determine if each action + * requested by the SQL statement is allowed. + * <p> + * This can be useful to dynamically block interaction with private, internal, + * or otherwise sensitive columns or tables inside a database, such as when + * compiling an untrusted SQL statement. + */ +public interface SQLiteAuthorizer { + /** @hide */ + @IntDef(prefix = { "SQLITE_AUTHORIZER_RESULT_" }, value = { + SQLITE_AUTHORIZER_RESULT_OK, + SQLITE_AUTHORIZER_RESULT_DENY, + SQLITE_AUTHORIZER_RESULT_IGNORE, + }) + @Retention(RetentionPolicy.SOURCE) + @interface AuthorizerResult {} + + /** @hide */ + @IntDef(prefix = { "SQLITE_ACTION_" }, value = { + SQLITE_ACTION_CREATE_INDEX, + SQLITE_ACTION_CREATE_TABLE, + SQLITE_ACTION_CREATE_TEMP_INDEX, + SQLITE_ACTION_CREATE_TEMP_TABLE, + SQLITE_ACTION_CREATE_TEMP_TRIGGER, + SQLITE_ACTION_CREATE_TEMP_VIEW, + SQLITE_ACTION_CREATE_TRIGGER, + SQLITE_ACTION_CREATE_VIEW, + SQLITE_ACTION_DELETE, + SQLITE_ACTION_DROP_INDEX, + SQLITE_ACTION_DROP_TABLE, + SQLITE_ACTION_DROP_TEMP_INDEX, + SQLITE_ACTION_DROP_TEMP_TABLE, + SQLITE_ACTION_DROP_TEMP_TRIGGER, + SQLITE_ACTION_DROP_TEMP_VIEW, + SQLITE_ACTION_DROP_TRIGGER, + SQLITE_ACTION_DROP_VIEW, + SQLITE_ACTION_INSERT, + SQLITE_ACTION_PRAGMA, + SQLITE_ACTION_READ, + SQLITE_ACTION_SELECT, + SQLITE_ACTION_TRANSACTION, + SQLITE_ACTION_UPDATE, + SQLITE_ACTION_ATTACH, + SQLITE_ACTION_DETACH, + SQLITE_ACTION_ALTER_TABLE, + SQLITE_ACTION_REINDEX, + SQLITE_ACTION_ANALYZE, + SQLITE_ACTION_CREATE_VTABLE, + SQLITE_ACTION_DROP_VTABLE, + SQLITE_ACTION_FUNCTION, + SQLITE_ACTION_SAVEPOINT, + SQLITE_ACTION_RECURSIVE, + }) + @Retention(RetentionPolicy.SOURCE) + @interface AuthorizerAction {} + + /** Successful result */ + int SQLITE_AUTHORIZER_RESULT_OK = 0; + /** Abort the SQL statement with an error */ + int SQLITE_AUTHORIZER_RESULT_DENY = 1; + /** Don't allow access, but don't generate an error */ + int SQLITE_AUTHORIZER_RESULT_IGNORE = 2; + + /** Authorizer action for {@code CREATE INDEX} */ + int SQLITE_ACTION_CREATE_INDEX = 1; + /** Authorizer action for {@code CREATE TABLE} */ + int SQLITE_ACTION_CREATE_TABLE = 2; + /** Authorizer action for {@code CREATE TEMP INDEX} */ + int SQLITE_ACTION_CREATE_TEMP_INDEX = 3; + /** Authorizer action for {@code CREATE TEMP TABLE} */ + int SQLITE_ACTION_CREATE_TEMP_TABLE = 4; + /** Authorizer action for {@code CREATE TEMP TRIGGER} */ + int SQLITE_ACTION_CREATE_TEMP_TRIGGER = 5; + /** Authorizer action for {@code CREATE TEMP VIEW} */ + int SQLITE_ACTION_CREATE_TEMP_VIEW = 6; + /** Authorizer action for {@code CREATE TRIGGER} */ + int SQLITE_ACTION_CREATE_TRIGGER = 7; + /** Authorizer action for {@code CREATE VIEW} */ + int SQLITE_ACTION_CREATE_VIEW = 8; + /** Authorizer action for {@code DELETE} */ + int SQLITE_ACTION_DELETE = 9; + /** Authorizer action for {@code DROP INDEX} */ + int SQLITE_ACTION_DROP_INDEX = 10; + /** Authorizer action for {@code DROP TABLE} */ + int SQLITE_ACTION_DROP_TABLE = 11; + /** Authorizer action for {@code DROP TEMP INDEX} */ + int SQLITE_ACTION_DROP_TEMP_INDEX = 12; + /** Authorizer action for {@code DROP TEMP TABLE} */ + int SQLITE_ACTION_DROP_TEMP_TABLE = 13; + /** Authorizer action for {@code DROP TEMP TRIGGER} */ + int SQLITE_ACTION_DROP_TEMP_TRIGGER = 14; + /** Authorizer action for {@code DROP TEMP VIEW} */ + int SQLITE_ACTION_DROP_TEMP_VIEW = 15; + /** Authorizer action for {@code DROP TRIGGER} */ + int SQLITE_ACTION_DROP_TRIGGER = 16; + /** Authorizer action for {@code DROP VIEW} */ + int SQLITE_ACTION_DROP_VIEW = 17; + /** Authorizer action for {@code INSERT} */ + int SQLITE_ACTION_INSERT = 18; + /** Authorizer action for {@code PRAGMA} */ + int SQLITE_ACTION_PRAGMA = 19; + /** Authorizer action for read access on a specific table and column */ + int SQLITE_ACTION_READ = 20; + /** Authorizer action for {@code SELECT} */ + int SQLITE_ACTION_SELECT = 21; + /** Authorizer action for transaction operations */ + int SQLITE_ACTION_TRANSACTION = 22; + /** Authorizer action for {@code UPDATE} */ + int SQLITE_ACTION_UPDATE = 23; + /** Authorizer action for {@code ATTACH} */ + int SQLITE_ACTION_ATTACH = 24; + /** Authorizer action for {@code DETACH} */ + int SQLITE_ACTION_DETACH = 25; + /** Authorizer action for {@code ALTER TABLE} */ + int SQLITE_ACTION_ALTER_TABLE = 26; + /** Authorizer action for {@code REINDEX} */ + int SQLITE_ACTION_REINDEX = 27; + /** Authorizer action for {@code ANALYZE} */ + int SQLITE_ACTION_ANALYZE = 28; + /** Authorizer action for {@code CREATE VIRTUAL TABLE} */ + int SQLITE_ACTION_CREATE_VTABLE = 29; + /** Authorizer action for {@code DROP VIRTUAL TABLE} */ + int SQLITE_ACTION_DROP_VTABLE = 30; + /** Authorizer action for invocation of a function */ + int SQLITE_ACTION_FUNCTION = 31; + /** Authorizer action for savepoint operations */ + int SQLITE_ACTION_SAVEPOINT = 32; + /** Authorizer action for recursive operations */ + int SQLITE_ACTION_RECURSIVE = 33; + + /** + * Test if the given action should be allowed. + * + * @param action The action requested by the SQL statement currently being + * compiled. + * @param arg3 Optional argument relevant to the given action. + * @param arg4 Optional argument relevant to the given action. + * @param arg5 Optional argument relevant to the given action. + * @param arg6 Optional argument relevant to the given action. + * @return {@link SQLiteConstants#SQLITE_AUTHORIZER_RESULT_OK} to allow the action, + * {@link SQLiteConstants#SQLITE_AUTHORIZER_RESULT_IGNORE} to disallow the specific + * action but allow the SQL statement to continue to be compiled, or + * {@link SQLiteConstants#SQLITE_AUTHORIZER_RESULT_DENY} to cause the entire SQL + * statement to be rejected with an error. + * @see <a href="https://www.sqlite.org/c3ref/c_alter_table.html">Upstream + * SQLite documentation</a> that describes possible actions and their + * arguments. + */ + @AuthorizerResult int onAuthorize(@AuthorizerAction int action, @Nullable String arg3, + @Nullable String arg4, @Nullable String arg5, @Nullable String arg6); +} diff --git a/core/java/android/database/sqlite/SQLiteConnection.java b/core/java/android/database/sqlite/SQLiteConnection.java index 8323f7c0eeb5..b757cd9fd6d6 100644 --- a/core/java/android/database/sqlite/SQLiteConnection.java +++ b/core/java/android/database/sqlite/SQLiteConnection.java @@ -17,7 +17,7 @@ package android.database.sqlite; import android.annotation.NonNull; - +import android.annotation.Nullable; import android.database.Cursor; import android.database.CursorWindow; import android.database.DatabaseUtils; @@ -33,8 +33,10 @@ import android.util.Log; import android.util.LruCache; import android.util.Pair; import android.util.Printer; + import dalvik.system.BlockGuard; import dalvik.system.CloseGuard; + import java.io.File; import java.io.IOException; import java.lang.ref.Reference; @@ -128,9 +130,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private int mCancellationSignalAttachCount; private static native long nativeOpen(String path, int openFlags, String label, - boolean enableTrace, boolean enableProfile, int lookasideSlotSize, - int lookasideSlotCount); + int lookasideSlotSize, int lookasideSlotCount); private static native void nativeClose(long connectionPtr); + private static native void nativeSetAuthorizer(long connectionPtr, + SQLiteAuthorizer authorizer); private static native void nativeRegisterCustomScalarFunction(long connectionPtr, String name, UnaryOperator<String> function); private static native void nativeRegisterCustomAggregateFunction(long connectionPtr, @@ -225,9 +228,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen final String file = mConfiguration.path; final int cookie = mRecentOperations.beginOperation("open", null, null); try { - mConnectionPtr = nativeOpen(file, mConfiguration.openFlags, - mConfiguration.label, - NoPreloadHolder.DEBUG_SQL_STATEMENTS, NoPreloadHolder.DEBUG_SQL_TIME, + mConnectionPtr = nativeOpen(file, mConfiguration.openFlags, mConfiguration.label, mConfiguration.lookasideSlotSize, mConfiguration.lookasideSlotCount); } catch (SQLiteCantOpenDatabaseException e) { final StringBuilder message = new StringBuilder("Cannot open database '") @@ -301,9 +302,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private void setPageSize() { if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { final long newValue = SQLiteGlobal.getDefaultPageSize(); - long value = executeForLong("PRAGMA page_size", null, null); + long value = executeForLong(null, "PRAGMA page_size", null, null); if (value != newValue) { - execute("PRAGMA page_size=" + newValue, null, null); + execute(null, "PRAGMA page_size=" + newValue, null, null); } } } @@ -311,9 +312,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private void setAutoCheckpointInterval() { if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { final long newValue = SQLiteGlobal.getWALAutoCheckpoint(); - long value = executeForLong("PRAGMA wal_autocheckpoint", null, null); + long value = executeForLong(null, "PRAGMA wal_autocheckpoint", null, null); if (value != newValue) { - executeForLong("PRAGMA wal_autocheckpoint=" + newValue, null, null); + executeForLong(null, "PRAGMA wal_autocheckpoint=" + newValue, null, null); } } } @@ -321,9 +322,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private void setJournalSizeLimit() { if (!mConfiguration.isInMemoryDb() && !mIsReadOnlyConnection) { final long newValue = SQLiteGlobal.getJournalSizeLimit(); - long value = executeForLong("PRAGMA journal_size_limit", null, null); + long value = executeForLong(null, "PRAGMA journal_size_limit", null, null); if (value != newValue) { - executeForLong("PRAGMA journal_size_limit=" + newValue, null, null); + executeForLong(null, "PRAGMA journal_size_limit=" + newValue, null, null); } } } @@ -331,9 +332,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen private void setForeignKeyModeFromConfiguration() { if (!mIsReadOnlyConnection) { final long newValue = mConfiguration.foreignKeyConstraintsEnabled ? 1 : 0; - long value = executeForLong("PRAGMA foreign_keys", null, null); + long value = executeForLong(null, "PRAGMA foreign_keys", null, null); if (value != newValue) { - execute("PRAGMA foreign_keys=" + newValue, null, null); + execute(null, "PRAGMA foreign_keys=" + newValue, null, null); } } } @@ -386,7 +387,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen Log.i(TAG, walFile.getAbsolutePath() + " " + size + " bytes: Bigger than " + threshold + "; truncating"); try { - executeForString("PRAGMA wal_checkpoint(TRUNCATE)", null, null); + executeForString(null, "PRAGMA wal_checkpoint(TRUNCATE)", null, null); mConfiguration.shouldTruncateWalFile = false; } catch (SQLiteException e) { Log.w(TAG, "Failed to truncate the -wal file", e); @@ -398,10 +399,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen // No change to the sync mode is intended return; } - String value = executeForString("PRAGMA synchronous", null, null); + String value = executeForString(null, "PRAGMA synchronous", null, null); if (!canonicalizeSyncMode(value).equalsIgnoreCase( canonicalizeSyncMode(newValue))) { - execute("PRAGMA synchronous=" + newValue, null, null); + execute(null, "PRAGMA synchronous=" + newValue, null, null); } } @@ -420,10 +421,11 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen // No change to the journal mode is intended return; } - String value = executeForString("PRAGMA journal_mode", null, null); + String value = executeForString(null, "PRAGMA journal_mode", null, null); if (!value.equalsIgnoreCase(newValue)) { try { - String result = executeForString("PRAGMA journal_mode=" + newValue, null, null); + String result = executeForString(null, "PRAGMA journal_mode=" + newValue, + null, null); if (result.equalsIgnoreCase(newValue)) { return; } @@ -475,26 +477,26 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen try { // Ensure the android metadata table exists. - execute("CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null); + execute(null, "CREATE TABLE IF NOT EXISTS android_metadata (locale TEXT)", null, null); // Check whether the locale was actually changed. - final String oldLocale = executeForString("SELECT locale FROM android_metadata " + final String oldLocale = executeForString(null, "SELECT locale FROM android_metadata " + "UNION SELECT NULL ORDER BY locale DESC LIMIT 1", null, null); if (oldLocale != null && oldLocale.equals(newLocale)) { return; } // Go ahead and update the indexes using the new locale. - execute("BEGIN", null, null); + execute(null, "BEGIN", null, null); boolean success = false; try { - execute("DELETE FROM android_metadata", null, null); - execute("INSERT INTO android_metadata (locale) VALUES(?)", + execute(null, "DELETE FROM android_metadata", null, null); + execute(null, "INSERT INTO android_metadata (locale) VALUES(?)", new Object[] { newLocale }, null); - execute("REINDEX LOCALIZED", null, null); + execute(null, "REINDEX LOCALIZED", null, null); success = true; } finally { - execute(success ? "COMMIT" : "ROLLBACK", null, null); + execute(null, success ? "COMMIT" : "ROLLBACK", null, null); } } catch (SQLiteException ex) { throw ex; @@ -523,10 +525,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen final int type = DatabaseUtils.getSqlStatementType(statement.first); switch (type) { case DatabaseUtils.STATEMENT_SELECT: - executeForString(statement.first, statement.second, null); + executeForString(null, statement.first, statement.second, null); break; case DatabaseUtils.STATEMENT_PRAGMA: - execute(statement.first, statement.second, null); + execute(null, statement.first, statement.second, null); break; default: throw new IllegalArgumentException( @@ -543,9 +545,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen final File checkFile = new File(mConfiguration.path + SQLiteGlobal.WIPE_CHECK_FILE_SUFFIX); - final boolean hasMetadataTable = executeForLong( + final boolean hasMetadataTable = executeForLong(null, "SELECT count(*) FROM sqlite_master" - + " WHERE type='table' AND name='android_metadata'", null, null) > 0; + + " WHERE type='table' AND name='android_metadata'", + null, null) > 0; final boolean hasCheckFile = checkFile.exists(); if (!mIsReadOnlyConnection && !hasCheckFile) { @@ -667,14 +670,15 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * * @throws SQLiteException if an error occurs, such as a syntax error. */ - public void prepare(String sql, SQLiteStatementInfo outStatementInfo) { + public void prepare(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql, + @Nullable SQLiteStatementInfo outStatementInfo) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } final int cookie = mRecentOperations.beginOperation("prepare", sql, null); try { - final PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(authorizer, sql); try { if (outStatementInfo != null) { outStatementInfo.numParameters = statement.mNumParameters; @@ -714,8 +718,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public void execute(String sql, Object[] bindArgs, - CancellationSignal cancellationSignal) { + public void execute(@Nullable SQLiteAuthorizer authorizer, + @NonNull String sql, @Nullable Object[] bindArgs, + @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -724,7 +729,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen try { final boolean isPragmaStmt = DatabaseUtils.getSqlStatementType(sql) == DatabaseUtils.STATEMENT_PRAGMA; - final PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(authorizer, sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -759,15 +764,15 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public long executeForLong(String sql, Object[] bindArgs, - CancellationSignal cancellationSignal) { + public long executeForLong(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql, + @Nullable Object[] bindArgs, @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } final int cookie = mRecentOperations.beginOperation("executeForLong", sql, bindArgs); try { - final PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(authorizer, sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -804,15 +809,15 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public String executeForString(String sql, Object[] bindArgs, - CancellationSignal cancellationSignal) { + public String executeForString(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql, + @Nullable Object[] bindArgs, @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } final int cookie = mRecentOperations.beginOperation("executeForString", sql, bindArgs); try { - final PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(authorizer, sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -851,8 +856,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs, - CancellationSignal cancellationSignal) { + public ParcelFileDescriptor executeForBlobFileDescriptor(@Nullable SQLiteAuthorizer authorizer, + @NonNull String sql, @Nullable Object[] bindArgs, + @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -860,7 +866,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen final int cookie = mRecentOperations.beginOperation("executeForBlobFileDescriptor", sql, bindArgs); try { - final PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(authorizer, sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -897,8 +903,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public int executeForChangedRowCount(String sql, Object[] bindArgs, - CancellationSignal cancellationSignal) { + public int executeForChangedRowCount(@Nullable SQLiteAuthorizer authorizer, + @NonNull String sql, @Nullable Object[] bindArgs, + @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -907,7 +914,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen final int cookie = mRecentOperations.beginOperation("executeForChangedRowCount", sql, bindArgs); try { - final PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(authorizer, sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -946,8 +953,9 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public long executeForLastInsertedRowId(String sql, Object[] bindArgs, - CancellationSignal cancellationSignal) { + public long executeForLastInsertedRowId(@Nullable SQLiteAuthorizer authorizer, + @NonNull String sql, @Nullable Object[] bindArgs, + @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -955,7 +963,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen final int cookie = mRecentOperations.beginOperation("executeForLastInsertedRowId", sql, bindArgs); try { - final PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(authorizer, sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -1000,9 +1008,10 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public int executeForCursorWindow(String sql, Object[] bindArgs, - CursorWindow window, int startPos, int requiredPos, boolean countAllRows, - CancellationSignal cancellationSignal) { + public int executeForCursorWindow(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql, + @Nullable Object[] bindArgs, @NonNull CursorWindow window, + int startPos, int requiredPos, boolean countAllRows, + @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -1018,7 +1027,7 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen final int cookie = mRecentOperations.beginOperation("executeForCursorWindow", sql, bindArgs); try { - final PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(authorizer, sql); try { throwIfStatementForbidden(statement); bindArguments(statement, bindArgs); @@ -1056,13 +1065,19 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } } + /** * Return a {@link #PreparedStatement}, possibly from the cache. */ - PreparedStatement acquirePreparedStatement(String sql) { + private PreparedStatement acquirePreparedStatement(@Nullable SQLiteAuthorizer authorizer, + @NonNull String sql) { ++mPool.mTotalPrepareStatements; - PreparedStatement statement = mPreparedStatementCache.get(sql); - boolean skipCache = false; + // Custom per-statement authorizers are typically used for incoming + // untrusted custom SQL, which is likely to have low cache hit ratios, + // so we compile them every time. This leaves the prepared statement + // cache for statements using the default authorizer. + boolean skipCache = (authorizer != null); + PreparedStatement statement = skipCache ? null : mPreparedStatementCache.get(sql); if (statement != null) { if (!statement.mInUse) { return statement; @@ -1073,8 +1088,12 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen skipCache = true; } ++mPool.mTotalPrepareStatementCacheMiss; - final long statementPtr = nativePrepareStatement(mConnectionPtr, sql); + if (authorizer != null) { + nativeSetAuthorizer(mConnectionPtr, authorizer); + } + long statementPtr = 0; try { + statementPtr = nativePrepareStatement(mConnectionPtr, sql); final int numParameters = nativeGetParameterCount(mConnectionPtr, statementPtr); final int type = DatabaseUtils.getSqlStatementType(sql); final boolean readOnly = nativeIsReadOnly(mConnectionPtr, statementPtr); @@ -1086,10 +1105,15 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen } catch (RuntimeException ex) { // Finalize the statement if an exception occurred and we did not add // it to the cache. If it is already in the cache, then leave it there. + // It is safe to call finalize on a NULL, if nativePrepare failed. if (statement == null || !statement.mInCache) { nativeFinalizeStatement(mConnectionPtr, statementPtr); } throw ex; + } finally { + if (authorizer != null) { + nativeSetAuthorizer(mConnectionPtr, null); + } } statement.mInUse = true; return statement; @@ -1130,10 +1154,11 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen * Return a prepared statement for use by {@link SQLiteRawStatement}. This throws if the * prepared statement is incompatible with this connection. */ - PreparedStatement acquirePersistentStatement(@NonNull String sql) { + PreparedStatement acquirePersistentStatement(@Nullable SQLiteAuthorizer authorizer, + @NonNull String sql) { final int cookie = mRecentOperations.beginOperation("prepare", sql, null); try { - final PreparedStatement statement = acquirePreparedStatement(sql); + final PreparedStatement statement = acquirePreparedStatement(authorizer, sql); throwIfStatementForbidden(statement); return statement; } catch (RuntimeException e) { @@ -1328,8 +1353,8 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen long pageCount = 0; long pageSize = 0; try { - pageCount = executeForLong("PRAGMA page_count;", null, null); - pageSize = executeForLong("PRAGMA page_size;", null, null); + pageCount = executeForLong(null, "PRAGMA page_count;", null, null); + pageSize = executeForLong(null, "PRAGMA page_size;", null, null); } catch (SQLiteException ex) { // Ignore. } @@ -1340,15 +1365,15 @@ public final class SQLiteConnection implements CancellationSignal.OnCancelListen // the main database which we have already described. CursorWindow window = new CursorWindow("collectDbStats"); try { - executeForCursorWindow("PRAGMA database_list;", null, window, 0, 0, false, null); + executeForCursorWindow(null, "PRAGMA database_list;", null, window, 0, 0, false, null); for (int i = 1; i < window.getNumRows(); i++) { String name = window.getString(i, 1); String path = window.getString(i, 2); pageCount = 0; pageSize = 0; try { - pageCount = executeForLong("PRAGMA " + name + ".page_count;", null, null); - pageSize = executeForLong("PRAGMA " + name + ".page_size;", null, null); + pageCount = executeForLong(null, "PRAGMA " + name + ".page_count;", null, null); + pageSize = executeForLong(null, "PRAGMA " + name + ".page_size;", null, null); } catch (SQLiteException ex) { // Ignore. } diff --git a/core/java/android/database/sqlite/SQLiteDatabase.java b/core/java/android/database/sqlite/SQLiteDatabase.java index 839186548e9a..06d4f2840ccd 100644 --- a/core/java/android/database/sqlite/SQLiteDatabase.java +++ b/core/java/android/database/sqlite/SQLiteDatabase.java @@ -44,10 +44,12 @@ import android.util.EventLog; import android.util.Log; import android.util.Pair; import android.util.Printer; + import com.android.internal.util.Preconditions; import dalvik.annotation.optimization.NeverCompile; import dalvik.system.CloseGuard; + import java.io.File; import java.io.FileFilter; import java.io.IOException; @@ -253,6 +255,13 @@ public final class SQLiteDatabase extends SQLiteClosable { */ public static final int NO_LOCALIZED_COLLATORS = 0x00000010; // update native code if changing + /** @hide */ + public static final int ENABLE_TRACE = 0x00000100; + /** @hide */ + public static final int ENABLE_PROFILE = 0x00000200; + /** @hide */ + public static final int ENABLE_AUTHORIZER = 0x00000400; + /** * Open flag: Flag for {@link #openDatabase} to create the database file if it does not * already exist. @@ -1453,9 +1462,38 @@ public final class SQLiteDatabase extends SQLiteClosable { * {@link SQLiteStatement}s are not synchronized, see the documentation for more details. */ public SQLiteStatement compileStatement(String sql) throws SQLException { + return compileStatementInternal(sql, null); + } + + /** + * Compiles an SQL statement into a reusable pre-compiled statement object. + * The parameters are identical to {@link #execSQL(String)}. You may put ?s in the + * statement and fill in those values with {@link SQLiteProgram#bindString} + * and {@link SQLiteProgram#bindLong} each time you want to run the + * statement. Statements may not return result sets larger than 1x1. + *<p> + * No two threads should be using the same {@link SQLiteStatement} at the same time. + * Must configure: {@link OpenParams.Builder#setAuthorizerSupportEnabled(boolean)}. + * + * @param sql The raw SQL statement, may contain ? for unknown values to be + * bound later. + * @param authorizer The sql authorizer attached to the statement. + * @return A pre-compiled {@link SQLiteStatement} object. Note that + * {@link SQLiteStatement}s are not synchronized, see the documentation for more details. + */ + public @NonNull SQLiteStatement compileStatement(@NonNull String sql, + @NonNull SQLiteAuthorizer authorizer) throws SQLException { + return compileStatementInternal(sql, authorizer); + } + + private @NonNull SQLiteStatement compileStatementInternal(@NonNull String sql, + @Nullable SQLiteAuthorizer authorizer) throws SQLException { + if (authorizer != null) { + throwIfNotAuthorizerEnabled(); + } acquireReference(); try { - return new SQLiteStatement(this, sql, null); + return new SQLiteStatement(this, authorizer, sql, null); } finally { releaseReference(); } @@ -1752,7 +1790,8 @@ public final class SQLiteDatabase extends SQLiteClosable { public Cursor rawQueryWithFactory( CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable) { - return rawQueryWithFactory(cursorFactory, sql, selectionArgs, editTable, null); + return rawQueryWithFactoryInternal(cursorFactory, sql, selectionArgs, + editTable, null, null); } /** @@ -1773,17 +1812,52 @@ public final class SQLiteDatabase extends SQLiteClosable { public Cursor rawQueryWithFactory( CursorFactory cursorFactory, String sql, String[] selectionArgs, String editTable, CancellationSignal cancellationSignal) { + return rawQueryWithFactoryInternal(cursorFactory, sql, selectionArgs, editTable, + cancellationSignal, null); + } + + /** + * Runs the provided SQL and returns a cursor over the result set. + * Must configure: {@link OpenParams.Builder#setAuthorizerSupportEnabled(boolean)}. + * + * @param cursorFactory the cursor factory to use, or null for the default factory + * @param sql the SQL query. The SQL string must not be ; terminated + * @param selectionArgs You may include ?s in where clause in the query, + * which will be replaced by the values from selectionArgs. The + * values will be bound as Strings. + * @param editTable the name of the first table, which is editable + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @param authorizer The sql authorizer attached to the query. + * @return A {@link Cursor} object, which is positioned before the first entry. Note that + * {@link Cursor}s are not synchronized, see the documentation for more details. + */ + public @NonNull Cursor rawQueryWithFactory(@Nullable CursorFactory cursorFactory, + @NonNull String sql, @SuppressLint("ArrayReturn") @Nullable String[] selectionArgs, + @Nullable String editTable, @Nullable CancellationSignal cancellationSignal, + @NonNull SQLiteAuthorizer authorizer) { + return rawQueryWithFactoryInternal(cursorFactory, sql, selectionArgs, editTable, + cancellationSignal, authorizer); + } + + private @NonNull Cursor rawQueryWithFactoryInternal(@Nullable CursorFactory cursorFactory, + @NonNull String sql, @SuppressLint("ArrayReturn") @Nullable String[] selectionArgs, + @Nullable String editTable, @Nullable CancellationSignal cancellationSignal, + @Nullable SQLiteAuthorizer authorizer) { + if (authorizer != null) { + throwIfNotAuthorizerEnabled(); + } acquireReference(); try { - SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, sql, editTable, - cancellationSignal); + SQLiteCursorDriver driver = new SQLiteDirectCursorDriver(this, authorizer, sql, + editTable, cancellationSignal); return driver.query(cursorFactory != null ? cursorFactory : mCursorFactory, selectionArgs); } finally { releaseReference(); } } - /** * Convenience method for inserting a row into the database. * @@ -1931,7 +2005,8 @@ public final class SQLiteDatabase extends SQLiteClosable { } sql.append(')'); - SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); + // Custom authorizers can be applied through SQLiteQueryBuilder + SQLiteStatement statement = new SQLiteStatement(this, null, sql.toString(), bindArgs); try { return statement.executeInsert(); } finally { @@ -1958,8 +2033,9 @@ public final class SQLiteDatabase extends SQLiteClosable { public int delete(String table, String whereClause, String[] whereArgs) { acquireReference(); try { - SQLiteStatement statement = new SQLiteStatement(this, "DELETE FROM " + table + - (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs); + // Custom authorizers can be applied through SQLiteQueryBuilder + SQLiteStatement statement = new SQLiteStatement(this, null, "DELETE FROM " + table + + (!TextUtils.isEmpty(whereClause) ? " WHERE " + whereClause : ""), whereArgs); try { return statement.executeUpdateDelete(); } finally { @@ -2036,7 +2112,8 @@ public final class SQLiteDatabase extends SQLiteClosable { sql.append(whereClause); } - SQLiteStatement statement = new SQLiteStatement(this, sql.toString(), bindArgs); + // Custom authorizers can be applied through SQLiteQueryBuilder + SQLiteStatement statement = new SQLiteStatement(this, null, sql.toString(), bindArgs); try { return statement.executeUpdateDelete(); } finally { @@ -2073,7 +2150,7 @@ public final class SQLiteDatabase extends SQLiteClosable { * @throws SQLException if the SQL string is invalid */ public void execSQL(String sql) throws SQLException { - executeSql(sql, null); + executeSql(null, sql, null); } /** @@ -2126,14 +2203,72 @@ public final class SQLiteDatabase extends SQLiteClosable { * @throws SQLException if the SQL string is invalid */ public void execSQL(String sql, Object[] bindArgs) throws SQLException { - if (bindArgs == null) { - throw new IllegalArgumentException("Empty bindArgs"); + executeSql(null, sql, bindArgs); + } + + /** + * Execute a single SQL statement that is NOT a SELECT/INSERT/UPDATE/DELETE. + * Must configure: {@link OpenParams.Builder#setAuthorizerSupportEnabled(boolean)}. + * <p> + * For INSERT statements, use any of the following instead. + * <ul> + * <li>{@link #insert(String, String, ContentValues)}</li> + * <li>{@link #insertOrThrow(String, String, ContentValues)}</li> + * <li>{@link #insertWithOnConflict(String, String, ContentValues, int)}</li> + * </ul> + * <p> + * For UPDATE statements, use any of the following instead. + * <ul> + * <li>{@link #update(String, ContentValues, String, String[])}</li> + * <li>{@link #updateWithOnConflict(String, ContentValues, String, String[], int)}</li> + * </ul> + * <p> + * For DELETE statements, use any of the following instead. + * <ul> + * <li>{@link #delete(String, String, String[])}</li> + * </ul> + * <p> + * For example, the following are good candidates for using this method: + * <ul> + * <li>ALTER TABLE</li> + * <li>CREATE or DROP table / trigger / view / index / virtual table</li> + * <li>REINDEX</li> + * <li>RELEASE</li> + * <li>SAVEPOINT</li> + * <li>PRAGMA that returns no data</li> + * </ul> + * </p> + * <p> + * When using {@link #enableWriteAheadLogging()}, journal_mode is + * automatically managed by this class. So, do not set journal_mode + * using "PRAGMA journal_mode'<value>" statement if your app is using + * {@link #enableWriteAheadLogging()} + * </p> + * <p> + * Note that {@code PRAGMA} values which apply on a per-connection basis + * should <em>not</em> be configured using this method; you should instead + * use {@link #execPerConnectionSQL} to ensure that they are uniformly + * applied to all current and future connections. + * </p> + * + * @param sql the SQL statement to be executed. Multiple statements separated by semicolons are + * not supported. + * @param bindArgs only byte[], String, Long and Double are supported in bindArgs. + * @param authorizer The sql authorizer attached to the query. + * @throws SQLException if the SQL string is invalid + */ + public void execSQL(@NonNull String sql, + @SuppressLint("ArrayReturn") @Nullable Object[] bindArgs, + @NonNull SQLiteAuthorizer authorizer) throws SQLException { + if (authorizer != null) { + throwIfNotAuthorizerEnabled(); } - executeSql(sql, bindArgs); + executeSql(authorizer, sql, bindArgs); } /** {@hide} */ - public int executeSql(String sql, Object[] bindArgs) throws SQLException { + public int executeSql(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql, + @Nullable Object[] bindArgs) throws SQLException { acquireReference(); try { final int statementType = DatabaseUtils.getSqlStatementType(sql); @@ -2151,7 +2286,7 @@ public final class SQLiteDatabase extends SQLiteClosable { } } - try (SQLiteStatement statement = new SQLiteStatement(this, sql, bindArgs)) { + try (SQLiteStatement statement = new SQLiteStatement(this, authorizer, sql, bindArgs)) { return statement.executeUpdateDelete(); } finally { // If schema was updated, close non-primary connections, otherwise they might @@ -2169,19 +2304,37 @@ public final class SQLiteDatabase extends SQLiteClosable { * Return a {@link SQLiteRawStatement} connected to the database. A transaction must be in * progress or an exception will be thrown. The resulting object will be closed automatically * when the current transaction closes. + * Must configure: {@link OpenParams.Builder#setAuthorizerSupportEnabled(boolean)}. * @param sql The SQL string to be compiled into a prepared statement. + * @param authorizer The sql authorizer attached to the statement. * @return A {@link SQLiteRawStatement} holding the compiled SQL. * @throws IllegalStateException if a transaction is not in progress. * @throws SQLiteException if the SQL cannot be compiled. * @hide */ @NonNull - public SQLiteRawStatement createRawStatement(@NonNull String sql) { + public SQLiteRawStatement createRawStatement(@NonNull String sql, + @NonNull SQLiteAuthorizer authorizer) { Objects.requireNonNull(sql); - return new SQLiteRawStatement(this, sql); + return new SQLiteRawStatement(this, sql, authorizer); } /** + * Return a {@link SQLiteRawStatement} connected to the database. A transaction must be in + * progress or an exception will be thrown. The resulting object will be closed automatically + * when the current transaction closes. + * @param sql The SQL string to be compiled into a prepared statement. + * @return A {@link SQLiteRawStatement} holding the compiled SQL. + * @throws IllegalStateException if a transaction is not in progress. + * @throws SQLiteException if the SQL cannot be compiled. + * @hide + */ + @NonNull + public SQLiteRawStatement createRawStatement(@NonNull String sql) { + Objects.requireNonNull(sql); + return createRawStatement(sql, null); + } + /** * Return the "rowid" of the last row to be inserted on the current connection. See the * SQLite documentation for the specific details. This method must only be called when inside * a transaction. {@link IllegalStateException} is thrown if the method is called outside a @@ -2206,7 +2359,33 @@ public final class SQLiteDatabase extends SQLiteClosable { * @throws SQLiteException if {@code sql} is invalid */ public void validateSql(@NonNull String sql, @Nullable CancellationSignal cancellationSignal) { - getThreadSession().prepare(sql, + validateSqlInternal(sql, cancellationSignal, null); + } + + /** + * Verifies that a SQL SELECT statement is valid by compiling it. + * If the SQL statement is not valid, this method will throw a {@link SQLiteException}. + * Must configure: {@link OpenParams.Builder#setAuthorizerSupportEnabled(boolean)}. + * + * @param sql SQL to be validated + * @param cancellationSignal A signal to cancel the operation in progress, or null if none. + * If the operation is canceled, then {@link OperationCanceledException} will be thrown + * when the query is executed. + * @param authorizer The sql authorizer attached to the query. + * @throws SQLiteException if {@code sql} is invalid + */ + public void validateSql(@NonNull String sql, @Nullable CancellationSignal cancellationSignal, + @NonNull SQLiteAuthorizer authorizer) { + validateSqlInternal(sql, cancellationSignal, authorizer); + } + + private void validateSqlInternal(@NonNull String sql, + @Nullable CancellationSignal cancellationSignal, + @Nullable SQLiteAuthorizer authorizer) { + if (authorizer != null) { + throwIfNotAuthorizerEnabled(); + } + getThreadSession().prepare(authorizer, sql, getThreadDefaultConnectionFlags(/* readOnly =*/ true), cancellationSignal, null); } @@ -2226,6 +2405,21 @@ public final class SQLiteDatabase extends SQLiteClosable { } /** + * Returns true if the database is opened with authorizer support enabled. + * + * @return True if authorizer support is enabled. + */ + public boolean isAuthorizerSupportEnabled() { + synchronized (mLock) { + return isAuthorizerSupportEnabledLocked(); + } + } + + private boolean isAuthorizerSupportEnabledLocked() { + return (mConfigurationLocked.openFlags & ENABLE_AUTHORIZER) != 0; + } + + /** * Returns true if the database is in-memory db. * * @return True if the database is in-memory. @@ -2810,6 +3004,13 @@ public final class SQLiteDatabase extends SQLiteClosable { return "SQLiteDatabase: " + getPath(); } + private void throwIfNotAuthorizerEnabled() { + if ((mConfigurationLocked.openFlags & ENABLE_AUTHORIZER) == 0) { + throw new IllegalStateException("The database '" + mConfigurationLocked.label + + "' does not have authorizer support enabled."); + } + } + private void throwIfNotOpenLocked() { if (mConnectionPoolLocked == null) { throw new IllegalStateException("The database '" + mConfigurationLocked.label @@ -3074,6 +3275,39 @@ public final class SQLiteDatabase extends SQLiteClosable { } /** + * Returns if support for {@link SQLiteAuthorizer} is enabled. + * + * @see SQLiteDatabase#compileStatement(String, SQLiteAuthorizer) + * @see SQLiteDatabase#execSQL(String, Object[], SQLiteAuthorizer) + * @see SQLiteDatabase#validateSql(String, CancellationSignal, + * SQLiteAuthorizer) + * @see SQLiteDatabase#rawQueryWithFactory(CursorFactory, String, + * String[], String, CancellationSignal, SQLiteAuthorizer) + */ + public boolean isAuthorizerSupportEnabled() { + return (mOpenFlags & ENABLE_AUTHORIZER) != 0; + } + + /** + * Enables or disables support for {@link SQLiteAuthorizer}. + * + * @see SQLiteDatabase#compileStatement(String, SQLiteAuthorizer) + * @see SQLiteDatabase#execSQL(String, Object[], SQLiteAuthorizer) + * @see SQLiteDatabase#validateSql(String, CancellationSignal, + * SQLiteAuthorizer) + * @see SQLiteDatabase#rawQueryWithFactory(CursorFactory, String, + * String[], String, CancellationSignal, SQLiteAuthorizer) + */ + public @NonNull Builder setAuthorizerSupportEnabled(boolean enabled) { + if (enabled) { + addOpenFlags(SQLiteDatabase.ENABLE_AUTHORIZER); + } else { + removeOpenFlags(SQLiteDatabase.ENABLE_AUTHORIZER); + } + return this; + } + + /** * Set an optional factory class that is called to instantiate a cursor when query * is called. * @@ -3174,7 +3408,8 @@ public final class SQLiteDatabase extends SQLiteClosable { OPEN_READONLY, CREATE_IF_NECESSARY, NO_LOCALIZED_COLLATORS, - ENABLE_WRITE_AHEAD_LOGGING + ENABLE_WRITE_AHEAD_LOGGING, + ENABLE_AUTHORIZER }) @Retention(RetentionPolicy.SOURCE) public @interface DatabaseOpenFlags {} diff --git a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java index bc6368639baa..63b59f77845d 100644 --- a/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java +++ b/core/java/android/database/sqlite/SQLiteDatabaseConfiguration.java @@ -17,9 +17,11 @@ package android.database.sqlite; import android.compat.annotation.UnsupportedAppUsage; +import android.database.sqlite.SQLiteDebug.NoPreloadHolder; import android.text.TextUtils; import android.util.ArrayMap; import android.util.Pair; + import java.util.ArrayList; import java.util.Locale; import java.util.function.BinaryOperator; @@ -155,6 +157,13 @@ public final class SQLiteDatabaseConfiguration { throw new IllegalArgumentException("path must not be null."); } + if (NoPreloadHolder.DEBUG_SQL_STATEMENTS) { + openFlags |= SQLiteDatabase.ENABLE_PROFILE; + } + if (NoPreloadHolder.DEBUG_SQL_TIME) { + openFlags |= SQLiteDatabase.ENABLE_TRACE; + } + this.path = path; label = stripPathForLogs(path); this.openFlags = openFlags; diff --git a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java index 1721e0c69dc3..8be11d042fe9 100644 --- a/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java +++ b/core/java/android/database/sqlite/SQLiteDirectCursorDriver.java @@ -16,6 +16,8 @@ package android.database.sqlite; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.annotation.TestApi; import android.database.Cursor; import android.database.sqlite.SQLiteDatabase.CursorFactory; @@ -29,6 +31,7 @@ import android.os.CancellationSignal; @TestApi public final class SQLiteDirectCursorDriver implements SQLiteCursorDriver { private final SQLiteDatabase mDatabase; + private final SQLiteAuthorizer mAuthorizer; private final String mEditTable; private final String mSql; private final CancellationSignal mCancellationSignal; @@ -36,14 +39,22 @@ public final class SQLiteDirectCursorDriver implements SQLiteCursorDriver { public SQLiteDirectCursorDriver(SQLiteDatabase db, String sql, String editTable, CancellationSignal cancellationSignal) { + this(db, null, sql, editTable, cancellationSignal); + } + + public SQLiteDirectCursorDriver(@NonNull SQLiteDatabase db, + @Nullable SQLiteAuthorizer authorizer, @NonNull String sql, + @Nullable String editTable, @Nullable CancellationSignal cancellationSignal) { mDatabase = db; + mAuthorizer = authorizer; mEditTable = editTable; mSql = sql; mCancellationSignal = cancellationSignal; } public Cursor query(CursorFactory factory, String[] selectionArgs) { - final SQLiteQuery query = new SQLiteQuery(mDatabase, mSql, mCancellationSignal); + final SQLiteQuery query = new SQLiteQuery(mDatabase, + mAuthorizer, mSql, mCancellationSignal); final Cursor cursor; try { query.bindAllArgsAsStrings(selectionArgs); diff --git a/core/java/android/database/sqlite/SQLiteProgram.java b/core/java/android/database/sqlite/SQLiteProgram.java index cd4131ce2abb..84b2a8a1b8aa 100644 --- a/core/java/android/database/sqlite/SQLiteProgram.java +++ b/core/java/android/database/sqlite/SQLiteProgram.java @@ -16,6 +16,8 @@ package android.database.sqlite; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.database.DatabaseUtils; import android.os.Build; @@ -33,6 +35,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { private static final String[] EMPTY_STRING_ARRAY = new String[0]; private final SQLiteDatabase mDatabase; + private final SQLiteAuthorizer mAuthorizer; @UnsupportedAppUsage private final String mSql; private final boolean mReadOnly; @@ -41,9 +44,11 @@ public abstract class SQLiteProgram extends SQLiteClosable { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) private final Object[] mBindArgs; - SQLiteProgram(SQLiteDatabase db, String sql, Object[] bindArgs, - CancellationSignal cancellationSignalForPrepare) { + SQLiteProgram(@NonNull SQLiteDatabase db, @Nullable SQLiteAuthorizer authorizer, + @NonNull String sql, @Nullable Object[] bindArgs, + @Nullable CancellationSignal cancellationSignalForPrepare) { mDatabase = db; + mAuthorizer = authorizer; mSql = sql.trim(); int n = DatabaseUtils.getSqlStatementType(mSql); @@ -59,7 +64,7 @@ public abstract class SQLiteProgram extends SQLiteClosable { default: boolean assumeReadOnly = (n == DatabaseUtils.STATEMENT_SELECT); SQLiteStatementInfo info = new SQLiteStatementInfo(); - db.getThreadSession().prepare(mSql, + db.getThreadSession().prepare(mAuthorizer, mSql, db.getThreadDefaultConnectionFlags(assumeReadOnly), cancellationSignalForPrepare, info); mReadOnly = info.readOnly; @@ -88,6 +93,10 @@ public abstract class SQLiteProgram extends SQLiteClosable { return mDatabase; } + final SQLiteAuthorizer getAuthorizer() { + return mAuthorizer; + } + final String getSql() { return mSql; } diff --git a/core/java/android/database/sqlite/SQLiteQuery.java b/core/java/android/database/sqlite/SQLiteQuery.java index 62bcc2060d2a..1d23193c68cf 100644 --- a/core/java/android/database/sqlite/SQLiteQuery.java +++ b/core/java/android/database/sqlite/SQLiteQuery.java @@ -16,6 +16,8 @@ package android.database.sqlite; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.database.CursorWindow; import android.os.CancellationSignal; import android.os.OperationCanceledException; @@ -33,8 +35,9 @@ public final class SQLiteQuery extends SQLiteProgram { private final CancellationSignal mCancellationSignal; - SQLiteQuery(SQLiteDatabase db, String query, CancellationSignal cancellationSignal) { - super(db, query, null, cancellationSignal); + SQLiteQuery(@NonNull SQLiteDatabase db, @Nullable SQLiteAuthorizer authorizer, + @NonNull String query, @Nullable CancellationSignal cancellationSignal) { + super(db, authorizer, query, null, cancellationSignal); mCancellationSignal = cancellationSignal; } @@ -59,9 +62,9 @@ public final class SQLiteQuery extends SQLiteProgram { try { window.acquireReference(); try { - int numRows = getSession().executeForCursorWindow(getSql(), getBindArgs(), - window, startPos, requiredPos, countAllRows, getConnectionFlags(), - mCancellationSignal); + int numRows = getSession().executeForCursorWindow(getAuthorizer(), getSql(), + getBindArgs(), window, startPos, requiredPos, countAllRows, + getConnectionFlags(), mCancellationSignal); return numRows; } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); diff --git a/core/java/android/database/sqlite/SQLiteRawStatement.java b/core/java/android/database/sqlite/SQLiteRawStatement.java index 6b43788cd479..6c1ec39fab9b 100644 --- a/core/java/android/database/sqlite/SQLiteRawStatement.java +++ b/core/java/android/database/sqlite/SQLiteRawStatement.java @@ -20,8 +20,6 @@ import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; -import com.android.internal.annotations.VisibleForTesting; - import dalvik.annotation.optimization.FastNative; import java.io.Closeable; @@ -138,14 +136,15 @@ public final class SQLiteRawStatement implements Closeable { * {@link IllegalStateException} if a transaction is not in progress. Clients should call * {@link SQLiteDatabase.createRawStatement} to create a new instance. */ - SQLiteRawStatement(@NonNull SQLiteDatabase db, @NonNull String sql) throws SQLiteException { + SQLiteRawStatement(@NonNull SQLiteDatabase db, @NonNull String sql, + @Nullable SQLiteAuthorizer authorizer) throws SQLiteException { mThread = Thread.currentThread(); mDatabase = db; mSession = mDatabase.getThreadSession(); mSession.throwIfNoTransaction(); mSql = sql; // Acquire a connection and prepare the statement. - mPreparedStatement = mSession.acquirePersistentStatement(mSql, this); + mPreparedStatement = mSession.acquirePersistentStatement(authorizer, mSql, this); mStatement = mPreparedStatement.mStatementPtr; } diff --git a/core/java/android/database/sqlite/SQLiteSession.java b/core/java/android/database/sqlite/SQLiteSession.java index 2379c849e534..936ff343a1f8 100644 --- a/core/java/android/database/sqlite/SQLiteSession.java +++ b/core/java/android/database/sqlite/SQLiteSession.java @@ -17,7 +17,7 @@ package android.database.sqlite; import android.annotation.NonNull; - +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.database.CursorWindow; import android.database.DatabaseUtils; @@ -80,7 +80,7 @@ import java.util.ArrayDeque; * specifying the desired transaction mode. Once an explicit transaction has begun, * all subsequent database operations will be performed as part of that transaction. * To end an explicit transaction, first call {@link #setTransactionSuccessful} if the - * transaction was successful, then call {@link #end}. If the transaction was + * transaction was successful, then call {@link #endTransaction}. If the transaction was * marked successful, its changes will be committed, otherwise they will be rolled back. * </p><p> * Explicit transactions can also be nested. A nested explicit transaction is @@ -309,12 +309,12 @@ public final class SQLiteSession { CancellationSignal cancellationSignal) { throwIfTransactionMarkedSuccessful(); beginTransactionUnchecked(transactionMode, transactionListener, connectionFlags, - cancellationSignal); + cancellationSignal); } private void beginTransactionUnchecked(int transactionMode, SQLiteTransactionListener transactionListener, int connectionFlags, - CancellationSignal cancellationSignal) { + @Nullable CancellationSignal cancellationSignal) { if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); } @@ -329,20 +329,21 @@ public final class SQLiteSession { // Execute SQL might throw a runtime exception. switch (transactionMode) { case TRANSACTION_MODE_IMMEDIATE: - mConnection.execute("BEGIN IMMEDIATE;", null, + mConnection.execute(null, "BEGIN IMMEDIATE;", null, cancellationSignal); // might throw break; case TRANSACTION_MODE_EXCLUSIVE: - mConnection.execute("BEGIN EXCLUSIVE;", null, + mConnection.execute(null, "BEGIN EXCLUSIVE;", null, cancellationSignal); // might throw break; case TRANSACTION_MODE_DEFERRED: - mConnection.execute("BEGIN DEFERRED;", null, + mConnection.execute(null, "BEGIN DEFERRED;", null, cancellationSignal); // might throw break; default: // Per SQLite documentation, this executes in DEFERRED mode. - mConnection.execute("BEGIN;", null, cancellationSignal); // might throw + mConnection.execute(null, "BEGIN;", + null, cancellationSignal); // might throw break; } } @@ -353,7 +354,8 @@ public final class SQLiteSession { transactionListener.onBegin(); // might throw } catch (RuntimeException ex) { if (mTransactionStack == null) { - mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw + mConnection.execute(null, "ROLLBACK;", null, + cancellationSignal); // might throw } throw ex; } @@ -415,14 +417,14 @@ public final class SQLiteSession { * @see #setTransactionSuccessful * @see #yieldTransaction */ - public void endTransaction(CancellationSignal cancellationSignal) { + public void endTransaction(@Nullable CancellationSignal cancellationSignal) { throwIfNoTransaction(); assert mConnection != null; - endTransactionUnchecked(cancellationSignal, false); } - private void endTransactionUnchecked(CancellationSignal cancellationSignal, boolean yielding) { + private void endTransactionUnchecked(@Nullable CancellationSignal cancellationSignal, + boolean yielding) { if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); } @@ -460,9 +462,11 @@ public final class SQLiteSession { try { if (successful) { - mConnection.execute("COMMIT;", null, cancellationSignal); // might throw + mConnection.execute(null, "COMMIT;", null, + cancellationSignal); // might throw } else { - mConnection.execute("ROLLBACK;", null, cancellationSignal); // might throw + mConnection.execute(null, "ROLLBACK;", null, + cancellationSignal); // might throw } } finally { releaseConnection(); // might throw @@ -599,8 +603,9 @@ public final class SQLiteSession { * @throws SQLiteException if an error occurs, such as a syntax error. * @throws OperationCanceledException if the operation was canceled. */ - public void prepare(String sql, int connectionFlags, CancellationSignal cancellationSignal, - SQLiteStatementInfo outStatementInfo) { + public void prepare(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql, + int connectionFlags, @Nullable CancellationSignal cancellationSignal, + @Nullable SQLiteStatementInfo outStatementInfo) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -611,7 +616,7 @@ public final class SQLiteSession { acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { - mConnection.prepare(sql, outStatementInfo); // might throw + mConnection.prepare(authorizer, sql, outStatementInfo); // might throw } finally { releaseConnection(); // might throw } @@ -630,19 +635,20 @@ public final class SQLiteSession { * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public void execute(String sql, Object[] bindArgs, int connectionFlags, - CancellationSignal cancellationSignal) { + public void execute(@Nullable SQLiteAuthorizer authorizer, + @NonNull String sql, @Nullable Object[] bindArgs, int connectionFlags, + @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { + if (executeSpecial(sql, connectionFlags, cancellationSignal)) { return; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { - mConnection.execute(sql, bindArgs, cancellationSignal); // might throw + mConnection.execute(authorizer, sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -663,19 +669,21 @@ public final class SQLiteSession { * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public long executeForLong(String sql, Object[] bindArgs, int connectionFlags, - CancellationSignal cancellationSignal) { + public long executeForLong(@Nullable SQLiteAuthorizer authorizer, + @NonNull String sql, @Nullable Object[] bindArgs, int connectionFlags, + @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { + if (executeSpecial(sql, connectionFlags, cancellationSignal)) { return 0; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { - return mConnection.executeForLong(sql, bindArgs, cancellationSignal); // might throw + return mConnection.executeForLong(authorizer, sql, bindArgs, + cancellationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -696,19 +704,21 @@ public final class SQLiteSession { * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public String executeForString(String sql, Object[] bindArgs, int connectionFlags, - CancellationSignal cancellationSignal) { + public String executeForString(@Nullable SQLiteAuthorizer authorizer, + @NonNull String sql, @Nullable Object[] bindArgs, int connectionFlags, + @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { + if (executeSpecial(sql, connectionFlags, cancellationSignal)) { return null; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { - return mConnection.executeForString(sql, bindArgs, cancellationSignal); // might throw + return mConnection.executeForString(authorizer, sql, bindArgs, + cancellationSignal); // might throw } finally { releaseConnection(); // might throw } @@ -731,19 +741,20 @@ public final class SQLiteSession { * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public ParcelFileDescriptor executeForBlobFileDescriptor(String sql, Object[] bindArgs, - int connectionFlags, CancellationSignal cancellationSignal) { + public ParcelFileDescriptor executeForBlobFileDescriptor(@Nullable SQLiteAuthorizer authorizer, + @NonNull String sql, @Nullable Object[] bindArgs, int connectionFlags, + @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { + if (executeSpecial(sql, connectionFlags, cancellationSignal)) { return null; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { - return mConnection.executeForBlobFileDescriptor(sql, bindArgs, + return mConnection.executeForBlobFileDescriptor(authorizer, sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw @@ -765,19 +776,20 @@ public final class SQLiteSession { * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public int executeForChangedRowCount(String sql, Object[] bindArgs, int connectionFlags, - CancellationSignal cancellationSignal) { + public int executeForChangedRowCount(@Nullable SQLiteAuthorizer authorizer, + @NonNull String sql, @Nullable Object[] bindArgs, int connectionFlags, + @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { + if (executeSpecial(sql, connectionFlags, cancellationSignal)) { return 0; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { - return mConnection.executeForChangedRowCount(sql, bindArgs, + return mConnection.executeForChangedRowCount(authorizer, sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw @@ -799,19 +811,20 @@ public final class SQLiteSession { * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public long executeForLastInsertedRowId(String sql, Object[] bindArgs, int connectionFlags, - CancellationSignal cancellationSignal) { + public long executeForLastInsertedRowId(@Nullable SQLiteAuthorizer authorizer, + @NonNull String sql, @Nullable Object[] bindArgs, int connectionFlags, + @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { + if (executeSpecial(sql, connectionFlags, cancellationSignal)) { return 0; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { - return mConnection.executeForLastInsertedRowId(sql, bindArgs, + return mConnection.executeForLastInsertedRowId(authorizer, sql, bindArgs, cancellationSignal); // might throw } finally { releaseConnection(); // might throw @@ -842,9 +855,10 @@ public final class SQLiteSession { * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - public int executeForCursorWindow(String sql, Object[] bindArgs, - CursorWindow window, int startPos, int requiredPos, boolean countAllRows, - int connectionFlags, CancellationSignal cancellationSignal) { + public int executeForCursorWindow(@Nullable SQLiteAuthorizer authorizer, @NonNull String sql, + @Nullable Object[] bindArgs, @NonNull CursorWindow window, int startPos, + int requiredPos, boolean countAllRows, int connectionFlags, + @Nullable CancellationSignal cancellationSignal) { if (sql == null) { throw new IllegalArgumentException("sql must not be null."); } @@ -852,14 +866,14 @@ public final class SQLiteSession { throw new IllegalArgumentException("window must not be null."); } - if (executeSpecial(sql, bindArgs, connectionFlags, cancellationSignal)) { + if (executeSpecial(sql, connectionFlags, cancellationSignal)) { window.clear(); return 0; } acquireConnection(sql, connectionFlags, cancellationSignal); // might throw try { - return mConnection.executeForCursorWindow(sql, bindArgs, + return mConnection.executeForCursorWindow(authorizer, sql, bindArgs, window, startPos, requiredPos, countAllRows, cancellationSignal); // might throw } finally { @@ -888,8 +902,8 @@ public final class SQLiteSession { * or invalid number of bind arguments. * @throws OperationCanceledException if the operation was canceled. */ - private boolean executeSpecial(String sql, Object[] bindArgs, int connectionFlags, - CancellationSignal cancellationSignal) { + private boolean executeSpecial(@NonNull String sql, int connectionFlags, + @Nullable CancellationSignal cancellationSignal) { if (cancellationSignal != null) { cancellationSignal.throwIfCanceled(); } @@ -942,13 +956,14 @@ public final class SQLiteSession { * method is called when the transaction is closed. */ @NonNull - SQLiteConnection.PreparedStatement acquirePersistentStatement(@NonNull String query, - @NonNull Closeable dependent) { + SQLiteConnection.PreparedStatement acquirePersistentStatement( + @Nullable SQLiteAuthorizer authorizer, + @NonNull String query, @NonNull Closeable dependent) { throwIfNoTransaction(); throwIfTransactionMarkedSuccessful(); mOpenDependents.addFirst(dependent); try { - return mConnection.acquirePersistentStatement(query); + return mConnection.acquirePersistentStatement(authorizer, query); } catch (Throwable e) { mOpenDependents.remove(dependent); throw e; diff --git a/core/java/android/database/sqlite/SQLiteStatement.java b/core/java/android/database/sqlite/SQLiteStatement.java index acdc0fac0e98..82023c79270f 100644 --- a/core/java/android/database/sqlite/SQLiteStatement.java +++ b/core/java/android/database/sqlite/SQLiteStatement.java @@ -16,6 +16,8 @@ package android.database.sqlite; +import android.annotation.NonNull; +import android.annotation.Nullable; import android.compat.annotation.UnsupportedAppUsage; import android.os.Build; import android.os.ParcelFileDescriptor; @@ -32,7 +34,12 @@ import android.os.ParcelFileDescriptor; public final class SQLiteStatement extends SQLiteProgram { @UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553) SQLiteStatement(SQLiteDatabase db, String sql, Object[] bindArgs) { - super(db, sql, bindArgs, null); + super(db, null, sql, bindArgs, null); + } + + SQLiteStatement(@NonNull SQLiteDatabase db, @Nullable SQLiteAuthorizer authorizer, + @NonNull String sql, @Nullable Object[] bindArgs) { + super(db, authorizer, sql, bindArgs, null); } /** @@ -45,7 +52,8 @@ public final class SQLiteStatement extends SQLiteProgram { public void execute() { acquireReference(); try { - getSession().execute(getSql(), getBindArgs(), getConnectionFlags(), null); + getSession().execute( + getAuthorizer(), getSql(), getBindArgs(), getConnectionFlags(), null); } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); throw ex; @@ -66,7 +74,7 @@ public final class SQLiteStatement extends SQLiteProgram { acquireReference(); try { return getSession().executeForChangedRowCount( - getSql(), getBindArgs(), getConnectionFlags(), null); + getAuthorizer(), getSql(), getBindArgs(), getConnectionFlags(), null); } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); throw ex; @@ -88,7 +96,7 @@ public final class SQLiteStatement extends SQLiteProgram { acquireReference(); try { return getSession().executeForLastInsertedRowId( - getSql(), getBindArgs(), getConnectionFlags(), null); + getAuthorizer(), getSql(), getBindArgs(), getConnectionFlags(), null); } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); throw ex; @@ -109,7 +117,7 @@ public final class SQLiteStatement extends SQLiteProgram { acquireReference(); try { return getSession().executeForLong( - getSql(), getBindArgs(), getConnectionFlags(), null); + getAuthorizer(), getSql(), getBindArgs(), getConnectionFlags(), null); } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); throw ex; @@ -130,7 +138,7 @@ public final class SQLiteStatement extends SQLiteProgram { acquireReference(); try { return getSession().executeForString( - getSql(), getBindArgs(), getConnectionFlags(), null); + getAuthorizer(), getSql(), getBindArgs(), getConnectionFlags(), null); } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); throw ex; @@ -151,7 +159,7 @@ public final class SQLiteStatement extends SQLiteProgram { acquireReference(); try { return getSession().executeForBlobFileDescriptor( - getSql(), getBindArgs(), getConnectionFlags(), null); + getAuthorizer(), getSql(), getBindArgs(), getConnectionFlags(), null); } catch (SQLiteDatabaseCorruptException ex) { onCorruption(); throw ex; diff --git a/core/jni/android_database_SQLiteConnection.cpp b/core/jni/android_database_SQLiteConnection.cpp index 7e827a837dce..729c1c453388 100644 --- a/core/jni/android_database_SQLiteConnection.cpp +++ b/core/jni/android_database_SQLiteConnection.cpp @@ -59,6 +59,10 @@ namespace android { static const int BUSY_TIMEOUT_MS = 2500; static struct { + jmethodID onAuthorize; +} gAuthorizer; + +static struct { jmethodID apply; } gUnaryOperator; @@ -70,11 +74,14 @@ struct SQLiteConnection { // Open flags. // Must be kept in sync with the constants defined in SQLiteDatabase.java. enum { - OPEN_READWRITE = 0x00000000, - OPEN_READONLY = 0x00000001, - OPEN_READ_MASK = 0x00000001, - NO_LOCALIZED_COLLATORS = 0x00000010, - CREATE_IF_NECESSARY = 0x10000000, + OPEN_READWRITE = 0x00000000, + OPEN_READONLY = 0x00000001, + OPEN_READ_MASK = 0x00000001, + NO_LOCALIZED_COLLATORS = 0x00000010, + ENABLE_TRACE = 0x00000100, + ENABLE_PROFILE = 0x00000200, + ENABLE_AUTHORIZER = 0x00000400, + CREATE_IF_NECESSARY = 0x10000000, }; sqlite3* const db; @@ -83,9 +90,15 @@ struct SQLiteConnection { const String8 label; volatile bool canceled; - - SQLiteConnection(sqlite3* db, int openFlags, const String8& path, const String8& label) : - db(db), openFlags(openFlags), path(path), label(label), canceled(false) { } + volatile jobject authorizer; + + SQLiteConnection(sqlite3* db, int openFlags, const String8& path, const String8& label) + : db(db), + openFlags(openFlags), + path(path), + label(label), + canceled(false), + authorizer(NULL) {} }; // Called each time a statement begins execution, when tracing is enabled. @@ -108,10 +121,34 @@ static int sqliteProgressHandlerCallback(void* data) { return connection->canceled; } +static int sqliteAuthorizerCallback(void* data, int action, const char* arg3, const char* arg4, + const char* arg5, const char* arg6) { + SQLiteConnection* connection = static_cast<SQLiteConnection*>(data); + if (connection->authorizer) { + JNIEnv* env = AndroidRuntime::getJNIEnv(); + ScopedLocalRef<jobject> authorizerObj(env, env->NewLocalRef(connection->authorizer)); + ScopedLocalRef<jstring> arg3String(env, env->NewStringUTF(arg3)); + ScopedLocalRef<jstring> arg4String(env, env->NewStringUTF(arg4)); + ScopedLocalRef<jstring> arg5String(env, env->NewStringUTF(arg5)); + ScopedLocalRef<jstring> arg6String(env, env->NewStringUTF(arg6)); + int res = env->CallIntMethod(authorizerObj.get(), gAuthorizer.onAuthorize, action, + arg3String.get(), arg4String.get(), arg5String.get(), + arg6String.get()); + if (env->ExceptionCheck()) { + ALOGE("Exception thrown by authorizer"); + env->ExceptionDescribe(); + env->ExceptionClear(); + return SQLITE_DENY; + } else { + return res; + } + } else { + return SQLITE_OK; + } +} static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFlags, - jstring labelStr, jboolean enableTrace, jboolean enableProfile, jint lookasideSz, - jint lookasideCnt) { + jstring labelStr, jint lookasideSz, jint lookasideCnt) { int sqliteFlags; if (openFlags & SQLiteConnection::CREATE_IF_NECESSARY) { sqliteFlags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_CREATE; @@ -172,13 +209,16 @@ static jlong nativeOpen(JNIEnv* env, jclass clazz, jstring pathStr, jint openFla // Create wrapper object. SQLiteConnection* connection = new SQLiteConnection(db, openFlags, path, label); - // Enable tracing and profiling if requested. - if (enableTrace) { + // Enable optional features if requested. + if (openFlags & SQLiteConnection::ENABLE_TRACE) { sqlite3_trace(db, &sqliteTraceCallback, connection); } - if (enableProfile) { + if (openFlags & SQLiteConnection::ENABLE_PROFILE) { sqlite3_profile(db, &sqliteProfileCallback, connection); } + if (openFlags & SQLiteConnection::ENABLE_AUTHORIZER) { + sqlite3_set_authorizer(db, &sqliteAuthorizerCallback, connection); + } ALOGV("Opened connection %p with label '%s'", db, label.string()); return reinterpret_cast<jlong>(connection); @@ -188,6 +228,11 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) { SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr); if (connection) { + if (connection->authorizer) { + env->DeleteGlobalRef(connection->authorizer); + connection->authorizer = NULL; + } + ALOGV("Closing connection %p", connection->db); int err = sqlite3_close(connection->db); if (err != SQLITE_OK) { @@ -201,6 +246,20 @@ static void nativeClose(JNIEnv* env, jclass clazz, jlong connectionPtr) { } } +static void nativeSetAuthorizer(JNIEnv* env, jclass clazz, jlong connectionPtr, + jobject authorizerObj) { + SQLiteConnection* connection = reinterpret_cast<SQLiteConnection*>(connectionPtr); + + if (connection->authorizer) { + env->DeleteGlobalRef(connection->authorizer); + connection->authorizer = NULL; + } + if (authorizerObj) { + jobject authorizerObjGlobal = env->NewGlobalRef(authorizerObj); + connection->authorizer = authorizerObjGlobal; + } +} + static void sqliteCustomScalarFunctionCallback(sqlite3_context *context, int argc, sqlite3_value **argv) { JNIEnv* env = AndroidRuntime::getJNIEnv(); @@ -885,69 +944,53 @@ static jint nativeLastInsertRowId(JNIEnv* env, jclass, jlong connectionPtr) { return sqlite3_last_insert_rowid(connection->db); } -static const JNINativeMethod sMethods[] = -{ - /* name, signature, funcPtr */ - { "nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZII)J", - (void*)nativeOpen }, - { "nativeClose", "(J)V", - (void*)nativeClose }, - { "nativeRegisterCustomScalarFunction", "(JLjava/lang/String;Ljava/util/function/UnaryOperator;)V", - (void*)nativeRegisterCustomScalarFunction }, - { "nativeRegisterCustomAggregateFunction", "(JLjava/lang/String;Ljava/util/function/BinaryOperator;)V", - (void*)nativeRegisterCustomAggregateFunction }, - { "nativeRegisterLocalizedCollators", "(JLjava/lang/String;)V", - (void*)nativeRegisterLocalizedCollators }, - { "nativePrepareStatement", "(JLjava/lang/String;)J", - (void*)nativePrepareStatement }, - { "nativeFinalizeStatement", "(JJ)V", - (void*)nativeFinalizeStatement }, - { "nativeGetParameterCount", "(JJ)I", - (void*)nativeGetParameterCount }, - { "nativeIsReadOnly", "(JJ)Z", - (void*)nativeIsReadOnly }, - { "nativeGetColumnCount", "(JJ)I", - (void*)nativeGetColumnCount }, - { "nativeGetColumnName", "(JJI)Ljava/lang/String;", - (void*)nativeGetColumnName }, - { "nativeBindNull", "(JJI)V", - (void*)nativeBindNull }, - { "nativeBindLong", "(JJIJ)V", - (void*)nativeBindLong }, - { "nativeBindDouble", "(JJID)V", - (void*)nativeBindDouble }, - { "nativeBindString", "(JJILjava/lang/String;)V", - (void*)nativeBindString }, - { "nativeBindBlob", "(JJI[B)V", - (void*)nativeBindBlob }, - { "nativeResetStatementAndClearBindings", "(JJ)V", - (void*)nativeResetStatementAndClearBindings }, - { "nativeExecute", "(JJZ)V", - (void*)nativeExecute }, - { "nativeExecuteForLong", "(JJ)J", - (void*)nativeExecuteForLong }, - { "nativeExecuteForString", "(JJ)Ljava/lang/String;", - (void*)nativeExecuteForString }, - { "nativeExecuteForBlobFileDescriptor", "(JJ)I", - (void*)nativeExecuteForBlobFileDescriptor }, - { "nativeExecuteForChangedRowCount", "(JJ)I", - (void*)nativeExecuteForChangedRowCount }, - { "nativeExecuteForLastInsertedRowId", "(JJ)J", - (void*)nativeExecuteForLastInsertedRowId }, - { "nativeExecuteForCursorWindow", "(JJJIIZ)J", - (void*)nativeExecuteForCursorWindow }, - { "nativeGetDbLookaside", "(J)I", - (void*)nativeGetDbLookaside }, - { "nativeCancel", "(J)V", - (void*)nativeCancel }, - { "nativeResetCancel", "(JZ)V", - (void*)nativeResetCancel }, - - { "nativeLastInsertRowId", "(J)I", (void*) nativeLastInsertRowId } -}; +static const JNINativeMethod sMethods[] = { + /* name, signature, funcPtr */ + {"nativeOpen", "(Ljava/lang/String;ILjava/lang/String;II)J", (void*)nativeOpen}, + {"nativeClose", "(J)V", (void*)nativeClose}, + {"nativeSetAuthorizer", "(JLandroid/database/sqlite/SQLiteAuthorizer;)V", + (void*)nativeSetAuthorizer}, + {"nativeRegisterCustomScalarFunction", + "(JLjava/lang/String;Ljava/util/function/UnaryOperator;)V", + (void*)nativeRegisterCustomScalarFunction}, + {"nativeRegisterCustomAggregateFunction", + "(JLjava/lang/String;Ljava/util/function/BinaryOperator;)V", + (void*)nativeRegisterCustomAggregateFunction}, + {"nativeRegisterLocalizedCollators", "(JLjava/lang/String;)V", + (void*)nativeRegisterLocalizedCollators}, + {"nativePrepareStatement", "(JLjava/lang/String;)J", (void*)nativePrepareStatement}, + {"nativeFinalizeStatement", "(JJ)V", (void*)nativeFinalizeStatement}, + {"nativeGetParameterCount", "(JJ)I", (void*)nativeGetParameterCount}, + {"nativeIsReadOnly", "(JJ)Z", (void*)nativeIsReadOnly}, + {"nativeGetColumnCount", "(JJ)I", (void*)nativeGetColumnCount}, + {"nativeGetColumnName", "(JJI)Ljava/lang/String;", (void*)nativeGetColumnName}, + {"nativeBindNull", "(JJI)V", (void*)nativeBindNull}, + {"nativeBindLong", "(JJIJ)V", (void*)nativeBindLong}, + {"nativeBindDouble", "(JJID)V", (void*)nativeBindDouble}, + {"nativeBindString", "(JJILjava/lang/String;)V", (void*)nativeBindString}, + {"nativeBindBlob", "(JJI[B)V", (void*)nativeBindBlob}, + {"nativeResetStatementAndClearBindings", "(JJ)V", + (void*)nativeResetStatementAndClearBindings}, + {"nativeExecute", "(JJZ)V", (void*)nativeExecute}, + {"nativeExecuteForLong", "(JJ)J", (void*)nativeExecuteForLong}, + {"nativeExecuteForString", "(JJ)Ljava/lang/String;", (void*)nativeExecuteForString}, + {"nativeExecuteForBlobFileDescriptor", "(JJ)I", (void*)nativeExecuteForBlobFileDescriptor}, + {"nativeExecuteForChangedRowCount", "(JJ)I", (void*)nativeExecuteForChangedRowCount}, + {"nativeExecuteForLastInsertedRowId", "(JJ)J", (void*)nativeExecuteForLastInsertedRowId}, + {"nativeExecuteForCursorWindow", "(JJJIIZ)J", (void*)nativeExecuteForCursorWindow}, + {"nativeGetDbLookaside", "(J)I", (void*)nativeGetDbLookaside}, + {"nativeCancel", "(J)V", (void*)nativeCancel}, + {"nativeResetCancel", "(JZ)V", (void*)nativeResetCancel}, + + {"nativeLastInsertRowId", "(J)I", (void*)nativeLastInsertRowId}}; int register_android_database_SQLiteConnection(JNIEnv *env) { + jclass authorizerClazz = FindClassOrDie(env, "android/database/sqlite/SQLiteAuthorizer"); + gAuthorizer.onAuthorize = GetMethodIDOrDie(env, authorizerClazz, "onAuthorize", + "(ILjava/lang/String;Ljava/lang/String;Ljava/lang/" + "String;Ljava/lang/String;)I"); + jclass unaryClazz = FindClassOrDie(env, "java/util/function/UnaryOperator"); gUnaryOperator.apply = GetMethodIDOrDie(env, unaryClazz, "apply", "(Ljava/lang/Object;)Ljava/lang/Object;"); |