@@ -4,7 +4,9 @@ import android.app.Activity
44import android.content.Context
55import android.content.Intent
66import android.content.SharedPreferences
7+ import android.net.Uri
78import android.os.Build
9+ import android.provider.OpenableColumns
810import android.util.Log
911import android.widget.Toast
1012import androidx.activity.ComponentActivity
@@ -63,6 +65,8 @@ class RoomBackup(var context: Context) {
6365 private var currentProcess: Int? = null
6466 private const val PROCESS_BACKUP = 1
6567 private const val PROCESS_RESTORE = 2
68+ private const val BACKUP_EXTENSION_BASE = " sqlite3"
69+ private const val BACKUP_EXTENSION_ENCRYPTED = " aes"
6670 private var backupFilename: String? = null
6771
6872 /* * Code for internal backup location, used for [backupLocation] */
@@ -173,7 +177,7 @@ class RoomBackup(var context: Context) {
173177
174178 /* *
175179 * Set custom Backup File Name, default = "$dbName-$currentTime.sqlite3"
176- *
180+ * customBackupFileName should not contain file extension. File extension(s) will be added automatically
177181 * @param customBackupFileName String
178182 */
179183 fun customBackupFileName (customBackupFileName : String ): RoomBackup {
@@ -347,13 +351,15 @@ class RoomBackup(var context: Context) {
347351 // Needed for storage permissions request
348352 currentProcess = PROCESS_BACKUP
349353
350- // Create name for backup file, if no custom name is set: Database name + currentTime +
351- // .sqlite3
354+ // Create name for backup file
355+ // if no custom name is set: Database name + currentTime + .sqlite3
356+ // for custom name: customBackupFileName + .sqlite3
357+ // if backup is encrypted: .aes will be added to filename
352358 var filename =
353- if (customBackupFileName == null ) " $dbName -${getTime()} .sqlite3 "
354- else customBackupFileName as String
359+ if (customBackupFileName == null ) " $dbName -${getTime()} .$BACKUP_EXTENSION_BASE "
360+ else " $ customBackupFileName. $BACKUP_EXTENSION_BASE "
355361 // Add .aes extension to filename, if file is encrypted
356- if (backupIsEncrypted) filename + = " .aes "
362+ if (backupIsEncrypted) filename + = " .$BACKUP_EXTENSION_ENCRYPTED "
357363 if (enableLogDebug) Log .d(TAG , " backupFilename: $filename " )
358364
359365 when (backupLocation) {
@@ -572,10 +578,11 @@ class RoomBackup(var context: Context) {
572578 * @param source File
573579 */
574580 private fun doRestore (source : File ) {
581+ val fileExtension = source.extension
582+ if (! isChosenFileValidForRestore(fileExtension)) return
575583 // Close the database
576584 roomDatabase!! .close()
577585 roomDatabase = null
578- val fileExtension = source.extension
579586 if (backupIsEncrypted) {
580587 copy(source, TEMP_BACKUP_FILE )
581588 val decryptedBytes = decryptBackup() ? : return
@@ -584,23 +591,9 @@ class RoomBackup(var context: Context) {
584591 bos.flush()
585592 bos.close()
586593 } else {
587- if (fileExtension == " aes" ) {
588- if (enableLogDebug)
589- Log .d(
590- TAG ,
591- " Cannot restore database, it is encrypted. Maybe you forgot to add the property .fileIsEncrypted(true)"
592- )
593- onCompleteListener?.onComplete(
594- false ,
595- " cannot restore database, see Log for more details (if enabled)" ,
596- OnCompleteListener .EXIT_CODE_ERROR_RESTORE_BACKUP_IS_ENCRYPTED
597- )
598- return
599- }
600594 // Copy back database and replace current database
601595 copy(source, DATABASE_FILE )
602596 }
603-
604597 if (enableLogDebug)
605598 Log .d(TAG , " Restore done, decrypted($backupIsEncrypted ) and restored from $source " )
606599 onCompleteListener?.onComplete(true , " success" , OnCompleteListener .EXIT_CODE_SUCCESS )
@@ -741,6 +734,8 @@ class RoomBackup(var context: Context) {
741734 ActivityResultContracts .OpenDocument ()
742735 ) { result ->
743736 if (result != null ) {
737+ val fileExtension = getFileExtension(result)
738+ if (fileExtension == null || ! isChosenFileValidForRestore(fileExtension)) return @registerForActivityResult
744739 val inputstream = context.contentResolver.openInputStream(result)!!
745740 doRestore(inputstream)
746741 return @registerForActivityResult
@@ -840,4 +835,68 @@ class RoomBackup(var context: Context) {
840835 }
841836 return true
842837 }
838+
839+ /* *
840+ * Gets the file extension of the [Uri] using ContentResolver.
841+ */
842+ private fun getFileExtension (uri : Uri ): String? {
843+ var fileName: String? = null
844+ val cursor = context.contentResolver.query(uri, null , null , null , null )
845+ cursor?.use {
846+ if (it.moveToFirst()) {
847+ val indexOfNameColumn = it.getColumnIndex(OpenableColumns .DISPLAY_NAME )
848+ if (indexOfNameColumn >= 0 ) fileName = it.getString(indexOfNameColumn)
849+ }
850+ }
851+ return fileName?.substringAfterLast(" ." )
852+ }
853+
854+ /* *
855+ * Validating the chosen file with it's [File.extension]
856+ * if [backupIsEncrypted] state matches file extension return true, else false
857+ */
858+ private fun isChosenFileValidForRestore (fileExtension : String ): Boolean {
859+ return when {
860+ backupIsEncrypted && fileExtension == BACKUP_EXTENSION_ENCRYPTED -> true
861+ ! backupIsEncrypted && fileExtension == BACKUP_EXTENSION_BASE -> true
862+ backupIsEncrypted && fileExtension == BACKUP_EXTENSION_BASE -> {
863+ if (enableLogDebug) Log .d(TAG ,
864+ " isChosenFileValid: chosen file is unencrypted while encrypted file is expected"
865+ )
866+ onCompleteListener?.onComplete(
867+ false ,
868+ " chosen file is unencrypted while encrypted file is expected" ,
869+ OnCompleteListener .EXIT_CODE_ERROR_DECRYPTION_ERROR
870+ )
871+ false
872+ }
873+ ! backupIsEncrypted && fileExtension == BACKUP_EXTENSION_ENCRYPTED -> {
874+ if (enableLogDebug)
875+ Log .d(
876+ TAG ,
877+ " isChosenFileValid: chosen file is encrypted while unencrypted file is expected"
878+ )
879+ onCompleteListener?.onComplete(
880+ false ,
881+ " chosen file is encrypted while unencrypted file is expected" ,
882+ OnCompleteListener .EXIT_CODE_ERROR_RESTORE_BACKUP_IS_ENCRYPTED
883+ )
884+ false
885+ }
886+ else -> {
887+ if (enableLogDebug)
888+ Log .d(
889+ TAG ,
890+ " isChosenFileValid: chosen file is of wrong extension: $fileExtension "
891+ )
892+ onCompleteListener?.onComplete(
893+ false ,
894+ " failed to verify the chosen file extension" ,
895+ OnCompleteListener .EXIT_CODE_ERROR
896+ )
897+ false
898+ }
899+ }
900+ }
901+
843902}
0 commit comments