Dunst
Lightweight notification daemon
Loading...
Searching...
No Matches
markup.c
Go to the documentation of this file.
1/* SPDX-License-Identifier: BSD-3-Clause */
8
9#include <assert.h>
10#include <ctype.h>
11#include <stdbool.h>
12#include <stdio.h>
13#include <string.h>
14
15#include "markup.h"
16#include "log.h"
17#include "settings.h"
18#include "utils.h"
19
24static char *markup_quote(char *str)
25{
26 ASSERT_OR_RET(str, NULL);
27
28 str = string_replace_all("&", "&amp;", str);
29 str = string_replace_all("\"", "&quot;", str);
30 str = string_replace_all("'", "&apos;", str);
31 str = string_replace_all("<", "&lt;", str);
32 str = string_replace_all(">", "&gt;", str);
33
34 return str;
35}
36
41static char *markup_unquote(char *str)
42{
43 ASSERT_OR_RET(str, NULL);
44
45 str = string_replace_all("&quot;", "\"", str);
46 str = string_replace_all("&apos;", "'", str);
47 str = string_replace_all("&lt;", "<", str);
48 str = string_replace_all("&gt;", ">", str);
49 str = string_replace_all("&amp;", "&", str);
50
51 return str;
52}
53
58static char *markup_br2nl(char *str)
59{
60 ASSERT_OR_RET(str, NULL);
61
62 str = string_replace_all("<br>", "\n", str);
63 str = string_replace_all("<br/>", "\n", str);
64 str = string_replace_all("<br />", "\n", str);
65 return str;
66}
67
68/* see markup.h */
69void markup_strip_a(char **str, char **urls)
70{
71 assert(*str);
72 char *tag1 = NULL;
73
74 if (urls)
75 *urls = NULL;
76
77 while ((tag1 = strstr(*str, "<a"))) {
78 // use href=" as stated in the notification spec
79 char *href = strstr(tag1, "href=\"");
80 char *tag1_end = strstr(tag1, ">");
81 char *tag2 = strstr(tag1, "</a>");
82
83 // the tag is broken, ignore it
84 if (!tag1_end) {
85 LOG_W("Given link is broken: '%s'", tag1);
86 string_replace_at(*str, tag1-*str, strlen(tag1), "");
87 break;
88 }
89 if (tag2 && tag2 < tag1_end) {
90 int repl_len = (tag2 - tag1) + strlen("</a>");
91 LOG_W("Given link is broken: '%.*s.'", repl_len, tag1);
92 string_replace_at(*str, tag1-*str, repl_len, "");
93 break;
94 }
95
96 // search contents of href attribute
97 char *plain_url = NULL;
98 if (href && href < tag1_end) {
99
100 // shift href to the actual begin of the value
101 href = href+6;
102
103 const char *quote = strstr(href, "\"");
104 if (quote && quote < tag1_end) {
105 plain_url = g_strndup(href, quote-href);
106 }
107 }
108
109 // text between a tags
110 int text_len;
111 if (tag2)
112 text_len = tag2 - (tag1_end+1);
113 else
114 text_len = strlen(tag1_end+1);
115
116 char *text = g_strndup(tag1_end+1, text_len);
117
118 int repl_len = text_len + (tag1_end-tag1) + 1;
119 repl_len += tag2 ? strlen("</a>") : 0;
120
121 *str = string_replace_at(*str, tag1-*str, repl_len, text);
122
123 // if there had been a href attribute,
124 // add it to the URLs
125 if (plain_url && urls) {
126 text = string_replace_all("]", "", text);
127 text = string_replace_all("[", "", text);
128
129 // Prevent dmenu from splitting items
130 text = string_replace_all("\n", "\\n", text);
131
132 char *url = g_strdup_printf("[%s] %s", text, plain_url);
133
134 *urls = string_append(*urls, url, "\n");
135 g_free(url);
136 }
137
138 g_free(plain_url);
139 g_free(text);
140 }
141}
142
143/* see markup.h */
144void markup_strip_img(char **str, char **urls)
145{
146 const char *start;
147
148 if (urls)
149 *urls = NULL;
150
151 while ((start = strstr(*str, "<img"))) {
152 const char *end = strstr(start, ">");
153
154 // the tag is broken, ignore it
155 if (!end) {
156 LOG_W("Given image is broken: '%s'", start);
157 string_replace_at(*str, start-*str, strlen(start), "");
158 break;
159 }
160
161 // use attribute=" as stated in the notification spec
162 const char *alt_s = strstr(start, "alt=\"");
163 const char *src_s = strstr(start, "src=\"");
164
165 char *text_alt = NULL, *text_src = NULL;
166 const char *src_e = NULL, *alt_e = NULL;
167
168 // Move pointer to the actual start and get end
169 if (alt_s) {
170 alt_s += strlen("alt=\"");
171 alt_e = strstr(alt_s, "\"");
172 }
173 if (src_s) {
174 src_s += strlen("src=\"");
175 src_e = strstr(src_s, "\"");
176 }
177
178 /* check if alt and src attribute are given
179 * If both given, check the alignment of all pointers */
180 if ( alt_s && alt_e
181 && src_s && src_e
182 && ( (alt_s < src_s && alt_e < src_s-strlen("src=\"") && src_e < end)
183 ||(src_s < alt_s && src_e < alt_s-strlen("alt=\"") && alt_e < end)) ) {
184
185 text_alt = g_strndup(alt_s, alt_e-alt_s);
186 text_src = g_strndup(src_s, src_e-src_s);
187
188 /* check if single valid alt attribute is available */
189 } else if (alt_s && alt_e && alt_e < end && (!src_s || src_s < alt_s || alt_e < src_s - strlen("src=\""))) {
190 text_alt = g_strndup(alt_s, alt_e-alt_s);
191
192 /* check if single valid src attribute is available */
193 } else if (src_s && src_e && src_e < end && (!alt_s || alt_s < src_s || src_e < alt_s - strlen("alt=\""))) {
194 text_src = g_strndup(src_s, src_e-src_s);
195
196 } else {
197 LOG_W("Given image argument is broken: '%.*s'",
198 (int)(end-start), start);
199 }
200
201 // replacement text for alt
202 int repl_len = end - start + 1;
203
204 if (!text_alt)
205 text_alt = g_strdup("[image]");
206
207 *str = string_replace_at(*str, start-*str, repl_len, text_alt);
208
209 // if there had been a href attribute,
210 // add it to the URLs
211 if (text_src && urls) {
212 text_alt = string_replace_all("]", "", text_alt);
213 text_alt = string_replace_all("[", "", text_alt);
214
215 // Prevent dmenu from splitting items
216 text_alt = string_replace_all("\n", "\\n", text_alt);
217
218 char *url = g_strdup_printf("[%s] %s", text_alt, text_src);
219
220 *urls = string_append(*urls, url, "\n");
221 g_free(url);
222 }
223
224 g_free(text_src);
225 g_free(text_alt);
226 }
227}
228
229/* see markup.h */
230char *markup_strip(char *str)
231{
232 ASSERT_OR_RET(str, NULL);
233
234 /* strip all tags */
235 string_strip_delimited(str, '<', '>');
236
237 /* unquote the remainder */
238 str = markup_unquote(str);
239
240 return str;
241}
242
250static bool markup_is_entity(const char *str)
251{
252 assert(str);
253 assert(*str == '&');
254
255 char *end = strchr(str, ';');
256 ASSERT_OR_RET(end, false);
257
258 // Parse (hexa)decimal entities with the format &#1234; or &#xABC;
259 if (str[1] == '#') {
260 const char *cur = str + 2;
261
262 if (*cur == 'x') {
263 cur++;
264
265 // Reject &#x;
266 if (*cur == ';')
267 return false;
268
269 while (isxdigit(*cur) && cur < end)
270 cur++;
271 } else {
272
273 // Reject &#;
274 if (*cur == ';')
275 return false;
276
277 while (isdigit(*cur) && cur < end)
278 cur++;
279 }
280
281 return (cur == end);
282 } else {
283 const char *supported_tags[] = {"&amp;", "&lt;", "&gt;", "&quot;", "&apos;"};
284 for (size_t i = 0; i < sizeof(supported_tags)/sizeof(*supported_tags); i++) {
285 if (g_str_has_prefix(str, supported_tags[i]))
286 return true;
287 }
288 return false;
289 }
290}
291
298static char *markup_escape_unsupported(char *str)
299{
300 ASSERT_OR_RET(str, NULL);
301
302 char *match = str;
303 while ((match = strchr(match, '&'))) {
304 if (!markup_is_entity(match)) {
305 int pos = match - str;
306 str = string_replace_at(str, pos, 1, "&amp;");
307 match = str + pos + strlen("&amp;");
308 } else {
309 match++;
310 }
311 }
312
313 return str;
314}
315
316/* see markup.h */
317char *markup_transform(char *str, enum markup_mode markup_mode)
318{
319 ASSERT_OR_RET(str, NULL);
320
321 switch (markup_mode) {
322 case MARKUP_NULL:
323 /* `assert(false)`, but with a meaningful error message */
324 assert(markup_mode != MARKUP_NULL);
325 break;
326 case MARKUP_NO:
327 str = markup_quote(str);
328 break;
329 case MARKUP_STRIP:
330 str = markup_br2nl(str);
331 str = markup_strip(str);
332 str = markup_quote(str);
333 break;
334 case MARKUP_FULL:
335 str = markup_escape_unsupported(str);
336 str = markup_br2nl(str);
337 markup_strip_a(&str, NULL);
338 markup_strip_img(&str, NULL);
339 break;
340 }
341
342 if (settings.ignore_newline) {
343 str = string_replace_all("\n", " ", str);
344 }
345
346 return str;
347}
Logging subsystem and helpers.
static char * markup_br2nl(char *str)
Convert all HTML linebreak tags to a newline character.
Definition markup.c:58
static char * markup_unquote(char *str)
Convert all HTML special entities to their actual char.
Definition markup.c:41
void markup_strip_img(char **str, char **urls)
Remove img-tags of a string.
Definition markup.c:144
void markup_strip_a(char **str, char **urls)
Remove HTML hyperlinks of a string.
Definition markup.c:69
static char * markup_escape_unsupported(char *str)
Escape all unsupported and invalid &-entities in a string.
Definition markup.c:298
char * markup_transform(char *str, enum markup_mode markup_mode)
Transform the string in accordance with markup_mode and settings.ignore_newline
Definition markup.c:317
char * markup_strip(char *str)
Strip any markup from text; turn it in to plain text.
Definition markup.c:230
static bool markup_is_entity(const char *str)
Determine if an & character pointed to by str is a markup & entity or part of the text.
Definition markup.c:250
static char * markup_quote(char *str)
Convert all HTML special symbols to HTML entities.
Definition markup.c:24
Markup handling for notifications body.
Type definitions for settings.
char * string_replace_at(char *buf, int pos, int len, const char *repl)
Replace a substring inside a string with another string.
Definition utils.c:37
void string_strip_delimited(char *str, char a, char b)
Strip content between two delimiter characters.
Definition utils.c:126
char * string_replace_all(const char *needle, const char *replacement, char *haystack)
Replace all occurences of a substring.
Definition utils.c:66
char * string_append(char *a, const char *b, const char *sep)
Append b to string a, then concatenate both with sep (if they are non-empty).
Definition utils.c:92
String, time and other various helpers.
#define ASSERT_OR_RET(expr, val)
Assert that expr evaluates to true, if not return val.
Definition utils.h:42