diff options
| author | 2025-09-14 00:30:00 +0200 | |
|---|---|---|
| committer | 2025-09-20 17:16:54 +0200 | |
| commit | dcf5acaf538924aab532ea95bb311e4b80538856 (patch) | |
| tree | 47ae5f679eab641c1a0fe28edbe834fce242c68e /activitypub.c | |
| parent | Don't use # anchors in the reply paging, as Misskey seems to dislike them. (diff) | |
| download | penes-snac2-dcf5acaf538924aab532ea95bb311e4b80538856.tar.gz penes-snac2-dcf5acaf538924aab532ea95bb311e4b80538856.tar.xz penes-snac2-dcf5acaf538924aab532ea95bb311e4b80538856.zip | |
implementing visibility scopes
Diffstat (limited to 'activitypub.c')
| -rw-r--r-- | activitypub.c | 120 |
1 files changed, 99 insertions, 21 deletions
diff --git a/activitypub.c b/activitypub.c index 05c3fb1..1efd9ec 100644 --- a/activitypub.c +++ b/activitypub.c | |||
| @@ -569,14 +569,21 @@ xs_list *recipient_list(snac *snac, const xs_dict *msg, int expand_public) | |||
| 569 | } | 569 | } |
| 570 | 570 | ||
| 571 | while (xs_list_iter(&l, &v)) { | 571 | while (xs_list_iter(&l, &v)) { |
| 572 | if (expand_public && strcmp(v, public_address) == 0) { | 572 | if (expand_public) { |
| 573 | /* iterate the followers and add them */ | 573 | if (strcmp(v, public_address) == 0 || |
| 574 | xs *fwers = follower_list(snac); | 574 | /* check if it's a followers collection URL */ |
| 575 | const char *actor; | 575 | (xs_type(v) == XSTYPE_STRING && |
| 576 | 576 | strcmp(v, xs_fmt("%s/followers", snac->actor)) == 0) || | |
| 577 | char *p = fwers; | 577 | (xs_type(v) == XSTYPE_LIST && |
| 578 | while (xs_list_iter(&p, &actor)) | 578 | xs_list_in(v, xs_fmt("%s/followers", snac->actor)) != -1)) { |
| 579 | xs_set_add(&rcpts, actor); | 579 | /* iterate the followers and add them */ |
| 580 | xs *fwers = follower_list(snac); | ||
| 581 | const char *actor; | ||
| 582 | |||
| 583 | char *p = fwers; | ||
| 584 | while (xs_list_iter(&p, &actor)) | ||
| 585 | xs_set_add(&rcpts, actor); | ||
| 586 | } | ||
| 580 | } | 587 | } |
| 581 | else | 588 | else |
| 582 | xs_set_add(&rcpts, v); | 589 | xs_set_add(&rcpts, v); |
| @@ -1840,8 +1847,7 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | |||
| 1840 | xs_list *p; | 1847 | xs_list *p; |
| 1841 | const xs_val *v; | 1848 | const xs_val *v; |
| 1842 | 1849 | ||
| 1843 | /* FIXME: implement scope 3 */ | 1850 | const int priv = (scope == SCOPE_MENTIONED || scope == SCOPE_FOLLOWERS); |
| 1844 | int priv = scope == 1; | ||
| 1845 | 1851 | ||
| 1846 | if (rcpts == NULL) | 1852 | if (rcpts == NULL) |
| 1847 | to = xs_list_new(); | 1853 | to = xs_list_new(); |
| @@ -1901,9 +1907,13 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | |||
| 1901 | if ((v = xs_dict_get(p_msg, "conversation"))) | 1907 | if ((v = xs_dict_get(p_msg, "conversation"))) |
| 1902 | msg = xs_dict_append(msg, "conversation", v); | 1908 | msg = xs_dict_append(msg, "conversation", v); |
| 1903 | 1909 | ||
| 1904 | /* if this message is public, ours will also be */ | 1910 | /* if this message is public or unlisted, ours will also be */ |
| 1905 | if (!priv && is_msg_public(p_msg) && xs_list_in(to, public_address) == -1) | 1911 | const int orig_scope = get_msg_visibility(p_msg); |
| 1912 | if (!priv && orig_scope == SCOPE_PUBLIC && xs_list_in(to, public_address) == -1) | ||
| 1906 | to = xs_list_append(to, public_address); | 1913 | to = xs_list_append(to, public_address); |
| 1914 | else | ||
| 1915 | if (!priv && orig_scope == SCOPE_UNLISTED && xs_list_in(cc, public_address) == -1) | ||
| 1916 | cc = xs_list_append(cc, public_address); | ||
| 1907 | } | 1917 | } |
| 1908 | 1918 | ||
| 1909 | irt = xs_dup(in_reply_to); | 1919 | irt = xs_dup(in_reply_to); |
| @@ -1947,28 +1957,50 @@ xs_dict *msg_note(snac *snac, const xs_str *content, const xs_val *rcpts, | |||
| 1947 | if (ctxt == NULL) | 1957 | if (ctxt == NULL) |
| 1948 | ctxt = xs_fmt("%s#ctxt", id); | 1958 | ctxt = xs_fmt("%s#ctxt", id); |
| 1949 | 1959 | ||
| 1950 | /* add all mentions to the cc */ | 1960 | /* add all mentions to the appropriate field */ |
| 1951 | p = tag; | 1961 | p = tag; |
| 1952 | while (xs_list_iter(&p, &v)) { | 1962 | while (xs_list_iter(&p, &v)) { |
| 1953 | if (xs_type(v) == XSTYPE_DICT) { | 1963 | if (xs_type(v) == XSTYPE_DICT) { |
| 1954 | const char *t; | 1964 | const char *t; |
| 1955 | 1965 | ||
| 1956 | if (!xs_is_null(t = xs_dict_get(v, "type")) && strcmp(t, "Mention") == 0) { | 1966 | if (!xs_is_null(t = xs_dict_get(v, "type")) && strcmp(t, "Mention") == 0) { |
| 1957 | if (!xs_is_null(t = xs_dict_get(v, "href"))) | 1967 | if (!xs_is_null(t = xs_dict_get(v, "href"))) { |
| 1958 | cc = xs_list_append(cc, t); | 1968 | if (scope == SCOPE_MENTIONED) { |
| 1969 | /* for DMs, mentions go to 'to' */ | ||
| 1970 | to = xs_list_append(to, t); | ||
| 1971 | } else { | ||
| 1972 | /* for other visibility levels, mentions go to 'cc' */ | ||
| 1973 | cc = xs_list_append(cc, t); | ||
| 1974 | } | ||
| 1975 | } | ||
| 1959 | } | 1976 | } |
| 1960 | } | 1977 | } |
| 1961 | } | 1978 | } |
| 1962 | 1979 | ||
| 1963 | if (scope == 2) { | 1980 | if (scope == SCOPE_UNLISTED) { |
| 1964 | /* Mastodon's "quiet public": add public address to cc */ | 1981 | /* Mastodon's "quiet public": remove from to, add public address to cc */ |
| 1982 | int idx; | ||
| 1983 | if ((idx = xs_list_in(to, public_address)) != -1) | ||
| 1984 | to = xs_list_del(to, idx); | ||
| 1965 | if (xs_list_in(cc, public_address) == -1) | 1985 | if (xs_list_in(cc, public_address) == -1) |
| 1966 | cc = xs_list_append(cc, public_address); | 1986 | cc = xs_list_append(cc, public_address); |
| 1967 | } | 1987 | } |
| 1968 | else | 1988 | else |
| 1969 | /* no recipients? must be for everybody */ | 1989 | if (scope == SCOPE_FOLLOWERS) { |
| 1970 | if (!priv && xs_list_len(to) == 0) | 1990 | /* followers-only: add followers collection to to */ |
| 1971 | to = xs_list_append(to, public_address); | 1991 | xs *followers_url = xs_fmt("%s/followers", snac->actor); |
| 1992 | if (xs_list_in(to, followers_url) == -1) | ||
| 1993 | to = xs_list_append(to, followers_url); | ||
| 1994 | } | ||
| 1995 | else | ||
| 1996 | if (scope == SCOPE_PUBLIC) { | ||
| 1997 | /* public: ensure public address is in to and not in cc */ | ||
| 1998 | int idx; | ||
| 1999 | if ((idx = xs_list_in(cc, public_address)) != -1) | ||
| 2000 | cc = xs_list_del(cc, idx); | ||
| 2001 | if (xs_list_in(to, public_address) == -1) | ||
| 2002 | to = xs_list_append(to, public_address); | ||
| 2003 | } | ||
| 1972 | 2004 | ||
| 1973 | /* delete all cc recipients that also are in the to */ | 2005 | /* delete all cc recipients that also are in the to */ |
| 1974 | p = to; | 2006 | p = to; |
| @@ -2113,6 +2145,42 @@ xs_dict *msg_question(snac *user, const char *content, xs_list *attach, | |||
| 2113 | return msg; | 2145 | return msg; |
| 2114 | } | 2146 | } |
| 2115 | 2147 | ||
| 2148 | int get_msg_visibility(const xs_dict *msg) | ||
| 2149 | /* determine visibility from message based on CC, TO and /followers mark */ | ||
| 2150 | { | ||
| 2151 | const xs_val *to = xs_dict_get(msg, "to"); | ||
| 2152 | const xs_val *cc = xs_dict_get(msg, "cc"); | ||
| 2153 | |||
| 2154 | /* check if it's unlisted (public in cc but not in to) */ | ||
| 2155 | int pub_in_to = 0; | ||
| 2156 | int pub_in_cc = 0; | ||
| 2157 | |||
| 2158 | if ((xs_type(to) == XSTYPE_STRING && strcmp(to, public_address) == 0) || | ||
| 2159 | (xs_type(to) == XSTYPE_LIST && xs_list_in(to, public_address) != -1)) | ||
| 2160 | pub_in_to = 1; | ||
| 2161 | |||
| 2162 | if ((xs_type(cc) == XSTYPE_STRING && strcmp(cc, public_address) == 0) || | ||
| 2163 | (xs_type(cc) == XSTYPE_LIST && xs_list_in(cc, public_address) != -1)) | ||
| 2164 | pub_in_cc = 1; | ||
| 2165 | |||
| 2166 | if (!pub_in_to && pub_in_cc) { | ||
| 2167 | return SCOPE_UNLISTED; | ||
| 2168 | } | ||
| 2169 | if (pub_in_to && !pub_in_cc) { | ||
| 2170 | return SCOPE_PUBLIC; | ||
| 2171 | } | ||
| 2172 | |||
| 2173 | xs *followers_url = xs_fmt("%s/followers", xs_dict_get(msg, "attributedTo")); | ||
| 2174 | |||
| 2175 | if ((xs_type(to) == XSTYPE_STRING && strcmp(to, followers_url) == 0) || | ||
| 2176 | (xs_type(to) == XSTYPE_LIST && xs_list_in(to, followers_url) != -1)) | ||
| 2177 | return SCOPE_FOLLOWERS; | ||
| 2178 | else | ||
| 2179 | return SCOPE_MENTIONED; | ||
| 2180 | /* should be unreachable, someone violated the protocol. */ | ||
| 2181 | /* better treat it as followers-only to make sure we don't leak information.*/ | ||
| 2182 | return SCOPE_FOLLOWERS; | ||
| 2183 | } | ||
| 2116 | 2184 | ||
| 2117 | int update_question(snac *user, const char *id) | 2185 | int update_question(snac *user, const char *id) |
| 2118 | /* updates the poll counts */ | 2186 | /* updates the poll counts */ |
| @@ -3156,7 +3224,7 @@ void process_queue_item(xs_dict *q_item) | |||
| 3156 | } | 3224 | } |
| 3157 | 3225 | ||
| 3158 | if (instance_failure(inbox, 0)) { | 3226 | if (instance_failure(inbox, 0)) { |
| 3159 | srv_debug(1, xs_fmt("too many failures for instance %s", inbox)); | 3227 | srv_debug(1, xs_fmt("output message error: too many failures for instance %s", inbox)); |
| 3160 | return; | 3228 | return; |
| 3161 | } | 3229 | } |
| 3162 | 3230 | ||
| @@ -3502,6 +3570,8 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, | |||
| 3502 | 3570 | ||
| 3503 | p_path = xs_list_get(l, 2); | 3571 | p_path = xs_list_get(l, 2); |
| 3504 | 3572 | ||
| 3573 | const xs_dict *q_vars = xs_dict_get(req, "q_vars"); | ||
| 3574 | |||
| 3505 | *ctype = "application/activity+json"; | 3575 | *ctype = "application/activity+json"; |
| 3506 | 3576 | ||
| 3507 | int show_contact_metrics = xs_is_true(xs_dict_get(snac.config, "show_contact_metrics")); | 3577 | int show_contact_metrics = xs_is_true(xs_dict_get(snac.config, "show_contact_metrics")); |
| @@ -3595,7 +3665,15 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path, | |||
| 3595 | if (!is_msg_public(obj)) | 3665 | if (!is_msg_public(obj)) |
| 3596 | status = HTTP_STATUS_NOT_FOUND; | 3666 | status = HTTP_STATUS_NOT_FOUND; |
| 3597 | else | 3667 | else |
| 3668 | if (xs_dict_get(q_vars, "page")) | ||
| 3598 | msg = msg_replies(&snac, id, 1); | 3669 | msg = msg_replies(&snac, id, 1); |
| 3670 | else { | ||
| 3671 | const xs_dict *replies = xs_dict_get(obj, "replies"); | ||
| 3672 | if (xs_is_dict(replies)) { | ||
| 3673 | msg = xs_dup(replies); | ||
| 3674 | msg = xs_dict_set(msg, "@context", "https:/""/www.w3.org/ns/activitystreams"); | ||
| 3675 | } | ||
| 3676 | } | ||
| 3599 | } | 3677 | } |
| 3600 | else | 3678 | else |
| 3601 | status = HTTP_STATUS_NOT_FOUND; | 3679 | status = HTTP_STATUS_NOT_FOUND; |