Fixed and improved NNTP authentication support.
[claws.git] / src / xml.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999,2000 Hiroyuki Yamamoto
4  *
5  * This program is free software; you can redistribute it and/or modify
6  * it under the terms of the GNU General Public License as published by
7  * the Free Software Foundation; either version 2 of the License, or
8  * (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License
16  * along with this program; if not, write to the Free Software
17  * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
18  */
19
20 #include <glib.h>
21 #include <stdio.h>
22 #include <string.h>
23 #include <ctype.h>
24
25 #include "xml.h"
26 #include "utils.h"
27
28 static void xml_free_tag        (XMLTag         *tag);
29 static gint xml_get_parenthesis (XMLFile        *file,
30                                  gchar          *buf,
31                                  gint            len);
32
33 XMLFile *xml_open_file(const gchar *path)
34 {
35         XMLFile *newfile;
36
37         g_return_val_if_fail(path != NULL, NULL);
38
39         newfile = g_new(XMLFile, 1);
40
41         newfile->fp = fopen(path, "r");
42         if (!newfile->fp) {
43                 g_free(newfile);
44                 return NULL;
45         }
46
47         newfile->buf = g_string_new(NULL);
48         newfile->bufp = newfile->buf->str;
49
50         newfile->dtd = NULL;
51         newfile->tag_stack = NULL;
52         newfile->level = 0;
53         newfile->is_empty_element = FALSE;
54
55         return newfile;
56 }
57
58 void xml_close_file(XMLFile *file)
59 {
60         g_return_if_fail(file != NULL);
61
62         if (file->fp) fclose(file->fp);
63
64         g_string_free(file->buf, TRUE);
65
66         g_free(file->dtd);
67
68         while (file->tag_stack != NULL)
69                 xml_pop_tag(file);
70
71         g_free(file);
72 }
73
74 static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level)
75 {
76         GNode *node = NULL;
77         XMLNode *xmlnode;
78         XMLTag *tag;
79
80         while (xml_parse_next_tag(file) == 0) {
81                 if (file->level < level) break;
82                 if (file->level == level) {
83                         g_warning("xml_build_tree(): Parse error\n");
84                         break;
85                 }
86
87                 tag = xml_get_current_tag(file);
88                 if (!tag) break;
89                 xmlnode = g_new(XMLNode, 1);
90                 xmlnode->tag = xml_copy_tag(tag);
91                 xmlnode->element = xml_get_element(file);
92                 if (!parent)
93                         node = g_node_new(xmlnode);
94                 else
95                         node = g_node_append_data(parent, xmlnode);
96
97                 xml_build_tree(file, node, file->level);
98                 if (file->level == 0) break;
99         }
100
101         return node;
102 }
103
104 GNode *xml_parse_file(const gchar *path)
105 {
106         XMLFile *file;
107         GNode *node;
108
109         file = xml_open_file(path);
110         g_return_val_if_fail(file != NULL, NULL);
111
112         xml_get_dtd(file);
113
114         node = xml_build_tree(file, NULL, file->level);
115
116         xml_close_file(file);
117         return node;
118 }
119
120 gint xml_get_dtd(XMLFile *file)
121 {
122         gchar buf[XMLBUFSIZE];
123         gchar *bufp = buf;
124
125         if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1;
126
127         if ((*bufp++ == '?') &&
128             (bufp = strcasestr(bufp, "xml")) &&
129             (bufp = strcasestr(bufp + 3, "version")) &&
130             (bufp = strchr(bufp + 7, '?')))
131                 file->dtd = g_strdup(buf);
132         else {
133                 g_warning("Can't get xml dtd\n");
134                 return -1;
135         }
136
137         return 0;
138 }
139
140 gint xml_parse_next_tag(XMLFile *file)
141 {
142         gchar buf[XMLBUFSIZE];
143         gchar *bufp = buf;
144         XMLTag *tag;
145         gint len;
146
147         if (file->is_empty_element == TRUE) {
148                 file->is_empty_element = FALSE;
149                 xml_pop_tag(file);
150                 return 0;
151         }
152
153         if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) {
154                 g_warning("xml_parse_next_tag(): Can't parse next tag\n");
155                 return -1;
156         }
157
158         /* end-tag */
159         if (buf[0] == '/') {
160                 if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) {
161                         g_warning("xml_parse_next_tag(): Tag name mismatch: %s\n", buf);
162                         return -1;
163                 }
164                 xml_pop_tag(file);
165                 return 0;
166         }
167
168         tag = g_new0(XMLTag, 1);
169         xml_push_tag(file, tag);
170
171         len = strlen(buf);
172         if (len > 0 && buf[len - 1] == '/') {
173                 file->is_empty_element = TRUE;
174                 buf[len - 1] = '\0';
175                 g_strchomp(buf);
176         }
177         if (strlen(buf) == 0) {
178                 g_warning("xml_parse_next_tag(): Tag name is empty\n");
179                 return -1;
180         }
181
182         while (*bufp != '\0' && !isspace(*bufp)) bufp++;
183         if (*bufp == '\0') {
184                 tag->tag = g_strdup(buf);
185                 return 0;
186         } else {
187                 *bufp++ = '\0';
188                 tag->tag = g_strdup(buf);
189         }
190
191         /* parse attributes ( name=value ) */
192         while (*bufp) {
193                 XMLAttr *attr;
194                 gchar *attr_name;
195                 gchar *attr_value;
196                 gchar *p;
197                 gchar quote;
198
199                 while (isspace(*bufp)) bufp++;
200                 attr_name = bufp;
201                 if ((p = strchr(attr_name, '=')) == NULL) {
202                         g_warning("xml_parse_next_tag(): Syntax error in tag\n");
203                         return -1;
204                 }
205                 bufp = p;
206                 *bufp++ = '\0';
207                 while (isspace(*bufp)) bufp++;
208
209                 if (*bufp != '"' && *bufp != '\'') {
210                         g_warning("xml_parse_next_tag(): Syntax error in tag\n");
211                         return -1;
212                 }
213                 quote = *bufp;
214                 bufp++;
215                 attr_value = bufp;
216                 if ((p = strchr(attr_value, quote)) == NULL) {
217                         g_warning("xml_parse_next_tag(): Syntax error in tag\n");
218                         return -1;
219                 }
220                 bufp = p;
221                 *bufp++ = '\0';
222
223                 g_strchomp(attr_name);
224                 xml_unescape_str(attr_value);
225
226                 attr = g_new(XMLAttr, 1);
227                 attr->name  = g_strdup(attr_name);
228                 attr->value = g_strdup(attr_value);
229                 tag->attr = g_list_append(tag->attr, attr);
230         }
231
232         return 0;
233 }
234
235 void xml_push_tag(XMLFile *file, XMLTag *tag)
236 {
237         g_return_if_fail(tag != NULL);
238
239         file->tag_stack = g_list_prepend(file->tag_stack, tag);
240         file->level++;
241 }
242
243 void xml_pop_tag(XMLFile *file)
244 {
245         XMLTag *tag;
246
247         if (!file->tag_stack) return;
248
249         tag = (XMLTag *)file->tag_stack->data;
250
251         xml_free_tag(tag);
252         file->tag_stack = g_list_remove(file->tag_stack, tag);
253         file->level--;
254 }
255
256 XMLTag *xml_get_current_tag(XMLFile *file)
257 {
258         if (file->tag_stack)
259                 return (XMLTag *)file->tag_stack->data;
260         else
261                 return NULL;
262 }
263
264 GList *xml_get_current_tag_attr(XMLFile *file)
265 {
266         XMLTag *tag;
267
268         tag = xml_get_current_tag(file);
269         if (!tag) return NULL;
270
271         return tag->attr;
272 }
273
274 gchar *xml_get_element(XMLFile *file)
275 {
276         gchar *str;
277         gchar *end;
278
279         while ((end = strchr(file->bufp, '<')) == NULL)
280                 if (xml_read_line(file) < 0) return NULL;
281
282         if (end == file->bufp)
283                 return NULL;
284
285         str = g_strndup(file->bufp, end - file->bufp);
286         /* this is not XML1.0 strict */
287         g_strstrip(str);
288         xml_unescape_str(str);
289
290         file->bufp = end;
291         xml_truncate_buf(file);
292
293         if (str[0] == '\0') {
294                 g_free(str);
295                 return NULL;
296         }
297
298         return str;
299 }
300
301 gint xml_read_line(XMLFile *file)
302 {
303         gchar buf[XMLBUFSIZE];
304         gint index;
305
306         if (fgets(buf, sizeof(buf), file->fp) == NULL)
307                 return -1;
308
309         index = file->bufp - file->buf->str;
310
311         g_string_append(file->buf, buf);
312
313         file->bufp = file->buf->str + index;
314
315         return 0;
316 }
317
318 void xml_truncate_buf(XMLFile *file)
319 {
320         gint len;
321
322         len = file->bufp - file->buf->str;
323         if (len > 0) {
324                 g_string_erase(file->buf, 0, len);
325                 file->bufp = file->buf->str;
326         }
327 }
328
329 gboolean xml_compare_tag(XMLFile *file, const gchar *name)
330 {
331         XMLTag *tag;
332
333         tag = xml_get_current_tag(file);
334
335         if (tag && strcmp(tag->tag, name) == 0)
336                 return TRUE;
337         else
338                 return FALSE;
339 }
340
341 XMLTag *xml_copy_tag(XMLTag *tag)
342 {
343         XMLTag *new_tag;
344         XMLAttr *attr;
345         GList *list;
346
347         new_tag = g_new(XMLTag, 1);
348         new_tag->tag = g_strdup(tag->tag);
349         new_tag->attr = NULL;
350         for (list = tag->attr; list != NULL; list = list->next) {
351                 attr = xml_copy_attr((XMLAttr *)list->data);
352                 new_tag->attr = g_list_append(new_tag->attr, attr);
353         }
354
355         return new_tag;
356 }
357
358 XMLAttr *xml_copy_attr(XMLAttr *attr)
359 {
360         XMLAttr *new_attr;
361
362         new_attr = g_new(XMLAttr, 1);
363         new_attr->name  = g_strdup(attr->name);
364         new_attr->value = g_strdup(attr->value);
365
366         return new_attr;
367 }
368
369 gint xml_unescape_str(gchar *str)
370 {
371         gchar *start;
372         gchar *end;
373         gchar *p = str;
374         gchar *esc_str;
375         gchar ch;
376         gint len;
377
378         while ((start = strchr(p, '&')) != NULL) {
379                 if ((end = strchr(start + 1, ';')) == NULL) {
380                         g_warning("Unescaped `&' appeared\n");
381                         p = start + 1;
382                         continue;
383                 }
384                 len = end - start + 1;
385                 if (len < 3) {
386                         p = end + 1;
387                         continue;
388                 }
389
390                 Xstrndup_a(esc_str, start, len, return -1);
391                 if (!strcmp(esc_str, "&lt;"))
392                         ch = '<';
393                 else if (!strcmp(esc_str, "&gt;"))
394                         ch = '>';
395                 else if (!strcmp(esc_str, "&amp;"))
396                         ch = '&';
397                 else if (!strcmp(esc_str, "&apos;"))
398                         ch = '\'';
399                 else if (!strcmp(esc_str, "&quot;"))
400                         ch = '\"';
401                 else {
402                         p = end + 1;
403                         continue;
404                 }
405
406                 *start = ch;
407                 memmove(start + 1, end + 1, strlen(end + 1) + 1);
408                 p = start + 1;
409         }
410
411         return 0;
412 }
413
414 gint xml_file_put_escape_str(FILE *fp, const gchar *str)
415 {
416         const gchar *p;
417
418         g_return_val_if_fail(fp != NULL, -1);
419
420         if (!str) return 0;
421
422         for (p = str; *p != '\0'; p++) {
423                 switch (*p) {
424                 case '<':
425                         fputs("&lt;", fp);
426                         break;
427                 case '>':
428                         fputs("&gt;", fp);
429                         break;
430                 case '&':
431                         fputs("&amp;", fp);
432                         break;
433                 case '\'':
434                         fputs("&apos;", fp);
435                         break;
436                 case '\"':
437                         fputs("&quot;", fp);
438                         break;
439                 default:
440                         fputc(*p, fp);
441                 }
442         }
443
444         return 0;
445 }
446
447 void xml_free_node(XMLNode *node)
448 {
449         if (!node) return;
450
451         xml_free_tag(node->tag);
452         g_free(node->element);
453         g_free(node);
454 }
455
456 static gboolean xml_free_func(GNode *node, gpointer data)
457 {
458         XMLNode *xmlnode = node->data;
459
460         xml_free_node(xmlnode);
461         return FALSE;
462 }
463
464 void xml_free_tree(GNode *node)
465 {
466         g_return_if_fail(node != NULL);
467
468         g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func,
469                         NULL);
470
471         g_node_destroy(node);
472 }
473
474 static void xml_free_tag(XMLTag *tag)
475 {
476         if (!tag) return;
477
478         g_free(tag->tag);
479         while (tag->attr != NULL) {
480                 XMLAttr *attr = (XMLAttr *)tag->attr->data;
481                 g_free(attr->name);
482                 g_free(attr->value);
483                 g_free(attr);
484                 tag->attr = g_list_remove(tag->attr, tag->attr->data);
485         }
486         g_free(tag);
487 }
488
489 static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len)
490 {
491         gchar *start;
492         gchar *end;
493
494         buf[0] = '\0';
495
496         while ((start = strchr(file->bufp, '<')) == NULL)
497                 if (xml_read_line(file) < 0) return -1;
498
499         start++;
500         file->bufp = start;
501
502         while ((end = strchr(file->bufp, '>')) == NULL)
503                 if (xml_read_line(file) < 0) return -1;
504
505         strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len));
506         g_strstrip(buf);
507         file->bufp = end + 1;
508         xml_truncate_buf(file);
509
510         return 0;
511 }