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