summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--RELEASE_NOTES.md8
-rw-r--r--doc/snac.82
-rw-r--r--html.c41
-rw-r--r--httpd.c2
-rw-r--r--utils.c1
-rw-r--r--xs_url.h103
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
7Each notification includes a link labelled `Context`, that leads to a page with the full conversation tree the post is a part of. 7Each notification includes a link labelled `Context`, that leads to a page with the full conversation tree the post is a part of.
8 8
9Fixed more crashes (thank you very much to inz for helping me debugging this). 9Fixed more crashes (contributed by inz).
10 10
11Fixed link detection in posts (contributed by inz). 11Fixed link detection in posts (contributed by inz).
12 12
13Allow multiple editors for command-line posts (contributed by inz). 13Allow multiple editors for command-line posts (contributed by inz).
14 14
15Separated maximum and default timeline entry count, allowing larger timelines to be requested without having to increase the default (contributed by lxo).
16
17Turned 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
19Special 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
17Fixed memory leak (contributed by inz). 23Fixed memory leak (contributed by inz).
diff --git a/doc/snac.8 b/doc/snac.8
index e9b33dd..aac9f70 100644
--- a/doc/snac.8
+++ b/doc/snac.8
@@ -154,6 +154,8 @@ to those servers that went timeout in the previous retry. If you want to
154give slow servers a chance to receive your messages, you can increase this 154give slow servers a chance to receive your messages, you can increase this
155value (but also take into account that processing the queue will take longer 155value (but also take into account that processing the queue will take longer
156while waiting for these molasses to respond). 156while waiting for these molasses to respond).
157.It Ic def_timeline_entries
158This is the default timeline entries shown in the web interface.
157.It Ic max_timeline_entries 159.It Ic max_timeline_entries
158This is the maximum timeline entries shown in the web interface. 160This is the maximum timeline entries shown in the web interface.
159.It Ic timeline_purge_days 161.It Ic timeline_purge_days
diff --git a/html.c b/html.c
index 5f1cd6c..13615b9 100644
--- a/html.c
+++ b/html.c
@@ -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
116xs_html *html_actor_icon(snac *user, xs_dict *actor, const char *date, 117xs_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
264xs_html *html_msg_icon(snac *user, const char *actor_id, const xs_dict *msg, const char *proxy) 285xs_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)
diff --git a/httpd.c b/httpd.c
index e0a36b6..b856b7d 100644
--- a/httpd.c
+++ b/httpd.c
@@ -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)
diff --git a/utils.c b/utils.c
index ae1c1b6..b799813 100644
--- a/utils.c
+++ b/utils.c
@@ -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,"
diff --git a/xs_url.h b/xs_url.h
index 8e2e243..bb31779 100644
--- a/xs_url.h
+++ b/xs_url.h
@@ -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
14char *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
14xs_str *xs_url_dec(const char *str) 47xs_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 }