Source File rpsl/syntax_api.c

    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