Skip to content

Commit

Permalink
fix: crash when saving file - duplicate names [WPB-5026] (#2396)
Browse files Browse the repository at this point in the history
  • Loading branch information
saleniuk committed Nov 8, 2023
1 parent e987c45 commit 76bd823
Show file tree
Hide file tree
Showing 2 changed files with 23 additions and 7 deletions.
21 changes: 14 additions & 7 deletions app/src/main/kotlin/com/wire/android/util/FileUtil.kt
Original file line number Diff line number Diff line change
Expand Up @@ -115,19 +115,18 @@ fun getTempWritableAttachmentUri(context: Context, attachmentPath: Path): Uri {
private fun Context.saveFileDataToDownloadsFolder(assetName: String, downloadedDataPath: Path, fileSize: Long): Uri? {
val resolver = contentResolver
val mimeType = Uri.parse(downloadedDataPath.toString()).getMimeType(this@saveFileDataToDownloadsFolder)
val downloadsDir = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS)
// we need to find the next available name with copy counter by ourselves before copying
val availableAssetName = findFirstUniqueName(downloadsDir, assetName.ifEmpty { ATTACHMENT_FILENAME })
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val contentValues = ContentValues().apply {
// ContentResolver modifies the name if another file with the given name already exists, so we don't have to worry about it
put(DISPLAY_NAME, assetName.ifEmpty { ATTACHMENT_FILENAME })
put(DISPLAY_NAME, availableAssetName)
put(MIME_TYPE, mimeType)
put(SIZE, fileSize)
}
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
} else {
val authority = getProviderAuthority()
val downloadsDir = Environment.getExternalStoragePublicDirectory(DIRECTORY_DOWNLOADS)
// we need to find the next available name with copy counter by ourselves before copying
val availableAssetName = findFirstUniqueName(downloadsDir, assetName.ifEmpty { ATTACHMENT_FILENAME })
val destinationFile = File(downloadsDir, availableAssetName)
val uri = FileProvider.getUriForFile(this, authority, destinationFile)
if (mimeType?.isNotEmpty() == true) {
Expand Down Expand Up @@ -409,14 +408,22 @@ fun Context.getProviderAuthority() = "$packageName.provider"

@VisibleForTesting
fun findFirstUniqueName(dir: File, desiredName: String): String {
var currentName: String = desiredName
var currentName: String = desiredName.sanitizeFilename()
while (File(dir, currentName).exists()) {
val (nameWithoutCopyCounter, copyCounter, extension) = currentName.splitFileExtensionAndCopyCounter()
currentName = buildFileName(nameWithoutCopyCounter, extension, copyCounter + 1)
currentName = buildFileName(nameWithoutCopyCounter, extension, copyCounter + 1).sanitizeFilename()
}
return currentName
}

/**
* Removes disallowed characters and returns valid filename.
*
* Uses the same cases as in `isValidFatFilenameChar` and `isValidExtFilenameChar` from [android.os.FileUtils].
*/
@VisibleForTesting
fun String.sanitizeFilename(): String = replace(Regex("[\u0000-\u001f\u007f\"*/:<>?\\\\|]"), "_")

fun getAudioLengthInMs(dataPath: Path, mimeType: String): Long =
if (isAudioMimeType(mimeType)) {
val retriever = MediaMetadataRetriever()
Expand Down
9 changes: 9 additions & 0 deletions app/src/test/kotlin/com/wire/android/util/FileUtilTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -60,4 +60,13 @@ class FileUtilTest {
val result = findFirstUniqueName(tempDir, desired)
assertEquals(expected, result)
}

@Test
fun `given file with invalid filename when finding unique name in directory then return name without disallowed characters`() {
val desired = "\u0020ab\u0008cd\u0000ef\u001fgh\u007Fij*kl/mn:op<qr>st?uv\\wx|yz.jpg"
val expected = "\u0020ab_cd_ef_gh_ij_kl_mn_op_qr_st_uv_wx_yz.jpg"

val result = desired.sanitizeFilename()
assertEquals(expected, result)
}
}

0 comments on commit 76bd823

Please sign in to comment.