summaryrefslogtreecommitdiff
path: root/html.c
diff options
context:
space:
mode:
Diffstat (limited to 'html.c')
-rw-r--r--html.c224
1 files changed, 144 insertions, 80 deletions
diff --git a/html.c b/html.c
index e742e94..6573630 100644
--- a/html.c
+++ b/html.c
@@ -17,7 +17,7 @@
17 17
18#include "snac.h" 18#include "snac.h"
19 19
20int login(snac *snac, const xs_dict *headers) 20int login(snac *user, const xs_dict *headers)
21/* tries a login */ 21/* tries a login */
22{ 22{
23 int logged_in = 0; 23 int logged_in = 0;
@@ -31,23 +31,23 @@ int login(snac *snac, const xs_dict *headers)
31 xs *l1 = xs_split_n(s2, ":", 1); 31 xs *l1 = xs_split_n(s2, ":", 1);
32 32
33 if (xs_list_len(l1) == 2) { 33 if (xs_list_len(l1) == 2) {
34 const char *user = xs_list_get(l1, 0); 34 const char *uid = xs_list_get(l1, 0);
35 const char *pwd = xs_list_get(l1, 1); 35 const char *pwd = xs_list_get(l1, 1);
36 const char *addr = xs_or(xs_dict_get(headers, "remote-addr"), 36 const char *addr = xs_or(xs_dict_get(headers, "remote-addr"),
37 xs_dict_get(headers, "x-forwarded-for")); 37 xs_dict_get(headers, "x-forwarded-for"));
38 38
39 if (badlogin_check(user, addr)) { 39 if (badlogin_check(uid, addr)) {
40 logged_in = check_password(user, pwd, 40 logged_in = check_password(uid, pwd,
41 xs_dict_get(snac->config, "passwd")); 41 xs_dict_get(user->config, "passwd"));
42 42
43 if (!logged_in) 43 if (!logged_in)
44 badlogin_inc(user, addr); 44 badlogin_inc(uid, addr);
45 } 45 }
46 } 46 }
47 } 47 }
48 48
49 if (logged_in) 49 if (logged_in)
50 lastlog_write(snac, "web"); 50 lastlog_write(user, "web");
51 51
52 return logged_in; 52 return logged_in;
53} 53}
@@ -69,7 +69,7 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p
69 69
70 xs *style = xs_fmt("height: %dem; width: %dem; vertical-align: middle;", ems, ems); 70 xs *style = xs_fmt("height: %dem; width: %dem; vertical-align: middle;", ems, ems);
71 71
72 const char *v; 72 const xs_dict *v;
73 int c = 0; 73 int c = 0;
74 74
75 while (xs_list_next(tag_list, &v, &c)) { 75 while (xs_list_next(tag_list, &v, &c)) {
@@ -77,19 +77,25 @@ xs_str *replace_shortnames(xs_str *s, const xs_list *tag, int ems, const char *p
77 77
78 if (t && strcmp(t, "Emoji") == 0) { 78 if (t && strcmp(t, "Emoji") == 0) {
79 const char *n = xs_dict_get(v, "name"); 79 const char *n = xs_dict_get(v, "name");
80 const char *i = xs_dict_get(v, "icon"); 80 const xs_dict *i = xs_dict_get(v, "icon");
81 81
82 if (n && i) { 82 if (xs_is_string(n) && xs_is_dict(i)) {
83 const char *u = xs_dict_get(i, "url"); 83 const char *u = xs_dict_get(i, "url");
84 xs *url = make_url(u, proxy, 0); 84 const char *mt = xs_dict_get(i, "mediaType");
85
86 if (xs_is_string(u) && xs_is_string(mt) && strcmp(mt, "image/svg+xml")) {
87 xs *url = make_url(u, proxy, 0);
85 88
86 xs_html *img = xs_html_sctag("img", 89 xs_html *img = xs_html_sctag("img",
87 xs_html_attr("loading", "lazy"), 90 xs_html_attr("loading", "lazy"),
88 xs_html_attr("src", url), 91 xs_html_attr("src", url),
89 xs_html_attr("style", style)); 92 xs_html_attr("style", style));
90 93
91 xs *s1 = xs_html_render(img); 94 xs *s1 = xs_html_render(img);
92 s = xs_replace_i(s, n, s1); 95 s = xs_replace_i(s, n, s1);
96 }
97 else
98 s = xs_replace_i(s, n, "");
93 } 99 }
94 } 100 }
95 } 101 }
@@ -621,6 +627,9 @@ static xs_html *html_instance_body(void)
621 const char *email = xs_dict_get(srv_config, "admin_email"); 627 const char *email = xs_dict_get(srv_config, "admin_email");
622 const char *acct = xs_dict_get(srv_config, "admin_account"); 628 const char *acct = xs_dict_get(srv_config, "admin_account");
623 629
630 /* for L() */
631 const snac *user = NULL;
632
624 xs *blurb = xs_replace(snac_blurb, "%host%", host); 633 xs *blurb = xs_replace(snac_blurb, "%host%", host);
625 634
626 xs_html *dl; 635 xs_html *dl;
@@ -735,9 +744,11 @@ xs_html *html_user_head(snac *user, const char *desc, const char *url)
735 xs *fwers = follower_list(user); 744 xs *fwers = follower_list(user);
736 xs *fwing = following_list(user); 745 xs *fwing = following_list(user);
737 746
738 xs *s1 = xs_fmt(L("%d following, %d followers · "), 747 xs *s1 = xs_fmt(L("%d following, %d followers"),
739 xs_list_len(fwing), xs_list_len(fwers)); 748 xs_list_len(fwing), xs_list_len(fwers));
740 749
750 s1 = xs_str_cat(s1, " · ");
751
741 s_desc = xs_str_prepend_i(s_desc, s1); 752 s_desc = xs_str_prepend_i(s_desc, s1);
742 } 753 }
743 754
@@ -1052,8 +1063,8 @@ static xs_html *html_user_body(snac *user, int read_only)
1052 const char *longitude = xs_dict_get_def(user->config, "longitude", ""); 1063 const char *longitude = xs_dict_get_def(user->config, "longitude", "");
1053 1064
1054 if (*latitude && *longitude) { 1065 if (*latitude && *longitude) {
1055 xs *label = xs_fmt(L("%s,%s"), latitude, longitude); 1066 xs *label = xs_fmt("%s,%s", latitude, longitude);
1056 xs *url = xs_fmt(L("https://openstreetmap.org/search?query=%s,%s"), 1067 xs *url = xs_fmt("https://openstreetmap.org/search?query=%s,%s",
1057 latitude, longitude); 1068 latitude, longitude);
1058 1069
1059 xs_html_add(top_user, 1070 xs_html_add(top_user,
@@ -1069,7 +1080,7 @@ static xs_html *html_user_body(snac *user, int read_only)
1069 xs *fwers = follower_list(user); 1080 xs *fwers = follower_list(user);
1070 xs *fwing = following_list(user); 1081 xs *fwing = following_list(user);
1071 1082
1072 xs *s1 = xs_fmt(L("%d following %d followers"), 1083 xs *s1 = xs_fmt(L("%d following, %d followers"),
1073 xs_list_len(fwing), xs_list_len(fwers)); 1084 xs_list_len(fwing), xs_list_len(fwers));
1074 1085
1075 xs_html_add(top_user, 1086 xs_html_add(top_user,
@@ -1085,16 +1096,16 @@ static xs_html *html_user_body(snac *user, int read_only)
1085} 1096}
1086 1097
1087 1098
1088xs_html *html_top_controls(snac *snac) 1099xs_html *html_top_controls(snac *user)
1089/* generates the top controls */ 1100/* generates the top controls */
1090{ 1101{
1091 xs *ops_action = xs_fmt("%s/admin/action", snac->actor); 1102 xs *ops_action = xs_fmt("%s/admin/action", user->actor);
1092 1103
1093 xs_html *top_controls = xs_html_tag("div", 1104 xs_html *top_controls = xs_html_tag("div",
1094 xs_html_attr("class", "snac-top-controls"), 1105 xs_html_attr("class", "snac-top-controls"),
1095 1106
1096 /** new post **/ 1107 /** new post **/
1097 html_note(snac, L("New Post..."), 1108 html_note(user, L("New Post..."),
1098 "new_post_div", "new_post_form", 1109 "new_post_div", "new_post_form",
1099 L("What's on your mind?"), "", 1110 L("What's on your mind?"), "",
1100 NULL, NULL, 1111 NULL, NULL,
@@ -1164,53 +1175,53 @@ xs_html *html_top_controls(snac *snac)
1164 const char *email = "[disabled by admin]"; 1175 const char *email = "[disabled by admin]";
1165 1176
1166 if (xs_type(xs_dict_get(srv_config, "disable_email_notifications")) != XSTYPE_TRUE) { 1177 if (xs_type(xs_dict_get(srv_config, "disable_email_notifications")) != XSTYPE_TRUE) {
1167 email = xs_dict_get(snac->config_o, "email"); 1178 email = xs_dict_get(user->config_o, "email");
1168 if (xs_is_null(email)) { 1179 if (xs_is_null(email)) {
1169 email = xs_dict_get(snac->config, "email"); 1180 email = xs_dict_get(user->config, "email");
1170 1181
1171 if (xs_is_null(email)) 1182 if (xs_is_null(email))
1172 email = ""; 1183 email = "";
1173 } 1184 }
1174 } 1185 }
1175 1186
1176 const char *cw = xs_dict_get(snac->config, "cw"); 1187 const char *cw = xs_dict_get(user->config, "cw");
1177 if (xs_is_null(cw)) 1188 if (xs_is_null(cw))
1178 cw = ""; 1189 cw = "";
1179 1190
1180 const char *telegram_bot = xs_dict_get(snac->config, "telegram_bot"); 1191 const char *telegram_bot = xs_dict_get(user->config, "telegram_bot");
1181 if (xs_is_null(telegram_bot)) 1192 if (xs_is_null(telegram_bot))
1182 telegram_bot = ""; 1193 telegram_bot = "";
1183 1194
1184 const char *telegram_chat_id = xs_dict_get(snac->config, "telegram_chat_id"); 1195 const char *telegram_chat_id = xs_dict_get(user->config, "telegram_chat_id");
1185 if (xs_is_null(telegram_chat_id)) 1196 if (xs_is_null(telegram_chat_id))
1186 telegram_chat_id = ""; 1197 telegram_chat_id = "";
1187 1198
1188 const char *ntfy_server = xs_dict_get(snac->config, "ntfy_server"); 1199 const char *ntfy_server = xs_dict_get(user->config, "ntfy_server");
1189 if (xs_is_null(ntfy_server)) 1200 if (xs_is_null(ntfy_server))
1190 ntfy_server = ""; 1201 ntfy_server = "";
1191 1202
1192 const char *ntfy_token = xs_dict_get(snac->config, "ntfy_token"); 1203 const char *ntfy_token = xs_dict_get(user->config, "ntfy_token");
1193 if (xs_is_null(ntfy_token)) 1204 if (xs_is_null(ntfy_token))
1194 ntfy_token = ""; 1205 ntfy_token = "";
1195 1206
1196 const char *purge_days = xs_dict_get(snac->config, "purge_days"); 1207 const char *purge_days = xs_dict_get(user->config, "purge_days");
1197 if (!xs_is_null(purge_days) && xs_type(purge_days) == XSTYPE_NUMBER) 1208 if (!xs_is_null(purge_days) && xs_type(purge_days) == XSTYPE_NUMBER)
1198 purge_days = (char *)xs_number_str(purge_days); 1209 purge_days = (char *)xs_number_str(purge_days);
1199 else 1210 else
1200 purge_days = "0"; 1211 purge_days = "0";
1201 1212
1202 const xs_val *d_dm_f_u = xs_dict_get(snac->config, "drop_dm_from_unknown"); 1213 const xs_val *d_dm_f_u = xs_dict_get(user->config, "drop_dm_from_unknown");
1203 const xs_val *bot = xs_dict_get(snac->config, "bot"); 1214 const xs_val *bot = xs_dict_get(user->config, "bot");
1204 const xs_val *a_private = xs_dict_get(snac->config, "private"); 1215 const xs_val *a_private = xs_dict_get(user->config, "private");
1205 const xs_val *auto_boost = xs_dict_get(snac->config, "auto_boost"); 1216 const xs_val *auto_boost = xs_dict_get(user->config, "auto_boost");
1206 const xs_val *coll_thrds = xs_dict_get(snac->config, "collapse_threads"); 1217 const xs_val *coll_thrds = xs_dict_get(user->config, "collapse_threads");
1207 const xs_val *pending = xs_dict_get(snac->config, "approve_followers"); 1218 const xs_val *pending = xs_dict_get(user->config, "approve_followers");
1208 const xs_val *show_foll = xs_dict_get(snac->config, "show_contact_metrics"); 1219 const xs_val *show_foll = xs_dict_get(user->config, "show_contact_metrics");
1209 const char *latitude = xs_dict_get_def(snac->config, "latitude", ""); 1220 const char *latitude = xs_dict_get_def(user->config, "latitude", "");
1210 const char *longitude = xs_dict_get_def(snac->config, "longitude", ""); 1221 const char *longitude = xs_dict_get_def(user->config, "longitude", "");
1211 1222
1212 xs *metadata = NULL; 1223 xs *metadata = NULL;
1213 const xs_dict *md = xs_dict_get(snac->config, "metadata"); 1224 const xs_dict *md = xs_dict_get(user->config, "metadata");
1214 1225
1215 if (xs_type(md) == XSTYPE_DICT) { 1226 if (xs_type(md) == XSTYPE_DICT) {
1216 const xs_str *k; 1227 const xs_str *k;
@@ -1232,7 +1243,29 @@ xs_html *html_top_controls(snac *snac)
1232 else 1243 else
1233 metadata = xs_str_new(NULL); 1244 metadata = xs_str_new(NULL);
1234 1245
1235 xs *user_setup_action = xs_fmt("%s/admin/user-setup", snac->actor); 1246 /* ui language */
1247 xs_html *lang_select = xs_html_tag("select",
1248 xs_html_attr("name", "web_ui_lang"));
1249
1250 const char *u_lang = xs_dict_get_def(user->config, "lang", "en");
1251 const char *lang;
1252 const xs_dict *langs;
1253
1254 xs_dict_foreach(srv_langs, lang, langs) {
1255 if (strcmp(u_lang, lang) == 0)
1256 xs_html_add(lang_select,
1257 xs_html_tag("option",
1258 xs_html_text(lang),
1259 xs_html_attr("value", lang),
1260 xs_html_attr("selected", "selected")));
1261 else
1262 xs_html_add(lang_select,
1263 xs_html_tag("option",
1264 xs_html_text(lang),
1265 xs_html_attr("value", lang)));
1266 }
1267
1268 xs *user_setup_action = xs_fmt("%s/admin/user-setup", user->actor);
1236 1269
1237 xs_html_add(top_controls, 1270 xs_html_add(top_controls,
1238 xs_html_tag("details", 1271 xs_html_tag("details",
@@ -1251,7 +1284,7 @@ xs_html *html_top_controls(snac *snac)
1251 xs_html_sctag("input", 1284 xs_html_sctag("input",
1252 xs_html_attr("type", "text"), 1285 xs_html_attr("type", "text"),
1253 xs_html_attr("name", "name"), 1286 xs_html_attr("name", "name"),
1254 xs_html_attr("value", xs_dict_get(snac->config, "name")), 1287 xs_html_attr("value", xs_dict_get(user->config, "name")),
1255 xs_html_attr("placeholder", L("Your name")))), 1288 xs_html_attr("placeholder", L("Your name")))),
1256 xs_html_tag("p", 1289 xs_html_tag("p",
1257 xs_html_text(L("Avatar: ")), 1290 xs_html_text(L("Avatar: ")),
@@ -1281,7 +1314,7 @@ xs_html *html_top_controls(snac *snac)
1281 xs_html_attr("cols", "40"), 1314 xs_html_attr("cols", "40"),
1282 xs_html_attr("rows", "4"), 1315 xs_html_attr("rows", "4"),
1283 xs_html_attr("placeholder", L("Write about yourself here...")), 1316 xs_html_attr("placeholder", L("Write about yourself here...")),
1284 xs_html_text(xs_dict_get(snac->config, "bio")))), 1317 xs_html_text(xs_dict_get(user->config, "bio")))),
1285 xs_html_sctag("input", 1318 xs_html_sctag("input",
1286 xs_html_attr("type", "checkbox"), 1319 xs_html_attr("type", "checkbox"),
1287 xs_html_attr("name", "cw"), 1320 xs_html_attr("name", "cw"),
@@ -1423,6 +1456,11 @@ xs_html *html_top_controls(snac *snac)
1423 xs_html_text(metadata))), 1456 xs_html_text(metadata))),
1424 1457
1425 xs_html_tag("p", 1458 xs_html_tag("p",
1459 xs_html_text(L("Web interface language:")),
1460 xs_html_sctag("br", NULL),
1461 lang_select),
1462
1463 xs_html_tag("p",
1426 xs_html_text(L("New password:")), 1464 xs_html_text(L("New password:")),
1427 xs_html_sctag("br", NULL), 1465 xs_html_sctag("br", NULL),
1428 xs_html_sctag("input", 1466 xs_html_sctag("input",
@@ -1444,8 +1482,8 @@ xs_html *html_top_controls(snac *snac)
1444 1482
1445 xs_html_tag("p", NULL))))); 1483 xs_html_tag("p", NULL)))));
1446 1484
1447 xs *followed_hashtags_action = xs_fmt("%s/admin/followed-hashtags", snac->actor); 1485 xs *followed_hashtags_action = xs_fmt("%s/admin/followed-hashtags", user->actor);
1448 xs *followed_hashtags = xs_join(xs_dict_get_def(snac->config, 1486 xs *followed_hashtags = xs_join(xs_dict_get_def(user->config,
1449 "followed_hashtags", xs_stock(XSTYPE_LIST)), "\n"); 1487 "followed_hashtags", xs_stock(XSTYPE_LIST)), "\n");
1450 1488
1451 xs_html_add(top_controls, 1489 xs_html_add(top_controls,
@@ -1480,7 +1518,7 @@ xs_html *html_top_controls(snac *snac)
1480} 1518}
1481 1519
1482 1520
1483static xs_html *html_button(char *clss, char *label, char *hint) 1521static xs_html *html_button(const char *clss, const char *label, const char *hint)
1484{ 1522{
1485 xs *c = xs_fmt("snac-btn-%s", clss); 1523 xs *c = xs_fmt("snac-btn-%s", clss);
1486 1524
@@ -1496,7 +1534,7 @@ static xs_html *html_button(char *clss, char *label, char *hint)
1496} 1534}
1497 1535
1498 1536
1499xs_str *build_mentions(snac *snac, const xs_dict *msg) 1537xs_str *build_mentions(snac *user, const xs_dict *msg)
1500/* returns a string with the mentions in msg */ 1538/* returns a string with the mentions in msg */
1501{ 1539{
1502 xs_str *s = xs_str_new(NULL); 1540 xs_str *s = xs_str_new(NULL);
@@ -1510,7 +1548,7 @@ xs_str *build_mentions(snac *snac, const xs_dict *msg)
1510 const char *name = xs_dict_get(v, "name"); 1548 const char *name = xs_dict_get(v, "name");
1511 1549
1512 if (type && strcmp(type, "Mention") == 0 && 1550 if (type && strcmp(type, "Mention") == 0 &&
1513 href && strcmp(href, snac->actor) != 0 && name) { 1551 href && strcmp(href, user->actor) != 0 && name) {
1514 xs *s1 = NULL; 1552 xs *s1 = NULL;
1515 1553
1516 if (name[0] != '@') { 1554 if (name[0] != '@') {
@@ -1551,7 +1589,7 @@ xs_str *build_mentions(snac *snac, const xs_dict *msg)
1551} 1589}
1552 1590
1553 1591
1554xs_html *html_entry_controls(snac *snac, const char *actor, 1592xs_html *html_entry_controls(snac *user, const char *actor,
1555 const xs_dict *msg, const char *md5) 1593 const xs_dict *msg, const char *md5)
1556{ 1594{
1557 const char *id = xs_dict_get(msg, "id"); 1595 const char *id = xs_dict_get(msg, "id");
@@ -1560,7 +1598,7 @@ xs_html *html_entry_controls(snac *snac, const char *actor,
1560 xs *likes = object_likes(id); 1598 xs *likes = object_likes(id);
1561 xs *boosts = object_announces(id); 1599 xs *boosts = object_announces(id);
1562 1600
1563 xs *action = xs_fmt("%s/admin/action", snac->actor); 1601 xs *action = xs_fmt("%s/admin/action", user->actor);
1564 xs *redir = xs_fmt("%s_entry", md5); 1602 xs *redir = xs_fmt("%s_entry", md5);
1565 1603
1566 xs_html *form; 1604 xs_html *form;
@@ -1587,8 +1625,8 @@ xs_html *html_entry_controls(snac *snac, const char *actor,
1587 xs_html_attr("name", "redir"), 1625 xs_html_attr("name", "redir"),
1588 xs_html_attr("value", redir)))); 1626 xs_html_attr("value", redir))));
1589 1627
1590 if (!xs_startswith(id, snac->actor)) { 1628 if (!xs_startswith(id, user->actor)) {
1591 if (xs_list_in(likes, snac->md5) == -1) { 1629 if (xs_list_in(likes, user->md5) == -1) {
1592 /* not already liked; add button */ 1630 /* not already liked; add button */
1593 xs_html_add(form, 1631 xs_html_add(form,
1594 html_button("like", L("Like"), L("Say you like this post"))); 1632 html_button("like", L("Like"), L("Say you like this post")));
@@ -1600,7 +1638,7 @@ xs_html *html_entry_controls(snac *snac, const char *actor,
1600 } 1638 }
1601 } 1639 }
1602 else { 1640 else {
1603 if (is_pinned(snac, id)) 1641 if (is_pinned(user, id))
1604 xs_html_add(form, 1642 xs_html_add(form,
1605 html_button("unpin", L("Unpin"), L("Unpin this post from your timeline"))); 1643 html_button("unpin", L("Unpin"), L("Unpin this post from your timeline")));
1606 else 1644 else
@@ -1609,7 +1647,7 @@ xs_html *html_entry_controls(snac *snac, const char *actor,
1609 } 1647 }
1610 1648
1611 if (is_msg_public(msg)) { 1649 if (is_msg_public(msg)) {
1612 if (xs_list_in(boosts, snac->md5) == -1) { 1650 if (xs_list_in(boosts, user->md5) == -1) {
1613 /* not already boosted; add button */ 1651 /* not already boosted; add button */
1614 xs_html_add(form, 1652 xs_html_add(form,
1615 html_button("boost", L("Boost"), L("Announce this post to your followers"))); 1653 html_button("boost", L("Boost"), L("Announce this post to your followers")));
@@ -1621,16 +1659,16 @@ xs_html *html_entry_controls(snac *snac, const char *actor,
1621 } 1659 }
1622 } 1660 }
1623 1661
1624 if (is_bookmarked(snac, id)) 1662 if (is_bookmarked(user, id))
1625 xs_html_add(form, 1663 xs_html_add(form,
1626 html_button("unbookmark", L("Unbookmark"), L("Delete this post from your bookmarks"))); 1664 html_button("unbookmark", L("Unbookmark"), L("Delete this post from your bookmarks")));
1627 else 1665 else
1628 xs_html_add(form, 1666 xs_html_add(form,
1629 html_button("bookmark", L("Bookmark"), L("Add this post to your bookmarks"))); 1667 html_button("bookmark", L("Bookmark"), L("Add this post to your bookmarks")));
1630 1668
1631 if (strcmp(actor, snac->actor) != 0) { 1669 if (strcmp(actor, user->actor) != 0) {
1632 /* controls for other actors than this one */ 1670 /* controls for other actors than this one */
1633 if (following_check(snac, actor)) { 1671 if (following_check(user, actor)) {
1634 xs_html_add(form, 1672 xs_html_add(form,
1635 html_button("unfollow", L("Unfollow"), L("Stop following this user's activity"))); 1673 html_button("unfollow", L("Unfollow"), L("Stop following this user's activity")));
1636 } 1674 }
@@ -1640,7 +1678,7 @@ xs_html *html_entry_controls(snac *snac, const char *actor,
1640 } 1678 }
1641 1679
1642 if (!xs_is_null(group)) { 1680 if (!xs_is_null(group)) {
1643 if (following_check(snac, group)) { 1681 if (following_check(user, group)) {
1644 xs_html_add(form, 1682 xs_html_add(form,
1645 html_button("unfollow", L("Unfollow Group"), 1683 html_button("unfollow", L("Unfollow Group"),
1646 L("Stop following this group or channel"))); 1684 L("Stop following this group or channel")));
@@ -1666,7 +1704,7 @@ xs_html *html_entry_controls(snac *snac, const char *actor,
1666 1704
1667 const char *prev_src = xs_dict_get(msg, "sourceContent"); 1705 const char *prev_src = xs_dict_get(msg, "sourceContent");
1668 1706
1669 if (!xs_is_null(prev_src) && strcmp(actor, snac->actor) == 0) { /** edit **/ 1707 if (!xs_is_null(prev_src) && strcmp(actor, user->actor) == 0) { /** edit **/
1670 /* post can be edited */ 1708 /* post can be edited */
1671 xs *div_id = xs_fmt("%s_edit", md5); 1709 xs *div_id = xs_fmt("%s_edit", md5);
1672 xs *form_id = xs_fmt("%s_edit_form", md5); 1710 xs *form_id = xs_fmt("%s_edit_form", md5);
@@ -1693,26 +1731,26 @@ xs_html *html_entry_controls(snac *snac, const char *actor,
1693 1731
1694 xs_html_add(controls, xs_html_tag("div", 1732 xs_html_add(controls, xs_html_tag("div",
1695 xs_html_tag("p", NULL), 1733 xs_html_tag("p", NULL),
1696 html_note(snac, L("Edit..."), 1734 html_note(user, L("Edit..."),
1697 div_id, form_id, 1735 div_id, form_id,
1698 "", prev_src, 1736 "", prev_src,
1699 id, NULL, 1737 id, NULL,
1700 xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"), 1738 xs_dict_get(msg, "sensitive"), xs_dict_get(msg, "summary"),
1701 xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir, 1739 xs_stock(is_msg_public(msg) ? XSTYPE_FALSE : XSTYPE_TRUE), redir,
1702 NULL, 0, att_files, att_alt_texts, is_draft(snac, id))), 1740 NULL, 0, att_files, att_alt_texts, is_draft(user, id))),
1703 xs_html_tag("p", NULL)); 1741 xs_html_tag("p", NULL));
1704 } 1742 }
1705 1743
1706 { /** reply **/ 1744 { /** reply **/
1707 /* the post textarea */ 1745 /* the post textarea */
1708 xs *ct = build_mentions(snac, msg); 1746 xs *ct = build_mentions(user, msg);
1709 xs *div_id = xs_fmt("%s_reply", md5); 1747 xs *div_id = xs_fmt("%s_reply", md5);
1710 xs *form_id = xs_fmt("%s_reply_form", md5); 1748 xs *form_id = xs_fmt("%s_reply_form", md5);
1711 xs *redir = xs_fmt("%s_entry", md5); 1749 xs *redir = xs_fmt("%s_entry", md5);
1712 1750
1713 xs_html_add(controls, xs_html_tag("div", 1751 xs_html_add(controls, xs_html_tag("div",
1714 xs_html_tag("p", NULL), 1752 xs_html_tag("p", NULL),
1715 html_note(snac, L("Reply..."), 1753 html_note(user, L("Reply..."),
1716 div_id, form_id, 1754 div_id, form_id,
1717 "", ct, 1755 "", ct,
1718 NULL, NULL, 1756 NULL, NULL,
@@ -1839,7 +1877,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
1839 xs_html_raw(" 📌 "))); 1877 xs_html_raw(" 📌 ")));
1840 } 1878 }
1841 1879
1842 if (user && is_bookmarked(user, id)) { 1880 if (user && !read_only && is_bookmarked(user, id)) {
1843 /* add a bookmark emoji */ 1881 /* add a bookmark emoji */
1844 xs_html_add(score, 1882 xs_html_add(score,
1845 xs_html_tag("span", 1883 xs_html_tag("span",
@@ -2242,6 +2280,11 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2242 if (content && xs_str_in(content, o_href) != -1) 2280 if (content && xs_str_in(content, o_href) != -1)
2243 continue; 2281 continue;
2244 2282
2283 /* drop silently any attachment that may include JavaScript */
2284 if (strcmp(type, "image/svg+xml") == 0 ||
2285 strcmp(type, "text/html") == 0)
2286 continue;
2287
2245 /* do this attachment include an icon? */ 2288 /* do this attachment include an icon? */
2246 const xs_dict *icon = xs_dict_get(a, "icon"); 2289 const xs_dict *icon = xs_dict_get(a, "icon");
2247 if (xs_type(icon) == XSTYPE_DICT) { 2290 if (xs_type(icon) == XSTYPE_DICT) {
@@ -2571,7 +2614,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only,
2571} 2614}
2572 2615
2573 2616
2574xs_html *html_footer(void) 2617xs_html *html_footer(const snac *user)
2575{ 2618{
2576 return xs_html_tag("div", 2619 return xs_html_tag("div",
2577 xs_html_attr("class", "snac-footer"), 2620 xs_html_attr("class", "snac-footer"),
@@ -2879,13 +2922,13 @@ xs_str *html_timeline(snac *user, const xs_list *list, int read_only,
2879 } 2922 }
2880 2923
2881 xs_html_add(body, 2924 xs_html_add(body,
2882 html_footer()); 2925 html_footer(user));
2883 2926
2884 return xs_html_render_s(html, "<!DOCTYPE html>\n"); 2927 return xs_html_render_s(html, "<!DOCTYPE html>\n");
2885} 2928}
2886 2929
2887 2930
2888xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, const char *proxy) 2931xs_html *html_people_list(snac *user, xs_list *list, const char *header, const char *t, const char *proxy)
2889{ 2932{
2890 xs_html *snac_posts; 2933 xs_html *snac_posts;
2891 xs_html *people = xs_html_tag("div", 2934 xs_html *people = xs_html_tag("div",
@@ -2910,7 +2953,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons
2910 xs_html_attr("name", md5)), 2953 xs_html_attr("name", md5)),
2911 xs_html_tag("div", 2954 xs_html_tag("div",
2912 xs_html_attr("class", "snac-post-header"), 2955 xs_html_attr("class", "snac-post-header"),
2913 html_actor_icon(snac, actor, xs_dict_get(actor, "published"), 2956 html_actor_icon(user, actor, xs_dict_get(actor, "published"),
2914 NULL, NULL, 0, 1, proxy, NULL, NULL))); 2957 NULL, NULL, 0, 1, proxy, NULL, NULL)));
2915 2958
2916 /* content (user bio) */ 2959 /* content (user bio) */
@@ -2934,7 +2977,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons
2934 } 2977 }
2935 2978
2936 /* buttons */ 2979 /* buttons */
2937 xs *btn_form_action = xs_fmt("%s/admin/action", snac->actor); 2980 xs *btn_form_action = xs_fmt("%s/admin/action", user->actor);
2938 2981
2939 xs_html *snac_controls = xs_html_tag("div", 2982 xs_html *snac_controls = xs_html_tag("div",
2940 xs_html_attr("class", "snac-controls")); 2983 xs_html_attr("class", "snac-controls"));
@@ -2954,12 +2997,12 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons
2954 2997
2955 xs_html_add(snac_controls, form); 2998 xs_html_add(snac_controls, form);
2956 2999
2957 if (following_check(snac, actor_id)) { 3000 if (following_check(user, actor_id)) {
2958 xs_html_add(form, 3001 xs_html_add(form,
2959 html_button("unfollow", L("Unfollow"), 3002 html_button("unfollow", L("Unfollow"),
2960 L("Stop following this user's activity"))); 3003 L("Stop following this user's activity")));
2961 3004
2962 if (is_limited(snac, actor_id)) 3005 if (is_limited(user, actor_id))
2963 xs_html_add(form, 3006 xs_html_add(form,
2964 html_button("unlimit", L("Unlimit"), 3007 html_button("unlimit", L("Unlimit"),
2965 L("Allow announces (boosts) from this user"))); 3008 L("Allow announces (boosts) from this user")));
@@ -2973,12 +3016,12 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons
2973 html_button("follow", L("Follow"), 3016 html_button("follow", L("Follow"),
2974 L("Start following this user's activity"))); 3017 L("Start following this user's activity")));
2975 3018
2976 if (follower_check(snac, actor_id)) 3019 if (follower_check(user, actor_id))
2977 xs_html_add(form, 3020 xs_html_add(form,
2978 html_button("delete", L("Delete"), L("Delete this user"))); 3021 html_button("delete", L("Delete"), L("Delete this user")));
2979 } 3022 }
2980 3023
2981 if (pending_check(snac, actor_id)) { 3024 if (pending_check(user, actor_id)) {
2982 xs_html_add(form, 3025 xs_html_add(form,
2983 html_button("approve", L("Approve"), 3026 html_button("approve", L("Approve"),
2984 L("Approve this follow request"))); 3027 L("Approve this follow request")));
@@ -2987,7 +3030,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons
2987 html_button("discard", L("Discard"), L("Discard this follow request"))); 3030 html_button("discard", L("Discard"), L("Discard this follow request")));
2988 } 3031 }
2989 3032
2990 if (is_muted(snac, actor_id)) 3033 if (is_muted(user, actor_id))
2991 xs_html_add(form, 3034 xs_html_add(form,
2992 html_button("unmute", L("Unmute"), 3035 html_button("unmute", L("Unmute"),
2993 L("Stop blocking activities from this user"))); 3036 L("Stop blocking activities from this user")));
@@ -3002,7 +3045,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons
3002 3045
3003 xs_html_add(snac_controls, 3046 xs_html_add(snac_controls,
3004 xs_html_tag("p", NULL), 3047 xs_html_tag("p", NULL),
3005 html_note(snac, L("Direct Message..."), 3048 html_note(user, L("Direct Message..."),
3006 dm_div_id, dm_form_id, 3049 dm_div_id, dm_form_id,
3007 "", "", 3050 "", "",
3008 NULL, actor_id, 3051 NULL, actor_id,
@@ -3048,7 +3091,7 @@ xs_str *html_people(snac *user)
3048 html_user_head(user, NULL, NULL), 3091 html_user_head(user, NULL, NULL),
3049 xs_html_add(html_user_body(user, 0), 3092 xs_html_add(html_user_body(user, 0),
3050 lists, 3093 lists,
3051 html_footer())); 3094 html_footer(user)));
3052 3095
3053 return xs_html_render_s(html, "<!DOCTYPE html>\n"); 3096 return xs_html_render_s(html, "<!DOCTYPE html>\n");
3054} 3097}
@@ -3298,7 +3341,7 @@ xs_str *html_notifications(snac *user, int skip, int show)
3298 xs_set_free(&rep); 3341 xs_set_free(&rep);
3299 3342
3300 xs_html_add(body, 3343 xs_html_add(body,
3301 html_footer()); 3344 html_footer(user));
3302 3345
3303 /* set the check time to now */ 3346 /* set the check time to now */
3304 xs *dummy = notify_check_time(user, 1); 3347 xs *dummy = notify_check_time(user, 1);
@@ -3310,12 +3353,24 @@ xs_str *html_notifications(snac *user, int skip, int show)
3310} 3353}
3311 3354
3312 3355
3356void set_user_lang(snac *user)
3357/* sets the language dict according to user configuration */
3358{
3359 user->lang = NULL;
3360 const char *lang = xs_dict_get(user->config, "lang");
3361
3362 if (xs_is_string(lang))
3363 user->lang = xs_dict_get(srv_langs, lang);
3364}
3365
3366
3313int html_get_handler(const xs_dict *req, const char *q_path, 3367int html_get_handler(const xs_dict *req, const char *q_path,
3314 char **body, int *b_size, char **ctype, 3368 char **body, int *b_size, char **ctype,
3315 xs_str **etag, xs_str **last_modified) 3369 xs_str **etag, xs_str **last_modified)
3316{ 3370{
3317 const char *accept = xs_dict_get(req, "accept"); 3371 const char *accept = xs_dict_get(req, "accept");
3318 int status = HTTP_STATUS_NOT_FOUND; 3372 int status = HTTP_STATUS_NOT_FOUND;
3373 const snac *user = NULL;
3319 snac snac; 3374 snac snac;
3320 xs *uid = NULL; 3375 xs *uid = NULL;
3321 const char *p_path; 3376 const char *p_path;
@@ -3382,6 +3437,9 @@ int html_get_handler(const xs_dict *req, const char *q_path,
3382 return HTTP_STATUS_NOT_FOUND; 3437 return HTTP_STATUS_NOT_FOUND;
3383 } 3438 }
3384 3439
3440 user = &snac; /* for L() */
3441 set_user_lang(&snac);
3442
3385 if (xs_is_true(xs_dict_get(srv_config, "proxy_media"))) 3443 if (xs_is_true(xs_dict_get(srv_config, "proxy_media")))
3386 proxy = 1; 3444 proxy = 1;
3387 3445
@@ -3550,7 +3608,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
3550 html_user_head(&snac, NULL, NULL), 3608 html_user_head(&snac, NULL, NULL),
3551 xs_html_add(html_user_body(&snac, 0), 3609 xs_html_add(html_user_body(&snac, 0),
3552 page, 3610 page,
3553 html_footer())); 3611 html_footer(user)));
3554 3612
3555 *body = xs_html_render_s(html, "<!DOCTYPE html>\n"); 3613 *body = xs_html_render_s(html, "<!DOCTYPE html>\n");
3556 *b_size = strlen(*body); 3614 *b_size = strlen(*body);
@@ -4005,6 +4063,7 @@ int html_post_handler(const xs_dict *req, const char *q_path,
4005 (void)ctype; 4063 (void)ctype;
4006 4064
4007 int status = 0; 4065 int status = 0;
4066 const snac *user = NULL;
4008 snac snac; 4067 snac snac;
4009 const char *uid; 4068 const char *uid;
4010 const char *p_path; 4069 const char *p_path;
@@ -4028,6 +4087,9 @@ int html_post_handler(const xs_dict *req, const char *q_path,
4028 return HTTP_STATUS_UNAUTHORIZED; 4087 return HTTP_STATUS_UNAUTHORIZED;
4029 } 4088 }
4030 4089
4090 user = &snac; /* for L() */
4091 set_user_lang(&snac);
4092
4031 p_vars = xs_dict_get(req, "p_vars"); 4093 p_vars = xs_dict_get(req, "p_vars");
4032 4094
4033 if (p_path && strcmp(p_path, "admin/note") == 0) { /** **/ 4095 if (p_path && strcmp(p_path, "admin/note") == 0) { /** **/
@@ -4470,6 +4532,8 @@ int html_post_handler(const xs_dict *req, const char *q_path,
4470 snac.config = xs_dict_set(snac.config, "show_contact_metrics", xs_stock(XSTYPE_TRUE)); 4532 snac.config = xs_dict_set(snac.config, "show_contact_metrics", xs_stock(XSTYPE_TRUE));
4471 else 4533 else
4472 snac.config = xs_dict_set(snac.config, "show_contact_metrics", xs_stock(XSTYPE_FALSE)); 4534 snac.config = xs_dict_set(snac.config, "show_contact_metrics", xs_stock(XSTYPE_FALSE));
4535 if ((v = xs_dict_get(p_vars, "web_ui_lang")) != NULL)
4536 snac.config = xs_dict_set(snac.config, "lang", v);
4473 4537
4474 snac.config = xs_dict_set(snac.config, "latitude", xs_dict_get_def(p_vars, "latitude", "")); 4538 snac.config = xs_dict_set(snac.config, "latitude", xs_dict_get_def(p_vars, "latitude", ""));
4475 snac.config = xs_dict_set(snac.config, "longitude", xs_dict_get_def(p_vars, "longitude", "")); 4539 snac.config = xs_dict_set(snac.config, "longitude", xs_dict_get_def(p_vars, "longitude", ""));