diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt index 141d2d2665..de6834ba41 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/helpers/Utils.android.kt @@ -233,7 +233,8 @@ actual fun getFileName(uri: URI): String? { val nameIndex = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME) cursor.moveToFirst() // Can make an exception - cursor.getString(nameIndex) + // the provider controls this value, and callers use it as a bare file name + cursor.getString(nameIndex)?.let { File(it).name } } } catch (e: Exception) { null diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 80f97d1caf..241826ac41 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -753,6 +753,11 @@ private fun saveArchiveFromURI(importedArchiveURI: URI): String? { if (inputStream != null && archiveName != null) { val archivePath = "$databaseExportDir${File.separator}$archiveName" val destFile = File(archivePath) + // resolves symlinks, so it also catches a final component linking outside the folder + if (destFile.canonicalFile.parentFile != databaseExportDir.canonicalFile) { + Log.e(TAG, "saveArchiveFromURI path outside of export folder") + return null + } Files.copy(inputStream, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING) archivePath } else { diff --git a/apps/multiplatform/spec/database.md b/apps/multiplatform/spec/database.md index f6ecedb721..8981fe7055 100644 --- a/apps/multiplatform/spec/database.md +++ b/apps/multiplatform/spec/database.md @@ -345,7 +345,7 @@ class ArchiveConfig( ### Import Flow 1. User selects an archive file. -2. UI copies it to a temp location and constructs an `ArchiveConfig`. +2. UI copies it into `databaseExportDir` and constructs an `ArchiveConfig`. The destination is confined to that folder: `getFileName` returns a bare file name on every platform, and `saveArchiveFromURI` checks the canonical destination before copying. 3. Calls `apiImportArchive(config)` which sends `CC.ApiImportArchive` to the Haskell core. 4. The core extracts and replaces both databases. 5. Returns `CR.ArchiveImported` with a list of `ArchiveError` (non-fatal issues during import).