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