diff options
Diffstat (limited to 'options')
-rw-r--r-- | options/m_config.c | 8 | ||||
-rw-r--r-- | options/parse_configfile.c | 352 |
2 files changed, 150 insertions, 210 deletions
diff --git a/options/m_config.c b/options/m_config.c index 5925b7a159..f4179e08cf 100644 --- a/options/m_config.c +++ b/options/m_config.c @@ -42,6 +42,8 @@ static const union m_option_value default_value; // Profiles allow to predefine some sets of options that can then // be applied later on with the internal -profile option. #define MAX_PROFILE_DEPTH 20 +// Maximal include depth. +#define MAX_RECURSION_DEPTH 8 struct m_profile { struct m_profile *next; @@ -66,8 +68,14 @@ static int parse_include(struct m_config *config, struct bstr param, bool set, return M_OPT_MISSING_PARAM; if (!set) return 1; + if (config->recursion_depth >= MAX_RECURSION_DEPTH) { + MP_ERR(config, "Maximum 'include' nesting depth exceeded.\n"); + return M_OPT_INVALID; + } char *filename = bstrdup0(NULL, param); + config->recursion_depth += 1; config->includefunc(config->includefunc_ctx, filename, flags); + config->recursion_depth -= 1; talloc_free(filename); return 1; } diff --git a/options/parse_configfile.c b/options/parse_configfile.c index 1f0834959c..b0bf814793 100644 --- a/options/parse_configfile.c +++ b/options/parse_configfile.c @@ -33,242 +33,174 @@ #include "m_option.h" #include "m_config.h" -/// Maximal include depth. -#define MAX_RECURSION_DEPTH 8 +// Skip whitespace and comments (assuming there are no line breaks) +static bool skip_ws(bstr *s) +{ + *s = bstr_lstrip(*s); + if (bstr_startswith0(*s, "#")) + s->len = 0; + return s->len; +} -// Load options and profiles from from a config file. -// conffile: path to the config file -// initial_section: default section where to add normal options -// flags: M_SETOPT_* bits -// returns: 1 on sucess, -1 on error, 0 if file not accessible. -int m_config_parse_config_file(m_config_t *config, const char *conffile, - char *initial_section, int flags) +static int m_config_parse(m_config_t *config, const char *location, bstr data, + char *initial_section, int flags) { -#define PRINT_LINENUM MP_ERR(config, "%s:%d: ", conffile, line_num) -#define MAX_LINE_LEN 10000 -#define MAX_OPT_LEN 1000 -#define MAX_PARAM_LEN 1500 - FILE *fp = NULL; - char *line = NULL; - char opt[MAX_OPT_LEN + 1]; - char param[MAX_PARAM_LEN + 1]; - char c; /* for the "" and '' check */ - int tmp; - int line_num = 0; - int line_pos; /* line pos */ - int opt_pos; /* opt pos */ - int param_pos; /* param pos */ - int ret = 1; - int errors = 0; m_profile_t *profile = m_config_add_profile(config, initial_section); + void *tmp = talloc_new(NULL); + int line_no = 0; + int errors = 0; - flags = flags | M_SETOPT_FROM_CONFIG_FILE; - - MP_VERBOSE(config, "Reading config file %s\n", conffile); - - if (config->recursion_depth > MAX_RECURSION_DEPTH) { - MP_ERR(config, "Maximum 'include' nesting depth exceeded.\n"); - ret = -1; - goto out; - } - - if ((line = malloc(MAX_LINE_LEN + 1)) == NULL) { - ret = -1; - goto out; - } else - - MP_VERBOSE(config, "\n"); - - if ((fp = fopen(conffile, "r")) == NULL) { - MP_VERBOSE(config, "Can't open config file: %s\n", mp_strerror(errno)); - ret = 0; - goto out; - } - - while (fgets(line, MAX_LINE_LEN, fp)) { - if (errors >= 16) { - MP_FATAL(config, "too many errors\n"); - goto out; - } - - line_num++; - line_pos = 0; + bstr_eatstart0(&data, "\xEF\xBB\xBF"); // skip BOM - /* skip BOM */ - if (strncmp(line, "\xEF\xBB\xBF", 3) == 0) - line_pos += 3; + while (data.len) { + talloc_free_children(tmp); + bool ok = false; - /* skip whitespaces */ - while (mp_isspace(line[line_pos])) - ++line_pos; + line_no++; + char loc[512]; + snprintf(loc, sizeof(loc), "%s:%d:", location, line_no); - /* EOL / comment */ - if (line[line_pos] == '\0' || line[line_pos] == '#') + bstr line = bstr_strip_linebreaks(bstr_getline(data, &data)); + if (!skip_ws(&line)) continue; - /* read option. */ - for (opt_pos = 0; mp_isprint(line[line_pos]) && - line[line_pos] != ' ' && - line[line_pos] != '#' && - line[line_pos] != '='; /* NOTHING */) { - opt[opt_pos++] = line[line_pos++]; - if (opt_pos >= MAX_OPT_LEN) { - PRINT_LINENUM; - MP_ERR(config, "option name too long\n"); - errors++; - ret = -1; - goto nextline; + // Profile declaration + if (bstr_eatstart0(&line, "[")) { + bstr profilename; + if (!bstr_split_tok(line, "]", &profilename, &line)) { + MP_ERR(config, "%s missing closing ]\n", loc); + goto error; } - } - if (opt_pos == 0) { - PRINT_LINENUM; - MP_ERR(config, "parse error\n"); - ret = -1; - errors++; - continue; - } - opt[opt_pos] = '\0'; - - /* Profile declaration */ - if (opt_pos > 2 && opt[0] == '[' && opt[opt_pos - 1] == ']') { - opt[opt_pos - 1] = '\0'; - profile = m_config_add_profile(config, opt + 1); + if (skip_ws(&line)) { + MP_ERR(config, "%s unparseable extra characters: '%.*s'\n", + loc, BSTR_P(line)); + goto error; + } + profile = m_config_add_profile(config, bstrto0(tmp, profilename)); continue; } - /* skip whitespaces */ - while (mp_isspace(line[line_pos])) - ++line_pos; - - param_pos = 0; - bool param_set = false; - - /* check '=' */ - if (line[line_pos] == '=') { - line_pos++; - param_set = true; - - /* whitespaces... */ - while (mp_isspace(line[line_pos])) - ++line_pos; - - /* read the parameter */ - if (line[line_pos] == '"' || line[line_pos] == '\'') { - c = line[line_pos]; - ++line_pos; - for (param_pos = 0; line[line_pos] != c; /* NOTHING */) { - if (!line[line_pos]) { - PRINT_LINENUM; - MP_ERR(config, "unterminated quotes\n"); - ret = -1; - errors++; - goto nextline; - } - param[param_pos++] = line[line_pos++]; - if (param_pos >= MAX_PARAM_LEN) { - PRINT_LINENUM; - MP_ERR(config, "option %s has a too long parameter\n", opt); - ret = -1; - errors++; - goto nextline; - } + bstr_eatstart0(&line, "--"); + + bstr option = line; + while (line.len && (mp_isalnum(line.start[0]) || line.start[0] == '_' || + line.start[0] == '-')) + line = bstr_cut(line, 1); + option.len = option.len - line.len; + skip_ws(&line); + + bstr value = {0}; + if (bstr_eatstart0(&line, "=")) { + skip_ws(&line); + if (line.len && (line.start[0] == '"' || line.start[0] == '\'')) { + // Simple quoting, like "value" + char term[2] = {line.start[0], 0}; + line = bstr_cut(line, 1); + if (!bstr_split_tok(line, term, &value, &line)) { + MP_ERR(config, "%s unterminated quote\n", loc); + goto error; } - line_pos++; /* skip the closing " or ' */ - goto param_done; - } - - if (line[line_pos] == '%') { - char *start = &line[line_pos + 1]; - char *end = start; - unsigned long len = strtoul(start, &end, 10); - if (start != end && end[0] == '%') { - if (len >= MAX_PARAM_LEN - 1 || - strlen(end + 1) < len) - { - PRINT_LINENUM; - MP_ERR(config, "bogus %% length\n"); - ret = -1; - errors++; - goto nextline; - } - param_pos = snprintf(param, sizeof(param), "%.*s", - (int)len, end + 1); - line_pos += 1 + (end - start) + 1 + len; - goto param_done; + } else if (bstr_eatstart0(&line, "%")) { + // Quoting with length, like %5%value + bstr rest; + long long len = bstrtoll(line, &rest, 10); + if (rest.len == line.len || !bstr_eatstart0(&rest, "%") || + len > rest.len) + { + MP_ERR(config, "%s broken escaping with '%%'\n", loc); + goto error; } + value = bstr_splice(line, 0, len); + line = bstr_cut(line, len); + } else { + // No quoting; take everything until the comment or end of line + int end = bstrchr(line, '#'); + value = bstr_strip(end < 0 ? line : bstr_splice(line, 0, end)); + line.len = 0; } + } + if (skip_ws(&line)) { + MP_ERR(config, "%s unparseable extra characters: '%.*s'\n", + loc, BSTR_P(line)); + goto error; + } - for (param_pos = 0; mp_isprint(line[line_pos]) - && !mp_isspace(line[line_pos]) - && line[line_pos] != '#'; /* NOTHING */) { - param[param_pos++] = line[line_pos++]; - if (param_pos >= MAX_PARAM_LEN) { - PRINT_LINENUM; - MP_ERR(config, "too long parameter\n"); - ret = -1; - errors++; - goto nextline; - } + int res; + if (profile) { + if (bstr_equals0(option, "profile-desc")) { + m_profile_set_desc(profile, value); + res = 0; + } else { + res = m_config_set_profile_option(config, profile, option, value); } - - param_done: - - while (mp_isspace(line[line_pos])) - ++line_pos; + } else { + res = m_config_set_option_ext(config, option, value, flags); } - param[param_pos] = '\0'; - - /* EOL / comment */ - if (line[line_pos] != '\0' && line[line_pos] != '#') { - PRINT_LINENUM; - MP_ERR(config, "extra characters: %s\n", line + line_pos); - ret = -1; + if (res < 0) { + MP_ERR(config, "%s setting option %.*s='%.*s' failed.\n", + loc, BSTR_P(option), BSTR_P(value)); + goto error; } - bstr bopt = bstr0(opt); - bstr bparam = bstr0(param); - - if (bopt.len >= 3) - bstr_eatstart0(&bopt, "--"); - - if (profile && bstr_equals0(bopt, "profile-desc")) { - m_profile_set_desc(profile, bparam); - goto nextline; + ok = true; + error: + if (!ok) + errors++; + if (errors > 16) { + MP_ERR(config, "%s: too many errors, stopping.\n", location); + break; } + } - bool need_param = m_config_option_requires_param(config, bopt) > 0; - if (need_param && !param_set) { - PRINT_LINENUM; - MP_ERR(config, "error parsing option %.*s=%.*s: %s\n", - BSTR_P(bopt), BSTR_P(bparam), - m_option_strerror(M_OPT_MISSING_PARAM)); - continue; - } + talloc_free(tmp); + return 1; +} - if (profile) { - tmp = m_config_set_profile_option(config, profile, bopt, bparam); - } else { - tmp = m_config_set_option_ext(config, bopt, bparam, flags); - } - if (tmp < 0) { - PRINT_LINENUM; - MP_ERR(config, "setting option %.*s='%.*s' failed.\n", - BSTR_P(bopt), BSTR_P(bparam)); +static bstr read_file(struct mp_log *log, const char *filename) +{ + FILE *f = fopen(filename, "rb"); + if (!f) { + mp_verbose(log, "Can't open config file: %s\n", mp_strerror(errno)); + return (bstr){0}; + } + char *data = talloc_array(NULL, char, 0); + size_t size = 0; + while (1) { + size_t left = talloc_get_size(data) - size; + if (!left) { + MP_TARRAY_GROW(NULL, data, size + 1); continue; - /* break */ } -nextline: - ; + size_t s = fread(data + size, 1, left, f); + if (!s) { + if (ferror(f)) + mp_err(log, "Error reading config file.\n"); + fclose(f); + MP_TARRAY_APPEND(NULL, data, size, 0); + return (bstr){data, size - 1}; + } + size += s; } + assert(0); +} -out: - free(line); - if (fp) - fclose(fp); - config->recursion_depth -= 1; - if (ret < 0) { - MP_FATAL(config, "Error loading config file %s.\n", - conffile); - } - return ret; +// Load options and profiles from from a config file. +// conffile: path to the config file +// initial_section: default section where to add normal options +// flags: M_SETOPT_* bits +// returns: 1 on success, -1 on error, 0 if file not accessible. +int m_config_parse_config_file(m_config_t *config, const char *conffile, + char *initial_section, int flags) +{ + flags = flags | M_SETOPT_FROM_CONFIG_FILE; + + MP_VERBOSE(config, "Reading config file %s\n", conffile); + + bstr data = read_file(config->log, conffile); + if (!data.start) + return 0; + + int r = m_config_parse(config, conffile, data, initial_section, flags); + talloc_free(data.start); + return r; } |