From ea8d0b6c1f3053b1a1c38b8b66c401a76e40e31c Mon Sep 17 00:00:00 2001 From: Andrei Coman Date: Tue, 6 Oct 2015 06:10:37 -0700 Subject: [PATCH] Protect against SQLiteFullExceptions Reviewed By: @kmagiera Differential Revision: D2512317 fb-gh-sync-id: 93fd65ebd88e42b5afc4e06c0612576101f15c97 --- .../modules/storage/AsyncStorageModule.java | 87 ++++++++++++++----- .../storage/ReactDatabaseSupplier.java | 8 +- 2 files changed, 70 insertions(+), 25 deletions(-) diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/AsyncStorageModule.java b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/AsyncStorageModule.java index fd2fa7105..abd6ed274 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/AsyncStorageModule.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/AsyncStorageModule.java @@ -23,6 +23,7 @@ import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; import com.facebook.react.bridge.ReadableArray; import com.facebook.react.bridge.WritableArray; +import com.facebook.react.bridge.WritableMap; import com.facebook.react.common.ReactConstants; import com.facebook.react.common.SetBuilder; import com.facebook.react.modules.common.ModuleDataCleaner; @@ -137,8 +138,9 @@ public final class AsyncStorageModule } while (cursor.moveToNext()); } } 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); + return; } finally { cursor.close(); } @@ -179,19 +181,20 @@ public final class AsyncStorageModule String sql = "INSERT OR REPLACE INTO " + TABLE_CATALYST + " VALUES (?, ?);"; SQLiteStatement statement = mReactDatabaseSupplier.get().compileStatement(sql); - mReactDatabaseSupplier.get().beginTransaction(); + WritableMap error = null; try { + mReactDatabaseSupplier.get().beginTransaction(); for (int idx=0; idx < keyValueArray.size(); idx++) { if (keyValueArray.getArray(idx).size() != 2) { - callback.invoke(AsyncStorageErrorUtil.getInvalidValueError(null)); + error = AsyncStorageErrorUtil.getInvalidValueError(null); return; } if (keyValueArray.getArray(idx).getString(0) == null) { - callback.invoke(AsyncStorageErrorUtil.getInvalidKeyError(null)); + error = AsyncStorageErrorUtil.getInvalidKeyError(null); return; } if (keyValueArray.getArray(idx).getString(1) == null) { - callback.invoke(AsyncStorageErrorUtil.getInvalidValueError(null)); + error = AsyncStorageErrorUtil.getInvalidValueError(null); return; } @@ -202,12 +205,23 @@ public final class AsyncStorageModule } mReactDatabaseSupplier.get().setTransactionSuccessful(); } catch (Exception e) { - FLog.w(ReactConstants.TAG, "Exception in database multiSet ", e); - callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage())); + FLog.w(ReactConstants.TAG, e.getMessage(), e); + error = AsyncStorageErrorUtil.getError(null, e.getMessage()); } finally { - mReactDatabaseSupplier.get().endTransaction(); + try { + 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(); } @@ -230,8 +244,9 @@ public final class AsyncStorageModule return; } - mReactDatabaseSupplier.get().beginTransaction(); + WritableMap error = null; try { + mReactDatabaseSupplier.get().beginTransaction(); for (int keyStart = 0; keyStart < keys.size(); keyStart += MAX_SQL_KEYS) { int keyCount = Math.min(keys.size() - keyStart, MAX_SQL_KEYS); mReactDatabaseSupplier.get().delete( @@ -241,12 +256,23 @@ public final class AsyncStorageModule } mReactDatabaseSupplier.get().setTransactionSuccessful(); } catch (Exception e) { - FLog.w(ReactConstants.TAG, "Exception in database multiRemove ", e); - callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage())); + FLog.w(ReactConstants.TAG, e.getMessage(), e); + error = AsyncStorageErrorUtil.getError(null, e.getMessage()); } finally { + try { 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(); } @@ -264,21 +290,22 @@ public final class AsyncStorageModule callback.invoke(AsyncStorageErrorUtil.getDBError(null)); return; } - mReactDatabaseSupplier.get().beginTransaction(); + WritableMap error = null; try { + mReactDatabaseSupplier.get().beginTransaction(); for (int idx = 0; idx < keyValueArray.size(); idx++) { if (keyValueArray.getArray(idx).size() != 2) { - callback.invoke(AsyncStorageErrorUtil.getInvalidValueError(null)); + error = AsyncStorageErrorUtil.getInvalidValueError(null); return; } if (keyValueArray.getArray(idx).getString(0) == null) { - callback.invoke(AsyncStorageErrorUtil.getInvalidKeyError(null)); + error = AsyncStorageErrorUtil.getInvalidKeyError(null); return; } if (keyValueArray.getArray(idx).getString(1) == null) { - callback.invoke(AsyncStorageErrorUtil.getInvalidValueError(null)); + error = AsyncStorageErrorUtil.getInvalidValueError(null); return; } @@ -286,18 +313,29 @@ public final class AsyncStorageModule mReactDatabaseSupplier.get(), keyValueArray.getArray(idx).getString(0), keyValueArray.getArray(idx).getString(1))) { - callback.invoke(AsyncStorageErrorUtil.getDBError(null)); + error = AsyncStorageErrorUtil.getDBError(null); return; } } mReactDatabaseSupplier.get().setTransactionSuccessful(); } catch (Exception e) { FLog.w(ReactConstants.TAG, e.getMessage(), e); - callback.invoke(AsyncStorageErrorUtil.getError(null, e.getMessage())); + error = AsyncStorageErrorUtil.getError(null, e.getMessage()); } finally { - mReactDatabaseSupplier.get().endTransaction(); + try { + 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(); } @@ -316,11 +354,11 @@ public final class AsyncStorageModule } try { mReactDatabaseSupplier.get().delete(TABLE_CATALYST, null, null); + callback.invoke(); } 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(); } }.execute(); } @@ -348,8 +386,9 @@ public final class AsyncStorageModule } while (cursor.moveToNext()); } } 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); + return; } finally { cursor.close(); } diff --git a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java index be14cc3aa..2f127c658 100644 --- a/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java +++ b/ReactAndroid/src/main/java/com/facebook/react/modules/storage/ReactDatabaseSupplier.java @@ -21,8 +21,10 @@ public class ReactDatabaseSupplier extends SQLiteOpenHelper { // VisibleForTesting 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 long DEFAULT_MAX_DB_SIZE = 6L * 1024L * 1024L; // 6 MB in bytes static final String TABLE_CATALYST = "catalystLocalStorage"; static final String KEY_COLUMN = "key"; @@ -85,6 +87,10 @@ public class ReactDatabaseSupplier extends SQLiteOpenHelper { if (mDb == null) { 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; }