/* * This file is part of mpv. * * mpv is free software; you can redistribute it and/or * modify it under the terms of the GNU Lesser General Public * License as published by the Free Software Foundation; either * version 2.1 of the License, or (at your option) any later version. * * mpv 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 Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with mpv. If not, see . */ #include #include #include #include #include #include "osdep/io.h" #include "parse_configfile.h" #include "common/common.h" #include "common/msg.h" #include "misc/ctype.h" #include "m_option.h" #include "m_config.h" #include "stream/stream.h" // 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; } int m_config_parse(m_config_t *config, const char *location, bstr data, char *initial_section, int flags) { m_profile_t *profile = m_config_add_profile(config, initial_section); void *tmp = talloc_new(NULL); int line_no = 0; int errors = 0; bstr_eatstart0(&data, "\xEF\xBB\xBF"); // skip BOM while (data.len) { talloc_free_children(tmp); bool ok = false; line_no++; char loc[512]; snprintf(loc, sizeof(loc), "%s:%d:", location, line_no); bstr line = bstr_strip_linebreaks(bstr_getline(data, &data)); if (!skip_ws(&line)) continue; // 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 (skip_ws(&line)) { MP_ERR(config, "%s unparsable extra characters: '%.*s'\n", loc, BSTR_P(line)); goto error; } profile = m_config_add_profile(config, bstrto0(tmp, profilename)); continue; } 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; } } 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 fixed-length quoting expected - put " "\"quotes\" around the option value if you did not " "intend to use this, but your option value starts " "with '%%'\n", loc); goto error; } value = bstr_splice(rest, 0, len); line = bstr_cut(rest, 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 unparsable extra characters: '%.*s'\n", loc, BSTR_P(line)); goto error; } int res = m_config_set_profile_option(config, profile, option, value); if (res < 0) { MP_ERR(config, "%s setting option %.*s='%.*s' failed.\n", loc, BSTR_P(option), BSTR_P(value)); goto error; } ok = true; error: if (!ok) errors++; if (errors > 16) { MP_ERR(config, "%s: too many errors, stopping.\n", location); break; } } if (config->recursion_depth == 0) m_config_finish_default_profile(config, flags); talloc_free(tmp); return 1; } // Load options and profiles 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, struct mpv_global *global, const char *conffile, char *initial_section, int flags) { flags = flags | M_SETOPT_FROM_CONFIG_FILE; MP_VERBOSE(config, "Reading config file %s\n", conffile); struct stream *s = stream_create(conffile, STREAM_READ | STREAM_ORIGIN_DIRECT, NULL, global); if (!s) return 0; bstr data = stream_read_complete(s, s, 1000000000); if (!data.start) return 0; int r = m_config_parse(config, conffile, data, initial_section, flags); talloc_free(data.start); free_stream(s); return r; }