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