diff options
Diffstat (limited to 'snac.c')
| -rw-r--r-- | snac.c | 191 |
1 files changed, 191 insertions, 0 deletions
| @@ -33,12 +33,18 @@ | |||
| 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 <stdio.h> | ||
| 39 | #include <stdlib.h> | ||
| 40 | #include <unistd.h> | ||
| 36 | 41 | ||
| 37 | xs_str *srv_basedir = NULL; | 42 | xs_str *srv_basedir = NULL; |
| 38 | xs_dict *srv_config = NULL; | 43 | xs_dict *srv_config = NULL; |
| 39 | xs_str *srv_baseurl = NULL; | 44 | xs_str *srv_baseurl = NULL; |
| 40 | xs_str *srv_proxy_token_seed = NULL; | 45 | xs_str *srv_proxy_token_seed = NULL; |
| 41 | xs_dict *srv_langs = NULL; | 46 | xs_dict *srv_langs = NULL; |
| 47 | const char *months[12] = {0}; | ||
| 42 | 48 | ||
| 43 | int dbglevel = 0; | 49 | int dbglevel = 0; |
| 44 | 50 | ||
| @@ -171,3 +177,188 @@ int check_password(const char *uid, const char *passwd, const char *hash) | |||
| 171 | 177 | ||
| 172 | return ret; | 178 | return ret; |
| 173 | } | 179 | } |
| 180 | |||
| 181 | |||
| 182 | char* findprog(const char *prog) | ||
| 183 | /* find a prog in PATH and return the first match */ | ||
| 184 | { | ||
| 185 | char *path_env, *path, *dir, filename[PATH_MAX]; | ||
| 186 | int len; | ||
| 187 | struct stat sbuf; | ||
| 188 | |||
| 189 | path_env = getenv("PATH"); | ||
| 190 | if (!prog || !path_env) | ||
| 191 | return NULL; | ||
| 192 | |||
| 193 | path_env = strdup(path_env); | ||
| 194 | if (!path_env) | ||
| 195 | return NULL; | ||
| 196 | path = path_env; | ||
| 197 | |||
| 198 | while ((dir = strsep(&path, ":")) != NULL) { | ||
| 199 | /* empty entries as ./ instead of / */ | ||
| 200 | if (*dir == '\0') | ||
| 201 | dir = "."; | ||
| 202 | |||
| 203 | /* strip trailing / */ | ||
| 204 | len = strlen(dir); | ||
| 205 | while (len > 0 && dir[len-1] == '/') | ||
| 206 | dir[--len] = '\0'; | ||
| 207 | |||
| 208 | len = snprintf(filename, sizeof(filename), "%s/%s", dir, prog); | ||
| 209 | if (len > 0 && len < (int) sizeof(filename) && | ||
| 210 | (stat(filename, &sbuf) == 0) && S_ISREG(sbuf.st_mode) && | ||
| 211 | access(filename, X_OK) == 0) { | ||
| 212 | free(path_env); | ||
| 213 | return strdup(filename); | ||
| 214 | } | ||
| 215 | } | ||
| 216 | |||
| 217 | free(path_env); | ||
| 218 | return NULL; | ||
| 219 | } | ||
| 220 | |||
| 221 | |||
| 222 | int strip_media(const char *fn) | ||
| 223 | /* strips EXIF data from a file */ | ||
| 224 | { | ||
| 225 | int ret = 0; | ||
| 226 | |||
| 227 | const xs_val *v = xs_dict_get(srv_config, "strip_exif"); | ||
| 228 | |||
| 229 | if (xs_type(v) == XSTYPE_TRUE) { | ||
| 230 | /* Heuristic: find 'user/' in the path to make it relative */ | ||
| 231 | /* This works for ~/user/..., /var/snac/user/..., etc. */ | ||
| 232 | const char *r_fn = strstr(fn, "user/"); | ||
| 233 | |||
| 234 | if (r_fn == NULL) { | ||
| 235 | /* Fallback: try to strip ~/ if present */ | ||
| 236 | if (strncmp(fn, "~/", 2) == 0) | ||
| 237 | r_fn = fn + 2; | ||
| 238 | else | ||
| 239 | r_fn = fn; | ||
| 240 | } | ||
| 241 | |||
| 242 | xs *l_fn = xs_tolower_i(xs_dup(r_fn)); | ||
| 243 | |||
| 244 | /* check image extensions */ | ||
| 245 | if (xs_endswith(l_fn, ".jpg") || xs_endswith(l_fn, ".jpeg") || | ||
| 246 | xs_endswith(l_fn, ".png") || xs_endswith(l_fn, ".webp") || | ||
| 247 | xs_endswith(l_fn, ".heic") || xs_endswith(l_fn, ".heif") || | ||
| 248 | xs_endswith(l_fn, ".avif") || xs_endswith(l_fn, ".tiff") || | ||
| 249 | xs_endswith(l_fn, ".gif") || xs_endswith(l_fn, ".bmp")) { | ||
| 250 | |||
| 251 | const char *mp = xs_dict_get(srv_config, "mogrify_path"); | ||
| 252 | |||
| 253 | pid_t pid = fork(); | ||
| 254 | if (pid == -1) { | ||
| 255 | srv_log(xs_fmt("strip_media: cannot fork()")); | ||
| 256 | return -1; | ||
| 257 | } else if (pid == 0) { | ||
| 258 | chdir(srv_basedir); | ||
| 259 | execl(mp, "-auto-orient", "-strip", r_fn, (char*) NULL); | ||
| 260 | _exit(1); | ||
| 261 | } | ||
| 262 | |||
| 263 | if (waitpid(pid, &ret, 0) == -1) { | ||
| 264 | srv_log(xs_fmt("strip_media: cannot waitpid()")); | ||
| 265 | return -1; | ||
| 266 | } | ||
| 267 | |||
| 268 | if (ret != 0) | ||
| 269 | srv_log(xs_fmt("strip_media: error stripping %s %d", r_fn, ret)); | ||
| 270 | else | ||
| 271 | srv_debug(1, xs_fmt("strip_media: stripped %s", r_fn)); | ||
| 272 | } | ||
| 273 | else | ||
| 274 | /* check video extensions */ | ||
| 275 | if (xs_endswith(l_fn, ".mp4") || xs_endswith(l_fn, ".m4v") || | ||
| 276 | xs_endswith(l_fn, ".mov") || xs_endswith(l_fn, ".webm") || | ||
| 277 | xs_endswith(l_fn, ".mkv") || xs_endswith(l_fn, ".avi")) { | ||
| 278 | |||
| 279 | const char *fp = xs_dict_get(srv_config, "ffmpeg_path"); | ||
| 280 | |||
| 281 | /* ffmpeg cannot modify in-place, so we need a temp file */ | ||
| 282 | /* we must preserve valid extension for ffmpeg to guess the format */ | ||
| 283 | const char *ext = strrchr(r_fn, '.'); | ||
| 284 | if (ext == NULL) ext = ""; | ||
| 285 | xs *tmp_fn = xs_fmt("%s.tmp%s", r_fn, ext); | ||
| 286 | |||
| 287 | pid_t pid = fork(); | ||
| 288 | if (pid == -1) { | ||
| 289 | srv_log(xs_fmt("strip_media: cannot fork()")); | ||
| 290 | return -1; | ||
| 291 | } else if (pid == 0) { | ||
| 292 | chdir(srv_basedir); | ||
| 293 | /* -map_metadata -1 strips all global metadata */ | ||
| 294 | /* -c copy copies input streams without re-encoding */ | ||
| 295 | execl(fp, "-y", "-i", r_fn, "-map_metadata", "-1", "-c", "copy", tmp_fn, (char*) NULL); | ||
| 296 | _exit(1); | ||
| 297 | } | ||
| 298 | |||
| 299 | if (waitpid(pid, &ret, 0) == -1) { | ||
| 300 | srv_log(xs_fmt("strip_media: cannot waitpid()")); | ||
| 301 | return -1; | ||
| 302 | } | ||
| 303 | |||
| 304 | if (ret != 0) { | ||
| 305 | srv_log(xs_fmt("strip_media: error stripping %s %d", r_fn, ret)); | ||
| 306 | |||
| 307 | /* try to cleanup, just in case */ | ||
| 308 | /* unlink needs full path too if we are not in basedir */ | ||
| 309 | xs *full_tmp_fn = xs_fmt("%s/%s", srv_basedir, tmp_fn); | ||
| 310 | unlink(full_tmp_fn); | ||
| 311 | } | ||
| 312 | else { | ||
| 313 | /* rename tmp file to original */ | ||
| 314 | /* use full path for source because it was created relative to basedir */ | ||
| 315 | xs *full_tmp_fn = xs_fmt("%s/%s", srv_basedir, tmp_fn); | ||
| 316 | |||
| 317 | if (rename(full_tmp_fn, fn) == 0) | ||
| 318 | srv_debug(1, xs_fmt("strip_media: stripped %s", fn)); | ||
| 319 | else | ||
| 320 | srv_log(xs_fmt("strip_media: error renaming %s to %s", full_tmp_fn, fn)); | ||
| 321 | } | ||
| 322 | } | ||
| 323 | } | ||
| 324 | |||
| 325 | return ret; | ||
| 326 | } | ||
| 327 | |||
| 328 | |||
| 329 | int check_strip_tool(void) | ||
| 330 | /* check if strip_exif tools do exist and fix their absolute path */ | ||
| 331 | { | ||
| 332 | const xs_val *v = xs_dict_get(srv_config, "strip_exif"); | ||
| 333 | /* skip if unless strip_exif; return non-error */ | ||
| 334 | if (xs_type(v) != XSTYPE_TRUE) | ||
| 335 | return 1; | ||
| 336 | |||
| 337 | int ret = 1; | ||
| 338 | const char *progs[] = { "ffmpeg", "mogrify" }; | ||
| 339 | |||
| 340 | for (int i = 0; i < (int)(sizeof(progs) / sizeof(progs[0])); i++) { | ||
| 341 | xs_str *key = xs_fmt("%s_path", progs[i]); | ||
| 342 | |||
| 343 | const char *val = xs_dict_get(srv_config, key); | ||
| 344 | if (val == NULL) { | ||
| 345 | val = findprog(progs[i]); | ||
| 346 | if (val != NULL) | ||
| 347 | srv_debug(1, xs_fmt("check_strip_tool: found %s in PATH at %s", progs[i], val)); | ||
| 348 | } | ||
| 349 | |||
| 350 | if (val == NULL) { | ||
| 351 | srv_log(xs_fmt("check_strip_tool: %s not found in PATH", progs[i])); | ||
| 352 | ret = 0; | ||
| 353 | } else if (access(val, X_OK) != 0) { | ||
| 354 | srv_log(xs_fmt("check_strip_tool: %s '%s' is not executable", progs[i], val)); | ||
| 355 | ret = 0; | ||
| 356 | } else { | ||
| 357 | srv_config = xs_dict_set(srv_config, key, val); | ||
| 358 | } | ||
| 359 | |||
| 360 | xs_free(key); | ||
| 361 | } | ||
| 362 | |||
| 363 | return ret; | ||
| 364 | } | ||