1 | /****************** 2 | Copyright (c) 2002 RIPE NCC 3 | 4 | All Rights Reserved 5 | 6 | Permission to use, copy, modify, and distribute this software and its 7 | documentation for any purpose and without fee is hereby granted, 8 | provided that the above copyright notice appear in all copies and that 9 | both that copyright notice and this permission notice appear in 10 | supporting documentation, and that the name of the author not be 11 | used in advertising or publicity pertaining to distribution of the 12 | software without specific, written prior permission. 13 | 14 | THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE, INCLUDING 15 | ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS; IN NO EVENT SHALL 16 | AUTHOR BE LIABLE FOR ANY SPECIAL, INDIRECT OR CONSEQUENTIAL DAMAGES OR ANY 17 | DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN 18 | AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 19 | OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 20 | ***************************************/ 21 | 22 | #include <ctype.h> 23 | #include <string.h> 24 | #include <stdlib.h> 25 | #include <stdarg.h> 26 | #include <glib.h> 27 | #include "syntax_api.h" 28 | #include "syntax.h" 29 | #include "attribute.h" 30 | #include "class.h" 31 | 32 | #include <assert.h> 33 | 34 | #define RUNTIME_CHECK 0 35 | 36 | #if RUNTIME_CHECK 37 | #include <stdio.h> 38 | #define chk_obj(o) rpsl_object_check((o),__FILE__,__LINE__) 39 | #define chk_attr(a) rpsl_attr_check((a),__FILE__,__LINE__) 40 | #define chk_err(e) rpsl_error_check((e),__FILE__,__LINE__) 41 | 42 | void rpsl_error_check(const GList *errors, const char *file, int line); 43 | void rpsl_attr_check(const rpsl_attr_t *attr, const char *file, int line); 44 | void rpsl_object_check(const rpsl_object_t *obj, const char *file, int line); 45 | #else 46 | #define chk_obj(o) 47 | #define chk_attr(a) 48 | #define chk_err(e) 49 | #endif /* RUNTIME_CHECK */ 50 | 51 | /* type of check currently in place */ 52 | static int rpsl_level = RPSL_DICT_FRONT_END; 53 | 54 | /* return true if the character is an RPSL line-continuation */ 55 | #define is_rpsl_line_cont(c) (((c)==' ') || ((c) == '\t') || ((c) == '+')) 56 | 57 | /* needed by hash table */ 58 | static guint 59 | my_g_str_hash (gconstpointer v) 60 | { 61 | gchar *s; 62 | guint hash; 63 | 64 | s = g_strdup(v); 65 | g_strdown(s); 66 | hash = g_str_hash(s); 67 | g_free(s); 68 | return hash; 69 | } 70 | 71 | static gint 72 | my_g_strcasecmp (gconstpointer a, gconstpointer b) 73 | { 74 | return (g_strcasecmp(a, b) == 0); 75 | } 76 | 77 | 78 | /* remove comments, join lines, and compress whitespace */ 79 | static gchar * 80 | attribute_clean (const gchar *val) 81 | { 82 | gchar **lines; 83 | int i; 84 | guchar *p; 85 | guchar *q; 86 | guchar *ret_val; 87 | 88 | /* split our value up into lines */ 89 | lines = g_strsplit(val, "\n", 0); 90 | 91 | for (i=0; lines[i] != NULL; i++) { 92 | /* remove comments */ 93 | p = (guchar *)strchr(lines[i], '#'); 94 | if (p != NULL) { 95 | *p = '\0'; 96 | } 97 | 98 | /* convert line continuation characters to whitespace */ 99 | if (i > 0) { 100 | /* XXX: error in attribute */ 101 | assert(is_rpsl_line_cont(lines[i][0])); 102 | lines[i][0] = ' '; 103 | } 104 | } 105 | 106 | /* join the lines into a single string */ 107 | ret_val = (guchar *)g_strjoinv("", lines); 108 | g_strfreev(lines); 109 | 110 | /* we read from p, and write to q */ 111 | p = ret_val; 112 | q = ret_val; 113 | 114 | /* skip leading whitespace */ 115 | while (isspace((int)*p)) { 116 | p++; 117 | } 118 | 119 | /* convert all series of whitespace into a single space */ 120 | /* (this will happily convert '\n' into ' ') */ 121 | while (*p != '\0') { 122 | if (isspace((int)*p)) { 123 | *q = ' '; 124 | q++; 125 | do { 126 | p++; 127 | } while (isspace((int)*p)); 128 | } else { 129 | *q = *p; 130 | q++; 131 | p++; 132 | } 133 | } 134 | 135 | /* remove any trailing whitespace (there will be at most one space, 136 | because of the whitespace compression already performed above) */ 137 | if ((q > ret_val) && isspace((int)*(q-1))) { 138 | q--; 139 | } 140 | 141 | /* NUL-terminate our string */ 142 | *q = '\0'; 143 | 144 | /* return our new line */ 145 | return (gchar *)ret_val; 146 | } 147 | 148 | /* see if the given string ends with the other string */ 149 | static gboolean 150 | str_ends_with (const char *s1, const char *s2) 151 | { 152 | int s1_len; 153 | int s2_len; 154 | 155 | s1_len = strlen(s1); 156 | s2_len = strlen(s2); 157 | if (s1_len < s2_len) { 158 | return FALSE; 159 | } 160 | if (strcmp(s1 + s1_len - s2_len, s2) == 0) { 161 | return TRUE; 162 | } else { 163 | return FALSE; 164 | } 165 | } 166 | 167 | /* return each element in the list as a seperate gchar* */ 168 | /* be sure to call g_strfreev() when done with this result!!! */ 169 | static gchar ** 170 | generic_list_split (const char *val, const char *separator_char) 171 | { 172 | gchar *tmp_str; 173 | gchar **ret_val; 174 | gboolean has_empty_last_element; 175 | int i; 176 | int list_size; 177 | 178 | /* clean up to remove comments and newlines */ 179 | tmp_str = attribute_clean(val); 180 | 181 | /* check for empty last element, which g_strsplit() will silently drop */ 182 | has_empty_last_element = str_ends_with(tmp_str, separator_char); 183 | 184 | /* split based on separator character */ 185 | ret_val = g_strsplit(tmp_str, separator_char, 0); 186 | 187 | /* free our temporary variable */ 188 | g_free(tmp_str); 189 | 190 | /* clean whitespace from each element */ 191 | list_size = 0; 192 | for (i=0; ret_val[i] != NULL; i++) { 193 | g_strstrip(ret_val[i]); 194 | list_size++; 195 | } 196 | 197 | /* if we have an empty last element, add it back in */ 198 | if (has_empty_last_element) { 199 | ret_val = g_renew(gchar*, ret_val, list_size+2); 200 | ret_val[list_size] = g_strdup(""); 201 | ret_val[list_size+1] = NULL; 202 | } 203 | 204 | /* return our array */ 205 | return ret_val; 206 | } 207 | 208 | 209 | static gchar ** 210 | attribute_list_split (const char *val) 211 | { 212 | return generic_list_split(val, ","); 213 | } 214 | 215 | static gchar ** 216 | ripe_list_split (const char *val) 217 | { 218 | /* 219 | We can call generic_list_split() because it calls 220 | attribute_clean() before using g_strsplit() to divide the string, 221 | and attribute_clean() converts any runs of whitespace into a 222 | single space. 223 | */ 224 | return generic_list_split(val, " "); 225 | } 226 | 227 | static void 228 | rpsl_error_assign (rpsl_error_t *error, 229 | gint level, 230 | gint code, 231 | gchar *descr_fmt, 232 | ...) 233 | { 234 | va_list args; 235 | 236 | if (error != NULL) { 237 | error->level = level; 238 | error->code = code; 239 | va_start(args, descr_fmt); 240 | error->descr = g_strdup_vprintf(descr_fmt, args); 241 | va_end(args); 242 | error->attr_num = -1; 243 | } 244 | } 245 | 246 | static void 247 | rpsl_error_add (GList **errors, gint level, gint code, gint attr_num, 248 | gchar *descr_fmt, ...) 249 | { 250 | rpsl_error_t *error; 251 | va_list args; 252 | 253 | error = g_new(rpsl_error_t, 1); 254 | error->level = level; 255 | error->code = code; 256 | va_start(args, descr_fmt); 257 | error->descr = g_strdup_vprintf(descr_fmt, args); 258 | va_end(args); 259 | error->attr_num = attr_num; 260 | *errors = g_list_append(*errors, error); 261 | } 262 | 263 | /* returns TRUE if okay, else FALSE */ 264 | static gboolean 265 | rpsl_attr_syntax_check (const attribute_t *attr_info, 266 | const gchar *value, 267 | GList **errors) 268 | { 269 | int level; 270 | gchar **split_val; 271 | int i; 272 | int error_cnt; 273 | GPtrArray *parse_errors; 274 | gchar *parse_descr; 275 | 276 | /* set up for exit */ 277 | split_val = NULL; 278 | 279 | /* set our syntax checking level */ 280 | if (rpsl_level == RPSL_DICT_CORE) { 281 | level = SYNTAX_CHECK_CORE; 282 | } else { 283 | level = SYNTAX_CHECK_FRONT_END; 284 | } 285 | 286 | error_cnt = 0; 287 | 288 | /* check the syntax */ 289 | if ((attr_info->is_list) || (attr_info->is_ripe_list)) { 290 | if (attr_info->is_list) { 291 | split_val = attribute_list_split(value); 292 | } else { 293 | split_val = ripe_list_split(value); 294 | } 295 | if (split_val[0] == NULL) { 296 | rpsl_error_add(errors, 297 | RPSL_ERRLVL_ERROR, 298 | RPSL_ERR_EMPTYLIST, 299 | -1, 300 | "Attribute \"%s\" has an empty list", 301 | attr_info->name); 302 | goto exit_rpsl_syntax; 303 | } 304 | } else { 305 | split_val = g_new(gchar*, 2); 306 | split_val[0] = attribute_clean(value); 307 | split_val[1] = NULL; 308 | } 309 | 310 | for (i=0; split_val[i] != NULL; i++) { 311 | parse_errors = syntax_check_by_offset(attr_info->syntax_offset, 312 | level, 313 | split_val[i]); 314 | error_cnt += parse_errors->len; 315 | while (parse_errors->len > 0) { 316 | parse_descr = g_ptr_array_remove_index(parse_errors, 0); 317 | rpsl_error_add(errors, 318 | RPSL_ERRLVL_ERROR, 319 | RPSL_ERR_SYNERR, 320 | -1, 321 | "%s", 322 | parse_descr); 323 | g_free(parse_descr); 324 | } 325 | g_ptr_array_free(parse_errors, TRUE); 326 | } 327 | 328 | exit_rpsl_syntax: 329 | if (split_val != NULL) { 330 | g_strfreev(split_val); 331 | } 332 | if (error_cnt == 0) { 333 | return TRUE; 334 | } else { 335 | return FALSE; 336 | } 337 | } 338 | 339 | /* 340 | returns NULL on *coding errors* 341 | non-existant class specified 342 | attribute does not exist for class 343 | attribute without class in ambiguous 344 | returns a structure otherwise 345 | on *syntax errors* errors are in the rpsl_attr_t structure 346 | */ 347 | 348 | /* XXX: there should be a way to preserve the original text, so 349 | that garbage attributes still retain meaning 350 | */ 351 | rpsl_attr_t * 352 | rpsl_attr_init (const gchar *s, const gchar *class) 353 | { 354 | rpsl_attr_t *retval; 355 | gchar **attr_val; 356 | const class_t *class_info; 357 | const class_attr_t *class_attr_info; 358 | const attribute_t *attr_info; 359 | gboolean is_ambiguous; 360 | 361 | /* return NULL if string is empty */ 362 | if ((s == NULL) || (s[0] == '\0')) { 363 | return NULL; 364 | } 365 | 366 | /* get class info as early as possible */ 367 | if (class != NULL) { 368 | class_info = class_lookup(class); 369 | if (class_info == NULL) { 370 | return NULL; 371 | } 372 | } else { 373 | class_info = NULL; 374 | } 375 | 376 | /* initialize the structure */ 377 | retval = g_new(rpsl_attr_t, 1); 378 | retval->name = NULL; 379 | retval->lcase_name = NULL; 380 | retval->value = NULL; 381 | retval->errors = NULL; 382 | retval->num = -1; 383 | retval->attr_info = NULL; 384 | 385 | /* prepare for early return */ 386 | attr_val = NULL; 387 | 388 | /* split into attribute and value */ 389 | /* g_strsplit() puts max # of tokens + the rest of the string */ 390 | /* so in this case we will have 1 token (attr name maybe) and the rest */ 391 | if (strchr(s, ':') == NULL) { 392 | /* this is a critical error - very bad if it is a class attribute */ 393 | rpsl_error_add(&retval->errors, 394 | RPSL_ERRLVL_CRIT, 395 | RPSL_ERR_BADATTR, 396 | -1, 397 | "Attribute missing colon, ':'"); 398 | retval->name = g_strdup(""); 399 | retval->lcase_name = g_strdup(""); 400 | retval->value = g_strdup(""); 401 | goto exit_rpsl_attr_init; 402 | 403 | } 404 | attr_val = g_strsplit(s, ":", 1); 405 | assert(attr_val[0] != NULL); 406 | 407 | /* assign our name and value */ 408 | retval->name = g_strdup(attr_val[0]); 409 | retval->lcase_name = g_strdup(attr_val[0]); 410 | g_strdown(retval->lcase_name); 411 | if (attr_val[1] == NULL) { 412 | /* possible if nothing after the ':' */ 413 | retval->value = g_strdup(""); 414 | } else { 415 | /* the usual case, copy our data */ 416 | retval->value = g_strdup(attr_val[1]); 417 | assert(attr_val[2] == NULL); 418 | } 419 | 420 | /* get our attribute information */ 421 | if (class_info != NULL) { 422 | class_attr_info = class_attr_lookup(class_info, retval->name); 423 | } else { 424 | class_attr_info = NULL; 425 | } 426 | if ((class_info != NULL) && (class_attr_info != NULL)) { 427 | attr_info = attribute_lookup_by_offset(class_attr_info->offset); 428 | assert(attr_info != NULL); 429 | } else { 430 | attr_info = attribute_lookup(retval->name, &is_ambiguous); 431 | if (is_ambiguous) { 432 | rpsl_attr_delete(retval); 433 | retval = NULL; 434 | goto exit_rpsl_attr_init; 435 | } 436 | if (attr_info == NULL) { 437 | /* this is a critical error - bad if it is a class attribute */ 438 | rpsl_error_add(&retval->errors, 439 | RPSL_ERRLVL_CRIT, 440 | RPSL_ERR_UNKNOWNATTR, 441 | -1, 442 | "\"%s\" is not a known RPSL attribute", 443 | retval->name); 444 | goto exit_rpsl_attr_init; 445 | } 446 | } 447 | /* dangerous, but we promise not to make any changes to this value! */ 448 | retval->attr_info = (void *)attr_info; 449 | 450 | /* check for errors (adds to the error list) */ 451 | rpsl_attr_syntax_check(attr_info, retval->value, &retval->errors); 452 | 453 | /* clean up and leave */ 454 | exit_rpsl_attr_init: 455 | if (attr_val != NULL) { 456 | g_strfreev(attr_val); 457 | } 458 | 459 | chk_attr(retval); 460 | 461 | return retval; 462 | } 463 | 464 | static rpsl_error_t * 465 | rpsl_error_copy (const rpsl_error_t *err) 466 | { 467 | rpsl_error_t *retval; 468 | 469 | retval = g_new(rpsl_error_t, 1); 470 | retval->level = err->level; 471 | retval->code = err->code; 472 | retval->descr = g_strdup(err->descr); 473 | retval->attr_num = err->attr_num; 474 | return retval; 475 | } 476 | 477 | rpsl_attr_t * 478 | rpsl_attr_copy (const rpsl_attr_t *attr) 479 | { 480 | rpsl_attr_t *retval; 481 | GList *ptr; 482 | 483 | chk_attr(attr); 484 | 485 | retval = g_new(rpsl_attr_t, 1); 486 | retval->name = g_strdup(attr->name); 487 | retval->lcase_name = g_strdup(attr->lcase_name); 488 | retval->value = g_strdup(attr->value); 489 | retval->errors = NULL; 490 | for (ptr=attr->errors; ptr != NULL; ptr = g_list_next(ptr)) { 491 | retval->errors = g_list_append(retval->errors, 492 | rpsl_error_copy(ptr->data)); 493 | } 494 | retval->num = attr->num; 495 | retval->attr_info = attr->attr_info; 496 | 497 | chk_attr(retval); 498 | 499 | return retval; 500 | } 501 | 502 | rpsl_attr_t * 503 | rpsl_attr_clean_copy (const rpsl_attr_t *attr) 504 | { 505 | rpsl_attr_t *retval; 506 | 507 | chk_attr(attr); 508 | 509 | retval = g_new(rpsl_attr_t, 1); 510 | retval->name = g_strdup(attr->name); 511 | retval->lcase_name = g_strdup(attr->lcase_name); 512 | retval->value = attribute_clean(attr->value); 513 | retval->errors = NULL; 514 | retval->num = attr->num; 515 | retval->attr_info = attr->attr_info; 516 | 517 | chk_attr(retval); 518 | 519 | return retval; 520 | } 521 | 522 | 523 | void 524 | rpsl_attr_delete (rpsl_attr_t *attr) 525 | { 526 | GList *ptr; 527 | rpsl_error_t *err; 528 | 529 | chk_attr(attr); 530 | 531 | g_free(attr->name); 532 | attr->name = NULL; 533 | g_free(attr->lcase_name); 534 | attr->lcase_name = NULL; 535 | g_free(attr->value); 536 | attr->value = NULL; 537 | for (ptr=attr->errors; ptr != NULL; ptr = g_list_next(ptr)) { 538 | err = ptr->data; 539 | g_free(err->descr); 540 | g_free(err); 541 | } 542 | g_list_free(attr->errors); 543 | attr->errors = NULL; 544 | attr->num = -1; 545 | attr->attr_info = NULL; 546 | g_free(attr); 547 | } 548 | 549 | void 550 | rpsl_attr_delete_list (GList *attributes) 551 | { 552 | GList *ptr; 553 | 554 | for (ptr=attributes; ptr != NULL; ptr = g_list_next(ptr)) { 555 | rpsl_attr_delete(ptr->data); 556 | } 557 | g_list_free(attributes); 558 | } 559 | 560 | const gchar * 561 | rpsl_attr_get_name (const rpsl_attr_t *attr) 562 | { 563 | chk_attr(attr); 564 | 565 | /* XXX: there should be a way to get the original name */ 566 | /*return attr->name;*/ 567 | return attr->lcase_name; 568 | } 569 | 570 | gint 571 | rpsl_attr_get_ofs (const rpsl_attr_t *attr) 572 | { 573 | chk_attr(attr); 574 | 575 | return attr->num; 576 | } 577 | 578 | const gchar * 579 | rpsl_attr_get_value (const rpsl_attr_t *attr) 580 | { 581 | chk_attr(attr); 582 | 583 | return attr->value; 584 | } 585 | 586 | gchar * 587 | rpsl_attr_get_clean_value (const rpsl_attr_t *attr) 588 | { 589 | gchar *tmp; 590 | gchar *retval; 591 | 592 | chk_attr(attr); 593 | 594 | /* don't just return the value from attribute_clean(), since we 595 | need to return memory that can be freed via free(), and the 596 | gchar* returned by attribute_clean needs to be freed via g_free() */ 597 | tmp = attribute_clean(attr->value); 598 | retval = strdup(tmp); 599 | g_free(tmp); 600 | return retval; 601 | } 602 | 603 | GList * 604 | rpsl_attr_get_split_list (const rpsl_attr_t *attr) 605 | { 606 | const attribute_t *attr_info; 607 | GList *retval; 608 | gchar **split; 609 | int i; 610 | rpsl_attr_t *newattr; 611 | 612 | chk_attr(attr); 613 | 614 | attr_info = attr->attr_info; 615 | if ((attr_info!=NULL) && (attr_info->is_list || attr_info->is_ripe_list)) { 616 | if (attr_info->is_list) { 617 | split = attribute_list_split(attr->value); 618 | } else { 619 | split = ripe_list_split(attr->value); 620 | } 621 | retval = NULL; 622 | for (i=0; split[i] != NULL; i++) { 623 | /* XXX: perpaps consolidate this with rpsl_attr_init()? */ 624 | newattr = g_new(rpsl_attr_t, 1); 625 | assert(newattr != NULL); 626 | newattr->name = g_strdup(attr->name); 627 | newattr->lcase_name = g_strdup(attr->lcase_name); 628 | newattr->value = g_strdup(split[i]); 629 | newattr->errors = NULL; 630 | newattr->num = attr->num; 631 | newattr->attr_info = attr->attr_info; 632 | retval = g_list_append(retval, newattr); 633 | } 634 | g_strfreev(split); 635 | return retval; 636 | } else { 637 | return g_list_append(NULL, rpsl_attr_clean_copy(attr)); 638 | } 639 | } 640 | 641 | void 642 | rpsl_attr_replace_value (rpsl_attr_t *attr, const gchar *value) 643 | { 644 | chk_attr(attr); 645 | 646 | /* perform check to add any new errors */ 647 | if (attr->attr_info != NULL) { 648 | rpsl_attr_syntax_check(attr->attr_info, value, &attr->errors); 649 | } 650 | 651 | /* copy the value */ 652 | g_free(attr->value); 653 | attr->value = g_strdup(value); 654 | 655 | chk_attr(attr); 656 | } 657 | 658 | const GList * 659 | rpsl_attr_errors (const rpsl_attr_t *attr) 660 | { 661 | chk_attr(attr); 662 | 663 | return attr->errors; 664 | } 665 | 666 | static gboolean 667 | object_is_comment (const gchar *s) 668 | { 669 | const gchar *p, *q; 670 | 671 | /* skip blank lines */ 672 | p = s; 673 | for (;;) { 674 | while ((*p == ' ') || (*p == '\t')) { 675 | p++; 676 | } 677 | /* if object is only blank lines, then we are *not* a comment */ 678 | if (*p == '\0') { 679 | return FALSE; 680 | } 681 | if (*p != '\n') { 682 | break; 683 | } 684 | p++; 685 | } 686 | /* skip comment lines */ 687 | for (;;) { 688 | if ((*p != '%') && (*p != '#')) { 689 | break; 690 | } 691 | q = strchr(p, '\n'); 692 | /* if we end on a comment without newline, we *are* a comment */ 693 | if (q == NULL) { 694 | return TRUE; 695 | } 696 | p = q + 1; 697 | } 698 | /* skip trailing blank lines */ 699 | for (;;) { 700 | while ((*p == ' ') || (*p == '\t')) { 701 | p++; 702 | } 703 | if (*p != '\n') { 704 | break; 705 | } 706 | p++; 707 | } 708 | /* see if we skipped the whole object */ 709 | if (*p == '\0') { 710 | return TRUE; 711 | } else { 712 | return FALSE; 713 | } 714 | } 715 | 716 | /* we don't want to check whether an attribute belongs in the template 717 | if it is a bad attribute, or and unknown attribute */ 718 | static gboolean 719 | template_check_needed (const rpsl_attr_t *attr) 720 | { 721 | const GList *p; 722 | const rpsl_error_t *error; 723 | 724 | p = rpsl_attr_errors(attr); 725 | while (p != NULL) { 726 | error = p->data; 727 | if (error->code == RPSL_ERR_BADATTR) { 728 | return FALSE; 729 | } 730 | if (error->code == RPSL_ERR_UNKNOWNATTR) { 731 | return FALSE; 732 | } 733 | p = g_list_next(p); 734 | } 735 | return TRUE; 736 | } 737 | 738 | static void 739 | renumber_attr (rpsl_attr_t *attr, int num) 740 | { 741 | attr->num = num; 742 | } 743 | 744 | static rpsl_attr_t * 745 | rpsl_empty_attr () 746 | { 747 | rpsl_attr_t *retval; 748 | 749 | retval = g_new(rpsl_attr_t, 1); 750 | retval->name = g_strdup(""); 751 | retval->lcase_name = g_strdup(""); 752 | retval->value = g_strdup(""); 753 | retval->errors = NULL; 754 | retval->num = -1; 755 | retval->attr_info = NULL; 756 | return retval; 757 | } 758 | 759 | rpsl_object_t * 760 | rpsl_object_init (const gchar *s) 761 | { 762 | rpsl_object_t *retval; 763 | GPtrArray *lines; 764 | const gchar *p, *q; 765 | gchar *line; 766 | rpsl_attr_t *attr; 767 | const class_t *class_info; 768 | GList *attr_list; 769 | const class_attr_t *class_attr_info; 770 | const attribute_t *attr_info; 771 | const gchar *attr_name; 772 | const gchar *class_name; 773 | int i; 774 | gboolean removed_trailing_empty_lines; 775 | 776 | /* initialize the structure */ 777 | retval = g_new(rpsl_object_t, 1); 778 | retval->attributes = NULL; 779 | retval->attr_lookup = g_hash_table_new(my_g_str_hash, my_g_strcasecmp); 780 | retval->errors = NULL; 781 | retval->class_info = NULL; 782 | 783 | /* make some lines */ 784 | lines = g_ptr_array_new(); 785 | 786 | /* see if entire string is comments */ 787 | if (object_is_comment(s)) { 788 | rpsl_error_add(&retval->errors, 789 | RPSL_ERRLVL_WARN, 790 | RPSL_ERR_ONLYCOMMENTS, 791 | -1, 792 | "Object contains only comments"); 793 | goto exit_rpsl_object_init; 794 | } 795 | 796 | /* p is the beginning of the current attribute, q searches for the end */ 797 | p = s; 798 | for (;;) { 799 | /* done with string, finish */ 800 | if (*p == '\0') { 801 | break; 802 | } 803 | 804 | /* search for end of attribute */ 805 | q = strchr(p, '\n'); 806 | while ((q != NULL) && is_rpsl_line_cont(q[1])) { 807 | q = strchr(q+1, '\n'); 808 | } 809 | 810 | if (q == NULL) { 811 | /* add the final attribute */ 812 | g_ptr_array_add(lines, g_strdup(p)); 813 | /* and exit */ 814 | break; 815 | } else { 816 | /* add this attribute */ 817 | g_ptr_array_add(lines, g_strndup(p, q-p)); 818 | /* and proceed to the next one */ 819 | p = q+1; 820 | } 821 | } 822 | 823 | /* be nice and strip empty lines at the end */ 824 | removed_trailing_empty_lines = FALSE; 825 | while (lines->len > 0) { 826 | line = g_ptr_array_index(lines, lines->len - 1); 827 | if (line[0] != '\0') { 828 | break; 829 | } 830 | g_ptr_array_remove_index_fast(lines, lines->len - 1); 831 | g_free(line); 832 | removed_trailing_empty_lines = TRUE; 833 | } 834 | if (removed_trailing_empty_lines) { 835 | rpsl_error_add(&retval->errors, 836 | RPSL_ERRLVL_INFO, 837 | RPSL_ERR_EMPTYATTR, 838 | -1, 839 | "Trailing blank lines ignored"); 840 | } 841 | 842 | /* verify we have at least one line */ 843 | if (lines->len <= 0) { 844 | rpsl_error_add(&retval->errors, 845 | RPSL_ERRLVL_CRIT, 846 | RPSL_ERR_BADCLASS, 847 | -1, 848 | "Empty object"); 849 | goto exit_rpsl_object_init; 850 | } 851 | 852 | /* create the magic first attribute, which is the class */ 853 | attr = rpsl_attr_init(g_ptr_array_index(lines, 0), NULL); 854 | if (attr == NULL) { 855 | rpsl_error_add(&retval->errors, 856 | RPSL_ERRLVL_CRIT, 857 | RPSL_ERR_BADCLASS, 858 | -1, 859 | "Error with class attribute, class invalid"); 860 | goto exit_rpsl_object_init; 861 | } 862 | renumber_attr(attr, 0); 863 | 864 | /* check for errors with the class attribute */ 865 | /* only critical errors matter - let innocent syntax errors pass through */ 866 | if (rpsl_attr_has_error(attr, RPSL_ERRLVL_CRIT)) { 867 | rpsl_error_add(&retval->errors, 868 | RPSL_ERRLVL_CRIT, 869 | RPSL_ERR_BADCLASS, 870 | -1, 871 | "Error with class attribute, class invalid"); 872 | rpsl_attr_delete(attr); 873 | goto exit_rpsl_object_init; 874 | } 875 | 876 | 877 | /* get the class information */ 878 | class_name = rpsl_attr_get_name(attr); 879 | class_info = class_lookup(class_name); 880 | if (class_info == NULL) { 881 | rpsl_error_add(&retval->errors, 882 | RPSL_ERRLVL_CRIT, 883 | RPSL_ERR_UNKNOWNCLASS, 884 | -1, 885 | "First attribute, \"%s\", is not a known RPSL class", 886 | class_name); 887 | rpsl_attr_delete(attr); 888 | goto exit_rpsl_object_init; 889 | } 890 | 891 | /* check for syntax errors with the class attribute */ 892 | if (rpsl_attr_errors(attr) != NULL) { 893 | rpsl_error_add(&retval->errors, 894 | RPSL_ERRLVL_ERROR, 895 | RPSL_ERR_BADATTR, 896 | 0, 897 | "Error with attribute \"%s\"", 898 | class_name); 899 | } 900 | 901 | /* possibly dangerous, but we promise only to read this value! */ 902 | retval->class_info = (void *)class_info; 903 | 904 | /* add class attribute */ 905 | retval->attributes = g_list_append(NULL, attr); 906 | attr_list = g_list_append(NULL, attr); 907 | g_hash_table_insert(retval->attr_lookup, 908 | (void *)rpsl_attr_get_name(attr), 909 | attr_list); 910 | 911 | /* valid class, process each attribute */ 912 | for (i=1; i < lines->len; i++) { 913 | attr = rpsl_attr_init(g_ptr_array_index(lines, i), class_name); 914 | if (attr == NULL) { 915 | /* XXX: we should preserve the original information somehow */ 916 | attr = rpsl_empty_attr(); 917 | rpsl_error_add(&(attr->errors), 918 | RPSL_ERRLVL_ERROR, 919 | RPSL_ERR_BADATTR, 920 | -1, 921 | "Attribute not valid in this class"); 922 | } 923 | assert(attr != NULL); 924 | renumber_attr(attr, i); 925 | 926 | /* add the attribute to the list of attributes for this object */ 927 | retval->attributes = g_list_append(retval->attributes, attr); 928 | 929 | /* check for errors at attribute level */ 930 | if (rpsl_attr_errors(attr) != NULL) { 931 | attr_name = rpsl_attr_get_name(attr); 932 | if (attr_name != NULL) { 933 | rpsl_error_add(&retval->errors, 934 | RPSL_ERRLVL_ERROR, 935 | RPSL_ERR_BADATTR, 936 | i, 937 | "Error with attribute \"%s\"", 938 | attr_name); 939 | } else { 940 | rpsl_error_add(&retval->errors, 941 | RPSL_ERRLVL_ERROR, 942 | RPSL_ERR_BADATTR, 943 | i, 944 | "Error with attribute"); 945 | /* no name - no sense to process this attr further */ 946 | continue; 947 | } 948 | } 949 | 950 | 951 | /* get list of existing attributes of this name, if any */ 952 | attr_list = g_hash_table_lookup(retval->attr_lookup, 953 | rpsl_attr_get_name(attr)); 954 | 955 | 956 | /* perform template checking if attribute is known type */ 957 | if (template_check_needed(attr)) { 958 | 959 | /* verify this attribute can go in this class */ 960 | class_attr_info = class_attr_lookup(class_info, 961 | rpsl_attr_get_name(attr)); 962 | if (class_attr_info == NULL) { 963 | rpsl_error_add(&retval->errors, 964 | RPSL_ERRLVL_ERROR, 965 | RPSL_ERR_ATTRNOTALLOWED, 966 | i, 967 | "Attribute \"%s\" is not allowed in this class", 968 | rpsl_attr_get_name(attr)); 969 | } else { 970 | /* if we have added a "single" attribute more than once */ 971 | if ((class_attr_info->number == ATTR_SINGLE) && 972 | (attr_list != NULL)) 973 | { 974 | rpsl_error_add(&retval->errors, 975 | RPSL_ERRLVL_ERROR, 976 | RPSL_ERR_ATTRSINGLE, 977 | i, 978 | "Attribute \"%s\" appears more than once", 979 | rpsl_attr_get_name(attr)); 980 | } 981 | /* if we have tried to initialize a "generated" attribute */ 982 | if (class_attr_info->choice == ATTR_GENERATED) { 983 | rpsl_error_add(&retval->errors, 984 | RPSL_ERRLVL_ERROR, 985 | RPSL_ERR_ATTRGENERATED, 986 | i, 987 | "Attribute \"%s\" is generated automatically", 988 | rpsl_attr_get_name(attr)); 989 | } 990 | } 991 | 992 | } /* template_check_required(attr)) */ 993 | 994 | /* add the attribute to the hash table for the class */ 995 | attr_list = g_list_append(attr_list, attr); 996 | g_hash_table_insert(retval->attr_lookup, 997 | (void *)rpsl_attr_get_name(attr), 998 | attr_list); /* replaces any old value */ 999 | } 1000 | 1001 | /* check for missing required attributes */ 1002 | for (i=0; i<class_info->num_attr; i++) { 1003 | class_attr_info = &class_info->attr[i]; 1004 | if (class_attr_info->choice == ATTR_MANDATORY) { 1005 | attr_info = attribute_lookup_by_offset(class_attr_info->offset); 1006 | attr_list = g_hash_table_lookup(retval->attr_lookup, 1007 | attr_info->name); 1008 | if (attr_list == NULL) { 1009 | rpsl_error_add(&retval->errors, 1010 | RPSL_ERRLVL_ERROR, 1011 | RPSL_ERR_MISSINGATTR, 1012 | -1, 1013 | "Required attribute \"%s\" is missing", 1014 | attr_info->name); 1015 | if (attr_info->is_primary) { 1016 | rpsl_error_add(&retval->errors, 1017 | RPSL_ERRLVL_ERROR, 1018 | RPSL_ERR_MISSINGKEY, 1019 | -1, 1020 | "Primary key \"%s\" is missing", 1021 | attr_info->name); 1022 | } 1023 | } 1024 | } 1025 | } 1026 | 1027 | /* done - enjoy your new object */ 1028 | 1029 | exit_rpsl_object_init: 1030 | /* free memory used by split lines */ 1031 | for (i=0; i<lines->len; i++) { 1032 | g_free(g_ptr_array_index(lines, i)); 1033 | } 1034 | g_ptr_array_free(lines, TRUE); 1035 | lines = NULL; 1036 | 1037 | chk_obj(retval); 1038 | 1039 | /* return our object */ 1040 | return retval; 1041 | } 1042 | 1043 | rpsl_object_t * 1044 | rpsl_object_copy (const rpsl_object_t *object) 1045 | { 1046 | rpsl_object_t *retval; 1047 | GList *p; 1048 | rpsl_attr_t *attr; 1049 | GList *attr_list; 1050 | 1051 | chk_obj(object); 1052 | 1053 | retval = g_new(rpsl_object_t, 1); 1054 | retval->attributes = NULL; 1055 | retval->attr_lookup = g_hash_table_new(my_g_str_hash, my_g_strcasecmp); 1056 | retval->errors = NULL; 1057 | retval->class_info = object->class_info; 1058 | 1059 | /* copy attributes */ 1060 | for (p=object->attributes; p != NULL; p = g_list_next(p)) { 1061 | /* insert copy of attribute into list */ 1062 | attr = rpsl_attr_copy(p->data); 1063 | retval->attributes = g_list_append(retval->attributes, attr); 1064 | 1065 | /* insert copy of attribute into hash table */ 1066 | attr_list = g_hash_table_lookup(retval->attr_lookup, 1067 | rpsl_attr_get_name(attr)); 1068 | attr_list = g_list_append(attr_list, attr); /* works for NULL too */ 1069 | g_hash_table_insert(retval->attr_lookup, 1070 | (void *)rpsl_attr_get_name(attr), 1071 | attr_list); /* replaces any old value */ 1072 | } 1073 | 1074 | /* copy errors */ 1075 | for (p=object->errors; p != NULL; p = g_list_next(p)) { 1076 | retval->errors = g_list_append(retval->errors, 1077 | rpsl_error_copy(p->data)); 1078 | } 1079 | 1080 | chk_obj(retval); 1081 | 1082 | /* return the copy */ 1083 | return retval; 1084 | } 1085 | 1086 | rpsl_object_t * 1087 | rpsl_object_copy_flattened (const rpsl_object_t *object) 1088 | { 1089 | rpsl_object_t *retval; 1090 | GList *p1, *p2; 1091 | GList *split_attr; 1092 | rpsl_attr_t *attr; 1093 | GList *attr_list; 1094 | int num_attr; 1095 | 1096 | chk_obj(object); 1097 | 1098 | retval = g_new(rpsl_object_t, 1); 1099 | retval->attributes = NULL; 1100 | retval->attr_lookup = g_hash_table_new(my_g_str_hash, my_g_strcasecmp); 1101 | retval->errors = NULL; 1102 | retval->class_info = object->class_info; 1103 | 1104 | /* copy attributes */ 1105 | num_attr = 0; 1106 | for (p1=object->attributes; p1 != NULL; p1 = g_list_next(p1)) { 1107 | /* split the attribute into separate values (may be 1) */ 1108 | split_attr = rpsl_attr_get_split_list(p1->data); 1109 | 1110 | /* add each resulting attribute */ 1111 | for (p2=split_attr; p2 != NULL; p2 = g_list_next(p2)) { 1112 | attr = p2->data; 1113 | 1114 | /* renumber attribute */ 1115 | renumber_attr(attr, num_attr); 1116 | num_attr++; 1117 | 1118 | /* insert split attribute into list */ 1119 | retval->attributes = g_list_append(retval->attributes, attr); 1120 | 1121 | /* insert split attribute into hash table */ 1122 | attr_list = g_hash_table_lookup(retval->attr_lookup, 1123 | rpsl_attr_get_name(attr)); 1124 | attr_list = g_list_append(attr_list, attr); /* works for NULL too */ 1125 | g_hash_table_insert(retval->attr_lookup, 1126 | (void *)rpsl_attr_get_name(attr), 1127 | attr_list); /* replaces any old value */ 1128 | } 1129 | 1130 | /* free the list */ 1131 | g_list_free(split_attr); 1132 | } 1133 | 1134 | chk_obj(retval); 1135 | 1136 | /* return the copy */ 1137 | return retval; 1138 | } 1139 | 1140 | static void 1141 | rpsl_object_delete_helper (gpointer attr_name, 1142 | gpointer attr_list, 1143 | gpointer null) 1144 | { 1145 | g_list_free((GList *)attr_list); 1146 | } 1147 | 1148 | void 1149 | rpsl_object_delete (rpsl_object_t *object) 1150 | { 1151 | GList *p; 1152 | rpsl_error_t *err; 1153 | 1154 | chk_obj(object); 1155 | 1156 | /* free the attributes */ 1157 | for (p=object->attributes; p != NULL; p = g_list_next(p)) { 1158 | rpsl_attr_delete(p->data); 1159 | } 1160 | g_list_free(object->attributes); 1161 | object->attributes = NULL; 1162 | 1163 | /* remove the lists from the hash table */ 1164 | g_hash_table_foreach(object->attr_lookup, rpsl_object_delete_helper, NULL); 1165 | g_hash_table_destroy(object->attr_lookup); 1166 | object->attr_lookup = NULL; 1167 | 1168 | /* free the errors */ 1169 | for (p=object->errors; p != NULL; p = g_list_next(p)) { 1170 | err = p->data; 1171 | g_free(err->descr); 1172 | g_free(err); 1173 | } 1174 | g_list_free(object->errors); 1175 | object->errors = NULL; 1176 | 1177 | /* free the object itself */ 1178 | g_free(object); 1179 | } 1180 | 1181 | const char * 1182 | rpsl_object_get_class (const rpsl_object_t *object) 1183 | { 1184 | rpsl_attr_t *attr; 1185 | 1186 | chk_obj(object); 1187 | 1188 | if (object->attributes != NULL) { 1189 | attr = (rpsl_attr_t *)object->attributes->data; 1190 | return attr->lcase_name; 1191 | } else { 1192 | return NULL; 1193 | } 1194 | } 1195 | 1196 | /* default number of spaces per tab character */ 1197 | #define TABSTOP 8 1198 | 1199 | /* returns the position of the next tab stop */ 1200 | static guint 1201 | next_tabstop (guint col) 1202 | { 1203 | guint tab; 1204 | 1205 | tab = (col / TABSTOP) + 1; 1206 | return tab * TABSTOP; 1207 | } 1208 | 1209 | /* gets the leading whitespace from the given string */ 1210 | static void 1211 | separate_leading_whitespace (const gchar *str, GString **ws, GString **non_ws) 1212 | { 1213 | int n; 1214 | 1215 | n = 0; 1216 | while ((str[n] == ' ') || (str[n] == '\t')) { 1217 | n++; 1218 | } 1219 | 1220 | *ws = g_string_new(str); 1221 | g_string_truncate(*ws, n); 1222 | *non_ws = g_string_new(str + n); 1223 | } 1224 | 1225 | /* gets the length of a string of whitespace */ 1226 | static int 1227 | whitespace_length (const gchar *str, int start_col) 1228 | { 1229 | int len; 1230 | 1231 | len = start_col; 1232 | for (;;) { 1233 | if (*str == ' ') { 1234 | len++; 1235 | } else if (*str == '\t') { 1236 | len = next_tabstop(len); 1237 | } else { 1238 | break; 1239 | } 1240 | str++; 1241 | } 1242 | return len; 1243 | } 1244 | 1245 | /* removes the number of columns specified from the string, from the right */ 1246 | static void 1247 | remove_columns (GString *s, int col, int start_col) 1248 | { 1249 | int old_len; 1250 | int new_len; 1251 | int col_removed; 1252 | 1253 | col_removed = 0; 1254 | 1255 | /* first, remove characters until we've removed enough */ 1256 | while ((s->len > 0) && (col_removed < col)) { 1257 | old_len = whitespace_length(s->str, start_col); 1258 | g_string_truncate(s, s->len-1); 1259 | new_len = whitespace_length(s->str, start_col); 1260 | col_removed += old_len - new_len; 1261 | } 1262 | 1263 | /* if we've removed too many, add some spaces back */ 1264 | while (col_removed > col) { 1265 | g_string_append_c(s, ' '); 1266 | col_removed--; 1267 | } 1268 | } 1269 | 1270 | /* align the text of the attribute to the specific column */ 1271 | static void 1272 | add_aligned_val (GString *s, const rpsl_attr_t *attr, int col) 1273 | { 1274 | const gchar *name; 1275 | const gchar *val; 1276 | int start_col; 1277 | const gchar *p, *q; 1278 | int col_to_add; 1279 | int col_to_sub; 1280 | gchar **lines; 1281 | int i, j; 1282 | GString *ws; 1283 | GString *non_ws; 1284 | 1285 | /* get the information from the attribute */ 1286 | name = rpsl_attr_get_name(attr); 1287 | val = rpsl_attr_get_value(attr); 1288 | 1289 | /* calculate the column we're at after the attribute name */ 1290 | start_col = strlen(name) + 1; 1291 | 1292 | /* if the desired column is too small based on the name of the 1293 | attribute, set to the smallest allowed column */ 1294 | if (col < (start_col + 1)) { 1295 | col = start_col + 1; 1296 | } 1297 | 1298 | 1299 | /* find out what column the attribute value currently starts at */ 1300 | p = val; 1301 | for (;;) { 1302 | if (*p == ' ') { 1303 | start_col++; 1304 | } else if (*p == '\t') { 1305 | start_col = next_tabstop(start_col); 1306 | } else { 1307 | break; 1308 | } 1309 | p++; 1310 | } 1311 | 1312 | /* special case: 1313 | if there are *only* whitespace on the first line, or if it only 1314 | contains a comment, then use "as-is" */ 1315 | if ((*p == '\0') || (*p == '\n') || (*p == '#')) { 1316 | g_string_append(s, val); 1317 | g_string_append_c(s, '\n'); 1318 | /* EARLY RETURN */ 1319 | return; 1320 | } 1321 | 1322 | /* next, see how many columns we need to add or subtract */ 1323 | col_to_add = col - start_col; 1324 | 1325 | /* adding is (relatively) easy */ 1326 | if (col_to_add > 0) { 1327 | lines = g_strsplit(val, "\n", 0); 1328 | /* for the first line, append the spaces and the line itself */ 1329 | q = lines[0]; 1330 | while ((*q == ' ') || (*q == '\t')) { 1331 | g_string_append_c(s, *q); 1332 | q++; 1333 | } 1334 | for (j=0; j<col_to_add; j++) { 1335 | g_string_append_c(s, ' '); 1336 | } 1337 | g_string_append(s, q); 1338 | g_string_append_c(s, '\n'); 1339 | for (i=1; lines[i] != NULL; i++) { 1340 | /* for subsequent lines... */ 1341 | /* append the first (line continuation) character */ 1342 | g_string_append_c(s, lines[i][0]); 1343 | /* append any leading whitespace */ 1344 | q = lines[i]+1; 1345 | while ((*q == ' ') || (*q == '\t')) { 1346 | g_string_append_c(s, *q); 1347 | q++; 1348 | } 1349 | /* now append the spaces and the remainder of the line */ 1350 | for (j=0; j<col_to_add; j++) { 1351 | g_string_append_c(s, ' '); 1352 | } 1353 | g_string_append(s, q); 1354 | g_string_append_c(s, '\n'); 1355 | } 1356 | g_strfreev(lines); 1357 | } 1358 | /* subtracting is a bit more tricky, due to tabs (AKA "minions of evil") */ 1359 | else if (col_to_add < 0) { 1360 | col_to_sub = -col_to_add; 1361 | 1362 | lines = g_strsplit(val, "\n", 0); 1363 | 1364 | /* add first line after subtracting columns */ 1365 | separate_leading_whitespace(lines[0], &ws, &non_ws); 1366 | remove_columns(ws, col_to_sub, strlen(name)+1); 1367 | g_string_append(s, ws->str); 1368 | g_string_append(s, non_ws->str); 1369 | g_string_append_c(s, '\n'); 1370 | g_string_free(ws, TRUE); 1371 | g_string_free(non_ws, TRUE); 1372 | 1373 | for (i=1; lines[i] != NULL; i++) { 1374 | separate_leading_whitespace(lines[i]+1, &ws, &non_ws); 1375 | /* if the line continuation character is a tab and 1376 | we don't have enough columns, convert it to spaces */ 1377 | if (lines[i][0] == '\t') { 1378 | if (whitespace_length(ws->str, 0) < col_to_sub) { 1379 | lines[i][0] = ' '; 1380 | for (j=1; j<TABSTOP; j++) { 1381 | g_string_prepend_c(ws, ' '); 1382 | } 1383 | } 1384 | } 1385 | remove_columns(ws, col_to_sub, 0); 1386 | g_string_append_c(s, lines[i][0]); 1387 | g_string_append(s, ws->str); 1388 | g_string_append(s, non_ws->str); 1389 | g_string_append_c(s, '\n'); 1390 | g_string_free(ws, TRUE); 1391 | g_string_free(non_ws, TRUE); 1392 | } 1393 | g_strfreev(lines); 1394 | } 1395 | /* and if no adjustment is necessary, it's trivial */ 1396 | else { 1397 | g_string_append(s, val); 1398 | g_string_append_c(s, '\n'); 1399 | } 1400 | } 1401 | 1402 | gchar * 1403 | rpsl_object_get_text (const rpsl_object_t *object, guint data_column) 1404 | { 1405 | GString *s; 1406 | GList *p; 1407 | rpsl_attr_t *attr; 1408 | gchar *retval; 1409 | const gchar *name; 1410 | 1411 | chk_obj(object); 1412 | 1413 | /* return NULL on empty object, as promised */ 1414 | if (object->attributes == NULL) { 1415 | return NULL; 1416 | } 1417 | 1418 | /* concatinate attribute names and values together */ 1419 | s = g_string_new(""); 1420 | for (p=object->attributes; p != NULL; p = g_list_next(p)) { 1421 | attr = p->data; 1422 | name = rpsl_attr_get_name(attr); 1423 | if (name != NULL) { 1424 | g_string_append(s, name); 1425 | g_string_append_c(s, ':'); 1426 | if (data_column > 0) { 1427 | add_aligned_val(s, attr, data_column); 1428 | } else { 1429 | g_string_append(s, rpsl_attr_get_value(attr)); 1430 | g_string_append_c(s, '\n'); 1431 | } 1432 | } 1433 | } 1434 | 1435 | /* copy value to return */ 1436 | retval = (gchar *)malloc(s->len + 1); 1437 | if (retval != NULL) { 1438 | strcpy(retval, s->str); 1439 | } 1440 | 1441 | /* free string */ 1442 | g_string_free(s, TRUE); 1443 | 1444 | /* return result (returns NULL if memory allocation failed) */ 1445 | return retval; 1446 | } 1447 | 1448 | gint 1449 | rpsl_object_get_num_attr (const rpsl_object_t *object) 1450 | { 1451 | chk_obj(object); 1452 | 1453 | return g_list_length(object->attributes); 1454 | } 1455 | 1456 | const GList * 1457 | rpsl_object_get_all_attr (const rpsl_object_t *object) 1458 | { 1459 | chk_obj(object); 1460 | 1461 | return object->attributes; 1462 | } 1463 | 1464 | GList * 1465 | rpsl_object_get_attr (const rpsl_object_t *object, const gchar *name) 1466 | { 1467 | GList *attr_list; 1468 | GList *retval; 1469 | 1470 | chk_obj(object); 1471 | 1472 | retval = NULL; 1473 | attr_list = g_hash_table_lookup(object->attr_lookup, name); 1474 | while (attr_list != NULL) { 1475 | retval = g_list_append(retval, rpsl_attr_copy(attr_list->data)); 1476 | attr_list = g_list_next(attr_list); 1477 | } 1478 | return retval; 1479 | } 1480 | 1481 | const rpsl_attr_t * 1482 | rpsl_object_get_attr_by_ofs (const rpsl_object_t *object, gint ofs) 1483 | { 1484 | rpsl_attr_t *attr; 1485 | 1486 | chk_obj(object); 1487 | attr = g_list_nth_data(object->attributes, ofs); 1488 | chk_attr(attr); 1489 | 1490 | return attr; 1491 | } 1492 | 1493 | /* using -1 for offset (ofs) to append to the end of the object */ 1494 | static int 1495 | add_attr_to_object(rpsl_object_t *object, 1496 | rpsl_attr_t *attr, 1497 | gint ofs, 1498 | rpsl_error_t *error) 1499 | { 1500 | const gchar *attr_name; 1501 | class_t *class_info; 1502 | const class_attr_t *class_attr_info; 1503 | GList *attr_list; 1504 | gint num_attr; 1505 | gint i; 1506 | GList *p; 1507 | rpsl_attr_t *tmp; 1508 | GList *err_list; 1509 | rpsl_error_t *err; 1510 | 1511 | chk_obj(object); 1512 | chk_attr(attr); 1513 | 1514 | /* empty object - bogus, reject, abort, error */ 1515 | if (object->attributes == NULL) { 1516 | rpsl_error_assign(error, 1517 | RPSL_ERRLVL_ERROR, 1518 | RPSL_ERR_BADCLASS, 1519 | "Empty class"); 1520 | chk_obj(object); 1521 | chk_attr(attr); 1522 | return 0; 1523 | } 1524 | 1525 | /* check our offset number */ 1526 | num_attr = rpsl_object_get_num_attr(object); 1527 | if ((ofs == 0) || (ofs > num_attr)) { 1528 | rpsl_error_assign(error, 1529 | RPSL_ERRLVL_ERROR, 1530 | RPSL_ERR_BADOFFSET, 1531 | "Offset %d not between 1 and %d", ofs, num_attr); 1532 | chk_obj(object); 1533 | chk_attr(attr); 1534 | return 0; 1535 | } 1536 | 1537 | /* get attributes with this name (may be NULL, which is okay) */ 1538 | attr_name = rpsl_attr_get_name(attr); 1539 | attr_list = g_hash_table_lookup(object->attr_lookup, attr_name); 1540 | 1541 | /* get class info */ 1542 | class_info = object->class_info; 1543 | if (class_info != NULL) { /* we can only check for valid classes... */ 1544 | 1545 | /* verify this attribute can go in this class */ 1546 | class_attr_info = class_attr_lookup(class_info, attr_name); 1547 | if (class_attr_info == NULL) { 1548 | rpsl_error_assign(error, 1549 | RPSL_ERRLVL_ERROR, 1550 | RPSL_ERR_ATTRNOTALLOWED, 1551 | "Attribute \"%s\" is not allowed in this class", 1552 | attr_name); 1553 | chk_obj(object); 1554 | chk_attr(attr); 1555 | return 0; 1556 | } 1557 | 1558 | /* check to see if it is "single" and already found */ 1559 | if ((class_attr_info->number == ATTR_SINGLE) && (attr_list != NULL)) { 1560 | rpsl_error_assign(error, 1561 | RPSL_ERRLVL_ERROR, 1562 | RPSL_ERR_ATTRSINGLE, 1563 | "Attribute \"%s\" already appears in this class", 1564 | attr_name); 1565 | chk_obj(object); 1566 | chk_attr(attr); 1567 | return 0; 1568 | } 1569 | 1570 | /* otherwise we can safely add this attribute */ 1571 | } 1572 | 1573 | /* update any attribute offsets in the error list */ 1574 | err_list = object->errors; 1575 | while (err_list != NULL) { 1576 | err = err_list->data; 1577 | if (err->attr_num >= ofs) { 1578 | /* increment errors from later attributes */ 1579 | err->attr_num++; 1580 | } 1581 | err_list = g_list_next(err_list); 1582 | } 1583 | 1584 | /* add attribute to attribute list */ 1585 | if ((ofs < 0) || (ofs >= num_attr)) { 1586 | renumber_attr(attr, num_attr); 1587 | object->attributes = g_list_append(object->attributes, attr); 1588 | } else { 1589 | /* insert the entry at the appriate offset */ 1590 | renumber_attr(attr, ofs); 1591 | object->attributes = g_list_insert(object->attributes, attr, ofs); 1592 | num_attr++; 1593 | 1594 | /* renumber entries moved down */ 1595 | p = g_list_nth(object->attributes, ofs+1); 1596 | for (i=ofs+1; p != NULL; i++, p = g_list_next(p)) { 1597 | tmp = p->data; 1598 | renumber_attr(tmp, i); 1599 | } 1600 | } 1601 | 1602 | /* add attribute to hash table */ 1603 | attr_list = g_list_append(attr_list, attr); 1604 | g_hash_table_insert(object->attr_lookup, (void *)attr_name, attr_list); 1605 | 1606 | chk_obj(object); 1607 | chk_attr(attr); 1608 | 1609 | return 1; 1610 | } 1611 | 1612 | int 1613 | rpsl_object_append_attr (rpsl_object_t *object, 1614 | rpsl_attr_t *attr, 1615 | rpsl_error_t *error) 1616 | { 1617 | return add_attr_to_object(object, attr, -1, error); 1618 | } 1619 | 1620 | int 1621 | rpsl_object_add_attr (rpsl_object_t *object, 1622 | rpsl_attr_t *attr, 1623 | gint ofs, 1624 | rpsl_error_t *error) 1625 | { 1626 | if (ofs <= 0) { 1627 | rpsl_error_assign(error, 1628 | RPSL_ERRLVL_ERROR, 1629 | RPSL_ERR_BADOFFSET, 1630 | "Offset %d is less than 1", ofs); 1631 | return 0; 1632 | } else { 1633 | return add_attr_to_object(object, attr, ofs, error); 1634 | } 1635 | } 1636 | 1637 | rpsl_attr_t * 1638 | rpsl_object_remove_attr (rpsl_object_t *object, gint ofs, rpsl_error_t *error) 1639 | { 1640 | gint num_attr; 1641 | rpsl_attr_t *attr; 1642 | const gchar *attr_name; 1643 | const gchar *new_attr_name; 1644 | class_t *class_info; 1645 | const class_attr_t *class_attr_info; 1646 | GList *attr_list; 1647 | GList *p; 1648 | gint i; 1649 | rpsl_attr_t *tmp; 1650 | GList *err_list, *tmp_err_list; 1651 | rpsl_error_t *err; 1652 | 1653 | chk_obj(object); 1654 | 1655 | num_attr = rpsl_object_get_num_attr(object); 1656 | if ((ofs <= 0) || (ofs >= num_attr)) { 1657 | rpsl_error_assign(error, 1658 | RPSL_ERRLVL_ERROR, 1659 | RPSL_ERR_BADOFFSET, 1660 | "Offset %d not between 1 and %d", ofs, num_attr); 1661 | chk_obj(object); 1662 | return NULL; 1663 | } 1664 | attr = g_list_nth_data(object->attributes, ofs); 1665 | attr_name = rpsl_attr_get_name(attr); 1666 | 1667 | /* get class info */ 1668 | class_info = object->class_info; 1669 | if (class_info != NULL) { /* we must check valid classes... */ 1670 | 1671 | /* verify this attribute can be removed */ 1672 | class_attr_info = class_attr_lookup(class_info, attr_name); 1673 | if ((class_attr_info != NULL) && 1674 | (class_attr_info->choice == ATTR_MANDATORY)) 1675 | { 1676 | rpsl_error_assign(error, 1677 | RPSL_ERRLVL_ERROR, 1678 | RPSL_ERR_ATTRNOTALLOWED, 1679 | "Attribute \"%s\" is required in this class", 1680 | attr_name); 1681 | } 1682 | } 1683 | 1684 | /* remove from list and renumber */ 1685 | object->attributes = g_list_remove(object->attributes, attr); 1686 | for (i=0, p=object->attributes; p != NULL; i++, p = g_list_next(p)) { 1687 | tmp = p->data; 1688 | renumber_attr(tmp, i); 1689 | } 1690 | 1691 | /* remove from hash table */ 1692 | attr_list = g_hash_table_lookup(object->attr_lookup, attr_name); 1693 | assert(attr_list != NULL); 1694 | g_hash_table_remove(object->attr_lookup, attr_name); 1695 | attr_list = g_list_remove(attr_list, attr); 1696 | if (attr_list != NULL) { 1697 | new_attr_name = rpsl_attr_get_name((rpsl_attr_t *)attr_list->data); 1698 | g_hash_table_insert(object->attr_lookup, 1699 | (void *)new_attr_name, 1700 | attr_list); 1701 | } 1702 | 1703 | /* fix any attribute offsets in the error list */ 1704 | err_list = object->errors; 1705 | while (err_list != NULL) { 1706 | err = err_list->data; 1707 | if (err->attr_num == ofs) { 1708 | /* remove errors from this attribute */ 1709 | /* XXX: is this safe? should I just scan from the beginning? */ 1710 | tmp_err_list = g_list_next(err_list); 1711 | object->errors = g_list_remove_link(object->errors, err_list); 1712 | g_free(err->descr); 1713 | g_free(err); 1714 | g_list_free(err_list); 1715 | err_list = tmp_err_list; 1716 | } else if (err->attr_num > ofs) { 1717 | /* decrement errors from later attributes */ 1718 | err->attr_num--; 1719 | err_list = g_list_next(err_list); 1720 | } else { 1721 | /* ignore earlier attributes */ 1722 | err_list = g_list_next(err_list); 1723 | } 1724 | } 1725 | 1726 | chk_obj(object); 1727 | chk_attr(attr); 1728 | 1729 | return attr; 1730 | } 1731 | 1732 | rpsl_attr_t * 1733 | rpsl_object_remove_attr_name (rpsl_object_t *object, 1734 | const gchar *name, 1735 | rpsl_error_t *error) 1736 | { 1737 | GList *attr_list; 1738 | rpsl_attr_t *attr; 1739 | rpsl_attr_t *retval; 1740 | 1741 | chk_obj(object); 1742 | 1743 | attr_list = g_hash_table_lookup(object->attr_lookup, name); 1744 | if (attr_list == NULL) { 1745 | rpsl_error_assign(error, 1746 | RPSL_ERRLVL_ERROR, 1747 | RPSL_ERR_NOSUCHATTR, 1748 | "Attribute \"%s\" not in this object", 1749 | name); 1750 | return NULL; 1751 | } 1752 | attr = attr_list->data; 1753 | 1754 | retval = rpsl_object_remove_attr(object, attr->num, error); 1755 | 1756 | chk_obj(object); 1757 | if (retval != NULL) { 1758 | chk_attr(retval); 1759 | } 1760 | 1761 | return retval; 1762 | } 1763 | 1764 | const GList * 1765 | rpsl_object_errors (const rpsl_object_t *object) 1766 | { 1767 | chk_obj(object); 1768 | 1769 | return object->errors; 1770 | } 1771 | 1772 | gboolean 1773 | rpsl_attr_is_required (const rpsl_object_t *object, const gchar *attr) 1774 | { 1775 | const class_attr_t *class_attr_info; 1776 | 1777 | chk_obj(object); 1778 | 1779 | class_attr_info = class_attr_lookup(object->class_info, attr); 1780 | return (class_attr_info != NULL) && 1781 | (class_attr_info->choice == ATTR_MANDATORY); 1782 | } 1783 | 1784 | gboolean 1785 | rpsl_attr_is_generated (const rpsl_object_t *object, const gchar *attr) 1786 | { 1787 | const class_attr_t *class_attr_info; 1788 | 1789 | chk_obj(object); 1790 | 1791 | class_attr_info = class_attr_lookup(object->class_info, attr); 1792 | return (class_attr_info != NULL) && 1793 | (class_attr_info->choice == ATTR_GENERATED); 1794 | } 1795 | 1796 | gboolean 1797 | rpsl_attr_is_multivalued (const rpsl_object_t *object, const gchar *attr) 1798 | { 1799 | const class_attr_t *class_attr_info; 1800 | 1801 | chk_obj(object); 1802 | 1803 | class_attr_info = class_attr_lookup(object->class_info, attr); 1804 | return (class_attr_info == NULL) || 1805 | (class_attr_info->number == ATTR_MULTIPLE); 1806 | } 1807 | 1808 | gboolean 1809 | rpsl_attr_is_lookup (const rpsl_object_t *object, const gchar *attr) 1810 | { 1811 | const class_attr_t *class_attr_info; 1812 | const attribute_t *attr_info; 1813 | 1814 | chk_obj(object); 1815 | 1816 | class_attr_info = class_attr_lookup(object->class_info, attr); 1817 | if (class_attr_info == NULL) { 1818 | return FALSE; 1819 | } else { 1820 | attr_info = attribute_lookup_by_offset(class_attr_info->offset); 1821 | assert(attr_info != NULL); 1822 | return attr_info->is_lookup || attr_info->is_inverse; 1823 | } 1824 | } 1825 | 1826 | gboolean 1827 | rpsl_attr_is_key (const rpsl_object_t *object, const gchar *attr) 1828 | { 1829 | const class_attr_t *class_attr_info; 1830 | const attribute_t *attr_info; 1831 | 1832 | chk_obj(object); 1833 | 1834 | class_attr_info = class_attr_lookup(object->class_info, attr); 1835 | if (class_attr_info == NULL) { 1836 | return FALSE; 1837 | } else { 1838 | attr_info = attribute_lookup_by_offset(class_attr_info->offset); 1839 | assert(attr_info != NULL); 1840 | return attr_info->is_primary; 1841 | } 1842 | } 1843 | 1844 | gboolean 1845 | rpsl_object_is_deleted (const rpsl_object_t *object) 1846 | { 1847 | GList *attr_list; 1848 | 1849 | chk_obj(object); 1850 | 1851 | attr_list = g_hash_table_lookup(object->attr_lookup, "delete"); 1852 | if (attr_list != NULL) { 1853 | return TRUE; 1854 | } else { 1855 | return FALSE; 1856 | } 1857 | } 1858 | 1859 | static gboolean 1860 | search_errors (const GList *errors, int error_level) 1861 | { 1862 | rpsl_error_t *e; 1863 | 1864 | while (errors != NULL) { 1865 | e = errors->data; 1866 | if (e->level >= error_level) { 1867 | return TRUE; 1868 | } 1869 | errors = g_list_next(errors); 1870 | } 1871 | return FALSE; 1872 | } 1873 | 1874 | 1875 | gboolean 1876 | rpsl_attr_has_error (const rpsl_attr_t *attr, int error_level) 1877 | { 1878 | chk_attr(attr); 1879 | 1880 | return search_errors(attr->errors, error_level); 1881 | } 1882 | 1883 | gboolean 1884 | rpsl_object_has_error (const rpsl_object_t *object, int error_level) 1885 | { 1886 | chk_obj(object); 1887 | 1888 | return search_errors(object->errors, error_level); 1889 | } 1890 | 1891 | gint 1892 | rpsl_get_attr_id (const gchar *attr_name) 1893 | { 1894 | const attribute_t *attr_info; 1895 | gboolean is_ambiguous; 1896 | 1897 | attr_info = attribute_lookup(attr_name, &is_ambiguous); 1898 | if (attr_info == NULL) { 1899 | return -1; 1900 | } else { 1901 | return attr_info->id; 1902 | } 1903 | } 1904 | 1905 | gint 1906 | rpsl_get_class_id (const gchar *class_name) 1907 | { 1908 | const class_t *class_info; 1909 | 1910 | if (class_name == NULL) { 1911 | return -1; 1912 | } 1913 | 1914 | class_info = class_lookup(class_name); 1915 | if (class_info == NULL) { 1916 | return -1; 1917 | } else { 1918 | return class_info->id; 1919 | } 1920 | } 1921 | 1922 | void 1923 | rpsl_load_dictionary (int level) 1924 | { 1925 | rpsl_level = level; 1926 | } 1927 | 1928 | int 1929 | rpsl_read_dictionary () 1930 | { 1931 | return rpsl_level; 1932 | } 1933 | 1934 | #if RUNTIME_CHECK 1935 | static void 1936 | rpsl_error_check (const GList *errors, const char *file, int line) 1937 | { 1938 | const rpsl_error_t *err; 1939 | 1940 | while (errors != NULL) { 1941 | err = errors->data; 1942 | switch (err->level) { 1943 | case RPSL_ERRLVL_NONE: 1944 | case RPSL_ERRLVL_DEBUG: 1945 | case RPSL_ERRLVL_INFO: 1946 | case RPSL_ERRLVL_NOTICE: 1947 | case RPSL_ERRLVL_WARN: 1948 | case RPSL_ERRLVL_ERROR: 1949 | case RPSL_ERRLVL_CRIT: 1950 | case RPSL_ERRLVL_FATAL: 1951 | break; 1952 | default: 1953 | fprintf(stderr, "rpsl_error_check: bad level %d at %s:%d\n", 1954 | err->level, file, line); 1955 | exit(1); 1956 | } 1957 | /* XXX: could check attr-codes ONLY appear in attr, and so on */ 1958 | switch (err->code) { 1959 | case RPSL_ERR_BADATTR: 1960 | case RPSL_ERR_UNKNOWNATTR: 1961 | case RPSL_ERR_EMPTYLIST: 1962 | case RPSL_ERR_EMPTYATTR: 1963 | case RPSL_ERR_SYNERR: 1964 | case RPSL_ERR_ONLYCOMMENTS: 1965 | case RPSL_ERR_BADCLASS: 1966 | case RPSL_ERR_UNKNOWNCLASS: 1967 | case RPSL_ERR_ATTRNOTALLOWED: 1968 | case RPSL_ERR_ATTRSINGLE: 1969 | case RPSL_ERR_ATTRGENERATED: 1970 | case RPSL_ERR_MISSINGATTR: 1971 | case RPSL_ERR_MISSINGKEY: 1972 | case RPSL_ERR_BADOFFSET: 1973 | case RPSL_ERR_NOSUCHATTR: 1974 | break; 1975 | default: 1976 | fprintf(stderr, "rpsl_error_check: bad code %d at %s:%d\n", 1977 | err->code, file, line); 1978 | exit(1); 1979 | } 1980 | if (err->descr == NULL) { 1981 | fprintf(stderr, "rpsl_error_check: NULL descr at %s:%d\n", 1982 | file, line); 1983 | exit(1); 1984 | } 1985 | /* XXX: should check attr_num is within object */ 1986 | if (err->attr_num < -1) { 1987 | fprintf(stderr, "rpsl_error_check: bad attr_num %d at %s:%d\n", 1988 | err->attr_num, file, line); 1989 | exit(1); 1990 | } 1991 | errors = g_list_next(errors); 1992 | } 1993 | } 1994 | 1995 | static void 1996 | rpsl_attr_check (const rpsl_attr_t *attr, const char *file, int line) 1997 | { 1998 | const GList *errors; 1999 | const rpsl_error_t *err; 2000 | 2001 | if (attr == NULL) { 2002 | fprintf(stderr, "rpsl_attr_check: NULL attr at %s:%d\n", 2003 | file, line); 2004 | exit(1); 2005 | } 2006 | if (attr->name == NULL) { 2007 | fprintf(stderr, "rpsl_attr_check: NULL name at %s:%d\n", 2008 | file, line); 2009 | exit(1); 2010 | } 2011 | if (attr->lcase_name == NULL) { 2012 | fprintf(stderr, "rpsl_attr_check: NULL name at %s:%d\n", 2013 | file, line); 2014 | exit(1); 2015 | } 2016 | if (attr->value == NULL) { 2017 | fprintf(stderr, "rpsl_attr_check: NULL value at %s:%d\n", 2018 | file, line); 2019 | exit(1); 2020 | } 2021 | rpsl_error_check(attr->errors, file, line); 2022 | if (attr->num < -1) { 2023 | fprintf(stderr, "rpsl_attr_check: bad num %d at %s:%d\n", 2024 | attr->num, file, line); 2025 | exit(1); 2026 | } 2027 | for (errors=attr->errors; errors != NULL; errors=g_list_next(errors)) { 2028 | err = errors->data; 2029 | if (err->attr_num != -1) { 2030 | fprintf(stderr, 2031 | "rpsl_attr_check: attr_num (%d) != -1 at %s:%d\n", 2032 | err->attr_num, file, line); 2033 | exit(1); 2034 | } 2035 | } 2036 | /* XXX: think of a check for attr_info.... */ 2037 | } 2038 | 2039 | /* XXX: could also verify keys - but that's a bit extreme */ 2040 | static void 2041 | count_attr_in_list (gpointer key, gpointer value, gpointer user_data) 2042 | { 2043 | GList *l; 2044 | int sum; 2045 | int *cnt; 2046 | 2047 | sum = 0; 2048 | for (l=value; l != NULL; l = g_list_next(l)) { 2049 | sum++; 2050 | } 2051 | cnt = (int *)user_data; 2052 | *cnt += sum; 2053 | } 2054 | 2055 | static void 2056 | rpsl_object_check (const rpsl_object_t *obj, const char *file, int line) 2057 | { 2058 | const GList *l; 2059 | int i; 2060 | const rpsl_attr_t *attr; 2061 | const GList *errors; 2062 | const rpsl_error_t *err; 2063 | int num_attr; 2064 | gboolean attr_in_list; 2065 | int cnt; 2066 | 2067 | if (obj == NULL) { 2068 | fprintf(stderr, "rpsl_object_check: NULL object at %s:%d\n", 2069 | file, line); 2070 | exit(1); 2071 | } 2072 | if (obj->attributes == NULL) { 2073 | fprintf(stderr, "rpsl_object_check: NULL attributes at %s:%d\n", 2074 | file, line); 2075 | exit(1); 2076 | } 2077 | if (obj->attr_lookup == NULL) { 2078 | fprintf(stderr, "rpsl_object_check: NULL attr_lookup at %s:%d\n", 2079 | file, line); 2080 | exit(1); 2081 | } 2082 | /* make sure each attribute in the hash is in the list */ 2083 | num_attr = g_list_length(obj->attributes); 2084 | cnt = 0; 2085 | g_hash_table_foreach(obj->attr_lookup, count_attr_in_list, &cnt); 2086 | if (num_attr != cnt) { 2087 | fprintf(stderr, 2088 | "rpsl_object_check: list count (%d) != hash count (%d) at %s:%d\n", 2089 | num_attr, cnt, 2090 | file, line); 2091 | exit(1); 2092 | } 2093 | for (l=obj->attributes, i=0; l != NULL; l=g_list_next(l), i++) { 2094 | attr = l->data; 2095 | rpsl_attr_check(attr, file, line); 2096 | /* make sure each attribute is in the hash table */ 2097 | l = g_hash_table_lookup(obj->attr_lookup, rpsl_attr_get_name(attr)); 2098 | attr_in_list = FALSE; 2099 | while ((l != NULL) && !attr_in_list) { 2100 | if (l->data == attr) { 2101 | attr_in_list = TRUE; 2102 | } 2103 | l = g_list_next(l); 2104 | } 2105 | if (!attr_in_list) { 2106 | fprintf(stderr, 2107 | "rpsl_object_check: attr #%d not in hash for %p %s:%d\n", 2108 | i, obj, file, line); 2109 | exit(1); 2110 | } 2111 | if (attr->num != i) { 2112 | fprintf(stderr, 2113 | "rpsl_object_check: attr #%d does not match offset %d %s:%d\n", 2114 | attr->num, i, file, line); 2115 | exit(1); 2116 | } 2117 | } 2118 | rpsl_error_check(obj->errors, file, line); 2119 | for (errors=attr->errors; errors != NULL; errors=g_list_next(errors)) { 2120 | err = errors->data; 2121 | if (err->attr_num >= num_attr) { 2122 | fprintf(stderr, 2123 | "rpsl_object_check: attr_num (%d) >= num_attr (%d) at %s:%d\n", 2124 | err->attr_num, num_attr, file, line); 2125 | exit(1); 2126 | } 2127 | } 2128 | /* XXX: think of a check for class_info... */ 2129 | } 2130 | #endif /* RUNTIME_CHECK */ 2131 | 2132 | #ifdef TEST 2133 | 2134 | #include <stdio.h> 2135 | 2136 | /* for a test, check to make sure our we convert the following values into 2137 | the expected results */ 2138 | struct { 2139 | gchar *input; 2140 | gchar *expected_result; 2141 | } test_attr[] = { 2142 | /* all tests on a single-line attributes */ 2143 | { "unmodified", "unmodified" }, 2144 | { "also unmodified", "also unmodified" }, 2145 | { " leading whitespace", "leading whitespace" }, 2146 | { "trailing whitespace ", "trailing whitespace" }, 2147 | { "compressed \t whitespace", "compressed whitespace" }, 2148 | { "value # some comment", "value" }, 2149 | { " lots of stuff# here too ", "lots of stuff" }, 2150 | { "", "" }, 2151 | /* basic tests on multi-line attributes */ 2152 | { "multiple\n" 2153 | " lines", 2154 | "multiple lines" }, 2155 | { "multiple\n" 2156 | "\ttablines", 2157 | "multiple tablines" }, 2158 | { "multiple\n" 2159 | "+pluslines", 2160 | "multiple pluslines" }, 2161 | { "\n" 2162 | " \n" 2163 | "\t\n" 2164 | "+\n", 2165 | "" }, 2166 | /* multi-line whitespace tests */ 2167 | { " leading\n" 2168 | " multiline whitespace", 2169 | "leading multiline whitespace" }, 2170 | { "\tleading\n" 2171 | "\ttabs multiline", 2172 | "leading tabs multiline" }, 2173 | { "\t \tleading\n" 2174 | "++ multiline", 2175 | "leading + multiline" }, 2176 | { "trailing\n" 2177 | " multiline \t", 2178 | "trailing multiline" }, 2179 | { "trailing\n" 2180 | "\ttabful multiline ", 2181 | "trailing tabful multiline" }, 2182 | { "trailing\n" 2183 | "+plus multiline\t", 2184 | "trailing plus multiline" }, 2185 | { "multiline \n" 2186 | " whitespace \n" 2187 | "+compression", 2188 | "multiline whitespace compression" }, 2189 | { " more \t\tmultiline \t\n" 2190 | "+ whitespace \t \t\n" 2191 | "+compression \t", 2192 | "more multiline whitespace compression" }, 2193 | /* multi-line comment tests */ 2194 | { "There # once was a man from Nantucket,\n" 2195 | "\tWhose nic-hdl # fell in the bitbucket.\n" 2196 | "\t\tHe grabbed his # nic-handle,\n" 2197 | "\t\tAnd made the mail queue # full.\n" 2198 | "\tBut # the mail bounced (we just chucked it).", 2199 | "There Whose nic-hdl He grabbed his And made the mail queue But" }, 2200 | { " # this is an evil,\n" 2201 | " # but legal,\n" 2202 | " # thing to do", 2203 | "" }, 2204 | { "this # is also \n" 2205 | "+ # legal, but less evil I suppose\n", 2206 | "this" }, 2207 | 2208 | }; 2209 | 2210 | #define NUM_TEST_ATTR (sizeof(test_attr) / sizeof(test_attr[0])) 2211 | 2212 | int 2213 | main() 2214 | { 2215 | int i; 2216 | gchar *actual_result; 2217 | int num_error; 2218 | 2219 | num_error = 0; 2220 | 2221 | /* test the attribute_clean() function */ 2222 | for (i=0; i<NUM_TEST_ATTR; i++) { 2223 | actual_result = attribute_clean(test_attr[i].input); 2224 | if (strcmp(actual_result, test_attr[i].expected_result) != 0) { 2225 | puts("ERROR: test failed"); 2226 | puts("--------[ input ]--------"); 2227 | puts(test_attr[i].input); 2228 | puts("---[ expected result ]---"); 2229 | puts(test_attr[i].expected_result); 2230 | puts("----[ actual result ]----"); 2231 | puts(actual_result); 2232 | puts("-------------------------"); 2233 | num_error++; 2234 | } 2235 | } 2236 | if (num_error == 0) { 2237 | printf("SUCCESS: all tests passed\n"); 2238 | return 0; 2239 | } else { 2240 | return 1; 2241 | } 2242 | } 2243 | 2244 | #endif