Jelajahi Sumber

Implement configurable EXIF stripping for uploaded media

- Add `strip_exif` configuration option to enable metadata removal.
- Add `mogrify_path` configuration to specify external tool location.
- Implement strip_media using `mogrify -strip`.
- Support multiple image formats: jpg, png, webp, heic, heif, avif, tiff, gif, bmp.
- Add strict startup check: fail to start if `strip_exif` is enabled but `mogrify` is missing/broken.
- Update documentation in `doc/snac.8`.
Stefano Marinelli 7 bulan lalu
induk
melakukan
688c54c873
4 mengubah file dengan 86 tambahan dan 2 penghapusan
  1. 11 2
      data.c
  2. 8 0
      doc/snac.8
  3. 64 0
      snac.c
  4. 3 0
      snac.h

+ 11 - 2
data.c

@@ -89,8 +89,15 @@ int srv_open(const char *basedir, int auto_upgrade)
                 else {
                 else {
                     if (xs_number_get(xs_dict_get(srv_config, "layout")) < disk_layout)
                     if (xs_number_get(xs_dict_get(srv_config, "layout")) < disk_layout)
                         error = xs_fmt("ERROR: disk layout changed - execute 'snac upgrade' first");
                         error = xs_fmt("ERROR: disk layout changed - execute 'snac upgrade' first");
-                    else
-                        ret = 1;
+                    else {
+                        if (!check_strip_tool()) {
+                            const char *mp = xs_dict_get(srv_config, "mogrify_path");
+                            if (mp == NULL) mp = "mogrify";
+                            error = xs_fmt("ERROR: strip_exif enabled but '%s' not found or not working (set 'mogrify_path' in server.json)", mp);
+                        }
+                        else
+                            ret = 1;
+                    }
                 }
                 }
             }
             }
 
 
@@ -2710,6 +2717,8 @@ void static_put(snac *snac, const char *id, const char *data, int size)
     if (fn && (f = fopen(fn, "wb")) != NULL) {
     if (fn && (f = fopen(fn, "wb")) != NULL) {
         fwrite(data, size, 1, f);
         fwrite(data, size, 1, f);
         fclose(f);
         fclose(f);
+
+        strip_media(fn);
     }
     }
 }
 }
 
 

+ 8 - 0
doc/snac.8

@@ -296,6 +296,14 @@ outgoing messages (default: 15). Anyway, whenever any incoming activity from a
 failed instance is detected, this counter is reset for it.
 failed instance is detected, this counter is reset for it.
 .It Ic vkey
 .It Ic vkey
 Public vapid key. Used for notification on some client.
 Public vapid key. Used for notification on some client.
+.It Ic strip_exif
+If set to true, EXIF metadata will be stripped from uploaded images (jpg, png, webp, heic, avif, tiff, gif, bmp). This requires the
+.Nm mogrify
+tool to be installed. If
+.Nm snac
+cannot find or execute the tool at startup, it will refuse to run.
+.It Ic mogrify_path
+Overrides the default "mogrify" command name or path. Use this if the tool is not in the system PATH or has a different name.
 .El
 .El
 .Pp
 .Pp
 You must restart the server to make effective these changes.
 You must restart the server to make effective these changes.

+ 64 - 0
snac.c

@@ -32,6 +32,7 @@
 
 
 #include <sys/time.h>
 #include <sys/time.h>
 #include <sys/stat.h>
 #include <sys/stat.h>
+#include <sys/wait.h>
 
 
 xs_str *srv_basedir = NULL;
 xs_str *srv_basedir = NULL;
 xs_dict *srv_config = NULL;
 xs_dict *srv_config = NULL;
@@ -170,3 +171,66 @@ int check_password(const char *uid, const char *passwd, const char *hash)
 
 
     return ret;
     return ret;
 }
 }
+
+
+int strip_media(const char *fn)
+/* strips EXIF data from a file */
+{
+    int ret = 0;
+    const xs_val *v = xs_dict_get(srv_config, "strip_exif");
+
+    if (xs_type(v) == XSTYPE_TRUE) {
+        xs *l_fn = xs_tolower_i(xs_dup(fn));
+
+        /* check extensions */
+        if (xs_endswith(l_fn, ".jpg") || xs_endswith(l_fn, ".jpeg") ||
+            xs_endswith(l_fn, ".png") || xs_endswith(l_fn, ".webp") ||
+            xs_endswith(l_fn, ".heic") || xs_endswith(l_fn, ".heif") ||
+            xs_endswith(l_fn, ".avif") || xs_endswith(l_fn, ".tiff") ||
+            xs_endswith(l_fn, ".gif")  || xs_endswith(l_fn, ".bmp")) {
+
+            const char *mp = xs_dict_get(srv_config, "mogrify_path");
+            if (mp == NULL)
+                mp = "mogrify";
+
+            xs *cmd = xs_fmt("%s -strip \"%s\" 2>/dev/null", mp, fn);
+
+            ret = system(cmd);
+
+            if (ret != 0) {
+                int code = 0;
+                if (WIFEXITED(ret))
+                    code = WEXITSTATUS(ret);
+                
+                if (code == 127)
+                    srv_log(xs_fmt("strip_media: error stripping %s. '%s' not found (exit 127). Set 'mogrify_path' in server.json.", fn, mp));
+                else
+                    srv_log(xs_fmt("strip_media: error stripping %s %d", fn, ret));
+            }
+            else
+                srv_debug(1, xs_fmt("strip_media: stripped %s", fn));
+        }
+    }
+
+    return ret;
+}
+
+
+int check_strip_tool(void)
+{
+    const xs_val *v = xs_dict_get(srv_config, "strip_exif");
+    int ret = 1;
+
+    if (xs_type(v) == XSTYPE_TRUE) {
+        const char *mp = xs_dict_get(srv_config, "mogrify_path");
+        if (mp == NULL)
+            mp = "mogrify";
+
+        xs *cmd = xs_fmt("%s -version 2>/dev/null >/dev/null", mp);
+        
+        if (system(cmd) != 0)
+            ret = 0;
+    }
+
+    return ret;
+}

+ 3 - 0
snac.h

@@ -105,6 +105,9 @@ int validate_uid(const char *uid);
 xs_str *hash_password(const char *uid, const char *passwd, const char *nonce);
 xs_str *hash_password(const char *uid, const char *passwd, const char *nonce);
 int check_password(const char *uid, const char *passwd, const char *hash);
 int check_password(const char *uid, const char *passwd, const char *hash);
 
 
+int strip_media(const char *fn);
+int check_strip_tool(void);
+
 void srv_archive(const char *direction, const char *url, xs_dict *req,
 void srv_archive(const char *direction, const char *url, xs_dict *req,
                  const char *payload, int p_size,
                  const char *payload, int p_size,
                  int status, xs_dict *headers,
                  int status, xs_dict *headers,