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