diff options
| -rw-r--r-- | RELEASE_NOTES.md | 8 | ||||
| -rw-r--r-- | doc/snac.8 | 2 | ||||
| -rw-r--r-- | html.c | 41 | ||||
| -rw-r--r-- | httpd.c | 2 | ||||
| -rw-r--r-- | utils.c | 1 | ||||
| -rw-r--r-- | xs_url.h | 103 |
6 files changed, 111 insertions, 46 deletions
diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index 7a23b8b..c6d439d 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md | |||
| @@ -6,12 +6,18 @@ Fixed a search bug. | |||
| 6 | 6 | ||
| 7 | Each notification includes a link labelled `Context`, that leads to a page with the full conversation tree the post is a part of. | 7 | Each notification includes a link labelled `Context`, that leads to a page with the full conversation tree the post is a part of. |
| 8 | 8 | ||
| 9 | Fixed more crashes (thank you very much to inz for helping me debugging this). | 9 | Fixed more crashes (contributed by inz). |
| 10 | 10 | ||
| 11 | Fixed link detection in posts (contributed by inz). | 11 | Fixed link detection in posts (contributed by inz). |
| 12 | 12 | ||
| 13 | Allow multiple editors for command-line posts (contributed by inz). | 13 | Allow multiple editors for command-line posts (contributed by inz). |
| 14 | 14 | ||
| 15 | Separated maximum and default timeline entry count, allowing larger timelines to be requested without having to increase the default (contributed by lxo). | ||
| 16 | |||
| 17 | Turned message date into a link to the local post, so that it can be loaded into a separate tab for interacting with (contributed by lxo). | ||
| 18 | |||
| 19 | Special thanks to fellow developer inz for bringing my attention to code places where I should have been more careful. | ||
| 20 | |||
| 15 | ## 2.71 | 21 | ## 2.71 |
| 16 | 22 | ||
| 17 | Fixed memory leak (contributed by inz). | 23 | Fixed memory leak (contributed by inz). |
| @@ -154,6 +154,8 @@ to those servers that went timeout in the previous retry. If you want to | |||
| 154 | give slow servers a chance to receive your messages, you can increase this | 154 | give slow servers a chance to receive your messages, you can increase this |
| 155 | value (but also take into account that processing the queue will take longer | 155 | value (but also take into account that processing the queue will take longer |
| 156 | while waiting for these molasses to respond). | 156 | while waiting for these molasses to respond). |
| 157 | .It Ic def_timeline_entries | ||
| 158 | This is the default timeline entries shown in the web interface. | ||
| 157 | .It Ic max_timeline_entries | 159 | .It Ic max_timeline_entries |
| 158 | This is the maximum timeline entries shown in the web interface. | 160 | This is the maximum timeline entries shown in the web interface. |
| 159 | .It Ic timeline_purge_days | 161 | .It Ic timeline_purge_days |
| @@ -13,6 +13,7 @@ | |||
| 13 | #include "xs_html.h" | 13 | #include "xs_html.h" |
| 14 | #include "xs_curl.h" | 14 | #include "xs_curl.h" |
| 15 | #include "xs_unicode.h" | 15 | #include "xs_unicode.h" |
| 16 | #include "xs_url.h" | ||
| 16 | 17 | ||
| 17 | #include "snac.h" | 18 | #include "snac.h" |
| 18 | 19 | ||
| @@ -115,7 +116,8 @@ xs_str *actor_name(xs_dict *actor, const char *proxy) | |||
| 115 | 116 | ||
| 116 | xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | 117 | xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, |
| 117 | const char *udate, const char *url, int priv, | 118 | const char *udate, const char *url, int priv, |
| 118 | int in_people, const char *proxy, const char *lang) | 119 | int in_people, const char *proxy, const char *lang, |
| 120 | const char *md5) | ||
| 119 | { | 121 | { |
| 120 | xs_html *actor_icon = xs_html_tag("p", NULL); | 122 | xs_html *actor_icon = xs_html_tag("p", NULL); |
| 121 | 123 | ||
| @@ -224,12 +226,31 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | |||
| 224 | if (xs_is_string(lang)) | 226 | if (xs_is_string(lang)) |
| 225 | date_title = xs_str_cat(date_title, " (", lang, ")"); | 227 | date_title = xs_str_cat(date_title, " (", lang, ")"); |
| 226 | 228 | ||
| 229 | xs_html *date_text = xs_html_text(date_label); | ||
| 230 | |||
| 231 | if (user && md5) { | ||
| 232 | xs *lpost_url = xs_fmt("%s/admin/p/%s#%s_entry", | ||
| 233 | user->actor, md5, md5); | ||
| 234 | date_text = xs_html_tag("a", | ||
| 235 | xs_html_attr("href", lpost_url), | ||
| 236 | xs_html_attr("class", "snac-pubdate"), | ||
| 237 | date_text); | ||
| 238 | } | ||
| 239 | else if (user && url) { | ||
| 240 | xs *lpost_url = xs_fmt("%s/admin?q=%s", | ||
| 241 | user->actor, xs_url_enc(url)); | ||
| 242 | date_text = xs_html_tag("a", | ||
| 243 | xs_html_attr("href", lpost_url), | ||
| 244 | xs_html_attr("class", "snac-pubdate"), | ||
| 245 | date_text); | ||
| 246 | } | ||
| 247 | |||
| 227 | xs_html_add(actor_icon, | 248 | xs_html_add(actor_icon, |
| 228 | xs_html_text(" "), | 249 | xs_html_text(" "), |
| 229 | xs_html_tag("time", | 250 | xs_html_tag("time", |
| 230 | xs_html_attr("class", "dt-published snac-pubdate"), | 251 | xs_html_attr("class", "dt-published snac-pubdate"), |
| 231 | xs_html_attr("title", date_title), | 252 | xs_html_attr("title", date_title), |
| 232 | xs_html_text(date_label))); | 253 | date_text)); |
| 233 | } | 254 | } |
| 234 | 255 | ||
| 235 | { | 256 | { |
| @@ -261,7 +282,7 @@ xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, | |||
| 261 | } | 282 | } |
| 262 | 283 | ||
| 263 | 284 | ||
| 264 | xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg, const char *proxy) | 285 | xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg, const char *proxy, const char *md5) |
| 265 | { | 286 | { |
| 266 | xs *actor = NULL; | 287 | xs *actor = NULL; |
| 267 | xs_html *actor_icon = NULL; | 288 | xs_html *actor_icon = NULL; |
| @@ -292,7 +313,7 @@ xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg, con | |||
| 292 | else | 313 | else |
| 293 | lang = NULL; | 314 | lang = NULL; |
| 294 | 315 | ||
| 295 | actor_icon = html_actor_icon(user, actor, date, udate, url, priv, 0, proxy, lang); | 316 | actor_icon = html_actor_icon(user, actor, date, udate, url, priv, 0, proxy, lang, md5); |
| 296 | } | 317 | } |
| 297 | 318 | ||
| 298 | return actor_icon; | 319 | return actor_icon; |
| @@ -1706,7 +1727,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1706 | xs_html_tag("div", | 1727 | xs_html_tag("div", |
| 1707 | xs_html_attr("class", "snac-origin"), | 1728 | xs_html_attr("class", "snac-origin"), |
| 1708 | xs_html_text(L("follows you"))), | 1729 | xs_html_text(L("follows you"))), |
| 1709 | html_msg_icon(read_only ? NULL : user, xs_dict_get(msg, "actor"), msg, proxy))); | 1730 | html_msg_icon(read_only ? NULL : user, xs_dict_get(msg, "actor"), msg, proxy, NULL))); |
| 1710 | } | 1731 | } |
| 1711 | else | 1732 | else |
| 1712 | if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) { | 1733 | if (!xs_match(type, POSTLIKE_OBJECT_TYPE)) { |
| @@ -1887,7 +1908,7 @@ xs_html *html_entry(snac *user, xs_dict *msg, int read_only, | |||
| 1887 | } | 1908 | } |
| 1888 | 1909 | ||
| 1889 | xs_html_add(post_header, | 1910 | xs_html_add(post_header, |
| 1890 | html_msg_icon(read_only ? NULL : user, actor, msg, proxy)); | 1911 | html_msg_icon(read_only ? NULL : user, actor, msg, proxy, md5)); |
| 1891 | 1912 | ||
| 1892 | /** post content **/ | 1913 | /** post content **/ |
| 1893 | 1914 | ||
| @@ -2820,7 +2841,7 @@ xs_html *html_people_list(snac *snac, xs_list *list, char *header, char *t, cons | |||
| 2820 | xs_html_tag("div", | 2841 | xs_html_tag("div", |
| 2821 | xs_html_attr("class", "snac-post-header"), | 2842 | xs_html_attr("class", "snac-post-header"), |
| 2822 | html_actor_icon(snac, actor, xs_dict_get(actor, "published"), | 2843 | html_actor_icon(snac, actor, xs_dict_get(actor, "published"), |
| 2823 | NULL, NULL, 0, 1, proxy, NULL))); | 2844 | NULL, NULL, 0, 1, proxy, NULL, NULL))); |
| 2824 | 2845 | ||
| 2825 | /* content (user bio) */ | 2846 | /* content (user bio) */ |
| 2826 | const char *c = xs_dict_get(actor, "summary"); | 2847 | const char *c = xs_dict_get(actor, "summary"); |
| @@ -3118,7 +3139,7 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 3118 | xs_html_add(entry, | 3139 | xs_html_add(entry, |
| 3119 | xs_html_tag("div", | 3140 | xs_html_tag("div", |
| 3120 | xs_html_attr("class", "snac-post"), | 3141 | xs_html_attr("class", "snac-post"), |
| 3121 | html_actor_icon(user, actor, NULL, NULL, NULL, 0, 0, proxy, NULL))); | 3142 | html_actor_icon(user, actor, NULL, NULL, NULL, 0, 0, proxy, NULL, NULL))); |
| 3122 | } | 3143 | } |
| 3123 | else | 3144 | else |
| 3124 | if (strcmp(type, "Move") == 0) { | 3145 | if (strcmp(type, "Move") == 0) { |
| @@ -3132,7 +3153,7 @@ xs_str *html_notifications(snac *user, int skip, int show) | |||
| 3132 | xs_html_add(entry, | 3153 | xs_html_add(entry, |
| 3133 | xs_html_tag("div", | 3154 | xs_html_tag("div", |
| 3134 | xs_html_attr("class", "snac-post"), | 3155 | xs_html_attr("class", "snac-post"), |
| 3135 | html_actor_icon(user, old_actor, NULL, NULL, NULL, 0, 0, proxy, NULL))); | 3156 | html_actor_icon(user, old_actor, NULL, NULL, NULL, 0, 0, proxy, NULL, NULL))); |
| 3136 | } | 3157 | } |
| 3137 | } | 3158 | } |
| 3138 | } | 3159 | } |
| @@ -3306,7 +3327,7 @@ int html_get_handler(const xs_dict *req, const char *q_path, | |||
| 3306 | cache = 0; | 3327 | cache = 0; |
| 3307 | 3328 | ||
| 3308 | int skip = 0; | 3329 | int skip = 0; |
| 3309 | int def_show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); | 3330 | int def_show = xs_number_get(xs_dict_get_def(srv_config, "def_timeline_entries", "50")); |
| 3310 | int show = def_show; | 3331 | int show = def_show; |
| 3311 | 3332 | ||
| 3312 | if ((v = xs_dict_get(q_vars, "skip")) != NULL) | 3333 | if ((v = xs_dict_get(q_vars, "skip")) != NULL) |
| @@ -219,7 +219,7 @@ int server_get_handler(xs_dict *req, const char *q_path, | |||
| 219 | if (xs_type(q_vars) == XSTYPE_DICT && (t = xs_dict_get(q_vars, "t"))) { | 219 | if (xs_type(q_vars) == XSTYPE_DICT && (t = xs_dict_get(q_vars, "t"))) { |
| 220 | /** search by tag **/ | 220 | /** search by tag **/ |
| 221 | int skip = 0; | 221 | int skip = 0; |
| 222 | int show = xs_number_get(xs_dict_get(srv_config, "max_timeline_entries")); | 222 | int show = xs_number_get(xs_dict_get_def(srv_config, "def_timeline_entries", "50")); |
| 223 | const char *v; | 223 | const char *v; |
| 224 | 224 | ||
| 225 | if ((v = xs_dict_get(q_vars, "skip")) != NULL) | 225 | if ((v = xs_dict_get(q_vars, "skip")) != NULL) |
| @@ -28,6 +28,7 @@ static const char *default_srv_config = "{" | |||
| 28 | "\"queue_timeout\": 6," | 28 | "\"queue_timeout\": 6," |
| 29 | "\"queue_timeout_2\": 8," | 29 | "\"queue_timeout_2\": 8," |
| 30 | "\"cssurls\": [\"\"]," | 30 | "\"cssurls\": [\"\"]," |
| 31 | "\"def_timeline_entries\": 50," | ||
| 31 | "\"max_timeline_entries\": 50," | 32 | "\"max_timeline_entries\": 50," |
| 32 | "\"timeline_purge_days\": 120," | 33 | "\"timeline_purge_days\": 120," |
| 33 | "\"local_purge_days\": 0," | 34 | "\"local_purge_days\": 0," |
| @@ -11,6 +11,39 @@ xs_dict *xs_multipart_form_data(const char *payload, int p_size, const char *hea | |||
| 11 | 11 | ||
| 12 | #ifdef XS_IMPLEMENTATION | 12 | #ifdef XS_IMPLEMENTATION |
| 13 | 13 | ||
| 14 | char *xs_url_dec_in(char *str, int qs) | ||
| 15 | { | ||
| 16 | char *w = str; | ||
| 17 | char *r; | ||
| 18 | |||
| 19 | for (r = str; *r != '\0'; r++) { | ||
| 20 | switch (*r) { | ||
| 21 | case '%': { | ||
| 22 | unsigned hex; | ||
| 23 | if (!r[1] || !r[2]) | ||
| 24 | return NULL; | ||
| 25 | if (sscanf(r + 1, "%2x", &hex) != 1) | ||
| 26 | return NULL; | ||
| 27 | *w++ = hex; | ||
| 28 | r += 2; | ||
| 29 | break; | ||
| 30 | } | ||
| 31 | |||
| 32 | case '+': | ||
| 33 | if (qs) { | ||
| 34 | *w++ = ' '; | ||
| 35 | break; | ||
| 36 | } | ||
| 37 | /* fall-through */ | ||
| 38 | default: | ||
| 39 | *w++ = *r; | ||
| 40 | } | ||
| 41 | } | ||
| 42 | |||
| 43 | *w++ = '\0'; | ||
| 44 | return str; | ||
| 45 | } | ||
| 46 | |||
| 14 | xs_str *xs_url_dec(const char *str) | 47 | xs_str *xs_url_dec(const char *str) |
| 15 | /* decodes an URL */ | 48 | /* decodes an URL */ |
| 16 | { | 49 | { |
| @@ -76,42 +109,44 @@ xs_dict *xs_url_vars(const char *str) | |||
| 76 | vars = xs_dict_new(); | 109 | vars = xs_dict_new(); |
| 77 | 110 | ||
| 78 | if (xs_is_string(str)) { | 111 | if (xs_is_string(str)) { |
| 79 | /* split by arguments */ | 112 | xs *dup = xs_dup(str); |
| 80 | xs *args = xs_split(str, "&"); | 113 | char *k; |
| 81 | 114 | char *saveptr; | |
| 82 | const xs_val *v; | 115 | for (k = strtok_r(dup, "&", &saveptr); |
| 83 | 116 | k; | |
| 84 | xs_list_foreach(args, v) { | 117 | k = strtok_r(NULL, "&", &saveptr)) { |
| 85 | xs *dv = xs_url_dec(v); | 118 | char *v = strchr(k, '='); |
| 86 | xs *kv = xs_split_n(dv, "=", 1); | 119 | if (!v) |
| 87 | 120 | continue; | |
| 88 | if (xs_list_len(kv) == 2) { | 121 | *v++ = '\0'; |
| 89 | const char *key = xs_list_get(kv, 0); | 122 | k = xs_url_dec_in(k, 1); |
| 90 | const char *pv = xs_dict_get(vars, key); | 123 | v = xs_url_dec_in(v, 1); |
| 91 | 124 | if (!xs_is_string(k) || !xs_is_string(v)) | |
| 92 | if (!xs_is_null(pv)) { | 125 | continue; |
| 93 | /* there is a previous value: convert to a list and append */ | 126 | |
| 94 | xs *vlist = NULL; | 127 | const char *pv = xs_dict_get(vars, k); |
| 95 | if (xs_type(pv) == XSTYPE_LIST) | 128 | if (!xs_is_null(pv)) { |
| 96 | vlist = xs_dup(pv); | 129 | /* there is a previous value: convert to a list and append */ |
| 97 | else { | 130 | xs *vlist = NULL; |
| 98 | vlist = xs_list_new(); | 131 | if (xs_type(pv) == XSTYPE_LIST) |
| 99 | vlist = xs_list_append(vlist, pv); | 132 | vlist = xs_dup(pv); |
| 100 | } | ||
| 101 | |||
| 102 | vlist = xs_list_append(vlist, xs_list_get(kv, 1)); | ||
| 103 | vars = xs_dict_set(vars, key, vlist); | ||
| 104 | } | ||
| 105 | else { | 133 | else { |
| 106 | /* ends with []? force to always be a list */ | 134 | vlist = xs_list_new(); |
| 107 | if (xs_endswith(key, "[]")) { | 135 | vlist = xs_list_append(vlist, pv); |
| 108 | xs *vlist = xs_list_new(); | 136 | } |
| 109 | vlist = xs_list_append(vlist, xs_list_get(kv, 1)); | 137 | |
| 110 | vars = xs_dict_append(vars, key, vlist); | 138 | vlist = xs_list_append(vlist, v); |
| 111 | } | 139 | vars = xs_dict_set(vars, k, vlist); |
| 112 | else | 140 | } |
| 113 | vars = xs_dict_append(vars, key, xs_list_get(kv, 1)); | 141 | else { |
| 142 | /* ends with []? force to always be a list */ | ||
| 143 | if (xs_endswith(k, "[]")) { | ||
| 144 | xs *vlist = xs_list_new(); | ||
| 145 | vlist = xs_list_append(vlist, v); | ||
| 146 | vars = xs_dict_append(vars, k, vlist); | ||
| 114 | } | 147 | } |
| 148 | else | ||
| 149 | vars = xs_dict_append(vars, k, v); | ||
| 115 | } | 150 | } |
| 116 | } | 151 | } |
| 117 | } | 152 | } |