commit a5272a2

grunfink  ·  2023-07-11 17:11:21 +0000 UTC
parents eb7bee9, 63741cb
Merge pull request 'Attempt to prevent XSS.' (#64) from yonle/snac2:master into master

Reviewed-on: https://codeberg.org/grunfink/snac2/pulls/64
2 files changed,  +71, -41
M html.c
M xs.h
M html.c
+56, -41
  1@@ -105,7 +105,7 @@ xs_str *html_actor_icon(xs_str *os, char *actor,
  2 
  3     {
  4         xs *s1 = xs_fmt("<a href=\"%s\" class=\"p-author h-card snac-author\">%s</a>",
  5-            xs_dict_get(actor, "id"), name);
  6+            xs_dict_get(actor, "id"), xs_encode_html(name));
  7         s = xs_str_cat(s, s1);
  8     }
  9 
 10@@ -139,7 +139,7 @@ xs_str *html_actor_icon(xs_str *os, char *actor,
 11 
 12         xs *s1 = xs_fmt(
 13             "\n<time class=\"dt-published snac-pubdate\" title=\"%s\">%s</time>\n",
 14-                date_title, date_label);
 15+                xs_encode_html(date_title), xs_encode_html(date_label));
 16 
 17         s = xs_str_cat(s, s1);
 18     }
 19@@ -164,7 +164,7 @@ xs_str *html_actor_icon(xs_str *os, char *actor,
 20 
 21         s1 = xs_fmt(
 22             "<br><a href=\"%s\" class=\"p-author-tag h-card snac-author-tag\">%s</a>",
 23-                xs_dict_get(actor, "id"), user);
 24+                xs_dict_get(actor, "id"), xs_encode_html(user));
 25 
 26         s = xs_str_cat(s, s1);
 27     }
 28@@ -245,9 +245,9 @@ d_char *html_user_header(snac *snac, d_char *s, int local)
 29 
 30     {
 31         xs *s1 = xs_fmt("<title>%s (@%s@%s)</title>\n",
 32-            xs_dict_get(snac->config, "name"),
 33-            snac->uid,
 34-            xs_dict_get(srv_config,   "host"));
 35+            xs_encode_html(xs_dict_get(snac->config, "name")),
 36+            xs_encode_html(snac->uid),
 37+            xs_encode_html(xs_dict_get(srv_config,   "host")));
 38 
 39         s = xs_str_cat(s, s1);
 40     }
 41@@ -284,18 +284,18 @@ d_char *html_user_header(snac *snac, d_char *s, int local)
 42             "<meta property=\"og:image\" content=\"%s\"/>\n"
 43             "<meta property=\"og:image:width\" content=\"300\"/>\n"
 44             "<meta property=\"og:image:height\" content=\"300\"/>\n",
 45-            xs_dict_get(srv_config, "host"),
 46-            xs_dict_get(snac->config, "name"),
 47-            snac->uid,
 48-            xs_dict_get(srv_config, "host"),
 49-            s_bio,
 50-            s_avatar);
 51+            xs_encode_html(xs_dict_get(srv_config, "host")),
 52+            xs_encode_html(xs_dict_get(snac->config, "name")),
 53+            xs_encode_html(snac->uid),
 54+            xs_encode_html(xs_dict_get(srv_config, "host")),
 55+            xs_encode_html(s_bio),
 56+            xs_encode_html(s_avatar));
 57         s = xs_str_cat(s, s1);
 58     }
 59 
 60     {
 61         xs *s1 = xs_fmt("<link rel=\"alternate\" type=\"application/rss+xml\" "
 62-                        "title=\"RSS\" href=\"%s.rss\" />\n", snac->actor);
 63+                        "title=\"RSS\" href=\"%s.rss\" />\n", snac->actor); /* snac->actor is likely need to be URLEncoded. */
 64         s = xs_str_cat(s, s1);
 65     }
 66 
 67@@ -355,16 +355,16 @@ d_char *html_user_header(snac *snac, d_char *s, int local)
 68             "<p class=\"snac-top-user-id\">@%s@%s</p>\n";
 69 
 70         xs *s1 = xs_fmt(_tmpl,
 71-            xs_dict_get(snac->config, "name"),
 72-            xs_dict_get(snac->config, "uid"), xs_dict_get(srv_config, "host")
 73+            xs_encode_html(xs_dict_get(snac->config, "name")),
 74+            xs_encode_html(xs_dict_get(snac->config, "uid")), xs_encode_html(xs_dict_get(srv_config, "host"))
 75         );
 76 
 77         s = xs_str_cat(s, s1);
 78 
 79         if (local) {
 80-            xs *bio1 = not_really_markdown(xs_dict_get(snac->config, "bio"), NULL);
 81+            xs *bio1 = not_really_markdown(xs_encode_html(xs_dict_get(snac->config, "bio")), NULL);
 82             xs *tags = xs_list_new();
 83-            xs *bio2 = process_tags(snac, bio1, &tags);
 84+            xs *bio2 = xs_encode_html(process_tags(snac, bio1, &tags));
 85             xs *s1   = xs_fmt("<div class=\"p-note snac-top-user-bio\">%s</div>\n", bio2);
 86 
 87             s = xs_str_cat(s, s1);
 88@@ -914,7 +914,7 @@ xs_str *html_entry(snac *snac, xs_str *os, const xs_dict *msg, int local,
 89             xs *s1 = xs_fmt(
 90                 "<div class=\"snac-origin\">"
 91                 "<a href=\"%s\">%s</a> %s</a></div>",
 92-                snac->actor, xs_dict_get(snac->config, "name"), L("boosted")
 93+                snac->actor, xs_encode_html(xs_dict_get(snac->config, "name")), L("boosted")
 94             );
 95 
 96             s = xs_str_cat(s, s1);
 97@@ -928,7 +928,7 @@ xs_str *html_entry(snac *snac, xs_str *os, const xs_dict *msg, int local,
 98                     "<div class=\"snac-origin\">"
 99                     "<a href=\"%s\">%s</a> %s</div>\n",
100                     xs_dict_get(actor_r, "id"),
101-                    name,
102+                    xs_encode_html(name),
103                     L("boosted")
104                 );
105 
106@@ -967,7 +967,7 @@ xs_str *html_entry(snac *snac, xs_str *os, const xs_dict *msg, int local,
107         char *cw = xs_dict_get(snac->config, "cw");
108         if (xs_is_null(cw) || local)
109             cw = "";
110-        xs *s1 = xs_fmt("<details %s><summary>%s [%s]</summary>\n", cw, v, L("SENSITIVE CONTENT"));
111+        xs *s1 = xs_fmt("<details %s><summary>%s [%s]</summary>\n", cw, xs_encode_html(v), L("SENSITIVE CONTENT"));
112         s = xs_str_cat(s, s1);
113         sensitive = 1;
114     }
115@@ -1049,7 +1049,7 @@ xs_str *html_entry(snac *snac, xs_str *os, const xs_dict *msg, int local,
116 
117                     if (name && replies) {
118                         int nr = xs_number_get(xs_dict_get(replies, "totalItems"));
119-                        xs *l = xs_fmt("<tr><td>%s:</td><td>%d</td></tr>\n", name, nr);
120+                        xs *l = xs_fmt("<tr><td>%s:</td><td>%d</td></tr>\n", xs_encode_html(name), nr);
121 
122                         c = xs_str_cat(c, l);
123                     }
124@@ -1073,7 +1073,7 @@ xs_str *html_entry(snac *snac, xs_str *os, const xs_dict *msg, int local,
125                         xs *opt = xs_fmt("<input type=\"%s\""
126                                     " id=\"%s\" value=\"%s\" name=\"question\"> %s<br>\n",
127                                     !xs_is_null(oo) ? "radio" : "checkbox",
128-                                    name, name, name);
129+                                    xs_encode_html(name), xs_encode_html(name), xs_encode_html(name));
130 
131                         s1 = xs_str_cat(s1, opt);
132                     }
133@@ -1107,7 +1107,7 @@ xs_str *html_entry(snac *snac, xs_str *os, const xs_dict *msg, int local,
134                         /* skip leading zeros */
135                         for (; *p == '0' || *p == ':'; p++);
136 
137-                        xs *s1 = xs_fmt("<p>%s %s</p>", L("Closes in"), p);
138+                        xs *s1 = xs_fmt("<p>%s %s</p>", L("Closes in"), xs_encode_html(p));
139                         c = xs_str_cat(c, s1);
140                     }
141                 }
142@@ -1136,7 +1136,7 @@ xs_str *html_entry(snac *snac, xs_str *os, const xs_dict *msg, int local,
143 
144             if (xs_startswith(t, "image/")) {
145                 char *url  = xs_dict_get(v, "url");
146-                char *name = xs_dict_get(v, "name");
147+                char *name = xs_encode_html(xs_dict_get(v, "name"));
148 
149                 if (url != NULL) {
150                     if (xs_is_null(name))
151@@ -1151,9 +1151,31 @@ xs_str *html_entry(snac *snac, xs_str *os, const xs_dict *msg, int local,
152             else
153             if (xs_startswith(t, "video/")) {
154                 char *url  = xs_dict_get(v, "url");
155+                char *name = xs_encode_html(xs_dict_get(v, "name"));
156 
157                 if (url != NULL) {
158-                    xs *s1 = xs_fmt("<object data=\"%s\"></object>\n", url);
159+                    xs *s1 = xs_fmt("<video style=\"max-width: 90vw; max-height: 70vh;\" controls src=\"%s\">Video: <a href=\"%s\">%s</a></video>\n", url, url, name ? name : "No description.");
160+
161+                    s = xs_str_cat(s, s1);
162+                }
163+            }
164+            else
165+            if (xs_startswith(t, "audio/")) {
166+                char *url  = xs_dict_get(v, "url");
167+                char *name = xs_encode_html(xs_dict_get(v, "name"));
168+
169+                if (url != NULL) {
170+                    xs *s1 = xs_fmt("<audio style=\"max-width: 90vw\" controls src=\"%s\">Audio: <a href=\"%s\">%s</a></audio>\n", url, url, name ? name : "No description.");
171+
172+                    s = xs_str_cat(s, s1);
173+                }
174+            }
175+            else {
176+                char *url  = xs_dict_get(v, "url");
177+                char *name = xs_encode_html(xs_dict_get(v, "name"));
178+
179+                if (url != NULL) {
180+                    xs *s1 = xs_fmt("<a href=\"%s\">Attachment: %s</a>", url, name ? name : "No description");
181 
182                     s = xs_str_cat(s, s1);
183                 }
184@@ -1327,7 +1349,7 @@ xs_str *html_timeline(snac *snac, const xs_list *list, int local, int skip, int
185 d_char *html_people_list(snac *snac, d_char *os, d_char *list, const char *header, const char *t)
186 {
187     xs *s = xs_str_new(NULL);
188-    xs *h = xs_fmt("<h2 class=\"snac-header\">%s</h2>\n", header);
189+    xs *h = xs_fmt("<h2 class=\"snac-header\">%s</h2>\n", xs_encode_html(header));
190     char *p, *actor_id;
191 
192     s = xs_str_cat(s, h);
193@@ -1357,7 +1379,7 @@ d_char *html_people_list(snac *snac, d_char *os, d_char *list, const char *heade
194                 if (xs_startswith(sc, "<p>"))
195                     s = xs_str_cat(s, sc);
196                 else {
197-                    xs *s1 = xs_fmt("<p>%s</p>", sc);
198+                    xs *s1 = xs_fmt("<p>%s</p>", xs_encode_html(sc));
199                     s = xs_str_cat(s, s1);
200                 }
201 
202@@ -1529,7 +1551,7 @@ xs_str *html_notifications(snac *snac)
203 
204         xs *s1 = xs_fmt("<div class=\"snac-post-with-desc\">\n"
205                         "<p><b>%s by <a href=\"%s\">%s</a></b>:</p>\n",
206-            label, actor_id, a_name);
207+            xs_encode_html(label), actor_id, xs_encode_html(a_name));
208         s = xs_str_cat(s, s1);
209 
210         if (strcmp(type, "Follow") == 0 || strcmp(utype, "Follow") == 0) {
211@@ -1754,9 +1776,6 @@ int html_get_handler(const xs_dict *req, const char *q_path,
212         xs *bio   = not_really_markdown(xs_dict_get(snac.config, "bio"), NULL);
213         char *p, *v;
214 
215-        /* escape tags */
216-        bio = xs_replace_i(bio, "<", "&lt;");
217-        bio = xs_replace_i(bio, ">", "&gt;");
218 
219         rss = xs_fmt(
220             "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n"
221@@ -1766,11 +1785,11 @@ int html_get_handler(const xs_dict *req, const char *q_path,
222             "<language>en</language>\n"
223             "<link>%s.rss</link>\n"
224             "<description>%s</description>\n",
225-            xs_dict_get(snac.config, "name"),
226-            snac.uid,
227-            xs_dict_get(srv_config, "host"),
228+            xs_encode_html(xs_dict_get(snac.config, "name")),
229+            xs_encode_html(snac.uid),
230+            xs_encode_html(xs_dict_get(srv_config, "host")),
231             snac.actor,
232-            bio
233+            xs_encode_html(bio)
234         );
235 
236         p = elems;
237@@ -1785,14 +1804,10 @@ int html_get_handler(const xs_dict *req, const char *q_path,
238             if (!xs_startswith(id, snac.actor))
239                 continue;
240 
241-            xs *content = sanitize(xs_dict_get(msg, "content"));
242+            xs *content = xs_encode_html(sanitize(xs_dict_get(msg, "content")));
243             xs *title   = xs_str_new(NULL);
244             int i;
245 
246-            /* escape tags */
247-            content = xs_replace_i(content, "<", "&lt;");
248-            content = xs_replace_i(content, ">", "&gt;");
249-
250             for (i = 0; content[i] && content[i] != '<' && content[i] != '&' && i < 40; i++)
251                 title = xs_append_m(title, &content[i], 1);
252 
253@@ -1802,7 +1817,7 @@ int html_get_handler(const xs_dict *req, const char *q_path,
254                 "<link>%s</link>\n"
255                 "<description>%s</description>\n"
256                 "</item>\n",
257-                title, id, content
258+                xs_encode_html(title), id, xs_encode_html(content)
259             );
260 
261             rss = xs_str_cat(rss, s);
M xs.h
+15, -0
 1@@ -72,6 +72,7 @@ xs_str *xs_replace_in(xs_str *str, const char *sfrom, const char *sto, int times
 2 xs_str *xs_fmt(const char *fmt, ...);
 3 int xs_str_in(const char *haystack, const char *needle);
 4 int _xs_startsorends(const char *str, const char *xfix, int ends);
 5+xs_str *xs_encode_html(const xs_str *str);
 6 #define xs_startswith(str, prefix) _xs_startsorends(str, prefix, 0)
 7 #define xs_endswith(str, postfix) _xs_startsorends(str, postfix, 1)
 8 xs_str *xs_crop_i(xs_str *str, int start, int end);
 9@@ -506,6 +507,20 @@ int _xs_startsorends(const char *str, const char *xfix, int ends)
10     return !!(ssz >= psz && memcmp(xfix, str + (ends ? ssz - psz : 0), psz) == 0);
11 }
12 
13+xs_str *xs_encode_html(const char *str)
14+/* escapes html characters */
15+{
16+    xs_str *encoded = xs_replace(str, "&", "&amp;");
17+    encoded = xs_replace(encoded, "<", "&lt;");
18+    encoded = xs_replace(encoded, ">", "&gt;");
19+    encoded = xs_replace(encoded, "\"", "&#34;");
20+    encoded = xs_replace(encoded, "'", "&#39;");
21+
22+    // Restore only <br>. Probably safe. Let's hope nothing goes wrong with this.
23+    encoded = xs_replace(encoded, "&lt;br&gt;", "<br>");
24+
25+    return encoded;
26+}
27 
28 xs_str *xs_crop_i(xs_str *str, int start, int end)
29 /* crops the d_char to be only from start to end */