fix indent (broken in last commit)
[claws.git] / src / common / xml.c
1 /*
2  * Sylpheed -- a GTK+ based, lightweight, and fast e-mail client
3  * Copyright (C) 1999-2012 Hiroyuki Yamamoto and the Claws Mail team
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 3 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, see <http://www.gnu.org/licenses/>.
17  * 
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 /* UGLY! */
28 #include "../codeconv.h"
29
30 #define SPARSE_MEMORY
31 /* if this is defined all attr.names and tag.names are stored
32  * in a hash table */
33 #if defined(SPARSE_MEMORY)
34 #include "stringtable.h" 
35
36 static StringTable *xml_string_table;
37 static XMLTag  *xml_copy_tag            (XMLTag         *tag);
38 static XMLAttr *xml_copy_attr           (XMLAttr        *attr);
39 static void xml_free_node               (XMLNode        *node);
40 static void xml_free_tag                (XMLTag         *tag);
41 static void xml_pop_tag         (XMLFile        *file);
42 static void xml_push_tag                (XMLFile        *file,
43                                  XMLTag         *tag);
44 static gint xml_read_line               (XMLFile        *file);
45 static void xml_truncate_buf            (XMLFile        *file);
46 static gint xml_unescape_str            (gchar          *str);
47
48 static void xml_string_table_create(void)
49 {
50         if (xml_string_table == NULL)
51                 xml_string_table = string_table_new();
52 }
53 #define XML_STRING_ADD(str) \
54         string_table_insert_string(xml_string_table, (str))
55 #define XML_STRING_FREE(str) \
56         string_table_free_string(xml_string_table, (str))
57
58 #define XML_STRING_TABLE_CREATE() \
59         xml_string_table_create()
60
61 #else /* !SPARSE_MEMORY */
62
63 #define XML_STRING_ADD(str) \
64         g_strdup(str)
65 #define XML_STRING_FREE(str) \
66         g_free(str)
67
68 #define XML_STRING_TABLE_CREATE()
69
70 #endif /* SPARSE_MEMORY */
71
72 static gint xml_get_parenthesis (XMLFile        *file,
73                                  gchar          *buf,
74                                  gint            len);
75
76 XMLFile *xml_open_file(const gchar *path)
77 {
78         XMLFile *newfile;
79
80         cm_return_val_if_fail(path != NULL, NULL);
81
82         newfile = g_new(XMLFile, 1);
83
84         newfile->fp = g_fopen(path, "rb");
85         if (!newfile->fp) {
86                 g_free(newfile);
87                 return NULL;
88         }
89
90         XML_STRING_TABLE_CREATE();
91
92         newfile->buf = g_string_new(NULL);
93         newfile->bufp = newfile->buf->str;
94
95         newfile->dtd = NULL;
96         newfile->encoding = NULL;
97         newfile->tag_stack = NULL;
98         newfile->level = 0;
99         newfile->is_empty_element = FALSE;
100
101         return newfile;
102 }
103
104 void xml_close_file(XMLFile *file)
105 {
106         cm_return_if_fail(file != NULL);
107
108         if (file->fp) fclose(file->fp);
109
110         g_string_free(file->buf, TRUE);
111
112         g_free(file->dtd);
113         g_free(file->encoding);
114
115         while (file->tag_stack != NULL)
116                 xml_pop_tag(file);
117
118         g_free(file);
119 }
120
121 static GNode *xml_build_tree(XMLFile *file, GNode *parent, guint level)
122 {
123         GNode *node = NULL;
124         XMLNode *xmlnode;
125         XMLTag *tag;
126
127         while (xml_parse_next_tag(file) == 0) {
128                 if (file->level < level) break;
129                 if (file->level == level) {
130                         g_warning("xml_build_tree(): Parse error\n");
131                         break;
132                 }
133
134                 tag = xml_get_current_tag(file);
135                 if (!tag) break;
136                 xmlnode = xml_node_new(xml_copy_tag(tag), NULL);
137                 xmlnode->element = xml_get_element(file);
138                 if (!parent)
139                         node = g_node_new(xmlnode);
140                 else
141                         node = g_node_append_data(parent, xmlnode);
142
143                 xml_build_tree(file, node, file->level);
144                 if (file->level == 0) break;
145         }
146
147         return node;
148 }
149
150 GNode *xml_parse_file(const gchar *path)
151 {
152         XMLFile *file;
153         GNode *node;
154
155         file = xml_open_file(path);
156         cm_return_val_if_fail(file != NULL, NULL);
157
158         xml_get_dtd(file);
159
160         node = xml_build_tree(file, NULL, file->level);
161
162         xml_close_file(file);
163
164 #if defined(SPARSE_MEMORY)
165         if (debug_get_mode())
166                 string_table_get_stats(xml_string_table);
167 #endif
168
169         return node;
170 }
171
172 gint xml_get_dtd(XMLFile *file)
173 {
174         gchar buf[XMLBUFSIZE];
175         gchar *bufp = buf;
176
177         if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) return -1;
178
179         if ((*bufp++ == '?') &&
180             (bufp = strcasestr(bufp, "xml")) &&
181             (bufp = strcasestr(bufp + 3, "version")) &&
182             (bufp = strchr(bufp + 7, '?'))) {
183                 file->dtd = g_strdup(buf);
184                 if ((bufp = strcasestr(buf, "encoding=\""))) {
185                         bufp += 9;
186                         extract_quote(bufp, '"');
187                         file->encoding = g_strdup(bufp);
188                         file->need_codeconv =
189                                 strcmp2(bufp, CS_INTERNAL);
190                 } else {
191                         file->encoding = g_strdup(CS_INTERNAL);
192                         file->need_codeconv = FALSE;
193                 }
194         } else {
195                 g_warning("Can't get xml dtd\n");
196                 return -1;
197         }
198
199         return 0;
200 }
201
202 gint xml_parse_next_tag(XMLFile *file)
203 {
204         gchar buf[XMLBUFSIZE];
205         gchar *bufp = buf;
206         gchar *tag_str;
207         XMLTag *tag;
208         gint len;
209
210 next:
211         if (file->is_empty_element == TRUE) {
212                 file->is_empty_element = FALSE;
213                 xml_pop_tag(file);
214                 return 0;
215         }
216
217         if (xml_get_parenthesis(file, buf, sizeof(buf)) < 0) {
218                 g_warning("xml_parse_next_tag(): Can't parse next tag\n");
219                 return -1;
220         }
221
222         len = strlen(buf);
223
224         /* end-tag */
225         if (buf[0] == '/') {
226                 if (strcmp(xml_get_current_tag(file)->tag, buf + 1) != 0) {
227                         g_warning("xml_parse_next_tag(): Tag name mismatch: %s (%s)\n", buf, xml_get_current_tag(file)->tag);
228                         return -1;
229                 }
230                 xml_pop_tag(file);
231                 return 0;
232         }
233
234         if (len >= 7 && !strncmp(buf, "!-- ", 4) && !strncmp(buf+len-3, " --", 3)) {
235                 /* skip comment */
236                 goto next;
237         }
238
239         tag = xml_tag_new(NULL);
240         xml_push_tag(file, tag);
241
242         if (len > 0 && buf[len - 1] == '/') {
243                 file->is_empty_element = TRUE;
244                 buf[len - 1] = '\0';
245                 g_strchomp(buf);
246         }
247         
248         if (strlen(buf) == 0) {
249                 g_warning("xml_parse_next_tag(): Tag name is empty\n");
250                 return -1;
251         }
252
253         while (*bufp != '\0' && !g_ascii_isspace(*bufp)) bufp++;
254         if (*bufp == '\0') {
255                 if (file->need_codeconv) {
256                         tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
257                         if (tag_str) {
258                                 tag->tag = XML_STRING_ADD(tag_str);
259                                 g_free(tag_str);
260                         } else
261                                 tag->tag = XML_STRING_ADD(buf);
262                 } else
263                         tag->tag = XML_STRING_ADD(buf);
264                 return 0;
265         } else {
266                 *bufp++ = '\0';
267                 if (file->need_codeconv) {
268                         tag_str = conv_codeset_strdup(buf, file->encoding, CS_INTERNAL);
269                         if (tag_str) {
270                                 tag->tag = XML_STRING_ADD(tag_str);
271                                 g_free(tag_str);
272                         } else
273                                 tag->tag = XML_STRING_ADD(buf);
274                 } else
275                         tag->tag = XML_STRING_ADD(buf);
276         }
277
278         /* parse attributes ( name=value ) */
279         while (*bufp) {
280                 XMLAttr *attr;
281                 gchar *attr_name;
282                 gchar *attr_value;
283                 gchar *utf8_attr_name;
284                 gchar *utf8_attr_value;
285                 gchar *p;
286                 gchar quote;
287
288                 while (g_ascii_isspace(*bufp)) bufp++;
289                 attr_name = bufp;
290                 if ((p = strchr(attr_name, '=')) == NULL) {
291                         g_warning("xml_parse_next_tag(): Syntax error in tag (a) %s\n", attr_name);
292                         return -1;
293                 }
294                 bufp = p;
295                 *bufp++ = '\0';
296                 while (g_ascii_isspace(*bufp)) bufp++;
297
298                 if (*bufp != '"' && *bufp != '\'') {
299                         g_warning("xml_parse_next_tag(): Syntax error in tag (b) %s\n", bufp);
300                         return -1;
301                 }
302                 quote = *bufp;
303                 bufp++;
304                 attr_value = bufp;
305                 if ((p = strchr(attr_value, quote)) == NULL) {
306                         g_warning("xml_parse_next_tag(): Syntax error in tag (c) %s\n", attr_value);
307                         return -1;
308                 }
309                 bufp = p;
310                 *bufp++ = '\0';
311
312                 g_strchomp(attr_name);
313                 xml_unescape_str(attr_value);
314                 if (file->need_codeconv) {
315                         utf8_attr_name = conv_codeset_strdup
316                                 (attr_name, file->encoding, CS_INTERNAL);
317                         utf8_attr_value = conv_codeset_strdup
318                                 (attr_value, file->encoding, CS_INTERNAL);
319                         if (!utf8_attr_name)
320                                 utf8_attr_name = g_strdup(attr_name);
321                         if (!utf8_attr_value)
322                                 utf8_attr_value = g_strdup(attr_value);
323
324                         attr = xml_attr_new(utf8_attr_name, utf8_attr_value);
325                         g_free(utf8_attr_value);
326                         g_free(utf8_attr_name);
327                 } else {
328                         attr = xml_attr_new(attr_name, attr_value);
329                 }
330                 xml_tag_add_attr(tag, attr);
331
332         }
333         tag->attr = g_list_reverse(tag->attr);
334
335         return 0;
336 }
337
338 static void xml_push_tag(XMLFile *file, XMLTag *tag)
339 {
340         cm_return_if_fail(tag != NULL);
341
342         file->tag_stack = g_list_prepend(file->tag_stack, tag);
343         file->level++;
344 }
345
346 static void xml_pop_tag(XMLFile *file)
347 {
348         XMLTag *tag;
349
350         if (!file->tag_stack) return;
351
352         tag = (XMLTag *)file->tag_stack->data;
353
354         xml_free_tag(tag);
355         file->tag_stack = g_list_remove(file->tag_stack, tag);
356         file->level--;
357 }
358
359 XMLTag *xml_get_current_tag(XMLFile *file)
360 {
361         if (file->tag_stack)
362                 return (XMLTag *)file->tag_stack->data;
363         else
364                 return NULL;
365 }
366
367 GList *xml_get_current_tag_attr(XMLFile *file)
368 {
369         XMLTag *tag;
370
371         tag = xml_get_current_tag(file);
372         if (!tag) return NULL;
373
374         return tag->attr;
375 }
376
377 gchar *xml_get_element(XMLFile *file)
378 {
379         gchar *str;
380         gchar *new_str;
381         gchar *end;
382
383         while ((end = strchr(file->bufp, '<')) == NULL)
384                 if (xml_read_line(file) < 0) return NULL;
385
386         if (end == file->bufp)
387                 return NULL;
388
389         str = g_strndup(file->bufp, end - file->bufp);
390         /* this is not XML1.0 strict */
391         g_strstrip(str);
392         xml_unescape_str(str);
393
394         file->bufp = end;
395         xml_truncate_buf(file);
396
397         if (str[0] == '\0') {
398                 g_free(str);
399                 return NULL;
400         }
401
402         if (!file->need_codeconv)
403                 return str;
404
405         new_str = conv_codeset_strdup(str, file->encoding, CS_INTERNAL);
406         if (!new_str)
407                 new_str = g_strdup(str);
408         g_free(str);
409
410         return new_str;
411 }
412
413 static gint xml_read_line(XMLFile *file)
414 {
415         gchar buf[XMLBUFSIZE];
416         gint index;
417
418         if (fgets(buf, sizeof(buf), file->fp) == NULL)
419                 return -1;
420
421         index = file->bufp - file->buf->str;
422
423         g_string_append(file->buf, buf);
424
425         file->bufp = file->buf->str + index;
426
427         return 0;
428 }
429
430 static void xml_truncate_buf(XMLFile *file)
431 {
432         gint len;
433
434         len = file->bufp - file->buf->str;
435         if (len > 0) {
436                 g_string_erase(file->buf, 0, len);
437                 file->bufp = file->buf->str;
438         }
439 }
440
441 gboolean xml_compare_tag(XMLFile *file, const gchar *name)
442 {
443         XMLTag *tag;
444
445         tag = xml_get_current_tag(file);
446
447         if (tag && strcmp(tag->tag, name) == 0)
448                 return TRUE;
449         else
450                 return FALSE;
451 }
452
453 XMLNode *xml_node_new(XMLTag *tag, const gchar *text)
454 {
455         XMLNode *node;
456
457         node = g_new(XMLNode, 1);
458         node->tag = tag;
459         node->element = g_strdup(text);
460
461         return node;
462 }
463
464 XMLTag *xml_tag_new(const gchar *tag)
465 {
466         XMLTag *new_tag;
467  
468         new_tag = g_new(XMLTag, 1);
469         if (tag)
470                 new_tag->tag = XML_STRING_ADD(tag);
471         else
472                 new_tag->tag = NULL;
473         new_tag->attr = NULL;
474  
475         return new_tag;
476 }
477
478 XMLAttr *xml_attr_new(const gchar *name, const gchar *value)
479 {
480         XMLAttr *new_attr;
481  
482         new_attr = g_new(XMLAttr, 1);
483         new_attr->name = XML_STRING_ADD(name);
484         new_attr->value = g_strdup(value);
485  
486         return new_attr;
487 }
488
489 XMLAttr *xml_attr_new_int(const gchar *name, const gint value)
490 {
491         XMLAttr *new_attr;
492         gchar *valuestr;
493
494         valuestr = g_strdup_printf("%d", value);
495
496         new_attr = g_new(XMLAttr, 1);
497         new_attr->name = XML_STRING_ADD(name);
498         new_attr->value = g_strdup(valuestr);
499  
500         g_free(valuestr);
501
502         return new_attr;
503 }
504
505 void xml_tag_add_attr(XMLTag *tag, XMLAttr *attr)
506 {
507         tag->attr = g_list_prepend(tag->attr, attr);
508 }
509
510 static XMLTag *xml_copy_tag(XMLTag *tag)
511 {
512         XMLTag *new_tag;
513         XMLAttr *attr;
514         GList *list;
515
516         new_tag = xml_tag_new(tag->tag);
517         for (list = tag->attr; list != NULL; list = list->next) {
518                 attr = xml_copy_attr((XMLAttr *)list->data);
519                 xml_tag_add_attr(new_tag, attr);
520         }
521         tag->attr = g_list_reverse(tag->attr);
522
523         return new_tag;
524 }
525
526 static XMLAttr *xml_copy_attr(XMLAttr *attr)
527 {
528         return xml_attr_new(attr->name, attr->value);
529 }
530
531 static gint xml_unescape_str(gchar *str)
532 {
533         gchar *start;
534         gchar *end;
535         gchar *p = str;
536         gchar *esc_str;
537         gchar ch;
538         gint len;
539
540         while ((start = strchr(p, '&')) != NULL) {
541                 if ((end = strchr(start + 1, ';')) == NULL) {
542                         g_warning("Unescaped '&' appeared\n");
543                         p = start + 1;
544                         continue;
545                 }
546                 len = end - start + 1;
547                 if (len < 3) {
548                         p = end + 1;
549                         continue;
550                 }
551
552                 Xstrndup_a(esc_str, start, len, return -1);
553                 if (!strcmp(esc_str, "&lt;"))
554                         ch = '<';
555                 else if (!strcmp(esc_str, "&gt;"))
556                         ch = '>';
557                 else if (!strcmp(esc_str, "&amp;"))
558                         ch = '&';
559                 else if (!strcmp(esc_str, "&apos;"))
560                         ch = '\'';
561                 else if (!strcmp(esc_str, "&quot;"))
562                         ch = '\"';
563                 else {
564                         p = end + 1;
565                         continue;
566                 }
567
568                 *start = ch;
569                 memmove(start + 1, end + 1, strlen(end + 1) + 1);
570                 p = start + 1;
571         }
572
573         return 0;
574 }
575
576 gint xml_file_put_escape_str(FILE *fp, const gchar *str)
577 {
578         const gchar *p;
579         int result = 0;
580         cm_return_val_if_fail(fp != NULL, -1);
581
582         if (!str) return 0;
583
584         for (p = str; *p != '\0'; p++) {
585                 switch (*p) {
586                 case '<':
587                         result = fputs("&lt;", fp);
588                         break;
589                 case '>':
590                         result = fputs("&gt;", fp);
591                         break;
592                 case '&':
593                         result = fputs("&amp;", fp);
594                         break;
595                 case '\'':
596                         result = fputs("&apos;", fp);
597                         break;
598                 case '\"':
599                         result = fputs("&quot;", fp);
600                         break;
601                 default:
602                         result = fputc(*p, fp);
603                 }
604         }
605
606         return (result == EOF ? -1 : 0);
607 }
608
609 gint xml_file_put_xml_decl(FILE *fp)
610 {
611         cm_return_val_if_fail(fp != NULL, -1);
612         XML_STRING_TABLE_CREATE();
613
614         return fprintf(fp, "<?xml version=\"1.0\" encoding=\"%s\"?>\n", CS_INTERNAL);
615 }
616
617 static void xml_free_node(XMLNode *node)
618 {
619         if (!node) return;
620
621         xml_free_tag(node->tag);
622         g_free(node->element);
623         g_free(node);
624 }
625
626 static gboolean xml_free_func(GNode *node, gpointer data)
627 {
628         XMLNode *xmlnode = node->data;
629
630         xml_free_node(xmlnode);
631         return FALSE;
632 }
633
634 void xml_free_tree(GNode *node)
635 {
636         cm_return_if_fail(node != NULL);
637
638         g_node_traverse(node, G_PRE_ORDER, G_TRAVERSE_ALL, -1, xml_free_func,
639                         NULL);
640
641         g_node_destroy(node);
642 }
643
644 static void xml_free_tag(XMLTag *tag)
645 {
646         if (!tag) return;
647
648         XML_STRING_FREE(tag->tag);
649         while (tag->attr != NULL) {
650                 XMLAttr *attr = (XMLAttr *)tag->attr->data;
651                 tag->attr = g_list_remove(tag->attr, tag->attr->data);
652                 XML_STRING_FREE(attr->name);
653                 g_free(attr->value); /* __not__ XML_STRING_FREE */
654                 g_free(attr);
655         }
656         g_free(tag);
657 }
658
659 static gint xml_get_parenthesis(XMLFile *file, gchar *buf, gint len)
660 {
661         gchar *start;
662         gchar *end;
663
664         buf[0] = '\0';
665
666         while ((start = strchr(file->bufp, '<')) == NULL)
667                 if (xml_read_line(file) < 0) return -1;
668
669         start++;
670         file->bufp = start;
671
672         while ((end = strchr(file->bufp, '>')) == NULL)
673                 if (xml_read_line(file) < 0) return -1;
674
675         strncpy2(buf, file->bufp, MIN(end - file->bufp + 1, len));
676         g_strstrip(buf);
677         file->bufp = end + 1;
678         xml_truncate_buf(file);
679
680         return 0;
681 }
682
683 #define TRY(func) \
684 if (!(func)) \
685 { \
686         g_warning("failed to write part of xml tree\n"); \
687         return -1; \
688 } \
689
690 static int xml_write_tree_recursive(GNode *node, FILE *fp)
691 {
692         gint i, depth;
693         XMLTag *tag;
694         GList *cur;
695
696         cm_return_val_if_fail(node != NULL, -1);
697         cm_return_val_if_fail(fp != NULL, -1);
698
699         depth = g_node_depth(node) - 1;
700         for (i = 0; i < depth; i++)
701                 TRY(fputs("    ", fp) != EOF);
702
703         tag = ((XMLNode *) node->data)->tag;
704
705         TRY(fprintf(fp, "<%s", tag->tag) > 0);
706
707         for (cur = tag->attr; cur != NULL; cur = g_list_next(cur)) {
708                 XMLAttr *attr = (XMLAttr *) cur->data;
709
710                 TRY(fprintf(fp, " %s=\"", attr->name) > 0);
711                 TRY(xml_file_put_escape_str(fp, attr->value) == 0);
712                 TRY(fputs("\"", fp) != EOF);
713                 
714         }
715
716         if (node->children) {
717                 GNode *child;
718                 TRY(fputs(">\n", fp) != EOF);
719
720                 child = node->children;
721                 while (child) {
722                         GNode *cur;
723
724                         cur = child;
725                         child = cur->next;
726                         TRY(xml_write_tree_recursive(cur, fp) == 0);
727                 }
728
729                 for (i = 0; i < depth; i++)
730                         TRY(fputs("    ", fp) != EOF);
731                 TRY(fprintf(fp, "</%s>\n", tag->tag) > 0);
732         } else
733                 TRY(fputs(" />\n", fp) != EOF);
734         
735         return 0;
736 }
737
738 #undef TRY
739
740 int xml_write_tree(GNode *node, FILE *fp)
741 {
742         return xml_write_tree_recursive(node, fp);
743 }
744
745 static gpointer copy_node_func(gpointer nodedata, gpointer data)
746 {
747         XMLNode *xmlnode = (XMLNode *) nodedata;
748         XMLNode *newxmlnode;
749         
750         newxmlnode = g_new0(XMLNode, 1);
751         newxmlnode->tag = xml_copy_tag(xmlnode->tag);
752         newxmlnode->element = g_strdup(xmlnode->element);
753
754         return newxmlnode;
755 }
756
757 GNode *xml_copy_tree(GNode *node)
758 {
759         return g_node_map(node, copy_node_func, NULL);
760 }