/* * Copyright (C) 2021 Daiki Ueno * * This file is part of GnuTLS. * * GnuTLS is free software: you can redistribute it and/or modify it * under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * GnuTLS is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see * . */ #include "config.h" #include "cfg.h" #include #include #include #include #include #include #include #include "xsize.h" #define SIZEOF(x) (sizeof(x) / sizeof((x)[0])) struct options_st { struct cfg_option_st *data; size_t length; size_t capacity; }; struct parser_st { FILE *fp; char pushback[2]; size_t pushback_length; }; static inline void clear_option(struct cfg_option_st *option) { free(option->name); free(option->value); memset(option, 0, sizeof(*option)); } void cfg_free(cfg_option_t options) { for (size_t i = 0; options[i].name; i++) { clear_option(&options[i]); } free(options); } #define HORIZONTAL_WHITESPACE "\t " #define WHITESPACE HORIZONTAL_WHITESPACE "\n\v\f\r\b" #define ALPHABETIC "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ" #define DECIMAL "0123456789" #define NAME_FIRST_CHARS "_" ALPHABETIC #define VALUE_NAME_CHARS ":^-" NAME_FIRST_CHARS DECIMAL struct buffer_st { char *data; size_t length; size_t capacity; }; static int buffer_append(struct buffer_st *buffer, int c) { size_t new_length = xsum(buffer->length, 1); if (size_overflow_p(new_length)) { return -EINVAL; } if (buffer->capacity < new_length) { size_t new_capacity; char *new_array; new_capacity = xtimes(xsum(buffer->capacity, 1), 2); if (size_overflow_p(new_capacity)) { return -EINVAL; } new_array = realloc(buffer->data, new_capacity); if (!new_array) { return -errno; } buffer->capacity = new_capacity; buffer->data = new_array; } assert(buffer->data); buffer->data[buffer->length++] = c; return 0; } static int parser_getc(struct parser_st *parser) { if (parser->pushback_length > 0) { return parser->pushback[--parser->pushback_length]; } int c = getc(parser->fp); return c; } static void parser_ungetc(struct parser_st *parser, int c) { assert(parser->pushback_length < SIZEOF(parser->pushback)); parser->pushback[parser->pushback_length++] = c; } static void skip_comment(struct parser_st *parser) { int c; c = parser_getc(parser); if (c == EOF) { return; } if (c == '#') { for (;;) { c = parser_getc(parser); if (c == EOF) { return; } if (c == '\n') { break; } } } parser_ungetc(parser, c); } static void skip_chars(struct parser_st *parser, const char *chars) { int c; for (;;) { c = parser_getc(parser); if (c == EOF) { return; } if (!strchr(chars, c)) { break; } } parser_ungetc(parser, c); } static void skip_comments_and_whitespaces(struct parser_st *parser) { int c; for (;;) { c = parser_getc(parser); if (c == EOF) { return; } parser_ungetc(parser, c); if (c == '#') { skip_comment(parser); } else if (strchr(WHITESPACE, c)) { skip_chars(parser, WHITESPACE); } else { break; } } } /* Read the name part of an option. Returns NULL if it fails. */ static char *read_name(struct parser_st *parser) { struct buffer_st buffer; int c; memset(&buffer, 0, sizeof(buffer)); skip_comments_and_whitespaces(parser); c = parser_getc(parser); if (c == EOF) { return NULL; } if (!strchr(NAME_FIRST_CHARS, c)) { parser_ungetc(parser, c); return NULL; } buffer_append(&buffer, c); for (;;) { c = parser_getc(parser); if (c == EOF) { break; } if (!strchr(VALUE_NAME_CHARS, c)) { parser_ungetc(parser, c); break; } buffer_append(&buffer, c); } assert(buffer.data); if (buffer.data[buffer.length - 1] == ':') { buffer.data[buffer.length - 1] = '\0'; buffer.length--; parser_ungetc(parser, ':'); } /* NUL terminate */ buffer_append(&buffer, '\0'); return buffer.data; } static char *read_quoted_value(struct parser_st *parser) { struct buffer_st buffer; int c, quote_char; memset(&buffer, 0, sizeof(buffer)); c = parser_getc(parser); if (c == EOF) { assert(false); return NULL; } if (c == '"' || c == '\'') { quote_char = c; } else { assert(false); return NULL; } for (;;) { c = parser_getc(parser); if (c == EOF) { break; } if (c == '\\') { c = parser_getc(parser); if (c == EOF) { /* unmatched quote */ free(buffer.data); return NULL; } if (c == '\n') { buffer_append(&buffer, ' '); } else if (c == quote_char) { buffer_append(&buffer, c); } } else if (c == quote_char) { break; } else { buffer_append(&buffer, c); } } /* NUL terminate */ buffer_append(&buffer, '\0'); return buffer.data; } /* Read the value part of an option. Returns NULL if it fails. */ static char *read_value(struct parser_st *parser) { struct buffer_st buffer; int c; memset(&buffer, 0, sizeof(buffer)); skip_chars(parser, HORIZONTAL_WHITESPACE); c = parser_getc(parser); if (c == EOF) { goto out; } /* skip delimiter if any, followed by horizontal whitespaces */ if (c == ':' || c == '=') { c = parser_getc(parser); if (c == EOF) { goto out; } parser_ungetc(parser, c); skip_chars(parser, HORIZONTAL_WHITESPACE); c = parser_getc(parser); if (c == EOF) { goto out; } } if (c == '\n') { return strdup(""); /* empty value */ } else if (c == '"' || c == '\'') { parser_ungetc(parser, c); return read_quoted_value(parser); } buffer_append(&buffer, c); for (;;) { c = parser_getc(parser); if (c == EOF) { break; } if (c == '\\') { c = parser_getc(parser); if (c == EOF) { break; } if (c == '\n') { buffer_append(&buffer, c); } } else if (c == '\n') { break; } else { buffer_append(&buffer, c); } } out: /* NUL terminate */ buffer_append(&buffer, '\0'); return buffer.data; } /* Append OPTION to OPTIONS. Take ownership of the fields of OPTION. */ static int take_option(struct options_st *options, struct cfg_option_st *option) { size_t new_length = xsum(options->length, 1); if (size_overflow_p(new_length)) { return -EINVAL; } if (options->capacity < new_length) { size_t new_capacity; struct cfg_option_st *new_array; new_capacity = xtimes(xsum(options->capacity, 1), 2); if (size_overflow_p(new_capacity)) { return -EINVAL; } new_array = reallocarray(options->data, new_capacity, sizeof(*option)); if (!new_array) { return -errno; } options->capacity = new_capacity; options->data = new_array; } assert(options->data); options->data[options->length].name = option->name; options->data[options->length].value = option->value; options->length++; option->name = NULL; option->value = NULL; return 0; } static void clear_options(struct options_st *options) { for (size_t i = 0; options->length; i++) { clear_option(&options->data[i]); } } cfg_option_t cfg_load(const char *filename) { struct parser_st parser; struct options_st options; struct cfg_option_st null_option = { NULL, NULL }; memset(&parser, 0, sizeof(parser)); memset(&options, 0, sizeof(options)); parser.fp = fopen(filename, "r"); if (!parser.fp) { return NULL; } for (;;) { struct cfg_option_st option; option.name = read_name(&parser); if (!option.name) { break; } option.value = read_value(&parser); if (!option.value) { clear_option(&option); goto error; } if (take_option(&options, &option) < 0) { clear_option(&option); goto error; } assert(!option.name && !option.value); } fclose(parser.fp); /* NUL terminate */ take_option(&options, &null_option); return options.data; error: clear_options(&options); fclose(parser.fp); return NULL; } cfg_option_t cfg_next(const cfg_option_t options, const char *name) { for (size_t i = 0; options[i].name; i++) { if (strcmp(options[i].name, name) == 0) { return &options[i]; } } return NULL; } #ifdef TEST int main(int argc, char **argv) { cfg_option_t opts; assert(argc == 2); opts = cfg_load(argv[1]); for (size_t i = 0; opts[i].name; i++) { printf("%s: %s\n", opts[i].name, opts[i].value); } cfg_free(opts); return 0; } #endif