diff options
Diffstat (limited to 'snac.c')
| -rw-r--r-- | snac.c | 153 |
1 files changed, 152 insertions, 1 deletions
| @@ -1,5 +1,5 @@ | |||
| 1 | /* snac - A simple, minimalistic ActivityPub instance */ | 1 | /* snac - A simple, minimalistic ActivityPub instance */ |
| 2 | /* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ | 2 | /* copyright (c) 2022 - 2026 grunfink et al. / MIT license */ |
| 3 | 3 | ||
| 4 | #define XS_IMPLEMENTATION | 4 | #define XS_IMPLEMENTATION |
| 5 | 5 | ||
| @@ -27,11 +27,15 @@ | |||
| 27 | #include "xs_html.h" | 27 | #include "xs_html.h" |
| 28 | #include "xs_po.h" | 28 | #include "xs_po.h" |
| 29 | #include "xs_webmention.h" | 29 | #include "xs_webmention.h" |
| 30 | #include "xs_list_tools.h" | ||
| 30 | 31 | ||
| 31 | #include "snac.h" | 32 | #include "snac.h" |
| 32 | 33 | ||
| 33 | #include <sys/time.h> | 34 | #include <sys/time.h> |
| 34 | #include <sys/stat.h> | 35 | #include <sys/stat.h> |
| 36 | #include <sys/wait.h> | ||
| 37 | #include <limits.h> | ||
| 38 | #include <stdlib.h> | ||
| 35 | 39 | ||
| 36 | xs_str *srv_basedir = NULL; | 40 | xs_str *srv_basedir = NULL; |
| 37 | xs_dict *srv_config = NULL; | 41 | xs_dict *srv_config = NULL; |
| @@ -170,3 +174,150 @@ int check_password(const char *uid, const char *passwd, const char *hash) | |||
| 170 | 174 | ||
| 171 | return ret; | 175 | return ret; |
| 172 | } | 176 | } |
| 177 | |||
| 178 | |||
| 179 | int 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 | |||
| 287 | int 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 | } | ||