Index: jni/Android.mk ================================================================== --- jni/Android.mk +++ jni/Android.mk @@ -5,10 +5,11 @@ LOCAL_CFLAGS += -DNO_SUPPORT_JS_BINDING -DQT_NO_WHEELEVENT -DKHTML_NO_XBL LOCAL_CFLAGS += -U__APPLE__ LOCAL_CFLAGS += -Wno-unused-parameter -Wno-int-to-pointer-cast LOCAL_CFLAGS += -Wno-maybe-uninitialized -Wno-parentheses LOCAL_CPPFLAGS += -Wno-conversion-null + ifeq ($(TARGET_ARCH), arm) LOCAL_CFLAGS += -DPACKED="__attribute__ ((packed))" else LOCAL_CFLAGS += -DPACKED="" @@ -17,15 +18,23 @@ LOCAL_SRC_FILES:= \ android_database_SQLiteCommon.cpp \ android_database_SQLiteConnection.cpp \ android_database_SQLiteGlobal.cpp \ android_database_SQLiteDebug.cpp \ - JNIHelp.cpp JniConstants.cpp \ - sqlite3.c + JNIHelp.cpp JniConstants.cpp + +# +# For a SEE build, add the SEE sources to the tree and replace the line +# below with: +# +# LOCAL_SRC_FILES += sqlite3-see.c +# LOCAL_CFLAGS += -DSQLITE_HAS_CODEC +# +LOCAL_SRC_FILES += sqlite3.c LOCAL_C_INCLUDES += nativehelper/ LOCAL_MODULE:= libsqliteX LOCAL_LDLIBS += -ldl -llog include $(BUILD_SHARED_LIBRARY) Index: jni/android_database_SQLiteConnection.cpp ================================================================== --- jni/android_database_SQLiteConnection.cpp +++ jni/android_database_SQLiteConnection.cpp @@ -851,10 +851,34 @@ } else { sqlite3_progress_handler(connection->db, 0, NULL, NULL); } } +static void nativeSetSeeKey(JNIEnv *pEnv, jobject clazz, jint connectionPtr, jstring zKey){ + SQLiteConnection* p = reinterpret_cast(connectionPtr); + sqlite3_stmt *pStmt = 0; + int rc; + + const char *zUtf8 = pEnv->GetStringUTFChars(zKey, 0); + char *zSql = sqlite3_mprintf("PRAGMA key = '%q'", zUtf8); + + ALOG(LOG_ERROR, SQLITE_TRACE_TAG, "nativeSetSeeKey: %s", zSql); + + rc = sqlite3_prepare_v2(p->db, zSql, -1, &pStmt, 0); + if( rc==SQLITE_OK ){ + sqlite3_step(pStmt); + rc = sqlite3_finalize(pStmt); + } + + pEnv->ReleaseStringUTFChars(zKey, zUtf8); + sqlite3_free(zSql); + + if( rc!=SQLITE_OK ){ + char *zErr = sqlite3_mprintf("nativeSetSeeKey %d \"%s\"", rc, sqlite3_errmsg(p->db)); + throw_sqlite3_exception(pEnv, p->db, zErr); + } +} static JNINativeMethod sMethods[] = { /* name, signature, funcPtr */ { "nativeOpen", "(Ljava/lang/String;ILjava/lang/String;ZZ)I", @@ -907,10 +931,12 @@ (void*)nativeGetDbLookaside }, { "nativeCancel", "(I)V", (void*)nativeCancel }, { "nativeResetCancel", "(IZ)V", (void*)nativeResetCancel }, + + { "nativeSetSeeKey", "(ILjava/lang/String;)V", (void*)nativeSetSeeKey }, }; #define FIND_CLASS(var, className) \ var = env->FindClass(className); \ LOG_FATAL_IF(! var, "Unable to find class " className); Index: src/org/sqlite/app/customsqlite/CustomSqlite.java ================================================================== --- src/org/sqlite/app/customsqlite/CustomSqlite.java +++ src/org/sqlite/app/customsqlite/CustomSqlite.java @@ -5,25 +5,42 @@ import android.os.Bundle; import android.util.Log; import android.view.View; import android.widget.TextView; +import java.io.File; +import java.io.FileInputStream; +import java.util.Arrays; + +import java.lang.InterruptedException; + import org.sqlite.database.sqlite.SQLiteDatabase; import org.sqlite.database.sqlite.SQLiteStatement; +import org.sqlite.database.sqlite.SQLiteDatabaseCorruptException; import android.database.Cursor; /* import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteStatement; */ + +import org.sqlite.database.DatabaseErrorHandler; +class DoNotDeleteErrorHandler implements DatabaseErrorHandler { + private static final String TAG = "DoNotDeleteErrorHandler"; + public void onCorruption(SQLiteDatabase dbObj) { + Log.e(TAG, "Corruption reported by sqlite on database: " + dbObj.getPath()); + } +} public class CustomSqlite extends Activity { private TextView myTV; /* Text view widget */ private int myNTest; /* Number of tests attempted */ private int myNErr; /* Number of tests failed */ + + File DB_PATH; /** Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState){ super.onCreate(savedInstanceState); @@ -58,16 +75,65 @@ myTV.append("FAILED\n"); myTV.append(" res= \"" + res + "\"\n"); myTV.append(" expected=\"" + expected + "\"\n"); } } + + /* + ** Test if the database at DB_PATH is encrypted or not. The db + ** is assumed to be encrypted if the first 6 bytes are anything + ** other than "SQLite". + ** + ** If the test reveals that the db is encrypted, return the string + ** "encrypted". Otherwise, "unencrypted". + */ + public String db_is_encrypted() throws Exception { + FileInputStream in = new FileInputStream(DB_PATH); + + byte[] buffer = new byte[6]; + in.read(buffer, 0, 6); + + String res = "encrypted"; + if( Arrays.equals(buffer, (new String("SQLite")).getBytes()) ){ + res = "unencrypted"; + } + return res; + } + + /* + ** Test that a database connection may be accessed from a second thread. + */ + public void thread_test_1(){ + SQLiteDatabase.deleteDatabase(DB_PATH); + final SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH, null); + + String db_path2 = DB_PATH.toString() + "2"; + + db.execSQL("CREATE TABLE t1(x, y)"); + db.execSQL("INSERT INTO t1 VALUES (1, 2), (3, 4)"); + + Thread t = new Thread( new Runnable() { + public void run() { + SQLiteStatement st = db.compileStatement("SELECT sum(x+y) FROM t1"); + String res = st.simpleQueryForString(); + test_result("thread_test_1", res, "10"); + } + }); + + t.start(); + try { + t.join(); + } catch (InterruptedException e) { + } + } /* ** Use a Cursor to loop through the results of a SELECT query. */ - public void csr_test_1(){ - SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(":memory:", null); + public void csr_test_1() throws Exception { + SQLiteDatabase.deleteDatabase(DB_PATH); + SQLiteDatabase db = SQLiteDatabase.openOrCreateDatabase(DB_PATH, null); String res = ""; db.execSQL("CREATE TABLE t1(x)"); db.execSQL("INSERT INTO t1 VALUES ('one'), ('two'), ('three')"); @@ -79,24 +145,82 @@ res = res + "." + x; } }else{ test_warning("csr_test_1", "c==NULL"); } + test_result("csr_test_1.1", res, ".one.two.three"); + + db.close(); + test_result("csr_test_1.2", db_is_encrypted(), "unencrypted"); + } + + public String string_from_t1_x(SQLiteDatabase db){ + String res = ""; + + Cursor c = db.rawQuery("SELECT x FROM t1", null); + boolean bRes; + for(bRes=c.moveToFirst(); bRes; bRes=c.moveToNext()){ + String x = c.getString(0); + res = res + "." + x; + } + + return res; + } + + /* + ** Check that using openSeeDatabase() creates encrypted databases. + */ + public void see_test_1() throws Exception { + SQLiteDatabase.deleteDatabase(DB_PATH); + String res = ""; + SQLiteDatabase db = SQLiteDatabase.openSeeDatabase( + DB_PATH.getPath(), "secretkey", null, SQLiteDatabase.CREATE_IF_NECESSARY, null + ); + + db.execSQL("CREATE TABLE t1(x)"); + db.execSQL("INSERT INTO t1 VALUES ('one'), ('two'), ('three')"); + + res = string_from_t1_x(db); + test_result("see_test_1.1", res, ".one.two.three"); + db.close(); + + test_result("see_test_1.2", db_is_encrypted(), "encrypted"); + + db = SQLiteDatabase.openSeeDatabase( + DB_PATH.getPath(), "secretkey", null, SQLiteDatabase.CREATE_IF_NECESSARY, null + ); + res = string_from_t1_x(db); + test_result("see_test_1.3", res, ".one.two.three"); + db.close(); - test_result("csr_test_1", res, ".one.two.three"); + res = "unencrypted"; + try { + db = SQLiteDatabase.openDatabase( + DB_PATH.getPath(), null, SQLiteDatabase.CREATE_IF_NECESSARY, new DoNotDeleteErrorHandler() + ); + db.close(); + } catch ( SQLiteDatabaseCorruptException e ){ + res = "encrypted"; + } + test_result("see_test_1.4", res, "encrypted"); + } public void run_the_tests(View view){ System.loadLibrary("sqliteX"); + DB_PATH = getApplicationContext().getDatabasePath("test.db"); + DB_PATH.mkdirs(); myTV.setText(""); myNErr = 0; myNTest = 0; try { report_version(); csr_test_1(); + thread_test_1(); + see_test_1(); myTV.append("\n" + myNErr + " errors from " + myNTest + " tests\n"); } catch(Exception e) { myTV.append("Exception: " + e.toString() + "\n"); myTV.append(android.util.Log.getStackTraceString(e) + "\n"); Index: src/org/sqlite/database/sqlite/SQLiteConnection.java ================================================================== --- src/org/sqlite/database/sqlite/SQLiteConnection.java +++ src/org/sqlite/database/sqlite/SQLiteConnection.java @@ -155,10 +155,11 @@ int connectionPtr, int statementPtr, CursorWindow win, int startPos, int requiredPos, boolean countAllRows); private static native int nativeGetDbLookaside(int connectionPtr); private static native void nativeCancel(int connectionPtr); private static native void nativeResetCancel(int connectionPtr, boolean cancelable); + private static native void nativeSetSeeKey(int connectionPtr, String key); private SQLiteConnection(SQLiteConnectionPool pool, SQLiteDatabaseConfiguration configuration, int connectionId, boolean primaryConnection) { mPool = pool; @@ -208,10 +209,15 @@ private void open() { mConnectionPtr = nativeOpen(mConfiguration.path, mConfiguration.openFlags, mConfiguration.label, SQLiteDebug.DEBUG_SQL_STATEMENTS, SQLiteDebug.DEBUG_SQL_TIME); + + if( mConfiguration.bSee ){ + nativeSetSeeKey(mConnectionPtr, mConfiguration.seekey); + mConfiguration.setSeeKey(null); + } setPageSize(); setForeignKeyModeFromConfiguration(); setWalModeFromConfiguration(); setJournalSizeLimit(); @@ -418,11 +424,11 @@ // Update configuration parameters. mConfiguration.updateParametersFrom(configuration); // Update prepared statement cache size. - mPreparedStatementCache.resize(configuration.maxSqlCacheSize); + /* mPreparedStatementCache.resize(configuration.maxSqlCacheSize); */ // Update foreign key mode. if (foreignKeyModeChanged) { setForeignKeyModeFromConfiguration(); } Index: src/org/sqlite/database/sqlite/SQLiteConnectionPool.java ================================================================== --- src/org/sqlite/database/sqlite/SQLiteConnectionPool.java +++ src/org/sqlite/database/sqlite/SQLiteConnectionPool.java @@ -182,10 +182,12 @@ private void open() { // Open the primary connection. // This might throw if the database is corrupt. mAvailablePrimaryConnection = openConnectionLocked(mConfiguration, true /*primaryConnection*/); // might throw + + mConfiguration.setSeeKey(null); // Mark the pool as being open for business. mIsOpen = true; mCloseGuard.open("close"); } Index: src/org/sqlite/database/sqlite/SQLiteDatabase.java ================================================================== --- src/org/sqlite/database/sqlite/SQLiteDatabase.java +++ src/org/sqlite/database/sqlite/SQLiteDatabase.java @@ -693,10 +693,24 @@ DatabaseErrorHandler errorHandler) { SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler); db.open(); return db; } + + public static SQLiteDatabase openSeeDatabase( + String path, + String key, + CursorFactory factory, + int flags, + DatabaseErrorHandler errorHandler + ){ + SQLiteDatabase db = new SQLiteDatabase(path, flags, factory, errorHandler); + db.mConfigurationLocked.setSeeKey(key); + db.open(); + db.mConfigurationLocked.setSeeKey(null); + return db; + } /** * Equivalent to openDatabase(file.getPath(), factory, CREATE_IF_NECESSARY). */ public static SQLiteDatabase openOrCreateDatabase(File file, CursorFactory factory) { @@ -715,10 +729,11 @@ */ public static SQLiteDatabase openOrCreateDatabase(String path, CursorFactory factory, DatabaseErrorHandler errorHandler) { return openDatabase(path, factory, CREATE_IF_NECESSARY, errorHandler); } + /** * Deletes a database including its journal file and other auxiliary files * that may have been created by the database engine. * @@ -734,10 +749,12 @@ deleted |= file.delete(); deleted |= new File(file.getPath() + "-journal").delete(); deleted |= new File(file.getPath() + "-shm").delete(); deleted |= new File(file.getPath() + "-wal").delete(); + /* TODO: This is throwing a NullPointerException. Do not know why. */ + /* File dir = file.getParentFile(); if (dir != null) { final String prefix = file.getName() + "-mj"; final FileFilter filter = new FileFilter() { @Override @@ -747,10 +764,11 @@ }; for (File masterJournal : dir.listFiles(filter)) { deleted |= masterJournal.delete(); } } + */ return deleted; } /** * Reopens the database in read-write mode. Index: src/org/sqlite/database/sqlite/SQLiteDatabaseConfiguration.java ================================================================== --- src/org/sqlite/database/sqlite/SQLiteDatabaseConfiguration.java +++ src/org/sqlite/database/sqlite/SQLiteDatabaseConfiguration.java @@ -87,10 +87,25 @@ * The custom functions to register. */ public final ArrayList customFunctions = new ArrayList(); + + /* + ** The following two variables are used by the SEE extension. + */ + public boolean bSee; + public String seekey; + + /* + ** Function to set SEE related variables. + */ + public void setSeeKey(String key){ + bSee = true; + seekey = key; + } + /** * Creates a database configuration with the required parameters for opening a * database and default values for all other parameters. * * @param path The database path. @@ -106,10 +121,14 @@ this.openFlags = openFlags; // Set default values for optional parameters. maxSqlCacheSize = 25; locale = Locale.getDefault(); + + // Set default values for SEE parameters (default is unencrypted). + bSee = false; + seekey = null; } /** * Creates a database configuration as a copy of another configuration. * @@ -121,10 +140,13 @@ } this.path = other.path; this.label = other.label; updateParametersFrom(other); + + this.bSee = other.bSee; + this.seekey = other.seekey; } /** * Updates the non-immutable parameters of this configuration object * from the other configuration object.