From 46bb87c2b54c9b9d9a4cd462b47158ede1eec170 Mon Sep 17 00:00:00 2001 From: grunfink Date: Sat, 29 Nov 2025 06:17:27 +0100 Subject: Moved is_msg_mine() to data.c. --- data.c | 14 ++++++++++++++ 1 file changed, 14 insertions(+) (limited to 'data.c') diff --git a/data.c b/data.c index d291ba7..c2fdccb 100644 --- a/data.c +++ b/data.c @@ -1358,6 +1358,20 @@ int pending_count(snac *user) } +int is_msg_mine(snac *user, const char *id) +/* returns true if a post id is by the given user */ +{ + int ret = 0; + + if (xs_is_string(id)) { + xs *s1 = xs_fmt("%s/", user->actor); + ret = xs_startswith(id, s1); + } + + return ret; +} + + /** timeline **/ double timeline_mtime(snac *snac) -- cgit v1.2.3 From a45c1ce152011e8fe25eb1d25594ac5705f65404 Mon Sep 17 00:00:00 2001 From: rako Date: Fri, 28 Nov 2025 10:37:49 +0100 Subject: Fix user matching In order to be a proper prefix, the actor url must end with a '/' otherwise it can match another user that starts with the same prefix: for example 'testuser' will match anything made by 'testuser2' --- data.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) (limited to 'data.c') diff --git a/data.c b/data.c index c2fdccb..688d2e3 100644 --- a/data.c +++ b/data.c @@ -1467,7 +1467,7 @@ void timeline_update_indexes(snac *snac, const char *id) { object_user_cache_add(snac, id, "private"); - if (xs_startswith(id, snac->actor)) { + if (is_msg_mine(snac, id)) { xs *msg = NULL; if (valid_status(object_get(id, &msg))) { @@ -1927,7 +1927,7 @@ int pin(snac *user, const char *id) { int ret = -2; - if (xs_startswith(id, user->actor)) { + if (is_msg_mine(user, id)) { if (is_pinned(user, id)) ret = -3; else @@ -3527,7 +3527,7 @@ void enqueue_output(snac *snac, const xs_dict *msg, const xs_str *inbox, int retries, int p_status) /* enqueues an output message to an inbox */ { - if (xs_startswith(inbox, snac->actor)) { + if (is_msg_mine(snac, inbox)) { snac_debug(snac, 1, xs_str_new("refusing enqueue to myself")); return; } @@ -4055,7 +4055,7 @@ void delete_purged_posts(snac *user, int days) if (xs_is_dict(msg)) { const char *id = xs_dict_get(msg, "id"); - if (xs_is_string(id) && xs_startswith(id, user->actor)) { + if (xs_is_string(id) && is_msg_mine(user, id)) { xs *d_msg = msg_delete(user, id); enqueue_message(user, d_msg); -- cgit v1.2.3 From 85ed0eb0d535700a5df837c37f51848811e461a0 Mon Sep 17 00:00:00 2001 From: violette Date: Thu, 18 Dec 2025 07:58:24 +0100 Subject: Added emoji reactions (contributed by violette). --- data.c | 124 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 116 insertions(+), 8 deletions(-) (limited to 'data.c') diff --git a/data.c b/data.c index 688d2e3..07eea3c 100644 --- a/data.c +++ b/data.c @@ -1043,6 +1043,14 @@ xs_list *object_children(const char *id) } +xs_list *object_get_emoji_reacts(const char *id) +/* returns the list of an object's emoji reactions */ +{ + xs *fn = _object_index_fn(id, "_e.idx"); + return index_list(fn, XS_ALL); +} + + xs_list *object_likes(const char *id) { xs *fn = _object_index_fn(id, "_l.idx"); @@ -1086,12 +1094,26 @@ int object_admire(const char *id, const char *actor, int like) int object_unadmire(const char *id, const char *actor, int like) -/* actor no longer likes or announces this object */ +/* actor retrives their likes, announces or emojis this object */ { + switch (like) { + case 0: + like = 'a'; + break; + case 1: + like = 'l'; + break; + case 2: + like = 'e'; + break; + } int status; xs *fn = _object_fn(id); - fn = xs_replace_i(fn, ".json", like ? "_l.idx" : "_a.idx"); + char sfx[7] = "_x.idx"; + sfx[1] = like; + + fn = xs_replace_i(fn, ".json", sfx); status = index_del(fn, actor); @@ -1099,7 +1121,46 @@ int object_unadmire(const char *id, const char *actor, int like) index_gc(fn); srv_debug(0, - xs_fmt("object_unadmire (%s) %s %s %d", like ? "Like" : "Announce", actor, fn, status)); + xs_fmt("object_unadmire (%s) %s %s %d", like >= 'e' ? + (like == 'l' ? "Like" : "EmojiReact") : "Announce" , actor, fn, status)); + + return status; +} + +int object_emoji_react(const char *mid, const char *eid) +/* actor reacts w/ an emoji */ +{ + int status = HTTP_STATUS_OK; + xs *fn = _object_fn(mid); + + fn = xs_replace_i(fn, ".json", "_e.idx"); + + if (!index_in(fn, eid)) { + status = index_add(fn, eid); + + srv_debug(1, xs_fmt("object_emoji_react (%s) added %s to %s", "EmojiReact", eid, fn)); + } + + return status; +} + + +int object_rm_emoji_react(const char *mid, const char *eid) +/* actor retrives their emoji reaction */ +{ + int status; + xs *fn = _object_fn(mid); + + fn = xs_replace_i(fn, ".json", "_e.idx"); + + status = index_del(fn, eid); + object_del(eid); + + if (valid_status(status)) + index_gc(fn); + + srv_debug(0, + xs_fmt("object_unadmire (EmojiReact) %s %s %d", eid, fn, status)); return status; } @@ -1506,19 +1567,47 @@ int timeline_add(snac *snac, const char *id, const xs_dict *o_msg) } -int timeline_admire(snac *snac, const char *id, const char *admirer, int like) -/* updates a timeline entry with a new admiration */ +int timeline_emoji_react(const char *act, const char *id, xs_dict *msg) +/* adds an emoji reaction to a message */ +{ + msg = xs_dict_append(msg, "attributedTo", act); + msg = xs_dict_set(msg, "type", "EmojiReact"); + const char *emote_id = xs_dict_get(msg, "id"); + + int ret = object_add(emote_id, msg); + if (ret == HTTP_STATUS_OK || ret == HTTP_STATUS_CREATED) + ret = object_emoji_react(id, emote_id); + + return ret; +} + + +int timeline_admire(snac *snac, const char *id, + const char *admirer, int like, xs_dict *msg) +/* updates a timeline entry with a new admiration or emoji reaction */ { + int ret; + const char *content = xs_dict_get_path(msg, "content"); + const char *type = xs_dict_get_path(msg, "type"); + /* if we are admiring this, add to both timelines */ if (!like && strcmp(admirer, snac->actor) == 0) { object_user_cache_add(snac, id, "public"); object_user_cache_add(snac, id, "private"); } - int ret = object_admire(id, admirer, like); + /* use utf <3 as a like, as it is ugly */ + if (type && xs_match(type, "Like|EmojiReact|Emoji") && + content && strcmp(content, "❤") != 0) { + ret = timeline_emoji_react(xs_dup(snac->actor), id, xs_dup(msg)); + snac_debug(snac, 1, xs_fmt("timeline_emoji_react %s", id)); + } - snac_debug(snac, 1, xs_fmt("timeline_admire (%s) %s %s", - like ? "Like" : "Announce", id, admirer)); + else { + ret = object_admire(id, admirer, like); + snac_debug(snac, 1, xs_fmt("timeline_admire (%s) %s %s", + like ? "Like" : "Announce", id, admirer)); + } return ret; } @@ -1867,6 +1956,25 @@ xs_list *muted_list(snac *user) return l; } +/** emojis react **/ + +const xs_str *emoji_reacted(snac *user, const char *id) +/* returns the emoji an user reacted to a message */ +{ + xs *emojis = object_get_emoji_reacts(id); + int c = 0; + const char *v; + xs_dict *msg; + + while (xs_list_next(emojis, &v, &c)) { + if (object_get_by_md5(v, &msg)) { + const xs_val *act = xs_dict_get(msg, "actor"); + if (act && strcmp(act, user->actor) == 0) + return xs_dict_get(msg, "content"); + } + } + return NULL; +} /** bookmarking **/ -- cgit v1.2.3 From 7c065cbc9879582985cef3d3ad8b2ae197b8b851 Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 18 Dec 2025 08:09:10 +0100 Subject: Fixed some xs_dup() leaks. --- data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'data.c') diff --git a/data.c b/data.c index 07eea3c..bbe92b8 100644 --- a/data.c +++ b/data.c @@ -1599,7 +1599,7 @@ int timeline_admire(snac *snac, const char *id, /* use utf <3 as a like, as it is ugly */ if (type && xs_match(type, "Like|EmojiReact|Emoji") && content && strcmp(content, "❤") != 0) { - ret = timeline_emoji_react(xs_dup(snac->actor), id, xs_dup(msg)); + ret = timeline_emoji_react(snac->actor, id, xs_dup(msg)); snac_debug(snac, 1, xs_fmt("timeline_emoji_react %s", id)); } -- cgit v1.2.3 From fd45b94c09233221d7b98a7270c768272bd25ac9 Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 18 Dec 2025 08:41:28 +0100 Subject: Fixed leak in timeline_admire(). --- data.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) (limited to 'data.c') diff --git a/data.c b/data.c index bbe92b8..8f7050c 100644 --- a/data.c +++ b/data.c @@ -1567,9 +1567,10 @@ int timeline_add(snac *snac, const char *id, const xs_dict *o_msg) } -int timeline_emoji_react(const char *act, const char *id, xs_dict *msg) +int timeline_emoji_react(const char *act, const char *id, const xs_dict *msg_o) /* adds an emoji reaction to a message */ { + xs *msg = xs_dup(msg_o); msg = xs_dict_append(msg, "attributedTo", act); msg = xs_dict_set(msg, "type", "EmojiReact"); const char *emote_id = xs_dict_get(msg, "id"); @@ -1583,7 +1584,7 @@ int timeline_emoji_react(const char *act, const char *id, xs_dict *msg) int timeline_admire(snac *snac, const char *id, - const char *admirer, int like, xs_dict *msg) + const char *admirer, int like, const xs_dict *msg) /* updates a timeline entry with a new admiration or emoji reaction */ { int ret; @@ -1599,7 +1600,7 @@ int timeline_admire(snac *snac, const char *id, /* use utf <3 as a like, as it is ugly */ if (type && xs_match(type, "Like|EmojiReact|Emoji") && content && strcmp(content, "❤") != 0) { - ret = timeline_emoji_react(snac->actor, id, xs_dup(msg)); + ret = timeline_emoji_react(snac->actor, id, msg); snac_debug(snac, 1, xs_fmt("timeline_emoji_react %s", id)); } -- cgit v1.2.3 From 219f3e6808bdcf8a03cce404caa5cb6a008d00c5 Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 18 Dec 2025 08:54:21 +0100 Subject: Fixed leak in emoji_reacted(). --- data.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) (limited to 'data.c') diff --git a/data.c b/data.c index 8f7050c..023bae6 100644 --- a/data.c +++ b/data.c @@ -1959,19 +1959,19 @@ xs_list *muted_list(snac *user) /** emojis react **/ -const xs_str *emoji_reacted(snac *user, const char *id) +xs_str *emoji_reacted(snac *user, const char *id) /* returns the emoji an user reacted to a message */ { xs *emojis = object_get_emoji_reacts(id); int c = 0; const char *v; - xs_dict *msg; while (xs_list_next(emojis, &v, &c)) { + xs *msg = NULL; if (object_get_by_md5(v, &msg)) { const xs_val *act = xs_dict_get(msg, "actor"); if (act && strcmp(act, user->actor) == 0) - return xs_dict_get(msg, "content"); + return xs_dup(xs_dict_get(msg, "content")); } } return NULL; -- cgit v1.2.3 From 11af00194e3e0ec15e17a23556dc2929f92e0210 Mon Sep 17 00:00:00 2001 From: grunfink Date: Thu, 1 Jan 2026 17:01:03 +0100 Subject: Bumped copyright year. --- data.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) (limited to 'data.c') diff --git a/data.c b/data.c index 023bae6..22ea7b0 100644 --- a/data.c +++ b/data.c @@ -1,5 +1,5 @@ /* snac - A simple, minimalistic ActivityPub instance */ -/* copyright (c) 2022 - 2025 grunfink et al. / MIT license */ +/* copyright (c) 2022 - 2026 grunfink et al. / MIT license */ #include "xs.h" #include "xs_hex.h" -- cgit v1.2.3 From 688c54c87355b5424f33f7b089814460a74af594 Mon Sep 17 00:00:00 2001 From: Stefano Marinelli Date: Tue, 6 Jan 2026 11:02:36 +0100 Subject: 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`. --- data.c | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) (limited to 'data.c') 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) else { if (xs_number_get(xs_dict_get(srv_config, "layout")) < disk_layout) 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) { fwrite(data, size, 1, f); fclose(f); + + strip_media(fn); } } -- cgit v1.2.3