safer content URI parsing

This commit is contained in:
Thibault Deckers 2021-01-31 16:11:11 +09:00
parent aab4800d9b
commit 020c63f499
7 changed files with 46 additions and 36 deletions

View file

@ -23,6 +23,7 @@ import deckers.thibault.aves.utils.MimeTypes.isSupportedByExifInterface
import deckers.thibault.aves.utils.MimeTypes.isSupportedByMetadataExtractor
import deckers.thibault.aves.utils.MimeTypes.isVideo
import deckers.thibault.aves.utils.StorageUtils
import deckers.thibault.aves.utils.UriUtils.tryParseId
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@ -96,8 +97,7 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
var contentUri: Uri = uri
if (uri.scheme == ContentResolver.SCHEME_CONTENT && MediaStore.AUTHORITY.equals(uri.host, ignoreCase = true)) {
try {
val id = ContentUris.parseId(uri)
uri.tryParseId()?.let { id ->
contentUri = when {
isImage(mimeType) -> ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
isVideo(mimeType) -> ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id)
@ -106,8 +106,6 @@ class DebugHandler(private val context: Context) : MethodCallHandler {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentUri = MediaStore.setRequireOriginal(contentUri)
}
} catch (e: NumberFormatException) {
// ignore
}
}

View file

@ -61,6 +61,7 @@ import deckers.thibault.aves.utils.MimeTypes.isSupportedByMetadataExtractor
import deckers.thibault.aves.utils.MimeTypes.isVideo
import deckers.thibault.aves.utils.MimeTypes.tiffExtensionPattern
import deckers.thibault.aves.utils.StorageUtils
import deckers.thibault.aves.utils.UriUtils.tryParseId
import io.flutter.plugin.common.MethodCall
import io.flutter.plugin.common.MethodChannel
import io.flutter.plugin.common.MethodChannel.MethodCallHandler
@ -639,8 +640,7 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
var contentUri: Uri = uri
if (uri.scheme == ContentResolver.SCHEME_CONTENT && MediaStore.AUTHORITY.equals(uri.host, ignoreCase = true)) {
try {
val id = ContentUris.parseId(uri)
uri.tryParseId()?.let { id ->
contentUri = when {
isImage(mimeType) -> ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, id)
isVideo(mimeType) -> ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, id)
@ -649,8 +649,6 @@ class MetadataHandler(private val context: Context) : MethodCallHandler {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
contentUri = MediaStore.setRequireOriginal(contentUri)
}
} catch (e: NumberFormatException) {
// ignore
}
}

View file

@ -1,6 +1,5 @@
package deckers.thibault.aves.channel.calls.fetchers
import android.content.ContentUris
import android.content.Context
import android.graphics.Bitmap
import android.net.Uri
@ -23,6 +22,7 @@ import deckers.thibault.aves.utils.MimeTypes.isHeifLike
import deckers.thibault.aves.utils.MimeTypes.isVideo
import deckers.thibault.aves.utils.MimeTypes.needRotationAfterContentResolverThumbnail
import deckers.thibault.aves.utils.MimeTypes.needRotationAfterGlide
import deckers.thibault.aves.utils.UriUtils.tryParseId
import io.flutter.plugin.common.MethodChannel
class ThumbnailFetcher internal constructor(
@ -94,7 +94,7 @@ class ThumbnailFetcher internal constructor(
}
private fun getByMediaStore(): Bitmap? {
val contentId = ContentUris.parseId(uri)
val contentId = uri.tryParseId() ?: return null
val resolver = context.contentResolver
return if (isVideo(mimeType)) {
@Suppress("DEPRECATION")

View file

@ -1,7 +1,6 @@
package deckers.thibault.aves.model
import android.content.ContentResolver
import android.content.ContentUris
import android.content.Context
import android.graphics.BitmapFactory
import android.media.MediaMetadataRetriever
@ -25,9 +24,9 @@ import deckers.thibault.aves.metadata.Metadata.getRotationDegreesForExifCode
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeDateMillis
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeInt
import deckers.thibault.aves.metadata.MetadataExtractorHelper.getSafeLong
import deckers.thibault.aves.model.FieldMap
import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.StorageUtils
import deckers.thibault.aves.utils.UriUtils.tryParseId
import org.beyka.tiffbitmapfactory.TiffBitmapFactory
import java.io.IOException
@ -93,16 +92,7 @@ class SourceEntry {
// ignore when the ID is not a number
// e.g. content://com.sec.android.app.myfiles.FileProvider/device_storage/20200109_162621.jpg
private val contentId: Long?
get() {
if (uri.scheme == ContentResolver.SCHEME_CONTENT) {
try {
return ContentUris.parseId(uri)
} catch (e: Exception) {
// ignore
}
}
return null
}
get() = if (uri.scheme == ContentResolver.SCHEME_CONTENT) uri.tryParseId() else null
val isSized: Boolean
get() = width ?: 0 > 0 && height ?: 0 > 0

View file

@ -25,6 +25,7 @@ import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.StorageUtils.copyFileToTemp
import deckers.thibault.aves.utils.StorageUtils.createDirectoryIfAbsent
import deckers.thibault.aves.utils.StorageUtils.getDocumentFile
import deckers.thibault.aves.utils.UriUtils.tryParseId
import java.io.File
import java.io.FileNotFoundException
import java.io.IOException
@ -292,16 +293,18 @@ abstract class ImageProvider {
protected suspend fun scanNewPath(context: Context, path: String, mimeType: String): FieldMap =
suspendCoroutine { cont ->
MediaScannerConnection.scanFile(context, arrayOf(path), arrayOf(mimeType)) { _, newUri: Uri? ->
var contentId: Long = 0
var contentId: Long? = null
var contentUri: Uri? = null
if (newUri != null) {
// `newURI` is possibly a file media URI (e.g. "content://media/12a9-8b42/file/62872")
// but we need an image/video media URI (e.g. "content://media/external/images/media/62872")
contentId = ContentUris.parseId(newUri)
if (MimeTypes.isImage(mimeType)) {
contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentId)
} else if (MimeTypes.isVideo(mimeType)) {
contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentId)
contentId = newUri.tryParseId()
if (contentId != null) {
if (MimeTypes.isImage(mimeType)) {
contentUri = ContentUris.withAppendedId(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, contentId)
} else if (MimeTypes.isVideo(mimeType)) {
contentUri = ContentUris.withAppendedId(MediaStore.Video.Media.EXTERNAL_CONTENT_URI, contentId)
}
}
}
if (contentUri == null) {

View file

@ -19,6 +19,7 @@ import deckers.thibault.aves.utils.StorageUtils.createDirectoryIfAbsent
import deckers.thibault.aves.utils.StorageUtils.ensureTrailingSeparator
import deckers.thibault.aves.utils.StorageUtils.getDocumentFile
import deckers.thibault.aves.utils.StorageUtils.requireAccessPermission
import deckers.thibault.aves.utils.UriUtils.tryParseId
import kotlinx.coroutines.delay
import java.io.File
import java.util.*
@ -34,19 +35,21 @@ class MediaStoreImageProvider : ImageProvider() {
}
override suspend fun fetchSingle(context: Context, uri: Uri, mimeType: String?, callback: ImageOpCallback) {
val id = ContentUris.parseId(uri)
val id = uri.tryParseId()
val onSuccess = fun(entry: FieldMap) {
entry["uri"] = uri.toString()
callback.onSuccess(entry)
}
val alwaysValid = { _: Int, _: Int -> true }
if (mimeType == null || isImage(mimeType)) {
val contentUri = ContentUris.withAppendedId(IMAGE_CONTENT_URI, id)
if (fetchFrom(context, alwaysValid, onSuccess, contentUri, IMAGE_PROJECTION) > 0) return
}
if (mimeType == null || isVideo(mimeType)) {
val contentUri = ContentUris.withAppendedId(VIDEO_CONTENT_URI, id)
if (fetchFrom(context, alwaysValid, onSuccess, contentUri, VIDEO_PROJECTION) > 0) return
if (id != null) {
if (mimeType == null || isImage(mimeType)) {
val contentUri = ContentUris.withAppendedId(IMAGE_CONTENT_URI, id)
if (fetchFrom(context, alwaysValid, onSuccess, contentUri, IMAGE_PROJECTION) > 0) return
}
if (mimeType == null || isVideo(mimeType)) {
val contentUri = ContentUris.withAppendedId(VIDEO_CONTENT_URI, id)
if (fetchFrom(context, alwaysValid, onSuccess, contentUri, VIDEO_PROJECTION) > 0) return
}
}
// the uri can be a file media URI (e.g. "content://0@media/external/file/30050")
// without an equivalent image/video if it is shared from a file browser

View file

@ -0,0 +1,18 @@
package deckers.thibault.aves.utils
import android.content.ContentUris
import android.net.Uri
import android.util.Log
object UriUtils {
private val LOG_TAG = LogUtils.createTag(UriUtils::class.java)
fun Uri.tryParseId(): Long? {
try {
return ContentUris.parseId(this)
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to parse ID from contentUri=$this")
}
return null
}
}