diff --git a/native/android/app/CMakeLists.txt b/native/android/app/CMakeLists.txt --- a/native/android/app/CMakeLists.txt +++ b/native/android/app/CMakeLists.txt @@ -130,6 +130,7 @@ "../../native_rust_library/RustAESCrypto.cpp" "../../native_rust_library/RustCSAMetadataEmitter.cpp" "../../native_rust_library/RustSecureStore.cpp" + "../../native_rust_library/RustBackupExecutor.cpp" ) file(GLOB CRYPTO_NATIVE_CODE "../../cpp/CommonCpp/CryptoTools/*.cpp") file(GLOB DB_NATIVE_CODE "../../cpp/CommonCpp/DatabaseManagers/*.cpp") diff --git a/native/android/app/src/cpp/PlatformSpecificTools.cpp b/native/android/app/src/cpp/PlatformSpecificTools.cpp --- a/native/android/app/src/cpp/PlatformSpecificTools.cpp +++ b/native/android/app/src/cpp/PlatformSpecificTools.cpp @@ -31,6 +31,27 @@ cls->getStaticMethod("getNotificationsCryptoAccountPath"); return method(cls)->toStdString(); } + + static std::string getBackupDirectoryPath() { + static const auto cls = javaClassStatic(); + static auto method = + cls->getStaticMethod("getBackupDirectoryPath"); + return method(cls)->toStdString(); + } + + static std::string + getBackupFilePath(std::string backupID, bool isAttachments) { + static const auto cls = javaClassStatic(); + static auto method = + cls->getStaticMethod("getBackupFilePath"); + return method(cls, backupID, isAttachments)->toStdString(); + } + + static void removeBackupDirectory() { + static const auto cls = javaClassStatic(); + static auto method = cls->getStaticMethod("removeBackupDirectory"); + method(cls); + } }; namespace comm { @@ -55,4 +76,28 @@ return path; } +std::string PlatformSpecificTools::getBackupDirectoryPath() { + std::string path; + NativeAndroidAccessProvider::runTask([&path]() { + path = PlatformSpecificToolsJavaClass::getBackupDirectoryPath(); + }); + return path; +} + +std::string PlatformSpecificTools::getBackupFilePath( + std::string backupID, + bool isAttachments) { + std::string path; + NativeAndroidAccessProvider::runTask([&path, backupID, isAttachments]() { + path = PlatformSpecificToolsJavaClass::getBackupFilePath( + backupID, isAttachments); + }); + return path; +} + +void PlatformSpecificTools::removeBackupDirectory() { + NativeAndroidAccessProvider::runTask( + []() { PlatformSpecificToolsJavaClass::removeBackupDirectory(); }); +} + } // namespace comm diff --git a/native/android/app/src/main/java/app/comm/android/fbjni/PlatformSpecificTools.java b/native/android/app/src/main/java/app/comm/android/fbjni/PlatformSpecificTools.java --- a/native/android/app/src/main/java/app/comm/android/fbjni/PlatformSpecificTools.java +++ b/native/android/app/src/main/java/app/comm/android/fbjni/PlatformSpecificTools.java @@ -3,9 +3,12 @@ import android.content.Context; import android.util.Log; import app.comm.android.MainApplication; +import java.io.File; +import java.lang.SecurityException; import java.nio.charset.StandardCharsets; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; + public class PlatformSpecificTools { static SecureRandom secureRandom = new SecureRandom(); @@ -26,4 +29,73 @@ .getFileStreamPath("comm_notifications_crypto_account") .getPath(); } + + public static String getBackupDirectoryPath() { + Context mainApplicationContext = + MainApplication.getMainApplicationContext(); + if (mainApplicationContext == null) { + throw new RuntimeException( + "Failed to resolve backup path - main application context not initialized."); + } + + String filesDirPath = mainApplicationContext.getFilesDir().getPath(); + String backupDirPath = String.join(File.separator, filesDirPath, "backup"); + + try { + File backupDirectory = new File(backupDirPath); + if (!backupDirectory.exists() && !backupDirectory.mkdirs()) { + throw new RuntimeException("Failed to create backup directory."); + } + return backupDirPath; + } catch (SecurityException | NullPointerException e) { + throw new RuntimeException( + "Failed to check if backup directory exists or to attempt its creation. Details: " + + e.getMessage()); + } + } + + public static String + getBackupFilePath(String backupID, boolean isAttachments) { + String backupDirPath = PlatformSpecificTools.getBackupDirectoryPath(); + + String filename; + if (isAttachments) { + filename = String.join("_", "backup", backupID, "attachments"); + } else { + filename = String.join("_", "backup", backupID); + } + return String.join(File.separator, backupDirPath, filename); + } + + public static void removeBackupDirectory() { + String backupDirPath = PlatformSpecificTools.getBackupDirectoryPath(); + try { + File backupDirectory = new File(backupDirPath); + if (!backupDirectory.exists()) { + return; + } + + File[] files = backupDirectory.listFiles(); + if (files == null && !backupDirectory.delete()) { + throw new RuntimeException("Failed to remove backup directory."); + } else if (files == null) { + return; + } + + // Backup directory structure is supposed to be flat. + for (File file : files) { + if (!file.delete()) { + throw new RuntimeException( + "Failed to remove backup file at path: " + file.getPath()); + } + } + + if (!backupDirectory.delete()) { + throw new RuntimeException("Failed to remove backup directory."); + } + } catch (NullPointerException | SecurityException e) { + throw new RuntimeException( + "Failed to remove backup directory. Details: " + e.getMessage()); + } + } } diff --git a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp --- a/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp +++ b/native/cpp/CommonCpp/DatabaseManagers/DatabaseManager.cpp @@ -2,6 +2,7 @@ #include "../Notifications/BackgroundDataStorage/NotificationsCryptoModule.h" #include "../Tools/CommSecureStore.h" #include "Logger.h" +#include "PlatformSpecificTools.h" #include "SQLiteQueryExecutor.h" namespace comm { @@ -34,6 +35,7 @@ CommSecureStore::set(CommSecureStore::deviceID, ""); CommSecureStore::set(CommSecureStore::commServicesAccessToken, ""); SQLiteQueryExecutor::clearSensitiveData(); + PlatformSpecificTools::removeBackupDirectory(); NotificationsCryptoModule::clearSensitiveData(); DatabaseManager::setDatabaseStatusAsWorkable(); } diff --git a/native/cpp/CommonCpp/Tools/PlatformSpecificTools.h b/native/cpp/CommonCpp/Tools/PlatformSpecificTools.h --- a/native/cpp/CommonCpp/Tools/PlatformSpecificTools.h +++ b/native/cpp/CommonCpp/Tools/PlatformSpecificTools.h @@ -9,6 +9,10 @@ static void generateSecureRandomBytes(crypto::OlmBuffer &buffer, size_t size); static std::string getDeviceOS(); static std::string getNotificationsCryptoAccountPath(); + static std::string getBackupDirectoryPath(); + static std::string + getBackupFilePath(std::string backupID, bool isAttachments); + static void removeBackupDirectory(); }; } // namespace comm diff --git a/native/ios/Comm.xcodeproj/project.pbxproj b/native/ios/Comm.xcodeproj/project.pbxproj --- a/native/ios/Comm.xcodeproj/project.pbxproj +++ b/native/ios/Comm.xcodeproj/project.pbxproj @@ -81,6 +81,7 @@ CBCA09062A8E0E7400F75B3E /* StaffUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBCA09052A8E0E6B00F75B3E /* StaffUtils.cpp */; }; CBCA09072A8E0E7D00F75B3E /* StaffUtils.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBCA09052A8E0E6B00F75B3E /* StaffUtils.cpp */; }; CBDEC69B28ED867000C17588 /* GlobalDBSingleton.mm in Sources */ = {isa = PBXBuildFile; fileRef = CBDEC69A28ED867000C17588 /* GlobalDBSingleton.mm */; }; + CBFBEEBA2B4ED90600729F1D /* RustBackupExecutor.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBFBEEB82B4ED90600729F1D /* RustBackupExecutor.cpp */; }; CBFE58292885852B003B94C9 /* ThreadOperations.cpp in Sources */ = {isa = PBXBuildFile; fileRef = CBFE58282885852B003B94C9 /* ThreadOperations.cpp */; }; D7DB6E0F85B2DBE15B01EC21 /* libPods-Comm.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 994BEBDD4E4959F69CEA0BC3 /* libPods-Comm.a */; }; DFD5E77C2B05181400C32B6A /* RustSecureStore.cpp in Sources */ = {isa = PBXBuildFile; fileRef = DFD5E77B2B05181400C32B6A /* RustSecureStore.cpp */; }; @@ -277,6 +278,8 @@ CBCF57AB2B05096F00EC4BC0 /* AESCryptoModuleObjCCompat.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = AESCryptoModuleObjCCompat.h; path = Comm/CommAESCryptoUtils/AESCryptoModuleObjCCompat.h; sourceTree = ""; }; CBDEC69928ED859600C17588 /* GlobalDBSingleton.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = GlobalDBSingleton.h; sourceTree = ""; }; CBDEC69A28ED867000C17588 /* GlobalDBSingleton.mm */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.objcpp; name = GlobalDBSingleton.mm; path = Comm/GlobalDBSingleton.mm; sourceTree = ""; }; + CBFBEEB82B4ED90600729F1D /* RustBackupExecutor.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; path = RustBackupExecutor.cpp; sourceTree = ""; }; + CBFBEEB92B4ED90600729F1D /* RustBackupExecutor.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RustBackupExecutor.h; sourceTree = ""; }; CBFE58272885852B003B94C9 /* ThreadOperations.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = ThreadOperations.h; path = PersistentStorageUtilities/ThreadOperationsUtilities/ThreadOperations.h; sourceTree = ""; }; CBFE58282885852B003B94C9 /* ThreadOperations.cpp */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.cpp.cpp; name = ThreadOperations.cpp; path = PersistentStorageUtilities/ThreadOperationsUtilities/ThreadOperations.cpp; sourceTree = ""; }; DFD5E77A2B05181400C32B6A /* RustSecureStore.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RustSecureStore.h; sourceTree = ""; }; @@ -593,6 +596,8 @@ 8B99AF6B28D50D4800EB5ADB /* native_rust_library */ = { isa = PBXGroup; children = ( + CBFBEEB82B4ED90600729F1D /* RustBackupExecutor.cpp */, + CBFBEEB92B4ED90600729F1D /* RustBackupExecutor.h */, CB74AB1E2B2B0C0900CBB494 /* RustCSAMetadataEmitter.cpp */, CB74AB1F2B2B0C0900CBB494 /* RustCSAMetadataEmitter.h */, DFD5E7842B052B1400C32B6A /* RustAESCrypto.cpp */, @@ -1094,6 +1099,7 @@ CBFE58292885852B003B94C9 /* ThreadOperations.cpp in Sources */, CB74AB1C2B2AFF6E00CBB494 /* CommServicesAuthMetadataEmitter.mm in Sources */, 8E3994552B039A7C00D5E950 /* UserStore.cpp in Sources */, + CBFBEEBA2B4ED90600729F1D /* RustBackupExecutor.cpp in Sources */, 7FBB2A7829E945C2002C6493 /* CommUtilsModule.cpp in Sources */, CB38B48228771C7A00171182 /* NonBlockingLock.mm in Sources */, 718DE99E2653D41C00365824 /* WorkerThread.cpp in Sources */, diff --git a/native/ios/Comm/PlatformSpecificTools.mm b/native/ios/Comm/PlatformSpecificTools.mm --- a/native/ios/Comm/PlatformSpecificTools.mm +++ b/native/ios/Comm/PlatformSpecificTools.mm @@ -37,4 +37,74 @@ .path UTF8String]); } +NSURL *getBackupDirAsURL() { + NSError *err = nil; + NSURL *documentsUrl = + [NSFileManager.defaultManager URLForDirectory:NSDocumentDirectory + inDomain:NSUserDomainMask + appropriateForURL:nil + create:false + error:&err]; + if (err) { + NSLog(@"Error: %@", err); + throw std::runtime_error( + "Failed to resolve backup path - could not find documentsUrl. " + "Details: " + + std::string([err.localizedDescription UTF8String])); + } + + NSURL *backupDir = [documentsUrl URLByAppendingPathComponent:@"backup"]; + NSError *backupDirCreateError = nil; + if (![NSFileManager.defaultManager fileExistsAtPath:backupDir.path]) { + [NSFileManager.defaultManager createDirectoryAtURL:backupDir + withIntermediateDirectories:YES + attributes:nil + error:&backupDirCreateError]; + } + if (backupDirCreateError) { + throw std::runtime_error( + "Failed to create backup directory. Details: " + + std::string([backupDirCreateError.localizedDescription UTF8String])); + } + return backupDir; +} + +std::string PlatformSpecificTools::getBackupDirectoryPath() { + return [getBackupDirAsURL().path UTF8String]; +} + +std::string PlatformSpecificTools::getBackupFilePath( + std::string backupID, + bool isAttachments) { + + NSURL *backupDir = getBackupDirAsURL(); + NSString *backupIDObjC = [NSString stringWithCString:backupID.c_str() + encoding:NSUTF8StringEncoding]; + NSString *filename; + if (isAttachments) { + filename = [@[ @"backup", backupIDObjC, @"attachments" ] + componentsJoinedByString:@"_"]; + } else { + filename = [@[ @"backup", backupIDObjC ] componentsJoinedByString:@"_"]; + } + return [[backupDir URLByAppendingPathComponent:filename].path UTF8String]; +} + +void PlatformSpecificTools::removeBackupDirectory() { + NSURL *backupDir = getBackupDirAsURL(); + if (![NSFileManager.defaultManager fileExistsAtPath:backupDir.path]) { + return; + } + + NSError *backupDirRemovalError = nil; + [NSFileManager.defaultManager removeItemAtURL:backupDir + error:&backupDirRemovalError]; + + if (backupDirRemovalError) { + throw std::runtime_error( + "Failed to remove backup directory. Details: " + + std::string([backupDirRemovalError.localizedDescription UTF8String])); + } +} + }; // namespace comm diff --git a/native/native_rust_library/RustBackupExecutor.h b/native/native_rust_library/RustBackupExecutor.h new file mode 100644 --- /dev/null +++ b/native/native_rust_library/RustBackupExecutor.h @@ -0,0 +1,10 @@ +#pragma once + +#include "cxx.h" + +namespace comm { + +rust::String getBackupDirectoryPath(); +rust::String getBackupFilePath(rust::String backupID, bool isAttachments); + +} // namespace comm diff --git a/native/native_rust_library/RustBackupExecutor.cpp b/native/native_rust_library/RustBackupExecutor.cpp new file mode 100644 --- /dev/null +++ b/native/native_rust_library/RustBackupExecutor.cpp @@ -0,0 +1,16 @@ +#include "RustBackupExecutor.h" +#include "../cpp/CommonCpp/Tools/PlatformSpecificTools.h" + +#include + +namespace comm { + +rust::String getBackupDirectoryPath() { + return rust::String(PlatformSpecificTools::getBackupDirectoryPath()); +} + +rust::String getBackupFilePath(rust::String backupID, bool isAttachments) { + return rust::String(PlatformSpecificTools::getBackupFilePath( + std::string(backupID), isAttachments)); +} +} // namespace comm diff --git a/native/native_rust_library/src/lib.rs b/native/native_rust_library/src/lib.rs --- a/native/native_rust_library/src/lib.rs +++ b/native/native_rust_library/src/lib.rs @@ -244,6 +244,23 @@ #[cxx_name = "secureStoreGet"] fn secure_store_get(key: &str) -> Result; } + + // C++ Backup creation + #[namespace = "comm"] + unsafe extern "C++" { + include!("RustBackupExecutor.h"); + + #[allow(unused)] + #[cxx_name = "getBackupDirectoryPath"] + fn get_backup_directory_path() -> Result; + + #[allow(unused)] + #[cxx_name = "getBackupFilePath"] + fn get_backup_file_path( + backup_id: String, + is_attachments: bool, + ) -> Result; + } } fn handle_string_result_as_callback(