/* Config file manipulation */ #ifdef HAVE_CONFIG_H #include "config.h" #endif #include #include #include #include #include /* OS/2 needs this after sys/types.h */ #ifdef HAVE_FCNTL_H #include /* OS/2 needs this after sys/types.h */ #endif #ifdef HAVE_UNISTD_H #include #endif #include "elinks.h" #include "config/conf.h" #include "config/dialogs.h" #include "config/domain.h" #include "config/home.h" #include "config/kbdbind.h" #include "config/options.h" #include "config/opttypes.h" #include "intl/libintl.h" #include "osdep/osdep.h" #include "terminal/terminal.h" #include "util/error.h" #include "util/memory.h" #include "util/qs_parse/qs_parse.h" #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. */ struct conf_parsing_pos { /** Points to the next character to be parsed from the * configuration file. */ char *look; /** The line number corresponding to #look. This is * shown in error messages. */ int line; }; 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. */ struct conf_parsing_pos pos; /** 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 */ char *mirrored; /** File name for error messages. If NULL then do not display * error messages. */ const char *filename; int exmode; }; /** 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) { static const char error_msg[][40] = { "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; } /** Skip comments and whitespace. */ static void skip_white(struct conf_parsing_pos *pos) { char *start = pos->look; while (*start) { while (isspace((unsigned char)*start)) { if (*start == '\n') { pos->line++; } start++; } if (*start == '#') { start += strcspn(start, "\n"); } else { pos->look = start; return; } } pos->look = start; } /** 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++; } } /** 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++; } } /* 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, * and we will even write option value from the tree to the output string. * We will only possibly set or clear OPT_MUST_SAVE flag in the option. */ static enum parse_error parse_set_common(struct option *opt_tree, struct conf_parsing_state *state, struct string *mirror, int is_system_conf, int want_domain) { const char *domain_orig = NULL; size_t domain_len = 0; char *domain_copy = NULL; const char *optname_orig; size_t optname_len; char *optname_copy; skip_white(&state->pos); if (!*state->pos.look) return show_parse_error(state, ERROR_PARSE); if (want_domain) { 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; skip_white(&state->pos); } /* Option name */ optname_orig = state->pos.look; while (is_option_name_char(*state->pos.look) || *state->pos.look == '.') state->pos.look++; optname_len = state->pos.look - optname_orig; skip_white(&state->pos); /* Equal sign */ if (*state->pos.look != '=') return show_parse_error(state, ERROR_PARSE); state->pos.look++; /* '=' */ skip_white(&state->pos); if (!*state->pos.look) return show_parse_error(state, ERROR_VALUE); optname_copy = memacpy(optname_orig, optname_len); if (!optname_copy) return show_parse_error(state, ERROR_NOMEM); if (want_domain) { domain_copy = memacpy(domain_orig, domain_len); if (!domain_copy) { mem_free(optname_copy); return show_parse_error(state, ERROR_NOMEM); } } /* Option value */ { struct option *opt; char *val; const struct conf_parsing_pos pos_before_value = state->pos; if (want_domain && *domain_copy) { struct option *domain_tree; domain_tree = get_domain_tree(domain_copy); if (!domain_tree) { mem_free(domain_copy); mem_free(optname_copy); skip_option_value(&state->pos); return show_parse_error(state, ERROR_NOMEM); } if (mirror) { opt = get_opt_rec_real(domain_tree, optname_copy); } else { opt = get_opt_rec(opt_tree, optname_copy); if (opt) { opt = get_option_shadow(opt, opt_tree, domain_tree); if (!opt) { mem_free(domain_copy); mem_free(optname_copy); skip_option_value(&state->pos); return show_parse_error(state, ERROR_NOMEM); } } } } else { opt = mirror ? get_opt_rec_real(opt_tree, optname_copy) : get_opt_rec(opt_tree, optname_copy); } if (want_domain) mem_free(domain_copy); domain_copy = NULL; mem_free(optname_copy); optname_copy = NULL; if (!opt || (opt->flags & OPT_HIDDEN)) { show_parse_error(state, ERROR_OPTION); skip_option_value(&state->pos); return ERROR_OPTION; /* 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. */ } if (!option_types[opt->type].read2) { show_parse_error(state, ERROR_VALUE); skip_option_value(&state->pos); return ERROR_VALUE; } val = option_types[opt->type].read2(opt, &state->pos.look, &state->pos.line); 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; } 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); } if (state->exmode) { option_changed(NULL, opt); } } 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; else flagsite->flags |= OPT_MUST_SAVE; } else { /* rewriting a configuration file */ struct option *flagsite = indirect_option(opt); if (flagsite->flags & OPT_DELETED) { /* 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; } else if (option_types[opt->type].write2) { add_bytes_to_string(mirror, state->mirrored, pos_before_value.look - state->mirrored); option_types[opt->type].write2(opt, mirror); state->mirrored = state->pos.look; } /* Remember that the option need not be * written to the end of the file. */ flagsite->flags &= ~OPT_MUST_SAVE; } mem_free(val); } return ERROR_NONE; } static enum parse_error parse_set_domain(struct option *opt_tree, struct conf_parsing_state *state, struct string *mirror, int is_system_conf) { return parse_set_common(opt_tree, state, mirror, is_system_conf, 1); } static enum parse_error parse_set(struct option *opt_tree, struct conf_parsing_state *state, struct string *mirror, int is_system_conf) { return parse_set_common(opt_tree, state, mirror, is_system_conf, 0); } static enum parse_error parse_unset(struct option *opt_tree, struct conf_parsing_state *state, struct string *mirror, int is_system_conf) { const char *optname_orig; size_t optname_len; char *optname_copy; skip_white(&state->pos); if (!*state->pos.look) return show_parse_error(state, ERROR_PARSE); /* Option name */ optname_orig = state->pos.look; while (is_option_name_char(*state->pos.look) || *state->pos.look == '.') state->pos.look++; optname_len = state->pos.look - optname_orig; optname_copy = memacpy(optname_orig, optname_len); if (!optname_copy) return show_parse_error(state, ERROR_NOMEM); { struct option *opt; opt = get_opt_rec_real(opt_tree, optname_copy); mem_free(optname_copy); optname_copy = NULL; 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; } if (!mirror) { /* loading a configuration file */ if (opt->flags & OPT_ALLOC) delete_option(opt); else mark_option_as_deleted(opt); } 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; else flagsite->flags |= OPT_MUST_SAVE; } else { /* rewriting a configuration file */ struct option *flagsite = indirect_option(opt); if (flagsite->flags & OPT_DELETED) { /* The "unset" command is already in the file, * and unlike with "set", there is no value * to be updated. */ } else if (option_types[opt->type].write2) { /* 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, " = "); option_types[opt->type].write2(opt, mirror); state->mirrored = state->pos.look; } /* Remember that the option need not be * written to the end of the file. */ flagsite->flags &= ~OPT_MUST_SAVE; } } return ERROR_NONE; } static enum parse_error parse_bind(struct option *opt_tree, struct conf_parsing_state *state, struct string *mirror, int is_system_conf) { char *keymap, *keystroke, *action; enum parse_error err = ERROR_NONE; struct conf_parsing_pos before_error; skip_white(&state->pos); if (!*state->pos.look) return show_parse_error(state, ERROR_PARSE); /* Keymap */ before_error = state->pos; keymap = option_types[OPT_STRING].read2(NULL, &state->pos.look, &state->pos.line); skip_white(&state->pos); if (!keymap || !*state->pos.look) { state->pos = before_error; return show_parse_error(state, ERROR_PARSE); } /* Keystroke */ before_error = state->pos; keystroke = option_types[OPT_STRING].read2(NULL, &state->pos.look, &state->pos.line); skip_white(&state->pos); if (!keystroke || !*state->pos.look) { mem_free(keymap); mem_free_if(keystroke); state->pos = before_error; return show_parse_error(state, ERROR_PARSE); } /* Equal sign */ skip_white(&state->pos); if (*state->pos.look != '=') { mem_free(keymap); mem_free(keystroke); return show_parse_error(state, ERROR_PARSE); } state->pos.look++; /* '=' */ skip_white(&state->pos); if (!*state->pos.look) { mem_free(keymap); mem_free(keystroke); return show_parse_error(state, ERROR_PARSE); } /* Action */ before_error = state->pos; action = option_types[OPT_STRING].read2(NULL, &state->pos.look, &state->pos.line); if (!action) { mem_free(keymap); mem_free(keystroke); state->pos = before_error; return show_parse_error(state, ERROR_PARSE); } if (!mirror) { /* loading a configuration file */ /* 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. */ err = show_parse_error(state, ERROR_VALUE); } else { err = ERROR_NONE; } } else if (is_system_conf) { /* scanning a file that will not be rewritten */ /* TODO */ } 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. */ char *act_str = bind_act(keymap, keystroke); if (act_str) { add_bytes_to_string(mirror, state->mirrored, before_error.look - state->mirrored); add_to_string(mirror, act_str); mem_free(act_str); state->mirrored = state->pos.look; } else { err = show_parse_error(state, ERROR_VALUE); } } mem_free(keymap); mem_free(keystroke); mem_free(action); return err; } static int load_config_file(const char *, const char *, struct option *, struct string *, int); static enum parse_error parse_include(struct option *opt_tree, struct conf_parsing_state *state, struct string *mirror, int is_system_conf) { char *fname; struct string dumbstring; struct conf_parsing_pos before_error; if (!init_string(&dumbstring)) return show_parse_error(state, ERROR_NOMEM); skip_white(&state->pos); if (!*state->pos.look) { done_string(&dumbstring); return show_parse_error(state, ERROR_PARSE); } /* File name */ before_error = state->pos; fname = option_types[OPT_STRING].read2(NULL, &state->pos.look, &state->pos.line); if (!fname) { done_string(&dumbstring); state->pos = before_error; return show_parse_error(state, ERROR_PARSE); } /* 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/ when proceeding * CONFDIR/ ;). --pasky */ if (load_config_file(fname[0] == '/' ? (char *) "" : elinks_home, fname, opt_tree, mirror ? &dumbstring : NULL, 1)) { done_string(&dumbstring); mem_free(fname); return show_parse_error(state, ERROR_VALUE); } done_string(&dumbstring); mem_free(fname); return ERROR_NONE; } struct parse_handler { const char *command; enum parse_error (*handler)(struct option *opt_tree, struct conf_parsing_state *state, struct string *mirror, int is_system_conf); }; static const struct parse_handler parse_handlers[] = { { "set_domain", parse_set_domain }, { "set", parse_set }, { "unset", parse_unset }, { "bind", parse_bind }, { "include", parse_include }, { NULL, NULL } }; static enum parse_error parse_config_command(struct option *options, struct conf_parsing_state *state, struct string *mirror, int is_system_conf) { const struct parse_handler *handler; /* If we're mirroring, then everything up to this point must * have already been mirrored. */ assert(mirror == NULL || state->mirrored == state->pos.look); if_assert_failed return show_parse_error(state, ERROR_PARSE); for (handler = parse_handlers; handler->command; handler++) { int cmdlen = strlen(handler->command); if (!strncmp(state->pos.look, handler->command, cmdlen) && isspace((unsigned char)state->pos.look[cmdlen])) { enum parse_error err; state->pos.look += cmdlen; err = handler->handler(options, state, mirror, is_system_conf); 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; } return err; } } return show_parse_error(state, ERROR_COMMAND); } enum parse_error parse_config_exmode_command(char *cmd) { struct conf_parsing_state state = {{ 0 }}; state.pos.look = cmd; state.pos.line = 0; state.mirrored = NULL; /* not read because mirror is NULL too */ state.filename = NULL; /* prevent error messages */ state.exmode = 1; return parse_config_command(config_options, &state, NULL, 0); } void parse_config_file(struct option *options, const char *name, char *file, struct string *mirror, int is_system_conf) { struct conf_parsing_state state = {{ 0 }}; int error_occurred = 0; state.pos.look = file; state.pos.line = 1; state.mirrored = file; if (!mirror && get_cmd_opt_int("verbose") >= VERBOSE_WARNINGS) state.filename = name; while (state.pos.look && *state.pos.look) { enum parse_error err = ERROR_NONE; /* Skip all possible comments and whitespace. */ skip_white(&state.pos); /* Mirror what we already have */ if (mirror) { add_bytes_to_string(mirror, state.mirrored, state.pos.look - state.mirrored); state.mirrored = state.pos.look; } /* Second chance to escape from the hell. */ if (!*state.pos.look) break; err = parse_config_command(options, &state, mirror, is_system_conf); switch (err) { case ERROR_NONE: break; case ERROR_COMMAND: case ERROR_PARSE: /* Jump over this crap we can't understand. */ skip_to_unquoted_newline_or_comment(&state.pos); /* Mirror what we already have */ if (mirror) { add_bytes_to_string(mirror, state.mirrored, state.pos.look - state.mirrored); state.mirrored = state.pos.look; } /* fall through */ default: error_occurred = 1; break; } } if (!error_occurred || !state.filename) return; /* If an error occurred make sure that the user is notified and is able * to see it. First sound the bell. Then, if the text viewer is going to * be started, sleep for a while so the message will be visible before * ELinks starts drawing to on the screen and overwriting any error * messages. This should not be necessary for terminals supporting the * alternate screen mode but better to be safe. (debian bug 305017) */ fputc('\a', stderr); if (get_cmd_opt_bool("dump") || get_cmd_opt_bool("source")) return; sleep(1); } static char * read_config_file(char *name) { #define FILE_BUF 1024 char cfg_buffer[FILE_BUF]; struct string string; int fd; ssize_t r; fd = open(name, O_RDONLY | O_NOCTTY); if (fd < 0) return NULL; set_bin(fd); if (!init_string(&string)) return NULL; while ((r = safe_read(fd, cfg_buffer, FILE_BUF)) > 0) { int i; /* Clear problems ;). */ for (i = 0; i < r; i++) if (!cfg_buffer[i]) cfg_buffer[i] = ' '; add_bytes_to_string(&string, cfg_buffer, r); } if (r < 0) done_string(&string); close(fd); return string.source; #undef FILE_BUF } /* Return 0 on success. */ static int load_config_file(const char *prefix, const char *name, struct option *options, struct string *mirror, int is_system_conf) { char *config_str, *config_file; config_file = straconcat(prefix, STRING_DIR_SEP, name, (char *) NULL); if (!config_file) return 1; config_str = read_config_file(config_file); if (!config_str) { mem_free(config_file); config_file = straconcat(prefix, STRING_DIR_SEP, ".", name, (char *) NULL); if (!config_file) return 2; config_str = read_config_file(config_file); if (!config_str) { mem_free(config_file); return 3; } } parse_config_file(options, config_file, config_str, mirror, is_system_conf); mem_free(config_str); mem_free(config_file); return 0; } static void load_config_from(char *file, struct option *tree) { load_config_file(CONFDIR, file, tree, NULL, 1); load_config_file(empty_string_or_(elinks_home), file, tree, NULL, 0); } void load_config(void) { load_config_from(get_cmd_opt_str("config-file"), config_options); } static int indentation = 2; /* 0 -> none, 1 -> only option full name+type, 2 -> only desc, 3 -> both */ static int comments = 3; static inline const char * conf_i18n(const char *s, int i18n) { if (i18n) return gettext(s); return s; } static void add_indent_to_string(struct string *string, int depth) { if (!depth) return; add_xchar_to_string(string, ' ', depth * indentation); } struct string * wrap_option_desc(struct string *out, const char *src, const struct string *indent, int maxwidth) { const char *last_space = NULL; const char *uncopied = src; int width = 0; /* TODO: multibyte or fullwidth characters */ for (; *src; src++, width++) { if (*src == '\n') { last_space = src; goto split; } if (*src == ' ') last_space = src; if (width >= maxwidth && last_space) { split: if (!add_string_to_string(out, indent)) return NULL; if (!add_bytes_to_string(out, uncopied, last_space - uncopied)) return NULL; if (!add_char_to_string(out, '\n')) return NULL; uncopied = last_space + 1; width = src - uncopied; last_space = NULL; } } if (*uncopied) { if (!add_string_to_string(out, indent)) return NULL; if (!add_to_string(out, uncopied)) return NULL; if (!add_char_to_string(out, '\n')) return NULL; } return out; } static void output_option_desc_as_comment(struct string *out, const struct option *option, int i18n, int depth) { const char *desc_i18n = conf_i18n(option->desc, i18n); struct string indent; if (!init_string(&indent)) return; add_indent_to_string(&indent, depth); if (!add_to_string(&indent, "# ")) goto out_of_memory; if (!wrap_option_desc(out, desc_i18n, &indent, 80 - indent.length)) goto out_of_memory; out_of_memory: done_string(&indent); } static char *smart_config_output_fn_domain; static void smart_config_output_fn(struct string *string, struct option *option, char *path, int depth, int do_print_comment, int action, int i18n) { if (option->type == OPT_ALIAS) return; switch (action) { case 0: if (!(comments & 1)) break; add_indent_to_string(string, depth); add_to_string(string, "## "); if (path) { add_to_string(string, path); add_char_to_string(string, '.'); } add_to_string(string, option->name); add_char_to_string(string, ' '); add_to_string(string, option_types[option->type].help_str); add_char_to_string(string, '\n'); break; case 1: if (!(comments & 2)) break; if (!option->desc || !do_print_comment) break; output_option_desc_as_comment(string, option, i18n, depth); break; case 2: add_indent_to_string(string, depth); if (option->flags & OPT_DELETED) { add_to_string(string, "un"); } if (smart_config_output_fn_domain) { add_to_string(string, "set_domain "); add_to_string(string, smart_config_output_fn_domain); add_char_to_string(string, ' '); } else { add_to_string(string, "set "); } if (path) { add_to_string(string, path); add_char_to_string(string, '.'); } add_to_string(string, option->name); if (!(option->flags & OPT_DELETED)) { add_to_string(string, " = "); /* OPT_ALIAS won't ever. OPT_TREE won't reach action 2. * OPT_SPECIAL makes no sense in the configuration * context. */ assert(option_types[option->type].write2); option_types[option->type].write2(option, string); } add_char_to_string(string, '\n'); if (do_print_comment) add_char_to_string(string, '\n'); break; case 3: if (do_print_comment < 2) add_char_to_string(string, '\n'); break; } } static void smart_config_output_fn_html(struct string *string, struct option *option, char *path, int depth, int do_print_comment, int action, int i18n) { static unsigned int counter; int is_str = 0; if (option->type == OPT_ALIAS) { return; } if (option->flags & OPT_DELETED) { return; } if (smart_config_output_fn_domain) { return; } switch (action) { case 0: case 1: break; case 2: counter++; add_to_string(string, ""); if (path) { add_to_string(string, path); add_char_to_string(string, '.'); } add_to_string(string, option->name); add_to_string(string, ""); add_format_to_string(string, "
", counter); add_to_string(string, "name); add_to_string(string, "\"/>type].write2); { struct string tmp; if (init_string(&tmp)) { option_types[option->type].write2(option, &tmp); if (tmp.length >= 2 && tmp.source[0] == '"' && tmp.source[tmp.length - 1] == '"') { add_bytes_to_string(string, tmp.source + 1, tmp.length - 2); is_str = 1; } else { add_string_to_string(string, &tmp); } done_string(&tmp); } } add_to_string(string, "\"/>"); if (is_str) { add_to_string(string, ""); } add_to_string(string, "
\n"); break; } } static void add_cfg_header_to_string(struct string *string, const char *text) { int n = strlen(text) + 2; int_bounds(&n, 10, 80); add_to_string(string, "\n\n\n"); add_xchar_to_string(string, '#', n); add_to_string(string, "\n# "); add_to_string(string, text); add_to_string(string, "#\n\n"); } char * create_about_config_string(void) { struct option *options = config_options; struct string config; /* Don't write headers if nothing will be added anyway. */ struct string tmpstring; int origlen; if (!init_string(&config)) return NULL; { int set_all = 1; struct domain_tree *domain; prepare_mustsave_flags(options->value.tree, set_all); foreach (domain, domain_trees) { prepare_mustsave_flags(domain->tree->value.tree, set_all); } } /* Scaring. */ if (!init_string(&tmpstring)) goto get_me_out; add_to_string(&tmpstring, "\n"); origlen = tmpstring.length; smart_config_string(&tmpstring, 2, 0, options->value.tree, NULL, 0, smart_config_output_fn_html); { struct domain_tree *domain; foreach (domain, domain_trees) { smart_config_output_fn_domain = domain->name; smart_config_string(&tmpstring, 2, 0, domain->tree->value.tree, NULL, 0, smart_config_output_fn_html); } smart_config_output_fn_domain = NULL; } add_to_string(&tmpstring, "
Option nameValue
"); if (tmpstring.length > origlen) add_string_to_string(&config, &tmpstring); done_string(&tmpstring); get_me_out: return config.source; } char * create_config_string(const char *prefix, const char *name) { struct option *options = config_options; struct string config; /* Don't write headers if nothing will be added anyway. */ struct string tmpstring; int origlen; int savestyle = get_opt_int("config.saving_style", NULL); int i18n = get_opt_bool("config.i18n", NULL); if (!init_string(&config)) return NULL; { int set_all = (savestyle == 1 || savestyle == 2); struct domain_tree *domain; prepare_mustsave_flags(options->value.tree, set_all); foreach (domain, domain_trees) { prepare_mustsave_flags(domain->tree->value.tree, set_all); } } /* Scaring. */ if (savestyle == 2 || load_config_file(prefix, name, options, &config, 0) || !config.length) { /* At first line, and in English, write ELinks version, may be * of some help in future. Please keep that format for it. * --Zas */ add_to_string(&config, "## ELinks " VERSION " configuration file\n\n"); assert(savestyle >= 0 && savestyle <= 3); switch (savestyle) { case 0: add_to_string(&config, conf_i18n(N_( "## This is ELinks configuration file. You can edit it manually,\n" "## if you wish so; this file is edited by ELinks when you save\n" "## options through UI, however only option values will be altered\n" "## and all your formatting, own comments etc will be kept as-is.\n"), i18n)); break; case 1: case 3: add_to_string(&config, conf_i18n(N_( "## This is ELinks configuration file. You can edit it manually,\n" "## if you wish so; this file is edited by ELinks when you save\n" "## options through UI, however only option values will be altered\n" "## and missing options will be added at the end of file; if option\n" "## is not written in this file, but in some file included from it,\n" "## it is NOT counted as missing. Note that all your formatting,\n" "## own comments and so on will be kept as-is.\n"), i18n)); break; case 2: add_to_string(&config, conf_i18n(N_( "## This is ELinks configuration file. You can edit it manually,\n" "## if you wish so, but keep in mind that this file is overwritten\n" "## by ELinks when you save options through UI and you are out of\n" "## luck with your formatting and own comments then, so beware.\n"), i18n)); break; } add_to_string(&config, "##\n"); add_to_string(&config, conf_i18n(N_( "## Obviously, if you don't like what ELinks is going to do with\n" "## this file, you can change it by altering the config.saving_style\n" "## option. Come on, aren't we friendly guys after all?\n"), i18n)); } if (savestyle == 0) goto get_me_out; indentation = get_opt_int("config.indentation", NULL); comments = get_opt_int("config.comments", NULL); if (!init_string(&tmpstring)) goto get_me_out; add_cfg_header_to_string(&tmpstring, conf_i18n(N_("Automatically saved options\n"), i18n)); origlen = tmpstring.length; smart_config_string(&tmpstring, 2, i18n, options->value.tree, NULL, 0, smart_config_output_fn); { struct domain_tree *domain; foreach (domain, domain_trees) { smart_config_output_fn_domain = domain->name; smart_config_string(&tmpstring, 2, i18n, domain->tree->value.tree, NULL, 0, smart_config_output_fn); } smart_config_output_fn_domain = NULL; } if (tmpstring.length > origlen) add_string_to_string(&config, &tmpstring); done_string(&tmpstring); if (!init_string(&tmpstring)) goto get_me_out; add_cfg_header_to_string(&tmpstring, conf_i18n(N_("Automatically saved keybindings\n"), i18n)); origlen = tmpstring.length; bind_config_string(&tmpstring); if (tmpstring.length > origlen) add_string_to_string(&config, &tmpstring); done_string(&tmpstring); get_me_out: return config.source; } static int write_config_file(char *prefix, char *name, struct terminal *term) { int ret = -1; struct secure_save_info *ssi; char *config_file = NULL; char *cfg_str = create_config_string(prefix, name); int prefixlen = strlen(prefix); int prefix_has_slash = (prefixlen && dir_sep(prefix[prefixlen - 1])); int name_has_slash = dir_sep(name[0]); const char *slash = name_has_slash || prefix_has_slash ? "" : STRING_DIR_SEP; if (!cfg_str) return -1; if (name_has_slash && prefix_has_slash) name++; config_file = straconcat(prefix, slash, name, (char *) NULL); if (!config_file) goto free_cfg_str; ssi = secure_open(config_file); if (ssi) { secure_fputs(ssi, cfg_str); ret = secure_close(ssi); if (!ret) { struct domain_tree *domain; untouch_options(config_options->value.tree); foreach (domain, domain_trees) untouch_options(domain->tree->value.tree); } } if (term) { write_config_dialog(term, config_file, secsave_errno, ret); } mem_free(config_file); free_cfg_str: mem_free(cfg_str); return ret; } int write_config(struct terminal *term) { if (!elinks_home) { if (term) { write_config_dialog(term, get_cmd_opt_str("config-file"), SS_ERR_DISABLED, 0); } return -1; } return write_config_file(elinks_home, get_cmd_opt_str("config-file"), term); } void set_option_or_save(const char *str) { #define NUMKVPAIRS 16 int i; char *kvpairs[NUMKVPAIRS]; char *option_name; char *option_value; char *set; char *save; struct string tmp; if (!init_string(&tmp)) { return; } add_to_string(&tmp, str); i = qs_parse(tmp.source, kvpairs, 16); option_name = qs_k2v("option", kvpairs, i); option_value = qs_k2v("val", kvpairs, i); set = qs_k2v("set", kvpairs, i); save = qs_k2v("save", kvpairs, i); if (set || save) { struct option *opt = get_opt_rec(config_options, option_name); if (opt) { /* Set option */ switch (opt->type) { case OPT_BOOL: { /* option_types[OPT_BOOL].set expects a long even though it * saves the value to opt->value.number, which is an int. */ long value = !!atol(option_value); option_types[opt->type].set(opt, (char *) (&value)); break; } case OPT_INT: case OPT_LONG: { /* option_types[OPT_INT].set expects a long even though it * saves the value to opt->value.number, which is an int. * option_types[OPT_LONG].set of course wants a long too. */ long value = atol(option_value); option_types[opt->type].set(opt, (char *) (&value)); break; } case OPT_STRING: case OPT_CODEPAGE: case OPT_LANGUAGE: case OPT_COLOR: option_types[opt->type].set(opt, option_value); break; default: break; } option_changed((struct session *)sessions.next, opt); if (save) { write_config(NULL); } } } done_string(&tmp); #undef NUMKVPAIRS }