Skip to content

Commit 760662e

Browse files
committed
fix(dnd): improve external URI parsing and file import safety
1 parent 35130da commit 760662e

File tree

4 files changed

+35
-19
lines changed

4 files changed

+35
-19
lines changed

app/src/main/java/com/itsaky/androidide/dnd/DragAndDropExtensions.kt

Lines changed: 14 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -17,30 +17,34 @@ fun DragEvent.hasImportableContent(context: Context): Boolean {
1717
DragEvent.ACTION_DROP -> {
1818
val clip = clipData ?: return false
1919
(0 until clip.itemCount).any { index ->
20-
clip.getItemAt(index).toImportableExternalUri(context) != null
20+
clip.getItemAt(index).toImportableExternalUris(context).isNotEmpty()
2121
}
2222
}
2323
else -> clipDescription?.hasImportableMimeType() == true
2424
}
2525
}
2626

2727
/**
28-
* Resolves the [ClipData.Item] to an external [Uri], ignoring internal application URIs.
28+
* Resolves the [ClipData.Item] to a list of external [Uri]s, ignoring internal application URIs.
2929
*/
30-
fun ClipData.Item.toImportableExternalUri(context: Context): Uri? {
31-
val resolvedUri = toExternalUri() ?: return null
32-
return resolvedUri.takeUnless { it.isInternalDragUri(context) }
30+
fun ClipData.Item.toImportableExternalUris(context: Context): List<Uri> {
31+
return toExternalUris().filterNot { it.isInternalDragUri(context) }
3332
}
3433

3534
private fun Uri.isInternalDragUri(context: Context): Boolean {
3635
return authority == "${context.packageName}.providers.fileprovider"
3736
}
3837

39-
private fun ClipData.Item.toExternalUri(): Uri? {
40-
return uri
41-
?: text?.toString()
42-
?.takeIf { it.startsWith("content://") || it.startsWith("file://") }
43-
?.toUri()
38+
private fun ClipData.Item.toExternalUris(): List<Uri> {
39+
uri?.let { return listOf(it) }
40+
41+
val textContent = text?.toString() ?: return emptyList()
42+
43+
return textContent.lineSequence()
44+
.map { it.trim() }
45+
.filter { it.startsWith("content://") || it.startsWith("file://") }
46+
.map { it.toUri() }
47+
.toList()
4448
}
4549

4650
private fun ClipDescription.hasImportableMimeType(): Boolean {

app/src/main/java/com/itsaky/androidide/dnd/DropTargetCallback.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ interface DropTargetCallback {
2929

3030
/**
3131
* Called when the user successfully drops a valid item on the target.
32-
* * @return True if the drop was successfully consumed and handled.
32+
* @return True if the drop was successfully consumed and handled.
3333
*/
3434
fun onDrop(event: DragEvent): Boolean
3535
}

app/src/main/java/com/itsaky/androidide/utils/FileImporter.kt

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ package com.itsaky.androidide.utils
33
import android.content.ClipData
44
import android.content.Context
55
import com.itsaky.androidide.R
6-
import com.itsaky.androidide.dnd.toImportableExternalUri
6+
import com.itsaky.androidide.dnd.toImportableExternalUris
77
import java.io.File
88

99
/**
@@ -28,10 +28,10 @@ class FileImporter(private val context: Context) {
2828
}
2929

3030
val validUris = (0 until clipData.itemCount)
31-
.mapNotNull { clipData.getItemAt(it).toImportableExternalUri(context) }
31+
.flatMap { clipData.getItemAt(it).toImportableExternalUris(context) }
3232

3333
if (validUris.isEmpty()) {
34-
return ImportResult.Failure(IllegalArgumentException("No importable files found"))
34+
return ImportResult.Failure(IllegalArgumentException(context.getString(R.string.msg_file_tree_drop_no_files)))
3535
}
3636

3737
val results = validUris.map { uri ->
@@ -50,7 +50,9 @@ class FileImporter(private val context: Context) {
5050
context = context,
5151
uri = uri,
5252
destinationFile = destinationFile,
53-
onOpenFailed = { IllegalStateException("Unable to open URI: $sanitizedName") }
53+
onOpenFailed = { IllegalStateException(
54+
context.getString(R.string.msg_file_tree_drop_read_failed, sanitizedName)
55+
)}
5456
)
5557
}
5658
}

app/src/main/java/com/itsaky/androidide/utils/UriFileImporter.kt

Lines changed: 14 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,21 @@ object UriFileImporter {
3232
destinationFile: File,
3333
onOpenFailed: (() -> Throwable)? = null,
3434
) {
35-
contentResolver.openInputStream(uri)?.use { input ->
36-
destinationFile.outputStream().use { output ->
37-
input.copyTo(output)
35+
val inputStream = contentResolver.openInputStream(uri)
36+
?: throw (onOpenFailed?.invoke() ?: IllegalStateException("Unable to open URI: $uri"))
37+
38+
try {
39+
inputStream.use { input ->
40+
destinationFile.outputStream().use { output ->
41+
input.copyTo(output)
42+
}
43+
}
44+
} catch (e: Exception) {
45+
if (destinationFile.exists()) {
46+
destinationFile.delete()
3847
}
39-
} ?: throw (onOpenFailed?.invoke() ?: IllegalStateException("Unable to open URI: $uri"))
48+
throw e
49+
}
4050
}
4151

4252
@JvmStatic

0 commit comments

Comments
 (0)