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