diff --git a/src/config/conf.c b/src/config/conf.c index b8b82b38..141a2af6 100644 --- a/src/config/conf.c +++ b/src/config/conf.c @@ -88,15 +88,30 @@ skip_white(unsigned char *start, int *line) static enum parse_error parse_set_common(struct option *opt_tree, unsigned char **file, int *line, - struct string *mirror, int is_system_conf) + struct string *mirror, int is_system_conf, int want_domain) { unsigned char *orig_pos = *file; + unsigned char *domain_name; unsigned char *optname; unsigned char bin; *file = skip_white(*file, line); if (!**file) return ERROR_PARSE; + if (want_domain) { + domain_name = *file; + while (isident(**file) || **file == '*' || **file == '.' || **file == '+') + (*file)++; + + bin = **file; + **file = '\0'; + domain_name = stracpy(domain_name); + **file = bin; + if (!domain_name) return ERROR_NOMEM; + + *file = skip_white(*file, line); + } + /* Option name */ optname = *file; while (isident(**file) || **file == '*' || **file == '.' || **file == '+') @@ -124,7 +139,35 @@ parse_set_common(struct option *opt_tree, unsigned char **file, int *line, struct option *opt; unsigned char *val; - opt = mirror ? get_opt_rec_real(opt_tree, optname) : get_opt_rec(opt_tree, optname); + if (want_domain && *domain_name) { + struct option *domain_tree; + + domain_tree = get_domain_tree(domain_name); + if (!domain_tree) { + mem_free(domain_name); + mem_free(optname); + return ERROR_NOMEM; + } + + if (mirror) { + opt = get_opt_rec_real(domain_tree, optname); + } else { + opt = get_opt_rec(opt_tree, optname); + if (opt) { + opt = get_option_shadow(opt, opt_tree, + domain_tree); + if (!opt) { + mem_free(domain_name); + mem_free(optname); + return ERROR_NOMEM; + } + } + } + } else { + opt = mirror ? get_opt_rec_real(opt_tree, optname) : get_opt_rec(opt_tree, optname); + } + if (want_domain) + mem_free(domain_name); mem_free(optname); if (!opt || (opt->flags & OPT_HIDDEN)) @@ -158,11 +201,18 @@ parse_set_common(struct option *opt_tree, unsigned char **file, int *line, return ERROR_NONE; } +static enum parse_error +parse_set_domain(struct option *opt_tree, unsigned char **file, int *line, + struct string *mirror, int is_system_conf) +{ + return parse_set_common(opt_tree, file, line, mirror, is_system_conf, 1); +} + static enum parse_error parse_set(struct option *opt_tree, unsigned char **file, int *line, struct string *mirror, int is_system_conf) { - return parse_set_common(opt_tree, file, line, mirror, is_system_conf); + return parse_set_common(opt_tree, file, line, mirror, is_system_conf, 0); } @@ -344,6 +394,7 @@ struct parse_handler { }; static struct parse_handler parse_handlers[] = { + { "set_domain", parse_set_domain }, { "set", parse_set }, { "unset", parse_unset }, { "bind", parse_bind }, @@ -570,6 +621,8 @@ add_indent_to_string(struct string *string, int depth) add_xchar_to_string(string, ' ', depth * indentation); } +static unsigned char *smart_config_output_fn_domain; + static void smart_config_output_fn(struct string *string, struct option *option, unsigned char *path, int depth, int do_print_comment, @@ -652,7 +705,13 @@ split: if (option->flags & OPT_DELETED) { add_to_string(string, "un"); } - add_to_string(string, "set "); + 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, '.'); @@ -774,6 +833,22 @@ create_config_string(unsigned char *prefix, unsigned char *name) 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); + unmark_options_tree(domain->tree->value.tree); + } + + smart_config_output_fn_domain = NULL; + } + if (tmpstring.length > origlen) add_string_to_string(&config, &tmpstring); done_string(&tmpstring); diff --git a/src/config/options.c b/src/config/options.c index b787e546..30fa0328 100644 --- a/src/config/options.c +++ b/src/config/options.c @@ -23,6 +23,8 @@ #include "main/main.h" /* shrink_memory() */ #include "main/select.h" #include "network/connection.h" +#include "protocol/uri.h" +#include "session/location.h" #include "session/session.h" #include "terminal/color.h" #include "terminal/screen.h" @@ -32,6 +34,7 @@ #include "util/memory.h" #include "util/string.h" #include "viewer/text/draw.h" +#include "viewer/text/vs.h" /* TODO? In the past, covered by shadow and legends, remembered only by the @@ -241,6 +244,8 @@ get_opt_rec_real(struct option *tree, const unsigned char *name) return opt; } +static struct option *get_domain_option(unsigned char *, int, unsigned char *); + /* Fetch pointer to value of certain option. It is guaranteed to never return * NULL. Note that you are supposed to use wrapper get_opt(). */ union option_value * @@ -257,7 +262,14 @@ get_opt_( if (ses && ses->option) opt = get_opt_rec_real(ses->option, name); - /* TODO: Look for a domain-specific option. */ + /* If given a session, the session has a location, and we can find + * an options tree for the location's domain, return the shadow. */ + if (ses && have_location(ses)) { + struct uri *uri = cur_loc(ses)->vs.uri; + + if (uri->host && uri->hostlen) + opt = get_domain_option(uri->host, uri->hostlen, name); + } /* Else, return the real option. */ if (!opt) @@ -698,6 +710,82 @@ get_option_shadow(struct option *option, struct option *tree, return shadow_option; } +INIT_LIST_OF(struct domain_tree, domain_trees); + +/* Look for the option with the given name in all domain shadow-trees that + * match the given domain-name. Return the option from the the shadow tree + * that best matches the given domain name. */ +static struct option * +get_domain_option(unsigned char *domain_name, int domain_len, + unsigned char *name) +{ + struct option *opt, *longest_match_opt = NULL; + struct domain_tree *longest_match = NULL; + struct domain_tree *domain; + + assert(domain_name); + assert(*domain_name); + + foreach (domain, domain_trees) + if ((!longest_match || domain->len > longest_match->len) + && is_in_domain(domain->name, domain_name, domain_len) + && (opt = get_opt_rec_real(domain->tree, name))) { + longest_match = domain; + longest_match_opt = opt; + } + + return longest_match_opt; +} + +/* Return the shadow shadow tree for the given domain name, and + * if the domain does not yet have a shadow tree, create it. */ +struct option * +get_domain_tree(unsigned char *domain_name) +{ + struct domain_tree *domain; + int domain_len; + + assert(domain_name); + assert(*domain_name); + + foreach (domain, domain_trees) + if (!strcasecmp(domain->name, domain_name)) + return domain->tree; + + domain_len = strlen(domain_name); + /* One byte is reserved for domain in struct domain_tree. */ + domain = mem_alloc(sizeof(*domain) + domain_len); + if (!domain) return NULL; + + domain->tree = copy_option(config_options, CO_SHALLOW + | CO_NO_LISTBOX_ITEM); + if (!domain->tree) { + mem_free(domain); + return NULL; + } + + memcpy(domain->name, domain_name, domain_len + 1); + domain->len = domain_len; + + add_to_list(domain_trees, domain); + + return domain->tree; +} + +void +done_domain_trees(void) +{ + struct domain_tree *domain, *next; + + foreachsafe (domain, next, domain_trees) { + delete_option(domain->tree); + domain->tree = NULL; + del_from_list(domain); + mem_free(domain); + } +} + + LIST_OF(struct option) * init_options_tree(void) { @@ -765,6 +853,7 @@ free_options_tree(LIST_OF(struct option) *tree, int recursive) void done_options(void) { + done_domain_trees(); unregister_options(config_options_info, config_options); unregister_options(cmdline_options_info, cmdline_options); config_options->box_item = NULL; diff --git a/src/config/options.h b/src/config/options.h index cc17b678..59ddc85b 100644 --- a/src/config/options.h +++ b/src/config/options.h @@ -178,8 +178,23 @@ enum copy_option_flags { }; extern struct option *copy_option(struct option *, int); + struct option *get_option_shadow(struct option *, struct option *, struct option *); + +struct domain_tree { + LIST_HEAD(struct domain_tree); + + struct option *tree; + + int len; + + unsigned char name[1]; /* Must be at end of struct. */ +}; + +extern LIST_OF(struct domain_tree) domain_trees; +struct option *get_domain_tree(unsigned char *); + extern void delete_option(struct option *); void mark_option_as_deleted(struct option *);