summaryrefslogtreecommitdiff
path: root/html.c
diff options
context:
space:
mode:
Diffstat (limited to 'html.c')
-rw-r--r--html.c224
1 files changed, 94 insertions, 130 deletions
diff --git a/html.c b/html.c
index de2fdce..7d748f9 100644
--- a/html.c
+++ b/html.c
@@ -636,13 +636,18 @@ static xs_html *html_base_head(void)
636 xs_html_attr("content", USER_AGENT))); 636 xs_html_attr("content", USER_AGENT)));
637 637
638 /* add server CSS and favicon */ 638 /* add server CSS and favicon */
639 xs *f; 639 xs *f = NULL;
640 f = xs_fmt("%s/favicon.ico", srv_baseurl); 640 const char *favicon = xs_dict_get(srv_config, "favicon_url");
641
642 if (xs_is_string(favicon))
643 f = xs_dup(favicon);
644 else
645 f = xs_fmt("%s/favicon.ico", srv_baseurl);
646
641 const xs_list *p = xs_dict_get(srv_config, "cssurls"); 647 const xs_list *p = xs_dict_get(srv_config, "cssurls");
642 const char *v; 648 const char *v;
643 int c = 0;
644 649
645 while (xs_list_next(p, &v, &c)) { 650 xs_list_foreach(p, v) {
646 xs_html_add(head, 651 xs_html_add(head,
647 xs_html_sctag("link", 652 xs_html_sctag("link",
648 xs_html_attr("rel", "stylesheet"), 653 xs_html_attr("rel", "stylesheet"),
@@ -863,6 +868,14 @@ xs_html *html_user_head(snac *user, const char *desc, const char *url)
863 xs_html_attr("type", "application/activity+json"), 868 xs_html_attr("type", "application/activity+json"),
864 xs_html_attr("href", url ? url : user->actor))); 869 xs_html_attr("href", url ? url : user->actor)));
865 870
871 /* webmention hook */
872 xs *wbh = xs_fmt("%s/webmention-hook", srv_baseurl);
873
874 xs_html_add(head,
875 xs_html_sctag("link",
876 xs_html_attr("rel", "webmention"),
877 xs_html_attr("href", wbh)));
878
866 return head; 879 return head;
867} 880}
868 881
@@ -1077,10 +1090,17 @@ static xs_html *html_user_body(snac *user, int read_only)
1077 while (xs_dict_next(metadata, &k, &v, &c)) { 1090 while (xs_dict_next(metadata, &k, &v, &c)) {
1078 xs_html *value; 1091 xs_html *value;
1079 1092
1080 if (xs_startswith(v, "https:/") || xs_startswith(v, "http:/")) { 1093 if (xs_startswith(v, "https:/") || xs_startswith(v, "http:/") || *v == '@') {
1081 /* is this link validated? */ 1094 /* is this link validated? */
1082 xs *verified_link = NULL; 1095 xs *verified_link = NULL;
1083 const xs_number *val_time = xs_dict_get(val_links, v); 1096 const xs_number *val_time = xs_dict_get(val_links, v);
1097 const char *url = NULL;
1098
1099 if (xs_is_string(val_time)) {
1100 /* resolve again, as it may be an account handle */
1101 url = val_time;
1102 val_time = xs_dict_get(val_links, val_time);
1103 }
1084 1104
1085 if (xs_type(val_time) == XSTYPE_NUMBER) { 1105 if (xs_type(val_time) == XSTYPE_NUMBER) {
1086 time_t t = xs_number_get(val_time); 1106 time_t t = xs_number_get(val_time);
@@ -1098,13 +1118,13 @@ static xs_html *html_user_body(snac *user, int read_only)
1098 xs_html_tag("a", 1118 xs_html_tag("a",
1099 xs_html_attr("rel", "me"), 1119 xs_html_attr("rel", "me"),
1100 xs_html_attr("target", "_blank"), 1120 xs_html_attr("target", "_blank"),
1101 xs_html_attr("href", v), 1121 xs_html_attr("href", url ? url : v),
1102 xs_html_text(v))); 1122 xs_html_text(v)));
1103 } 1123 }
1104 else { 1124 else {
1105 value = xs_html_tag("a", 1125 value = xs_html_tag("a",
1106 xs_html_attr("rel", "me"), 1126 xs_html_attr("rel", "me"),
1107 xs_html_attr("href", v), 1127 xs_html_attr("href", url ? url : v),
1108 xs_html_text(v)); 1128 xs_html_text(v));
1109 } 1129 }
1110 } 1130 }
@@ -1290,6 +1310,7 @@ xs_html *html_top_controls(snac *user)
1290 const xs_val *show_foll = xs_dict_get(user->config, "show_contact_metrics"); 1310 const xs_val *show_foll = xs_dict_get(user->config, "show_contact_metrics");
1291 const char *latitude = xs_dict_get_def(user->config, "latitude", ""); 1311 const char *latitude = xs_dict_get_def(user->config, "latitude", "");
1292 const char *longitude = xs_dict_get_def(user->config, "longitude", ""); 1312 const char *longitude = xs_dict_get_def(user->config, "longitude", "");
1313 const char *webhook = xs_dict_get_def(user->config, "notify_webhook", "");
1293 1314
1294 xs *metadata = NULL; 1315 xs *metadata = NULL;
1295 const xs_dict *md = xs_dict_get(user->config, "metadata"); 1316 const xs_dict *md = xs_dict_get(user->config, "metadata");
@@ -1452,6 +1473,14 @@ xs_html *html_top_controls(snac *user)
1452 xs_html_attr("value", ntfy_token), 1473 xs_html_attr("value", ntfy_token),
1453 xs_html_attr("placeholder", L("ntfy token - if needed")))), 1474 xs_html_attr("placeholder", L("ntfy token - if needed")))),
1454 xs_html_tag("p", 1475 xs_html_tag("p",
1476 xs_html_text(L("Notify webhook:")),
1477 xs_html_sctag("br", NULL),
1478 xs_html_sctag("input",
1479 xs_html_attr("type", "url"),
1480 xs_html_attr("name", "notify_webhook"),
1481 xs_html_attr("value", webhook),
1482 xs_html_attr("placeholder", L("http://example.com/webhook")))),
1483 xs_html_tag("p",
1455 xs_html_text(L("Maximum days to keep posts (0: server settings):")), 1484 xs_html_text(L("Maximum days to keep posts (0: server settings):")),
1456 xs_html_sctag("br", NULL), 1485 xs_html_sctag("br", NULL),
1457 xs_html_sctag("input", 1486 xs_html_sctag("input",
@@ -1601,7 +1630,8 @@ xs_html *html_top_controls(snac *user)
1601 xs_html_attr("name", "followed_hashtags"), 1630 xs_html_attr("name", "followed_hashtags"),
1602 xs_html_attr("cols", "40"), 1631 xs_html_attr("cols", "40"),
1603 xs_html_attr("rows", "4"), 1632 xs_html_attr("rows", "4"),
1604 xs_html_attr("placeholder", "#cats\n#windowfriday\n#classicalmusic"), 1633 xs_html_attr("placeholder", "#cats\n#windowfriday\n#classicalmusic\nhttps:/"
1634 "/mastodon.social/tags/dogs"),
1605 xs_html_text(followed_hashtags)), 1635 xs_html_text(followed_hashtags)),
1606 1636
1607 xs_html_tag("br", NULL), 1637 xs_html_tag("br", NULL),
@@ -1978,8 +2008,13 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1978 } 2008 }
1979 2009
1980 if ((user == NULL || strcmp(actor, user->actor) != 0) 2010 if ((user == NULL || strcmp(actor, user->actor) != 0)
1981 && !valid_status(actor_get(actor, NULL))) 2011 && !valid_status(actor_get(actor, NULL))) {
2012
2013 if (user)
2014 enqueue_actor_refresh(user, actor, 0);
2015
1982 return NULL; 2016 return NULL;
2017 }
1983 2018
1984 /** html_entry top tag **/ 2019 /** html_entry top tag **/
1985 xs_html *entry_top = xs_html_tag("div", NULL); 2020 xs_html *entry_top = xs_html_tag("div", NULL);
@@ -2115,9 +2150,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2115 const char *parent = get_in_reply_to(msg); 2150 const char *parent = get_in_reply_to(msg);
2116 2151
2117 if (!xs_is_null(parent) && *parent) { 2152 if (!xs_is_null(parent) && *parent) {
2118 xs *md5 = xs_md5_hex(parent, strlen(parent)); 2153 if (!timeline_here(user, parent)) {
2119
2120 if (!timeline_here(user, md5)) {
2121 xs_html_add(post_header, 2154 xs_html_add(post_header,
2122 xs_html_tag("div", 2155 xs_html_tag("div",
2123 xs_html_attr("class", "snac-origin"), 2156 xs_html_attr("class", "snac-origin"),
@@ -2419,6 +2452,9 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2419 const char *o_href = xs_dict_get(a, "href"); 2452 const char *o_href = xs_dict_get(a, "href");
2420 const char *name = xs_dict_get(a, "name"); 2453 const char *name = xs_dict_get(a, "name");
2421 2454
2455 if (!xs_is_string(type) || !xs_is_string(o_href))
2456 continue;
2457
2422 /* if this URL is already in the post content, skip */ 2458 /* if this URL is already in the post content, skip */
2423 if (content && xs_str_in(content, o_href) != -1) 2459 if (content && xs_str_in(content, o_href) != -1)
2424 continue; 2460 continue;
@@ -3284,12 +3320,12 @@ xs_str *html_people(snac *user)
3284 3320
3285 xs *wing = following_list(user); 3321 xs *wing = following_list(user);
3286 xs *wers = follower_list(user); 3322 xs *wers = follower_list(user);
3323 xs *pending = pending_list(user);
3287 3324
3288 xs_html *lists = xs_html_tag("div", 3325 xs_html *lists = xs_html_tag("div",
3289 xs_html_attr("class", "snac-posts")); 3326 xs_html_attr("class", "snac-posts"));
3290 3327
3291 if (xs_is_true(xs_dict_get(user->config, "approve_followers"))) { 3328 if (xs_list_len(pending) || xs_is_true(xs_dict_get(user->config, "approve_followers"))) {
3292 xs *pending = pending_list(user);
3293 xs_html_add(lists, 3329 xs_html_add(lists,
3294 html_people_list(user, pending, L("Pending follow confirmations"), "p", proxy)); 3330 html_people_list(user, pending, L("Pending follow confirmations"), "p", proxy));
3295 } 3331 }
@@ -3384,13 +3420,15 @@ xs_str *html_notifications(snac *user, int skip, int show)
3384 3420
3385 const char *actor_id = xs_dict_get(noti, "actor"); 3421 const char *actor_id = xs_dict_get(noti, "actor");
3386 xs *actor = NULL; 3422 xs *actor = NULL;
3423 xs *a_name = NULL;
3387 3424
3388 if (!valid_status(actor_get(actor_id, &actor))) 3425 if (valid_status(actor_get(actor_id, &actor)))
3389 continue; 3426 a_name = actor_name(actor, proxy);
3427 else
3428 a_name = xs_dup(actor_id);
3390 3429
3391 xs *a_name = actor_name(actor, proxy); 3430 xs *label_sanitized = sanitize(type);
3392 xs *label_sanatized = sanitize(type); 3431 const char *label = label_sanitized;
3393 const char *label = label_sanatized;
3394 3432
3395 if (strcmp(type, "Create") == 0) 3433 if (strcmp(type, "Create") == 0)
3396 label = L("Mention"); 3434 label = L("Mention");
@@ -3462,10 +3500,11 @@ xs_str *html_notifications(snac *user, int skip, int show)
3462 html_label); 3500 html_label);
3463 3501
3464 if (strcmp(type, "Follow") == 0 || strcmp(utype, "Follow") == 0 || strcmp(type, "Block") == 0) { 3502 if (strcmp(type, "Follow") == 0 || strcmp(utype, "Follow") == 0 || strcmp(type, "Block") == 0) {
3465 xs_html_add(entry, 3503 if (actor)
3466 xs_html_tag("div", 3504 xs_html_add(entry,
3467 xs_html_attr("class", "snac-post"), 3505 xs_html_tag("div",
3468 html_actor_icon(user, actor, NULL, NULL, NULL, 0, 0, proxy, NULL, NULL))); 3506 xs_html_attr("class", "snac-post"),
3507 html_actor_icon(user, actor, NULL, NULL, NULL, 0, 0, proxy, NULL, NULL)));
3469 } 3508 }
3470 else 3509 else
3471 if (strcmp(type, "Move") == 0) { 3510 if (strcmp(type, "Move") == 0) {
@@ -3498,6 +3537,13 @@ xs_str *html_notifications(snac *user, int skip, int show)
3498 xs_html_text(L("Context")))), 3537 xs_html_text(L("Context")))),
3499 h); 3538 h);
3500 } 3539 }
3540 else
3541 xs_html_add(entry,
3542 xs_html_tag("p",
3543 xs_html_text(L("Location: ")),
3544 xs_html_tag("a",
3545 xs_html_attr("href", id),
3546 xs_html_text(id))));
3501 } 3547 }
3502 3548
3503 if (strcmp(v, n_time) > 0) { 3549 if (strcmp(v, n_time) > 0) {
@@ -3748,7 +3794,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
3748 /* may by an actor; try a webfinger */ 3794 /* may by an actor; try a webfinger */
3749 xs *actor_obj = NULL; 3795 xs *actor_obj = NULL;
3750 3796
3751 if (valid_status(webfinger_request(q, &actor_obj, &url_acct))) { 3797 if (valid_status(webfinger_request(q, &actor_obj, &url_acct)) && xs_is_string(url_acct)) {
3752 /* it's an actor; do the dirty trick of changing q to the account name */ 3798 /* it's an actor; do the dirty trick of changing q to the account name */
3753 q = url_acct; 3799 q = url_acct;
3754 } 3800 }
@@ -3773,12 +3819,18 @@ int html_get_handler(const xs_dict *req, const char *q_path,
3773 q = url_acct; 3819 q = url_acct;
3774 3820
3775 /* add the post to the timeline */ 3821 /* add the post to the timeline */
3776 xs *md5 = xs_md5_hex(q, strlen(q)); 3822 if (!timeline_here(&snac, q))
3777
3778 if (!timeline_here(&snac, md5))
3779 timeline_add(&snac, q, object); 3823 timeline_add(&snac, q, object);
3780 } 3824 }
3781 } 3825 }
3826 else {
3827 /* retry webfinger, this time with the 'official' id */
3828 const char *id = xs_dict_get(object, "id");
3829
3830 if (xs_is_string(id) && valid_status(webfinger_request(id, &actor_obj, &url_acct)) &&
3831 xs_is_string(url_acct))
3832 q = url_acct;
3833 }
3782 } 3834 }
3783 } 3835 }
3784 3836
@@ -3917,7 +3969,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
3917 xs *l = xs_split(p_path, "/"); 3969 xs *l = xs_split(p_path, "/");
3918 const char *md5 = xs_list_get(l, -1); 3970 const char *md5 = xs_list_get(l, -1);
3919 3971
3920 if (md5 && *md5 && timeline_here(&snac, md5)) { 3972 if (md5 && *md5 && timeline_here_by_md5(&snac, md5)) {
3921 xs *list0 = xs_list_append(xs_list_new(), md5); 3973 xs *list0 = xs_list_append(xs_list_new(), md5);
3922 xs *list = timeline_top_level(&snac, list0); 3974 xs *list = timeline_top_level(&snac, list0);
3923 3975
@@ -4127,7 +4179,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
4127 xs_dict_get(srv_config, "host")); 4179 xs_dict_get(srv_config, "host"));
4128 xs *rss_link = xs_fmt("%s.rss", snac.actor); 4180 xs *rss_link = xs_fmt("%s.rss", snac.actor);
4129 4181
4130 *body = timeline_to_rss(&snac, elems, rss_title, rss_link, bio); 4182 *body = rss_from_timeline(&snac, elems, rss_title, rss_link, bio);
4131 *b_size = strlen(*body); 4183 *b_size = strlen(*body);
4132 *ctype = "application/rss+xml; charset=utf-8"; 4184 *ctype = "application/rss+xml; charset=utf-8";
4133 status = HTTP_STATUS_OK; 4185 status = HTTP_STATUS_OK;
@@ -4365,7 +4417,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
4365 xs_rnd_buf(rnd, sizeof(rnd)); 4417 xs_rnd_buf(rnd, sizeof(rnd));
4366 4418
4367 const char *ext = strrchr(fn, '.'); 4419 const char *ext = strrchr(fn, '.');
4368 xs *hash = xs_md5_hex(rnd, strlen(rnd)); 4420 xs *hash = xs_md5_hex(rnd, sizeof(rnd));
4369 xs *id = xs_fmt("post-%s%s", hash, ext ? ext : ""); 4421 xs *id = xs_fmt("post-%s%s", hash, ext ? ext : "");
4370 xs *url = xs_fmt("%s/s/%s", snac.actor, id); 4422 xs *url = xs_fmt("%s/s/%s", snac.actor, id);
4371 int fo = xs_number_get(xs_list_get(attach_file, 1)); 4423 int fo = xs_number_get(xs_list_get(attach_file, 1));
@@ -4825,6 +4877,8 @@ int html_post_handler(const xs_dict *req, const char *q_path,
4825 snac.config = xs_dict_set(snac.config, "latitude", xs_dict_get_def(p_vars, "latitude", "")); 4877 snac.config = xs_dict_set(snac.config, "latitude", xs_dict_get_def(p_vars, "latitude", ""));
4826 snac.config = xs_dict_set(snac.config, "longitude", xs_dict_get_def(p_vars, "longitude", "")); 4878 snac.config = xs_dict_set(snac.config, "longitude", xs_dict_get_def(p_vars, "longitude", ""));
4827 4879
4880 snac.config = xs_dict_set(snac.config, "notify_webhook", xs_dict_get_def(p_vars, "notify_webhook", ""));
4881
4828 if ((v = xs_dict_get(p_vars, "metadata")) != NULL) 4882 if ((v = xs_dict_get(p_vars, "metadata")) != NULL)
4829 snac.config = xs_dict_set(snac.config, "metadata", v); 4883 snac.config = xs_dict_set(snac.config, "metadata", v);
4830 4884
@@ -4960,9 +5014,16 @@ int html_post_handler(const xs_dict *req, const char *q_path,
4960 if (*s1 == '\0') 5014 if (*s1 == '\0')
4961 continue; 5015 continue;
4962 5016
4963 xs *s2 = xs_utf8_to_lower(s1); 5017 xs *s2 = NULL;
4964 if (*s2 != '#') 5018
4965 s2 = xs_str_prepend_i(s2, "#"); 5019 if (xs_startswith(s1, "https:/"))
5020 s2 = xs_dup(s1);
5021 else {
5022 s2 = xs_utf8_to_lower(s1);
5023
5024 if (*s2 != '#')
5025 s2 = xs_str_prepend_i(s2, "#");
5026 }
4966 5027
4967 new_hashtags = xs_list_append(new_hashtags, s2); 5028 new_hashtags = xs_list_append(new_hashtags, s2);
4968 } 5029 }
@@ -5024,100 +5085,3 @@ int html_post_handler(const xs_dict *req, const char *q_path,
5024 5085
5025 return status; 5086 return status;
5026} 5087}
5027
5028
5029xs_str *timeline_to_rss(snac *user, const xs_list *timeline,
5030 const char *title, const char *link, const char *desc)
5031/* converts a timeline to rss */
5032{
5033 xs_html *rss = xs_html_tag("rss",
5034 xs_html_attr("xmlns:content", "http:/" "/purl.org/rss/1.0/modules/content/"),
5035 xs_html_attr("version", "2.0"),
5036 xs_html_attr("xmlns:atom", "http:/" "/www.w3.org/2005/Atom"));
5037
5038 xs_html *channel = xs_html_tag("channel",
5039 xs_html_tag("title",
5040 xs_html_text(title)),
5041 xs_html_tag("language",
5042 xs_html_text("en")),
5043 xs_html_tag("link",
5044 xs_html_text(link)),
5045 xs_html_sctag("atom:link",
5046 xs_html_attr("href", link),
5047 xs_html_attr("rel", "self"),
5048 xs_html_attr("type", "application/rss+xml")),
5049 xs_html_tag("generator",
5050 xs_html_text(USER_AGENT)),
5051 xs_html_tag("description",
5052 xs_html_text(desc)));
5053
5054 xs_html_add(rss, channel);
5055
5056 int cnt = 0;
5057 const char *v;
5058
5059 xs_list_foreach(timeline, v) {
5060 xs *msg = NULL;
5061
5062 if (user) {
5063 if (!valid_status(timeline_get_by_md5(user, v, &msg)))
5064 continue;
5065 }
5066 else {
5067 if (!valid_status(object_get_by_md5(v, &msg)))
5068 continue;
5069 }
5070
5071 const char *id = xs_dict_get(msg, "id");
5072 const char *content = xs_dict_get(msg, "content");
5073 const char *published = xs_dict_get(msg, "published");
5074
5075 if (user && !xs_startswith(id, user->actor))
5076 continue;
5077
5078 if (!id || !content || !published)
5079 continue;
5080
5081 /* create a title with the first line of the content */
5082 xs *title = xs_replace(content, "<br>", "\n");
5083 title = xs_regex_replace_i(title, "<[^>]+>", " ");
5084 title = xs_regex_replace_i(title, "&[^;]+;", " ");
5085 int i;
5086
5087 for (i = 0; title[i] && title[i] != '\n' && i < 50; i++);
5088
5089 if (title[i] != '\0') {
5090 title[i] = '\0';
5091 title = xs_str_cat(title, "...");
5092 }
5093
5094 title = xs_strip_i(title);
5095
5096 /* convert the date */
5097 time_t t = xs_parse_iso_date(published, 0);
5098 xs *rss_date = xs_str_utctime(t, "%a, %d %b %Y %T +0000");
5099
5100 /* if it's the first one, add it to the header */
5101 if (cnt == 0)
5102 xs_html_add(channel,
5103 xs_html_tag("lastBuildDate",
5104 xs_html_text(rss_date)));
5105
5106 xs_html_add(channel,
5107 xs_html_tag("item",
5108 xs_html_tag("title",
5109 xs_html_text(title)),
5110 xs_html_tag("link",
5111 xs_html_text(id)),
5112 xs_html_tag("guid",
5113 xs_html_text(id)),
5114 xs_html_tag("pubDate",
5115 xs_html_text(rss_date)),
5116 xs_html_tag("description",
5117 xs_html_text(content))));
5118
5119 cnt++;
5120 }
5121
5122 return xs_html_render_s(rss, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n");
5123}