changeset 2:efd9fe2d70d7

Rationalize exceptions, code whitelist, add command-line tool.
author David Barts <n5jrn@me.com>
date Wed, 01 Apr 2020 14:23:54 -0700
parents 42277ce58ace
children 19c381c536ec
files src/name/blackcap/exifwasher/Test2.kt src/name/blackcap/exifwasher/Whitelist.kt src/name/blackcap/exifwasher/default.properties src/name/blackcap/exifwasher/exiv2/Initialize.kt src/name/blackcap/exifwasher/exiv2/native.cpp
diffstat 5 files changed, 130 insertions(+), 21 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/name/blackcap/exifwasher/Test2.kt	Wed Apr 01 14:23:54 2020 -0700
@@ -0,0 +1,56 @@
+/*
+ * A basic test of the library: try to use it to print out the EXIF
+ * data.
+ */
+package name.blackcap.exifwasher
+
+import java.io.File
+import java.io.FileInputStream
+import java.io.FileOutputStream
+import name.blackcap.exifwasher.exiv2.*
+
+/* entry point */
+fun main(args: Array<String>) {
+    /* must have a file name */
+    if (args.size != 1) {
+        System.err.println("expecting a file name")
+        System.exit(1)
+    }
+
+    /* copy the file; we don't want to scribble on the original */
+    val (name, ext) = splitext(args[0])
+    val newName = "${name}_washed${ext}"
+    FileInputStream(args[0]).use { source ->
+        FileOutputStream(newName).use { target ->
+            source.copyTo(target)
+        }
+    }
+
+    /* load the whitelist and image data */
+    val white = Whitelist.parse(PROPERTIES.getProperty("whitelist"))
+    val image = Image(newName)
+
+    /* do the washing */
+    val meta = image.metadata
+    val keys = meta.keys
+    val keysin = keys.size
+    var deleted = 0
+    keys.forEach {
+        if (!white.contains(it)) {
+            meta.erase(it)
+            deleted += 1
+        }
+    }
+
+    /* save and summarize */
+    image.store()
+    println("${keysin} in - ${deleted} deleted = ${keysin - deleted} out")
+}
+
+fun splitext(s: String): Pair<String, String> {
+    val pos = s.lastIndexOf('.')
+    if (pos == -1) {
+        return Pair(s, "")
+    }
+    return Pair(s.substring(0, pos), s.substring(pos))
+}
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/src/name/blackcap/exifwasher/Whitelist.kt	Wed Apr 01 14:23:54 2020 -0700
@@ -0,0 +1,48 @@
+/*
+ * An exif key whitelist. Supports both prefixes and entire strings.
+ */
+package name.blackcap.exifwasher
+
+import java.util.regex.Pattern
+import kotlin.collections.mutableSetOf
+import kotlin.collections.mutableListOf
+
+class Whitelist {
+    private val entire = mutableSetOf<String>()
+    private val prefixes = mutableListOf<String>()
+
+    fun addEntire(s: String) = entire.add(s)
+
+    fun addPrefix(s: String) = prefixes.add(s)
+
+    private fun autoOp(s: String, pfx: (String) -> Boolean, ent: (String) -> Boolean): Boolean {
+        return if (s.endsWith('*')) { pfx(s.dropLast(1)) } else { ent(s) }
+    }
+
+    fun add(s: String) = autoOp(s, ::addPrefix, ::addEntire)
+
+    fun removeEntire(s: String) = entire.remove(s)
+
+    fun removePrefix(s: String) = prefixes.remove(s)
+
+    fun remove(s: String) = autoOp(s, ::removePrefix, ::removeEntire)
+
+    fun contains(s: String) = entire.contains(s) || prefixes.find { s.startsWith(it) } != null
+
+    fun toList(): List<String> = mutableListOf<String>().also {
+        it.addAll(entire)
+        it.addAll(prefixes)
+        it.sort()
+    }
+
+    override fun toString(): String = toList().joinToString(",")
+
+    companion object {
+        private val SPLITTER = Pattern.compile(",\\s*")
+        fun parse(raw: String) = Whitelist().also { 
+            for (s in raw.split(SPLITTER)) {
+                it.add(s)
+            }
+        }
+    }
+}
--- a/src/name/blackcap/exifwasher/default.properties	Tue Mar 31 15:38:25 2020 -0700
+++ b/src/name/blackcap/exifwasher/default.properties	Wed Apr 01 14:23:54 2020 -0700
@@ -1,1 +1,17 @@
-# A placeholder, because we currently don't use properties.
+whitelist=\
+    Exif.Image.Orientation,\
+    Exif.Image.ResolutionUnit,\
+    Exif.Image.XResolution,\
+    Exif.Image.YCbCrCoefficients,\
+    Exif.Image.YCbCrPositioning,\
+    Exif.Image.YCbCrSubSampling,\
+    Exif.Image.YResolution,\
+    Exif.Iop.*,\
+    Exif.Photo.ColorSpace,\
+    Exif.Photo.ComponentsConfiguration,\
+    Exif.Photo.CompressedBitsPerPixel,\
+    Exif.Photo.ExifVersion,\
+    Exif.Photo.InteroperabilityTag,\
+    Exif.Photo.PixelXDimension,\
+    Exif.Photo.PixelYDimension,\
+    Exif.Thumbnail.*
--- a/src/name/blackcap/exifwasher/exiv2/Initialize.kt	Tue Mar 31 15:38:25 2020 -0700
+++ b/src/name/blackcap/exifwasher/exiv2/Initialize.kt	Wed Apr 01 14:23:54 2020 -0700
@@ -61,7 +61,6 @@
                 die("unable to create ${tPath}: ${message}")
             }
             try {
-                println("loading: ${tPath}")  /* debug */
                 System.load(tPath)
             } catch (e: UnsatisfiedLinkError) {
                 val message = e.message ?: "unsatisfied link"
@@ -70,7 +69,6 @@
         }
 
         initialized = true
