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