summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--activitypub.c56
-rw-r--r--data.c95
-rw-r--r--html.c98
-rw-r--r--http_codes.h45
-rw-r--r--httpd.c38
-rw-r--r--mastoapi.c496
-rw-r--r--snac.c12
-rw-r--r--snac.h14
-rw-r--r--webfinger.c8
-rw-r--r--xs.h2
-rw-r--r--xs_httpd.h6
11 files changed, 560 insertions, 310 deletions
diff --git a/activitypub.c b/activitypub.c
index 6e40a88..4247078 100644
--- a/activitypub.c
+++ b/activitypub.c
@@ -96,19 +96,19 @@ int activitypub_request(snac *user, const char *url, xs_dict **data)
96 ctype = xs_dict_get(response, "content-type"); 96 ctype = xs_dict_get(response, "content-type");
97 97
98 if (xs_is_null(ctype)) 98 if (xs_is_null(ctype))
99 status = 400; 99 status = HTTP_STATUS_BAD_REQUEST;
100 else 100 else
101 if (xs_str_in(ctype, "application/activity+json") != -1 || 101 if (xs_str_in(ctype, "application/activity+json") != -1 ||
102 xs_str_in(ctype, "application/ld+json") != -1) { 102 xs_str_in(ctype, "application/ld+json") != -1) {
103 103
104 /* if there is no payload, fail */ 104 /* if there is no payload, fail */
105 if (xs_is_null(payload)) 105 if (xs_is_null(payload))
106 status = 400; 106 status = HTTP_STATUS_BAD_REQUEST;
107 else 107 else
108 *data = xs_json_loads(payload); 108 *data = xs_json_loads(payload);
109 } 109 }
110 else 110 else
111 status = 500; 111 status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
112 } 112 }
113 113
114 return status; 114 return status;
@@ -443,7 +443,7 @@ int send_to_actor(snac *snac, const char *actor, const xs_dict *msg,
443 xs_val **payload, int *p_size, int timeout) 443 xs_val **payload, int *p_size, int timeout)
444/* sends a message to an actor */ 444/* sends a message to an actor */
445{ 445{
446 int status = 400; 446 int status = HTTP_STATUS_BAD_REQUEST;
447 xs *inbox = get_actor_inbox(actor); 447 xs *inbox = get_actor_inbox(actor);
448 448
449 if (!xs_is_null(inbox)) 449 if (!xs_is_null(inbox))
@@ -1762,7 +1762,9 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req)
1762 a_status = actor_request(snac, actor, &actor_o); 1762 a_status = actor_request(snac, actor, &actor_o);
1763 1763
1764 /* do not retry permanent failures */ 1764 /* do not retry permanent failures */
1765 if (a_status == 404 || a_status == 410 || a_status < 0) { 1765 if (a_status == HTTP_STATUS_NOT_FOUND
1766 || a_status == HTTP_STATUS_GONE
1767 || a_status < 0) {
1766 srv_debug(1, xs_fmt("dropping message due to actor error %s %d", actor, a_status)); 1768 srv_debug(1, xs_fmt("dropping message due to actor error %s %d", actor, a_status));
1767 return -1; 1769 return -1;
1768 } 1770 }
@@ -1905,7 +1907,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req)
1905 } 1907 }
1906 else 1908 else
1907 if (strcmp(utype, "Announce") == 0) { /** **/ 1909 if (strcmp(utype, "Announce") == 0) { /** **/
1908 int status = 200; 1910 int status = HTTP_STATUS_OK;
1909 1911
1910 /* commented out: if a followed user boosts something that 1912 /* commented out: if a followed user boosts something that
1911 is requested and then unboosts, the post remains here, 1913 is requested and then unboosts, the post remains here,
@@ -2015,7 +2017,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req)
2015 if (xs_type(object) == XSTYPE_DICT) 2017 if (xs_type(object) == XSTYPE_DICT)
2016 object = xs_dict_get(object, "id"); 2018 object = xs_dict_get(object, "id");
2017 2019
2018 if (timeline_admire(snac, object, actor, 1) == 201) 2020 if (timeline_admire(snac, object, actor, 1) == HTTP_STATUS_CREATED)
2019 snac_log(snac, xs_fmt("new 'Like' %s %s", actor, object)); 2021 snac_log(snac, xs_fmt("new 'Like' %s %s", actor, object));
2020 else 2022 else
2021 snac_log(snac, xs_fmt("repeated 'Like' from %s to %s", actor, object)); 2023 snac_log(snac, xs_fmt("repeated 'Like' from %s to %s", actor, object));
@@ -2046,7 +2048,7 @@ int process_input_message(snac *snac, const xs_dict *msg, const xs_dict *req)
2046 xs *who_o = NULL; 2048 xs *who_o = NULL;
2047 2049
2048 if (valid_status(actor_request(snac, who, &who_o))) { 2050 if (valid_status(actor_request(snac, who, &who_o))) {
2049 if (timeline_admire(snac, object, actor, 0) == 201) 2051 if (timeline_admire(snac, object, actor, 0) == HTTP_STATUS_CREATED)
2050 snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object)); 2052 snac_log(snac, xs_fmt("new 'Announce' %s %s", actor, object));
2051 else 2053 else
2052 snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s", 2054 snac_log(snac, xs_fmt("repeated 'Announce' from %s to %s",
@@ -2383,11 +2385,15 @@ void process_queue_item(xs_dict *q_item)
2383 2385
2384 /* if it's not the first time it fails with a timeout, 2386 /* if it's not the first time it fails with a timeout,
2385 penalize the server by skipping one retry */ 2387 penalize the server by skipping one retry */
2386 if (p_status == status && status == 499) 2388 if (p_status == status && status == HTTP_STATUS_CLIENT_CLOSED_REQUEST)
2387 retries++; 2389 retries++;
2388 2390
2389 /* error sending; requeue? */ 2391 /* error sending; requeue? */
2390 if (status == 400 || status == 404 || status == 405 || status == 410 || status < 0) 2392 if (status == HTTP_STATUS_BAD_REQUEST
2393 || status == HTTP_STATUS_NOT_FOUND
2394 || status == HTTP_STATUS_METHOD_NOT_ALLOWED
2395 || status == HTTP_STATUS_GONE
2396 || status < 0)
2391 /* explicit error: discard */ 2397 /* explicit error: discard */
2392 srv_log(xs_fmt("output message: fatal error %s %d", inbox, status)); 2398 srv_log(xs_fmt("output message: fatal error %s %d", inbox, status));
2393 else 2399 else
@@ -2574,7 +2580,7 @@ int process_queue(void)
2574int activitypub_get_handler(const xs_dict *req, const char *q_path, 2580int activitypub_get_handler(const xs_dict *req, const char *q_path,
2575 char **body, int *b_size, char **ctype) 2581 char **body, int *b_size, char **ctype)
2576{ 2582{
2577 int status = 200; 2583 int status = HTTP_STATUS_OK;
2578 const char *accept = xs_dict_get(req, "accept"); 2584 const char *accept = xs_dict_get(req, "accept");
2579 snac snac; 2585 snac snac;
2580 xs *msg = NULL; 2586 xs *msg = NULL;
@@ -2594,7 +2600,7 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path,
2594 if (!user_open(&snac, uid)) { 2600 if (!user_open(&snac, uid)) {
2595 /* invalid user */ 2601 /* invalid user */
2596 srv_debug(1, xs_fmt("activitypub_get_handler bad user %s", uid)); 2602 srv_debug(1, xs_fmt("activitypub_get_handler bad user %s", uid));
2597 return 404; 2603 return HTTP_STATUS_NOT_FOUND;
2598 } 2604 }
2599 2605
2600 p_path = xs_list_get(l, 2); 2606 p_path = xs_list_get(l, 2);
@@ -2652,12 +2658,12 @@ int activitypub_get_handler(const xs_dict *req, const char *q_path,
2652 2658
2653 /* don't return non-public objects */ 2659 /* don't return non-public objects */
2654 if (valid_status(status) && !is_msg_public(msg)) 2660 if (valid_status(status) && !is_msg_public(msg))
2655 status = 404; 2661 status = HTTP_STATUS_NOT_FOUND;
2656 } 2662 }
2657 else 2663 else
2658 status = 404; 2664 status = HTTP_STATUS_NOT_FOUND;
2659 2665
2660 if (status == 200 && msg != NULL) { 2666 if (status == HTTP_STATUS_OK && msg != NULL) {
2661 *body = xs_json_dumps(msg, 4); 2667 *body = xs_json_dumps(msg, 4);
2662 *b_size = strlen(*body); 2668 *b_size = strlen(*body);
2663 } 2669 }
@@ -2677,7 +2683,7 @@ int activitypub_post_handler(const xs_dict *req, const char *q_path,
2677{ 2683{
2678 (void)b_size; 2684 (void)b_size;
2679 2685
2680 int status = 202; /* accepted */ 2686 int status = HTTP_STATUS_ACCEPTED;
2681 const char *i_ctype = xs_dict_get(req, "content-type"); 2687 const char *i_ctype = xs_dict_get(req, "content-type");
2682 snac snac; 2688 snac snac;
2683 const char *v; 2689 const char *v;
@@ -2685,13 +2691,13 @@ int activitypub_post_handler(const xs_dict *req, const char *q_path,
2685 if (i_ctype == NULL) { 2691 if (i_ctype == NULL) {
2686 *body = xs_str_new("no content-type"); 2692 *body = xs_str_new("no content-type");
2687 *ctype = "text/plain"; 2693 *ctype = "text/plain";
2688 return 400; 2694 return HTTP_STATUS_BAD_REQUEST;
2689 } 2695 }
2690 2696
2691 if (xs_is_null(payload)) { 2697 if (xs_is_null(payload)) {
2692 *body = xs_str_new("no payload"); 2698 *body = xs_str_new("no payload");
2693 *ctype = "text/plain"; 2699 *ctype = "text/plain";
2694 return 400; 2700 return HTTP_STATUS_BAD_REQUEST;
2695 } 2701 }
2696 2702
2697 if (xs_str_in(i_ctype, "application/activity+json") == -1 && 2703 if (xs_str_in(i_ctype, "application/activity+json") == -1 &&
@@ -2709,7 +2715,7 @@ int activitypub_post_handler(const xs_dict *req, const char *q_path,
2709 2715
2710 *body = xs_str_new("JSON error"); 2716 *body = xs_str_new("JSON error");
2711 *ctype = "text/plain"; 2717 *ctype = "text/plain";
2712 return 400; 2718 return HTTP_STATUS_BAD_REQUEST;
2713 } 2719 }
2714 2720
2715 if (id && is_instance_blocked(id)) { 2721 if (id && is_instance_blocked(id)) {
@@ -2717,7 +2723,7 @@ int activitypub_post_handler(const xs_dict *req, const char *q_path,
2717 2723
2718 *body = xs_str_new("blocked"); 2724 *body = xs_str_new("blocked");
2719 *ctype = "text/plain"; 2725 *ctype = "text/plain";
2720 return 403; 2726 return HTTP_STATUS_FORBIDDEN;
2721 } 2727 }
2722 2728
2723 /* get the user and path */ 2729 /* get the user and path */
@@ -2725,20 +2731,20 @@ int activitypub_post_handler(const xs_dict *req, const char *q_path,
2725 2731
2726 if (xs_list_len(l) == 2 && strcmp(xs_list_get(l, 1), "shared-inbox") == 0) { 2732 if (xs_list_len(l) == 2 && strcmp(xs_list_get(l, 1), "shared-inbox") == 0) {
2727 enqueue_shared_input(msg, req, 0); 2733 enqueue_shared_input(msg, req, 0);
2728 return 202; 2734 return HTTP_STATUS_ACCEPTED;
2729 } 2735 }
2730 2736
2731 if (xs_list_len(l) != 3 || strcmp(xs_list_get(l, 2), "inbox") != 0) { 2737 if (xs_list_len(l) != 3 || strcmp(xs_list_get(l, 2), "inbox") != 0) {
2732 /* strange q_path */ 2738 /* strange q_path */
2733 srv_debug(1, xs_fmt("activitypub_post_handler unsupported path %s", q_path)); 2739 srv_debug(1, xs_fmt("activitypub_post_handler unsupported path %s", q_path));
2734 return 404; 2740 return HTTP_STATUS_NOT_FOUND;
2735 } 2741 }
2736 2742
2737 const char *uid = xs_list_get(l, 1); 2743 const char *uid = xs_list_get(l, 1);
2738 if (!user_open(&snac, uid)) { 2744 if (!user_open(&snac, uid)) {
2739 /* invalid user */ 2745 /* invalid user */
2740 srv_debug(1, xs_fmt("activitypub_post_handler bad user %s", uid)); 2746 srv_debug(1, xs_fmt("activitypub_post_handler bad user %s", uid));
2741 return 404; 2747 return HTTP_STATUS_NOT_FOUND;
2742 } 2748 }
2743 2749
2744 /* if it has a digest, check it now, because 2750 /* if it has a digest, check it now, because
@@ -2752,7 +2758,7 @@ int activitypub_post_handler(const xs_dict *req, const char *q_path,
2752 2758
2753 *body = xs_str_new("bad digest"); 2759 *body = xs_str_new("bad digest");
2754 *ctype = "text/plain"; 2760 *ctype = "text/plain";
2755 status = 400; 2761 status = HTTP_STATUS_BAD_REQUEST;
2756 } 2762 }
2757 } 2763 }
2758 2764
@@ -2763,7 +2769,7 @@ int activitypub_post_handler(const xs_dict *req, const char *q_path,
2763 2769
2764 *body = xs_str_new("rejected"); 2770 *body = xs_str_new("rejected");
2765 *ctype = "text/plain"; 2771 *ctype = "text/plain";
2766 status = 403; 2772 status = HTTP_STATUS_FORBIDDEN;
2767 } 2773 }
2768 } 2774 }
2769 2775
diff --git a/data.c b/data.c
index edbc64f..e24bf16 100644
--- a/data.c
+++ b/data.c
@@ -303,6 +303,33 @@ int user_open_by_md5(snac *snac, const char *md5)
303 return 0; 303 return 0;
304} 304}
305 305
306int user_persist(snac *snac)
307/* store user */
308{
309 xs *fn = xs_fmt("%s/user.json", snac->basedir);
310 xs *bfn = xs_fmt("%s.bak", fn);
311 FILE *f;
312
313 rename(fn, bfn);
314
315 if ((f = fopen(fn, "w")) != NULL) {
316 xs_json_dump(snac->config, 4, f);
317 fclose(f);
318 }
319 else
320 rename(bfn, fn);
321
322 history_del(snac, "timeline.html_");
323
324 xs *a_msg = msg_actor(snac);
325 xs *u_msg = msg_update(snac, a_msg);
326
327 enqueue_message(snac, u_msg);
328 enqueue_verify_links(snac);
329
330 return 0;
331}
332
306 333
307double mtime_nl(const char *fn, int *n_link) 334double mtime_nl(const char *fn, int *n_link)
308/* returns the mtime and number of links of a file or directory, or 0.0 */ 335/* returns the mtime and number of links of a file or directory, or 0.0 */
@@ -355,12 +382,12 @@ int is_md5_hex(const char *md5)
355int index_add_md5(const char *fn, const char *md5) 382int index_add_md5(const char *fn, const char *md5)
356/* adds an md5 to an index */ 383/* adds an md5 to an index */
357{ 384{
358 int status = 201; /* Created */ 385 int status = HTTP_STATUS_CREATED;
359 FILE *f; 386 FILE *f;
360 387
361 if (!is_md5_hex(md5)) { 388 if (!is_md5_hex(md5)) {
362 srv_log(xs_fmt("index_add_md5: bad md5 %s %s", fn, md5)); 389 srv_log(xs_fmt("index_add_md5: bad md5 %s %s", fn, md5));
363 return 400; 390 return HTTP_STATUS_BAD_REQUEST;
364 } 391 }
365 392
366 pthread_mutex_lock(&data_mutex); 393 pthread_mutex_lock(&data_mutex);
@@ -375,7 +402,7 @@ int index_add_md5(const char *fn, const char *md5)
375 fclose(f); 402 fclose(f);
376 } 403 }
377 else 404 else
378 status = 500; 405 status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
379 406
380 pthread_mutex_unlock(&data_mutex); 407 pthread_mutex_unlock(&data_mutex);
381 408
@@ -394,7 +421,7 @@ int index_add(const char *fn, const char *id)
394int index_del_md5(const char *fn, const char *md5) 421int index_del_md5(const char *fn, const char *md5)
395/* deletes an md5 from an index */ 422/* deletes an md5 from an index */
396{ 423{
397 int status = 404; 424 int status = HTTP_STATUS_NOT_FOUND;
398 FILE *f; 425 FILE *f;
399 426
400 pthread_mutex_lock(&data_mutex); 427 pthread_mutex_lock(&data_mutex);
@@ -411,7 +438,7 @@ int index_del_md5(const char *fn, const char *md5)
411 [yes: this breaks index_len()] */ 438 [yes: this breaks index_len()] */
412 fseek(f, -33, SEEK_CUR); 439 fseek(f, -33, SEEK_CUR);
413 fwrite("-", 1, 1, f); 440 fwrite("-", 1, 1, f);
414 status = 200; 441 status = HTTP_STATUS_OK;
415 442
416 break; 443 break;
417 } 444 }
@@ -420,7 +447,7 @@ int index_del_md5(const char *fn, const char *md5)
420 fclose(f); 447 fclose(f);
421 } 448 }
422 else 449 else
423 status = 410; 450 status = HTTP_STATUS_GONE;
424 451
425 pthread_mutex_unlock(&data_mutex); 452 pthread_mutex_unlock(&data_mutex);
426 453
@@ -660,7 +687,7 @@ int object_here(const char *id)
660int object_get_by_md5(const char *md5, xs_dict **obj) 687int object_get_by_md5(const char *md5, xs_dict **obj)
661/* returns a stored object, optionally of the requested type */ 688/* returns a stored object, optionally of the requested type */
662{ 689{
663 int status = 404; 690 int status = HTTP_STATUS_NOT_FOUND;
664 xs *fn = _object_fn_by_md5(md5, "object_get_by_md5"); 691 xs *fn = _object_fn_by_md5(md5, "object_get_by_md5");
665 FILE *f; 692 FILE *f;
666 693
@@ -669,7 +696,7 @@ int object_get_by_md5(const char *md5, xs_dict **obj)
669 fclose(f); 696 fclose(f);
670 697
671 if (*obj) 698 if (*obj)
672 status = 200; 699 status = HTTP_STATUS_OK;
673 } 700 }
674 else 701 else
675 *obj = NULL; 702 *obj = NULL;
@@ -689,7 +716,7 @@ int object_get(const char *id, xs_dict **obj)
689int _object_add(const char *id, const xs_dict *obj, int ow) 716int _object_add(const char *id, const xs_dict *obj, int ow)
690/* stores an object */ 717/* stores an object */
691{ 718{
692 int status = 201; /* Created */ 719 int status = HTTP_STATUS_CREATED; /* Created */
693 xs *fn = _object_fn(id); 720 xs *fn = _object_fn(id);
694 FILE *f; 721 FILE *f;
695 722
@@ -697,10 +724,10 @@ int _object_add(const char *id, const xs_dict *obj, int ow)
697 if (!ow) { 724 if (!ow) {
698 /* object already here */ 725 /* object already here */
699 srv_debug(1, xs_fmt("object_add object already here %s", id)); 726 srv_debug(1, xs_fmt("object_add object already here %s", id));
700 return 204; /* No content */ 727 return HTTP_STATUS_NO_CONTENT;
701 } 728 }
702 else 729 else
703 status = 200; 730 status = HTTP_STATUS_OK;
704 } 731 }
705 732
706 if ((f = fopen(fn, "w")) != NULL) { 733 if ((f = fopen(fn, "w")) != NULL) {
@@ -736,7 +763,7 @@ int _object_add(const char *id, const xs_dict *obj, int ow)
736 } 763 }
737 else { 764 else {
738 srv_log(xs_fmt("object_add error writing %s (errno: %d)", fn, errno)); 765 srv_log(xs_fmt("object_add error writing %s (errno: %d)", fn, errno));
739 status = 500; 766 status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
740 } 767 }
741 768
742 srv_debug(1, xs_fmt("object_add %s %s %d", id, fn, status)); 769 srv_debug(1, xs_fmt("object_add %s %s %d", id, fn, status));
@@ -762,11 +789,11 @@ int object_add_ow(const char *id, const xs_dict *obj)
762int object_del_by_md5(const char *md5) 789int object_del_by_md5(const char *md5)
763/* deletes an object by its md5 */ 790/* deletes an object by its md5 */
764{ 791{
765 int status = 404; 792 int status = HTTP_STATUS_NOT_FOUND;
766 xs *fn = _object_fn_by_md5(md5, "object_del_by_md5"); 793 xs *fn = _object_fn_by_md5(md5, "object_del_by_md5");
767 794
768 if (unlink(fn) != -1) { 795 if (unlink(fn) != -1) {
769 status = 200; 796 status = HTTP_STATUS_OK;
770 797
771 /* also delete associated indexes */ 798 /* also delete associated indexes */
772 xs *spec = xs_dup(fn); 799 xs *spec = xs_dup(fn);
@@ -907,7 +934,7 @@ int object_parent(const char *md5, char *buf, int size)
907int object_admire(const char *id, const char *actor, int like) 934int object_admire(const char *id, const char *actor, int like)
908/* actor likes or announces this object */ 935/* actor likes or announces this object */
909{ 936{
910 int status = 200; 937 int status = HTTP_STATUS_OK;
911 xs *fn = _object_fn(id); 938 xs *fn = _object_fn(id);
912 939
913 fn = xs_replace_i(fn, ".json", like ? "_l.idx" : "_a.idx"); 940 fn = xs_replace_i(fn, ".json", like ? "_l.idx" : "_a.idx");
@@ -1007,7 +1034,7 @@ int follower_add(snac *snac, const char *actor)
1007 1034
1008 snac_debug(snac, 2, xs_fmt("follower_add %s", actor)); 1035 snac_debug(snac, 2, xs_fmt("follower_add %s", actor));
1009 1036
1010 return ret == -1 ? 500 : 200; 1037 return ret == -1 ? HTTP_STATUS_INTERNAL_SERVER_ERROR : HTTP_STATUS_OK;
1011} 1038}
1012 1039
1013 1040
@@ -1018,7 +1045,7 @@ int follower_del(snac *snac, const char *actor)
1018 1045
1019 snac_debug(snac, 2, xs_fmt("follower_del %s", actor)); 1046 snac_debug(snac, 2, xs_fmt("follower_del %s", actor));
1020 1047
1021 return ret == -1 ? 404 : 200; 1048 return ret == -1 ? HTTP_STATUS_NOT_FOUND : HTTP_STATUS_OK;
1022} 1049}
1023 1050
1024 1051
@@ -1109,7 +1136,7 @@ int timeline_here(snac *snac, const char *md5)
1109int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg) 1136int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg)
1110/* gets a message from the timeline */ 1137/* gets a message from the timeline */
1111{ 1138{
1112 int status = 404; 1139 int status = HTTP_STATUS_NOT_FOUND;
1113 FILE *f = NULL; 1140 FILE *f = NULL;
1114 1141
1115 xs *fn = timeline_fn_by_md5(snac, md5); 1142 xs *fn = timeline_fn_by_md5(snac, md5);
@@ -1119,7 +1146,7 @@ int timeline_get_by_md5(snac *snac, const char *md5, xs_dict **msg)
1119 fclose(f); 1146 fclose(f);
1120 1147
1121 if (*msg != NULL) 1148 if (*msg != NULL)
1122 status = 200; 1149 status = HTTP_STATUS_OK;
1123 } 1150 }
1124 1151
1125 return status; 1152 return status;
@@ -1282,7 +1309,7 @@ xs_str *_following_fn(snac *snac, const char *actor)
1282int following_add(snac *snac, const char *actor, const xs_dict *msg) 1309int following_add(snac *snac, const char *actor, const xs_dict *msg)
1283/* adds to the following list */ 1310/* adds to the following list */
1284{ 1311{
1285 int ret = 201; /* created */ 1312 int ret = HTTP_STATUS_CREATED;
1286 xs *fn = _following_fn(snac, actor); 1313 xs *fn = _following_fn(snac, actor);
1287 FILE *f; 1314 FILE *f;
1288 xs *p_object = NULL; 1315 xs *p_object = NULL;
@@ -1295,7 +1322,7 @@ int following_add(snac *snac, const char *actor, const xs_dict *msg)
1295 1322
1296 if (!xs_is_null(type) && strcmp(type, "Accept") == 0) { 1323 if (!xs_is_null(type) && strcmp(type, "Accept") == 0) {
1297 snac_debug(snac, 1, xs_fmt("following_add actor already confirmed %s", actor)); 1324 snac_debug(snac, 1, xs_fmt("following_add actor already confirmed %s", actor));
1298 return 200; 1325 return HTTP_STATUS_OK;
1299 } 1326 }
1300 } 1327 }
1301 1328
@@ -1311,7 +1338,7 @@ int following_add(snac *snac, const char *actor, const xs_dict *msg)
1311 link(actor_fn, fn); 1338 link(actor_fn, fn);
1312 } 1339 }
1313 else 1340 else
1314 ret = 500; 1341 ret = HTTP_STATUS_INTERNAL_SERVER_ERROR;
1315 1342
1316 snac_debug(snac, 2, xs_fmt("following_add %s %s", actor, fn)); 1343 snac_debug(snac, 2, xs_fmt("following_add %s %s", actor, fn));
1317 1344
@@ -1332,7 +1359,7 @@ int following_del(snac *snac, const char *actor)
1332 fn = xs_replace_i(fn, ".json", "_a.json"); 1359 fn = xs_replace_i(fn, ".json", "_a.json");
1333 unlink(fn); 1360 unlink(fn);
1334 1361
1335 return 200; 1362 return HTTP_STATUS_OK;
1336} 1363}
1337 1364
1338 1365
@@ -1350,14 +1377,14 @@ int following_get(snac *snac, const char *actor, xs_dict **data)
1350{ 1377{
1351 xs *fn = _following_fn(snac, actor); 1378 xs *fn = _following_fn(snac, actor);
1352 FILE *f; 1379 FILE *f;
1353 int status = 200; 1380 int status = HTTP_STATUS_OK;
1354 1381
1355 if ((f = fopen(fn, "r")) != NULL) { 1382 if ((f = fopen(fn, "r")) != NULL) {
1356 *data = xs_json_load(f); 1383 *data = xs_json_load(f);
1357 fclose(f); 1384 fclose(f);
1358 } 1385 }
1359 else 1386 else
1360 status = 404; 1387 status = HTTP_STATUS_NOT_FOUND;
1361 1388
1362 return status; 1389 return status;
1363} 1390}
@@ -1576,7 +1603,7 @@ int actor_add(const char *actor, const xs_dict *msg)
1576int actor_get(const char *actor, xs_dict **data) 1603int actor_get(const char *actor, xs_dict **data)
1577/* returns an already downloaded actor */ 1604/* returns an already downloaded actor */
1578{ 1605{
1579 int status = 200; 1606 int status = HTTP_STATUS_OK;
1580 xs_dict *d = NULL; 1607 xs_dict *d = NULL;
1581 1608
1582 if (xs_startswith(actor, srv_baseurl)) { 1609 if (xs_startswith(actor, srv_baseurl)) {
@@ -1590,10 +1617,10 @@ int actor_get(const char *actor, xs_dict **data)
1590 *data = msg_actor(&user); 1617 *data = msg_actor(&user);
1591 1618
1592 user_free(&user); 1619 user_free(&user);
1593 return 200; 1620 return HTTP_STATUS_OK;
1594 } 1621 }
1595 else 1622 else
1596 return 404; 1623 return HTTP_STATUS_NOT_FOUND;
1597 } 1624 }
1598 1625
1599 /* read the object */ 1626 /* read the object */
@@ -1606,7 +1633,7 @@ int actor_get(const char *actor, xs_dict **data)
1606 if (xs_is_null(xs_dict_get(d, "id")) || xs_is_null(xs_dict_get(d, "type"))) { 1633 if (xs_is_null(xs_dict_get(d, "id")) || xs_is_null(xs_dict_get(d, "type"))) {
1607 srv_debug(1, xs_fmt("corrupted actor object %s", actor)); 1634 srv_debug(1, xs_fmt("corrupted actor object %s", actor));
1608 d = xs_free(d); 1635 d = xs_free(d);
1609 return 404; 1636 return HTTP_STATUS_NOT_FOUND;
1610 } 1637 }
1611 1638
1612 if (data) 1639 if (data)
@@ -1622,7 +1649,7 @@ int actor_get(const char *actor, xs_dict **data)
1622 1649
1623 if (mtime(fn) + max_time < (double) time(NULL)) { 1650 if (mtime(fn) + max_time < (double) time(NULL)) {
1624 /* actor data exists but also stinks */ 1651 /* actor data exists but also stinks */
1625 status = 205; /* "205: Reset Content" "110: Response Is Stale" */ 1652 status = HTTP_STATUS_RESET_CONTENT; /* "110: Response Is Stale" */
1626 } 1653 }
1627 1654
1628 return status; 1655 return status;
@@ -1634,7 +1661,7 @@ int actor_get_refresh(snac *user, const char *actor, xs_dict **data)
1634{ 1661{
1635 int status = actor_get(actor, data); 1662 int status = actor_get(actor, data);
1636 1663
1637 if (status == 205 && user && !xs_startswith(actor, srv_baseurl)) 1664 if (status == HTTP_STATUS_RESET_CONTENT && user && !xs_startswith(actor, srv_baseurl))
1638 enqueue_actor_refresh(user, actor, 0); 1665 enqueue_actor_refresh(user, actor, 0);
1639 1666
1640 return status; 1667 return status;
@@ -1953,7 +1980,7 @@ static int _load_raw_file(const char *fn, xs_val **data, int *size,
1953 const char *inm, xs_str **etag) 1980 const char *inm, xs_str **etag)
1954/* loads a cached file */ 1981/* loads a cached file */
1955{ 1982{
1956 int status = 404; 1983 int status = HTTP_STATUS_NOT_FOUND;
1957 1984
1958 if (fn) { 1985 if (fn) {
1959 double tm = mtime(fn); 1986 double tm = mtime(fn);
@@ -1965,7 +1992,7 @@ static int _load_raw_file(const char *fn, xs_val **data, int *size,
1965 /* if if-none-match is set, check if it's the same */ 1992 /* if if-none-match is set, check if it's the same */
1966 if (!xs_is_null(inm) && strcmp(e, inm) == 0) { 1993 if (!xs_is_null(inm) && strcmp(e, inm) == 0) {
1967 /* client has the newest version */ 1994 /* client has the newest version */
1968 status = 304; 1995 status = HTTP_STATUS_NOT_MODIFIED;
1969 } 1996 }
1970 else { 1997 else {
1971 /* newer or never downloaded; read the full file */ 1998 /* newer or never downloaded; read the full file */
@@ -1976,7 +2003,7 @@ static int _load_raw_file(const char *fn, xs_val **data, int *size,
1976 *data = xs_read(f, size); 2003 *data = xs_read(f, size);
1977 fclose(f); 2004 fclose(f);
1978 2005
1979 status = 200; 2006 status = HTTP_STATUS_OK;
1980 } 2007 }
1981 } 2008 }
1982 2009
diff --git a/html.c b/html.c
index d801b39..bacee5b 100644
--- a/html.c
+++ b/html.c
@@ -2540,7 +2540,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2540 char **body, int *b_size, char **ctype, xs_str **etag) 2540 char **body, int *b_size, char **ctype, xs_str **etag)
2541{ 2541{
2542 const char *accept = xs_dict_get(req, "accept"); 2542 const char *accept = xs_dict_get(req, "accept");
2543 int status = 404; 2543 int status = HTTP_STATUS_NOT_FOUND;
2544 snac snac; 2544 snac snac;
2545 xs *uid = NULL; 2545 xs *uid = NULL;
2546 const char *p_path; 2546 const char *p_path;
@@ -2553,7 +2553,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2553 2553
2554 if (xs_is_null(v)) { 2554 if (xs_is_null(v)) {
2555 srv_log(xs_fmt("html_get_handler bad query '%s'", q_path)); 2555 srv_log(xs_fmt("html_get_handler bad query '%s'", q_path));
2556 return 404; 2556 return HTTP_STATUS_NOT_FOUND;
2557 } 2557 }
2558 2558
2559 uid = xs_dup(v); 2559 uid = xs_dup(v);
@@ -2569,7 +2569,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2569 if (!uid || !user_open(&snac, uid)) { 2569 if (!uid || !user_open(&snac, uid)) {
2570 /* invalid user */ 2570 /* invalid user */
2571 srv_debug(1, xs_fmt("html_get_handler bad user %s", uid)); 2571 srv_debug(1, xs_fmt("html_get_handler bad user %s", uid));
2572 return 404; 2572 return HTTP_STATUS_NOT_FOUND;
2573 } 2573 }
2574 2574
2575 /* return the RSS if requested by Accept header */ 2575 /* return the RSS if requested by Accept header */
@@ -2598,7 +2598,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2598 /** empty public timeline for private users **/ 2598 /** empty public timeline for private users **/
2599 *body = html_timeline(&snac, NULL, 1, 0, 0, 0, NULL, "", 1); 2599 *body = html_timeline(&snac, NULL, 1, 0, 0, 0, NULL, "", 1);
2600 *b_size = strlen(*body); 2600 *b_size = strlen(*body);
2601 status = 200; 2601 status = HTTP_STATUS_OK;
2602 } 2602 }
2603 else 2603 else
2604 if (cache && history_mtime(&snac, h) > timeline_mtime(&snac)) { 2604 if (cache && history_mtime(&snac, h) > timeline_mtime(&snac)) {
@@ -2617,7 +2617,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2617 *body = html_timeline(&snac, pins, 1, skip, show, xs_list_len(next), NULL, "", 1); 2617 *body = html_timeline(&snac, pins, 1, skip, show, xs_list_len(next), NULL, "", 1);
2618 2618
2619 *b_size = strlen(*body); 2619 *b_size = strlen(*body);
2620 status = 200; 2620 status = HTTP_STATUS_OK;
2621 2621
2622 if (save) 2622 if (save)
2623 history_add(&snac, h, *body, *b_size, etag); 2623 history_add(&snac, h, *body, *b_size, etag);
@@ -2627,7 +2627,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2627 if (strcmp(p_path, "admin") == 0) { /** private timeline **/ 2627 if (strcmp(p_path, "admin") == 0) { /** private timeline **/
2628 if (!login(&snac, req)) { 2628 if (!login(&snac, req)) {
2629 *body = xs_dup(uid); 2629 *body = xs_dup(uid);
2630 status = 401; 2630 status = HTTP_STATUS_UNAUTHORIZED;
2631 } 2631 }
2632 else { 2632 else {
2633 const char *q = xs_dict_get(q_vars, "q"); 2633 const char *q = xs_dict_get(q_vars, "q");
@@ -2649,7 +2649,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2649 2649
2650 *body = html_timeline(&snac, tl, 0, skip, show, more, title, page, 0); 2650 *body = html_timeline(&snac, tl, 0, skip, show, more, title, page, 0);
2651 *b_size = strlen(*body); 2651 *b_size = strlen(*body);
2652 status = 200; 2652 status = HTTP_STATUS_OK;
2653 } 2653 }
2654 else { 2654 else {
2655 /** search by content **/ 2655 /** search by content **/
@@ -2670,7 +2670,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2670 2670
2671 *body = html_timeline(&snac, tl, 0, skip, tl_len, to || tl_len == show, title, page, 0); 2671 *body = html_timeline(&snac, tl, 0, skip, tl_len, to || tl_len == show, title, page, 0);
2672 *b_size = strlen(*body); 2672 *b_size = strlen(*body);
2673 status = 200; 2673 status = HTTP_STATUS_OK;
2674 } 2674 }
2675 } 2675 }
2676 else { 2676 else {
@@ -2699,7 +2699,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2699 xs_list_len(next), NULL, "/admin", 1); 2699 xs_list_len(next), NULL, "/admin", 1);
2700 2700
2701 *b_size = strlen(*body); 2701 *b_size = strlen(*body);
2702 status = 200; 2702 status = HTTP_STATUS_OK;
2703 2703
2704 if (save) 2704 if (save)
2705 history_add(&snac, "timeline.html_", *body, *b_size, etag); 2705 history_add(&snac, "timeline.html_", *body, *b_size, etag);
@@ -2711,7 +2711,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2711 if (xs_startswith(p_path, "admin/p/")) { /** unique post by md5 **/ 2711 if (xs_startswith(p_path, "admin/p/")) { /** unique post by md5 **/
2712 if (!login(&snac, req)) { 2712 if (!login(&snac, req)) {
2713 *body = xs_dup(uid); 2713 *body = xs_dup(uid);
2714 status = 401; 2714 status = HTTP_STATUS_UNAUTHORIZED;
2715 } 2715 }
2716 else { 2716 else {
2717 xs *l = xs_split(p_path, "/"); 2717 xs *l = xs_split(p_path, "/");
@@ -2722,7 +2722,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2722 2722
2723 *body = html_timeline(&snac, list, 0, 0, 0, 0, NULL, "/admin", 1); 2723 *body = html_timeline(&snac, list, 0, 0, 0, 0, NULL, "/admin", 1);
2724 *b_size = strlen(*body); 2724 *b_size = strlen(*body);
2725 status = 200; 2725 status = HTTP_STATUS_OK;
2726 } 2726 }
2727 } 2727 }
2728 } 2728 }
@@ -2730,31 +2730,31 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2730 if (strcmp(p_path, "people") == 0) { /** the list of people **/ 2730 if (strcmp(p_path, "people") == 0) { /** the list of people **/
2731 if (!login(&snac, req)) { 2731 if (!login(&snac, req)) {
2732 *body = xs_dup(uid); 2732 *body = xs_dup(uid);
2733 status = 401; 2733 status = HTTP_STATUS_UNAUTHORIZED;
2734 } 2734 }
2735 else { 2735 else {
2736 *body = html_people(&snac); 2736 *body = html_people(&snac);
2737 *b_size = strlen(*body); 2737 *b_size = strlen(*body);
2738 status = 200; 2738 status = HTTP_STATUS_OK;
2739 } 2739 }
2740 } 2740 }
2741 else 2741 else
2742 if (strcmp(p_path, "notifications") == 0) { /** the list of notifications **/ 2742 if (strcmp(p_path, "notifications") == 0) { /** the list of notifications **/
2743 if (!login(&snac, req)) { 2743 if (!login(&snac, req)) {
2744 *body = xs_dup(uid); 2744 *body = xs_dup(uid);
2745 status = 401; 2745 status = HTTP_STATUS_UNAUTHORIZED;
2746 } 2746 }
2747 else { 2747 else {
2748 *body = html_notifications(&snac, skip, show); 2748 *body = html_notifications(&snac, skip, show);
2749 *b_size = strlen(*body); 2749 *b_size = strlen(*body);
2750 status = 200; 2750 status = HTTP_STATUS_OK;
2751 } 2751 }
2752 } 2752 }
2753 else 2753 else
2754 if (strcmp(p_path, "instance") == 0) { /** instance timeline **/ 2754 if (strcmp(p_path, "instance") == 0) { /** instance timeline **/
2755 if (!login(&snac, req)) { 2755 if (!login(&snac, req)) {
2756 *body = xs_dup(uid); 2756 *body = xs_dup(uid);
2757 status = 401; 2757 status = HTTP_STATUS_UNAUTHORIZED;
2758 } 2758 }
2759 else { 2759 else {
2760 xs *list = timeline_instance_list(skip, show); 2760 xs *list = timeline_instance_list(skip, show);
@@ -2763,14 +2763,14 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2763 *body = html_timeline(&snac, list, 0, skip, show, 2763 *body = html_timeline(&snac, list, 0, skip, show,
2764 xs_list_len(next), L("Showing instance timeline"), "/instance", 0); 2764 xs_list_len(next), L("Showing instance timeline"), "/instance", 0);
2765 *b_size = strlen(*body); 2765 *b_size = strlen(*body);
2766 status = 200; 2766 status = HTTP_STATUS_OK;
2767 } 2767 }
2768 } 2768 }
2769 else 2769 else
2770 if (xs_startswith(p_path, "list/")) { /** list timelines **/ 2770 if (xs_startswith(p_path, "list/")) { /** list timelines **/
2771 if (!login(&snac, req)) { 2771 if (!login(&snac, req)) {
2772 *body = xs_dup(uid); 2772 *body = xs_dup(uid);
2773 status = 401; 2773 status = HTTP_STATUS_UNAUTHORIZED;
2774 } 2774 }
2775 else { 2775 else {
2776 xs *l = xs_split(p_path, "/"); 2776 xs *l = xs_split(p_path, "/");
@@ -2787,14 +2787,14 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2787 *body = html_timeline(&snac, list, 0, skip, show, 2787 *body = html_timeline(&snac, list, 0, skip, show,
2788 xs_list_len(next), title, base, 1); 2788 xs_list_len(next), title, base, 1);
2789 *b_size = strlen(*body); 2789 *b_size = strlen(*body);
2790 status = 200; 2790 status = HTTP_STATUS_OK;
2791 } 2791 }
2792 } 2792 }
2793 } 2793 }
2794 else 2794 else
2795 if (xs_startswith(p_path, "p/")) { /** a timeline with just one entry **/ 2795 if (xs_startswith(p_path, "p/")) { /** a timeline with just one entry **/
2796 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) 2796 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE)
2797 return 403; 2797 return HTTP_STATUS_FORBIDDEN;
2798 2798
2799 xs *id = xs_fmt("%s/%s", snac.actor, p_path); 2799 xs *id = xs_fmt("%s/%s", snac.actor, p_path);
2800 xs *msg = NULL; 2800 xs *msg = NULL;
@@ -2807,7 +2807,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2807 2807
2808 *body = html_timeline(&snac, list, 1, 0, 0, 0, NULL, "", 1); 2808 *body = html_timeline(&snac, list, 1, 0, 0, 0, NULL, "", 1);
2809 *b_size = strlen(*body); 2809 *b_size = strlen(*body);
2810 status = 200; 2810 status = HTTP_STATUS_OK;
2811 } 2811 }
2812 } 2812 }
2813 else 2813 else
@@ -2829,10 +2829,10 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2829 else 2829 else
2830 if (xs_startswith(p_path, "h/")) { /** an entry from the history **/ 2830 if (xs_startswith(p_path, "h/")) { /** an entry from the history **/
2831 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) 2831 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE)
2832 return 403; 2832 return HTTP_STATUS_FORBIDDEN;
2833 2833
2834 if (xs_type(xs_dict_get(srv_config, "disable_history")) == XSTYPE_TRUE) 2834 if (xs_type(xs_dict_get(srv_config, "disable_history")) == XSTYPE_TRUE)
2835 return 403; 2835 return HTTP_STATUS_FORBIDDEN;
2836 2836
2837 xs *l = xs_split(p_path, "/"); 2837 xs *l = xs_split(p_path, "/");
2838 const char *id = xs_list_get(l, 1); 2838 const char *id = xs_list_get(l, 1);
@@ -2841,7 +2841,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2841 if (xs_endswith(id, "timeline.html_")) { 2841 if (xs_endswith(id, "timeline.html_")) {
2842 /* Don't let them in */ 2842 /* Don't let them in */
2843 *b_size = 0; 2843 *b_size = 0;
2844 status = 404; 2844 status = HTTP_STATUS_NOT_FOUND;
2845 } 2845 }
2846 else 2846 else
2847 status = history_get(&snac, id, body, b_size, 2847 status = history_get(&snac, id, body, b_size,
@@ -2851,7 +2851,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2851 else 2851 else
2852 if (strcmp(p_path, ".rss") == 0) { /** public timeline in RSS format **/ 2852 if (strcmp(p_path, ".rss") == 0) { /** public timeline in RSS format **/
2853 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE) 2853 if (xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE)
2854 return 403; 2854 return HTTP_STATUS_FORBIDDEN;
2855 2855
2856 xs *elems = timeline_simple_list(&snac, "public", 0, 20); 2856 xs *elems = timeline_simple_list(&snac, "public", 0, 20);
2857 xs *bio = not_really_markdown(xs_dict_get(snac.config, "bio"), NULL, NULL); 2857 xs *bio = not_really_markdown(xs_dict_get(snac.config, "bio"), NULL, NULL);
@@ -2865,12 +2865,12 @@ int html_get_handler(const xs_dict *req, const char *q_path,
2865 *body = timeline_to_rss(&snac, elems, rss_title, rss_link, bio); 2865 *body = timeline_to_rss(&snac, elems, rss_title, rss_link, bio);
2866 *b_size = strlen(*body); 2866 *b_size = strlen(*body);
2867 *ctype = "application/rss+xml; charset=utf-8"; 2867 *ctype = "application/rss+xml; charset=utf-8";
2868 status = 200; 2868 status = HTTP_STATUS_OK;
2869 2869
2870 snac_debug(&snac, 1, xs_fmt("serving RSS")); 2870 snac_debug(&snac, 1, xs_fmt("serving RSS"));
2871 } 2871 }
2872 else 2872 else
2873 status = 404; 2873 status = HTTP_STATUS_NOT_FOUND;
2874 2874
2875 user_free(&snac); 2875 user_free(&snac);
2876 2876
@@ -2901,7 +2901,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
2901 if (!uid || !user_open(&snac, uid)) { 2901 if (!uid || !user_open(&snac, uid)) {
2902 /* invalid user */ 2902 /* invalid user */
2903 srv_debug(1, xs_fmt("html_post_handler bad user %s", uid)); 2903 srv_debug(1, xs_fmt("html_post_handler bad user %s", uid));
2904 return 404; 2904 return HTTP_STATUS_NOT_FOUND;
2905 } 2905 }
2906 2906
2907 p_path = xs_list_get(l, 2); 2907 p_path = xs_list_get(l, 2);
@@ -2910,7 +2910,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
2910 if (!login(&snac, req)) { 2910 if (!login(&snac, req)) {
2911 user_free(&snac); 2911 user_free(&snac);
2912 *body = xs_dup(uid); 2912 *body = xs_dup(uid);
2913 return 401; 2913 return HTTP_STATUS_UNAUTHORIZED;
2914 } 2914 }
2915 2915
2916 p_vars = xs_dict_get(req, "p_vars"); 2916 p_vars = xs_dict_get(req, "p_vars");
@@ -3049,7 +3049,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3049 history_del(&snac, "timeline.html_"); 3049 history_del(&snac, "timeline.html_");
3050 } 3050 }
3051 3051
3052 status = 303; 3052 status = HTTP_STATUS_SEE_OTHER;
3053 } 3053 }
3054 else 3054 else
3055 if (p_path && strcmp(p_path, "admin/action") == 0) { /** **/ 3055 if (p_path && strcmp(p_path, "admin/action") == 0) { /** **/
@@ -3060,11 +3060,11 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3060 const char *group = xs_dict_get(p_vars, "group"); 3060 const char *group = xs_dict_get(p_vars, "group");
3061 3061
3062 if (action == NULL) 3062 if (action == NULL)
3063 return 404; 3063 return HTTP_STATUS_NOT_FOUND;
3064 3064
3065 snac_debug(&snac, 1, xs_fmt("web action '%s' received", action)); 3065 snac_debug(&snac, 1, xs_fmt("web action '%s' received", action));
3066 3066
3067 status = 303; 3067 status = HTTP_STATUS_SEE_OTHER;
3068 3068
3069 if (strcmp(action, L("Like")) == 0) { /** **/ 3069 if (strcmp(action, L("Like")) == 0) { /** **/
3070 xs *msg = msg_admiration(&snac, id, "Like"); 3070 xs *msg = msg_admiration(&snac, id, "Like");
@@ -3216,10 +3216,10 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3216 timeline_touch(&snac); 3216 timeline_touch(&snac);
3217 } 3217 }
3218 else 3218 else
3219 status = 404; 3219 status = HTTP_STATUS_NOT_FOUND;
3220 3220
3221 /* delete the cached timeline */ 3221 /* delete the cached timeline */
3222 if (status == 303) 3222 if (status == HTTP_STATUS_SEE_OTHER)
3223 history_del(&snac, "timeline.html_"); 3223 history_del(&snac, "timeline.html_");
3224 } 3224 }
3225 else 3225 else
@@ -3334,36 +3334,16 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3334 snac.config = xs_dict_set(snac.config, "passwd", pw); 3334 snac.config = xs_dict_set(snac.config, "passwd", pw);
3335 } 3335 }
3336 3336
3337 xs *fn = xs_fmt("%s/user.json", snac.basedir); 3337 user_persist(&snac);
3338 xs *bfn = xs_fmt("%s.bak", fn);
3339 FILE *f;
3340
3341 rename(fn, bfn);
3342
3343 if ((f = fopen(fn, "w")) != NULL) {
3344 xs_json_dump(snac.config, 4, f);
3345 fclose(f);
3346 }
3347 else
3348 rename(bfn, fn);
3349
3350 history_del(&snac, "timeline.html_");
3351
3352 xs *a_msg = msg_actor(&snac);
3353 xs *u_msg = msg_update(&snac, a_msg);
3354
3355 enqueue_message(&snac, u_msg);
3356
3357 enqueue_verify_links(&snac);
3358 3338
3359 status = 303; 3339 status = HTTP_STATUS_SEE_OTHER;
3360 } 3340 }
3361 else 3341 else
3362 if (p_path && strcmp(p_path, "admin/clear-notifications") == 0) { /** **/ 3342 if (p_path && strcmp(p_path, "admin/clear-notifications") == 0) { /** **/
3363 notify_clear(&snac); 3343 notify_clear(&snac);
3364 timeline_touch(&snac); 3344 timeline_touch(&snac);
3365 3345
3366 status = 303; 3346 status = HTTP_STATUS_SEE_OTHER;
3367 } 3347 }
3368 else 3348 else
3369 if (p_path && strcmp(p_path, "admin/vote") == 0) { /** **/ 3349 if (p_path && strcmp(p_path, "admin/vote") == 0) { /** **/
@@ -3416,10 +3396,10 @@ int html_post_handler(const xs_dict *req, const char *q_path,
3416 } 3396 }
3417 } 3397 }
3418 3398
3419 status = 303; 3399 status = HTTP_STATUS_SEE_OTHER;
3420 } 3400 }
3421 3401
3422 if (status == 303) { 3402 if (status == HTTP_STATUS_SEE_OTHER) {
3423 const char *redir = xs_dict_get(p_vars, "redir"); 3403 const char *redir = xs_dict_get(p_vars, "redir");
3424 3404
3425 if (xs_is_null(redir)) 3405 if (xs_is_null(redir))
diff --git a/http_codes.h b/http_codes.h
new file mode 100644
index 0000000..795f92a
--- /dev/null
+++ b/http_codes.h
@@ -0,0 +1,45 @@
1HTTP_STATUS(100, CONTINUE, Continue)
2HTTP_STATUS(101, SWITCHING_PROTOCOLS, Switching Protocols)
3HTTP_STATUS(102, PROCESSING, Processing)
4HTTP_STATUS(103, EARLY_HINTS, Early Hints)
5HTTP_STATUS(200, OK, OK)
6HTTP_STATUS(201, CREATED, Created)
7HTTP_STATUS(202, ACCEPTED, Accepted)
8HTTP_STATUS(203, NON_AUTHORITATIVE_INFORMATION, Non Authoritative Information)
9HTTP_STATUS(204, NO_CONTENT, No Content)
10HTTP_STATUS(205, RESET_CONTENT, Reset Content)
11HTTP_STATUS(206, PARTIAL_CONTENT, Partial Content)
12HTTP_STATUS(207, MULTI_STATUS, Multi Status)
13HTTP_STATUS(208, ALREADY_REPORTED, Already Reported)
14HTTP_STATUS(218, THIS_IS_FINE, This Is Fine)
15HTTP_STATUS(226, IM_USED, IM Used)
16HTTP_STATUS(300, MULTIPLE_CHOICES, Multiple Choices)
17HTTP_STATUS(301, MOVED_PERMANENTLY, Moved Permanently)
18HTTP_STATUS(302, FOUND, Found)
19HTTP_STATUS(303, SEE_OTHER, See Other)
20HTTP_STATUS(304, NOT_MODIFIED, Not Modified)
21HTTP_STATUS(305, USE_PROXY, Use Proxy)
22HTTP_STATUS(306, SWITCH_PROXY, Switch Proxy)
23HTTP_STATUS(307, TEMPORARY_REDIRECT, Temporary Redirect)
24HTTP_STATUS(308, PERMANENT_REDIRECT, Permanent Redirect)
25HTTP_STATUS(400, BAD_REQUEST, Bad Request)
26HTTP_STATUS(401, UNAUTHORIZED, Unauthorized)
27HTTP_STATUS(402, PAYMENT_REQUIRED, Payment Required)
28HTTP_STATUS(403, FORBIDDEN, Forbidden)
29HTTP_STATUS(404, NOT_FOUND, Not Found)
30HTTP_STATUS(405, METHOD_NOT_ALLOWED, Method Not Allowed)
31HTTP_STATUS(406, NOT_ACCEPTABLE, Not Acceptable)
32HTTP_STATUS(407, PROXY_AUTHENTICATION_REQUIRED, Proxy Authentication Required)
33HTTP_STATUS(408, REQUEST_TIMEOUT, Request Timeout)
34HTTP_STATUS(409, CONFLICT, Conflict)
35HTTP_STATUS(410, GONE, Gone)
36HTTP_STATUS(421, MISDIRECTED_REQUEST, Misdirected Request)
37HTTP_STATUS(422, UNPROCESSABLE_CONTENT, Unprocessable Content)
38HTTP_STATUS(499, CLIENT_CLOSED_REQUEST, Client Closed Request)
39HTTP_STATUS(500, INTERNAL_SERVER_ERROR, Internal Server Error)
40HTTP_STATUS(501, NOT_IMPLEMENTED, Not Implemented)
41HTTP_STATUS(502, BAD_GATEWAY, Bad Gateway)
42HTTP_STATUS(503, SERVICE_UNAVAILABLE, Service Unavailable)
43HTTP_STATUS(504, GATEWAY_TIMEOUT, Gateway Timeout)
44HTTP_STATUS(505, HTTP_VERSION_NOT_SUPPORTED, HTTP Version Not Supported)
45HTTP_STATUS(507, INSUFFICIENT_STORAGE, Insufficient Storage) \ No newline at end of file
diff --git a/httpd.c b/httpd.c
index a7396e8..d566b1e 100644
--- a/httpd.c
+++ b/httpd.c
@@ -217,17 +217,17 @@ int server_get_handler(xs_dict *req, const char *q_path,
217 *body = greeting_html(); 217 *body = greeting_html();
218 218
219 if (*body) 219 if (*body)
220 status = 200; 220 status = HTTP_STATUS_OK;
221 } 221 }
222 else 222 else
223 if (strcmp(q_path, "/susie.png") == 0 || strcmp(q_path, "/favicon.ico") == 0 ) { 223 if (strcmp(q_path, "/susie.png") == 0 || strcmp(q_path, "/favicon.ico") == 0 ) {
224 status = 200; 224 status = HTTP_STATUS_OK;
225 *body = xs_base64_dec(default_avatar_base64(), b_size); 225 *body = xs_base64_dec(default_avatar_base64(), b_size);
226 *ctype = "image/png"; 226 *ctype = "image/png";
227 } 227 }
228 else 228 else
229 if (strcmp(q_path, "/.well-known/nodeinfo") == 0) { 229 if (strcmp(q_path, "/.well-known/nodeinfo") == 0) {
230 status = 200; 230 status = HTTP_STATUS_OK;
231 *ctype = "application/json; charset=utf-8"; 231 *ctype = "application/json; charset=utf-8";
232 *body = xs_fmt("{\"links\":[" 232 *body = xs_fmt("{\"links\":["
233 "{\"rel\":\"http:/" "/nodeinfo.diaspora.software/ns/schema/2.0\"," 233 "{\"rel\":\"http:/" "/nodeinfo.diaspora.software/ns/schema/2.0\","
@@ -236,7 +236,7 @@ int server_get_handler(xs_dict *req, const char *q_path,
236 } 236 }
237 else 237 else
238 if (strcmp(q_path, "/.well-known/host-meta") == 0) { 238 if (strcmp(q_path, "/.well-known/host-meta") == 0) {
239 status = 200; 239 status = HTTP_STATUS_OK;
240 *ctype = "application/xrd+xml"; 240 *ctype = "application/xrd+xml";
241 *body = xs_fmt("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n" 241 *body = xs_fmt("<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
242 "<XRD>" 242 "<XRD>"
@@ -245,13 +245,13 @@ int server_get_handler(xs_dict *req, const char *q_path,
245 } 245 }
246 else 246 else
247 if (strcmp(q_path, "/nodeinfo_2_0") == 0) { 247 if (strcmp(q_path, "/nodeinfo_2_0") == 0) {
248 status = 200; 248 status = HTTP_STATUS_OK;
249 *ctype = "application/json; charset=utf-8"; 249 *ctype = "application/json; charset=utf-8";
250 *body = nodeinfo_2_0(); 250 *body = nodeinfo_2_0();
251 } 251 }
252 else 252 else
253 if (strcmp(q_path, "/robots.txt") == 0) { 253 if (strcmp(q_path, "/robots.txt") == 0) {
254 status = 200; 254 status = HTTP_STATUS_OK;
255 *ctype = "text/plain"; 255 *ctype = "text/plain";
256 *body = xs_str_new("User-agent: *\n" 256 *body = xs_str_new("User-agent: *\n"
257 "Disallow: /\n"); 257 "Disallow: /\n");
@@ -362,8 +362,18 @@ void httpd_connection(FILE *f)
362 362
363 } 363 }
364 else 364 else
365 if (strcmp(method, "PATCH") == 0) {
366
367#ifndef NO_MASTODON_API
368 if (status == 0)
369 status = mastoapi_patch_handler(req, q_path,
370 payload, p_size, &body, &b_size, &ctype);
371#endif
372
373 }
374 else
365 if (strcmp(method, "OPTIONS") == 0) { 375 if (strcmp(method, "OPTIONS") == 0) {
366 status = 200; 376 status = HTTP_STATUS_OK;
367 } 377 }
368 else 378 else
369 if (strcmp(method, "DELETE") == 0) { 379 if (strcmp(method, "DELETE") == 0) {
@@ -378,22 +388,22 @@ void httpd_connection(FILE *f)
378 if (status == 0) { 388 if (status == 0) {
379 srv_archive_error("unattended_method", "unattended method", req, payload); 389 srv_archive_error("unattended_method", "unattended method", req, payload);
380 srv_debug(1, xs_fmt("httpd_connection unattended %s %s", method, q_path)); 390 srv_debug(1, xs_fmt("httpd_connection unattended %s %s", method, q_path));
381 status = 404; 391 status = HTTP_STATUS_NOT_FOUND;
382 } 392 }
383 393
384 if (status == 403) 394 if (status == HTTP_STATUS_FORBIDDEN)
385 body = xs_str_new("<h1>403 Forbidden</h1>"); 395 body = xs_str_new("<h1>403 Forbidden</h1>");
386 396
387 if (status == 404) 397 if (status == HTTP_STATUS_NOT_FOUND)
388 body = xs_str_new("<h1>404 Not Found</h1>"); 398 body = xs_str_new("<h1>404 Not Found</h1>");
389 399
390 if (status == 400 && body != NULL) 400 if (status == HTTP_STATUS_BAD_REQUEST && body != NULL)
391 body = xs_str_new("<h1>400 Bad Request</h1>"); 401 body = xs_str_new("<h1>400 Bad Request</h1>");
392 402
393 if (status == 303) 403 if (status == HTTP_STATUS_SEE_OTHER)
394 headers = xs_dict_append(headers, "location", body); 404 headers = xs_dict_append(headers, "location", body);
395 405
396 if (status == 401) { 406 if (status == HTTP_STATUS_UNAUTHORIZED) {
397 xs *www_auth = xs_fmt("Basic realm=\"@%s@%s snac login\"", 407 xs *www_auth = xs_fmt("Basic realm=\"@%s@%s snac login\"",
398 body, xs_dict_get(srv_config, "host")); 408 body, xs_dict_get(srv_config, "host"));
399 409
@@ -432,7 +442,7 @@ void httpd_connection(FILE *f)
432 if (p_state->use_fcgi) 442 if (p_state->use_fcgi)
433 xs_fcgi_response(f, status, headers, body, b_size, fcgi_id); 443 xs_fcgi_response(f, status, headers, body, b_size, fcgi_id);
434 else 444 else
435 xs_httpd_response(f, status, headers, body, b_size); 445 xs_httpd_response(f, status, http_status_text(status), headers, body, b_size);
436 446
437 fclose(f); 447 fclose(f);
438 448
diff --git a/mastoapi.c b/mastoapi.c
index 3936c2a..120c1aa 100644
--- a/mastoapi.c
+++ b/mastoapi.c
@@ -32,9 +32,9 @@ int app_add(const char *id, const xs_dict *app)
32/* stores an app */ 32/* stores an app */
33{ 33{
34 if (!xs_is_hex(id)) 34 if (!xs_is_hex(id))
35 return 500; 35 return HTTP_STATUS_INTERNAL_SERVER_ERROR;
36 36
37 int status = 201; 37 int status = HTTP_STATUS_CREATED;
38 xs *fn = xs_fmt("%s/app/", srv_basedir); 38 xs *fn = xs_fmt("%s/app/", srv_basedir);
39 FILE *f; 39 FILE *f;
40 40
@@ -47,7 +47,7 @@ int app_add(const char *id, const xs_dict *app)
47 fclose(f); 47 fclose(f);
48 } 48 }
49 else 49 else
50 status = 500; 50 status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
51 51
52 return status; 52 return status;
53} 53}
@@ -95,9 +95,9 @@ int token_add(const char *id, const xs_dict *token)
95/* stores a token */ 95/* stores a token */
96{ 96{
97 if (!xs_is_hex(id)) 97 if (!xs_is_hex(id))
98 return 500; 98 return HTTP_STATUS_INTERNAL_SERVER_ERROR;
99 99
100 int status = 201; 100 int status = HTTP_STATUS_CREATED;
101 xs *fn = xs_fmt("%s/token/", srv_basedir); 101 xs *fn = xs_fmt("%s/token/", srv_basedir);
102 FILE *f; 102 FILE *f;
103 103
@@ -110,7 +110,7 @@ int token_add(const char *id, const xs_dict *token)
110 fclose(f); 110 fclose(f);
111 } 111 }
112 else 112 else
113 status = 500; 113 status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
114 114
115 return status; 115 return status;
116} 116}
@@ -174,7 +174,7 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
174 if (!xs_startswith(q_path, "/oauth/")) 174 if (!xs_startswith(q_path, "/oauth/"))
175 return 0; 175 return 0;
176 176
177 int status = 404; 177 int status = HTTP_STATUS_NOT_FOUND;
178 const xs_dict *msg = xs_dict_get(req, "q_vars"); 178 const xs_dict *msg = xs_dict_get(req, "q_vars");
179 xs *cmd = xs_replace_n(q_path, "/oauth", "", 1); 179 xs *cmd = xs_replace_n(q_path, "/oauth", "", 1);
180 180
@@ -186,7 +186,7 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
186 const char *rtype = xs_dict_get(msg, "response_type"); 186 const char *rtype = xs_dict_get(msg, "response_type");
187 const char *state = xs_dict_get(msg, "state"); 187 const char *state = xs_dict_get(msg, "state");
188 188
189 status = 400; 189 status = HTTP_STATUS_BAD_REQUEST;
190 190
191 if (cid && ruri && rtype && strcmp(rtype, "code") == 0) { 191 if (cid && ruri && rtype && strcmp(rtype, "code") == 0) {
192 xs *app = app_get(cid); 192 xs *app = app_get(cid);
@@ -201,7 +201,7 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
201 *body = xs_fmt(login_page, host, host, "", proto, host, "oauth/x-snac-login", 201 *body = xs_fmt(login_page, host, host, "", proto, host, "oauth/x-snac-login",
202 ruri, cid, state, USER_AGENT); 202 ruri, cid, state, USER_AGENT);
203 *ctype = "text/html"; 203 *ctype = "text/html";
204 status = 200; 204 status = HTTP_STATUS_OK;
205 205
206 srv_debug(1, xs_fmt("oauth authorize: generating login page")); 206 srv_debug(1, xs_fmt("oauth authorize: generating login page"));
207 } 207 }
@@ -219,7 +219,7 @@ int oauth_get_handler(const xs_dict *req, const char *q_path,
219 *body = xs_fmt(login_page, host, host, "", proto, host, "oauth/x-snac-get-token", 219 *body = xs_fmt(login_page, host, host, "", proto, host, "oauth/x-snac-get-token",
220 "", "", "", USER_AGENT); 220 "", "", "", USER_AGENT);
221 *ctype = "text/html"; 221 *ctype = "text/html";
222 status = 200; 222 status = HTTP_STATUS_OK;
223 223
224 } 224 }
225 225
@@ -237,7 +237,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
237 if (!xs_startswith(q_path, "/oauth/")) 237 if (!xs_startswith(q_path, "/oauth/"))
238 return 0; 238 return 0;
239 239
240 int status = 404; 240 int status = HTTP_STATUS_NOT_FOUND;
241 241
242 const char *i_ctype = xs_dict_get(req, "content-type"); 242 const char *i_ctype = xs_dict_get(req, "content-type");
243 xs *args = NULL; 243 xs *args = NULL;
@@ -255,7 +255,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
255 args = xs_dup(xs_dict_get(req, "p_vars")); 255 args = xs_dup(xs_dict_get(req, "p_vars"));
256 256
257 if (args == NULL) 257 if (args == NULL)
258 return 400; 258 return HTTP_STATUS_BAD_REQUEST;
259 259
260 xs *cmd = xs_replace_n(q_path, "/oauth", "", 1); 260 xs *cmd = xs_replace_n(q_path, "/oauth", "", 1);
261 261
@@ -274,7 +274,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
274 *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", proto, host, "oauth/x-snac-login", 274 *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", proto, host, "oauth/x-snac-login",
275 redir, cid, state, USER_AGENT); 275 redir, cid, state, USER_AGENT);
276 *ctype = "text/html"; 276 *ctype = "text/html";
277 status = 200; 277 status = HTTP_STATUS_OK;
278 278
279 if (login && passwd && redir && cid) { 279 if (login && passwd && redir && cid) {
280 snac snac; 280 snac snac;
@@ -296,7 +296,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
296 else 296 else
297 *body = xs_fmt("%s?code=%s", redir, code); 297 *body = xs_fmt("%s?code=%s", redir, code);
298 298
299 status = 303; 299 status = HTTP_STATUS_SEE_OTHER;
300 } 300 }
301 301
302 /* if there is a state, add it */ 302 /* if there is a state, add it */
@@ -375,12 +375,12 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
375 xs *app = app_get(cid); 375 xs *app = app_get(cid);
376 376
377 if (app == NULL) { 377 if (app == NULL) {
378 status = 401; 378 status = HTTP_STATUS_UNAUTHORIZED;
379 srv_log(xs_fmt("oauth token: invalid app %s", cid)); 379 srv_log(xs_fmt("oauth token: invalid app %s", cid));
380 } 380 }
381 else 381 else
382 if (strcmp(csec, xs_dict_get(app, "client_secret")) != 0) { 382 if (strcmp(csec, xs_dict_get(app, "client_secret")) != 0) {
383 status = 401; 383 status = HTTP_STATUS_UNAUTHORIZED;
384 srv_log(xs_fmt("oauth token: invalid client_secret for app %s", cid)); 384 srv_log(xs_fmt("oauth token: invalid client_secret for app %s", cid));
385 } 385 }
386 else { 386 else {
@@ -397,7 +397,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
397 397
398 *body = xs_json_dumps(rsp, 4); 398 *body = xs_json_dumps(rsp, 4);
399 *ctype = "application/json"; 399 *ctype = "application/json";
400 status = 200; 400 status = HTTP_STATUS_OK;
401 401
402 const char *uid = xs_dict_get(app, "uid"); 402 const char *uid = xs_dict_get(app, "uid");
403 403
@@ -416,7 +416,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
416 } 416 }
417 else { 417 else {
418 srv_debug(1, xs_fmt("oauth token: invalid or unset arguments")); 418 srv_debug(1, xs_fmt("oauth token: invalid or unset arguments"));
419 status = 400; 419 status = HTTP_STATUS_BAD_REQUEST;
420 } 420 }
421 } 421 }
422 else 422 else
@@ -433,12 +433,12 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
433 433
434 if (token == NULL || strcmp(csec, xs_dict_get(token, "client_secret")) != 0) { 434 if (token == NULL || strcmp(csec, xs_dict_get(token, "client_secret")) != 0) {
435 srv_debug(1, xs_fmt("oauth revoke: bad secret for token %s", tokid)); 435 srv_debug(1, xs_fmt("oauth revoke: bad secret for token %s", tokid));
436 status = 403; 436 status = HTTP_STATUS_FORBIDDEN;
437 } 437 }
438 else { 438 else {
439 token_del(tokid); 439 token_del(tokid);
440 srv_debug(1, xs_fmt("oauth revoke: revoked token %s", tokid)); 440 srv_debug(1, xs_fmt("oauth revoke: revoked token %s", tokid));
441 status = 200; 441 status = HTTP_STATUS_OK;
442 442
443 /* also delete the app, as it serves no purpose from now on */ 443 /* also delete the app, as it serves no purpose from now on */
444 app_del(cid); 444 app_del(cid);
@@ -446,7 +446,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
446 } 446 }
447 else { 447 else {
448 srv_debug(1, xs_fmt("oauth revoke: invalid or unset arguments")); 448 srv_debug(1, xs_fmt("oauth revoke: invalid or unset arguments"));
449 status = 403; 449 status = HTTP_STATUS_FORBIDDEN;
450 } 450 }
451 } 451 }
452 if (strcmp(cmd, "/x-snac-get-token") == 0) { /** **/ 452 if (strcmp(cmd, "/x-snac-get-token") == 0) { /** **/
@@ -459,7 +459,7 @@ int oauth_post_handler(const xs_dict *req, const char *q_path,
459 *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", proto, host, "oauth/x-snac-get-token", 459 *body = xs_fmt(login_page, host, host, "LOGIN INCORRECT", proto, host, "oauth/x-snac-get-token",
460 "", "", "", USER_AGENT); 460 "", "", "", USER_AGENT);
461 *ctype = "text/html"; 461 *ctype = "text/html";
462 status = 200; 462 status = HTTP_STATUS_OK;
463 463
464 if (login && passwd) { 464 if (login && passwd) {
465 snac user; 465 snac user;
@@ -1150,109 +1150,123 @@ int process_auth_token(snac *snac, const xs_dict *req)
1150 return logged_in; 1150 return logged_in;
1151} 1151}
1152 1152
1153 1153void credentials_get(char **body, char **ctype, int *status, snac snac)
1154int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1155 char **body, int *b_size, char **ctype)
1156{ 1154{
1157 (void)b_size; 1155 xs *acct = xs_dict_new();
1156
1157 acct = xs_dict_append(acct, "id", snac.md5);
1158 acct = xs_dict_append(acct, "username", xs_dict_get(snac.config, "uid"));
1159 acct = xs_dict_append(acct, "acct", xs_dict_get(snac.config, "uid"));
1160 acct = xs_dict_append(acct, "display_name", xs_dict_get(snac.config, "name"));
1161 acct = xs_dict_append(acct, "created_at", xs_dict_get(snac.config, "published"));
1162 acct = xs_dict_append(acct, "last_status_at", xs_dict_get(snac.config, "published"));
1163 acct = xs_dict_append(acct, "note", xs_dict_get(snac.config, "bio"));
1164 acct = xs_dict_append(acct, "url", snac.actor);
1165 acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE));
1166 acct = xs_dict_append(acct, "bot", xs_dict_get(snac.config, "bot"));
1158 1167
1159 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 1168 xs *src = xs_json_loads("{\"privacy\":\"public\","
1160 return 0; 1169 "\"sensitive\":false,\"fields\":[],\"note\":\"\"}");
1170 /* some apps take the note from the source object */
1171 src = xs_dict_set(src, "note", xs_dict_get(snac.config, "bio"));
1172 src = xs_dict_set(src, "privacy", xs_type(xs_dict_get(snac.config, "private")) == XSTYPE_TRUE ? "private" : "public");
1161 1173
1162 int status = 404; 1174 const xs_str *cw = xs_dict_get(snac.config, "cw");
1163 const xs_dict *args = xs_dict_get(req, "q_vars"); 1175 src = xs_dict_set(src, "sensitive",
1164 xs *cmd = xs_replace_n(q_path, "/api", "", 1); 1176 strcmp(cw, "open") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE));
1165 1177
1166 snac snac1 = {0}; 1178 src = xs_dict_set(src, "bot", xs_dict_get(snac.config, "bot"));
1167 int logged_in = process_auth_token(&snac1, req);
1168 1179
1169 if (strcmp(cmd, "/v1/accounts/verify_credentials") == 0) { /** **/ 1180 xs *avatar = NULL;
1170 if (logged_in) { 1181 const char *av = xs_dict_get(snac.config, "avatar");
1171 xs *acct = xs_dict_new();
1172
1173 acct = xs_dict_append(acct, "id", snac1.md5);
1174 acct = xs_dict_append(acct, "username", xs_dict_get(snac1.config, "uid"));
1175 acct = xs_dict_append(acct, "acct", xs_dict_get(snac1.config, "uid"));
1176 acct = xs_dict_append(acct, "display_name", xs_dict_get(snac1.config, "name"));
1177 acct = xs_dict_append(acct, "created_at", xs_dict_get(snac1.config, "published"));
1178 acct = xs_dict_append(acct, "last_status_at", xs_dict_get(snac1.config, "published"));
1179 acct = xs_dict_append(acct, "note", xs_dict_get(snac1.config, "bio"));
1180 acct = xs_dict_append(acct, "url", snac1.actor);
1181 acct = xs_dict_append(acct, "locked", xs_stock(XSTYPE_FALSE));
1182 acct = xs_dict_append(acct, "bot", xs_dict_get(snac1.config, "bot"));
1183
1184 xs *src = xs_json_loads("{\"privacy\":\"public\","
1185 "\"sensitive\":false,\"fields\":[],\"note\":\"\"}");
1186 acct = xs_dict_append(acct, "source", src);
1187
1188 xs *avatar = NULL;
1189 const char *av = xs_dict_get(snac1.config, "avatar");
1190
1191 if (xs_is_null(av) || *av == '\0')
1192 avatar = xs_fmt("%s/susie.png", srv_baseurl);
1193 else
1194 avatar = xs_dup(av);
1195 1182
1196 acct = xs_dict_append(acct, "avatar", avatar); 1183 if (xs_is_null(av) || *av == '\0')
1197 acct = xs_dict_append(acct, "avatar_static", avatar); 1184 avatar = xs_fmt("%s/susie.png", srv_baseurl);
1185 else
1186 avatar = xs_dup(av);
1198 1187
1199 xs *header = NULL; 1188 acct = xs_dict_append(acct, "avatar", avatar);
1200 const char *hd = xs_dict_get(snac1.config, "header"); 1189 acct = xs_dict_append(acct, "avatar_static", avatar);
1201 1190
1202 if (!xs_is_null(hd)) 1191 xs *header = NULL;
1203 header = xs_dup(hd); 1192 const char *hd = xs_dict_get(snac.config, "header");
1204 else
1205 header = xs_fmt("%s/header.png", srv_baseurl);
1206 1193
1207 acct = xs_dict_append(acct, "header", header); 1194 if (!xs_is_null(hd))
1208 acct = xs_dict_append(acct, "header_static", header); 1195 header = xs_dup(hd);
1196 else
1197 header = xs_fmt("%s/header.png", srv_baseurl);
1209 1198
1210 const xs_dict *metadata = xs_dict_get(snac1.config, "metadata"); 1199 acct = xs_dict_append(acct, "header", header);
1211 if (xs_type(metadata) == XSTYPE_DICT) { 1200 acct = xs_dict_append(acct, "header_static", header);
1212 xs *fields = xs_list_new();
1213 const xs_str *k;
1214 const xs_str *v;
1215 1201
1216 xs_dict *val_links = snac1.links; 1202 const xs_dict *metadata = xs_dict_get(snac.config, "metadata");
1217 if (xs_is_null(val_links)) 1203 if (xs_type(metadata) == XSTYPE_DICT) {
1218 val_links = xs_stock(XSTYPE_DICT); 1204 xs *fields = xs_list_new();
1205 const xs_str *k;
1206 const xs_str *v;
1219 1207
1220 int c = 0; 1208 xs_dict *val_links = snac.links;
1221 while (xs_dict_next(metadata, &k, &v, &c)) { 1209 if (xs_is_null(val_links))
1222 xs *val_date = NULL; 1210 val_links = xs_stock(XSTYPE_DICT);
1223 1211
1224 const xs_number *verified_time = xs_dict_get(val_links, v); 1212 int c = 0;
1225 if (xs_type(verified_time) == XSTYPE_NUMBER) { 1213 while (xs_dict_next(metadata, &k, &v, &c)) {
1226 time_t t = xs_number_get(verified_time); 1214 xs *val_date = NULL;
1227 1215
1228 if (t > 0) 1216 const xs_number *verified_time = xs_dict_get(val_links, v);
1229 val_date = xs_str_utctime(t, ISO_DATE_SPEC); 1217 if (xs_type(verified_time) == XSTYPE_NUMBER) {
1230 } 1218 time_t t = xs_number_get(verified_time);
1231 1219
1232 xs *d = xs_dict_new(); 1220 if (t > 0)
1221 val_date = xs_str_utctime(t, ISO_DATE_SPEC);
1222 }
1233 1223
1234 d = xs_dict_append(d, "name", k); 1224 xs *d = xs_dict_new();
1235 d = xs_dict_append(d, "value", v);
1236 d = xs_dict_append(d, "verified_at",
1237 xs_type(val_date) == XSTYPE_STRING && *val_date ?
1238 val_date : xs_stock(XSTYPE_NULL));
1239 1225
1240 fields = xs_list_append(fields, d); 1226 d = xs_dict_append(d, "name", k);
1241 } 1227 d = xs_dict_append(d, "value", v);
1228 d = xs_dict_append(d, "verified_at",
1229 xs_type(val_date) == XSTYPE_STRING && *val_date ? val_date : xs_stock(XSTYPE_NULL));
1242 1230
1243 acct = xs_dict_set(acct, "fields", fields); 1231 fields = xs_list_append(fields, d);
1244 } 1232 }
1245 1233
1246 acct = xs_dict_append(acct, "followers_count", xs_stock(0)); 1234 acct = xs_dict_set(acct, "fields", fields);
1247 acct = xs_dict_append(acct, "following_count", xs_stock(0)); 1235 /* some apps take the fields from the source object */
1248 acct = xs_dict_append(acct, "statuses_count", xs_stock(0)); 1236 src = xs_dict_set(src, "fields", fields);
1237 }
1249 1238
1250 *body = xs_json_dumps(acct, 4); 1239 acct = xs_dict_append(acct, "source", src);
1251 *ctype = "application/json"; 1240 acct = xs_dict_append(acct, "followers_count", xs_stock(0));
1252 status = 200; 1241 acct = xs_dict_append(acct, "following_count", xs_stock(0));
1242 acct = xs_dict_append(acct, "statuses_count", xs_stock(0));
1243
1244 *body = xs_json_dumps(acct, 4);
1245 *ctype = "application/json";
1246 *status = HTTP_STATUS_OK;
1247}
1248
1249int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1250 char **body, int *b_size, char **ctype)
1251{
1252 (void)b_size;
1253
1254 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
1255 return 0;
1256
1257 int status = HTTP_STATUS_NOT_FOUND;
1258 const xs_dict *args = xs_dict_get(req, "q_vars");
1259 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
1260
1261 snac snac1 = {0};
1262 int logged_in = process_auth_token(&snac1, req);
1263
1264 if (strcmp(cmd, "/v1/accounts/verify_credentials") == 0) { /** **/
1265 if (logged_in) {
1266 credentials_get(body, ctype, &status, snac1);
1253 } 1267 }
1254 else { 1268 else {
1255 status = 422; // "Unprocessable entity" (no login) 1269 status = HTTP_STATUS_UNPROCESSABLE_CONTENT; // (no login)
1256 } 1270 }
1257 } 1271 }
1258 else 1272 else
@@ -1279,10 +1293,10 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1279 1293
1280 *body = xs_json_dumps(res, 4); 1294 *body = xs_json_dumps(res, 4);
1281 *ctype = "application/json"; 1295 *ctype = "application/json";
1282 status = 200; 1296 status = HTTP_STATUS_OK;
1283 } 1297 }
1284 else 1298 else
1285 status = 422; 1299 status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
1286 } 1300 }
1287 else 1301 else
1288 if (strcmp(cmd, "/v1/accounts/lookup") == 0) { /** **/ 1302 if (strcmp(cmd, "/v1/accounts/lookup") == 0) { /** **/
@@ -1304,7 +1318,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1304 1318
1305 *body = xs_json_dumps(macct, 4); 1319 *body = xs_json_dumps(macct, 4);
1306 *ctype = "application/json"; 1320 *ctype = "application/json";
1307 status = 200; 1321 status = HTTP_STATUS_OK;
1308 1322
1309 user_free(&user); 1323 user_free(&user);
1310 } 1324 }
@@ -1450,7 +1464,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1450 if (out != NULL) { 1464 if (out != NULL) {
1451 *body = xs_json_dumps(out, 4); 1465 *body = xs_json_dumps(out, 4);
1452 *ctype = "application/json"; 1466 *ctype = "application/json";
1453 status = 200; 1467 status = HTTP_STATUS_OK;
1454 } 1468 }
1455 } 1469 }
1456 } 1470 }
@@ -1554,12 +1568,12 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1554 1568
1555 *body = xs_json_dumps(out, 4); 1569 *body = xs_json_dumps(out, 4);
1556 *ctype = "application/json"; 1570 *ctype = "application/json";
1557 status = 200; 1571 status = HTTP_STATUS_OK;
1558 1572
1559 srv_debug(2, xs_fmt("mastoapi timeline: returned %d entries", xs_list_len(out))); 1573 srv_debug(2, xs_fmt("mastoapi timeline: returned %d entries", xs_list_len(out)));
1560 } 1574 }
1561 else { 1575 else {
1562 status = 401; // unauthorized 1576 status = HTTP_STATUS_UNAUTHORIZED;
1563 } 1577 }
1564 } 1578 }
1565 else 1579 else
@@ -1612,7 +1626,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1612 1626
1613 *body = xs_json_dumps(out, 4); 1627 *body = xs_json_dumps(out, 4);
1614 *ctype = "application/json"; 1628 *ctype = "application/json";
1615 status = 200; 1629 status = HTTP_STATUS_OK;
1616 } 1630 }
1617 else 1631 else
1618 if (xs_startswith(cmd, "/v1/timelines/tag/")) { /** **/ 1632 if (xs_startswith(cmd, "/v1/timelines/tag/")) { /** **/
@@ -1661,7 +1675,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1661 1675
1662 *body = xs_json_dumps(out, 4); 1676 *body = xs_json_dumps(out, 4);
1663 *ctype = "application/json"; 1677 *ctype = "application/json";
1664 status = 200; 1678 status = HTTP_STATUS_OK;
1665 } 1679 }
1666 else 1680 else
1667 if (xs_startswith(cmd, "/v1/timelines/list/")) { /** **/ 1681 if (xs_startswith(cmd, "/v1/timelines/list/")) { /** **/
@@ -1729,17 +1743,17 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1729 1743
1730 *body = xs_json_dumps(out, 4); 1744 *body = xs_json_dumps(out, 4);
1731 *ctype = "application/json"; 1745 *ctype = "application/json";
1732 status = 200; 1746 status = HTTP_STATUS_OK;
1733 } 1747 }
1734 else 1748 else
1735 status = 421; 1749 status = HTTP_STATUS_MISDIRECTED_REQUEST;
1736 } 1750 }
1737 else 1751 else
1738 if (strcmp(cmd, "/v1/conversations") == 0) { /** **/ 1752 if (strcmp(cmd, "/v1/conversations") == 0) { /** **/
1739 /* TBD */ 1753 /* TBD */
1740 *body = xs_dup("[]"); 1754 *body = xs_dup("[]");
1741 *ctype = "application/json"; 1755 *ctype = "application/json";
1742 status = 200; 1756 status = HTTP_STATUS_OK;
1743 } 1757 }
1744 else 1758 else
1745 if (strcmp(cmd, "/v1/notifications") == 0) { /** **/ 1759 if (strcmp(cmd, "/v1/notifications") == 0) { /** **/
@@ -1817,17 +1831,17 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1817 1831
1818 *body = xs_json_dumps(out, 4); 1832 *body = xs_json_dumps(out, 4);
1819 *ctype = "application/json"; 1833 *ctype = "application/json";
1820 status = 200; 1834 status = HTTP_STATUS_OK;
1821 } 1835 }
1822 else 1836 else
1823 status = 401; 1837 status = HTTP_STATUS_UNAUTHORIZED;
1824 } 1838 }
1825 else 1839 else
1826 if (strcmp(cmd, "/v1/filters") == 0) { /** **/ 1840 if (strcmp(cmd, "/v1/filters") == 0) { /** **/
1827 /* snac will never have filters */ 1841 /* snac will never have filters */
1828 *body = xs_dup("[]"); 1842 *body = xs_dup("[]");
1829 *ctype = "application/json"; 1843 *ctype = "application/json";
1830 status = 200; 1844 status = HTTP_STATUS_OK;
1831 } 1845 }
1832 else 1846 else
1833 if (strcmp(cmd, "/v2/filters") == 0) { /** **/ 1847 if (strcmp(cmd, "/v2/filters") == 0) { /** **/
@@ -1836,21 +1850,21 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1836 * in some apps */ 1850 * in some apps */
1837 *body = xs_dup("[]"); 1851 *body = xs_dup("[]");
1838 *ctype = "application/json"; 1852 *ctype = "application/json";
1839 status = 200; 1853 status = HTTP_STATUS_OK;
1840 } 1854 }
1841 else 1855 else
1842 if (strcmp(cmd, "/v1/favourites") == 0) { /** **/ 1856 if (strcmp(cmd, "/v1/favourites") == 0) { /** **/
1843 /* snac will never support a list of favourites */ 1857 /* snac will never support a list of favourites */
1844 *body = xs_dup("[]"); 1858 *body = xs_dup("[]");
1845 *ctype = "application/json"; 1859 *ctype = "application/json";
1846 status = 200; 1860 status = HTTP_STATUS_OK;
1847 } 1861 }
1848 else 1862 else
1849 if (strcmp(cmd, "/v1/bookmarks") == 0) { /** **/ 1863 if (strcmp(cmd, "/v1/bookmarks") == 0) { /** **/
1850 /* snac does not support bookmarks */ 1864 /* snac does not support bookmarks */
1851 *body = xs_dup("[]"); 1865 *body = xs_dup("[]");
1852 *ctype = "application/json"; 1866 *ctype = "application/json";
1853 status = 200; 1867 status = HTTP_STATUS_OK;
1854 } 1868 }
1855 else 1869 else
1856 if (strcmp(cmd, "/v1/lists") == 0) { /** list of lists **/ 1870 if (strcmp(cmd, "/v1/lists") == 0) { /** list of lists **/
@@ -1873,7 +1887,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1873 1887
1874 *body = xs_json_dumps(l, 4); 1888 *body = xs_json_dumps(l, 4);
1875 *ctype = "application/json"; 1889 *ctype = "application/json";
1876 status = 200; 1890 status = HTTP_STATUS_OK;
1877 } 1891 }
1878 } 1892 }
1879 else 1893 else
@@ -1903,7 +1917,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1903 1917
1904 *body = xs_json_dumps(out, 4); 1918 *body = xs_json_dumps(out, 4);
1905 *ctype = "application/json"; 1919 *ctype = "application/json";
1906 status = 200; 1920 status = HTTP_STATUS_OK;
1907 } 1921 }
1908 } 1922 }
1909 else 1923 else
@@ -1931,7 +1945,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1931 1945
1932 *body = xs_json_dumps(out, 4); 1946 *body = xs_json_dumps(out, 4);
1933 *ctype = "application/json"; 1947 *ctype = "application/json";
1934 status = 200; 1948 status = HTTP_STATUS_OK;
1935 } 1949 }
1936 } 1950 }
1937 } 1951 }
@@ -1941,28 +1955,28 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
1941 /* snac does not schedule notes */ 1955 /* snac does not schedule notes */
1942 *body = xs_dup("[]"); 1956 *body = xs_dup("[]");
1943 *ctype = "application/json"; 1957 *ctype = "application/json";
1944 status = 200; 1958 status = HTTP_STATUS_OK;
1945 } 1959 }
1946 else 1960 else
1947 if (strcmp(cmd, "/v1/follow_requests") == 0) { /** **/ 1961 if (strcmp(cmd, "/v1/follow_requests") == 0) { /** **/
1948 /* snac does not support optional follow confirmations */ 1962 /* snac does not support optional follow confirmations */
1949 *body = xs_dup("[]"); 1963 *body = xs_dup("[]");
1950 *ctype = "application/json"; 1964 *ctype = "application/json";
1951 status = 200; 1965 status = HTTP_STATUS_OK;
1952 } 1966 }
1953 else 1967 else
1954 if (strcmp(cmd, "/v1/announcements") == 0) { /** **/ 1968 if (strcmp(cmd, "/v1/announcements") == 0) { /** **/
1955 /* snac has no announcements (yet?) */ 1969 /* snac has no announcements (yet?) */
1956 *body = xs_dup("[]"); 1970 *body = xs_dup("[]");
1957 *ctype = "application/json"; 1971 *ctype = "application/json";
1958 status = 200; 1972 status = HTTP_STATUS_OK;
1959 } 1973 }
1960 else 1974 else
1961 if (strcmp(cmd, "/v1/custom_emojis") == 0) { /** **/ 1975 if (strcmp(cmd, "/v1/custom_emojis") == 0) { /** **/
1962 /* are you kidding me? */ 1976 /* are you kidding me? */
1963 *body = xs_dup("[]"); 1977 *body = xs_dup("[]");
1964 *ctype = "application/json"; 1978 *ctype = "application/json";
1965 status = 200; 1979 status = HTTP_STATUS_OK;
1966 } 1980 }
1967 else 1981 else
1968 if (strcmp(cmd, "/v1/instance") == 0) { /** **/ 1982 if (strcmp(cmd, "/v1/instance") == 0) { /** **/
@@ -2075,7 +2089,7 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2075 2089
2076 *body = xs_json_dumps(ins, 4); 2090 *body = xs_json_dumps(ins, 4);
2077 *ctype = "application/json"; 2091 *ctype = "application/json";
2078 status = 200; 2092 status = HTTP_STATUS_OK;
2079 } 2093 }
2080 else 2094 else
2081 if (xs_startswith(cmd, "/v1/statuses/")) { /** **/ 2095 if (xs_startswith(cmd, "/v1/statuses/")) { /** **/
@@ -2188,30 +2202,30 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2188 if (out != NULL) { 2202 if (out != NULL) {
2189 *body = xs_json_dumps(out, 4); 2203 *body = xs_json_dumps(out, 4);
2190 *ctype = "application/json"; 2204 *ctype = "application/json";
2191 status = 200; 2205 status = HTTP_STATUS_OK;
2192 } 2206 }
2193 } 2207 }
2194 } 2208 }
2195 else 2209 else
2196 status = 401; 2210 status = HTTP_STATUS_UNAUTHORIZED;
2197 } 2211 }
2198 else 2212 else
2199 if (strcmp(cmd, "/v1/preferences") == 0) { /** **/ 2213 if (strcmp(cmd, "/v1/preferences") == 0) { /** **/
2200 *body = xs_dup("{}"); 2214 *body = xs_dup("{}");
2201 *ctype = "application/json"; 2215 *ctype = "application/json";
2202 status = 200; 2216 status = HTTP_STATUS_OK;
2203 } 2217 }
2204 else 2218 else
2205 if (strcmp(cmd, "/v1/markers") == 0) { /** **/ 2219 if (strcmp(cmd, "/v1/markers") == 0) { /** **/
2206 *body = xs_dup("{}"); 2220 *body = xs_dup("{}");
2207 *ctype = "application/json"; 2221 *ctype = "application/json";
2208 status = 200; 2222 status = HTTP_STATUS_OK;
2209 } 2223 }
2210 else 2224 else
2211 if (strcmp(cmd, "/v1/followed_tags") == 0) { /** **/ 2225 if (strcmp(cmd, "/v1/followed_tags") == 0) { /** **/
2212 *body = xs_dup("[]"); 2226 *body = xs_dup("[]");
2213 *ctype = "application/json"; 2227 *ctype = "application/json";
2214 status = 200; 2228 status = HTTP_STATUS_OK;
2215 } 2229 }
2216 else 2230 else
2217 if (strcmp(cmd, "/v2/search") == 0) { /** **/ 2231 if (strcmp(cmd, "/v2/search") == 0) { /** **/
@@ -2290,10 +2304,10 @@ int mastoapi_get_handler(const xs_dict *req, const char *q_path,
2290 2304
2291 *body = xs_json_dumps(res, 4); 2305 *body = xs_json_dumps(res, 4);
2292 *ctype = "application/json"; 2306 *ctype = "application/json";
2293 status = 200; 2307 status = HTTP_STATUS_OK;
2294 } 2308 }
2295 else 2309 else
2296 status = 401; 2310 status = HTTP_STATUS_UNAUTHORIZED;
2297 } 2311 }
2298 2312
2299 /* user cleanup */ 2313 /* user cleanup */
@@ -2316,7 +2330,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2316 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 2330 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
2317 return 0; 2331 return 0;
2318 2332
2319 int status = 404; 2333 int status = HTTP_STATUS_NOT_FOUND;
2320 xs *args = NULL; 2334 xs *args = NULL;
2321 const char *i_ctype = xs_dict_get(req, "content-type"); 2335 const char *i_ctype = xs_dict_get(req, "content-type");
2322 2336
@@ -2336,7 +2350,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2336 args = xs_dup(xs_dict_get(req, "p_vars")); 2350 args = xs_dup(xs_dict_get(req, "p_vars"));
2337 2351
2338 if (args == NULL) 2352 if (args == NULL)
2339 return 400; 2353 return HTTP_STATUS_BAD_REQUEST;
2340 2354
2341 xs *cmd = xs_replace_n(q_path, "/api", "", 1); 2355 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
2342 2356
@@ -2378,7 +2392,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2378 2392
2379 *body = xs_json_dumps(app, 4); 2393 *body = xs_json_dumps(app, 4);
2380 *ctype = "application/json"; 2394 *ctype = "application/json";
2381 status = 200; 2395 status = HTTP_STATUS_OK;
2382 2396
2383 app = xs_dict_append(app, "code", ""); 2397 app = xs_dict_append(app, "code", "");
2384 2398
@@ -2470,10 +2484,10 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2470 2484
2471 *body = xs_json_dumps(st, 4); 2485 *body = xs_json_dumps(st, 4);
2472 *ctype = "application/json"; 2486 *ctype = "application/json";
2473 status = 200; 2487 status = HTTP_STATUS_OK;
2474 } 2488 }
2475 else 2489 else
2476 status = 401; 2490 status = HTTP_STATUS_UNAUTHORIZED;
2477 } 2491 }
2478 else 2492 else
2479 if (xs_startswith(cmd, "/v1/statuses")) { /** **/ 2493 if (xs_startswith(cmd, "/v1/statuses")) { /** **/
@@ -2552,7 +2566,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2552 if (pin(&snac, id)) 2566 if (pin(&snac, id))
2553 out = mastoapi_status(&snac, msg); 2567 out = mastoapi_status(&snac, msg);
2554 else 2568 else
2555 status = 422; 2569 status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
2556 } 2570 }
2557 else 2571 else
2558 if (strcmp(op, "unpin") == 0) { /** **/ 2572 if (strcmp(op, "unpin") == 0) { /** **/
@@ -2573,12 +2587,12 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2573 if (out != NULL) { 2587 if (out != NULL) {
2574 *body = xs_json_dumps(out, 4); 2588 *body = xs_json_dumps(out, 4);
2575 *ctype = "application/json"; 2589 *ctype = "application/json";
2576 status = 200; 2590 status = HTTP_STATUS_OK;
2577 } 2591 }
2578 } 2592 }
2579 } 2593 }
2580 else 2594 else
2581 status = 401; 2595 status = HTTP_STATUS_UNAUTHORIZED;
2582 } 2596 }
2583 else 2597 else
2584 if (strcmp(cmd, "/v1/notifications/clear") == 0) { /** **/ 2598 if (strcmp(cmd, "/v1/notifications/clear") == 0) { /** **/
@@ -2588,10 +2602,10 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2588 2602
2589 *body = xs_dup("{}"); 2603 *body = xs_dup("{}");
2590 *ctype = "application/json"; 2604 *ctype = "application/json";
2591 status = 200; 2605 status = HTTP_STATUS_OK;
2592 } 2606 }
2593 else 2607 else
2594 status = 401; 2608 status = HTTP_STATUS_UNAUTHORIZED;
2595 } 2609 }
2596 else 2610 else
2597 if (strcmp(cmd, "/v1/push/subscription") == 0) { /** **/ 2611 if (strcmp(cmd, "/v1/push/subscription") == 0) { /** **/
@@ -2616,10 +2630,10 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2616 2630
2617 *body = xs_json_dumps(wpush, 4); 2631 *body = xs_json_dumps(wpush, 4);
2618 *ctype = "application/json"; 2632 *ctype = "application/json";
2619 status = 200; 2633 status = HTTP_STATUS_OK;
2620 } 2634 }
2621 else 2635 else
2622 status = 401; 2636 status = HTTP_STATUS_UNAUTHORIZED;
2623 } 2637 }
2624 else 2638 else
2625 if (strcmp(cmd, "/v1/media") == 0 || strcmp(cmd, "/v2/media") == 0) { /** **/ 2639 if (strcmp(cmd, "/v1/media") == 0 || strcmp(cmd, "/v2/media") == 0) { /** **/
@@ -2630,7 +2644,7 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2630 if (xs_is_null(desc)) 2644 if (xs_is_null(desc))
2631 desc = ""; 2645 desc = "";
2632 2646
2633 status = 400; 2647 status = HTTP_STATUS_BAD_REQUEST;
2634 2648
2635 if (xs_type(file) == XSTYPE_LIST) { 2649 if (xs_type(file) == XSTYPE_LIST) {
2636 const char *fn = xs_list_get(file, 0); 2650 const char *fn = xs_list_get(file, 0);
@@ -2659,12 +2673,12 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2659 2673
2660 *body = xs_json_dumps(rsp, 4); 2674 *body = xs_json_dumps(rsp, 4);
2661 *ctype = "application/json"; 2675 *ctype = "application/json";
2662 status = 200; 2676 status = HTTP_STATUS_OK;
2663 } 2677 }
2664 } 2678 }
2665 } 2679 }
2666 else 2680 else
2667 status = 401; 2681 status = HTTP_STATUS_UNAUTHORIZED;
2668 } 2682 }
2669 else 2683 else
2670 if (xs_startswith(cmd, "/v1/accounts")) { /** **/ 2684 if (xs_startswith(cmd, "/v1/accounts")) { /** **/
@@ -2744,11 +2758,11 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2744 if (rsp != NULL) { 2758 if (rsp != NULL) {
2745 *body = xs_json_dumps(rsp, 4); 2759 *body = xs_json_dumps(rsp, 4);
2746 *ctype = "application/json"; 2760 *ctype = "application/json";
2747 status = 200; 2761 status = HTTP_STATUS_OK;
2748 } 2762 }
2749 } 2763 }
2750 else 2764 else
2751 status = 401; 2765 status = HTTP_STATUS_UNAUTHORIZED;
2752 } 2766 }
2753 else 2767 else
2754 if (xs_startswith(cmd, "/v1/polls")) { /** **/ 2768 if (xs_startswith(cmd, "/v1/polls")) { /** **/
@@ -2810,12 +2824,12 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2810 if (out != NULL) { 2824 if (out != NULL) {
2811 *body = xs_json_dumps(out, 4); 2825 *body = xs_json_dumps(out, 4);
2812 *ctype = "application/json"; 2826 *ctype = "application/json";
2813 status = 200; 2827 status = HTTP_STATUS_OK;
2814 } 2828 }
2815 } 2829 }
2816 } 2830 }
2817 else 2831 else
2818 status = 401; 2832 status = HTTP_STATUS_UNAUTHORIZED;
2819 } 2833 }
2820 else 2834 else
2821 if (strcmp(cmd, "/v1/lists") == 0) { 2835 if (strcmp(cmd, "/v1/lists") == 0) {
@@ -2831,18 +2845,18 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2831 out = xs_dict_append(out, "replies_policy", xs_dict_get_def(args, "replies_policy", "list")); 2845 out = xs_dict_append(out, "replies_policy", xs_dict_get_def(args, "replies_policy", "list"));
2832 out = xs_dict_append(out, "exclusive", xs_stock(XSTYPE_FALSE)); 2846 out = xs_dict_append(out, "exclusive", xs_stock(XSTYPE_FALSE));
2833 2847
2834 status = 200; 2848 status = HTTP_STATUS_OK;
2835 } 2849 }
2836 else { 2850 else {
2837 out = xs_dict_append(out, "error", "cannot create list"); 2851 out = xs_dict_append(out, "error", "cannot create list");
2838 status = 422; 2852 status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
2839 } 2853 }
2840 2854
2841 *body = xs_json_dumps(out, 4); 2855 *body = xs_json_dumps(out, 4);
2842 *ctype = "application/json"; 2856 *ctype = "application/json";
2843 } 2857 }
2844 else 2858 else
2845 status = 422; 2859 status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
2846 } 2860 }
2847 } 2861 }
2848 if (xs_startswith(cmd, "/v1/lists/")) { /** list maintenance **/ 2862 if (xs_startswith(cmd, "/v1/lists/")) { /** list maintenance **/
@@ -2861,12 +2875,12 @@ int mastoapi_post_handler(const xs_dict *req, const char *q_path,
2861 list_content(&snac, id, v, 1); 2875 list_content(&snac, id, v, 1);
2862 } 2876 }
2863 2877
2864 status = 200; 2878 status = HTTP_STATUS_OK;
2865 } 2879 }
2866 } 2880 }
2867 } 2881 }
2868 else 2882 else
2869 status = 422; 2883 status = HTTP_STATUS_UNPROCESSABLE_CONTENT;
2870 } 2884 }
2871 2885
2872 /* user cleanup */ 2886 /* user cleanup */
@@ -2891,7 +2905,7 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
2891 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 2905 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
2892 return 0; 2906 return 0;
2893 2907
2894 int status = 404; 2908 int status = HTTP_STATUS_NOT_FOUND;
2895 xs *args = NULL; 2909 xs *args = NULL;
2896 const char *i_ctype = xs_dict_get(req, "content-type"); 2910 const char *i_ctype = xs_dict_get(req, "content-type");
2897 2911
@@ -2911,7 +2925,7 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
2911 args = xs_dup(xs_dict_get(req, "p_vars")); 2925 args = xs_dup(xs_dict_get(req, "p_vars"));
2912 2926
2913 if (args == NULL) 2927 if (args == NULL)
2914 return 400; 2928 return HTTP_STATUS_BAD_REQUEST;
2915 2929
2916 snac snac = {0}; 2930 snac snac = {0};
2917 int logged_in = process_auth_token(&snac, req); 2931 int logged_in = process_auth_token(&snac, req);
@@ -2920,7 +2934,7 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
2920 2934
2921 if (xs_startswith(cmd, "/v1/push/subscription") || xs_startswith(cmd, "/v2/push/subscription")) { /** **/ 2935 if (xs_startswith(cmd, "/v1/push/subscription") || xs_startswith(cmd, "/v2/push/subscription")) { /** **/
2922 // pretend we deleted it, since it doesn't exist anyway 2936 // pretend we deleted it, since it doesn't exist anyway
2923 status = 200; 2937 status = HTTP_STATUS_OK;
2924 } 2938 }
2925 else 2939 else
2926 if (xs_startswith(cmd, "/v1/lists/")) { 2940 if (xs_startswith(cmd, "/v1/lists/")) {
@@ -2948,10 +2962,10 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
2948 } 2962 }
2949 } 2963 }
2950 2964
2951 status = 200; 2965 status = HTTP_STATUS_OK;
2952 } 2966 }
2953 else 2967 else
2954 status = 401; 2968 status = HTTP_STATUS_UNAUTHORIZED;
2955 } 2969 }
2956 2970
2957 /* user cleanup */ 2971 /* user cleanup */
@@ -2974,7 +2988,7 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
2974 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/")) 2988 if (!xs_startswith(q_path, "/api/v1/") && !xs_startswith(q_path, "/api/v2/"))
2975 return 0; 2989 return 0;
2976 2990
2977 int status = 404; 2991 int status = HTTP_STATUS_NOT_FOUND;
2978 xs *args = NULL; 2992 xs *args = NULL;
2979 const char *i_ctype = xs_dict_get(req, "content-type"); 2993 const char *i_ctype = xs_dict_get(req, "content-type");
2980 2994
@@ -2986,7 +3000,7 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
2986 args = xs_dup(xs_dict_get(req, "p_vars")); 3000 args = xs_dup(xs_dict_get(req, "p_vars"));
2987 3001
2988 if (args == NULL) 3002 if (args == NULL)
2989 return 400; 3003 return HTTP_STATUS_BAD_REQUEST;
2990 3004
2991 xs *cmd = xs_replace_n(q_path, "/api", "", 1); 3005 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
2992 3006
@@ -3017,11 +3031,11 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
3017 3031
3018 *body = xs_json_dumps(rsp, 4); 3032 *body = xs_json_dumps(rsp, 4);
3019 *ctype = "application/json"; 3033 *ctype = "application/json";
3020 status = 200; 3034 status = HTTP_STATUS_OK;
3021 } 3035 }
3022 } 3036 }
3023 else 3037 else
3024 status = 401; 3038 status = HTTP_STATUS_UNAUTHORIZED;
3025 } 3039 }
3026 else 3040 else
3027 if (xs_startswith(cmd, "/v1/statuses")) { 3041 if (xs_startswith(cmd, "/v1/statuses")) {
@@ -3060,12 +3074,12 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
3060 if (rsp != NULL) { 3074 if (rsp != NULL) {
3061 *body = xs_json_dumps(rsp, 4); 3075 *body = xs_json_dumps(rsp, 4);
3062 *ctype = "application/json"; 3076 *ctype = "application/json";
3063 status = 200; 3077 status = HTTP_STATUS_OK;
3064 } 3078 }
3065 } 3079 }
3066 } 3080 }
3067 else 3081 else
3068 status = 401; 3082 status = HTTP_STATUS_UNAUTHORIZED;
3069 } 3083 }
3070 3084
3071 /* user cleanup */ 3085 /* user cleanup */
@@ -3077,6 +3091,148 @@ int mastoapi_put_handler(const xs_dict *req, const char *q_path,
3077 return status; 3091 return status;
3078} 3092}
3079 3093
3094void persist_image(const char *key, const xs_val *data, const char *payload, snac *snac)
3095/* Store header or avatar */
3096{
3097 if (data != NULL) {
3098 if (xs_type(data) == XSTYPE_LIST) {
3099 const char *fn = xs_list_get(data, 0);
3100
3101 if (fn && *fn) {
3102 const char *ext = strrchr(fn, '.');
3103 /* Mona iOS sends JPG file as application/octet-stream with filename "header"
3104 * Make sure we have a unique file name, otherwise updated images will not be
3105 * loaded by clients.
3106 */
3107 if (ext == NULL || strcmp(fn, key) == 0) {
3108 fn = random_str();
3109 ext = ".jpg";
3110 }
3111 xs *hash = xs_md5_hex(fn, strlen(fn));
3112 xs *id = xs_fmt("%s%s", hash, ext);
3113 xs *url = xs_fmt("%s/s/%s", snac->actor, id);
3114 int fo = xs_number_get(xs_list_get(data, 1));
3115 int fs = xs_number_get(xs_list_get(data, 2));
3116
3117 /* store */
3118 static_put(snac, id, payload + fo, fs);
3119
3120 snac->config = xs_dict_set(snac->config, key, url);
3121 }
3122 }
3123 }
3124}
3125
3126int mastoapi_patch_handler(const xs_dict *req, const char *q_path,
3127 const char *payload, int p_size,
3128 char **body, int *b_size, char **ctype)
3129/* Handle profile updates */
3130{
3131 (void)p_size;
3132 (void)b_size;
3133
3134 if (!xs_startswith(q_path, "/api/v1/"))
3135 return 0;
3136
3137 int status = HTTP_STATUS_NOT_FOUND;
3138 xs *args = NULL;
3139 const char *i_ctype = xs_dict_get(req, "content-type");
3140
3141 if (i_ctype && xs_startswith(i_ctype, "application/json")) {
3142 if (!xs_is_null(payload))
3143 args = xs_json_loads(payload);
3144 }
3145 else
3146 args = xs_dup(xs_dict_get(req, "p_vars"));
3147
3148 if (args == NULL)
3149 return HTTP_STATUS_BAD_REQUEST;
3150
3151 xs *cmd = xs_replace_n(q_path, "/api", "", 1);
3152
3153 snac snac = {0};
3154 int logged_in = process_auth_token(&snac, req);
3155
3156 if (xs_startswith(cmd, "/v1/accounts/update_credentials")) {
3157 /* Update user profile fields */
3158 if (logged_in) {
3159 /*
3160 xs_str *dump = xs_json_dumps(args, 4);
3161 printf("%s\n\n", dump);
3162 */
3163 int c = 0;
3164 const xs_str *k;
3165 const xs_val *v;
3166 const xs_str *field_name = NULL;
3167 xs_dict *new_fields = xs_dict_new();
3168 while (xs_dict_next(args, &k, &v, &c)) {
3169 if (strcmp(k, "display_name") == 0) {
3170 if (v != NULL)
3171 snac.config = xs_dict_set(snac.config, "name", v);
3172 }
3173 else
3174 if (strcmp(k, "note") == 0) {
3175 if (v != NULL)
3176 snac.config = xs_dict_set(snac.config, "bio", v);
3177 }
3178 else
3179 if (strcmp(k, "bot") == 0) {
3180 if (v != NULL)
3181 snac.config = xs_dict_set(snac.config, "bot",
3182 strcmp(v, "true") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE));
3183 }
3184 else
3185 if (strcmp(k, "source[sensitive]") == 0) {
3186 if (v != NULL)
3187 snac.config = xs_dict_set(snac.config, "cw",
3188 strcmp(v, "true") == 0 ? "open" : "");
3189 }
3190 else
3191 if (strcmp(k, "source[privacy]") == 0) {
3192 if (v != NULL)
3193 snac.config = xs_dict_set(snac.config, "private",
3194 strcmp(v, "private") == 0 ? xs_stock(XSTYPE_TRUE) : xs_stock(XSTYPE_FALSE));
3195 }
3196 else
3197 if (strcmp(k, "header") == 0) {
3198 persist_image("header", v, payload, &snac);
3199 }
3200 else
3201 if (strcmp(k, "avatar") == 0) {
3202 persist_image("avatar", v, payload, &snac);
3203 }
3204 else
3205 if (xs_starts_and_ends("fields_attributes", k, "[name]")) {
3206 field_name = strcmp(v, "") != 0 ? v : NULL;
3207 }
3208 else
3209 if (xs_starts_and_ends("fields_attributes", k, "[value]")) {
3210 if (field_name != NULL) {
3211 new_fields = xs_dict_set(new_fields, field_name, v);
3212 snac.config = xs_dict_set(snac.config, "metadata", new_fields);
3213 }
3214 }
3215 }
3216
3217 /* Persist profile */
3218 if (user_persist(&snac) == 0)
3219 credentials_get(body, ctype, &status, snac);
3220 else
3221 status = HTTP_STATUS_INTERNAL_SERVER_ERROR;
3222 }
3223 else
3224 status = HTTP_STATUS_UNAUTHORIZED;
3225 }
3226
3227 /* user cleanup */
3228 if (logged_in)
3229 user_free(&snac);
3230
3231 srv_debug(1, xs_fmt("mastoapi_patch_handler %s %d", q_path, status));
3232
3233 return status;
3234}
3235
3080 3236
3081void mastoapi_purge(void) 3237void mastoapi_purge(void)
3082{ 3238{
diff --git a/snac.c b/snac.c
index 5ba98e1..0df8691 100644
--- a/snac.c
+++ b/snac.c
@@ -170,3 +170,15 @@ int check_password(const char *uid, const char *passwd, const char *hash)
170 170
171 return ret; 171 return ret;
172} 172}
173
174
175const char *http_status_text(int status)
176/* translate status codes to canonical status texts */
177{
178 switch (status) {
179#define HTTP_STATUS(code, name, text) case HTTP_STATUS_ ## name: return #text;
180#include "http_codes.h"
181#undef HTTP_STATUS
182 default: return "Unknown";
183 }
184}
diff --git a/snac.h b/snac.h
index 0f1dfff..2561b6c 100644
--- a/snac.h
+++ b/snac.h
@@ -76,6 +76,7 @@ int user_open(snac *snac, const char *uid);
76void user_free(snac *snac); 76void user_free(snac *snac);
77xs_list *user_list(void); 77xs_list *user_list(void);
78int user_open_by_md5(snac *snac, const char *md5); 78int user_open_by_md5(snac *snac, const char *md5);
79int user_persist(snac *snac);
79 80
80int validate_uid(const char *uid); 81int validate_uid(const char *uid);
81 82
@@ -358,6 +359,19 @@ int mastoapi_delete_handler(const xs_dict *req, const char *q_path,
358int mastoapi_put_handler(const xs_dict *req, const char *q_path, 359int mastoapi_put_handler(const xs_dict *req, const char *q_path,
359 const char *payload, int p_size, 360 const char *payload, int p_size,
360 char **body, int *b_size, char **ctype); 361 char **body, int *b_size, char **ctype);
362void persist_image(const char *key, const xs_val *data, const char *payload, snac *snac);
363int mastoapi_patch_handler(const xs_dict *req, const char *q_path,
364 const char *payload, int p_size,
365 char **body, int *b_size, char **ctype);
361void mastoapi_purge(void); 366void mastoapi_purge(void);
362 367
363void verify_links(snac *user); 368void verify_links(snac *user);
369
370
371typedef enum {
372#define HTTP_STATUS(code, name, text) HTTP_STATUS_ ## name = code,
373#include "http_codes.h"
374#undef HTTP_STATUS
375} http_status;
376
377const char *http_status_text(int status);
diff --git a/webfinger.c b/webfinger.c
index c79fd44..0c18362 100644
--- a/webfinger.c
+++ b/webfinger.c
@@ -42,7 +42,7 @@ int webfinger_request_signed(snac *snac, const char *qs, char **actor, char **us
42 } 42 }
43 43
44 if (host == NULL || resource == NULL) 44 if (host == NULL || resource == NULL)
45 return 400; 45 return HTTP_STATUS_BAD_REQUEST;
46 46
47 headers = xs_dict_append(headers, "accept", "application/json"); 47 headers = xs_dict_append(headers, "accept", "application/json");
48 headers = xs_dict_append(headers, "user-agent", USER_AGENT); 48 headers = xs_dict_append(headers, "user-agent", USER_AGENT);
@@ -139,7 +139,7 @@ int webfinger_get_handler(xs_dict *req, char *q_path,
139 const char *resource = xs_dict_get(q_vars, "resource"); 139 const char *resource = xs_dict_get(q_vars, "resource");
140 140
141 if (resource == NULL) 141 if (resource == NULL)
142 return 400; 142 return HTTP_STATUS_BAD_REQUEST;
143 143
144 snac snac; 144 snac snac;
145 int found = 0; 145 int found = 0;
@@ -220,12 +220,12 @@ int webfinger_get_handler(xs_dict *req, char *q_path,
220 220
221 user_free(&snac); 221 user_free(&snac);
222 222
223 status = 200; 223 status = HTTP_STATUS_OK;
224 *body = j; 224 *body = j;
225 *ctype = "application/jrd+json"; 225 *ctype = "application/jrd+json";
226 } 226 }
227 else 227 else
228 status = 404; 228 status = HTTP_STATUS_NOT_FOUND;
229 229
230 srv_debug(1, xs_fmt("webfinger_get_handler resource=%s %d", resource, status)); 230 srv_debug(1, xs_fmt("webfinger_get_handler resource=%s %d", resource, status));
231 231
diff --git a/xs.h b/xs.h
index 8e04c99..4da0d6f 100644
--- a/xs.h
+++ b/xs.h
@@ -52,7 +52,7 @@ typedef char xs_data;
52 52
53void *xs_free(void *ptr); 53void *xs_free(void *ptr);
54void *_xs_realloc(void *ptr, size_t size, const char *file, int line, const char *func); 54void *_xs_realloc(void *ptr, size_t size, const char *file, int line, const char *func);
55#define xs_realloc(ptr, size) _xs_realloc(ptr, size, __FILE__, __LINE__, __FUNCTION__) 55#define xs_realloc(ptr, size) _xs_realloc(ptr, size, __FILE__, __LINE__, __func__)
56int _xs_blk_size(int sz); 56int _xs_blk_size(int sz);
57void _xs_destroy(char **var); 57void _xs_destroy(char **var);
58#define xs_debug() raise(SIGTRAP) 58#define xs_debug() raise(SIGTRAP)
diff --git a/xs_httpd.h b/xs_httpd.h
index 4195b81..d080b39 100644
--- a/xs_httpd.h
+++ b/xs_httpd.h
@@ -5,7 +5,7 @@
5#define _XS_HTTPD_H 5#define _XS_HTTPD_H
6 6
7xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size); 7xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size);
8void xs_httpd_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b_size); 8void xs_httpd_response(FILE *f, int status, const char *status_text, xs_dict *headers, xs_str *body, int b_size);
9 9
10 10
11#ifdef XS_IMPLEMENTATION 11#ifdef XS_IMPLEMENTATION
@@ -95,14 +95,14 @@ xs_dict *xs_httpd_request(FILE *f, xs_str **payload, int *p_size)
95} 95}
96 96
97 97
98void xs_httpd_response(FILE *f, int status, xs_dict *headers, xs_str *body, int b_size) 98void xs_httpd_response(FILE *f, int status, const char *status_text, xs_dict *headers, xs_str *body, int b_size)
99/* sends an httpd response */ 99/* sends an httpd response */
100{ 100{
101 xs *proto; 101 xs *proto;
102 const xs_str *k; 102 const xs_str *k;
103 const xs_val *v; 103 const xs_val *v;
104 104
105 proto = xs_fmt("HTTP/1.1 %d %s", status, status / 100 == 2 ? "OK" : "ERROR"); 105 proto = xs_fmt("HTTP/1.1 %d %s", status, status_text);
106 fprintf(f, "%s\r\n", proto); 106 fprintf(f, "%s\r\n", proto);
107 107
108 int c = 0; 108 int c = 0;