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 <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