diff --git a/app/lint-baseline.xml b/app/lint-baseline.xml index dcf119b846..f17604c045 100644 --- a/app/lint-baseline.xml +++ b/app/lint-baseline.xml @@ -26454,61 +26454,6 @@ column="7"/> - - - - - - - - - - - - - - - - - - - - runInTransaction(block: (SignalSQLiteDatabase) -> T): T { return instance!!.signalWritableDatabase.withinTransaction { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java deleted file mode 100644 index 97001e80cc..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/ClassicOpenHelper.java +++ /dev/null @@ -1,1525 +0,0 @@ -package org.thoughtcrime.securesms.database.helpers; - - -import android.Manifest; -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.database.sqlite.SQLiteConstraintException; -import android.database.sqlite.SQLiteDatabase; -import android.database.sqlite.SQLiteOpenHelper; -import android.net.Uri; -import android.provider.ContactsContract; -import android.text.TextUtils; - -import androidx.annotation.Nullable; - -import com.fasterxml.jackson.annotation.JsonProperty; -import com.google.i18n.phonenumbers.NumberParseException; -import com.google.i18n.phonenumbers.PhoneNumberUtil; -import com.google.i18n.phonenumbers.Phonenumber; -import com.google.i18n.phonenumbers.ShortNumberInfo; - -import org.signal.core.util.Hex; -import org.signal.core.util.StreamUtil; -import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.IdentityKey; -import org.signal.libsignal.protocol.InvalidMessageException; -import org.thoughtcrime.securesms.crypto.AttachmentSecret; -import org.thoughtcrime.securesms.crypto.ClassicDecryptingPartInputStream; -import org.thoughtcrime.securesms.crypto.MasterCipher; -import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.crypto.MasterSecretUtil; -import org.thoughtcrime.securesms.database.AttachmentTable; -import org.thoughtcrime.securesms.database.DraftTable; -import org.thoughtcrime.securesms.database.GroupReceiptTable; -import org.thoughtcrime.securesms.database.GroupTable; -import org.thoughtcrime.securesms.database.IdentityTable; -import org.thoughtcrime.securesms.database.MessageTable; -import org.thoughtcrime.securesms.database.MessageTypes; -import org.thoughtcrime.securesms.database.RecipientTable; -import org.thoughtcrime.securesms.database.ThreadTable; -import org.thoughtcrime.securesms.dependencies.AppDependencies; -import org.thoughtcrime.securesms.groups.GroupId; -import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; -import org.signal.core.ui.permissions.Permissions; -import org.thoughtcrime.securesms.phonenumbers.NumberUtil; -import org.signal.core.util.Base64; -import org.thoughtcrime.securesms.util.DelimiterUtil; -import org.thoughtcrime.securesms.util.JsonUtils; -import org.thoughtcrime.securesms.util.MediaUtil; -import org.thoughtcrime.securesms.util.TextSecurePreferences; -import org.signal.core.util.Util; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.security.SecureRandom; -import java.util.Arrays; -import java.util.Collections; -import java.util.HashSet; -import java.util.LinkedList; -import java.util.List; -import java.util.Set; -import java.util.regex.Pattern; - -public class ClassicOpenHelper extends SQLiteOpenHelper { - - public static final String NAME = "messages.db"; - - private static final int INTRODUCED_IDENTITIES_VERSION = 2; - private static final int INTRODUCED_INDEXES_VERSION = 3; - private static final int INTRODUCED_DATE_SENT_VERSION = 4; - private static final int INTRODUCED_DRAFTS_VERSION = 5; - private static final int INTRODUCED_NEW_TYPES_VERSION = 6; - private static final int INTRODUCED_MMS_BODY_VERSION = 7; - private static final int INTRODUCED_MMS_FROM_VERSION = 8; - private static final int INTRODUCED_TOFU_IDENTITY_VERSION = 9; - private static final int INTRODUCED_PUSH_DATABASE_VERSION = 10; - private static final int INTRODUCED_GROUP_DATABASE_VERSION = 11; - private static final int INTRODUCED_PUSH_FIX_VERSION = 12; - private static final int INTRODUCED_DELIVERY_RECEIPTS = 13; - private static final int INTRODUCED_PART_DATA_SIZE_VERSION = 14; - private static final int INTRODUCED_THUMBNAILS_VERSION = 15; - private static final int INTRODUCED_IDENTITY_COLUMN_VERSION = 16; - private static final int INTRODUCED_UNIQUE_PART_IDS_VERSION = 17; - private static final int INTRODUCED_RECIPIENT_PREFS_DB = 18; - private static final int INTRODUCED_ENVELOPE_CONTENT_VERSION = 19; - private static final int INTRODUCED_COLOR_PREFERENCE_VERSION = 20; - private static final int INTRODUCED_DB_OPTIMIZATIONS_VERSION = 21; - private static final int INTRODUCED_INVITE_REMINDERS_VERSION = 22; - private static final int INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION = 23; - private static final int INTRODUCED_ARCHIVE_VERSION = 24; - private static final int INTRODUCED_CONVERSATION_LIST_STATUS_VERSION = 25; - private static final int MIGRATED_CONVERSATION_LIST_STATUS_VERSION = 26; - private static final int INTRODUCED_SUBSCRIPTION_ID_VERSION = 27; - private static final int INTRODUCED_EXPIRE_MESSAGES_VERSION = 28; - private static final int INTRODUCED_LAST_SEEN = 29; - private static final int INTRODUCED_DIGEST = 30; - private static final int INTRODUCED_NOTIFIED = 31; - private static final int INTRODUCED_DOCUMENTS = 32; - private static final int INTRODUCED_FAST_PREFLIGHT = 33; - private static final int INTRODUCED_VOICE_NOTES = 34; - private static final int INTRODUCED_IDENTITY_TIMESTAMP = 35; - private static final int SANIFY_ATTACHMENT_DOWNLOAD = 36; - private static final int NO_MORE_CANONICAL_ADDRESS_DATABASE = 37; - private static final int NO_MORE_RECIPIENTS_PLURAL = 38; - private static final int INTERNAL_DIRECTORY = 39; - private static final int INTERNAL_SYSTEM_DISPLAY_NAME = 40; - private static final int PROFILES = 41; - private static final int PROFILE_SHARING_APPROVAL = 42; - private static final int UNSEEN_NUMBER_OFFER = 43; - private static final int READ_RECEIPTS = 44; - private static final int GROUP_RECEIPT_TRACKING = 45; - private static final int UNREAD_COUNT_VERSION = 46; - private static final int MORE_RECIPIENT_FIELDS = 47; - private static final int DATABASE_VERSION = 47; - - private static final String TAG = Log.tag(ClassicOpenHelper.class); - - private final Context context; - - public ClassicOpenHelper(Context context) { - super(context, NAME, null, DATABASE_VERSION); - this.context = context.getApplicationContext(); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(MessageTable.CREATE_TABLE); - db.execSQL(AttachmentTable.CREATE_TABLE); - db.execSQL(ThreadTable.CREATE_TABLE); - db.execSQL(IdentityTable.CREATE_TABLE); - db.execSQL(DraftTable.CREATE_TABLE); - db.execSQL(GroupTable.CREATE_TABLE); - db.execSQL(RecipientTable.CREATE_TABLE); - db.execSQL(GroupReceiptTable.CREATE_TABLE); - - executeStatements(db, MessageTable.CREATE_INDEXS); - executeStatements(db, AttachmentTable.CREATE_INDEXS); - executeStatements(db, ThreadTable.CREATE_INDEXS); - executeStatements(db, DraftTable.CREATE_INDEXS); - executeStatements(db, GroupTable.CREATE_INDEXS); - executeStatements(db, GroupReceiptTable.CREATE_INDEXES); - } - - public void onApplicationLevelUpgrade(Context context, MasterSecret masterSecret, int fromVersion, - LegacyMigrationJob.DatabaseUpgradeListener listener) - { - SQLiteDatabase db = getWritableDatabase(); - db.beginTransaction(); - - if (fromVersion < LegacyMigrationJob.NO_MORE_KEY_EXCHANGE_PREFIX_VERSION) { - String KEY_EXCHANGE = "?TextSecureKeyExchange"; - String PROCESSED_KEY_EXCHANGE = "?TextSecureKeyExchangd"; - String STALE_KEY_EXCHANGE = "?TextSecureKeyExchangs"; - int ROW_LIMIT = 500; - - MasterCipher masterCipher = new MasterCipher(masterSecret); - int smsCount = 0; - int threadCount = 0; - int skip = 0; - - Cursor cursor = db.query("sms", new String[] {"COUNT(*)"}, "type & " + 0x80000000 + " != 0", - null, null, null, null); - - if (cursor != null && cursor.moveToFirst()) { - smsCount = cursor.getInt(0); - cursor.close(); - } - - cursor = db.query("thread", new String[] {"COUNT(*)"}, "snippet_type & " + 0x80000000 + " != 0", - null, null, null, null); - - if (cursor != null && cursor.moveToFirst()) { - threadCount = cursor.getInt(0); - cursor.close(); - } - - Cursor smsCursor = null; - - Log.i(TAG, "Upgrade count: " + (smsCount + threadCount)); - - do { - Log.i(TAG, "Looping SMS cursor..."); - if (smsCursor != null) - smsCursor.close(); - - smsCursor = db.query("sms", new String[] {"_id", "type", "body"}, - "type & " + 0x80000000 + " != 0", - null, null, null, "_id", skip + "," + ROW_LIMIT); - - while (smsCursor != null && smsCursor.moveToNext()) { - listener.setProgress(smsCursor.getPosition() + skip, smsCount + threadCount); - - try { - String body = masterCipher.decryptBody(smsCursor.getString(smsCursor.getColumnIndexOrThrow("body"))); - long type = smsCursor.getLong(smsCursor.getColumnIndexOrThrow("type")); - long id = smsCursor.getLong(smsCursor.getColumnIndexOrThrow("_id")); - - if (body.startsWith(KEY_EXCHANGE)) { - body = body.substring(KEY_EXCHANGE.length()); - body = masterCipher.encryptBody(body); - type |= 0x8000; - - db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?", - new String[] {body, type+"", id+""}); - } else if (body.startsWith(PROCESSED_KEY_EXCHANGE)) { - body = body.substring(PROCESSED_KEY_EXCHANGE.length()); - body = masterCipher.encryptBody(body); - type |= (0x8000 | 0x2000); - - db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?", - new String[] {body, type+"", id+""}); - } else if (body.startsWith(STALE_KEY_EXCHANGE)) { - body = body.substring(STALE_KEY_EXCHANGE.length()); - body = masterCipher.encryptBody(body); - type |= (0x8000 | 0x4000); - - db.execSQL("UPDATE sms SET body = ?, type = ? WHERE _id = ?", - new String[] {body, type+"", id+""}); - } - } catch (InvalidMessageException e) { - Log.w(TAG, e); - } - } - - skip += ROW_LIMIT; - } while (smsCursor != null && smsCursor.getCount() > 0); - - - - Cursor threadCursor = null; - skip = 0; - - do { - Log.i(TAG, "Looping thread cursor..."); - - if (threadCursor != null) - threadCursor.close(); - - threadCursor = db.query("thread", new String[] {"_id", "snippet_type", "snippet"}, - "snippet_type & " + 0x80000000 + " != 0", - null, null, null, "_id", skip + "," + ROW_LIMIT); - - while (threadCursor != null && threadCursor.moveToNext()) { - listener.setProgress(smsCount + threadCursor.getPosition(), smsCount + threadCount); - - try { - String snippet = threadCursor.getString(threadCursor.getColumnIndexOrThrow("snippet")); - long snippetType = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("snippet_type")); - long id = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("_id")); - - if (!TextUtils.isEmpty(snippet)) { - snippet = masterCipher.decryptBody(snippet); - } - - if (snippet.startsWith(KEY_EXCHANGE)) { - snippet = snippet.substring(KEY_EXCHANGE.length()); - snippet = masterCipher.encryptBody(snippet); - snippetType |= 0x8000; - - db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?", - new String[] {snippet, snippetType+"", id+""}); - } else if (snippet.startsWith(PROCESSED_KEY_EXCHANGE)) { - snippet = snippet.substring(PROCESSED_KEY_EXCHANGE.length()); - snippet = masterCipher.encryptBody(snippet); - snippetType |= (0x8000 | 0x2000); - - db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?", - new String[] {snippet, snippetType+"", id+""}); - } else if (snippet.startsWith(STALE_KEY_EXCHANGE)) { - snippet = snippet.substring(STALE_KEY_EXCHANGE.length()); - snippet = masterCipher.encryptBody(snippet); - snippetType |= (0x8000 | 0x4000); - - db.execSQL("UPDATE thread SET snippet = ?, snippet_type = ? WHERE _id = ?", - new String[] {snippet, snippetType+"", id+""}); - } - } catch (InvalidMessageException e) { - Log.w(TAG, e); - } - } - - skip += ROW_LIMIT; - } while (threadCursor != null && threadCursor.getCount() > 0); - - if (smsCursor != null) - smsCursor.close(); - - if (threadCursor != null) - threadCursor.close(); - } - - if (fromVersion < LegacyMigrationJob.MMS_BODY_VERSION) { - Log.i(TAG, "Update MMS bodies..."); - MasterCipher masterCipher = new MasterCipher(masterSecret); - Cursor mmsCursor = db.query("mms", new String[] {"_id"}, - "msg_box & " + 0x80000000L + " != 0", - null, null, null, null); - - Log.i(TAG, "Got MMS rows: " + (mmsCursor == null ? "null" : mmsCursor.getCount())); - - while (mmsCursor != null && mmsCursor.moveToNext()) { - listener.setProgress(mmsCursor.getPosition(), mmsCursor.getCount()); - - long mmsId = mmsCursor.getLong(mmsCursor.getColumnIndexOrThrow("_id")); - String body = null; - int partCount = 0; - Cursor partCursor = db.query("part", new String[] {"_id", "ct", "_data", "encrypted"}, - "mid = ?", new String[] {mmsId+""}, null, null, null); - - while (partCursor != null && partCursor.moveToNext()) { - String contentType = partCursor.getString(partCursor.getColumnIndexOrThrow("ct")); - - if (MediaUtil.isTextType(contentType)) { - try { - long partId = partCursor.getLong(partCursor.getColumnIndexOrThrow("_id")); - String dataLocation = partCursor.getString(partCursor.getColumnIndexOrThrow("_data")); - boolean encrypted = partCursor.getInt(partCursor.getColumnIndexOrThrow("encrypted")) == 1; - File dataFile = new File(dataLocation); - - InputStream is; - - AttachmentSecret attachmentSecret = new AttachmentSecret(masterSecret.getEncryptionKey().getEncoded(), - masterSecret.getMacKey().getEncoded(), null); - if (encrypted) is = ClassicDecryptingPartInputStream.createFor(attachmentSecret, dataFile); - else is = new FileInputStream(dataFile); - - body = (body == null) ? StreamUtil.readFullyAsString(is) : body + " " + StreamUtil.readFullyAsString(is); - - //noinspection ResultOfMethodCallIgnored - dataFile.delete(); - db.delete("part", "_id = ?", new String[] {partId+""}); - } catch (IOException e) { - Log.w(TAG, e); - } - } else if (MediaUtil.isAudioType(contentType) || - MediaUtil.isImageType(contentType) || - MediaUtil.isVideoType(contentType)) - { - partCount++; - } - } - - if (!TextUtils.isEmpty(body)) { - body = masterCipher.encryptBody(body); - db.execSQL("UPDATE mms SET body = ?, part_count = ? WHERE _id = ?", - new String[] {body, partCount+"", mmsId+""}); - } else { - db.execSQL("UPDATE mms SET part_count = ? WHERE _id = ?", - new String[] {partCount+"", mmsId+""}); - } - - Log.i(TAG, "Updated body: " + body + " and part_count: " + partCount); - } - } - - if (fromVersion < LegacyMigrationJob.TOFU_IDENTITIES_VERSION) { - File sessionDirectory = new File(context.getFilesDir() + File.separator + "sessions"); - - if (sessionDirectory.exists() && sessionDirectory.isDirectory()) { - File[] sessions = sessionDirectory.listFiles(); - - if (sessions != null) { - for (File session : sessions) { - String name = session.getName(); - - if (name.matches("[0-9]+")) { - long recipientId = Long.parseLong(name); - IdentityKey identityKey = null; - // NOTE (4/21/14) -- At this moment in time, we're forgetting the ability to parse - // V1 session records. Despite our usual attempts to avoid using shared code in the - // upgrade path, this is too complex to put here directly. Thus, unfortunately - // this operation is now lost to the ages. From the git log, it seems to have been - // almost exactly a year since this went in, so hopefully the bulk of people have - // already upgraded. -// IdentityKey identityKey = Session.getRemoteIdentityKey(context, masterSecret, recipientId); - - if (identityKey != null) { - MasterCipher masterCipher = new MasterCipher(masterSecret); - String identityKeyString = Base64.encodeWithPadding(identityKey.serialize()); - String macString = Base64.encodeWithPadding(masterCipher.getMacFor(recipientId + - identityKeyString)); - - db.execSQL("REPLACE INTO identities (recipient, key, mac) VALUES (?, ?, ?)", - new String[] {recipientId+"", identityKeyString, macString}); - } - } - } - } - } - } - - if (fromVersion < LegacyMigrationJob.ASYMMETRIC_MASTER_SECRET_FIX_VERSION) { - if (!MasterSecretUtil.hasAsymmericMasterSecret(context)) { - MasterSecretUtil.generateAsymmetricMasterSecret(context, masterSecret); - - MasterCipher masterCipher = new MasterCipher(masterSecret); - Cursor cursor = null; - - try { - cursor = db.query("sms", - new String[] { "_id", "body", "type"}, - "type & ? == 0", - new String[] {String.valueOf(MessageTypes.ENCRYPTION_MASK)}, - null, null, null); - - while (cursor.moveToNext()) { - long id = cursor.getLong(0); - String body = cursor.getString(1); - long type = cursor.getLong(2); - - String encryptedBody = masterCipher.encryptBody(body); - - ContentValues update = new ContentValues(); - update.put("body", encryptedBody); - update.put("type", type | 0x80000000); // Inline now deprecated symmetric encryption type - - db.update("sms", update, "_id = ?", - new String[] {String.valueOf(id)}); - } - } finally { - if (cursor != null) - cursor.close(); - } - } - } - - db.setTransactionSuccessful(); - db.endTransaction(); - -// DecryptingQueue.schedulePendingDecrypts(context, masterSecret); - AppDependencies.getMessageNotifier().updateNotification(context); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - db.beginTransaction(); - - if (oldVersion < INTRODUCED_IDENTITIES_VERSION) { - db.execSQL("CREATE TABLE identities (_id INTEGER PRIMARY KEY, key TEXT UNIQUE, name TEXT UNIQUE, mac TEXT);"); - } - - if (oldVersion < INTRODUCED_INDEXES_VERSION) { - executeStatements(db, new String[] { - "CREATE INDEX IF NOT EXISTS sms_thread_id_index ON sms (thread_id);", - "CREATE INDEX IF NOT EXISTS sms_read_index ON sms (read);", - "CREATE INDEX IF NOT EXISTS sms_read_and_thread_id_index ON sms (read,thread_id);", - "CREATE INDEX IF NOT EXISTS sms_type_index ON sms (type);" - }); - executeStatements(db, new String[] { - "CREATE INDEX IF NOT EXISTS mms_thread_id_index ON mms (thread_id);", - "CREATE INDEX IF NOT EXISTS mms_read_index ON mms (read);", - "CREATE INDEX IF NOT EXISTS mms_read_and_thread_id_index ON mms (read,thread_id);", - "CREATE INDEX IF NOT EXISTS mms_message_box_index ON mms (msg_box);" - }); - executeStatements(db, new String[] { - "CREATE INDEX IF NOT EXISTS part_mms_id_index ON part (mid);" - }); - executeStatements(db, new String[] { - "CREATE INDEX IF NOT EXISTS thread_recipient_ids_index ON thread (recipient_ids);", - }); - executeStatements(db, new String[] { - "CREATE INDEX IF NOT EXISTS mms_addresses_mms_id_index ON mms_addresses (mms_id);", - }); - } - - if (oldVersion < INTRODUCED_DATE_SENT_VERSION) { - db.execSQL("ALTER TABLE sms ADD COLUMN date_sent INTEGER;"); - db.execSQL("UPDATE sms SET date_sent = date;"); - - db.execSQL("ALTER TABLE mms ADD COLUMN date_received INTEGER;"); - db.execSQL("UPDATE mms SET date_received = date;"); - } - - if (oldVersion < INTRODUCED_DRAFTS_VERSION) { - db.execSQL("CREATE TABLE drafts (_id INTEGER PRIMARY KEY, thread_id INTEGER, type TEXT, value TEXT);"); - executeStatements(db, new String[] { - "CREATE INDEX IF NOT EXISTS draft_thread_index ON drafts (thread_id);", - }); - } - - if (oldVersion < INTRODUCED_NEW_TYPES_VERSION) { - String KEY_EXCHANGE = "?TextSecureKeyExchange"; - String SYMMETRIC_ENCRYPT = "?TextSecureLocalEncrypt"; - String ASYMMETRIC_ENCRYPT = "?TextSecureAsymmetricEncrypt"; - String ASYMMETRIC_LOCAL_ENCRYPT = "?TextSecureAsymmetricLocalEncrypt"; - String PROCESSED_KEY_EXCHANGE = "?TextSecureKeyExchangd"; - String STALE_KEY_EXCHANGE = "?TextSecureKeyExchangs"; - - // SMS Updates - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {20L+"", 1L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {21L+"", 43L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {22L+"", 4L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {23L+"", 2L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {24L+"", 5L+""}); - - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(21L | 0x800000L)+"", 42L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(23L | 0x800000L)+"", 44L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L)+"", 45L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L | 0x10000000L)+"", 46L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L)+"", 47L+""}); - db.execSQL("UPDATE sms SET type = ? WHERE type = ?", new String[] {(20L | 0x800000L | 0x08000000L)+"", 48L+""}); - - db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", - new String[] {(SYMMETRIC_ENCRYPT.length()+1)+"", - 0x80000000L+"", - SYMMETRIC_ENCRYPT + "%"}); - - db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", - new String[] {(ASYMMETRIC_LOCAL_ENCRYPT.length()+1)+"", - 0x40000000L+"", - ASYMMETRIC_LOCAL_ENCRYPT + "%"}); - - db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", - new String[] {(ASYMMETRIC_ENCRYPT.length()+1)+"", - (0x800000L | 0x20000000L)+"", - ASYMMETRIC_ENCRYPT + "%"}); - - db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", - new String[] {(KEY_EXCHANGE.length()+1)+"", - 0x8000L+"", - KEY_EXCHANGE + "%"}); - - db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", - new String[] {(PROCESSED_KEY_EXCHANGE.length()+1)+"", - (0x8000L | 0x2000L)+"", - PROCESSED_KEY_EXCHANGE + "%"}); - - db.execSQL("UPDATE sms SET body = substr(body, ?), type = type | ? WHERE body LIKE ?", - new String[] {(STALE_KEY_EXCHANGE.length()+1)+"", - (0x8000L | 0x4000L)+"", - STALE_KEY_EXCHANGE + "%"}); - - // MMS Updates - - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x80000000L)+"", 1+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(23L | 0x80000000L)+"", 2+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(21L | 0x80000000L)+"", 4+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(24L | 0x80000000L)+"", 12+""}); - - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(21L | 0x80000000L | 0x800000L) +"", 5+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(23L | 0x80000000L | 0x800000L) +"", 6+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x20000000L | 0x800000L) +"", 7+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x80000000L | 0x800000L) +"", 8+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x08000000L | 0x800000L) +"", 9+""}); - db.execSQL("UPDATE mms SET msg_box = ? WHERE msg_box = ?", new String[] {(20L | 0x10000000L | 0x800000L) +"", 10+""}); - - // Thread Updates - - db.execSQL("ALTER TABLE thread ADD COLUMN snippet_type INTEGER;"); - - db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + - "snippet_type = ? WHERE snippet LIKE ?", - new String[] {(SYMMETRIC_ENCRYPT.length()+1)+"", - 0x80000000L+"", - SYMMETRIC_ENCRYPT + "%"}); - - db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + - "snippet_type = ? WHERE snippet LIKE ?", - new String[] {(ASYMMETRIC_LOCAL_ENCRYPT.length()+1)+"", - 0x40000000L+"", - ASYMMETRIC_LOCAL_ENCRYPT + "%"}); - - db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + - "snippet_type = ? WHERE snippet LIKE ?", - new String[] {(ASYMMETRIC_ENCRYPT.length()+1)+"", - (0x800000L | 0x20000000L)+"", - ASYMMETRIC_ENCRYPT + "%"}); - - db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + - "snippet_type = ? WHERE snippet LIKE ?", - new String[] {(KEY_EXCHANGE.length()+1)+"", - 0x8000L+"", - KEY_EXCHANGE + "%"}); - - db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + - "snippet_type = ? WHERE snippet LIKE ?", - new String[] {(STALE_KEY_EXCHANGE.length()+1)+"", - (0x8000L | 0x4000L)+"", - STALE_KEY_EXCHANGE + "%"}); - - db.execSQL("UPDATE thread SET snippet = substr(snippet, ?), " + - "snippet_type = ? WHERE snippet LIKE ?", - new String[] {(PROCESSED_KEY_EXCHANGE.length()+1)+"", - (0x8000L | 0x2000L)+"", - PROCESSED_KEY_EXCHANGE + "%"}); - } - - if (oldVersion < INTRODUCED_MMS_BODY_VERSION) { - db.execSQL("ALTER TABLE mms ADD COLUMN body TEXT"); - db.execSQL("ALTER TABLE mms ADD COLUMN part_count INTEGER"); - } - - if (oldVersion < INTRODUCED_MMS_FROM_VERSION) { - db.execSQL("ALTER TABLE mms ADD COLUMN address TEXT"); - - Cursor cursor = db.query("mms_addresses", null, "type = ?", new String[] {0x89+""}, - null, null, null); - - while (cursor != null && cursor.moveToNext()) { - long mmsId = cursor.getLong(cursor.getColumnIndexOrThrow("mms_id")); - String address = cursor.getString(cursor.getColumnIndexOrThrow("address")); - - if (!TextUtils.isEmpty(address)) { - db.execSQL("UPDATE mms SET address = ? WHERE _id = ?", new String[]{address, mmsId+""}); - } - } - - if (cursor != null) - cursor.close(); - } - - if (oldVersion < INTRODUCED_TOFU_IDENTITY_VERSION) { - db.execSQL("DROP TABLE identities"); - db.execSQL("CREATE TABLE identities (_id INTEGER PRIMARY KEY, recipient INTEGER UNIQUE, key TEXT, mac TEXT);"); - } - - if (oldVersion < INTRODUCED_PUSH_DATABASE_VERSION) { - db.execSQL("CREATE TABLE push (_id INTEGER PRIMARY KEY, type INTEGER, source TEXT, destinations TEXT, body TEXT, TIMESTAMP INTEGER);"); - db.execSQL("ALTER TABLE part ADD COLUMN pending_push INTEGER;"); - db.execSQL("CREATE INDEX IF NOT EXISTS pending_push_index ON part (pending_push);"); - } - - if (oldVersion < INTRODUCED_GROUP_DATABASE_VERSION) { - db.execSQL("CREATE TABLE groups (_id INTEGER PRIMARY KEY, group_id TEXT, title TEXT, members TEXT, avatar BLOB, avatar_id INTEGER, avatar_key BLOB, avatar_content_type TEXT, avatar_relay TEXT, timestamp INTEGER, active INTEGER DEFAULT 1);"); - db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS group_id_index ON groups (GROUP_ID);"); - db.execSQL("ALTER TABLE push ADD COLUMN device_id INTEGER DEFAULT 1;"); - db.execSQL("ALTER TABLE sms ADD COLUMN address_device_id INTEGER DEFAULT 1;"); - db.execSQL("ALTER TABLE mms ADD COLUMN address_device_id INTEGER DEFAULT 1;"); - } - - if (oldVersion < INTRODUCED_PUSH_FIX_VERSION) { - db.execSQL("CREATE TEMPORARY table push_backup (_id INTEGER PRIMARY KEY, type INTEGER, source, TEXT, destinations TEXT, body TEXT, timestamp INTEGER, device_id INTEGER DEFAULT 1);"); - db.execSQL("INSERT INTO push_backup(_id, type, source, body, timestamp, device_id) SELECT _id, type, source, body, timestamp, device_id FROM push;"); - db.execSQL("DROP TABLE push"); - db.execSQL("CREATE TABLE push (_id INTEGER PRIMARY KEY, type INTEGER, source TEXT, body TEXT, timestamp INTEGER, device_id INTEGER DEFAULT 1);"); - db.execSQL("INSERT INTO push (_id, type, source, body, timestamp, device_id) SELECT _id, type, source, body, timestamp, device_id FROM push_backup;"); - db.execSQL("DROP TABLE push_backup;"); - } - - if (oldVersion < INTRODUCED_DELIVERY_RECEIPTS) { - db.execSQL("ALTER TABLE sms ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0;"); - db.execSQL("ALTER TABLE mms ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0;"); - db.execSQL("CREATE INDEX IF NOT EXISTS sms_date_sent_index ON sms (date_sent);"); - db.execSQL("CREATE INDEX IF NOT EXISTS mms_date_sent_index ON mms (date);"); - } - - if (oldVersion < INTRODUCED_PART_DATA_SIZE_VERSION) { - db.execSQL("ALTER TABLE part ADD COLUMN data_size INTEGER DEFAULT 0;"); - } - - if (oldVersion < INTRODUCED_THUMBNAILS_VERSION) { - db.execSQL("ALTER TABLE part ADD COLUMN thumbnail TEXT;"); - db.execSQL("ALTER TABLE part ADD COLUMN aspect_ratio REAL;"); - } - - if (oldVersion < INTRODUCED_IDENTITY_COLUMN_VERSION) { - db.execSQL("ALTER TABLE sms ADD COLUMN mismatched_identities TEXT"); - db.execSQL("ALTER TABLE mms ADD COLUMN mismatched_identities TEXT"); - db.execSQL("ALTER TABLE mms ADD COLUMN network_failures TEXT"); - } - - if (oldVersion < INTRODUCED_UNIQUE_PART_IDS_VERSION) { - db.execSQL("ALTER TABLE part ADD COLUMN unique_id INTEGER NOT NULL DEFAULT 0"); - } - - if (oldVersion < INTRODUCED_RECIPIENT_PREFS_DB) { - db.execSQL("CREATE TABLE recipient_preferences " + - "(_id INTEGER PRIMARY KEY, recipient_ids TEXT UNIQUE, block INTEGER DEFAULT 0, " + - "notification TEXT DEFAULT NULL, vibrate INTEGER DEFAULT 0, mute_until INTEGER DEFAULT 0)"); - } - - if (oldVersion < INTRODUCED_ENVELOPE_CONTENT_VERSION) { - db.execSQL("ALTER TABLE push ADD COLUMN content TEXT"); - } - - if (oldVersion < INTRODUCED_COLOR_PREFERENCE_VERSION) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN color TEXT DEFAULT NULL"); - } - - if (oldVersion < INTRODUCED_DB_OPTIMIZATIONS_VERSION) { - db.execSQL("UPDATE mms SET date_received = (date_received * 1000), date = (date * 1000);"); - db.execSQL("CREATE INDEX IF NOT EXISTS sms_thread_date_index ON sms (thread_id, date);"); - db.execSQL("CREATE INDEX IF NOT EXISTS mms_thread_date_index ON mms (thread_id, date_received);"); - } - - if (oldVersion < INTRODUCED_INVITE_REMINDERS_VERSION) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN seen_invite_reminder INTEGER DEFAULT 0"); - } - - if (oldVersion < INTRODUCED_CONVERSATION_LIST_THUMBNAILS_VERSION) { - db.execSQL("ALTER TABLE thread ADD COLUMN snippet_uri TEXT DEFAULT NULL"); - } - - if (oldVersion < INTRODUCED_ARCHIVE_VERSION) { - db.execSQL("ALTER TABLE thread ADD COLUMN archived INTEGER DEFAULT 0"); - db.execSQL("CREATE INDEX IF NOT EXISTS archived_index ON thread (archived)"); - } - - if (oldVersion < INTRODUCED_CONVERSATION_LIST_STATUS_VERSION) { - db.execSQL("ALTER TABLE thread ADD COLUMN status INTEGER DEFAULT -1"); - db.execSQL("ALTER TABLE thread ADD COLUMN delivery_receipt_count INTEGER DEFAULT 0"); - } - - if (oldVersion < MIGRATED_CONVERSATION_LIST_STATUS_VERSION) { - Cursor threadCursor = db.query("thread", new String[] {"_id"}, null, null, null, null, null); - - while (threadCursor != null && threadCursor.moveToNext()) { - long threadId = threadCursor.getLong(threadCursor.getColumnIndexOrThrow("_id")); - - Cursor cursor = db.rawQuery("SELECT DISTINCT date AS date_received, status, " + - "delivery_receipt_count FROM sms WHERE (thread_id = ?1) " + - "UNION ALL SELECT DISTINCT date_received, -1 AS status, " + - "delivery_receipt_count FROM mms WHERE (thread_id = ?1) " + - "ORDER BY date_received DESC LIMIT 1", new String[]{threadId + ""}); - - if (cursor != null && cursor.moveToNext()) { - int status = cursor.getInt(cursor.getColumnIndexOrThrow("status")); - int receiptCount = cursor.getInt(cursor.getColumnIndexOrThrow("delivery_receipt_count")); - - db.execSQL("UPDATE thread SET status = ?, delivery_receipt_count = ? WHERE _id = ?", - new String[]{status + "", receiptCount + "", threadId + ""}); - } - } - } - - if (oldVersion < INTRODUCED_SUBSCRIPTION_ID_VERSION) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN default_subscription_id INTEGER DEFAULT -1"); - db.execSQL("ALTER TABLE sms ADD COLUMN subscription_id INTEGER DEFAULT -1"); - db.execSQL("ALTER TABLE mms ADD COLUMN subscription_id INTEGER DEFAULT -1"); - } - - if (oldVersion < INTRODUCED_EXPIRE_MESSAGES_VERSION) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN expire_messages INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE sms ADD COLUMN expires_in INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE mms ADD COLUMN expires_in INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE sms ADD COLUMN expire_started INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE mms ADD COLUMN expire_started INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE thread ADD COLUMN expires_in INTEGER DEFAULT 0"); - } - - if (oldVersion < INTRODUCED_LAST_SEEN) { - db.execSQL("ALTER TABLE thread ADD COLUMN last_seen INTEGER DEFAULT 0"); - } - - if (oldVersion < INTRODUCED_DIGEST) { - db.execSQL("ALTER TABLE part ADD COLUMN digest BLOB"); - db.execSQL("ALTER TABLE groups ADD COLUMN avatar_digest BLOB"); - } - - if (oldVersion < INTRODUCED_NOTIFIED) { - db.execSQL("ALTER TABLE sms ADD COLUMN notified INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE mms ADD COLUMN notified INTEGER DEFAULT 0"); - - db.execSQL("DROP INDEX sms_read_and_thread_id_index"); - db.execSQL("CREATE INDEX IF NOT EXISTS sms_read_and_notified_and_thread_id_index ON sms(read,notified,thread_id)"); - - db.execSQL("DROP INDEX mms_read_and_thread_id_index"); - db.execSQL("CREATE INDEX IF NOT EXISTS mms_read_and_notified_and_thread_id_index ON mms(read,notified,thread_id)"); - } - - if (oldVersion < INTRODUCED_DOCUMENTS) { - db.execSQL("ALTER TABLE part ADD COLUMN file_name TEXT"); - } - - if (oldVersion < INTRODUCED_FAST_PREFLIGHT) { - db.execSQL("ALTER TABLE part ADD COLUMN fast_preflight_id TEXT"); - } - - if (oldVersion < INTRODUCED_VOICE_NOTES) { - db.execSQL("ALTER TABLE part ADD COLUMN voice_note INTEGER DEFAULT 0"); - } - - if (oldVersion < INTRODUCED_IDENTITY_TIMESTAMP) { - db.execSQL("ALTER TABLE identities ADD COLUMN timestamp INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE identities ADD COLUMN first_use INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE identities ADD COLUMN nonblocking_approval INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE identities ADD COLUMN verified INTEGER DEFAULT 0"); - - db.execSQL("DROP INDEX archived_index"); - db.execSQL("CREATE INDEX IF NOT EXISTS archived_count_index ON thread (archived, message_count)"); - } - - if (oldVersion < SANIFY_ATTACHMENT_DOWNLOAD) { - db.execSQL("UPDATE part SET pending_push = '2' WHERE pending_push = '1'"); - } - - if (oldVersion < NO_MORE_CANONICAL_ADDRESS_DATABASE && isValidNumber(TextSecurePreferences.getStringPreference(context, "pref_local_number", null))) { - SQLiteOpenHelper canonicalAddressDatabaseHelper = new SQLiteOpenHelper(context, "canonical_address.db", null, 1) { - @Override - public void onCreate(SQLiteDatabase db) { - throw new AssertionError("No canonical address DB?"); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {} - }; - - SQLiteDatabase canonicalAddressDatabase = canonicalAddressDatabaseHelper.getReadableDatabase(); - NumberMigrator numberMigrator = new NumberMigrator(TextSecurePreferences.getStringPreference(context, "pref_local_number", null)); - - // Migrate Thread Database - Cursor cursor = db.query("thread", new String[] {"_id", "recipient_ids"}, null, null, null, null, null); - - while (cursor != null && cursor.moveToNext()) { - long threadId = cursor.getLong(0); - String recipientIdsList = cursor.getString(1); - String[] recipientIds = recipientIdsList.split(" "); - String[] addresses = new String[recipientIds.length]; - - for (int i=0;i newDocumentList = new LinkedList<>(); - - for (PreCanonicalAddressIdentityMismatchDocument oldDocument : oldDocumentList.list) { - Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(oldDocument.recipientId)}, null, null, null); - - if (resolved != null && resolved.moveToFirst()) { - String address = resolved.getString(0); - newDocumentList.add(new PostCanonicalAddressIdentityMismatchDocument(numberMigrator.migrate(address), oldDocument.identityKey)); - } else { - throw new AssertionError("Unable to resolve: " + oldDocument.recipientId); - } - - if (resolved != null) resolved.close(); - } - - ContentValues values = new ContentValues(1); - values.put("mismatched_identities", JsonUtils.toJson(new PostCanonicalAddressIdentityMismatchList(newDocumentList))); - db.update("sms", values, "_id = ?", new String[] {String.valueOf(id)}); - } catch (IOException e) { - Log.w(TAG, e); - } - } - } - - if (cursor != null) cursor.close(); - - // Migrate MMS mismatched identities - cursor = db.query("mms", new String[] {"_id", "mismatched_identities"}, "mismatched_identities IS NOT NULL", null, null, null, null); - - while (cursor != null && cursor.moveToNext()) { - long id = cursor.getLong(0); - String document = cursor.getString(1); - - if (!TextUtils.isEmpty(document)) { - try { - PreCanonicalAddressIdentityMismatchList oldDocumentList = JsonUtils.fromJson(document, PreCanonicalAddressIdentityMismatchList.class); - List newDocumentList = new LinkedList<>(); - - for (PreCanonicalAddressIdentityMismatchDocument oldDocument : oldDocumentList.list) { - Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(oldDocument.recipientId)}, null, null, null); - - if (resolved != null && resolved.moveToFirst()) { - String address = resolved.getString(0); - newDocumentList.add(new PostCanonicalAddressIdentityMismatchDocument(numberMigrator.migrate(address), oldDocument.identityKey)); - } else { - throw new AssertionError("Unable to resolve: " + oldDocument.recipientId); - } - - if (resolved != null) resolved.close(); - } - - ContentValues values = new ContentValues(1); - values.put("mismatched_identities", JsonUtils.toJson(new PostCanonicalAddressIdentityMismatchList(newDocumentList))); - db.update("mms", values, "_id = ?", new String[] {String.valueOf(id)}); - } catch (IOException e) { - Log.w(TAG, e); - } - } - } - - if (cursor != null) cursor.close(); - - // Migrate MMS network failures - cursor = db.query("mms", new String[] {"_id", "network_failures"}, "network_failures IS NOT NULL", null, null, null, null); - - while (cursor != null && cursor.moveToNext()) { - long id = cursor.getLong(0); - String document = cursor.getString(1); - - if (!TextUtils.isEmpty(document)) { - try { - PreCanonicalAddressNetworkFailureList oldDocumentList = JsonUtils.fromJson(document, PreCanonicalAddressNetworkFailureList.class); - List newDocumentList = new LinkedList<>(); - - for (PreCanonicalAddressNetworkFailureDocument oldDocument : oldDocumentList.list) { - Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(oldDocument.recipientId)}, null, null, null); - - if (resolved != null && resolved.moveToFirst()) { - String address = resolved.getString(0); - newDocumentList.add(new PostCanonicalAddressNetworkFailureDocument(numberMigrator.migrate(address))); - } else { - throw new AssertionError("Unable to resolve: " + oldDocument.recipientId); - } - - if (resolved != null) resolved.close(); - } - - ContentValues values = new ContentValues(1); - values.put("network_failures", JsonUtils.toJson(new PostCanonicalAddressNetworkFailureList(newDocumentList))); - db.update("mms", values, "_id = ?", new String[] {String.valueOf(id)}); - } catch (IOException e) { - Log.w(TAG, e); - } - } - } - - // Migrate sessions - File sessionsDirectory = new File(context.getFilesDir(), "sessions-v2"); - - if (sessionsDirectory.exists() && sessionsDirectory.isDirectory()) { - File[] sessions = sessionsDirectory.listFiles(); - - for (File session : sessions) { - try { - String[] sessionParts = session.getName().split("[.]"); - long recipientId = Long.parseLong(sessionParts[0]); - - int deviceId; - - if (sessionParts.length > 1) deviceId = Integer.parseInt(sessionParts[1]); - else deviceId = 1; - - Cursor resolved = canonicalAddressDatabase.query("canonical_addresses", new String[] {"address"}, "_id = ?", new String[] {String.valueOf(recipientId)}, null, null, null); - - if (resolved != null && resolved.moveToNext()) { - String address = resolved.getString(0); - File destination = new File(session.getParentFile(), address + (deviceId != 1 ? "." + deviceId : "")); - - if (!session.renameTo(destination)) { - Log.w(TAG, "Session rename failed: " + destination); - } - } - - if (resolved != null) resolved.close(); - } catch (NumberFormatException e) { - Log.w(TAG, e); - } - } - } - - } - - if (oldVersion < NO_MORE_RECIPIENTS_PLURAL) { - db.execSQL("ALTER TABLE groups ADD COLUMN mms INTEGER DEFAULT 0"); - - Cursor cursor = db.query("thread", new String[] {"_id", "recipient_ids"}, null, null, null, null, null); - - while (cursor != null && cursor.moveToNext()) { - long threadId = cursor.getLong(0); - String addressListString = cursor.getString(1); - String[] addressList = DelimiterUtil.split(addressListString, ' '); - - if (addressList.length == 1) { - ContentValues contentValues = new ContentValues(); - contentValues.put("recipient_ids", DelimiterUtil.unescape(addressListString, ' ')); - db.update("thread", contentValues, "_id = ?", new String[] {String.valueOf(threadId)}); - } else { - byte[] groupId = new byte[16]; - List members = new LinkedList<>(); - - new SecureRandom().nextBytes(groupId); - - for (String address : addressList) { - members.add(DelimiterUtil.escape(DelimiterUtil.unescape(address, ' '), ',')); - } - - members.add(DelimiterUtil.escape(TextSecurePreferences.getStringPreference(context, "pref_local_number", null), ',')); - - Collections.sort(members); - - String encodedGroupId = "__signal_mms_group__!" + Hex.toStringCondensed(groupId); - ContentValues groupValues = new ContentValues(); - ContentValues threadValues = new ContentValues(); - - groupValues.put("group_id", encodedGroupId); - groupValues.put("members", Util.join(members, ",")); - groupValues.put("mms", 1); - - threadValues.put("recipient_ids", encodedGroupId); - - db.insert("groups", null, groupValues); - db.update("thread", threadValues, "_id = ?", new String[] {String.valueOf(threadId)}); - db.update("recipient_preferences", threadValues, "recipient_ids = ?", new String[] {addressListString}); - } - } - - if (cursor != null) cursor.close(); - - cursor = db.query("recipient_preferences", new String[] {"_id", "recipient_ids"}, null, null, null, null, null); - - while (cursor != null && cursor.moveToNext()) { - long id = cursor.getLong(0); - String addressListString = cursor.getString(1); - String[] addressList = DelimiterUtil.split(addressListString, ' '); - - if (addressList.length == 1) { - ContentValues contentValues = new ContentValues(); - contentValues.put("recipient_ids", DelimiterUtil.unescape(addressListString, ' ')); - db.update("recipient_preferences", contentValues, "_id = ?", new String[] {String.valueOf(id)}); - } else { - Log.w(TAG, "Found preferences for MMS thread that appears to be gone: " + addressListString); - db.delete("recipient_preferences", "_id = ?", new String[] {String.valueOf(id)}); - } - } - - if (cursor != null) cursor.close(); - - cursor = db.rawQuery("SELECT mms._id, thread.recipient_ids FROM mms, thread WHERE mms.address IS NULL AND mms.thread_id = thread._id", null); - - while (cursor != null && cursor.moveToNext()) { - long id = cursor.getLong(0); - ContentValues contentValues = new ContentValues(1); - - contentValues.put("address", cursor.getString(1)); - db.update("mms", contentValues, "_id = ?", new String[] {String.valueOf(id)}); - } - - if (cursor != null) cursor.close(); - } - - if (oldVersion < INTERNAL_DIRECTORY) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN registered INTEGER DEFAULT 0"); - - if (isValidNumber(TextSecurePreferences.getStringPreference(context, "pref_local_number", null))) { - OldDirectoryDatabaseHelper directoryDatabaseHelper = new OldDirectoryDatabaseHelper(context); - SQLiteDatabase directoryDatabase = directoryDatabaseHelper.getWritableDatabase(); - - Cursor cursor = directoryDatabase.query("directory", new String[] {"number", "registered"}, null, null, null, null, null); - - while (cursor != null && cursor.moveToNext()) { - String address = new NumberMigrator(TextSecurePreferences.getStringPreference(context, "pref_local_number", null)).migrate(cursor.getString(0)); - ContentValues contentValues = new ContentValues(1); - - contentValues.put("registered", cursor.getInt(1) == 1 ? 1 : 2); - - if (db.update("recipient_preferences", contentValues, "recipient_ids = ?", new String[] {address}) < 1) { - contentValues.put("recipient_ids", address); - db.insert("recipient_preferences", null, contentValues); - } - } - - if (cursor != null) cursor.close(); - } - } - - if (oldVersion < INTERNAL_SYSTEM_DISPLAY_NAME) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_display_name TEXT DEFAULT NULL"); - } - - if (oldVersion < PROFILES) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN profile_key TEXT DEFAULT NULL"); - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN signal_profile_name TEXT DEFAULT NULL"); - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN signal_profile_avatar TEXT DEFAULT NULL"); - } - - if (oldVersion < PROFILE_SHARING_APPROVAL) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN profile_sharing_approval INTEGER DEFAULT 0"); - } - - if (oldVersion < UNSEEN_NUMBER_OFFER) { - db.execSQL("ALTER TABLE thread ADD COLUMN has_sent INTEGER DEFAULT 0"); - } - - if (oldVersion < READ_RECEIPTS) { - db.execSQL("ALTER TABLE sms ADD COLUMN read_receipt_count INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE mms ADD COLUMN read_receipt_count INTEGER DEFAULT 0"); - db.execSQL("ALTER TABLE thread ADD COLUMN read_receipt_count INTEGER DEFAULT 0"); - } - - if (oldVersion < GROUP_RECEIPT_TRACKING) { - db.execSQL("CREATE TABLE group_receipts (_id INTEGER PRIMARY KEY, mms_id INTEGER, address TEXT, status INTEGER, timestamp INTEGER)"); - db.execSQL("CREATE INDEX IF NOT EXISTS group_receipt_mms_id_index ON group_receipts (mms_id)"); - } - - if (oldVersion < UNREAD_COUNT_VERSION) { - db.execSQL("ALTER TABLE thread ADD COLUMN unread_count INTEGER DEFAULT 0"); - - try (Cursor cursor = db.query("thread", new String[] {"_id"}, "read = 0", null, null, null, null)) { - while (cursor != null && cursor.moveToNext()) { - long threadId = cursor.getLong(0); - int unreadCount = 0; - - try (Cursor smsCursor = db.rawQuery("SELECT COUNT(*) FROM sms WHERE thread_id = ? AND read = '0'", new String[] {String.valueOf(threadId)})) { - if (smsCursor != null && smsCursor.moveToFirst()) { - unreadCount += smsCursor.getInt(0); - } - } - - try (Cursor mmsCursor = db.rawQuery("SELECT COUNT(*) FROM mms WHERE thread_id = ? AND read = '0'", new String[] {String.valueOf(threadId)})) { - if (mmsCursor != null && mmsCursor.moveToFirst()) { - unreadCount += mmsCursor.getInt(0); - } - } - - db.execSQL("UPDATE thread SET unread_count = ? WHERE _id = ?", - new String[] {String.valueOf(unreadCount), - String.valueOf(threadId)}); - } - } - } - - if (oldVersion < MORE_RECIPIENT_FIELDS) { - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_contact_photo TEXT DEFAULT NULL"); - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_phone_label TEXT DEFAULT NULL"); - db.execSQL("ALTER TABLE recipient_preferences ADD COLUMN system_contact_uri TEXT DEFAULT NULL"); - - if (Permissions.hasAny(context, Manifest.permission.READ_CONTACTS, Manifest.permission.WRITE_CONTACTS)) { - try (Cursor cursor = db.query("recipient_preferences", null, null, null, null, null, null)) { - while (cursor != null && cursor.moveToNext()) { - String address = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids")); - - if (!TextUtils.isEmpty(address) && !GroupId.isEncodedGroup(address) && !NumberUtil.isValidEmail(address)) { - Uri lookup = Uri.withAppendedPath(ContactsContract.PhoneLookup.CONTENT_FILTER_URI, Uri.encode(address)); - - try (Cursor contactCursor = context.getContentResolver().query(lookup, new String[] {ContactsContract.PhoneLookup.DISPLAY_NAME, - ContactsContract.PhoneLookup.LOOKUP_KEY, - ContactsContract.PhoneLookup._ID, - ContactsContract.PhoneLookup.NUMBER, - ContactsContract.PhoneLookup.LABEL, - ContactsContract.PhoneLookup.PHOTO_URI}, - null, null, null)) - { - if (contactCursor != null && contactCursor.moveToFirst()) { - ContentValues contentValues = new ContentValues(3); - contentValues.put("system_contact_photo", contactCursor.getString(5)); - contentValues.put("system_phone_label", contactCursor.getString(4)); - contentValues.put("system_contact_uri", ContactsContract.Contacts.getLookupUri(contactCursor.getLong(2), contactCursor.getString(1)).toString()); - - db.update("recipient_preferences", contentValues, "recipient_ids = ?", new String[] {address}); - } - } - } - } - } - } - } - - db.setTransactionSuccessful(); - db.endTransaction(); - } - - private void executeStatements(SQLiteDatabase db, String[] statements) { - for (String statement : statements) - db.execSQL(statement); - } - - private static class PreCanonicalAddressIdentityMismatchList { - @JsonProperty(value = "m") - private List list; - } - - private static class PostCanonicalAddressIdentityMismatchList { - @JsonProperty(value = "m") - private List list; - - public PostCanonicalAddressIdentityMismatchList(List list) { - this.list = list; - } - } - - private static class PreCanonicalAddressIdentityMismatchDocument { - @JsonProperty(value = "r") - private long recipientId; - - @JsonProperty(value = "k") - private String identityKey; - } - - private static class PostCanonicalAddressIdentityMismatchDocument { - @JsonProperty(value = "a") - private String address; - - @JsonProperty(value = "k") - private String identityKey; - - public PostCanonicalAddressIdentityMismatchDocument() {} - - public PostCanonicalAddressIdentityMismatchDocument(String address, String identityKey) { - this.address = address; - this.identityKey = identityKey; - } - } - - private static class PreCanonicalAddressNetworkFailureList { - @JsonProperty(value = "l") - private List list; - } - - private static class PostCanonicalAddressNetworkFailureList { - @JsonProperty(value = "l") - private List list; - - public PostCanonicalAddressNetworkFailureList(List list) { - this.list = list; - } - } - - private static class PreCanonicalAddressNetworkFailureDocument { - @JsonProperty(value = "r") - private long recipientId; - } - - private static class PostCanonicalAddressNetworkFailureDocument { - @JsonProperty(value = "a") - private String address; - - public PostCanonicalAddressNetworkFailureDocument() {} - - public PostCanonicalAddressNetworkFailureDocument(String address) { - this.address = address; - } - } - - private static boolean isValidNumber(@Nullable String number) { - if (TextUtils.isEmpty(number)) { - return false; - } - - try { - PhoneNumberUtil.getInstance().parse(number, null); - return true; - } catch (NumberParseException e) { - return false; - } - } - - private static class NumberMigrator { - - private static final String TAG = Log.tag(NumberMigrator.class); - - private static final Set SHORT_COUNTRIES = new HashSet() {{ - add("NU"); - add("TK"); - add("NC"); - add("AC"); - }}; - - private final Phonenumber.PhoneNumber localNumber; - private final String localNumberString; - private final String localCountryCode; - - private final PhoneNumberUtil phoneNumberUtil = PhoneNumberUtil.getInstance(); - private final Pattern ALPHA_PATTERN = Pattern.compile("[a-zA-Z]"); - - - public NumberMigrator(String localNumber) { - try { - this.localNumberString = localNumber; - this.localNumber = phoneNumberUtil.parse(localNumber, null); - this.localCountryCode = phoneNumberUtil.getRegionCodeForNumber(this.localNumber); - } catch (NumberParseException e) { - throw new AssertionError(e); - } - } - - public String migrate(@Nullable String number) { - if (number == null) return "Unknown"; - if (number.startsWith("__textsecure_group__!")) return number; - if (ALPHA_PATTERN.matcher(number).find()) return number.trim(); - - String bareNumber = number.replaceAll("[^0-9+]", ""); - - if (bareNumber.length() == 0) { - if (TextUtils.isEmpty(number.trim())) return "Unknown"; - else return number.trim(); - } - - // libphonenumber doesn't seem to be correct for Germany and Finland - if (bareNumber.length() <= 6 && ("DE".equals(localCountryCode) || "FI".equals(localCountryCode) || "SK".equals(localCountryCode))) { - return bareNumber; - } - - // libphonenumber seems incorrect for Russia and a few other countries with 4 digit short codes. - if (bareNumber.length() <= 4 && !SHORT_COUNTRIES.contains(localCountryCode)) { - return bareNumber; - } - - try { - Phonenumber.PhoneNumber parsedNumber = phoneNumberUtil.parse(bareNumber, localCountryCode); - - if (ShortNumberInfo.getInstance().isPossibleShortNumberForRegion(parsedNumber, localCountryCode)) { - return bareNumber; - } - - return phoneNumberUtil.format(parsedNumber, PhoneNumberUtil.PhoneNumberFormat.E164); - } catch (NumberParseException e) { - Log.w(TAG, e); - if (bareNumber.charAt(0) == '+') - return bareNumber; - - String localNumberImprecise = localNumberString; - - if (localNumberImprecise.charAt(0) == '+') - localNumberImprecise = localNumberImprecise.substring(1); - - if (localNumberImprecise.length() == bareNumber.length() || bareNumber.length() > localNumberImprecise.length()) - return "+" + number; - - int difference = localNumberImprecise.length() - bareNumber.length(); - - return "+" + localNumberImprecise.substring(0, difference) + bareNumber; - } - } - } - - private static class OldDirectoryDatabaseHelper extends SQLiteOpenHelper { - - private static final int INTRODUCED_CHANGE_FROM_TOKEN_TO_E164_NUMBER = 2; - private static final int INTRODUCED_VOICE_COLUMN = 4; - private static final int INTRODUCED_VIDEO_COLUMN = 5; - - private static final String DATABASE_NAME = "whisper_directory.db"; - private static final int DATABASE_VERSION = 5; - - private static final String TABLE_NAME = "directory"; - private static final String ID = "_id"; - private static final String NUMBER = "number"; - private static final String REGISTERED = "registered"; - private static final String RELAY = "relay"; - private static final String TIMESTAMP = "timestamp"; - private static final String VOICE = "voice"; - private static final String VIDEO = "video"; - - private static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + "(" + ID + " INTEGER PRIMARY KEY, " + - NUMBER + " TEXT UNIQUE, " + - REGISTERED + " INTEGER, " + - RELAY + " TEXT, " + - TIMESTAMP + " INTEGER, " + - VOICE + " INTEGER, " + - VIDEO + " INTEGER);"; - - public OldDirectoryDatabaseHelper(Context context) { - super(context, DATABASE_NAME, null, DATABASE_VERSION); - } - - @Override - public void onCreate(SQLiteDatabase db) { - db.execSQL(CREATE_TABLE); - } - - @Override - public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { - if (oldVersion < INTRODUCED_CHANGE_FROM_TOKEN_TO_E164_NUMBER) { - db.execSQL("DROP TABLE directory;"); - db.execSQL("CREATE TABLE directory ( _id INTEGER PRIMARY KEY, " + - "number TEXT UNIQUE, " + - "registered INTEGER, " + - "relay TEXT, " + - "supports_sms INTEGER, " + - "timestamp INTEGER);"); - } - - if (oldVersion < INTRODUCED_VOICE_COLUMN) { - db.execSQL("ALTER TABLE directory ADD COLUMN voice INTEGER;"); - } - - if (oldVersion < INTRODUCED_VIDEO_COLUMN) { - db.execSQL("ALTER TABLE directory ADD COLUMN video INTEGER;"); - } - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/PreKeyMigrationHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/PreKeyMigrationHelper.java deleted file mode 100644 index 04060f90c9..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/PreKeyMigrationHelper.java +++ /dev/null @@ -1,227 +0,0 @@ -package org.thoughtcrime.securesms.database.helpers; - - -import android.content.ContentValues; -import android.content.Context; - -import androidx.annotation.NonNull; - -import com.fasterxml.jackson.annotation.JsonProperty; - -import net.zetetic.database.sqlcipher.SQLiteDatabase; - -import org.signal.core.util.Conversions; -import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.InvalidKeyException; -import org.signal.libsignal.protocol.InvalidMessageException; -import org.signal.libsignal.protocol.state.PreKeyRecord; -import org.signal.libsignal.protocol.state.SignedPreKeyRecord; -import org.thoughtcrime.securesms.database.OneTimePreKeyTable; -import org.thoughtcrime.securesms.database.SignedPreKeyTable; -import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.signal.core.util.Base64; -import org.thoughtcrime.securesms.util.JsonUtils; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStreamReader; - -public final class PreKeyMigrationHelper { - - private static final String PREKEY_DIRECTORY = "prekeys"; - private static final String SIGNED_PREKEY_DIRECTORY = "signed_prekeys"; - - private static final int PLAINTEXT_VERSION = 2; - private static final int CURRENT_VERSION_MARKER = 2; - - private static final String TAG = Log.tag(PreKeyMigrationHelper.class); - - public static boolean migratePreKeys(Context context, SQLiteDatabase database) { - File[] preKeyFiles = getPreKeyDirectory(context).listFiles(); - boolean clean = true; - - if (preKeyFiles != null) { - for (File preKeyFile : preKeyFiles) { - if (!"index.dat".equals(preKeyFile.getName())) { - try { - PreKeyRecord preKey = new PreKeyRecord(loadSerializedRecord(preKeyFile)); - - ContentValues contentValues = new ContentValues(); - contentValues.put(OneTimePreKeyTable.KEY_ID, preKey.getId()); - contentValues.put(OneTimePreKeyTable.PUBLIC_KEY, Base64.encodeWithPadding(preKey.getKeyPair().getPublicKey().serialize())); - contentValues.put(OneTimePreKeyTable.PRIVATE_KEY, Base64.encodeWithPadding(preKey.getKeyPair().getPrivateKey().serialize())); - database.insert(OneTimePreKeyTable.TABLE_NAME, null, contentValues); - Log.i(TAG, "Migrated one-time prekey: " + preKey.getId()); - } catch (IOException | InvalidMessageException | InvalidKeyException e) { - Log.w(TAG, e); - clean = false; - } - } - } - } - - File[] signedPreKeyFiles = getSignedPreKeyDirectory(context).listFiles(); - - if (signedPreKeyFiles != null) { - for (File signedPreKeyFile : signedPreKeyFiles) { - if (!"index.dat".equals(signedPreKeyFile.getName())) { - try { - SignedPreKeyRecord signedPreKey = new SignedPreKeyRecord(loadSerializedRecord(signedPreKeyFile)); - - ContentValues contentValues = new ContentValues(); - contentValues.put(SignedPreKeyTable.KEY_ID, signedPreKey.getId()); - contentValues.put(SignedPreKeyTable.PUBLIC_KEY, Base64.encodeWithPadding(signedPreKey.getKeyPair().getPublicKey().serialize())); - contentValues.put(SignedPreKeyTable.PRIVATE_KEY, Base64.encodeWithPadding(signedPreKey.getKeyPair().getPrivateKey().serialize())); - contentValues.put(SignedPreKeyTable.SIGNATURE, Base64.encodeWithPadding(signedPreKey.getSignature())); - contentValues.put(SignedPreKeyTable.TIMESTAMP, signedPreKey.getTimestamp()); - database.insert(SignedPreKeyTable.TABLE_NAME, null, contentValues); - Log.i(TAG, "Migrated signed prekey: " + signedPreKey.getId()); - } catch (IOException | InvalidMessageException | InvalidKeyException e) { - Log.w(TAG, e); - clean = false; - } - } - } - } - - File oneTimePreKeyIndex = new File(getPreKeyDirectory(context), PreKeyIndex.FILE_NAME); - File signedPreKeyIndex = new File(getSignedPreKeyDirectory(context), SignedPreKeyIndex.FILE_NAME); - - if (oneTimePreKeyIndex.exists()) { - try { - InputStreamReader reader = new InputStreamReader(new FileInputStream(oneTimePreKeyIndex)); - PreKeyIndex index = JsonUtils.fromJson(reader, PreKeyIndex.class); - reader.close(); - - Log.i(TAG, "Setting next prekey id: " + index.nextPreKeyId); - SignalStore.account().aciPreKeys().setNextEcOneTimePreKeyId(index.nextPreKeyId); - } catch (IOException e) { - Log.w(TAG, e); - } - } - - if (signedPreKeyIndex.exists()) { - try { - InputStreamReader reader = new InputStreamReader(new FileInputStream(signedPreKeyIndex)); - SignedPreKeyIndex index = JsonUtils.fromJson(reader, SignedPreKeyIndex.class); - reader.close(); - - Log.i(TAG, "Setting next signed prekey id: " + index.nextSignedPreKeyId); - Log.i(TAG, "Setting active signed prekey id: " + index.activeSignedPreKeyId); - SignalStore.account().aciPreKeys().setNextSignedPreKeyId(index.nextSignedPreKeyId); - SignalStore.account().aciPreKeys().setActiveSignedPreKeyId(index.activeSignedPreKeyId); - } catch (IOException e) { - Log.w(TAG, e); - } - } - - return clean; - } - - public static void cleanUpPreKeys(@NonNull Context context) { - File preKeyDirectory = getPreKeyDirectory(context); - File[] preKeyFiles = preKeyDirectory.listFiles(); - - if (preKeyFiles != null) { - for (File preKeyFile : preKeyFiles) { - Log.i(TAG, "Deleting: " + preKeyFile.getAbsolutePath()); - preKeyFile.delete(); - } - - Log.i(TAG, "Deleting: " + preKeyDirectory.getAbsolutePath()); - preKeyDirectory.delete(); - } - - File signedPreKeyDirectory = getSignedPreKeyDirectory(context); - File[] signedPreKeyFiles = signedPreKeyDirectory.listFiles(); - - if (signedPreKeyFiles != null) { - for (File signedPreKeyFile : signedPreKeyFiles) { - Log.i(TAG, "Deleting: " + signedPreKeyFile.getAbsolutePath()); - signedPreKeyFile.delete(); - } - - Log.i(TAG, "Deleting: " + signedPreKeyDirectory.getAbsolutePath()); - signedPreKeyDirectory.delete(); - } - } - - private static byte[] loadSerializedRecord(File recordFile) - throws IOException, InvalidMessageException - { - FileInputStream fin = new FileInputStream(recordFile); - int recordVersion = readInteger(fin); - - if (recordVersion > CURRENT_VERSION_MARKER) { - throw new IOException("Invalid version: " + recordVersion); - } - - byte[] serializedRecord = readBlob(fin); - - if (recordVersion < PLAINTEXT_VERSION) { - throw new IOException("Migration didn't happen! " + recordFile.getAbsolutePath() + ", " + recordVersion); - } - - fin.close(); - return serializedRecord; - } - - private static File getPreKeyDirectory(Context context) { - return getRecordsDirectory(context, PREKEY_DIRECTORY); - } - - private static File getSignedPreKeyDirectory(Context context) { - return getRecordsDirectory(context, SIGNED_PREKEY_DIRECTORY); - } - - private static File getRecordsDirectory(Context context, String directoryName) { - File directory = new File(context.getFilesDir(), directoryName); - - if (!directory.exists()) { - if (!directory.mkdirs()) { - Log.w(TAG, "PreKey directory creation failed!"); - } - } - - return directory; - } - - private static byte[] readBlob(FileInputStream in) throws IOException { - int length = readInteger(in); - byte[] blobBytes = new byte[length]; - - in.read(blobBytes, 0, blobBytes.length); - return blobBytes; - } - - private static int readInteger(FileInputStream in) throws IOException { - byte[] integer = new byte[4]; - in.read(integer, 0, integer.length); - return Conversions.byteArrayToInt(integer); - } - - private static class PreKeyIndex { - static final String FILE_NAME = "index.dat"; - - @JsonProperty - private int nextPreKeyId; - - public PreKeyIndex() {} - } - - private static class SignedPreKeyIndex { - static final String FILE_NAME = "index.dat"; - - @JsonProperty - private int nextSignedPreKeyId; - - @JsonProperty - private int activeSignedPreKeyId = -1; - - public SignedPreKeyIndex() {} - - } - - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdCleanupHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdCleanupHelper.java deleted file mode 100644 index 9a9d902d78..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdCleanupHelper.java +++ /dev/null @@ -1,91 +0,0 @@ -package org.thoughtcrime.securesms.database.helpers; - -import android.database.Cursor; -import android.text.TextUtils; - -import androidx.annotation.NonNull; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.database.SQLiteDatabase; -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/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java deleted file mode 100644 index 50d984c13b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/RecipientIdMigrationHelper.java +++ /dev/null @@ -1,282 +0,0 @@ -package org.thoughtcrime.securesms.database.helpers; - -import android.content.ContentValues; -import android.database.Cursor; -import android.text.TextUtils; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import net.zetetic.database.sqlcipher.SQLiteDatabase; - -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.groups.GroupId; -import org.thoughtcrime.securesms.phonenumbers.NumberUtil; -import org.thoughtcrime.securesms.util.DelimiterUtil; -import org.signal.core.util.Util; - -import java.util.HashSet; -import java.util.Set; - -public class RecipientIdMigrationHelper { - - private static final String TAG = Log.tag(RecipientIdMigrationHelper.class); - - public static void execute(SQLiteDatabase db) { - Log.i(TAG, "Starting the recipient ID migration."); - - long insertStart = System.currentTimeMillis(); - - Log.i(TAG, "Starting inserts for missing recipients."); - db.execSQL(buildInsertMissingRecipientStatement("identities", "address")); - db.execSQL(buildInsertMissingRecipientStatement("sessions", "address")); - db.execSQL(buildInsertMissingRecipientStatement("thread", "recipient_ids")); - db.execSQL(buildInsertMissingRecipientStatement("sms", "address")); - db.execSQL(buildInsertMissingRecipientStatement("mms", "address")); - db.execSQL(buildInsertMissingRecipientStatement("mms", "quote_author")); - db.execSQL(buildInsertMissingRecipientStatement("group_receipts", "address")); - db.execSQL(buildInsertMissingRecipientStatement("groups", "group_id")); - Log.i(TAG, "Finished inserts for missing recipients in " + (System.currentTimeMillis() - insertStart) + " ms."); - - long updateMissingStart = System.currentTimeMillis(); - - Log.i(TAG, "Starting updates for invalid or missing addresses."); - db.execSQL(buildMissingAddressUpdateStatement("sms", "address")); - db.execSQL(buildMissingAddressUpdateStatement("mms", "address")); - db.execSQL(buildMissingAddressUpdateStatement("mms", "quote_author")); - Log.i(TAG, "Finished updates for invalid or missing addresses in " + (System.currentTimeMillis() - updateMissingStart) + " ms."); - - db.execSQL("ALTER TABLE groups ADD COLUMN recipient_id INTEGER DEFAULT 0"); - - long updateStart = System.currentTimeMillis(); - - Log.i(TAG, "Starting recipient ID updates."); - db.execSQL(buildUpdateAddressToRecipientIdStatement("identities", "address")); - db.execSQL(buildUpdateAddressToRecipientIdStatement("sessions", "address")); - db.execSQL(buildUpdateAddressToRecipientIdStatement("thread", "recipient_ids")); - db.execSQL(buildUpdateAddressToRecipientIdStatement("sms", "address")); - db.execSQL(buildUpdateAddressToRecipientIdStatement("mms", "address")); - db.execSQL(buildUpdateAddressToRecipientIdStatement("mms", "quote_author")); - db.execSQL(buildUpdateAddressToRecipientIdStatement("group_receipts", "address")); - db.execSQL("UPDATE groups SET recipient_id = (SELECT _id FROM recipient_preferences WHERE recipient_preferences.recipient_ids = groups.group_id)"); - Log.i(TAG, "Finished recipient ID updates in " + (System.currentTimeMillis() - updateStart) + " ms."); - - // NOTE: Because there's an open cursor on the same table, inserts and updates aren't visible - // until afterwards, which is why this group stuff is split into multiple loops - - long findGroupStart = System.currentTimeMillis(); - - Log.i(TAG, "Starting to find missing group recipients."); - Set missingGroupMembers = new HashSet<>(); - - 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 rawMember : members) { - String member = DelimiterUtil.unescape(rawMember, ','); - - if (!TextUtils.isEmpty(member) && !recipientExists(db, member)) { - missingGroupMembers.add(member); - } - } - } - } - Log.i(TAG, "Finished finding " + missingGroupMembers.size() + " missing group recipients in " + (System.currentTimeMillis() - findGroupStart) + " ms."); - - long insertGroupStart = System.currentTimeMillis(); - - Log.i(TAG, "Starting the insert of missing group recipients."); - for (String member : missingGroupMembers) { - ContentValues values = new ContentValues(); - values.put("recipient_ids", member); - db.insert("recipient_preferences", null, values); - } - Log.i(TAG, "Finished inserting missing group recipients in " + (System.currentTimeMillis() - insertGroupStart) + " ms."); - - long updateGroupStart = System.currentTimeMillis(); - - Log.i(TAG, "Starting group recipient ID updates."); - try (Cursor cursor = db.rawQuery("SELECT _id, members FROM groups", null)) { - while (cursor != null && cursor.moveToNext()) { - long groupId = cursor.getLong(cursor.getColumnIndexOrThrow("_id")); - String serializedMembers = cursor.getString(cursor.getColumnIndexOrThrow("members")); - String[] members = DelimiterUtil.split(serializedMembers, ','); - long[] memberIds = new long[members.length]; - - for (int i = 0; i < members.length; i++) { - String member = DelimiterUtil.unescape(members[i], ','); - memberIds[i] = requireRecipientId(db, member); - } - - String serializedMemberIds = Util.join(memberIds, ","); - - db.execSQL("UPDATE groups SET members = ? WHERE _id = ?", new String[]{ serializedMemberIds, String.valueOf(groupId) }); - } - } - db.execSQL("CREATE UNIQUE INDEX IF NOT EXISTS group_recipient_id_index ON groups (recipient_id)"); - Log.i(TAG, "Finished group recipient ID updates in " + (System.currentTimeMillis() - updateGroupStart) + " ms."); - - - long tableCopyStart = System.currentTimeMillis(); - - Log.i(TAG, "Starting to copy the recipient table."); - db.execSQL("CREATE TABLE recipient (_id INTEGER PRIMARY KEY AUTOINCREMENT, " + - "uuid TEXT UNIQUE DEFAULT NULL, " + - "phone TEXT UNIQUE DEFAULT NULL, " + - "email TEXT UNIQUE DEFAULT NULL, " + - "group_id TEXT UNIQUE DEFAULT NULL, " + - "blocked INTEGER DEFAULT 0, " + - "message_ringtone TEXT DEFAULT NULL, " + - "message_vibrate INTEGER DEFAULT 0, " + - "call_ringtone TEXT DEFAULT NULL, " + - "call_vibrate INTEGER DEFAULT 0, " + - "notification_channel TEXT DEFAULT NULL, " + - "mute_until INTEGER DEFAULT 0, " + - "color TEXT DEFAULT NULL, " + - "seen_invite_reminder INTEGER DEFAULT 0, " + - "default_subscription_id INTEGER DEFAULT -1, " + - "message_expiration_time INTEGER DEFAULT 0, " + - "registered INTEGER DEFAULT 0, " + - "system_display_name TEXT DEFAULT NULL, " + - "system_photo_uri TEXT DEFAULT NULL, " + - "system_phone_label TEXT DEFAULT NULL, " + - "system_contact_uri TEXT DEFAULT NULL, " + - "profile_key TEXT DEFAULT NULL, " + - "signal_profile_name TEXT DEFAULT NULL, " + - "signal_profile_avatar TEXT DEFAULT NULL, " + - "profile_sharing INTEGER DEFAULT 0, " + - "unidentified_access_mode INTEGER DEFAULT 0, " + - "force_sms_selection INTEGER DEFAULT 0)"); - - try (Cursor cursor = db.query("recipient_preferences", null, null, null, null, null, null)) { - while (cursor != null && cursor.moveToNext()) { - String address = cursor.getString(cursor.getColumnIndexOrThrow("recipient_ids")); - boolean isGroup = GroupId.isEncodedGroup(address); - boolean isEmail = !isGroup && NumberUtil.isValidEmail(address); - boolean isPhone = !isGroup && !isEmail; - - ContentValues values = new ContentValues(); - - values.put("_id", cursor.getLong(cursor.getColumnIndexOrThrow("_id"))); - values.put("uuid", (String) null); - values.put("phone", isPhone ? address : null); - values.put("email", isEmail ? address : null); - values.put("group_id", isGroup ? address : null); - values.put("blocked", cursor.getInt(cursor.getColumnIndexOrThrow("block"))); - values.put("message_ringtone", cursor.getString(cursor.getColumnIndexOrThrow("notification"))); - values.put("message_vibrate", cursor.getString(cursor.getColumnIndexOrThrow("vibrate"))); - values.put("call_ringtone", cursor.getString(cursor.getColumnIndexOrThrow("call_ringtone"))); - values.put("call_vibrate", cursor.getString(cursor.getColumnIndexOrThrow("call_vibrate"))); - values.put("notification_channel", cursor.getString(cursor.getColumnIndexOrThrow("notification_channel"))); - values.put("mute_until", cursor.getLong(cursor.getColumnIndexOrThrow("mute_until"))); - values.put("color", cursor.getString(cursor.getColumnIndexOrThrow("color"))); - values.put("seen_invite_reminder", cursor.getInt(cursor.getColumnIndexOrThrow("seen_invite_reminder"))); - values.put("default_subscription_id", cursor.getInt(cursor.getColumnIndexOrThrow("default_subscription_id"))); - values.put("message_expiration_time", cursor.getInt(cursor.getColumnIndexOrThrow("expire_messages"))); - values.put("registered", cursor.getInt(cursor.getColumnIndexOrThrow("registered"))); - values.put("system_display_name", cursor.getString(cursor.getColumnIndexOrThrow("system_display_name"))); - values.put("system_photo_uri", cursor.getString(cursor.getColumnIndexOrThrow("system_contact_photo"))); - values.put("system_phone_label", cursor.getString(cursor.getColumnIndexOrThrow("system_phone_label"))); - values.put("system_contact_uri", cursor.getString(cursor.getColumnIndexOrThrow("system_contact_uri"))); - values.put("profile_key", cursor.getString(cursor.getColumnIndexOrThrow("profile_key"))); - values.put("signal_profile_name", cursor.getString(cursor.getColumnIndexOrThrow("signal_profile_name"))); - values.put("signal_profile_avatar", cursor.getString(cursor.getColumnIndexOrThrow("signal_profile_avatar"))); - values.put("profile_sharing", cursor.getInt(cursor.getColumnIndexOrThrow("profile_sharing_approval"))); - values.put("unidentified_access_mode", cursor.getInt(cursor.getColumnIndexOrThrow("unidentified_access_mode"))); - values.put("force_sms_selection", cursor.getInt(cursor.getColumnIndexOrThrow("force_sms_selection"))); - - db.insert("recipient", null, values); - } - } - - db.execSQL("DROP TABLE recipient_preferences"); - Log.i(TAG, "Finished copying the recipient table in " + (System.currentTimeMillis() - tableCopyStart) + " ms."); - - long sanityCheckStart = System.currentTimeMillis(); - - Log.i(TAG, "Starting DB integrity sanity checks."); - assertEmptyQuery(db, "identities", buildSanityCheckQuery("identities", "address")); - assertEmptyQuery(db, "sessions", buildSanityCheckQuery("sessions", "address")); - assertEmptyQuery(db, "groups", buildSanityCheckQuery("groups", "recipient_id")); - assertEmptyQuery(db, "thread", buildSanityCheckQuery("thread", "recipient_ids")); - assertEmptyQuery(db, "sms", buildSanityCheckQuery("sms", "address")); - assertEmptyQuery(db, "mms -- address", buildSanityCheckQuery("mms", "address")); - assertEmptyQuery(db, "mms -- quote_author", buildSanityCheckQuery("mms", "quote_author")); - assertEmptyQuery(db, "group_receipts", buildSanityCheckQuery("group_receipts", "address")); - Log.i(TAG, "Finished DB integrity sanity checks in " + (System.currentTimeMillis() - sanityCheckStart) + " ms."); - - Log.i(TAG, "Finished recipient ID migration in " + (System.currentTimeMillis() - insertStart) + " ms."); - } - - private static String buildUpdateAddressToRecipientIdStatement(@NonNull String table, @NonNull String addressColumn) { - return "UPDATE " + table + " SET " + addressColumn + "=(SELECT _id " + - "FROM recipient_preferences " + - "WHERE recipient_preferences.recipient_ids = " + table + "." + addressColumn + ")"; - } - - private static String buildInsertMissingRecipientStatement(@NonNull String table, @NonNull String addressColumn) { - return "INSERT INTO recipient_preferences(recipient_ids) SELECT DISTINCT " + addressColumn + " " + - "FROM " + table + " " + - "WHERE " + addressColumn + " != '' AND " + - addressColumn + " != 'insert-address-column' AND " + - addressColumn + " NOT NULL AND " + - addressColumn + " NOT IN (SELECT recipient_ids FROM recipient_preferences)"; - } - - private static String buildMissingAddressUpdateStatement(@NonNull String table, @NonNull String addressColumn) { - return "UPDATE " + table + " SET " + addressColumn + " = -1 " + - "WHERE " + addressColumn + " = '' OR " + - addressColumn + " IS NULL OR " + - addressColumn + " = 'insert-address-token'"; - } - - private static boolean recipientExists(@NonNull SQLiteDatabase db, @NonNull String address) { - return getRecipientId(db, address) != null; - } - - private static @Nullable Long getRecipientId(@NonNull SQLiteDatabase db, @NonNull String address) { - try (Cursor cursor = db.rawQuery("SELECT _id FROM recipient_preferences WHERE recipient_ids = ?", new String[]{ address })) { - if (cursor != null && cursor.moveToFirst()) { - return cursor.getLong(cursor.getColumnIndexOrThrow("_id")); - } else { - return null; - } - } - } - - private static long requireRecipientId(@NonNull SQLiteDatabase db, @NonNull String address) { - Long id = getRecipientId(db, address); - - if (id != null) { - return id; - } else { - throw new MissingRecipientError(address); - } - } - - private static String buildSanityCheckQuery(@NonNull String table, @NonNull String idColumn) { - return "SELECT " + idColumn + " FROM " + table + " WHERE " + idColumn + " != -1 AND " + idColumn + " NOT IN (SELECT _id FROM recipient)"; - } - - private static void assertEmptyQuery(@NonNull SQLiteDatabase db, @NonNull String tag, @NonNull String query) { - try (Cursor cursor = db.rawQuery(query, null)) { - if (cursor != null && cursor.moveToFirst()) { - throw new FailedSanityCheckError(tag); - } - } - } - - private static final class MissingRecipientError extends AssertionError { - MissingRecipientError(@NonNull String address) { - super("Could not find recipient with address " + address); - } - } - - private static final class FailedSanityCheckError extends AssertionError { - FailedSanityCheckError(@NonNull String tableName) { - super("Sanity check failed for tag '" + tableName + "'"); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherMigrationHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherMigrationHelper.java deleted file mode 100644 index 8e8a463046..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherMigrationHelper.java +++ /dev/null @@ -1,259 +0,0 @@ -package org.thoughtcrime.securesms.database.helpers; - - -import android.content.ContentValues; -import android.content.Context; -import android.database.Cursor; -import android.text.TextUtils; -import kotlin.Pair; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; - -import com.annimon.stream.function.BiFunction; - -import net.zetetic.database.sqlcipher.SQLiteDatabase; - -import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.InvalidMessageException; -import org.thoughtcrime.securesms.R; -import org.thoughtcrime.securesms.crypto.AsymmetricMasterCipher; -import org.thoughtcrime.securesms.crypto.AttachmentSecretProvider; -import org.thoughtcrime.securesms.crypto.MasterCipher; -import org.thoughtcrime.securesms.crypto.MasterSecret; -import org.thoughtcrime.securesms.crypto.MasterSecretUtil; -import org.thoughtcrime.securesms.jobs.UnableToStartException; -import org.thoughtcrime.securesms.migrations.LegacyMigrationJob; -import org.thoughtcrime.securesms.service.GenericForegroundService; -import org.thoughtcrime.securesms.service.NotificationController; -import org.signal.core.util.Base64; -import org.thoughtcrime.securesms.util.TextSecurePreferences; - -import java.io.IOException; -import java.util.HashSet; -import java.util.Set; - -public class SQLCipherMigrationHelper { - - private static final String TAG = Log.tag(SQLCipherMigrationHelper.class); - - private static final long ENCRYPTION_SYMMETRIC_BIT = 0x80000000; - private static final long ENCRYPTION_ASYMMETRIC_BIT = 0x40000000; - - public static void migratePlaintext(@NonNull Context context, - @NonNull android.database.sqlite.SQLiteDatabase legacyDb, - @NonNull SQLiteDatabase modernDb) - { - modernDb.beginTransaction(); - try (NotificationController controller = GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database))) { - copyTable("identities", legacyDb, modernDb, null); - copyTable("push", legacyDb, modernDb, null); - copyTable("groups", legacyDb, modernDb, null); - copyTable("recipient_preferences", legacyDb, modernDb, null); - copyTable("group_receipts", legacyDb, modernDb, null); - modernDb.setTransactionSuccessful(); - } catch (UnableToStartException e) { - throw new IllegalStateException(e); - } finally { - modernDb.endTransaction(); - } - } - - public static void migrateCiphertext(@NonNull Context context, - @NonNull MasterSecret masterSecret, - @NonNull android.database.sqlite.SQLiteDatabase legacyDb, - @NonNull SQLiteDatabase modernDb, - @Nullable LegacyMigrationJob.DatabaseUpgradeListener listener) - { - MasterCipher legacyCipher = new MasterCipher(masterSecret); - AsymmetricMasterCipher legacyAsymmetricCipher = new AsymmetricMasterCipher(MasterSecretUtil.getAsymmetricMasterSecret(context, masterSecret)); - - modernDb.beginTransaction(); - - try (NotificationController controller = GenericForegroundService.startForegroundTask(context, context.getString(R.string.SQLCipherMigrationHelper_migrating_signal_database))) { - int total = 5000; - - copyTable("sms", legacyDb, modernDb, (row, progress) -> { - Pair plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher, - row.getAsLong("type"), - row.getAsString("body")); - - row.put("body", plaintext.getSecond()); - row.put("type", plaintext.getFirst()); - - if (listener != null && (progress.getFirst() % 1000 == 0)) { - listener.setProgress(getTotalProgress(0, progress.getFirst(), progress.getSecond()), total); - } - - return row; - }); - - copyTable("mms", legacyDb, modernDb, (row, progress) -> { - Pair plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher, - row.getAsLong("msg_box"), - row.getAsString("body")); - - row.put("body", plaintext.getSecond()); - row.put("msg_box", plaintext.getFirst()); - - if (listener != null && (progress.getFirst() % 1000 == 0)) { - listener.setProgress(getTotalProgress(1000, progress.getFirst(), progress.getSecond()), total); - } - - return row; - }); - - copyTable("part", legacyDb, modernDb, (row, progress) -> { - String fileName = row.getAsString("file_name"); - String mediaKey = row.getAsString("cd"); - - try { - if (!TextUtils.isEmpty(fileName)) { - row.put("file_name", legacyCipher.decryptBody(fileName)); - } - } catch (InvalidMessageException e) { - Log.w(TAG, e); - } - - try { - if (!TextUtils.isEmpty(mediaKey)) { - byte[] plaintext; - - if (mediaKey.startsWith("?ASYNC-")) { - plaintext = legacyAsymmetricCipher.decryptBytes(Base64.decode(mediaKey.substring("?ASYNC-".length()))); - } else { - plaintext = legacyCipher.decryptBytes(Base64.decode(mediaKey)); - } - - row.put("cd", Base64.encodeWithPadding(plaintext)); - } - } catch (IOException | InvalidMessageException e) { - Log.w(TAG, e); - } - - if (listener != null && (progress.getFirst() % 1000 == 0)) { - listener.setProgress(getTotalProgress(2000, progress.getFirst(), progress.getSecond()), total); - } - - return row; - }); - - copyTable("thread", legacyDb, modernDb, (row, progress) -> { - Long snippetType = row.getAsLong("snippet_type"); - if (snippetType == null) snippetType = 0L; - - Pair plaintext = getPlaintextBody(legacyCipher, legacyAsymmetricCipher, - snippetType, row.getAsString("snippet")); - - row.put("snippet", plaintext.getSecond()); - row.put("snippet_type", plaintext.getFirst()); - - if (listener != null && (progress.getFirst() % 1000 == 0)) { - listener.setProgress(getTotalProgress(3000, progress.getFirst(), progress.getSecond()), total); - } - - return row; - }); - - - copyTable("drafts", legacyDb, modernDb, (row, progress) -> { - String draftType = row.getAsString("type"); - String draft = row.getAsString("value"); - - try { - if (!TextUtils.isEmpty(draftType)) row.put("type", legacyCipher.decryptBody(draftType)); - if (!TextUtils.isEmpty(draft)) row.put("value", legacyCipher.decryptBody(draft)); - } catch (InvalidMessageException e) { - Log.w(TAG, e); - } - - if (listener != null && (progress.getFirst() % 1000 == 0)) { - listener.setProgress(getTotalProgress(4000, progress.getFirst(), progress.getSecond()), total); - } - - return row; - }); - - AttachmentSecretProvider.getInstance(context).setClassicKey(context, masterSecret.getEncryptionKey().getEncoded(), masterSecret.getMacKey().getEncoded()); - TextSecurePreferences.setNeedsSqlCipherMigration(context, false); - modernDb.setTransactionSuccessful(); - } catch (UnableToStartException e) { - throw new IllegalStateException(e); - } finally { - modernDb.endTransaction(); - } - } - - private static void copyTable(@NonNull String tableName, - @NonNull android.database.sqlite.SQLiteDatabase legacyDb, - @NonNull SQLiteDatabase modernDb, - @Nullable BiFunction, ContentValues> transformer) - { - Set destinationColumns = getTableColumns(tableName, modernDb); - - try (Cursor cursor = legacyDb.query(tableName, null, null, null, null, null, null)) { - int count = (cursor != null) ? cursor.getCount() : 0; - int progress = 1; - - while (cursor != null && cursor.moveToNext()) { - ContentValues row = new ContentValues(); - - for (int i=0;i(progress++, count)); - } - - modernDb.insert(tableName, null, row); - } - } - } - - private static Pair getPlaintextBody(@NonNull MasterCipher legacyCipher, - @NonNull AsymmetricMasterCipher legacyAsymmetricCipher, - long type, - @Nullable String body) - { - try { - if (!TextUtils.isEmpty(body)) { - if ((type & ENCRYPTION_SYMMETRIC_BIT) != 0) body = legacyCipher.decryptBody(body); - else if ((type & ENCRYPTION_ASYMMETRIC_BIT) != 0) body = legacyAsymmetricCipher.decryptBody(body); - } - } catch (InvalidMessageException | IOException e) { - Log.w(TAG, e); - } - - type &= ~(ENCRYPTION_SYMMETRIC_BIT); - type &= ~(ENCRYPTION_ASYMMETRIC_BIT); - - return new Pair<>(type, body); - } - - private static Set getTableColumns(String tableName, SQLiteDatabase database) { - Set results = new HashSet<>(); - - try (Cursor cursor = database.rawQuery("PRAGMA table_info(" + tableName + ")", null)) { - while (cursor != null && cursor.moveToNext()) { - results.add(cursor.getString(1)); - } - } - - return results; - } - - private static int getTotalProgress(int sectionOffset, int sectionProgress, int sectionTotal) { - double percentOfSectionComplete = ((double)sectionProgress) / ((double)sectionTotal); - return sectionOffset + (int)(((double)1000) * percentOfSectionComplete); - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SessionStoreMigrationHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SessionStoreMigrationHelper.java deleted file mode 100644 index 92fcabda9d..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SessionStoreMigrationHelper.java +++ /dev/null @@ -1,104 +0,0 @@ -package org.thoughtcrime.securesms.database.helpers; - - -import android.content.ContentValues; -import android.content.Context; - -import net.zetetic.database.sqlcipher.SQLiteDatabase; - -import org.signal.core.util.Conversions; -import org.signal.core.util.logging.Log; -import org.signal.libsignal.protocol.InvalidMessageException; -import org.signal.libsignal.protocol.state.SessionRecord; -import org.thoughtcrime.securesms.database.SessionTable; -import org.whispersystems.signalservice.api.push.SignalServiceAddress; - -import java.io.File; -import java.io.FileInputStream; -import java.io.IOException; - -public final class SessionStoreMigrationHelper { - - private static final String TAG = Log.tag(SessionStoreMigrationHelper.class); - - private static final String SESSIONS_DIRECTORY_V2 = "sessions-v2"; - private static final Object FILE_LOCK = new Object(); - - private static final int SINGLE_STATE_VERSION = 1; - private static final int ARCHIVE_STATES_VERSION = 2; - private static final int PLAINTEXT_VERSION = 3; - private static final int CURRENT_VERSION = 3; - - public static void migrateSessions(Context context, SQLiteDatabase database) { - File directory = new File(context.getFilesDir(), SESSIONS_DIRECTORY_V2); - - if (directory.exists()) { - File[] sessionFiles = directory.listFiles(); - - if (sessionFiles != null) { - for (File sessionFile : sessionFiles) { - try { - String[] parts = sessionFile.getName().split("[.]"); - String address = parts[0]; - - int deviceId; - - if (parts.length > 1) deviceId = Integer.parseInt(parts[1]); - else deviceId = SignalServiceAddress.DEFAULT_DEVICE_ID; - - FileInputStream in = new FileInputStream(sessionFile); - int versionMarker = readInteger(in); - - if (versionMarker > CURRENT_VERSION) { - throw new AssertionError("Unknown version: " + versionMarker + ", " + sessionFile.getAbsolutePath()); - } - - byte[] serialized = readBlob(in); - in.close(); - - if (versionMarker < PLAINTEXT_VERSION) { - throw new AssertionError("Not plaintext: " + versionMarker + ", " + sessionFile.getAbsolutePath()); - } - - SessionRecord sessionRecord; - - if (versionMarker == SINGLE_STATE_VERSION) { - Log.i(TAG, "Migrating single state version: " + sessionFile.getAbsolutePath()); - sessionRecord = new SessionRecord(serialized); - } else if (versionMarker >= ARCHIVE_STATES_VERSION) { - Log.i(TAG, "Migrating session: " + sessionFile.getAbsolutePath()); - sessionRecord = new SessionRecord(serialized); - } else { - throw new AssertionError("Unknown version: " + versionMarker + ", " + sessionFile.getAbsolutePath()); - } - - - ContentValues contentValues = new ContentValues(); - contentValues.put(SessionTable.ADDRESS, address); - contentValues.put(SessionTable.DEVICE, deviceId); - contentValues.put(SessionTable.RECORD, sessionRecord.serialize()); - - database.insert(SessionTable.TABLE_NAME, null, contentValues); - } catch (NumberFormatException | IOException | InvalidMessageException e) { - Log.w(TAG, e); - } - } - } - } - } - - private static byte[] readBlob(FileInputStream in) throws IOException { - int length = readInteger(in); - byte[] blobBytes = new byte[length]; - - in.read(blobBytes, 0, blobBytes.length); - return blobBytes; - } - - private static int readInteger(FileInputStream in) throws IOException { - byte[] integer = new byte[4]; - in.read(integer, 0, integer.length); - return Conversions.byteArrayToInt(integer); - } - -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt index fa27057820..59e253a340 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SignalDatabaseMigrations.kt @@ -392,8 +392,5 @@ object SignalDatabaseMigrations { @JvmStatic fun migratePostTransaction(context: Context, oldVersion: Int) { - if (oldVersion < V149_LegacyMigrations.MIGRATE_PREKEYS_VERSION) { - PreKeyMigrationHelper.cleanUpPreKeys(context) - } } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V149_LegacyMigrations.kt b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V149_LegacyMigrations.kt index 712846bd1d..687f98cc2c 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V149_LegacyMigrations.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/migration/V149_LegacyMigrations.kt @@ -31,10 +31,6 @@ import org.thoughtcrime.securesms.conversation.colors.ChatColorsMapper import org.thoughtcrime.securesms.database.KeyValueDatabase import org.thoughtcrime.securesms.database.RecipientTable import org.thoughtcrime.securesms.database.SQLiteDatabase -import org.thoughtcrime.securesms.database.helpers.PreKeyMigrationHelper -import org.thoughtcrime.securesms.database.helpers.RecipientIdCleanupHelper -import org.thoughtcrime.securesms.database.helpers.RecipientIdMigrationHelper -import org.thoughtcrime.securesms.database.helpers.SessionStoreMigrationHelper import org.thoughtcrime.securesms.database.helpers.SignalDatabaseMigrations import org.thoughtcrime.securesms.database.model.databaseprotos.ReactionList import org.thoughtcrime.securesms.groups.GroupId @@ -221,14 +217,11 @@ object V149_LegacyMigrations : SignalDatabaseMigration { db.execSQL("CREATE TABLE signed_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL, signature TEXT NOT NULL, timestamp INTEGER DEFAULT 0)") db.execSQL("CREATE TABLE one_time_prekeys (_id INTEGER PRIMARY KEY, key_id INTEGER UNIQUE, public_key TEXT NOT NULL, private_key TEXT NOT NULL)") - if (!PreKeyMigrationHelper.migratePreKeys(context, db.sqlCipherDatabase)) { - PreKeysSyncJob.enqueue() - } + PreKeysSyncJob.enqueue() } if (oldVersion < MIGRATE_SESSIONS_VERSION) { db.execSQL("CREATE TABLE sessions (_id INTEGER PRIMARY KEY, address TEXT NOT NULL, device INTEGER NOT NULL, record BLOB NOT NULL, UNIQUE(address, device) ON CONFLICT REPLACE)") - SessionStoreMigrationHelper.migrateSessions(context, db.sqlCipherDatabase) } if (oldVersion < NO_MORE_IMAGE_THUMBNAILS_VERSION) { @@ -615,7 +608,7 @@ object V149_LegacyMigrations : SignalDatabaseMigration { } if (oldVersion < RECIPIENT_IDS) { - RecipientIdMigrationHelper.execute(db.sqlCipherDatabase) + // RecipientIdMigrationHelper was removed -- migration from this old version is no longer supported } if (oldVersion < RECIPIENT_SEARCH) { @@ -638,7 +631,7 @@ object V149_LegacyMigrations : SignalDatabaseMigration { } if (oldVersion < RECIPIENT_CLEANUP) { - RecipientIdCleanupHelper.execute(db) + // RecipientIdCleanupHelper was removed -- migration from this old version is no longer supported } if (oldVersion < MMS_RECIPIENT_CLEANUP) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraint.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraint.java deleted file mode 100644 index ee624cf54b..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraint.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.impl; - -import android.app.Application; -import android.app.job.JobInfo; - -import androidx.annotation.NonNull; - -import org.thoughtcrime.securesms.jobmanager.Constraint; -import org.thoughtcrime.securesms.util.TextSecurePreferences; - -public class SqlCipherMigrationConstraint implements Constraint { - - public static final String KEY = "SqlCipherMigrationConstraint"; - - private final Application application; - - private SqlCipherMigrationConstraint(@NonNull Application application) { - this.application = application; - } - - @Override - public boolean isMet() { - return !TextSecurePreferences.getNeedsSqlCipherMigration(application); - } - - @NonNull - @Override - public String getFactoryKey() { - return KEY; - } - - @Override - public void applyToJobInfo(@NonNull JobInfo.Builder jobInfoBuilder) { - } - - public static final class Factory implements Constraint.Factory { - - private final Application application; - - public Factory(@NonNull Application application) { - this.application = application; - } - - @Override - public SqlCipherMigrationConstraint create() { - return new SqlCipherMigrationConstraint(application); - } - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraintObserver.java b/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraintObserver.java deleted file mode 100644 index 56067aa3f1..0000000000 --- a/app/src/main/java/org/thoughtcrime/securesms/jobmanager/impl/SqlCipherMigrationConstraintObserver.java +++ /dev/null @@ -1,33 +0,0 @@ -package org.thoughtcrime.securesms.jobmanager.impl; - -import androidx.annotation.NonNull; - -import org.greenrobot.eventbus.EventBus; -import org.greenrobot.eventbus.Subscribe; -import org.greenrobot.eventbus.ThreadMode; -import org.signal.core.util.logging.Log; -import org.thoughtcrime.securesms.jobmanager.ConstraintObserver; - -public class SqlCipherMigrationConstraintObserver implements ConstraintObserver { - - private static final String REASON = Log.tag(SqlCipherMigrationConstraintObserver.class); - - private Notifier notifier; - - public SqlCipherMigrationConstraintObserver() { - EventBus.getDefault().register(this); - } - - @Override - public void register(@NonNull Notifier notifier) { - this.notifier = notifier; - } - - @Subscribe(threadMode = ThreadMode.MAIN) - public void onEvent(SqlCipherNeedsMigrationEvent event) { - if (notifier != null) notifier.onConstraintMet(REASON); - } - - public static class SqlCipherNeedsMigrationEvent { - } -} diff --git a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java index 7936f8596f..832c9cef51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java +++ b/app/src/main/java/org/thoughtcrime/securesms/jobs/JobManagerFactories.java @@ -34,8 +34,6 @@ import org.thoughtcrime.securesms.jobmanager.impl.NotInCallConstraintObserver; import org.thoughtcrime.securesms.jobmanager.impl.RegisteredConstraint; import org.thoughtcrime.securesms.jobmanager.impl.RestoreAttachmentConstraint; import org.thoughtcrime.securesms.jobmanager.impl.RestoreAttachmentConstraintObserver; -import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraint; -import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver; import org.thoughtcrime.securesms.jobmanager.impl.StickersNotDownloadingConstraint; import org.thoughtcrime.securesms.jobmanager.impl.WifiConstraint; import org.thoughtcrime.securesms.jobmanager.migrations.DeprecatedJobMigration; @@ -454,7 +452,6 @@ public final class JobManagerFactories { put(RegisteredConstraint.KEY, new RegisteredConstraint.Factory()); put(RestoreAttachmentConstraint.KEY, new RestoreAttachmentConstraint.Factory(application)); put(SealedSenderConstraint.KEY, new SealedSenderConstraint.Factory()); - put(SqlCipherMigrationConstraint.KEY, new SqlCipherMigrationConstraint.Factory(application)); put(StickersNotDownloadingConstraint.KEY, new StickersNotDownloadingConstraint.Factory()); put(WifiConstraint.KEY, new WifiConstraint.Factory(application)); }}; @@ -464,7 +461,6 @@ public final class JobManagerFactories { return Arrays.asList(CellServiceConstraintObserver.getInstance(application), new ChargingAndBatteryIsNotLowConstraintObserver(application), new NetworkConstraintObserver(application), - new SqlCipherMigrationConstraintObserver(), new DecryptionsDrainedConstraintObserver(), new NotInCallConstraintObserver(), ChangeNumberConstraintObserver.INSTANCE, diff --git a/app/src/main/java/org/thoughtcrime/securesms/migrations/LegacyMigrationJob.java b/app/src/main/java/org/thoughtcrime/securesms/migrations/LegacyMigrationJob.java index fa3c0c6e00..06f5709e60 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/migrations/LegacyMigrationJob.java +++ b/app/src/main/java/org/thoughtcrime/securesms/migrations/LegacyMigrationJob.java @@ -10,7 +10,6 @@ import com.bumptech.glide.Glide; import org.signal.core.util.logging.Log; import org.thoughtcrime.securesms.attachments.DatabaseAttachment; -import org.thoughtcrime.securesms.crypto.MasterSecret; import org.thoughtcrime.securesms.database.AttachmentTable; import org.thoughtcrime.securesms.database.MessageTable; import org.thoughtcrime.securesms.database.MessageTable.MmsReader; @@ -23,7 +22,6 @@ import org.thoughtcrime.securesms.jobs.DirectoryRefreshJob; import org.thoughtcrime.securesms.jobs.PreKeysSyncJob; import org.thoughtcrime.securesms.jobs.RefreshAttributesJob; import org.thoughtcrime.securesms.keyvalue.SignalStore; -import org.thoughtcrime.securesms.service.KeyCachingService; import org.thoughtcrime.securesms.transport.RetryLaterException; import org.thoughtcrime.securesms.util.FileUtils; import org.thoughtcrime.securesms.util.TextSecurePreferences; @@ -98,15 +96,6 @@ public class LegacyMigrationJob extends MigrationJob { void performMigration() throws RetryLaterException { Log.i(TAG, "Running background upgrade.."); int lastSeenVersion = VersionTracker.getLastSeenVersion(context); - MasterSecret masterSecret = KeyCachingService.getMasterSecret(context); - - if (lastSeenVersion < SQLCIPHER && masterSecret != null) { - SignalDatabase.onApplicationLevelUpgrade(context, masterSecret, lastSeenVersion, (progress, total) -> { - Log.i(TAG, "onApplicationLevelUpgrade: " + progress + "/" + total); - }); - } else if (lastSeenVersion < SQLCIPHER) { - throw new RetryLaterException(); - } if (lastSeenVersion < NO_V1_VERSION) { File v1sessions = new File(context.getFilesDir(), "sessions"); diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java index 24c2e1b40f..8501551f7d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java +++ b/app/src/main/java/org/thoughtcrime/securesms/util/TextSecurePreferences.java @@ -26,7 +26,6 @@ import org.thoughtcrime.securesms.backup.proto.SharedPreference; import org.thoughtcrime.securesms.crypto.ProfileKeyUtil; import org.thoughtcrime.securesms.database.SignalDatabase; import org.thoughtcrime.securesms.dependencies.AppDependencies; -import org.thoughtcrime.securesms.jobmanager.impl.SqlCipherMigrationConstraintObserver; import org.thoughtcrime.securesms.keyvalue.SettingsValues; import org.thoughtcrime.securesms.keyvalue.SignalStore; import org.thoughtcrime.securesms.lock.RegistrationLockReminders; @@ -97,7 +96,6 @@ public class TextSecurePreferences { private static final String DATABASE_UNENCRYPTED_SECRET = "pref_database_unencrypted_secret"; private static final String ATTACHMENT_ENCRYPTED_SECRET = "pref_attachment_encrypted_secret"; private static final String ATTACHMENT_UNENCRYPTED_SECRET = "pref_attachment_unencrypted_secret"; - private static final String NEEDS_SQLCIPHER_MIGRATION = "pref_needs_sql_cipher_migration"; public static final String CALL_NOTIFICATIONS_PREF = "pref_call_notifications"; public static final String CALL_RINGTONE_PREF = "pref_call_ringtone"; @@ -336,15 +334,6 @@ public class TextSecurePreferences { return getLongPreference(context, BACKUP_TIME, -1); } - public static void setNeedsSqlCipherMigration(@NonNull Context context, boolean value) { - setBooleanPreference(context, NEEDS_SQLCIPHER_MIGRATION, value); - EventBus.getDefault().post(new SqlCipherMigrationConstraintObserver.SqlCipherNeedsMigrationEvent()); - } - - public static boolean getNeedsSqlCipherMigration(@NonNull Context context) { - return getBooleanPreference(context, NEEDS_SQLCIPHER_MIGRATION, false); - } - public static void setAttachmentEncryptedSecret(@NonNull Context context, @NonNull String secret) { setStringPreference(context, ATTACHMENT_ENCRYPTED_SECRET, secret); }