Fix reading past buffer boundary in RSSyl's strreplace.
[claws.git] / src / plugins / rssyl / strutils.c
1 /*
2  * Claws-Mail-- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 2005 Andrej Kacian <andrej@kacian.sk>
4  *
5  * - a strreplace function (something like sed's s/foo/bar/g)
6  *
7  * This program is free software; you can redistribute it and/or modify
8  * it under the terms of the GNU General Public License as published by
9  * the Free Software Foundation; either version 2 of the License, or
10  * (at your option) any later version.
11  *
12  * This program is distributed in the hope that it will be useful,
13  * but WITHOUT ANY WARRANTY; without even the implied warranty of
14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
15  * GNU General Public License for more details.
16  *
17  * You should have received a copy of the GNU General Public License
18  * along with this program; if not, write to the Free Software
19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
20  */
21
22 #ifdef HAVE_CONFIG_H
23 #  include "config.h"
24 #endif
25
26 /* Global includes */
27 #include <glib.h>
28 #include <stdlib.h>
29 #include <ctype.h>
30
31 /* Claws Mail includes */
32 #include <common/utils.h>
33
34 /* Local includes */
35 /* (shouldn't be any) */
36
37 gchar *rssyl_strreplace(gchar *source, gchar *pattern,
38                 gchar *replacement)
39 {
40         gchar *new, *w_new = NULL, *c;
41         guint count = 0, final_length;
42         size_t len_pattern, len_replacement;
43
44         /*
45         debug_print("RSSyl: ======= strreplace: '%s': '%s'->'%s'\n", source, pattern,
46                         replacement);
47         */
48
49         if( source == NULL || pattern == NULL ) {
50                 debug_print("RSSyl: source or pattern is NULL!!!\n");
51                 return source;
52         }
53
54         if( !g_utf8_validate(source, -1, NULL) ) {
55                 debug_print("RSSyl: source is not an UTF-8 encoded text\n");
56                 return source;
57         }
58
59         if( !g_utf8_validate(pattern, -1, NULL) ) {
60                 debug_print("RSSyl: pattern is not an UTF-8 encoded text\n");
61                 return source;
62         }
63
64         len_pattern = strlen(pattern);
65         len_replacement = strlen(replacement);
66
67         c = source;
68         while( ( c = g_strstr_len(c, strlen(c), pattern) ) ) {
69                 count++;
70                 c += len_pattern;
71         }
72
73         /*
74         debug_print("RSSyl: ==== count = %d\n", count);
75         */
76
77         final_length = strlen(source)
78                 - ( count * len_pattern )
79                 + ( count * len_replacement );
80
81         new = malloc(final_length + 1);
82         memset(new, '\0', final_length + 1);
83
84         /* 'c' will be our iterator over original string
85          * 'w_new' our iterator over the new string */
86         c = source;
87         w_new = new;
88
89         /* Go until either end of string is reached, or until the
90          * remaining text is shorter than the pattern. */
91         while( *c != '\0' && strlen(c) <= len_pattern) {
92                 if( !memcmp(c, pattern, len_pattern) ) {
93                         int i;
94                         for (i = 0; i < len_replacement; i++) {
95                                 *w_new = replacement[i];
96                                 w_new++;
97                         }
98                         c = c + len_pattern;
99                 } else {
100                         *w_new = *c;
101                         w_new++;
102                         c++;
103                 }
104         }
105
106         /* We broke off the above cycle because remaining text was not
107          * long enough for the pattern, so now we need to append the
108          * remaining text to the new string. */
109         if (c != '\0') {
110                 strncat(new, c, final_length - strlen(new));
111         }
112
113         return new;
114 }
115
116 typedef struct _RSSyl_HTMLSymbol RSSyl_HTMLSymbol;
117 struct _RSSyl_HTMLSymbol
118 {
119         gchar *const key;
120         gchar *const val;
121 };
122
123 /* TODO: find a way to offload this to a library which knows all the
124  * defined named entities (over 200). */
125 static RSSyl_HTMLSymbol symbol_list[] = {
126         { "lt", "<" },
127         { "gt", ">" },
128         { "amp", "&" },
129         { "apos", "'" },
130         { "quot", "\"" },
131         { "lsquo",  "‘" },
132         { "rsquo",  "’" },
133         { "ldquo",  "“" },
134         { "rdquo",  "”" },
135         { "nbsp", " " },
136         { "trade", "™" },
137         { "copy", "©" },
138         { "reg", "®" },
139         { "hellip", "…" },
140         { "mdash", "—" },
141         { "euro", "€" },
142         { NULL, NULL }
143 };
144
145 static RSSyl_HTMLSymbol tag_list[] = {
146         { "<cite>", "\"" },
147         { "</cite>", "\"" },
148         { "<i>", "" },
149         { "</i>", "" },
150         { "<em>", "" },
151         { "</em>", "" },
152         { "<b>", "" },
153         { "</b>", "" },
154         { "<nobr>", "" },
155         { "</nobr>", "" },
156         { "<wbr>", "" },
157         { NULL, NULL }
158 };
159
160 static gchar *rssyl_replace_chrefs(gchar *string)
161 {
162         char *new = g_malloc0(strlen(string) + 1), *ret;
163         char buf[16], tmp[6];
164         int i, ii, j, n, len;
165         gunichar c;
166         gboolean valid, replaced;
167
168         /* &xx; */
169         ii = 0;
170         for (i = 0; i < strlen(string); ++i) {
171                 if (string[i] == '&') {
172                         j = i+1;
173                         n = 0;
174                         valid = FALSE;
175                         while (string[j] != '\0' && n < 16) {
176                                 if (string[j] != ';') {
177                                         buf[n++] = string[j];
178                                 } else {
179                                         /* End of entity */
180                                         valid = TRUE;
181                                         buf[n] = '\0';
182                                         break;
183                                 }
184                                 j++;
185                         }
186                         if (strlen(buf) > 0 && valid) {
187                                 replaced = FALSE;
188
189                                 if (buf[0] == '#' && (c = atoi(buf+1)) > 0) {
190                                         len = g_unichar_to_utf8(c, tmp);
191                                         tmp[len] = '\0';
192                                         g_strlcat(new, tmp, strlen(string));
193                                         ii += len;
194                                         replaced = TRUE;
195                                 } else {
196                                         for (c = 0; symbol_list[c].key != NULL; c++) {
197                                                 if (!strcmp(buf, symbol_list[c].key)) {
198                                                         g_strlcat(new, symbol_list[c].val, strlen(string));
199                                                         ii += strlen(symbol_list[c].val);
200                                                         replaced = TRUE;
201                                                         break;
202                                                 }
203                                         }
204                                 }
205                                 if (!replaced) {
206                                         new[ii++] = '&'; /* & */
207                                         g_strlcat(new, buf, strlen(string));
208                                         ii += strlen(buf);
209                                         new[ii++] = ';';
210                                 }
211                                 i = j;
212                         } else {
213                                 new[ii++] = string[i];
214                         }
215                 } else {
216                         new[ii++] = string[i];
217                 }
218         }
219
220         ret = g_strdup(new);
221         g_free(new);
222         return ret;
223 }
224
225 gchar *rssyl_replace_html_stuff(gchar *text,
226                 gboolean symbols, gboolean tags)
227 {
228         gchar *tmp = NULL, *wtext = NULL;
229         gint i;
230
231         g_return_val_if_fail(text != NULL, NULL);
232
233         if( symbols ) {
234                 wtext = rssyl_replace_chrefs(text);
235         } else {
236                 wtext = g_strdup(text);
237         }
238
239         /* TODO: rewrite this part to work similarly to rssyl_replace_chrefs() */
240         if( tags ) {
241                 for( i = 0; tag_list[i].key != NULL; i++ ) {
242                         if( g_strstr_len(text, strlen(text), symbol_list[i].key) ) {
243                                 tmp = rssyl_strreplace(wtext, tag_list[i].key, tag_list[i].val);
244                                 g_free(wtext);
245                                 wtext = g_strdup(tmp);
246                                 g_free(tmp);
247                         }
248                 }
249         }
250
251         return wtext;
252 }
253
254 static gchar *rssyl_sanitize_string(gchar *str, gboolean strip_nl)
255 {
256         gchar *new = NULL, *c = str, *n = NULL;
257
258         if( str == NULL )
259                 return NULL;
260
261         n = new = malloc(strlen(str) + 1);
262         memset(new, '\0', strlen(str) + 1);
263
264         while( *c != '\0' ) {
265                 if( !isspace(*c) || *c == ' ' || (!strip_nl && *c == '\n') ) {
266                         *n = *c;
267                         n++;
268                 }
269                 c++;
270         }
271
272         return new;
273 }
274
275 /* rssyl_format_string()
276  * - return value needs to be freed
277  */
278 gchar *rssyl_format_string(gchar *str, gboolean replace_html,
279                 gboolean strip_nl)
280 {
281         gchar *res = NULL, *tmp = NULL;
282
283         g_return_val_if_fail(str != NULL, NULL);
284
285         if (replace_html)
286                 tmp = rssyl_replace_html_stuff(str, TRUE, TRUE);
287         else
288                 tmp = g_strdup(str);
289
290         res = rssyl_sanitize_string(tmp, strip_nl);
291         g_free(tmp);
292
293         g_strstrip(res);
294
295         return res;
296 }
297
298 /* this functions splits a string into an array of string, by 
299  * returning an array of pointers to positions of the delimiter
300  * in the original string and replacing this delimiter with a
301  * NULL. It does not duplicate memory, hence you should only
302  * free the array and not its elements, and you should not
303  * free the original string before you're done with the array.
304  * maybe could be part of the core (utils.c).
305  */
306 gchar **strsplit_no_copy(gchar *str, char delimiter)
307 {
308         gchar **array = g_new(gchar *, 1);
309         int i = 0;
310         gchar *cur = str, *next;
311         
312         array[i] = cur;
313         i++;
314         while ((next = strchr(cur, delimiter)) != NULL) {
315                 *(next) = '\0';
316                 array = g_realloc(array, (sizeof(gchar *)) * (i + 1));
317                 array[i] = next + 1;
318                 cur = next + 1;
319                 i++;
320         }
321         array = g_realloc(array, (sizeof(gchar *)) * (i + 1));
322         array[i] = NULL;
323         return array;
324 }
325
326 /* This is a very dumb function - it just strips <, > and everything between
327  * them. */
328 void strip_html(gchar *str)
329 {
330         gchar *p = str;
331         gboolean intag = FALSE;
332
333         while (*p) {
334                 if (*p == '<')
335                         intag = TRUE;
336                 else if (*p == '>')
337                         intag = FALSE;
338
339                 if (*p == '<' || *p == '>' || intag)
340                         memmove(p, p + 1, strlen(p));
341                 else
342                         p++;
343         }
344 }
345
346 gchar *my_normalize_url(const gchar *url)
347 {
348         gchar *myurl = NULL;
349
350         if (!strncmp(url, "feed://", 7))
351                 myurl = g_strdup(url+7);
352         else if (!strncmp(url, "feed:", 5))
353                 myurl = g_strdup(url+5);
354         else
355                 myurl = g_strdup(url);
356
357         return myurl;
358 }