LCOV - code coverage report
Current view: top level - lib/encoding - confline.c (source / functions) Hit Total Coverage
Test: lcov.info Lines: 206 213 96.7 %
Date: 2021-11-24 03:28:48 Functions: 12 13 92.3 %

          Line data    Source code
       1             : /* Copyright (c) 2001 Matej Pfajfar.
       2             :  * Copyright (c) 2001-2004, Roger Dingledine.
       3             :  * Copyright (c) 2004-2006, Roger Dingledine, Nick Mathewson.
       4             :  * Copyright (c) 2007-2021, The Tor Project, Inc. */
       5             : /* See LICENSE for licensing information */
       6             : 
       7             : /**
       8             :  * \file confline.c
       9             :  *
      10             :  * \brief Functions to manipulate a linked list of key-value pairs, of the
      11             :  *   type used in Tor's configuration files.
      12             :  *
      13             :  * Tor uses the config_line_t type and its associated serialized format for
      14             :  * human-readable key-value pairs in many places, including its configuration,
      15             :  * its state files, its consensus cache, and so on.
      16             :  **/
      17             : 
      18             : #include "lib/encoding/confline.h"
      19             : #include "lib/encoding/cstring.h"
      20             : #include "lib/log/log.h"
      21             : #include "lib/log/util_bug.h"
      22             : #include "lib/malloc/malloc.h"
      23             : #include "lib/string/compat_ctype.h"
      24             : #include "lib/string/compat_string.h"
      25             : #include "lib/string/util_string.h"
      26             : 
      27             : #include <string.h>
      28             : 
      29             : /** Helper: allocate a new configuration option mapping 'key' to 'val',
      30             :  * append it to *<b>lst</b>. */
      31             : void
      32        1404 : config_line_append(config_line_t **lst,
      33             :                    const char *key,
      34             :                    const char *val)
      35             : {
      36        1404 :   tor_assert(lst);
      37             : 
      38        1404 :   config_line_t *newline;
      39             : 
      40        1404 :   newline = tor_malloc_zero(sizeof(config_line_t));
      41        1404 :   newline->key = tor_strdup(key);
      42        1404 :   newline->value = tor_strdup(val);
      43        1404 :   newline->next = NULL;
      44        2194 :   while (*lst)
      45         790 :     lst = &((*lst)->next);
      46             : 
      47        1404 :   (*lst) = newline;
      48        1404 : }
      49             : 
      50             : /** Helper: allocate a new configuration option mapping 'key' to 'val',
      51             :  * and prepend it to *<b>lst</b> */
      52             : void
      53         482 : config_line_prepend(config_line_t **lst,
      54             :                     const char *key,
      55             :                     const char *val)
      56             : {
      57         482 :   tor_assert(lst);
      58             : 
      59         482 :   config_line_t *newline;
      60             : 
      61         482 :   newline = tor_malloc_zero(sizeof(config_line_t));
      62         482 :   newline->key = tor_strdup(key);
      63         482 :   newline->value = tor_strdup(val);
      64         482 :   newline->next = *lst;
      65         482 :   *lst = newline;
      66         482 : }
      67             : 
      68             : /** Return the first line in <b>lines</b> whose key is exactly <b>key</b>, or
      69             :  * NULL if no such key exists.
      70             :  *
      71             :  * (In options parsing, this is for handling commandline-only options only;
      72             :  * other options should be looked up in the appropriate data structure.) */
      73             : const config_line_t *
      74        8518 : config_line_find(const config_line_t *lines,
      75             :                  const char *key)
      76             : {
      77        8518 :   const config_line_t *cl;
      78       37740 :   for (cl = lines; cl; cl = cl->next) {
      79       34593 :     if (!strcmp(cl->key, key))
      80        5371 :       return cl;
      81             :   }
      82             :   return NULL;
      83             : }
      84             : 
      85             : /** As config_line_find(), but perform a case-insensitive comparison. */
      86             : const config_line_t *
      87           0 : config_line_find_case(const config_line_t *lines,
      88             :                       const char *key)
      89             : {
      90           0 :   const config_line_t *cl;
      91           0 :   for (cl = lines; cl; cl = cl->next) {
      92           0 :     if (!strcasecmp(cl->key, key))
      93           0 :       return cl;
      94             :   }
      95             :   return NULL;
      96             : }
      97             : 
      98             : /** Auxiliary function that does all the work of config_get_lines.
      99             :  * <b>recursion_level</b> is the count of how many nested %includes we have.
     100             :  * <b>opened_lst</b> will have a list of opened files if provided.
     101             :  * Returns the a pointer to the last element of the <b>result</b> in
     102             :  * <b>last</b>. */
     103             : int
     104        1512 : config_get_lines_aux(const char *string, config_line_t **result, int extended,
     105             :                      int allow_include, int *has_include,
     106             :                      struct smartlist_t *opened_lst, int recursion_level,
     107             :                      config_line_t **last,
     108             :                      include_handler_fn handle_include)
     109             : {
     110        1512 :   config_line_t *list = NULL, **next, *list_last = NULL;
     111        1512 :   char *k, *v;
     112        1512 :   const char *parse_err;
     113        1512 :   int include_used = 0;
     114             : 
     115        1512 :   if (recursion_level > MAX_INCLUDE_RECURSION_LEVEL) {
     116           1 :     log_warn(LD_CONFIG, "Error while parsing configuration: more than %d "
     117             :              "nested %%includes.", MAX_INCLUDE_RECURSION_LEVEL);
     118           1 :     return -1;
     119             :   }
     120             : 
     121             :   next = &list;
     122        4365 :   do {
     123        4365 :     k = v = NULL;
     124        4365 :     string = parse_config_line_from_str_verbose(string, &k, &v, &parse_err);
     125        4365 :     if (!string) {
     126           6 :       log_warn(LD_CONFIG, "Error while parsing configuration: %s",
     127             :                parse_err?parse_err:"<unknown>");
     128           3 :       config_free_lines(list);
     129           3 :       tor_free(k);
     130           3 :       tor_free(v);
     131           3 :       return -1;
     132             :     }
     133        4362 :     if (k && v) {
     134        3724 :       unsigned command = CONFIG_LINE_NORMAL;
     135        3724 :       if (extended) {
     136        2179 :         if (k[0] == '+') {
     137           4 :           char *k_new = tor_strdup(k+1);
     138           4 :           tor_free(k);
     139           4 :           k = k_new;
     140           4 :           command = CONFIG_LINE_APPEND;
     141        2175 :         } else if (k[0] == '/') {
     142           6 :           char *k_new = tor_strdup(k+1);
     143           6 :           tor_free(k);
     144           6 :           k = k_new;
     145           6 :           tor_free(v);
     146           6 :           v = tor_strdup("");
     147           6 :           command = CONFIG_LINE_CLEAR;
     148             :         }
     149             :       }
     150             : 
     151        3724 :       if (allow_include && !strcmp(k, "%include") && handle_include) {
     152         125 :         tor_free(k);
     153         125 :         include_used = 1;
     154         125 :         log_notice(LD_CONFIG, "Processing configuration path \"%s\" at "
     155             :                    "recursion level %d.", v, recursion_level);
     156             : 
     157         125 :         config_line_t *include_list;
     158         125 :         if (handle_include(v, recursion_level, extended, &include_list,
     159             :                            &list_last, opened_lst) < 0) {
     160          35 :           log_warn(LD_CONFIG, "Error reading included configuration "
     161             :                    "file or directory: \"%s\".", v);
     162          35 :           config_free_lines(list);
     163          35 :           tor_free(v);
     164          35 :           return -1;
     165             :         }
     166          90 :         *next = include_list;
     167          90 :         if (list_last)
     168          78 :           next = &list_last->next;
     169          90 :         tor_free(v);
     170             :       } else {
     171             :         /* This list can get long, so we keep a pointer to the end of it
     172             :          * rather than using config_line_append over and over and getting
     173             :          * n^2 performance. */
     174        3599 :         *next = tor_malloc_zero(sizeof(**next));
     175        3599 :         (*next)->key = k;
     176        3599 :         (*next)->value = v;
     177        3599 :         (*next)->next = NULL;
     178        3599 :         (*next)->command = command;
     179        3599 :         list_last = *next;
     180        3599 :         next = &((*next)->next);
     181             :       }
     182             :     } else {
     183         638 :       tor_free(k);
     184         638 :       tor_free(v);
     185             :     }
     186        4327 :   } while (*string);
     187             : 
     188        1473 :   if (last) {
     189          97 :     *last = list_last;
     190             :   }
     191        1473 :   if (has_include) {
     192         251 :     *has_include = include_used;
     193             :   }
     194        1473 :   *result = list;
     195        1473 :   return 0;
     196             : }
     197             : 
     198             : /** Same as config_get_lines_include but does not allow %include */
     199             : int
     200         898 : config_get_lines(const char *string, config_line_t **result, int extended)
     201             : {
     202         898 :   return config_get_lines_aux(string, result, extended, 0, NULL, NULL, 1,
     203             :                               NULL, NULL);
     204             : }
     205             : 
     206             : /**
     207             :  * Free all the configuration lines on the linked list <b>front</b>.
     208             :  */
     209             : void
     210      782958 : config_free_lines_(config_line_t *front)
     211             : {
     212      782958 :   config_line_t *tmp;
     213             : 
     214     1024113 :   while (front) {
     215      241155 :     tmp = front;
     216      241155 :     front = tmp->next;
     217             : 
     218      241155 :     tor_free(tmp->key);
     219      241155 :     tor_free(tmp->value);
     220      241155 :     tor_free(tmp);
     221             :   }
     222      782958 : }
     223             : 
     224             : /** Return a newly allocated deep copy of the lines in <b>inp</b>. */
     225             : config_line_t *
     226       13273 : config_lines_dup(const config_line_t *inp)
     227             : {
     228       13273 :   return config_lines_dup_and_filter(inp, NULL);
     229             : }
     230             : 
     231             : /** Return a newly allocated deep copy of the lines in <b>inp</b>,
     232             :  * but only the ones whose keys begin with <b>key</b> (case-insensitive).
     233             :  * If <b>key</b> is NULL, do not filter. */
     234             : config_line_t *
     235       13278 : config_lines_dup_and_filter(const config_line_t *inp,
     236             :                             const char *key)
     237             : {
     238       13278 :   config_line_t *result = NULL;
     239       13278 :   config_line_t **next_out = &result;
     240       16255 :   while (inp) {
     241        2977 :     if (key && strcasecmpstart(inp->key, key)) {
     242           5 :       inp = inp->next;
     243           5 :       continue;
     244             :     }
     245        2972 :     *next_out = tor_malloc_zero(sizeof(config_line_t));
     246        2972 :     (*next_out)->key = tor_strdup(inp->key);
     247        2972 :     (*next_out)->value = tor_strdup(inp->value);
     248        2972 :     inp = inp->next;
     249        2972 :     next_out = &((*next_out)->next);
     250             :   }
     251       13278 :   (*next_out) = NULL;
     252       13278 :   return result;
     253             : }
     254             : 
     255             : /**
     256             :  * Given a linelist <b>inp</b> beginning with the key <b>header</b>, find the
     257             :  * next line with that key, and remove that instance and all following lines
     258             :  * from the list.  Return the lines that were removed.  Operate
     259             :  * case-insensitively.
     260             :  *
     261             :  * For example, if the header is "H", and <b>inp</b> contains "H, A, B, H, C,
     262             :  * H, D", this function will alter <b>inp</b> to contain only "H, A, B", and
     263             :  * return the elements "H, C, H, D" as a separate list.
     264             :  **/
     265             : config_line_t *
     266          38 : config_lines_partition(config_line_t *inp, const char *header)
     267             : {
     268          38 :   if (BUG(inp == NULL))
     269           0 :     return NULL;
     270          38 :   if (BUG(strcasecmp(inp->key, header)))
     271           0 :     return NULL;
     272             : 
     273             :   /* Advance ptr until it points to the link to the next segment of this
     274             :      list. */
     275          38 :   config_line_t **ptr = &inp->next;
     276         139 :   while (*ptr && strcasecmp((*ptr)->key, header)) {
     277         101 :     ptr = &(*ptr)->next;
     278             :   }
     279          38 :   config_line_t *remainder = *ptr;
     280          38 :   *ptr = NULL;
     281          38 :   return remainder;
     282             : }
     283             : 
     284             : /** Return true iff a and b contain identical keys and values in identical
     285             :  * order. */
     286             : int
     287        5210 : config_lines_eq(const config_line_t *a, const config_line_t *b)
     288             : {
     289        5214 :   while (a && b) {
     290           5 :     if (strcasecmp(a->key, b->key) || strcmp(a->value, b->value))
     291             :       return 0;
     292           4 :     a = a->next;
     293           4 :     b = b->next;
     294             :   }
     295        5209 :   if (a || b)
     296         162 :     return 0;
     297             :   return 1;
     298             : }
     299             : 
     300             : /** Return the number of lines in <b>a</b> whose key is <b>key</b>. */
     301             : int
     302           9 : config_count_key(const config_line_t *a, const char *key)
     303             : {
     304           9 :   int n = 0;
     305          15 :   while (a) {
     306           6 :     if (!strcasecmp(a->key, key)) {
     307           3 :       ++n;
     308             :     }
     309           6 :     a = a->next;
     310             :   }
     311           9 :   return n;
     312             : }
     313             : 
     314             : /** Given a string containing part of a configuration file or similar format,
     315             :  * advance past comments and whitespace and try to parse a single line.  If we
     316             :  * parse a line successfully, set *<b>key_out</b> to a new string holding the
     317             :  * key portion and *<b>value_out</b> to a new string holding the value portion
     318             :  * of the line, and return a pointer to the start of the next line.  If we run
     319             :  * out of data, return a pointer to the end of the string.  If we encounter an
     320             :  * error, return NULL and set *<b>err_out</b> (if provided) to an error
     321             :  * message.
     322             :  */
     323             : const char *
     324        4414 : parse_config_line_from_str_verbose(const char *line, char **key_out,
     325             :                                    char **value_out,
     326             :                                    const char **err_out)
     327             : {
     328             :   /*
     329             :     See torrc_format.txt for a description of the (silly) format this parses.
     330             :    */
     331        4414 :   const char *key, *val, *cp;
     332        4414 :   int continuation = 0;
     333             : 
     334        4414 :   tor_assert(key_out);
     335        4414 :   tor_assert(value_out);
     336             : 
     337        4414 :   *key_out = *value_out = NULL;
     338        4414 :   key = val = NULL;
     339             :   /* Skip until the first keyword. */
     340             :   while (1) {
     341        5007 :     while (TOR_ISSPACE(*line))
     342         315 :       ++line;
     343        4692 :     if (*line == '#') {
     344       10220 :       while (*line && *line != '\n')
     345        9942 :         ++line;
     346             :     } else {
     347             :       break;
     348             :     }
     349             :   }
     350             : 
     351        4414 :   if (!*line) { /* End of string? */
     352             :     *key_out = *value_out = NULL;
     353             :     return line;
     354             :   }
     355             : 
     356             :   /* Skip until the next space or \ followed by newline. */
     357       53235 :   key = line;
     358       53235 :   while (*line && !TOR_ISSPACE(*line) && *line != '#' &&
     359           1 :          ! (line[0] == '\\' && line[1] == '\n'))
     360       49459 :     ++line;
     361        3776 :   *key_out = tor_strndup(key, line-key);
     362             : 
     363             :   /* Skip until the value. */
     364        7593 :   while (*line == ' ' || *line == '\t')
     365        3817 :     ++line;
     366             : 
     367        3776 :   val = line;
     368             : 
     369             :   /* Find the end of the line. */
     370        3776 :   if (*line == '\"') { // XXX No continuation handling is done here
     371          47 :     if (!(line = unescape_string(line, value_out, NULL))) {
     372          13 :       if (err_out)
     373           4 :         *err_out = "Invalid escape sequence in quoted string";
     374          13 :       return NULL;
     375             :     }
     376          40 :     while (*line == ' ' || *line == '\t')
     377           6 :       ++line;
     378          34 :     if (*line == '\r' && *(++line) == '\n')
     379           1 :       ++line;
     380          34 :     if (*line && *line != '#' && *line != '\n') {
     381           4 :       if (err_out)
     382           2 :         *err_out = "Excess data after quoted string";
     383           4 :       return NULL;
     384             :     }
     385             :   } else {
     386             :     /* Look for the end of the line. */
     387       59955 :     while (*line && *line != '\n' && (*line != '#' || continuation)) {
     388       56226 :       if (*line == '\\' && line[1] == '\n') {
     389           6 :         continuation = 1;
     390           6 :         line += 2;
     391       56220 :       } else if (*line == '#') {
     392           8 :         do {
     393           8 :           ++line;
     394           8 :         } while (*line && *line != '\n');
     395           1 :         if (*line == '\n')
     396           1 :           ++line;
     397             :       } else {
     398       56219 :         ++line;
     399             :       }
     400             :     }
     401             : 
     402        3729 :     if (*line == '\n') {
     403        3624 :       cp = line++;
     404             :     } else {
     405        3729 :       cp = line;
     406             :     }
     407             :     /* Now back cp up to be the last nonspace character */
     408        3960 :     while (cp>val && TOR_ISSPACE(*(cp-1)))
     409         231 :       --cp;
     410             : 
     411        3729 :     tor_assert(cp >= val);
     412             : 
     413             :     /* Now copy out and decode the value. */
     414        3729 :     *value_out = tor_strndup(val, cp-val);
     415        3729 :     if (continuation) {
     416             :       char *v_out, *v_in;
     417             :       v_out = v_in = *value_out;
     418         141 :       while (*v_in) {
     419         136 :         if (*v_in == '#') {
     420           8 :           do {
     421           8 :             ++v_in;
     422           8 :           } while (*v_in && *v_in != '\n');
     423           1 :           if (*v_in == '\n')
     424           1 :             ++v_in;
     425         135 :         } else if (v_in[0] == '\\' && v_in[1] == '\n') {
     426           6 :           v_in += 2;
     427             :         } else {
     428         129 :           *v_out++ = *v_in++;
     429             :         }
     430             :       }
     431           5 :       *v_out = '\0';
     432             :     }
     433             :   }
     434             : 
     435        3759 :   if (*line == '#') {
     436         174 :     do {
     437         174 :       ++line;
     438         174 :     } while (*line && *line != '\n');
     439             :   }
     440        3828 :   while (TOR_ISSPACE(*line)) ++line;
     441             : 
     442             :   return line;
     443             : }

Generated by: LCOV version 1.14