-        System.err.println("libraries loaded")  /* debug */
     }
 
     private fun die(message: String) {
--- a/src/name/blackcap/exifwasher/exiv2/native.cpp	Tue Mar 31 15:38:25 2020 -0700
+++ b/src/name/blackcap/exifwasher/exiv2/native.cpp	Wed Apr 01 14:23:54 2020 -0700
@@ -1,5 +1,6 @@
 #include <jni.h>
 #include <exiv2/exiv2.hpp>
+#include <exception>
 #include <iostream>
 #include <iomanip>
 #include <cassert>
@@ -30,15 +31,10 @@
       jlong ret = 0;
       try {
           ret = reinterpret_cast<jlong> (Exiv2::ImageFactory::open(cPath).release());
-      } catch (...) {
+      } catch (std::exception& e) {
           jEnv->ExceptionClear();
           jclass ex = jEnv->FindClass("name/blackcap/exifwasher/exiv2/Exiv2Exception");
-          const char *pfx = "unable to open ";
-          char *message = (char *) malloc(strlen(cPath) + strlen(pfx) + 1);
-          strcpy(message, pfx);
-          strcat(message, cPath);
-          jEnv->ThrowNew(ex, message);
-          free(message);
+          jEnv->ThrowNew(ex, e.what());
       }
       jEnv->ReleaseStringUTFChars(path, cPath);
       return ret;
@@ -55,10 +51,10 @@
       if (jEnv->ExceptionCheck()) return;
       try {
           image->writeMetadata();
-      } catch (...) {
+      } catch (std::exception& e) {
           jEnv->ExceptionClear();
           jclass ex = jEnv->FindClass("name/blackcap/exifwasher/exiv2/Exiv2Exception");
-          jEnv->ThrowNew(ex, "unable to write metadata");
+          jEnv->ThrowNew(ex, e.what());
       }
   }
 
@@ -73,10 +69,10 @@
       if (jEnv->ExceptionCheck()) return 0;
       try {
           image->readMetadata();
-      } catch (...) {
+      } catch (std::exception& e) {
           jEnv->ExceptionClear();
           jclass ex = jEnv->FindClass("name/blackcap/exifwasher/exiv2/Exiv2Exception");
-          jEnv->ThrowNew(ex, "unable to read metadata");
+          jEnv->ThrowNew(ex, e.what());
           return 0;
       }
       return reinterpret_cast<jlong> (&(image->exifData()));
@@ -118,15 +114,10 @@
       Exiv2::ExifData::iterator found = data->findKey(Exiv2::ExifKey(std::string(cKey)));
       try {
           data->erase(found);
-      } catch (...) {
+      } catch (std::exception& e) {
           jEnv->ExceptionClear();
           jclass ex = jEnv->FindClass("name/blackcap/exifwasher/exiv2/Exiv2Exception");
-          const char *pfx = "unable to delete ";
-          char *message = (char *) malloc(strlen(cKey) + strlen(pfx) + 1);
-          strcpy(message, pfx);
-          strcat(message, cKey);
-          jEnv->ThrowNew(ex, message);
-          free(message);
+          jEnv->ThrowNew(ex, e.what());
       }
       jEnv->ReleaseStringUTFChars(key, cKey);
   }