2005-09-15 09:58:31 -04:00
|
|
|
/* Config file manipulation */
|
|
|
|
|
|
|
|
#ifdef HAVE_CONFIG_H
|
|
|
|
#include "config.h"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include <stdio.h>
|
|
|
|
#include <stdlib.h>
|
|
|
|
#include <string.h>
|
|
|
|
#include <sys/types.h>
|
|
|
|
#include <sys/stat.h> /* OS/2 needs this after sys/types.h */
|
|
|
|
#ifdef HAVE_FCNTL_H
|
|
|
|
#include <fcntl.h> /* OS/2 needs this after sys/types.h */
|
|
|
|
#endif
|
|
|
|
#ifdef HAVE_UNISTD_H
|
|
|
|
#include <unistd.h>
|
|
|
|
#endif
|
|
|
|
|
|
|
|
#include "elinks.h"
|
|
|
|
|
|
|
|
#include "config/conf.h"
|
|
|
|
#include "config/dialogs.h"
|
2007-08-30 18:03:14 -04:00
|
|
|
#include "config/domain.h"
|
2005-09-15 09:58:31 -04:00
|
|
|
#include "config/home.h"
|
|
|
|
#include "config/kbdbind.h"
|
|
|
|
#include "config/options.h"
|
|
|
|
#include "config/opttypes.h"
|
2021-08-08 15:25:08 -04:00
|
|
|
#include "intl/libintl.h"
|
2005-09-15 09:58:31 -04:00
|
|
|
#include "osdep/osdep.h"
|
|
|
|
#include "terminal/terminal.h"
|
|
|
|
#include "util/error.h"
|
|
|
|
#include "util/memory.h"
|
2021-12-29 16:11:47 -05:00
|
|
|
#include "util/qs_parse/qs_parse.h"
|
2005-09-15 09:58:31 -04:00
|
|
|
#include "util/secsave.h"
|
|
|
|
#include "util/string.h"
|
|
|
|
|
|
|
|
|
|
|
|
/* Config file has only very simple grammar:
|
|
|
|
*
|
|
|
|
* /set *option *= *value/
|
|
|
|
* /bind *keymap *keystroke *= *action/
|
|
|
|
* /include *file/
|
|
|
|
* /#.*$/
|
|
|
|
*
|
|
|
|
* Where option consists from any number of categories separated by dots and
|
|
|
|
* name of the option itself. Both category and option name consists from
|
|
|
|
* [a-zA-Z0-9_-*] - using uppercase letters is not recommended, though. '*' is
|
|
|
|
* reserved and is used only as escape character in place of '.' originally in
|
|
|
|
* option name.
|
|
|
|
*
|
|
|
|
* Value can consist from:
|
|
|
|
* - number (it will be converted to int/long)
|
|
|
|
* - enum (like on, off; true, fake, last_url; etc ;) - in planning state yet
|
|
|
|
* - string - "blah blah" (keymap, keystroke and action and file looks like that too)
|
|
|
|
*
|
|
|
|
* "set" command is parsed first, and then type-specific function is called,
|
|
|
|
* with option as one parameter and value as a second. Usually it just assigns
|
|
|
|
* value to an option, but sometimes you may want to first create the option
|
|
|
|
* ;). Then this will come handy. */
|
|
|
|
|
2016-04-20 14:57:09 -04:00
|
|
|
struct conf_parsing_pos {
|
|
|
|
/** Points to the next character to be parsed from the
|
|
|
|
* configuration file. */
|
2021-01-02 10:20:27 -05:00
|
|
|
char *look;
|
2016-04-20 14:57:09 -04:00
|
|
|
|
|
|
|
/** The line number corresponding to #look. This is
|
|
|
|
* shown in error messages. */
|
|
|
|
int line;
|
|
|
|
};
|
|
|
|
|
2008-02-02 17:16:38 -05:00
|
|
|
struct conf_parsing_state {
|
|
|
|
/** This part may be copied to a local variable as a bookmark
|
|
|
|
* and restored later. So it must not contain any pointers
|
|
|
|
* that would have to be freed in that situation. */
|
2016-04-20 14:57:09 -04:00
|
|
|
struct conf_parsing_pos pos;
|
2008-02-02 18:52:33 -05:00
|
|
|
|
|
|
|
/** When ELinks is rewriting the configuration file, @c mirrored
|
|
|
|
* indicates the end of the part that has already been copied
|
|
|
|
* to the mirror string. Otherwise, @c mirrored is not used.
|
|
|
|
*
|
|
|
|
* @invariant @c mirrored @<= @c pos.look */
|
2021-01-02 10:20:27 -05:00
|
|
|
char *mirrored;
|
2008-02-02 20:15:57 -05:00
|
|
|
|
|
|
|
/** File name for error messages. If NULL then do not display
|
|
|
|
* error messages. */
|
2021-01-02 10:20:27 -05:00
|
|
|
const char *filename;
|
2008-02-02 17:16:38 -05:00
|
|
|
};
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-02 20:15:57 -05:00
|
|
|
/** Tell the user about an error in the configuration file.
|
|
|
|
* @return @a err, for convenience. */
|
|
|
|
static enum parse_error
|
|
|
|
show_parse_error(const struct conf_parsing_state *state, enum parse_error err)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
2021-01-02 10:20:27 -05:00
|
|
|
static const char error_msg[][40] = {
|
2008-02-02 20:15:57 -05:00
|
|
|
"no error", /* ERROR_NONE */
|
|
|
|
"unknown command", /* ERROR_COMMAND */
|
|
|
|
"parse error", /* ERROR_PARSE */
|
|
|
|
"unknown option", /* ERROR_OPTION */
|
|
|
|
"bad value", /* ERROR_VALUE */
|
|
|
|
"no memory left", /* ERROR_NOMEM */
|
|
|
|
};
|
|
|
|
|
|
|
|
if (state->filename) {
|
|
|
|
fprintf(stderr, "%s:%d: %s\n",
|
|
|
|
state->filename, state->pos.line, error_msg[err]);
|
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2008-02-02 17:16:38 -05:00
|
|
|
/** Skip comments and whitespace. */
|
|
|
|
static void
|
|
|
|
skip_white(struct conf_parsing_pos *pos)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
2021-01-02 10:20:27 -05:00
|
|
|
char *start = pos->look;
|
2008-02-02 17:16:38 -05:00
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
while (*start) {
|
2022-06-28 14:25:06 -04:00
|
|
|
while (isspace((unsigned char)*start)) {
|
2005-09-15 09:58:31 -04:00
|
|
|
if (*start == '\n') {
|
2008-02-02 17:16:38 -05:00
|
|
|
pos->line++;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
start++;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (*start == '#') {
|
|
|
|
start += strcspn(start, "\n");
|
|
|
|
} else {
|
2008-02-02 17:16:38 -05:00
|
|
|
pos->look = start;
|
|
|
|
return;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-02-02 17:16:38 -05:00
|
|
|
pos->look = start;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
2008-02-02 21:05:03 -05:00
|
|
|
/** Skip a quoted string.
|
|
|
|
* This function allows "mismatching quotes' because str_rd() does so. */
|
|
|
|
static void
|
|
|
|
skip_quoted(struct conf_parsing_pos *pos)
|
|
|
|
{
|
|
|
|
assert(isquote(*pos->look));
|
|
|
|
if_assert_failed return;
|
|
|
|
pos->look++;
|
|
|
|
|
|
|
|
for (;;) {
|
|
|
|
if (!*pos->look)
|
|
|
|
return;
|
|
|
|
if (isquote(*pos->look)) {
|
|
|
|
pos->look++;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
if (*pos->look == '\\' && pos->look[1])
|
|
|
|
pos->look++;
|
|
|
|
if (*pos->look == '\n')
|
|
|
|
pos->line++;
|
|
|
|
pos->look++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/** Skip the value of an option.
|
|
|
|
*
|
|
|
|
* This job is normally done by the reader function that corresponds
|
|
|
|
* to the type of the option. However, if ELinks does not recognize
|
|
|
|
* the name of the option, it cannot look up the type and has to use
|
|
|
|
* this function instead. */
|
|
|
|
static void
|
|
|
|
skip_option_value(struct conf_parsing_pos *pos)
|
|
|
|
{
|
|
|
|
if (isquote(*pos->look)) {
|
|
|
|
/* Looks like OPT_STRING, OPT_CODEPAGE, OPT_LANGUAGE,
|
|
|
|
* or OPT_COLOR. */
|
|
|
|
skip_quoted(pos);
|
|
|
|
} else {
|
|
|
|
/* Looks like OPT_BOOL, OPT_INT, or OPT_LONG. */
|
|
|
|
while (isasciialnum(*pos->look) || *pos->look == '.'
|
|
|
|
|| *pos->look == '+' || *pos->look == '-')
|
|
|
|
pos->look++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-02-02 21:38:24 -05:00
|
|
|
/** Skip to the next newline or comment that is not part of a quoted
|
|
|
|
* string. When ELinks hits a parse error in the configuration file,
|
|
|
|
* it calls this in order to find the place where is should resume
|
|
|
|
* parsing. This is intended to prevent ELinks from treating words
|
|
|
|
* in strings as commands. */
|
|
|
|
static void
|
|
|
|
skip_to_unquoted_newline_or_comment(struct conf_parsing_pos *pos)
|
|
|
|
{
|
|
|
|
while (*pos->look && *pos->look != '#' && *pos->look != '\n') {
|
|
|
|
if (isquote(*pos->look))
|
|
|
|
skip_quoted(pos);
|
|
|
|
else
|
|
|
|
pos->look++;
|
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Parse a command. Returns error code. */
|
|
|
|
/* If dynamic string credentials are supplied, we will mirror the command at
|
|
|
|
* the end of the string; however, we won't load the option value to the tree,
|
2008-02-03 06:07:43 -05:00
|
|
|
* and we will even write option value from the tree to the output string.
|
2008-02-03 07:36:48 -05:00
|
|
|
* We will only possibly set or clear OPT_MUST_SAVE flag in the option. */
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
static enum parse_error
|
2008-02-03 15:22:00 -05:00
|
|
|
parse_set_common(struct option *opt_tree, struct conf_parsing_state *state,
|
2019-04-21 06:27:40 -04:00
|
|
|
struct string *mirror, int is_system_conf, int want_domain)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
2021-01-02 10:20:27 -05:00
|
|
|
const char *domain_orig = NULL;
|
2008-02-03 15:22:00 -05:00
|
|
|
size_t domain_len = 0;
|
2021-01-02 10:20:27 -05:00
|
|
|
char *domain_copy = NULL;
|
|
|
|
const char *optname_orig;
|
2008-02-02 23:48:15 -05:00
|
|
|
size_t optname_len;
|
2021-01-02 10:20:27 -05:00
|
|
|
char *optname_copy;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-02 17:16:38 -05:00
|
|
|
skip_white(&state->pos);
|
2008-02-02 20:15:57 -05:00
|
|
|
if (!*state->pos.look) return show_parse_error(state, ERROR_PARSE);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2007-08-30 16:29:40 -04:00
|
|
|
if (want_domain) {
|
2008-02-03 15:22:00 -05:00
|
|
|
domain_orig = state->pos.look;
|
|
|
|
while (isident(*state->pos.look) || *state->pos.look == '*'
|
|
|
|
|| *state->pos.look == '.' || *state->pos.look == '+')
|
|
|
|
state->pos.look++;
|
|
|
|
domain_len = state->pos.look - domain_orig;
|
2007-08-30 16:29:40 -04:00
|
|
|
|
2008-02-03 15:22:00 -05:00
|
|
|
skip_white(&state->pos);
|
2007-08-30 16:29:40 -04:00
|
|
|
}
|
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
/* Option name */
|
2008-02-02 23:48:15 -05:00
|
|
|
optname_orig = state->pos.look;
|
2009-08-19 18:01:05 -04:00
|
|
|
while (is_option_name_char(*state->pos.look)
|
|
|
|
|| *state->pos.look == '.')
|
2008-02-02 17:16:38 -05:00
|
|
|
state->pos.look++;
|
2008-02-02 23:48:15 -05:00
|
|
|
optname_len = state->pos.look - optname_orig;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-02 17:16:38 -05:00
|
|
|
skip_white(&state->pos);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* Equal sign */
|
2008-02-02 23:48:15 -05:00
|
|
|
if (*state->pos.look != '=')
|
2008-02-02 20:15:57 -05:00
|
|
|
return show_parse_error(state, ERROR_PARSE);
|
2008-02-02 17:16:38 -05:00
|
|
|
state->pos.look++; /* '=' */
|
|
|
|
skip_white(&state->pos);
|
2008-02-02 23:48:15 -05:00
|
|
|
if (!*state->pos.look)
|
2008-02-02 20:15:57 -05:00
|
|
|
return show_parse_error(state, ERROR_VALUE);
|
2008-02-02 23:48:15 -05:00
|
|
|
|
|
|
|
optname_copy = memacpy(optname_orig, optname_len);
|
|
|
|
if (!optname_copy) return show_parse_error(state, ERROR_NOMEM);
|
2008-02-03 15:22:00 -05:00
|
|
|
if (want_domain) {
|
|
|
|
domain_copy = memacpy(domain_orig, domain_len);
|
|
|
|
if (!domain_copy) {
|
|
|
|
mem_free(optname_copy);
|
|
|
|
return show_parse_error(state, ERROR_NOMEM);
|
|
|
|
}
|
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* Option value */
|
|
|
|
{
|
|
|
|
struct option *opt;
|
2021-01-02 10:20:27 -05:00
|
|
|
char *val;
|
2008-02-02 21:05:03 -05:00
|
|
|
const struct conf_parsing_pos pos_before_value = state->pos;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-03 15:22:00 -05:00
|
|
|
if (want_domain && *domain_copy) {
|
2007-08-30 16:29:40 -04:00
|
|
|
struct option *domain_tree;
|
2007-09-14 09:14:34 -04:00
|
|
|
|
2008-02-03 15:22:00 -05:00
|
|
|
domain_tree = get_domain_tree(domain_copy);
|
2007-08-30 16:29:40 -04:00
|
|
|
if (!domain_tree) {
|
2008-02-03 15:22:00 -05:00
|
|
|
mem_free(domain_copy);
|
|
|
|
mem_free(optname_copy);
|
|
|
|
skip_option_value(&state->pos);
|
|
|
|
return show_parse_error(state, ERROR_NOMEM);
|
2007-08-30 16:29:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mirror) {
|
2008-02-03 15:22:00 -05:00
|
|
|
opt = get_opt_rec_real(domain_tree,
|
|
|
|
optname_copy);
|
2007-08-30 16:29:40 -04:00
|
|
|
} else {
|
2008-02-03 15:22:00 -05:00
|
|
|
opt = get_opt_rec(opt_tree, optname_copy);
|
2007-08-30 16:29:40 -04:00
|
|
|
if (opt) {
|
|
|
|
opt = get_option_shadow(opt, opt_tree,
|
|
|
|
domain_tree);
|
|
|
|
if (!opt) {
|
2008-02-03 15:22:00 -05:00
|
|
|
mem_free(domain_copy);
|
|
|
|
mem_free(optname_copy);
|
|
|
|
skip_option_value(&state->pos);
|
|
|
|
return show_parse_error(state,
|
|
|
|
ERROR_NOMEM);
|
2007-08-30 16:29:40 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2008-02-03 15:22:00 -05:00
|
|
|
opt = mirror
|
|
|
|
? get_opt_rec_real(opt_tree, optname_copy)
|
|
|
|
: get_opt_rec(opt_tree, optname_copy);
|
2007-08-30 16:29:40 -04:00
|
|
|
}
|
|
|
|
if (want_domain)
|
2008-02-03 15:22:00 -05:00
|
|
|
mem_free(domain_copy);
|
|
|
|
domain_copy = NULL;
|
2008-02-02 23:48:15 -05:00
|
|
|
mem_free(optname_copy);
|
|
|
|
optname_copy = NULL;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-02 21:05:03 -05:00
|
|
|
if (!opt || (opt->flags & OPT_HIDDEN)) {
|
|
|
|
show_parse_error(state, ERROR_OPTION);
|
|
|
|
skip_option_value(&state->pos);
|
2005-09-15 09:58:31 -04:00
|
|
|
return ERROR_OPTION;
|
2008-02-03 06:07:43 -05:00
|
|
|
/* TODO: Distinguish between two scenarios:
|
|
|
|
* - A newer version of ELinks has saved an
|
|
|
|
* option that this version does not recognize.
|
|
|
|
* The option must be preserved. (This works.)
|
|
|
|
* - The user has added an option, saved
|
|
|
|
* elinks.conf, restarted ELinks, deleted the
|
|
|
|
* option, and is now saving elinks.conf again.
|
|
|
|
* The option should be rewritten to "unset".
|
|
|
|
* (This does not work yet.)
|
|
|
|
* In both cases, ELinks has no struct option
|
|
|
|
* for that name. Possible fixes:
|
|
|
|
* - If the tree has OPT_AUTOCREATE, then
|
|
|
|
* assume the user had created that option,
|
|
|
|
* and rewrite it to "unset". Otherwise,
|
|
|
|
* keep it.
|
|
|
|
* - When the user deletes an option, just mark
|
|
|
|
* it with OPT_DELETED, and keep it in memory
|
|
|
|
* as long as OPT_TOUCHED is set. */
|
2008-02-02 21:05:03 -05:00
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2022-05-09 12:53:36 -04:00
|
|
|
if (!option_types[opt->type].read2) {
|
2008-02-02 21:05:03 -05:00
|
|
|
show_parse_error(state, ERROR_VALUE);
|
|
|
|
skip_option_value(&state->pos);
|
2005-09-15 09:58:31 -04:00
|
|
|
return ERROR_VALUE;
|
2008-02-02 21:05:03 -05:00
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2022-05-09 12:53:36 -04:00
|
|
|
val = option_types[opt->type].read2(opt, &state->pos.look,
|
2008-02-02 17:16:38 -05:00
|
|
|
&state->pos.line);
|
2008-02-02 21:05:03 -05:00
|
|
|
if (!val) {
|
|
|
|
/* The reader function failed. Jump back to
|
|
|
|
* the beginning of the value and skip it with
|
|
|
|
* the generic code. For the error message,
|
|
|
|
* use the line number at the beginning of the
|
|
|
|
* value, because the ending position is not
|
|
|
|
* interesting if there is an unclosed quote. */
|
|
|
|
state->pos = pos_before_value;
|
|
|
|
show_parse_error(state, ERROR_VALUE);
|
|
|
|
skip_option_value(&state->pos);
|
|
|
|
return ERROR_VALUE;
|
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-03 05:41:34 -05:00
|
|
|
if (!mirror) {
|
|
|
|
/* loading a configuration file */
|
|
|
|
if (!option_types[opt->type].set
|
|
|
|
|| !option_types[opt->type].set(opt, val)) {
|
|
|
|
mem_free(val);
|
|
|
|
return show_parse_error(state, ERROR_VALUE);
|
|
|
|
}
|
2008-02-03 07:36:48 -05:00
|
|
|
} else if (is_system_conf) {
|
|
|
|
/* scanning a file that will not be rewritten */
|
|
|
|
struct option *flagsite = indirect_option(opt);
|
|
|
|
|
|
|
|
if (!(flagsite->flags & OPT_DELETED)
|
|
|
|
&& option_types[opt->type].equals
|
|
|
|
&& option_types[opt->type].equals(opt, val))
|
|
|
|
flagsite->flags &= ~OPT_MUST_SAVE;
|
2005-09-15 09:58:31 -04:00
|
|
|
else
|
2008-02-03 07:36:48 -05:00
|
|
|
flagsite->flags |= OPT_MUST_SAVE;
|
2008-02-03 05:41:34 -05:00
|
|
|
} else {
|
|
|
|
/* rewriting a configuration file */
|
2008-02-03 06:57:13 -05:00
|
|
|
struct option *flagsite = indirect_option(opt);
|
|
|
|
|
|
|
|
if (flagsite->flags & OPT_DELETED) {
|
2008-02-03 06:07:43 -05:00
|
|
|
/* Replace the "set" command with an
|
|
|
|
* "unset" command. */
|
|
|
|
add_to_string(mirror, "unset ");
|
|
|
|
add_bytes_to_string(mirror, optname_orig,
|
|
|
|
optname_len);
|
|
|
|
state->mirrored = state->pos.look;
|
2022-05-09 12:53:36 -04:00
|
|
|
} else if (option_types[opt->type].write2) {
|
2008-02-03 05:49:49 -05:00
|
|
|
add_bytes_to_string(mirror, state->mirrored,
|
|
|
|
pos_before_value.look
|
|
|
|
- state->mirrored);
|
2022-05-09 12:53:36 -04:00
|
|
|
option_types[opt->type].write2(opt, mirror);
|
2008-02-02 18:52:33 -05:00
|
|
|
state->mirrored = state->pos.look;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
2008-02-03 06:07:43 -05:00
|
|
|
/* Remember that the option need not be
|
|
|
|
* written to the end of the file. */
|
2008-02-03 06:57:13 -05:00
|
|
|
flagsite->flags &= ~OPT_MUST_SAVE;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
mem_free(val);
|
|
|
|
}
|
|
|
|
|
|
|
|
return ERROR_NONE;
|
|
|
|
}
|
|
|
|
|
2007-08-30 16:29:40 -04:00
|
|
|
static enum parse_error
|
2008-02-03 15:22:00 -05:00
|
|
|
parse_set_domain(struct option *opt_tree, struct conf_parsing_state *state,
|
2019-04-21 06:27:40 -04:00
|
|
|
struct string *mirror, int is_system_conf)
|
2007-08-30 16:29:40 -04:00
|
|
|
{
|
2008-02-03 15:22:00 -05:00
|
|
|
return parse_set_common(opt_tree, state, mirror, is_system_conf, 1);
|
2007-08-30 16:29:40 -04:00
|
|
|
}
|
|
|
|
|
2007-08-29 09:30:40 -04:00
|
|
|
static enum parse_error
|
2008-02-03 15:22:00 -05:00
|
|
|
parse_set(struct option *opt_tree, struct conf_parsing_state *state,
|
2019-04-21 06:27:40 -04:00
|
|
|
struct string *mirror, int is_system_conf)
|
2007-08-29 09:30:40 -04:00
|
|
|
{
|
2008-02-03 15:22:00 -05:00
|
|
|
return parse_set_common(opt_tree, state, mirror, is_system_conf, 0);
|
2007-08-29 09:30:40 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2005-09-15 09:58:31 -04:00
|
|
|
static enum parse_error
|
2008-02-02 17:16:38 -05:00
|
|
|
parse_unset(struct option *opt_tree, struct conf_parsing_state *state,
|
2019-04-21 06:27:40 -04:00
|
|
|
struct string *mirror, int is_system_conf)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
2021-01-02 10:20:27 -05:00
|
|
|
const char *optname_orig;
|
2008-02-02 23:48:15 -05:00
|
|
|
size_t optname_len;
|
2021-01-02 10:20:27 -05:00
|
|
|
char *optname_copy;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-02 17:16:38 -05:00
|
|
|
skip_white(&state->pos);
|
2008-02-02 20:15:57 -05:00
|
|
|
if (!*state->pos.look) return show_parse_error(state, ERROR_PARSE);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* Option name */
|
2008-02-02 23:48:15 -05:00
|
|
|
optname_orig = state->pos.look;
|
2009-08-19 18:01:05 -04:00
|
|
|
while (is_option_name_char(*state->pos.look)
|
|
|
|
|| *state->pos.look == '.')
|
2008-02-02 17:16:38 -05:00
|
|
|
state->pos.look++;
|
2008-02-02 23:48:15 -05:00
|
|
|
optname_len = state->pos.look - optname_orig;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-02 23:48:15 -05:00
|
|
|
optname_copy = memacpy(optname_orig, optname_len);
|
|
|
|
if (!optname_copy) return show_parse_error(state, ERROR_NOMEM);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
{
|
|
|
|
struct option *opt;
|
|
|
|
|
2008-02-02 23:48:15 -05:00
|
|
|
opt = get_opt_rec_real(opt_tree, optname_copy);
|
|
|
|
mem_free(optname_copy);
|
|
|
|
optname_copy = NULL;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-03 05:35:56 -05:00
|
|
|
if (!opt || (opt->flags & OPT_HIDDEN)) {
|
|
|
|
/* The user wanted to delete the option, and
|
|
|
|
* it has already been deleted; this is not an
|
|
|
|
* error. This might happen if a version of
|
|
|
|
* ELinks has a built-in URL rewriting rule,
|
|
|
|
* the user disables it, and a later version
|
|
|
|
* no longer has it. */
|
|
|
|
return ERROR_NONE;
|
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
if (!mirror) {
|
2008-02-03 05:41:34 -05:00
|
|
|
/* loading a configuration file */
|
2005-09-15 09:58:31 -04:00
|
|
|
if (opt->flags & OPT_ALLOC) delete_option(opt);
|
2008-02-03 13:23:58 -05:00
|
|
|
else mark_option_as_deleted(opt);
|
2008-02-03 07:36:48 -05:00
|
|
|
} else if (is_system_conf) {
|
|
|
|
/* scanning a file that will not be rewritten */
|
|
|
|
struct option *flagsite = indirect_option(opt);
|
|
|
|
|
|
|
|
if (flagsite->flags & OPT_DELETED)
|
|
|
|
flagsite->flags &= ~OPT_MUST_SAVE;
|
2005-09-15 09:58:31 -04:00
|
|
|
else
|
2008-02-03 07:36:48 -05:00
|
|
|
flagsite->flags |= OPT_MUST_SAVE;
|
2005-09-15 09:58:31 -04:00
|
|
|
} else {
|
2008-02-03 05:41:34 -05:00
|
|
|
/* rewriting a configuration file */
|
2008-02-03 06:57:13 -05:00
|
|
|
struct option *flagsite = indirect_option(opt);
|
|
|
|
|
|
|
|
if (flagsite->flags & OPT_DELETED) {
|
2008-02-03 06:07:43 -05:00
|
|
|
/* The "unset" command is already in the file,
|
|
|
|
* and unlike with "set", there is no value
|
|
|
|
* to be updated. */
|
2022-05-09 12:53:36 -04:00
|
|
|
} else if (option_types[opt->type].write2) {
|
2008-02-03 06:07:43 -05:00
|
|
|
/* Replace the "unset" command with a
|
|
|
|
* "set" command. */
|
|
|
|
add_to_string(mirror, "set ");
|
|
|
|
add_bytes_to_string(mirror, optname_orig,
|
|
|
|
optname_len);
|
|
|
|
add_to_string(mirror, " = ");
|
2022-05-09 12:53:36 -04:00
|
|
|
option_types[opt->type].write2(opt, mirror);
|
2008-02-03 06:07:43 -05:00
|
|
|
state->mirrored = state->pos.look;
|
|
|
|
}
|
|
|
|
/* Remember that the option need not be
|
|
|
|
* written to the end of the file. */
|
2008-02-03 06:57:13 -05:00
|
|
|
flagsite->flags &= ~OPT_MUST_SAVE;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return ERROR_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static enum parse_error
|
2008-02-02 17:16:38 -05:00
|
|
|
parse_bind(struct option *opt_tree, struct conf_parsing_state *state,
|
2019-04-21 06:27:40 -04:00
|
|
|
struct string *mirror, int is_system_conf)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
2021-01-02 10:20:27 -05:00
|
|
|
char *keymap, *keystroke, *action;
|
2005-09-15 09:58:31 -04:00
|
|
|
enum parse_error err = ERROR_NONE;
|
2008-02-02 21:38:24 -05:00
|
|
|
struct conf_parsing_pos before_error;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-02 17:16:38 -05:00
|
|
|
skip_white(&state->pos);
|
2008-02-02 20:15:57 -05:00
|
|
|
if (!*state->pos.look) return show_parse_error(state, ERROR_PARSE);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* Keymap */
|
2008-02-02 21:38:24 -05:00
|
|
|
before_error = state->pos;
|
2022-05-09 12:53:36 -04:00
|
|
|
keymap = option_types[OPT_STRING].read2(NULL, &state->pos.look,
|
2008-02-02 17:16:38 -05:00
|
|
|
&state->pos.line);
|
|
|
|
skip_white(&state->pos);
|
2008-02-02 21:38:24 -05:00
|
|
|
if (!keymap || !*state->pos.look) {
|
|
|
|
state->pos = before_error;
|
|
|
|
return show_parse_error(state, ERROR_PARSE);
|
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* Keystroke */
|
2008-02-02 21:38:24 -05:00
|
|
|
before_error = state->pos;
|
2022-05-09 12:53:36 -04:00
|
|
|
keystroke = option_types[OPT_STRING].read2(NULL, &state->pos.look,
|
2008-02-02 17:16:38 -05:00
|
|
|
&state->pos.line);
|
|
|
|
skip_white(&state->pos);
|
|
|
|
if (!keystroke || !*state->pos.look) {
|
2008-02-02 21:17:21 -05:00
|
|
|
mem_free(keymap); mem_free_if(keystroke);
|
2008-02-02 21:38:24 -05:00
|
|
|
state->pos = before_error;
|
|
|
|
return show_parse_error(state, ERROR_PARSE);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Equal sign */
|
2008-02-02 17:16:38 -05:00
|
|
|
skip_white(&state->pos);
|
|
|
|
if (*state->pos.look != '=') {
|
2005-09-15 09:58:31 -04:00
|
|
|
mem_free(keymap); mem_free(keystroke);
|
2008-02-02 20:15:57 -05:00
|
|
|
return show_parse_error(state, ERROR_PARSE);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
2008-02-02 17:16:38 -05:00
|
|
|
state->pos.look++; /* '=' */
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-02 17:16:38 -05:00
|
|
|
skip_white(&state->pos);
|
|
|
|
if (!*state->pos.look) {
|
2005-09-15 09:58:31 -04:00
|
|
|
mem_free(keymap); mem_free(keystroke);
|
2008-02-02 20:15:57 -05:00
|
|
|
return show_parse_error(state, ERROR_PARSE);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* Action */
|
2008-02-02 21:38:24 -05:00
|
|
|
before_error = state->pos;
|
2022-05-09 12:53:36 -04:00
|
|
|
action = option_types[OPT_STRING].read2(NULL, &state->pos.look,
|
2008-02-02 17:16:38 -05:00
|
|
|
&state->pos.line);
|
2005-09-15 09:58:31 -04:00
|
|
|
if (!action) {
|
2008-02-02 14:05:49 -05:00
|
|
|
mem_free(keymap); mem_free(keystroke);
|
2008-02-02 21:38:24 -05:00
|
|
|
state->pos = before_error;
|
|
|
|
return show_parse_error(state, ERROR_PARSE);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
2008-02-03 05:41:34 -05:00
|
|
|
if (!mirror) {
|
|
|
|
/* loading a configuration file */
|
2005-09-15 09:58:31 -04:00
|
|
|
/* We don't bother to bind() if -default-keys. */
|
|
|
|
if (!get_cmd_opt_bool("default-keys")
|
|
|
|
&& bind_do(keymap, keystroke, action, is_system_conf)) {
|
|
|
|
/* bind_do() tried but failed. */
|
2008-02-03 05:41:34 -05:00
|
|
|
err = show_parse_error(state, ERROR_VALUE);
|
2005-09-15 09:58:31 -04:00
|
|
|
} else {
|
|
|
|
err = ERROR_NONE;
|
|
|
|
}
|
2008-02-03 07:36:48 -05:00
|
|
|
} else if (is_system_conf) {
|
|
|
|
/* scanning a file that will not be rewritten */
|
|
|
|
/* TODO */
|
2008-02-03 05:41:34 -05:00
|
|
|
} else {
|
|
|
|
/* rewriting a configuration file */
|
|
|
|
/* Mirror what we already have. If the keystroke has
|
|
|
|
* been unbound, then act_str is simply "none" and
|
|
|
|
* this does not require special handling. */
|
2021-01-02 10:20:27 -05:00
|
|
|
char *act_str = bind_act(keymap, keystroke);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
if (act_str) {
|
2008-02-02 18:52:33 -05:00
|
|
|
add_bytes_to_string(mirror, state->mirrored,
|
2008-02-02 21:38:24 -05:00
|
|
|
before_error.look - state->mirrored);
|
2005-09-15 09:58:31 -04:00
|
|
|
add_to_string(mirror, act_str);
|
|
|
|
mem_free(act_str);
|
2008-02-02 18:52:33 -05:00
|
|
|
state->mirrored = state->pos.look;
|
2005-09-15 09:58:31 -04:00
|
|
|
} else {
|
2008-02-02 20:15:57 -05:00
|
|
|
err = show_parse_error(state, ERROR_VALUE);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
}
|
|
|
|
mem_free(keymap); mem_free(keystroke); mem_free(action);
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
|
2022-01-30 08:32:48 -05:00
|
|
|
static int load_config_file(const char *, const char *, struct option *,
|
2019-04-21 06:27:40 -04:00
|
|
|
struct string *, int);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
static enum parse_error
|
2008-02-02 17:16:38 -05:00
|
|
|
parse_include(struct option *opt_tree, struct conf_parsing_state *state,
|
2019-04-21 06:27:40 -04:00
|
|
|
struct string *mirror, int is_system_conf)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
2021-01-02 10:20:27 -05:00
|
|
|
char *fname;
|
2019-04-21 06:27:40 -04:00
|
|
|
struct string dumbstring;
|
2008-02-02 21:38:24 -05:00
|
|
|
struct conf_parsing_pos before_error;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-02 20:15:57 -05:00
|
|
|
if (!init_string(&dumbstring))
|
|
|
|
return show_parse_error(state, ERROR_NOMEM);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-02 17:16:38 -05:00
|
|
|
skip_white(&state->pos);
|
|
|
|
if (!*state->pos.look) {
|
2008-02-02 14:05:49 -05:00
|
|
|
done_string(&dumbstring);
|
2008-02-02 20:15:57 -05:00
|
|
|
return show_parse_error(state, ERROR_PARSE);
|
2008-02-02 14:05:49 -05:00
|
|
|
}
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
/* File name */
|
2008-02-02 21:38:24 -05:00
|
|
|
before_error = state->pos;
|
2022-05-09 12:53:36 -04:00
|
|
|
fname = option_types[OPT_STRING].read2(NULL, &state->pos.look,
|
2008-02-02 17:16:38 -05:00
|
|
|
&state->pos.line);
|
2005-09-15 09:58:31 -04:00
|
|
|
if (!fname) {
|
|
|
|
done_string(&dumbstring);
|
2008-02-02 21:38:24 -05:00
|
|
|
state->pos = before_error;
|
|
|
|
return show_parse_error(state, ERROR_PARSE);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
/* We want load_config_file() to watermark stuff, but not to load
|
|
|
|
* anything, polluting our beloved options tree - thus, we will feed it
|
|
|
|
* with some dummy string which we will destroy later; still better
|
|
|
|
* than cloning whole options tree or polluting interface with another
|
|
|
|
* rarely-used option ;). */
|
|
|
|
/* XXX: We should try CONFDIR/<file> when proceeding
|
|
|
|
* CONFDIR/<otherfile> ;). --pasky */
|
2021-01-02 10:20:27 -05:00
|
|
|
if (load_config_file(fname[0] == '/' ? (char *) ""
|
2005-09-15 09:58:31 -04:00
|
|
|
: elinks_home,
|
2008-02-03 07:36:48 -05:00
|
|
|
fname, opt_tree,
|
|
|
|
mirror ? &dumbstring : NULL, 1)) {
|
2005-09-15 09:58:31 -04:00
|
|
|
done_string(&dumbstring);
|
|
|
|
mem_free(fname);
|
2008-02-02 20:15:57 -05:00
|
|
|
return show_parse_error(state, ERROR_VALUE);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
|
|
|
done_string(&dumbstring);
|
|
|
|
mem_free(fname);
|
|
|
|
return ERROR_NONE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
struct parse_handler {
|
2021-01-02 10:20:27 -05:00
|
|
|
const char *command;
|
2005-09-15 09:58:31 -04:00
|
|
|
enum parse_error (*handler)(struct option *opt_tree,
|
2008-02-02 17:16:38 -05:00
|
|
|
struct conf_parsing_state *state,
|
2019-04-21 06:27:40 -04:00
|
|
|
struct string *mirror, int is_system_conf);
|
2005-09-15 09:58:31 -04:00
|
|
|
};
|
|
|
|
|
2008-01-26 17:22:44 -05:00
|
|
|
static const struct parse_handler parse_handlers[] = {
|
2007-08-30 16:29:40 -04:00
|
|
|
{ "set_domain", parse_set_domain },
|
2005-09-15 09:58:31 -04:00
|
|
|
{ "set", parse_set },
|
|
|
|
{ "unset", parse_unset },
|
|
|
|
{ "bind", parse_bind },
|
|
|
|
{ "include", parse_include },
|
|
|
|
{ NULL, NULL }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2008-02-02 17:20:07 -05:00
|
|
|
static enum parse_error
|
2008-02-02 17:16:38 -05:00
|
|
|
parse_config_command(struct option *options, struct conf_parsing_state *state,
|
2019-04-21 06:27:40 -04:00
|
|
|
struct string *mirror, int is_system_conf)
|
2005-09-15 09:58:31 -04:00
|
|
|
{
|
2008-01-26 17:22:44 -05:00
|
|
|
const struct parse_handler *handler;
|
2005-09-15 09:58:31 -04:00
|
|
|
|
2008-02-02 18:52:33 -05:00
|
|
|
/* If we're mirroring, then everything up to this point must
|
|
|
|
* have already been mirrored. */
|
|
|
|
assert(mirror == NULL || state->mirrored == state->pos.look);
|
2008-02-02 20:15:57 -05:00
|
|
|
if_assert_failed return show_parse_error(state, ERROR_PARSE);
|
2005-09-15 09:58:31 -04:00
|
|
|
|
|
|
|
for (handler = parse_handlers; handler->command;
|
|
|
|
handler++) {
|
|
|
|
int cmdlen = strlen(handler->command);
|
|
|
|
|
2008-02-02 17:16:38 -05:00
|
|
|
if (!strncmp(state->pos.look, handler->command, cmdlen)
|
2022-06-28 14:25:06 -04:00
|
|
|
&& isspace((unsigned char)state->pos.look[cmdlen])) {
|
2005-09-15 09:58:31 -04:00
|
|
|
enum parse_error err;
|
|
|
|
|
2008-02-02 17:16:38 -05:00
|
|
|
state->pos.look += cmdlen;
|
2008-02-02 18:52:33 -05:00
|
|
|
err = handler->handler(options, state, mirror,
|
2005-09-15 09:58:31 -04:00
|
|
|
is_system_conf);
|
2008-02-02 18:52:33 -05:00
|
|
|
if (mirror) {
|
|
|
|
/* Mirror any characters that the handler
|
|
|
|
* consumed but did not already mirror. */
|
|
|
|
add_bytes_to_string(mirror, state->mirrored,
|
|
|
|
state->pos.look - state->mirrored);
|
|
|
|
state->mirrored = state->pos.look;
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
return err;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2008-02-02 20:15:57 -05:00
|
|
|
return show_parse_error(state, ERROR_COMMAND);
|
2005-09-15 09:58:31 -04:00
|
|
|
}
|
|
|
|
|
2008-02-02 17:20:07 -05:00
|
|
|
enum parse_error
|
2021-01-02 10:20:27 -05:00
|
|
|
parse_config_exmode_command(char *cmd)
|
2008-02-02 17:20:07 -05:00
|
|
|