Protect against SQLiteFullExceptions

Reviewed By: @kmagiera

Differential Revision: D2512317

fb-gh-sync-id: 93fd65ebd88e42b5afc4e06c0612576101f15c97
This commit is contained in:
Andrei Coman 2015-10-06 06:10:37 -07:00 committed by facebook-github-bot-4
parent b7b83e4f12
commit ea8d0b6c1f
2 changed files with 70 additions and 25 deletions

View File

@ -23,6 +23,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.WritableArray; import com.facebook.react.bridge.WritableArray;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.common.ReactConstants; import com.facebook.react.common.ReactConstants;
import com.facebook.react.common.SetBuilder; import com.facebook.react.common.SetBuilder;
import com.facebook.react.modules.common.ModuleDataCleaner; import com.facebook.react.modules.common.ModuleDataCleaner;
@ -137,8 +138,9 @@ public final class AsyncStorageModule
} while (cursor.moveToNext()); } while (cursor.moveToNext());
} }
} catch (Exception e) { } catch (Exception e) {
FLog.w(ReactConstants.TAG, "Exception in database multiGet ", e); FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()), null); callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()), null);
return;
} finally { } finally {
cursor.close(); cursor.close();
} }
@ -179,19 +181,20 @@ public final class AsyncStorageModule
String sql = "INSERT OR REPLACE INTO " + TABLE_CATALYST + " VALUES (?, ?);"; String sql = "INSERT OR REPLACE INTO " + TABLE_CATALYST + " VALUES (?, ?);";
SQLiteStatement statement = mReactDatabaseSupplier.get().compileStatement(sql); SQLiteStatement statement = mReactDatabaseSupplier.get().compileStatement(sql);
mReactDatabaseSupplier.get().beginTransaction(); WritableMap error = null;
try { try {
mReactDatabaseSupplier.get().beginTransaction();
for (int idx=0; idx < keyValueArray.size(); idx++) { for (int idx=0; idx < keyValueArray.size(); idx++) {
if (keyValueArray.getArray(idx).size() != 2) { if (keyValueArray.getArray(idx).size() != 2) {
callback.invoke(AsyncStorageErrorUtil.getInvalidValueError(null)); error = AsyncStorageErrorUtil.getInvalidValueError(null);
return; return;
} }
if (keyValueArray.getArray(idx).getString(0) == null) { if (keyValueArray.getArray(idx).getString(0) == null) {
callback.invoke(AsyncStorageErrorUtil.getInvalidKeyError(null)); error = AsyncStorageErrorUtil.getInvalidKeyError(null);
return; return;
} }
if (keyValueArray.getArray(idx).getString(1) == null) { if (keyValueArray.getArray(idx).getString(1) == null) {
callback.invoke(AsyncStorageErrorUtil.getInvalidValueError(null)); error = AsyncStorageErrorUtil.getInvalidValueError(null);
return; return;
} }
@ -202,13 +205,24 @@ public final class AsyncStorageModule
} }
mReactDatabaseSupplier.get().setTransactionSuccessful(); mReactDatabaseSupplier.get().setTransactionSuccessful();
} catch (Exception e) { } catch (Exception e) {
FLog.w(ReactConstants.TAG, "Exception in database multiSet ", e); FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage())); error = AsyncStorageErrorUtil.getError(null, e.getMessage());
} finally { } finally {
try {
mReactDatabaseSupplier.get().endTransaction(); mReactDatabaseSupplier.get().endTransaction();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
if (error == null) {
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
} }
}
}
if (error != null) {
callback.invoke(error);
} else {
callback.invoke(); callback.invoke();
} }
}
}.execute(); }.execute();
} }
@ -230,8 +244,9 @@ public final class AsyncStorageModule
return; return;
} }
mReactDatabaseSupplier.get().beginTransaction(); WritableMap error = null;
try { try {
mReactDatabaseSupplier.get().beginTransaction();
for (int keyStart = 0; keyStart < keys.size(); keyStart += MAX_SQL_KEYS) { for (int keyStart = 0; keyStart < keys.size(); keyStart += MAX_SQL_KEYS) {
int keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS); int keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS);
mReactDatabaseSupplier.get().delete( mReactDatabaseSupplier.get().delete(
@ -241,13 +256,24 @@ public final class AsyncStorageModule
} }
mReactDatabaseSupplier.get().setTransactionSuccessful(); mReactDatabaseSupplier.get().setTransactionSuccessful();
} catch (Exception e) { } catch (Exception e) {
FLog.w(ReactConstants.TAG, "Exception in database multiRemove ", e); FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage())); error = AsyncStorageErrorUtil.getError(null, e.getMessage());
} finally { } finally {
try {
mReactDatabaseSupplier.get().endTransaction(); mReactDatabaseSupplier.get().endTransaction();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
if (error == null) {
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
} }
}
}
if (error != null) {
callback.invoke(error);
} else {
callback.invoke(); callback.invoke();
} }
}
}.execute(); }.execute();
} }
@ -264,21 +290,22 @@ public final class AsyncStorageModule
callback.invoke(AsyncStorageErrorUtil.getDBError(null)); callback.invoke(AsyncStorageErrorUtil.getDBError(null));
return; return;
} }
mReactDatabaseSupplier.get().beginTransaction(); WritableMap error = null;
try { try {
mReactDatabaseSupplier.get().beginTransaction();
for (int idx = 0; idx < keyValueArray.size(); idx++) { for (int idx = 0; idx < keyValueArray.size(); idx++) {
if (keyValueArray.getArray(idx).size() != 2) { if (keyValueArray.getArray(idx).size() != 2) {
callback.invoke(AsyncStorageErrorUtil.getInvalidValueError(null)); error = AsyncStorageErrorUtil.getInvalidValueError(null);
return; return;
} }
if (keyValueArray.getArray(idx).getString(0) == null) { if (keyValueArray.getArray(idx).getString(0) == null) {
callback.invoke(AsyncStorageErrorUtil.getInvalidKeyError(null)); error = AsyncStorageErrorUtil.getInvalidKeyError(null);
return; return;
} }
if (keyValueArray.getArray(idx).getString(1) == null) { if (keyValueArray.getArray(idx).getString(1) == null) {
callback.invoke(AsyncStorageErrorUtil.getInvalidValueError(null)); error = AsyncStorageErrorUtil.getInvalidValueError(null);
return; return;
} }
@ -286,19 +313,30 @@ public final class AsyncStorageModule
mReactDatabaseSupplier.get(), mReactDatabaseSupplier.get(),
keyValueArray.getArray(idx).getString(0), keyValueArray.getArray(idx).getString(0),
keyValueArray.getArray(idx).getString(1))) { keyValueArray.getArray(idx).getString(1))) {
callback.invoke(AsyncStorageErrorUtil.getDBError(null)); error = AsyncStorageErrorUtil.getDBError(null);
return; return;
} }
} }
mReactDatabaseSupplier.get().setTransactionSuccessful(); mReactDatabaseSupplier.get().setTransactionSuccessful();
} catch (Exception e) { } catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e); FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage())); error = AsyncStorageErrorUtil.getError(null, e.getMessage());
} finally { } finally {
try {
mReactDatabaseSupplier.get().endTransaction(); mReactDatabaseSupplier.get().endTransaction();
} catch (Exception e) {
FLog.w(ReactConstants.TAG, e.getMessage(), e);
if (error == null) {
error = AsyncStorageErrorUtil.getError(null, e.getMessage());
} }
}
}
if (error != null) {
callback.invoke(error);
} else {
callback.invoke(); callback.invoke();
} }
}
}.execute(); }.execute();
} }
@ -316,11 +354,11 @@ public final class AsyncStorageModule
} }
try { try {
mReactDatabaseSupplier.get().delete(TABLE_CATALYST, null, null); mReactDatabaseSupplier.get().delete(TABLE_CATALYST, null, null);
callback.invoke();
} catch (Exception e) { } catch (Exception e) {
FLog.w(ReactConstants.TAG, "Exception in database clear ", e); FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage())); callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()));
} }
callback.invoke();
} }
}.execute(); }.execute();
} }
@ -348,8 +386,9 @@ public final class AsyncStorageModule
} while (cursor.moveToNext()); } while (cursor.moveToNext());
} }
} catch (Exception e) { } catch (Exception e) {
FLog.w(ReactConstants.TAG, "Exception in database getAllKeys ", e); FLog.w(ReactConstants.TAG, e.getMessage(), e);
callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()), null); callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage()), null);
return;
} finally { } finally {
cursor.close(); cursor.close();
} }

