prevent editing item when Exif editing changes mime type

This commit is contained in:
Thibault Deckers 2023-04-26 18:50:45 +02:00
parent 806f57785c
commit 7f9229a227
4 changed files with 44 additions and 2 deletions

View file

@ -15,6 +15,7 @@ All notable changes to this project will be documented in this file.
### Fixed
- Viewer: multi-page context update when removing burst entries
- prevent editing item when Exif editing changes mime type
## <a id="v1.8.5"></a>[v1.8.5] - 2023-04-18

View file

@ -336,7 +336,7 @@ open class MainActivity : FlutterFragmentActivity() {
private fun submitPickedItems(call: MethodCall) {
val pickedUris = call.argument<List<String>>("uris")
if (pickedUris != null && pickedUris.isNotEmpty()) {
if (!pickedUris.isNullOrEmpty()) {
val toUri = { uriString: String -> AppAdapterHandler.getShareableUri(this, Uri.parse(uriString)) }
val intent = Intent().apply {
val firstUri = toUri(pickedUris.first())

View file

@ -31,6 +31,7 @@ import deckers.thibault.aves.metadata.Mp4ParserHelper.updateRotation
import deckers.thibault.aves.metadata.Mp4ParserHelper.updateXmp
import deckers.thibault.aves.metadata.PixyMetaHelper.extendedXmpDocString
import deckers.thibault.aves.metadata.PixyMetaHelper.xmpDocString
import deckers.thibault.aves.metadata.metadataextractor.Helper
import deckers.thibault.aves.model.*
import deckers.thibault.aves.utils.*
import deckers.thibault.aves.utils.FileUtils.transferFrom
@ -330,6 +331,7 @@ abstract class ImageProvider {
@Suppress("deprecation")
Bitmap.CompressFormat.WEBP
}
else -> throw Exception("unsupported export MIME type=$exportMimeType")
}
bitmap.compress(format, quality, output)
@ -592,12 +594,14 @@ abstract class ImageProvider {
}
nameWithoutExtension
}
NameConflictStrategy.REPLACE -> {
if (targetFile.exists()) {
deletePath(contextWrapper, targetFile.path, mimeType)
}
desiredNameWithoutExtension
}
NameConflictStrategy.SKIP -> {
if (targetFile.exists()) {
null
@ -608,6 +612,25 @@ abstract class ImageProvider {
}
}
// cf `MetadataFetchHandler.getCatalogMetadataByMetadataExtractor()` for a more thorough check
private fun detectMimeType(context: Context, uri: Uri, mimeType: String): String? {
var detectedMimeType: String? = null
if (MimeTypes.canReadWithMetadataExtractor(mimeType)) {
try {
StorageUtils.openInputStream(context, uri)?.use { input ->
detectedMimeType = Helper.readMimeType(input)
}
} catch (e: Exception) {
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
} catch (e: NoClassDefFoundError) {
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
} catch (e: AssertionError) {
Log.w(LOG_TAG, "failed to read metadata by metadata-extractor for mimeType=$mimeType uri=$uri", e)
}
}
return detectedMimeType
}
private fun editExif(
context: Context,
path: String,
@ -657,6 +680,11 @@ abstract class ImageProvider {
try {
edit(ExifInterface(editableFile))
val editedMimeType = detectMimeType(context, Uri.fromFile(editableFile), mimeType)
if (editedMimeType != mimeType) {
throw Exception("editing Exif changes mimeType=$mimeType -> $editedMimeType for uri=$uri path=$path")
}
if (videoBytes != null) {
// append trailer video, if any
editableFile.appendBytes(videoBytes!!)
@ -730,8 +758,10 @@ abstract class ImageProvider {
when {
iptc != null ->
PixyMetaHelper.setIptc(input, output, iptc)
canRemoveMetadata(mimeType) ->
PixyMetaHelper.removeMetadata(input, output, setOf(TYPE_IPTC))
else -> {
Log.w(LOG_TAG, "setting empty IPTC for mimeType=$mimeType")
PixyMetaHelper.setIptc(input, output, null)
@ -787,6 +817,7 @@ abstract class ImageProvider {
newFields["rotationDegrees"] = degrees
}
}
"xmp" -> isoFile.updateXmp(value)
}
}
@ -1039,6 +1070,7 @@ abstract class ImageProvider {
exif.setAttribute(ExifInterface.TAG_GPS_TIMESTAMP, ExifInterfaceHelper.GPS_TIME_FORMAT.format(date))
}
}
shiftMinutes != null -> {
// shift
val shiftMillis = shiftMinutes * 60000
@ -1067,6 +1099,7 @@ abstract class ImageProvider {
}
}
}
else -> {
// clear
if (fields.contains(ExifInterface.TAG_DATETIME)) {
@ -1135,6 +1168,7 @@ abstract class ImageProvider {
ExifInterface.TAG_GPS_LONGITUDE_REF -> {
setLocation = true
}
else -> {
if (value is String) {
exif.setAttribute(tag, value)

View file

@ -25,6 +25,7 @@ import deckers.thibault.aves.utils.MimeTypes.isVideo
import deckers.thibault.aves.utils.PermissionManager.getGrantedDirForPath
import deckers.thibault.aves.utils.UriUtils.tryParseId
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
import java.io.OutputStream
import java.util.*
@ -588,6 +589,7 @@ object StorageUtils {
// e.g. `content://media/external_primary/downloads/...`
getMediaUriImageVideoUri(uri, mimeType)?.let { imageVideUri -> return imageVideUri }
}
uriPath?.contains("/file/") == true -> {
// e.g. `content://media/external/file/...`
// create an ad-hoc temporary file for decoding only
@ -601,6 +603,7 @@ object StorageUtils {
}
}
}
uri.userInfo != null -> return stripMediaUriUserInfo(uri)
}
}
@ -617,6 +620,7 @@ object StorageUtils {
// e.g. `content://media/external_primary/downloads/...`
getMediaUriImageVideoUri(uri, mimeType)?.let { imageVideUri -> return imageVideUri }
}
uri.userInfo != null -> return stripMediaUriUserInfo(uri)
}
}
@ -643,7 +647,10 @@ object StorageUtils {
fun openInputStream(context: Context, uri: Uri): InputStream? {
val effectiveUri = getOriginalUri(context, uri)
return try {
context.contentResolver.openInputStream(effectiveUri)
return when (uri.scheme) {
ContentResolver.SCHEME_FILE -> FileInputStream(uri.path)
else -> context.contentResolver.openInputStream(effectiveUri)
}
} catch (e: Exception) {
// among various other exceptions,
// opening a file marked pending and owned by another package throws an `IllegalStateException`