cscg24-guacamole

CSCG 2024 Challenge 'Guacamole Mashup'
git clone https://git.sinitax.com/sinitax/cscg24-guacamole
Log | Files | Refs | sfeed.txt

conf-parse.c (14228B)


      1/*
      2 * Licensed to the Apache Software Foundation (ASF) under one
      3 * or more contributor license agreements.  See the NOTICE file
      4 * distributed with this work for additional information
      5 * regarding copyright ownership.  The ASF licenses this file
      6 * to you under the Apache License, Version 2.0 (the
      7 * "License"); you may not use this file except in compliance
      8 * with the License.  You may obtain a copy of the License at
      9 *
     10 *   http://www.apache.org/licenses/LICENSE-2.0
     11 *
     12 * Unless required by applicable law or agreed to in writing,
     13 * software distributed under the License is distributed on an
     14 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
     15 * KIND, either express or implied.  See the License for the
     16 * specific language governing permissions and limitations
     17 * under the License.
     18 */
     19
     20#include "config.h"
     21
     22#include "conf.h"
     23#include "conf-parse.h"
     24
     25#include <guacamole/client.h>
     26
     27#include <ctype.h>
     28#include <string.h>
     29
     30/*
     31 * Simple recursive descent parser for an INI-like conf file grammar.  The
     32 * grammar is, roughly:
     33 *
     34 * <line>            ::= <opt-whitespace> <declaration> <line-end>
     35 * <line-end>        ::= <opt-whitespace> <opt-comment> <EOL>
     36 * <declaration>     ::= <section-name> | <parameter-value> | ""
     37 * <section-name>    ::= "[" <name> "]"
     38 * <parameter-value> ::= <name> <opt-whitespace> "=" <opt-whitespace> <value>
     39 *
     40 * Where:
     41 *  <opt-whitespace> is any number of tabs or spaces.
     42 *  <opt-comment> is a # character followed by any length of text without an EOL.
     43 *  <name> is an alpha-numeric string consisting of: [A-Za-z0-9_-].
     44 *  <value> is any length of text without an EOL or # character, or a double-quoted string (backslash escapes legal).
     45 *  <EOL> is a carriage return or line feed character.
     46 */
     47
     48/**
     49 * The current section. Note that this means the parser is NOT threadsafe.
     50 */
     51char __guacd_current_section[GUACD_CONF_MAX_NAME_LENGTH + 1] = "";
     52
     53char* guacd_conf_parse_error = NULL;
     54
     55char* guacd_conf_parse_error_location = NULL;
     56
     57/**
     58 * Reads through all whitespace at the beginning of the buffer, returning the
     59 * number of characters read. This is <opt-whitespace> in the grammar above. As
     60 * the whitespace is zero or more whitespace characters, this function cannot
     61 * fail, but it may read zero chars overall.
     62 */
     63static int guacd_parse_whitespace(char* buffer, int length) {
     64
     65    int chars_read = 0;
     66
     67    /* Read through all whitespace */
     68    while (chars_read < length) {
     69
     70        /* Read character */
     71        char c = *buffer;
     72
     73        /* Stop at non-whitespace */
     74        if (c != ' ' && c != '\t')
     75            break;
     76
     77        chars_read++;
     78        buffer++;
     79
     80    }
     81
     82    return chars_read;
     83
     84}
     85
     86/**
     87 * Parses the name of a parameter, section, etc. A section/parameter name can
     88 * consist only of alphanumeric characters and underscores. The resulting name
     89 * will be stored in the name string, which must have at least 256 bytes
     90 * available.
     91 */
     92static int guacd_parse_name(char* buffer, int length, char* name) {
     93
     94    char* name_start = buffer;
     95    int chars_read = 0;
     96
     97    /* Read through all valid name chars */
     98    while (chars_read < length) {
     99
    100        /* Read character */
    101        char c = *buffer;
    102
    103        /* Stop at non-name characters */
    104        if (!isalnum(c) && c != '_')
    105            break;
    106
    107        chars_read++;
    108        buffer++;
    109
    110        /* Ensure name does not exceed maximum length */
    111        if (chars_read > GUACD_CONF_MAX_NAME_LENGTH) {
    112            guacd_conf_parse_error = "Names can be no more than 255 characters long";
    113            guacd_conf_parse_error_location = buffer;
    114            return -1;
    115        }
    116
    117    }
    118
    119    /* Names must contain at least one character */
    120    if (chars_read == 0)
    121        return 0;
    122
    123    /* Copy name from buffer */
    124    memcpy(name, name_start, chars_read);
    125    name[chars_read] = '\0';
    126
    127    return chars_read;
    128
    129}
    130
    131/**
    132 * Parses the value of a parameter. A value can consist of any character except
    133 * '#', whitespace, or EOL. The resulting value will be stored in the value
    134 * string, which must have at least 256 bytes available.
    135 */ 
    136static int guacd_parse_value(char* buffer, int length, char* value) {
    137
    138    char* value_start = buffer;
    139    int chars_read = 0;
    140
    141    /* Read through all valid value chars */
    142    while (chars_read < length) {
    143
    144        /* Read character */
    145        char c = *buffer;
    146
    147        /* Stop at invalid character */
    148        if (c == '#' || c == '"' || c == '\r' || c == '\n' || c == ' ' || c == '\t')
    149            break;
    150
    151        chars_read++;
    152        buffer++;
    153
    154        /* Ensure value does not exceed maximum length */
    155        if (chars_read > GUACD_CONF_MAX_VALUE_LENGTH) {
    156            guacd_conf_parse_error = "Values can be no more than 8191 characters long";
    157            guacd_conf_parse_error_location = buffer;
    158            return -1;
    159        }
    160
    161    }
    162
    163    /* Values must contain at least one character */
    164    if (chars_read == 0) {
    165        guacd_conf_parse_error = "Unquoted values must contain at least one character";
    166        guacd_conf_parse_error_location = buffer;
    167        return -1;
    168    }
    169
    170    /* Copy value from buffer */
    171    memcpy(value, value_start, chars_read);
    172    value[chars_read] = '\0';
    173
    174    return chars_read;
    175
    176}
    177
    178/**
    179 * Parses the quoted value of a parameter. Quoted values may contain any
    180 * character except double quotes or backslashes, which must be
    181 * backslash-escaped.
    182 */ 
    183static int guacd_parse_quoted_value(char* buffer, int length, char* value) {
    184
    185    int escaped = 0;
    186
    187    /* Assert first character is '"' */
    188    if (length == 0 || *buffer != '"')
    189        return 0;
    190
    191    int chars_read = 1;
    192    buffer++;
    193    length--;
    194
    195    /* Read until end of quoted value */
    196    while (chars_read < length) {
    197
    198        /* Read character */
    199        char c = *buffer;
    200
    201        /* Handle special characters if not escaped */
    202        if (!escaped) {
    203
    204            /* Stop at quote or invalid character */
    205            if (c == '"' || c == '\r' || c == '\n')
    206                break;
    207
    208            /* Backslash escaping */
    209            else if (c == '\\')
    210                escaped = 1;
    211
    212            else
    213                *(value++) = c;
    214
    215        }
    216
    217        /* Reset escape flag */
    218        else {
    219            escaped = 0;
    220            *(value++) = c;
    221        }
    222
    223        chars_read++;
    224        buffer++;
    225
    226        /* Ensure value does not exceed maximum length */
    227        if (chars_read > GUACD_CONF_MAX_VALUE_LENGTH) {
    228            guacd_conf_parse_error = "Values can be no more than 8191 characters long";
    229            guacd_conf_parse_error_location = buffer;
    230            return -1;
    231        }
    232
    233    }
    234
    235    /* Assert value ends with '"' */
    236    if (length == 0 || *buffer != '"') {
    237        guacd_conf_parse_error = "'\"' expected";
    238        guacd_conf_parse_error_location = buffer;
    239        return -1;
    240    }
    241
    242    chars_read++;
    243
    244    /* Terminate read value */
    245    *value = '\0';
    246
    247    return chars_read;
    248
    249}
    250
    251/**
    252 * Reads a parameter/value pair, separated by an '=' character. If the
    253 * parameter/value pair is invalid for any reason, a negative value is
    254 * returned.
    255 */
    256static int guacd_parse_parameter(guacd_param_callback* callback, char* buffer, int length, void* data) {
    257
    258    char param_name[GUACD_CONF_MAX_NAME_LENGTH + 1];
    259    char param_value[GUACD_CONF_MAX_VALUE_LENGTH + 1];
    260
    261    int retval;
    262    int chars_read = 0;
    263
    264    char* param_start = buffer;
    265
    266    retval = guacd_parse_name(buffer, length, param_name);
    267    if (retval < 0)
    268        return -1;
    269
    270    /* If no name found, no parameter/value pair */
    271    if (retval == 0)
    272        return 0;
    273
    274    /* Validate presence of section header */
    275    if (__guacd_current_section[0] == '\0') {
    276        guacd_conf_parse_error = "Parameters must have a corresponding section";
    277        guacd_conf_parse_error_location = buffer;
    278        return -1;
    279    }
    280
    281    chars_read += retval;
    282    buffer += retval;
    283    length -= retval;
    284
    285    /* Optional whitespace before '=' */
    286    retval = guacd_parse_whitespace(buffer, length);
    287    chars_read += retval;
    288    buffer += retval;
    289    length -= retval;
    290
    291    /* Required '=' */
    292    if (length == 0 || *buffer != '=') {
    293        guacd_conf_parse_error = "'=' expected";
    294        guacd_conf_parse_error_location = buffer;
    295        return -1;
    296    }
    297
    298    chars_read++;
    299    buffer++;
    300    length--;
    301
    302    /* Optional whitespace before value */
    303    retval = guacd_parse_whitespace(buffer, length);
    304    chars_read += retval;
    305    buffer += retval;
    306    length -= retval;
    307
    308    /* Quoted parameter value */
    309    retval = guacd_parse_quoted_value(buffer, length, param_value);
    310    if (retval < 0)
    311        return -1;
    312
    313    /* Non-quoted parameter value (required if no quoted value given) */
    314    if (retval == 0) retval = guacd_parse_value(buffer, length, param_value);
    315    if (retval < 0)
    316        return -1;
    317    
    318    chars_read += retval;
    319
    320    /* Call callback, handling error code */
    321    if (callback(__guacd_current_section, param_name, param_value, data)) {
    322        guacd_conf_parse_error_location = param_start;
    323        return -1;
    324    }
    325
    326    return chars_read;
    327
    328}
    329
    330/**
    331 * Reads a section name from the beginning of the given buffer. This section
    332 * name must conform to the grammar definition. If the section name does not
    333 * match, a negative value is returned.
    334 */
    335static int guacd_parse_section(char* buffer, int length) {
    336
    337    int retval;
    338
    339    /* Assert first character is '[' */
    340    if (length == 0 || *buffer != '[')
    341        return 0;
    342
    343    int chars_read = 1;
    344    buffer++;
    345    length--;
    346
    347    retval = guacd_parse_name(buffer, length, __guacd_current_section);
    348    if (retval < 0)
    349        return -1;
    350
    351    /* If no name found, invalid section */
    352    if (retval == 0) {
    353        guacd_conf_parse_error = "Section names must contain at least one character";
    354        guacd_conf_parse_error_location = buffer;
    355        return -1;
    356    }
    357
    358    chars_read += retval;
    359    buffer += retval;
    360    length -= retval;
    361
    362    /* Name must end with ']' */
    363    if (length == 0 || *buffer != ']') {
    364        guacd_conf_parse_error = "']' expected";
    365        guacd_conf_parse_error_location = buffer;
    366        return -1;
    367    }
    368
    369    chars_read++;
    370    
    371    return chars_read;
    372
    373}
    374
    375/**
    376 * Parses a declaration, which may be either a section name or a
    377 * parameter/value pair. The empty string is acceptable, as well, as a
    378 * "null declaration".
    379 */
    380static int guacd_parse_declaration(guacd_param_callback* callback, char* buffer, int length, void* data) {
    381
    382    int retval;
    383
    384    /* Look for section name */
    385    retval = guacd_parse_section(buffer, length);
    386    if (retval != 0)
    387        return retval;
    388
    389    /* Lacking a section name, read parameter/value pair */
    390    retval = guacd_parse_parameter(callback, buffer, length, data);
    391    if (retval != 0)
    392        return retval;
    393
    394    /* Null declaration (default) */
    395    return 0;
    396
    397}
    398
    399/**
    400 * Parses a comment, which must start with a '#' character, and terminate with
    401 * an end-of-line character. If no EOL is found, or the first character is not
    402 * a '#', a negative value is returned. Otherwise, the number of characters
    403 * parsed is returned. If no comment is present, zero is returned.
    404 */
    405static int guacd_parse_comment(char* buffer, int length) {
    406
    407    /* Need at least one character */
    408    if (length == 0)
    409        return 0;
    410
    411    /* Assert first character is '#' */
    412    if (*(buffer++) != '#')
    413        return 0;
    414
    415    int chars_read = 1;
    416
    417    /* Advance to first non-space character */
    418    while (chars_read < length) {
    419
    420        /* Read character */
    421        char c = *buffer;
    422
    423        /* End of comment found at end of line */
    424        if (c == '\n' || c == '\r')
    425            return chars_read;
    426
    427        chars_read++;
    428        buffer++;
    429
    430    }
    431
    432    /* No end of line in comment */
    433    guacd_conf_parse_error = "expected end-of-line";
    434    guacd_conf_parse_error_location = buffer;
    435    return -1;
    436
    437}
    438
    439/**
    440 * Parses the end of a line, which may contain a comment. If a parse error
    441 * occurs, a negative value is returned. Otherwise, the number of characters
    442 * parsed is returned.
    443 */
    444static int guacd_parse_line_end(char* buffer, int length) {
    445
    446    int chars_read = 0;
    447    int retval;
    448  
    449    /* Initial optional whitespace */ 
    450    retval = guacd_parse_whitespace(buffer, length);
    451    chars_read += retval;
    452    buffer += retval;
    453    length -= retval;
    454
    455    /* Optional comment */ 
    456    retval = guacd_parse_comment(buffer, length);
    457    if (retval < 0)
    458        return -1;
    459
    460    chars_read += retval;
    461    buffer += retval;
    462    length -= retval;
    463
    464    /* Assert EOL */
    465    if (length == 0 || (*buffer != '\r' && *buffer != '\n')) {
    466        guacd_conf_parse_error = "expected end-of-line";
    467        guacd_conf_parse_error_location = buffer;
    468        return -1;
    469    }
    470
    471    chars_read++;
    472
    473    /* Line is valid */
    474    return chars_read;
    475
    476}
    477
    478/**
    479 * Parses an entire line - declaration, comment, and all. If a parse error
    480 * occurs, a negative value is returned. Otherwise, the number of characters
    481 * parsed is returned.
    482 */
    483static int guacd_parse_line(guacd_param_callback* callback, char* buffer, int length, void* data) {
    484
    485    int chars_read = 0;
    486    int retval;
    487  
    488    /* Initial optional whitespace */ 
    489    retval = guacd_parse_whitespace(buffer, length);
    490    chars_read += retval;
    491    buffer += retval;
    492    length -= retval;
    493
    494    /* Declaration (which may be the empty string) */
    495    retval = guacd_parse_declaration(callback, buffer, length, data);
    496    if (retval < 0)
    497        return retval;
    498
    499    chars_read += retval;
    500    buffer += retval;
    501    length -= retval;
    502
    503    /* End of line */
    504    retval = guacd_parse_line_end(buffer, length);
    505    if (retval < 0)
    506        return retval;
    507
    508    chars_read += retval;
    509
    510    return chars_read;
    511
    512}
    513
    514int guacd_parse_conf(guacd_param_callback* callback, char* buffer, int length, void* data) {
    515
    516    /* Empty buffers are valid */
    517    if (length == 0)
    518        return 0;
    519
    520    return guacd_parse_line(callback, buffer, length, data);
    521
    522}
    523
    524int guacd_parse_log_level(const char* name) {
    525
    526    /* Translate log level name */
    527    if (strcmp(name, "info")    == 0) return GUAC_LOG_INFO;
    528    if (strcmp(name, "error")   == 0) return GUAC_LOG_ERROR;
    529    if (strcmp(name, "warning") == 0) return GUAC_LOG_WARNING;
    530    if (strcmp(name, "debug")   == 0) return GUAC_LOG_DEBUG;
    531    if (strcmp(name, "trace")   == 0) return GUAC_LOG_TRACE;
    532
    533    /* No such log level */
    534    return -1;
    535
    536}
    537