View File

@ -21,8 +21,10 @@ public class ReactDatabaseSupplier extends SQLiteOpenHelper {
// VisibleForTesting // VisibleForTesting
public static final String DATABASE_NAME = "RKStorage"; public static final String DATABASE_NAME = "RKStorage";
static final int DATABASE_VERSION = 1;
private static final int DATABASE_VERSION = 1;
private static final int SLEEP_TIME_MS = 30; private static final int SLEEP_TIME_MS = 30;
private static final long DEFAULT_MAX_DB_SIZE = 6L * 1024L * 1024L; // 6 MB in bytes
static final String TABLE_CATALYST = "catalystLocalStorage"; static final String TABLE_CATALYST = "catalystLocalStorage";
static final String KEY_COLUMN = "key"; static final String KEY_COLUMN = "key";
@ -85,6 +87,10 @@ public class ReactDatabaseSupplier extends SQLiteOpenHelper {
if (mDb == null) { if (mDb == null) {
throw lastSQLiteException; throw lastSQLiteException;
} }
// This is a sane limit to protect the user from the app storing too much data in the database.
// This also protects the database from filling up the disk cache and becoming malformed
// (endTransaction() calls will throw an exception, not rollback, and leave the db malformed).
mDb.setMaximumSize(DEFAULT_MAX_DB_SIZE);
return true; return true;
} }