summaryrefslogtreecommitdiff
path: root/mastoapi.c
diff options
context:
space:
mode:
authorGravatar shtrophic2024-12-23 13:42:45 +0100
committerGravatar shtrophic2024-12-23 13:42:45 +0100
commita7ca4007f2a55a8becab1e4595d2696dd6e7bfd1 (patch)
tree3128196bd7eb298be5a37edac5922009ec5fcac1 /mastoapi.c
parentMerge tag '2.66' (diff)
parentVersion 2.67 RELEASED. (diff)
downloadpenes-snac2-a7ca4007f2a55a8becab1e4595d2696dd6e7bfd1.tar.gz
penes-snac2-a7ca4007f2a55a8becab1e4595d2696dd6e7bfd1.tar.xz
penes-snac2-a7ca4007f2a55a8becab1e4595d2696dd6e7bfd1.zip
Merge tag '2.67'
Version 2.67 RELEASED.
Diffstat (limited to 'mastoapi.c')
-rw-r--r--mastoapi.c259
1 files changed, 192 insertions, 67 deletions
diff --git a/mastoapi.c b/mastoapi.c
index 990898b..09e18a1 100644
--- a/mastoapi.c
+++ b/mastoapi.c
@@ -293,47 +293,54 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
293 snac snac; 293 snac snac;
294 294
295 if (user_open(&snac, login)) { 295 if (user_open(&snac, login)) {
296 /* check the login + password */ 296 const char *addr = xs_or(xs_dict_get(req, "remote-addr"),
297 if (check_password(login, passwd, xs_dict_get(snac.config, "passwd"))) { 297 xs_dict_get(req, "x-forwarded-for"));
298 /* success! redirect to the desired uri */
299 xs *code = random_str();
300 298
301 xs_free(*body); 299 if (badlogin_check(login, addr)) {
300 /* check the login + password */
301 if (check_password(login, passwd, xs_dict_get(snac.config, "passwd"))) {
302 /* success! redirect to the desired uri */
303 xs *code = random_str();
302 304
303 if (strcmp(redir, "urn:ietf:wg:oauth:2.0:oob") == 0) { 305 xs_free(*body);
304 *body = xs_dup(code);
305 }
306 else {
307 if (xs_str_in(redir, "?") != -1)
308 *body = xs_fmt("%s&code=%s", redir, code);
309 else
310 *body = xs_fmt("%s?code=%s", redir, code);
311 306
312 status = HTTP_STATUS_SEE_OTHER; 307 if (strcmp(redir, "urn:ietf:wg:oauth:2.0:oob") == 0) {
313 } 308 *body = xs_dup(code);
309 }
310 else {
311 if (xs_str_in(redir, "?") != -1)
312 *body = xs_fmt("%s&code=%s", redir, code);
313 else
314 *body = xs_fmt("%s?code=%s", redir, code);
314 315
315 /* if there is a state, add it */ 316 status = HTTP_STATUS_SEE_OTHER;
316 if (!xs_is_null(state) && *state) { 317 }
317 *body = xs_str_cat(*body, "&state=");
318 *body = xs_str_cat(*body, state);
319 }
320 318
321 srv_log(xs_fmt("oauth x-snac-login: '%s' success, redirect to %s", 319 /* if there is a state, add it */
320 if (!xs_is_null(state) && *state) {
321 *body = xs_str_cat(*body, "&state=");
322 *body = xs_str_cat(*body, state);
323 }
324
325 srv_log(xs_fmt("oauth x-snac-login: '%s' success, redirect to %s",
322 login, *body)); 326 login, *body));
323 327
324 /* assign the login to the app */ 328 /* assign the login to the app */
325 xs *app = app_get(cid); 329 xs *app = app_get(cid);
326 330
327 if (app != NULL) { 331 if (app != NULL) {
328 app = xs_dict_set(app, "uid", login); 332 app = xs_dict_set(app, "uid", login);
329 app = xs_dict_set(app, "code", code); 333 app = xs_dict_set(app, "code", code);
330 app_add(cid, app); 334 app_add(cid, app);
335 }
336 else
337 srv_log(xs_fmt("oauth x-snac-login: error getting app %s", cid));
338 }
339 else {
340 srv_debug(1, xs_fmt("oauth x-snac-login: login '%s' incorrect", login));
341 badlogin_inc(login, addr);
331 } 342 }
332 else
333 srv_log(xs_fmt("oauth x-snac-login: error getting app %s", cid));
334 } 343 }
335 else
336 srv_debug(1, xs_fmt("oauth x-snac-login: login '%s' incorrect", login));
337 344
338 user_free(&snac); 345 user_free(&snac);
339 } 346 }
@@ -474,29 +481,36 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
474 snac user; 481 snac user;
475 482
476 if (user_open(&user, login)) { 483 if (user_open(&user, login)) {
477 /* check the login + password */ 484 const char *addr = xs_or(xs_dict_get(req, "remote-addr"),
478 if (check_password(login, passwd, xs_dict_get(user.config, "passwd"))) { 485 xs_dict_get(req, "x-forwarded-for"));
479 /* success! create a new token */
480 xs *tokid = random_str();
481 486
482 srv_debug(1, xs_fmt("x-snac-new-token: " 487 if (badlogin_check(login, addr)) {
488 /* check the login + password */
489 if (check_password(login, passwd, xs_dict_get(user.config, "passwd"))) {
490 /* success! create a new token */
491 xs *tokid = random_str();
492
493 srv_debug(1, xs_fmt("x-snac-new-token: "
483 "successful login for %s, new token %s", login, tokid)); 494 "successful login for %s, new token %s", login, tokid));
484 495
485 xs *token = xs_dict_new(); 496 xs *token = xs_dict_new();
486 token = xs_dict_append(token, "token", tokid); 497 token = xs_dict_append(token, "token", tokid);
487 token = xs_dict_append(token, "client_id", "snac-client"); 498 token = xs_dict_append(token, "client_id", "snac-client");
488 token = xs_dict_append(token, "client_secret", ""); 499 token = xs_dict_append(token, "client_secret", "");
489 token = xs_dict_append(token, "uid", login); 500 token = xs_dict_append(token, "uid", login);
490 token = xs_dict_append(token, "code", ""); 501 token = xs_dict_append(token, "code", "");
491 502
492 token_add(tokid, token); 503 token_add(tokid, token);
493 504
494 *ctype = "text/plain"; 505 *ctype = "text/plain";
495 xs_free(*body); 506 xs_free(*body);
496 *body = xs_dup(tokid); 507 *body = xs_dup(tokid);
497 } 508 }
509 else
510 badlogin_inc(login, addr);
498 511
499 user_free(&user); 512 user_free(&user);
513 }
500 } 514 }
501 } 515 }
502 } 516 }
@@ -898,7 +912,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
898 const char *o_href = xs_dict_get(v, "href"); 912 const char *o_href = xs_dict_get(v, "href");
899 const char *name = xs_dict_get(v, "name"); 913 const char *name = xs_dict_get(v, "name");
900 914
901 if (xs_match(type, "image/*|video/*|Image|Video")) { /* */ 915 if (xs_match(type, "image/*|video/*|audio/*|Image|Video")) { /* */
902 xs *matteid = xs_fmt("%s_%d", id, xs_list_len(matt)); 916 xs *matteid = xs_fmt("%s_%d", id, xs_list_len(matt));
903 xs *href = make_url(o_href, proxy, 1); 917 xs *href = make_url(o_href, proxy, 1);
904 918
@@ -910,7 +924,8 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
910 d = xs_dict_append(d, "remote_url", href); 924 d = xs_dict_append(d, "remote_url", href);
911 d = xs_dict_append(d, "description", name); 925 d = xs_dict_append(d, "description", name);
912 926
913 d = xs_dict_append(d, "type", (*type == 'v' || *type == 'V') ? "video" : "image"); 927 d = xs_dict_append(d, "type", (*type == 'v' || *type == 'V') ? "video" :
928 (*type == 'a' || *type == 'A') ? "audio" : "image");
914 929
915 matt = xs_list_append(matt, d); 930 matt = xs_list_append(matt, d);
916 } 931 }
@@ -990,7 +1005,7 @@ xs_dict *mastoapi_status(snac *snac, const xs_dict *msg)
990 const char *o_url = xs_dict_get(icon, "url"); 1005 const char *o_url = xs_dict_get(icon, "url");
991 1006
992 if (!xs_is_null(o_url)) { 1007 if (!xs_is_null(o_url)) {
993 xs *url = make_url(o_url, snac->actor, 1); 1008 xs *url = make_url(o_url, snac ? snac->actor : NULL, 1);
994 xs *nm = xs_strip_chars_i(xs_dup(name), ":"); 1009 xs *nm = xs_strip_chars_i(xs_dup(name), ":");
995 1010
996 d1 = xs_dict_append(d1, "shortcode", nm); 1011 d1 = xs_dict_append(d1, "shortcode", nm);
@@ -1193,10 +1208,13 @@ int process_auth_token(snac *snac, const xs_dict *req)
1193 return logged_in; 1208 return logged_in;
1194} 1209}
1195 1210
1211
1196void credentials_get(char **body, char **ctype, int *status, snac snac) 1212void credentials_get(char **body, char **ctype, int *status, snac snac)
1197{ 1213{
1198 xs *acct = xs_dict_new(); 1214 xs *acct = xs_dict_new();
1199 1215
1216 const xs_val *bot = xs_dict_get(snac.config, "bot");
1217
1200 acct = xs_dict_append(acct, "id", snac.md5); 1218 acct = xs_dict_append(acct, "id", snac.md5);
1201 acct = xs_dict_append(acct, "username", xs_dict_get(snac.config, "uid")); 1219 acct = xs_dict_append(acct, "username", xs_dict_get(snac.config, "uid"));
1202 acct = xs_dict_append(acct, "acct", xs_dict_get(snac.config, "uid")); 1220 acct = xs_dict_append(acct, "acct", xs_dict_get(snac.config, "uid"));
@@ -1206,7 +1224,7 @@ void credentials_get(char **body, char **ctype, int *status, snac snac)
1206 acct = xs_dict_append(acct, "note", xs_dict_get(snac.config, "bio")); 1224 acct = xs_dict_append(acct, "note", xs_dict_get(snac.config, "bio"));
1207 acct = xs_dict_append(acct, "url", snac.actor); 1225 acct = xs_dict_append(acct, "url", snac.actor);
1208 acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE)); 1226 acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE));
1209 acct = xs_dict_append(acct, "bot", xs_dict_get(snac.config, "bot")); 1227 acct = xs_dict_append(acct, "bot", xs_stock(xs_is_true(bot) ? XSTYPE_TRUE : XSTYPE_FALSE));
1210 acct = xs_dict_append(acct, "emojis", xs_stock(XSTYPE_LIST)); 1228 acct = xs_dict_append(acct, "emojis", xs_stock(XSTYPE_LIST));
1211 1229
1212 xs *src = xs_json_loads("{\"privacy\":\"public\", \"language\":\"en\"," 1230 xs *src = xs_json_loads("{\"privacy\":\"public\", \"language\":\"en\","
@@ -1220,7 +1238,7 @@ void credentials_get(char **body, char **ctype, int *status, snac snac)
1220 src = xs_dict_set(src, "sensitive", 1238 src = xs_dict_set(src, "sensitive",
1221 strcmp(cw, "open") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE)); 1239 strcmp(cw, "open") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE));
1222 1240
1223 src = xs_dict_set(src, "bot", xs_dict_get(snac.config, "bot")); 1241 src = xs_dict_set(src, "bot", xs_stock(xs_is_true(bot) ? XSTYPE_TRUE : XSTYPE_FALSE));
1224 1242
1225 xs *avatar = NULL; 1243 xs *avatar = NULL;
1226 const char *av = xs_dict_get(snac.config, "avatar"); 1244 const char *av = xs_dict_get(snac.config, "avatar");
@@ -1319,7 +1337,7 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn
1319 1337
1320 const char *max_id = xs_dict_get(args, "max_id"); 1338 const char *max_id = xs_dict_get(args, "max_id");
1321 const char *since_id = xs_dict_get(args, "since_id"); 1339 const char *since_id = xs_dict_get(args, "since_id");
1322 const char *min_id = xs_dict_get(args, "min_id"); 1340 const char *min_id = xs_dict_get(args, "min_id"); /* unsupported old-to-new navigation */
1323 const char *limit_s = xs_dict_get(args, "limit"); 1341 const char *limit_s = xs_dict_get(args, "limit");
1324 int limit = 0; 1342 int limit = 0;
1325 int cnt = 0; 1343 int cnt = 0;
@@ -1330,7 +1348,7 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn
1330 if (limit == 0) 1348 if (limit == 0)
1331 limit = 20; 1349 limit = 20;
1332 1350
1333 if (index_desc_first(f, md5, 0)) { 1351 if (min_id == NULL && index_desc_first(f, md5, 0)) {
1334 do { 1352 do {
1335 xs *msg = NULL; 1353 xs *msg = NULL;
1336 1354
@@ -1348,13 +1366,6 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn
1348 break; 1366 break;
1349 } 1367 }
1350 1368
1351 /* only returns entries newer than min_id */
1352 /* what does really "Return results immediately newer than ID" mean? */
1353 if (min_id) {
1354 if (strcmp(md5, MID_TO_MD5(min_id)) == 0)
1355 break;
1356 }
1357
1358 /* get the entry */ 1369 /* get the entry */
1359 if (user) { 1370 if (user) {
1360 if (!valid_status(timeline_get_by_md5(user, md5, &msg))) 1371 if (!valid_status(timeline_get_by_md5(user, md5, &msg)))
@@ -1438,8 +1449,35 @@ xs_list *mastoapi_timeline(snac *user, const xs_dict *args, const char *index_fn
1438} 1449}
1439 1450
1440 1451
1452xs_str *timeline_link_header(const char *endpoint, xs_list *timeline)
1453/* returns a Link header with paging information */
1454{
1455 xs_str *s = NULL;
1456
1457 if (xs_list_len(timeline) == 0)
1458 return NULL;
1459
1460 const xs_dict *first_st = xs_list_get(timeline, 0);
1461 const xs_dict *last_st = xs_list_get(timeline, -1);
1462 const char *first_id = xs_dict_get(first_st, "id");
1463 const char *last_id = xs_dict_get(last_st, "id");
1464 const char *host = xs_dict_get(srv_config, "host");
1465 const char *protocol = xs_dict_get_def(srv_config, "protocol", "https");
1466
1467 s = xs_fmt(
1468 "<%s:/" "/%s%s?max_id=%s>; rel=\"next\", "
1469 "<%s:/" "/%s%s?since_id=%s>; rel=\"prev\"",
1470 protocol, host, endpoint, last_id,
1471 protocol, host, endpoint, first_id);
1472
1473 srv_debug(1, xs_fmt("timeline_link_header %s", s));
1474
1475 return s;
1476}
1477
1478
1441int mastoapi_get_handler(const xs_dict *req, const char *q_path, 1479int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1442 char **body, int *b_size, char **ctype) 1480 char **body, int *b_size, char **ctype, xs_str **link)
1443{ 1481{
1444 (void)b_size; 1482 (void)b_size;
1445 1483
@@ -1699,6 +1737,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1699 xs *ifn = user_index_fn(&snac1, "private"); 1737 xs *ifn = user_index_fn(&snac1, "private");
1700 xs *out = mastoapi_timeline(&snac1, args, ifn); 1738 xs *out = mastoapi_timeline(&snac1, args, ifn);
1701 1739
1740 *link = timeline_link_header("/api/v1/timelines/home", out);
1741
1702 *body = xs_json_dumps(out, 4); 1742 *body = xs_json_dumps(out, 4);
1703 *ctype = "application/json"; 1743 *ctype = "application/json";
1704 status = HTTP_STATUS_OK; 1744 status = HTTP_STATUS_OK;
@@ -1763,8 +1803,14 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1763 xs *out = xs_list_new(); 1803 xs *out = xs_list_new();
1764 const xs_dict *v; 1804 const xs_dict *v;
1765 const xs_list *excl = xs_dict_get(args, "exclude_types[]"); 1805 const xs_list *excl = xs_dict_get(args, "exclude_types[]");
1806 const char *min_id = xs_dict_get(args, "min_id");
1766 const char *max_id = xs_dict_get(args, "max_id"); 1807 const char *max_id = xs_dict_get(args, "max_id");
1767 1808
1809 if (dbglevel) {
1810 xs *js = xs_json_dumps(args, 0);
1811 srv_debug(1, xs_fmt("mastoapi_notifications args %s", js));
1812 }
1813
1768 xs_list_foreach(l, v) { 1814 xs_list_foreach(l, v) {
1769 xs *noti = notify_get(&snac1, v); 1815 xs *noti = notify_get(&snac1, v);
1770 1816
@@ -1795,6 +1841,12 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1795 continue; 1841 continue;
1796 } 1842 }
1797 1843
1844 if (min_id) {
1845 if (strcmp(fid, min_id) <= 0) {
1846 continue;
1847 }
1848 }
1849
1798 /* convert the type */ 1850 /* convert the type */
1799 if (strcmp(type, "Like") == 0 || strcmp(type, "EmojiReact") == 0) 1851 if (strcmp(type, "Like") == 0 || strcmp(type, "EmojiReact") == 0)
1800 type = "favourite"; 1852 type = "favourite";
@@ -1842,6 +1894,8 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1842 out = xs_list_append(out, mn); 1894 out = xs_list_append(out, mn);
1843 } 1895 }
1844 1896
1897 srv_debug(1, xs_fmt("mastoapi_notifications count %d", xs_list_len(out)));
1898
1845 *body = xs_json_dumps(out, 4); 1899 *body = xs_json_dumps(out, 4);
1846 *ctype = "application/json"; 1900 *ctype = "application/json";
1847 status = HTTP_STATUS_OK; 1901 status = HTTP_STATUS_OK;
@@ -2273,9 +2327,22 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2273 } 2327 }
2274 else 2328 else
2275 if (strcmp(cmd, "/v1/markers") == 0) { /** **/ 2329 if (strcmp(cmd, "/v1/markers") == 0) { /** **/
2276 *body = xs_dup("{}"); 2330 if (logged_in) {
2277 *ctype = "application/json"; 2331 const xs_list *timeline = xs_dict_get(args, "timeline[]");
2278 status = HTTP_STATUS_OK; 2332 xs_str *json = NULL;
2333 if (!xs_is_null(timeline))
2334 json = xs_json_dumps(markers_get(&snac1, timeline), 4);
2335
2336 if (!xs_is_null(json))
2337 *body = json;
2338 else
2339 *body = xs_dup("{}");
2340
2341 *ctype = "application/json";
2342 status = HTTP_STATUS_OK;
2343 }
2344 else
2345 status = HTTP_STATUS_UNAUTHORIZED;
2279 } 2346 }
2280 else 2347 else
2281 if (strcmp(cmd, "/v1/followed_tags") == 0) { /** **/ 2348 if (strcmp(cmd, "/v1/followed_tags") == 0) { /** **/
@@ -2310,6 +2377,37 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2310 if (xs_is_null(offset) || strcmp(offset, "0") == 0) { 2377 if (xs_is_null(offset) || strcmp(offset, "0") == 0) {
2311 /* reply something only for offset 0; otherwise, 2378 /* reply something only for offset 0; otherwise,
2312 apps like Tusky keep asking again and again */ 2379 apps like Tusky keep asking again and again */
2380 if (xs_startswith(q, "https://")) {
2381 xs *md5 = xs_md5_hex(q, strlen(q));
2382
2383 if (!timeline_here(&snac1, md5)) {
2384 xs *object = NULL;
2385 int status;
2386
2387 status = activitypub_request(&snac1, q, &object);
2388 snac_debug(&snac1, 1, xs_fmt("Request searched URL %s %d", q, status));
2389
2390 if (valid_status(status)) {
2391 /* got it; also request the actor */
2392 const char *attr_to = get_atto(object);
2393 xs *actor_obj = NULL;
2394
2395 if (!xs_is_null(attr_to)) {
2396 status = actor_request(&snac1, attr_to, &actor_obj);
2397
2398 snac_debug(&snac1, 1, xs_fmt("Request author %s of %s %d", attr_to, q, status));
2399
2400 if (valid_status(status)) {
2401 /* add the actor */
2402 actor_add(attr_to, actor_obj);
2403
2404 /* add the post to the timeline */
2405 timeline_add(&snac1, q, object);
2406 }
2407 }
2408 }
2409 }
2410 }
2313 2411
2314 if (!xs_is_null(q)) { 2412 if (!xs_is_null(q)) {
2315 if (xs_is_null(type) || strcmp(type, "accounts") == 0) { 2413 if (xs_is_null(type) || strcmp(type, "accounts") == 0) {
@@ -2945,6 +3043,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2945 status = HTTP_STATUS_UNPROCESSABLE_CONTENT; 3043 status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
2946 } 3044 }
2947 } 3045 }
3046 else
2948 if (xs_startswith(cmd, "/v1/lists/")) { /** list maintenance **/ 3047 if (xs_startswith(cmd, "/v1/lists/")) { /** list maintenance **/
2949 if (logged_in) { 3048 if (logged_in) {
2950 xs *l = xs_split(cmd, "/"); 3049 xs *l = xs_split(cmd, "/");
@@ -2972,9 +3071,35 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2972 } 3071 }
2973 } 3072 }
2974 } 3073 }
3074 }
3075 else if (strcmp(cmd, "/v1/markers") == 0) { /** **/
3076 xs_str *json = NULL;
3077 if (logged_in) {
3078 const xs_str *home_marker = xs_dict_get(args, "home[last_read_id]");
3079 if (xs_is_null(home_marker)) {
3080 const xs_dict *home = xs_dict_get(args, "home");
3081 if (!xs_is_null(home))
3082 home_marker = xs_dict_get(home, "last_read_id");
3083 }
3084
3085 const xs_str *notify_marker = xs_dict_get(args, "notifications[last_read_id]");
3086 if (xs_is_null(notify_marker)) {
3087 const xs_dict *notify = xs_dict_get(args, "notifications");
3088 if (!xs_is_null(notify))
3089 notify_marker = xs_dict_get(notify, "last_read_id");
3090 }
3091 json = xs_json_dumps(markers_set(&snac, home_marker, notify_marker), 4);
3092 }
3093 if (!xs_is_null(json))
3094 *body = json;
2975 else 3095 else
2976 status = HTTP_STATUS_UNPROCESSABLE_CONTENT; 3096 *body = xs_dup("{}");
3097
3098 *ctype = "application/json";
3099 status = HTTP_STATUS_OK;
2977 } 3100 }
3101 else
3102 status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
2978 3103
2979 /* user cleanup */ 3104 /* user cleanup */
2980 if (logged_in) 3105 if (logged_in)