From 95333eccd4cad16ac1b0fc60d4987cbf6db2c934 Mon Sep 17 00:00:00 2001 From: Greyson Parrelli Date: Wed, 2 Oct 2019 11:23:21 -0400 Subject: [PATCH] Cleanup bad recipients. --- .../helpers/RecipientIdCleanupHelper.java | 92 +++++++++++++++++++ .../database/helpers/SQLCipherOpenHelper.java | 11 ++- .../migrations/ApplicationMigrations.java | 13 ++- 3 files changed, 107 insertions(+), 9 deletions(-) create mode 100644 src/org/thoughtcrime/securesms/database/helpers/RecipientIdCleanupHelper.java diff --git a/src/org/thoughtcrime/securesms/database/helpers/RecipientIdCleanupHelper.java b/src/org/thoughtcrime/securesms/database/helpers/RecipientIdCleanupHelper.java new file mode 100644 index 0000000000..9ef4518789 --- /dev/null +++ b/src/org/thoughtcrime/securesms/database/helpers/RecipientIdCleanupHelper.java @@ -0,0 +1,92 @@ +package org.thoughtcrime.securesms.database.helpers; + +import android.database.Cursor; +import android.text.TextUtils; + +import androidx.annotation.NonNull; + +import net.sqlcipher.database.SQLiteDatabase; + +import org.thoughtcrime.securesms.logging.Log; +import org.thoughtcrime.securesms.util.DelimiterUtil; + +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class RecipientIdCleanupHelper { + + private static final String TAG = Log.tag(RecipientIdCleanupHelper.class); + + public static void execute(@NonNull SQLiteDatabase db) { + Log.i(TAG, "Beginning migration."); + + long startTime = System.currentTimeMillis(); + Pattern pattern = Pattern.compile("^[0-9\\-+]+$"); + Set deletionCandidates = new HashSet<>(); + + try (Cursor cursor = db.query("recipient", new String[] { "_id", "phone" }, "group_id IS NULL AND email IS NULL", null, null, null, null)) { + while (cursor != null && cursor.moveToNext()) { + String id = cursor.getString(cursor.getColumnIndexOrThrow("_id")); + String phone = cursor.getString(cursor.getColumnIndexOrThrow("phone")); + + if (TextUtils.isEmpty(phone) || !pattern.matcher(phone).matches()) { + Log.i(TAG, "Recipient ID " + id + " has non-numeric characters and can potentially be deleted."); + + if (!isIdUsed(db, "identities", "address", id) && + !isIdUsed(db, "sessions", "address", id) && + !isIdUsed(db, "thread", "recipient_ids", id) && + !isIdUsed(db, "sms", "address", id) && + !isIdUsed(db, "mms", "address", id) && + !isIdUsed(db, "mms", "quote_author", id) && + !isIdUsed(db, "group_receipts", "address", id) && + !isIdUsed(db, "groups", "recipient_id", id)) + { + Log.i(TAG, "Determined ID " + id + " is unused in non-group membership. Marking for potential deletion."); + deletionCandidates.add(id); + } else { + Log.i(TAG, "Found that ID " + id + " is actually used in another table."); + } + } + } + } + + Set deletions = findUnusedInGroupMembership(db, deletionCandidates); + + for (String deletion : deletions) { + Log.i(TAG, "Deleting ID " + deletion); + db.delete("recipient", "_id = ?", new String[] { String.valueOf(deletion) }); + } + + Log.i(TAG, "Migration took " + (System.currentTimeMillis() - startTime) + " ms."); + } + + private static boolean isIdUsed(@NonNull SQLiteDatabase db, @NonNull String tableName, @NonNull String columnName, String id) { + try (Cursor cursor = db.query(tableName, new String[] { columnName }, columnName + " = ?", new String[] { id }, null, null, null, "1")) { + boolean used = cursor != null && cursor.moveToFirst(); + if (used) { + Log.i(TAG, "Recipient " + id + " was used in (" + tableName + ", " + columnName + ")"); + } + return used; + } + } + + private static Set findUnusedInGroupMembership(@NonNull SQLiteDatabase db, Set candidates) { + Set unused = new HashSet<>(candidates); + + try (Cursor cursor = db.rawQuery("SELECT members FROM groups", null)) { + while (cursor != null && cursor.moveToNext()) { + String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow("members")); + String[] members = DelimiterUtil.split(serializedMembers, ','); + + for (String member : members) { + if (unused.remove(member)) { + Log.i(TAG, "Recipient " + member + " was found in a group membership list."); + } + } + } + } + + return unused; + } +} diff --git a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index baf6c23fbf..32d8c25f8e 100644 --- a/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/src/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -39,13 +39,9 @@ import org.thoughtcrime.securesms.jobs.RefreshPreKeysJob; import org.thoughtcrime.securesms.logging.Log; import org.thoughtcrime.securesms.notifications.NotificationChannels; import org.thoughtcrime.securesms.phonenumbers.PhoneNumberFormatter; -import org.thoughtcrime.securesms.recipients.RecipientId; import org.thoughtcrime.securesms.service.KeyCachingService; -import org.thoughtcrime.securesms.util.DelimiterUtil; import org.thoughtcrime.securesms.util.GroupUtil; -import org.thoughtcrime.securesms.phonenumbers.NumberUtil; import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.thoughtcrime.securesms.util.Util; import java.io.File; @@ -78,8 +74,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int VIEW_ONCE_ONLY = 23; private static final int RECIPIENT_IDS = 24; private static final int RECIPIENT_SEARCH = 25; + private static final int RECIPIENT_CLEANUP = 26; - private static final int DATABASE_VERSION = 25; + private static final int DATABASE_VERSION = 26; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -513,6 +510,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { } } + if (oldVersion < RECIPIENT_CLEANUP) { + RecipientIdCleanupHelper.execute(db); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java b/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java index a2ba9bdb0c..13d98febbc 100644 --- a/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java +++ b/src/org/thoughtcrime/securesms/migrations/ApplicationMigrations.java @@ -38,12 +38,13 @@ public class ApplicationMigrations { private static final int LEGACY_CANONICAL_VERSION = 455; - public static final int CURRENT_VERSION = 3; + public static final int CURRENT_VERSION = 4; private static final class Version { - static final int LEGACY = 1; - static final int RECIPIENT_ID = 2; - static final int RECIPIENT_SEARCH = 3; + static final int LEGACY = 1; + static final int RECIPIENT_ID = 2; + static final int RECIPIENT_SEARCH = 3; + static final int RECIPIENT_CLEANUP = 4; } /** @@ -168,6 +169,10 @@ public class ApplicationMigrations { jobs.put(Version.RECIPIENT_SEARCH, new RecipientSearchMigrationJob()); } + if (lastSeenVersion < Version.RECIPIENT_CLEANUP) { + jobs.put(Version.RECIPIENT_CLEANUP, new DatabaseMigrationJob()); + } + return jobs; }