summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorGravatar grunfink2026-01-12 14:27:05 +0100
committerGravatar grunfink2026-01-12 14:27:05 +0100
commit3ffdf91a4c6e90b0738a767d75b80941500713e0 (patch)
treec716b2fc1121c97a4ee82f3993d97ed41e63a7ad
parentRevert "Updated documentation." (diff)
parentImplement metadata stripping for uploaded videos (diff)
downloadsnac2-3ffdf91a4c6e90b0738a767d75b80941500713e0.tar.gz
snac2-3ffdf91a4c6e90b0738a767d75b80941500713e0.tar.xz
snac2-3ffdf91a4c6e90b0738a767d75b80941500713e0.zip
Merge pull request 'Implement metadata stripping for uploaded photos and videos' (#515) from draga79/snac2:master into master
Reviewed-on: https://codeberg.org/grunfink/snac2/pulls/515
-rw-r--r--data.c13
-rw-r--r--doc/snac.812
-rw-r--r--snac.c150
-rw-r--r--snac.h3
4 files changed, 176 insertions, 2 deletions
diff --git a/data.c b/data.c
index 22ea7b0..f32dc81 100644
--- a/data.c
+++ b/data.c
@@ -89,8 +89,15 @@ int srv_open(const char *basedir, int auto_upgrade)
89 else { 89 else {
90 if (xs_number_get(xs_dict_get(srv_config, "layout")) < disk_layout) 90 if (xs_number_get(xs_dict_get(srv_config, "layout")) < disk_layout)
91 error = xs_fmt("ERROR: disk layout changed - execute 'snac upgrade' first"); 91 error = xs_fmt("ERROR: disk layout changed - execute 'snac upgrade' first");
92 else 92 else {
93 ret = 1; 93 if (!check_strip_tool()) {
94 const char *mp = xs_dict_get(srv_config, "mogrify_path");
95 if (mp == NULL) mp = "mogrify";
96 error = xs_fmt("ERROR: strip_exif enabled but '%s' not found or not working (set 'mogrify_path' in server.json)", mp);
97 }
98 else
99 ret = 1;
100 }
94 } 101 }
95 } 102 }
96 103
@@ -2710,6 +2717,8 @@ void static_put(snac *snac, const char *id, const char *data, int size)
2710 if (fn && (f = fopen(fn, "wb")) != NULL) { 2717 if (fn && (f = fopen(fn, "wb")) != NULL) {
2711 fwrite(data, size, 1, f); 2718 fwrite(data, size, 1, f);
2712 fclose(f); 2719 fclose(f);
2720
2721 strip_media(fn);
2713 } 2722 }
2714} 2723}
2715 2724
diff --git a/doc/snac.8 b/doc/snac.8
index b8a75fa..c53bb59 100644
--- a/doc/snac.8
+++ b/doc/snac.8
@@ -296,6 +296,18 @@ outgoing messages (default: 15). Anyway, whenever any incoming activity from a
296failed instance is detected, this counter is reset for it. 296failed instance is detected, this counter is reset for it.
297.It Ic vkey 297.It Ic vkey
298Public vapid key. Used for notification on some client. 298Public vapid key. Used for notification on some client.
299.It Ic strip_exif
300If set to true, EXIF and other metadata will be stripped from uploaded images (jpg, png, webp, heic, avif, tiff, gif, bmp) and videos (mp4, m4v, mov, webm, mkv, avi). This requires the
301.Nm mogrify
302(from ImageMagick) and
303.Nm ffmpeg
304tools to be installed. If
305.Nm snac
306cannot find or execute these tools at startup, it will refuse to run.
307.It Ic mogrify_path
308Overrides the default "mogrify" command name or path. Use this if the tool is not in the system PATH or has a different name.
309.It Ic ffmpeg_path
310Overrides the default "ffmpeg" command name or path. Use this if the tool is not in the system PATH or has a different name.
299.El 311.El
300.Pp 312.Pp
301You must restart the server to make effective these changes. 313You must restart the server to make effective these changes.
diff --git a/snac.c b/snac.c
index 41db86d..87b0d63 100644
--- a/snac.c
+++ b/snac.c
@@ -33,6 +33,9 @@
33 33
34#include <sys/time.h> 34#include <sys/time.h>
35#include <sys/stat.h> 35#include <sys/stat.h>
36#include <sys/wait.h>
37#include <limits.h>
38#include <stdlib.h>
36 39
37xs_str *srv_basedir = NULL; 40xs_str *srv_basedir = NULL;
38xs_dict *srv_config = NULL; 41xs_dict *srv_config = NULL;
@@ -171,3 +174,150 @@ int check_password(const char *uid, const char *passwd, const char *hash)
171 174
172 return ret; 175 return ret;
173} 176}
177
178
179int strip_media(const char *fn)
180/* strips EXIF data from a file */
181{
182 int ret = 0;
183
184 const xs_val *v = xs_dict_get(srv_config, "strip_exif");
185
186 if (xs_type(v) == XSTYPE_TRUE) {
187 /* Heuristic: find 'user/' in the path to make it relative */
188 /* This works for ~/user/..., /var/snac/user/..., etc. */
189 const char *r_fn = strstr(fn, "user/");
190
191 if (r_fn == NULL) {
192 /* Fallback: try to strip ~/ if present */
193 if (strncmp(fn, "~/", 2) == 0)
194 r_fn = fn + 2;
195 else
196 r_fn = fn;
197 }
198
199 xs *l_fn = xs_tolower_i(xs_dup(r_fn));
200
201 /* check image extensions */
202 if (xs_endswith(l_fn, ".jpg") || xs_endswith(l_fn, ".jpeg") ||
203 xs_endswith(l_fn, ".png") || xs_endswith(l_fn, ".webp") ||
204 xs_endswith(l_fn, ".heic") || xs_endswith(l_fn, ".heif") ||
205 xs_endswith(l_fn, ".avif") || xs_endswith(l_fn, ".tiff") ||
206 xs_endswith(l_fn, ".gif") || xs_endswith(l_fn, ".bmp")) {
207
208 const char *mp = xs_dict_get(srv_config, "mogrify_path");
209 if (mp == NULL)
210 mp = "mogrify";
211
212 xs *cmd = xs_fmt("cd \"%s\" && %s -auto-orient -strip \"%s\" 2>/dev/null", srv_basedir, mp, r_fn);
213
214 ret = system(cmd);
215
216 if (ret != 0) {
217 int code = 0;
218 if (WIFEXITED(ret))
219 code = WEXITSTATUS(ret);
220
221 if (code == 127)
222 srv_log(xs_fmt("strip_media: error stripping %s. '%s' not found (exit 127). Set 'mogrify_path' in server.json.", r_fn, mp));
223 else
224 srv_log(xs_fmt("strip_media: error stripping %s %d", r_fn, ret));
225 }
226 else
227 srv_debug(1, xs_fmt("strip_media: stripped %s", r_fn));
228 }
229 else
230 /* check video extensions */
231 if (xs_endswith(l_fn, ".mp4") || xs_endswith(l_fn, ".m4v") ||
232 xs_endswith(l_fn, ".mov") || xs_endswith(l_fn, ".webm") ||
233 xs_endswith(l_fn, ".mkv") || xs_endswith(l_fn, ".avi")) {
234
235 const char *fp = xs_dict_get(srv_config, "ffmpeg_path");
236 if (fp == NULL)
237 fp = "ffmpeg";
238
239 /* ffmpeg cannot modify in-place, so we need a temp file */
240 /* we must preserve valid extension for ffmpeg to guess the format */
241 const char *ext = strrchr(r_fn, '.');
242 if (ext == NULL) ext = "";
243 xs *tmp_fn = xs_fmt("%s.tmp%s", r_fn, ext);
244
245 /* -map_metadata -1 strips all global metadata */
246 /* -c copy copies input streams without re-encoding */
247 /* we don't silence stderr so we can debug issues */
248 /* we explicitly cd to srv_basedir to ensure relative paths work */
249 xs *cmd = xs_fmt("cd \"%s\" && %s -y -i \"%s\" -map_metadata -1 -c copy \"%s\"", srv_basedir, fp, r_fn, tmp_fn);
250
251 ret = system(cmd);
252
253 if (ret != 0) {
254 int code = 0;
255 if (WIFEXITED(ret))
256 code = WEXITSTATUS(ret);
257
258 if (code == 127)
259 srv_log(xs_fmt("strip_media: error stripping %s. '%s' not found (exit 127). Set 'ffmpeg_path' in server.json.", r_fn, fp));
260 else {
261 srv_log(xs_fmt("strip_media: error stripping %s %d", r_fn, ret));
262 srv_log(xs_fmt("strip_media: command was: %s", cmd));
263 }
264
265 /* try to cleanup, just in case */
266 /* unlink needs full path too if we are not in basedir */
267 xs *full_tmp_fn = xs_fmt("%s/%s", srv_basedir, tmp_fn);
268 unlink(full_tmp_fn);
269 }
270 else {
271 /* rename tmp file to original */
272 /* use full path for source because it was created relative to basedir */
273 xs *full_tmp_fn = xs_fmt("%s/%s", srv_basedir, tmp_fn);
274
275 if (rename(full_tmp_fn, fn) == 0)
276 srv_debug(1, xs_fmt("strip_media: stripped %s", fn));
277 else
278 srv_log(xs_fmt("strip_media: error renaming %s to %s", full_tmp_fn, fn));
279 }
280 }
281 }
282
283 return ret;
284}
285
286
287int check_strip_tool(void)
288{
289 const xs_val *v = xs_dict_get(srv_config, "strip_exif");
290 int ret = 1;
291
292 if (xs_type(v) == XSTYPE_TRUE) {
293 /* check mogrify */
294 {
295 const char *mp = xs_dict_get(srv_config, "mogrify_path");
296 if (mp == NULL)
297 mp = "mogrify";
298
299 xs *cmd = xs_fmt("%s -version 2>/dev/null >/dev/null", mp);
300
301 if (system(cmd) != 0) {
302 srv_log(xs_fmt("check_strip_tool: '%s' not working", mp));
303 ret = 0;
304 }
305 }
306
307 /* check ffmpeg */
308 if (ret) {
309 const char *fp = xs_dict_get(srv_config, "ffmpeg_path");
310 if (fp == NULL)
311 fp = "ffmpeg";
312
313 xs *cmd = xs_fmt("%s -version 2>/dev/null >/dev/null", fp);
314
315 if (system(cmd) != 0) {
316 srv_log(xs_fmt("check_strip_tool: '%s' not working", fp));
317 ret = 0;
318 }
319 }
320 }
321
322 return ret;
323}
diff --git a/snac.h b/snac.h
index d57391f..469982d 100644
--- a/snac.h
+++ b/snac.h
@@ -105,6 +105,9 @@ int validate_uid(const char *uid);
105xs_str *hash_password(const char *uid, const char *passwd, const char *nonce); 105xs_str *hash_password(const char *uid, const char *passwd, const char *nonce);
106int check_password(const char *uid, const char *passwd, const char *hash); 106int check_password(const char *uid, const char *passwd, const char *hash);
107 107
108int strip_media(const char *fn);
109int check_strip_tool(void);
110
108void srv_archive(const char *direction, const char *url, xs_dict *req, 111void srv_archive(const char *direction, const char *url, xs_dict *req,
109 const char *payload, int p_size, 112 const char *payload, int p_size,
110 int status, xs_dict *headers, 113 int status, xs_dict *headers,