diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-07 02:04:06 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-05-07 02:04:06 +0000 |
commit | 5dff2d61cc1c27747ee398e04d8e02843aabb1f8 (patch) | |
tree | a67c336b406c8227bac912beb74a1ad3cdc55100 /modules/core/mod_macro.c | |
parent | Initial commit. (diff) | |
download | apache2-5dff2d61cc1c27747ee398e04d8e02843aabb1f8.tar.xz apache2-5dff2d61cc1c27747ee398e04d8e02843aabb1f8.zip |
Adding upstream version 2.4.38.upstream/2.4.38upstream
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'modules/core/mod_macro.c')
-rw-r--r-- | modules/core/mod_macro.c | 950 |
1 files changed, 950 insertions, 0 deletions
diff --git a/modules/core/mod_macro.c b/modules/core/mod_macro.c new file mode 100644 index 0000000..04af43b --- /dev/null +++ b/modules/core/mod_macro.c @@ -0,0 +1,950 @@ +/* Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "httpd.h" +#include "http_config.h" +#include "http_log.h" + +#include "apr.h" +#include "apr_strings.h" +#include "apr_hash.h" + +/************************************************ COMPILE TIME DEBUG CONTROL */ +/* + debug: + #define MOD_MACRO_DEBUG 1 + + gdb: + run -f ./test/conf/test??.conf +*/ +/* #define MOD_MACRO_DEBUG 1 */ +#undef MOD_MACRO_DEBUG + +#if defined(debug) +#undef debug +#endif /* debug */ + +#if defined(MOD_MACRO_DEBUG) +#define debug(stmt) stmt +#else +#define debug(stmt) +#endif /* MOD_MACRO_DEBUG */ + +/******************************************************** MODULE DECLARATION */ + +module AP_MODULE_DECLARE_DATA macro_module; + +/********************************************************** MACRO MANAGEMENT */ + +/* + this is a macro: name, arguments, contents, location. +*/ +typedef struct +{ + char *name; /* lower case name of the macro */ + apr_array_header_t *arguments; /* of char*, macro parameter names */ + apr_array_header_t *contents; /* of char*, macro body */ + char *location; /* of macro definition, for error messages */ +} ap_macro_t; + +/* configuration tokens. + */ +#define BEGIN_MACRO "<Macro" +#define END_MACRO "</Macro>" +#define USE_MACRO "Use" +#define UNDEF_MACRO "UndefMacro" + +/* + Macros are kept globally... + They are not per-server or per-directory entities. + + note: they are in a temp_pool, and there is a lazy initialization. + ap_macros is reset to NULL in pre_config hook to not depend + on static vs dynamic configuration. + + hash type: (char *) name -> (ap_macro_t *) macro +*/ +static apr_hash_t *ap_macros = NULL; + +/*************************************************************** PARSE UTILS */ + +#define empty_string_p(p) (!(p) || *(p) == '\0') +#define trim(line) while (*(line) == ' ' || *(line) == '\t') (line)++ + +/* + return configuration-parsed arguments from line as an array. + the line is expected not to contain any '\n'? +*/ +static apr_array_header_t *get_arguments(apr_pool_t * pool, const char *line) +{ + apr_array_header_t *args = apr_array_make(pool, 1, sizeof(char *)); + + trim(line); + while (*line) { + char *arg = ap_getword_conf(pool, &line); + char **new = apr_array_push(args); + *new = arg; + trim(line); + } + + return args; +} + +/* + warn if anything non blank appears, but ignore comments... +*/ +static void warn_if_non_blank(const char * what, + char * ptr, + ap_configfile_t * cfg) +{ + char * p; + for (p=ptr; *p; p++) { + if (*p == '#') + break; + if (*p != ' ' && *p != '\t') { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02989) + "%s on line %d of %s: %s", + what, cfg->line_number, cfg->name, ptr); + break; + } + } +} + +/* + get read lines as an array till end_token. + counts nesting for begin_token/end_token. + it assumes a line-per-line configuration (thru getline). + this function could be exported. + begin_token may be NULL. +*/ +static char *get_lines_till_end_token(apr_pool_t * pool, + ap_configfile_t * config_file, + const char *end_token, + const char *begin_token, + const char *where, + apr_array_header_t ** plines) +{ + apr_array_header_t *lines = apr_array_make(pool, 1, sizeof(char *)); + char line[MAX_STRING_LEN]; /* sorry, but this is expected by getline:-( */ + int macro_nesting = 1, any_nesting = 1; + int line_number_start = config_file->line_number; + + while (!ap_cfg_getline(line, MAX_STRING_LEN, config_file)) { + char *ptr = line; + char *first, **new; + /* skip comments */ + if (*line == '#') + continue; + first = ap_getword_conf_nc(pool, &ptr); + if (first) { + /* detect nesting... */ + if (!strncmp(first, "</", 2)) { + any_nesting--; + if (any_nesting < 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, + 0, NULL, APLOGNO(02793) + "bad (negative) nesting on line %d of %s", + config_file->line_number - line_number_start, + where); + } + } + else if (!strncmp(first, "<", 1)) { + any_nesting++; + } + + if (!strcasecmp(first, end_token)) { + /* check for proper closing */ + char * endp = (char *) ap_strrchr_c(line, '>'); + + /* this cannot happen if end_token contains '>' */ + if (endp == NULL) { + return "end directive missing closing '>'"; + } + + warn_if_non_blank( + APLOGNO(02794) "non blank chars found after directive closing", + endp+1, config_file); + + macro_nesting--; + if (!macro_nesting) { + if (any_nesting) { + ap_log_error(APLOG_MARK, + APLOG_WARNING, 0, NULL, APLOGNO(02795) + "bad cumulated nesting (%+d) in %s", + any_nesting, where); + } + *plines = lines; + return NULL; + } + } + else if (begin_token && !strcasecmp(first, begin_token)) { + macro_nesting++; + } + } + new = apr_array_push(lines); + *new = apr_psprintf(pool, "%s" APR_EOL_STR, line); /* put EOL back? */ + } + + return apr_psprintf(pool, "expected token not found: %s", end_token); +} + +/* the @* arguments are double-quote escaped when substituted */ +#define ESCAPE_ARG '@' + +/* other $* and %* arguments are simply replaced without escaping */ +#define ARG_PREFIX "$%@" + +/* + characters allowed in an argument? + not used yet, because that would trigger some backward compatibility. +*/ +#define ARG_CONTENT \ + "abcdefghijklmnopqrstuvwxyz" \ + "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \ + "0123456789_" ARG_PREFIX + +/* + returns whether it looks like an argument, i.e. prefixed by ARG_PREFIX. +*/ +static int looks_like_an_argument(const char *word) +{ + return ap_strchr(ARG_PREFIX, *word) != 0; +} + +/* + generates an error on macro with two arguments of the same name. + generates an error if a macro argument name is empty. + generates a warning if arguments name prefixes conflict. + generates a warning if the first char of an argument is not in ARG_PREFIX +*/ +static const char *check_macro_arguments(apr_pool_t * pool, + const ap_macro_t * macro) +{ + char **tab = (char **) macro->arguments->elts; + int nelts = macro->arguments->nelts; + int i; + + for (i = 0; i < nelts; i++) { + size_t ltabi = strlen(tab[i]); + int j; + + if (ltabi == 0) { + return apr_psprintf(pool, + "macro '%s' (%s): empty argument #%d name", + macro->name, macro->location, i + 1); + } + else if (!looks_like_an_argument(tab[i])) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02796) + "macro '%s' (%s) " + "argument name '%s' (#%d) without expected prefix, " + "better prefix argument names with one of '%s'.", + macro->name, macro->location, + tab[i], i + 1, ARG_PREFIX); + } + + for (j = i + 1; j < nelts; j++) { + size_t ltabj = strlen(tab[j]); + + /* must not use the same argument name twice */ + if (!strcmp(tab[i], tab[j])) { + return apr_psprintf(pool, + "argument name conflict in macro '%s' (%s): " + "argument '%s': #%d and #%d, " + "change argument names!", + macro->name, macro->location, + tab[i], i + 1, j + 1); + } + + /* warn about common prefix, but only if non empty names */ + if (ltabi && ltabj && + !strncmp(tab[i], tab[j], ltabi < ltabj ? ltabi : ltabj)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, + 0, NULL, APLOGNO(02797) + "macro '%s' (%s): " + "argument name prefix conflict (%s #%d and %s #%d), " + "be careful about your macro definition!", + macro->name, macro->location, + tab[i], i + 1, tab[j], j + 1); + } + } + } + + return NULL; +} + +/* + warn about empty strings in array. could be legitimate. +*/ +static void check_macro_use_arguments(const char *where, + const apr_array_header_t * array) +{ + char **tab = (char **) array->elts; + int i; + for (i = 0; i < array->nelts; i++) { + if (empty_string_p(tab[i])) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02798) + "%s: empty argument #%d", where, i + 1); + } + } +} + +/******************************************************** SUBSTITUTION UTILS */ + +/* could be switched to '\'' */ +#define DELIM '"' +#define ESCAPE '\\' + +/* + returns the number of needed escapes for the string +*/ +static int number_of_escapes(const char delim, const char *str) +{ + int nesc = 0; + const char *s = str; + while (*s) { + if (*s == ESCAPE || *s == delim) + nesc++; + s++; + } + debug(fprintf(stderr, "escapes: %d ---%s---\n", nesc, str)); + return nesc; +} + +/* + replace name by replacement at the beginning of buf of bufsize. + returns an error message or NULL. + C is not really a nice language for processing strings. +*/ +static char *substitute(char *buf, + const int bufsize, + const char *name, + const char *replacement, const int do_esc) +{ + int lbuf = strlen(buf), + lname = strlen(name), + lrepl = strlen(replacement), + lsubs = lrepl + + (do_esc ? (2 + number_of_escapes(DELIM, replacement)) : 0), + shift = lsubs - lname, size = lbuf + shift, i, j; + + /* buf must starts with name */ + ap_assert(!strncmp(buf, name, lname)); + + /* hmmm??? */ + if (!strcmp(name, replacement)) + return NULL; + + debug(fprintf(stderr, + "substitute(%s,%s,%s,%d,sh=%d,lbuf=%d,lrepl=%d,lsubs=%d)\n", + buf, name, replacement, do_esc, shift, lbuf, lrepl, lsubs)); + + if (size >= bufsize) { + /* could/should I reallocate? */ + return "cannot substitute, buffer size too small"; + } + + /* cannot use strcpy as strings may overlap */ + if (shift != 0) { + memmove(buf + lname + shift, buf + lname, lbuf - lname + 1); + } + + /* insert the replacement with escapes */ + j = 0; + if (do_esc) + buf[j++] = DELIM; + for (i = 0; i < lrepl; i++, j++) { + if (do_esc && (replacement[i] == DELIM || replacement[i] == ESCAPE)) + buf[j++] = ESCAPE; + buf[j] = replacement[i]; + } + if (do_esc) + buf[j++] = DELIM; + + return NULL; +} + +/* + find first occurrence of args in buf. + in case of conflict, the LONGEST argument is kept. (could be the FIRST?). + returns the pointer and the whichone found, or NULL. +*/ +static char *next_substitution(const char *buf, + const apr_array_header_t * args, int *whichone) +{ + char *chosen = NULL, **tab = (char **) args->elts; + size_t lchosen = 0; + int i; + + for (i = 0; i < args->nelts; i++) { + char *found = ap_strstr((char *) buf, tab[i]); + size_t lfound = strlen(tab[i]); + if (found && (!chosen || found < chosen || + (found == chosen && lchosen < lfound))) { + chosen = found; + lchosen = lfound; + *whichone = i; + } + } + + return chosen; +} + +/* + substitute macro arguments by replacements in buf of bufsize. + returns an error message or NULL. + if used is defined, returns the used macro arguments. +*/ +static const char *substitute_macro_args( + char *buf, + int bufsize, + const ap_macro_t * macro, + const apr_array_header_t * replacements, + apr_array_header_t * used) +{ + char *ptr = buf, + **atab = (char **) macro->arguments->elts, + **rtab = (char **) replacements->elts; + int whichone = -1; + + if (used) { + ap_assert(used->nalloc >= replacements->nelts); + } + debug(fprintf(stderr, "1# %s", buf)); + + while ((ptr = next_substitution(ptr, macro->arguments, &whichone))) { + const char *errmsg = substitute(ptr, buf - ptr + bufsize, + atab[whichone], rtab[whichone], + atab[whichone][0] == ESCAPE_ARG); + if (errmsg) { + return errmsg; + } + ptr += strlen(rtab[whichone]); + if (used) { + used->elts[whichone] = 1; + } + } + debug(fprintf(stderr, "2# %s", buf)); + + return NULL; +} + +/* + perform substitutions in a macro contents and + return the result as a newly allocated array, if result is defined. + may also return an error message. + passes used down to substitute_macro_args. +*/ +static const char *process_content(apr_pool_t * pool, + const ap_macro_t * macro, + const apr_array_header_t * replacements, + apr_array_header_t * used, + apr_array_header_t ** result) +{ + apr_array_header_t *contents = macro->contents; + char line[MAX_STRING_LEN]; + int i; + + if (result) { + *result = apr_array_make(pool, contents->nelts, sizeof(char *)); + } + + /* for each line of the macro body */ + for (i = 0; i < contents->nelts; i++) { + const char *errmsg; + /* copy the line and substitute macro parameters */ + strncpy(line, ((char **) contents->elts)[i], MAX_STRING_LEN - 1); + errmsg = substitute_macro_args(line, MAX_STRING_LEN, + macro, replacements, used); + if (errmsg) { + return apr_psprintf(pool, + "while processing line %d of macro '%s' (%s) %s", + i + 1, macro->name, macro->location, errmsg); + } + /* append substituted line to result array */ + if (result) { + char **new = apr_array_push(*result); + *new = apr_pstrdup(pool, line); + } + } + + return NULL; +} + +/* + warn if some macro arguments are not used. +*/ +static const char *check_macro_contents(apr_pool_t * pool, + const ap_macro_t * macro) +{ + int nelts = macro->arguments->nelts; + char **names = (char **) macro->arguments->elts; + apr_array_header_t *used; + int i; + const char *errmsg; + + if (macro->contents->nelts == 0) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02799) + "macro '%s' (%s): empty contents!", + macro->name, macro->location); + return NULL; /* no need to further warnings... */ + } + + used = apr_array_make(pool, nelts, sizeof(char)); + + for (i = 0; i < nelts; i++) { + used->elts[i] = 0; + } + + errmsg = process_content(pool, macro, macro->arguments, used, NULL); + + if (errmsg) { + return errmsg; + } + + for (i = 0; i < nelts; i++) { + if (!used->elts[i]) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02800) + "macro '%s' (%s): argument '%s' (#%d) never used", + macro->name, macro->location, names[i], i + 1); + } + } + + return NULL; +} + + +/************************************************** MACRO PSEUDO CONFIG FILE */ + +/* + The expanded content of the macro is to be parsed as a ap_configfile_t. + This is used to have some kind of old fashionned C object oriented inherited + data structure for configs. + + The following struct stores the contents. + + This structure holds pointers (next, upper) to the current "file" which was + being processed and is interrupted by the macro expansion. At the end + of processing the macro, the initial data structure will be put back + in place (see function next_one) and the reading will go on from there. + + If macros are used within macros, there may be a cascade of such temporary + arrays used to insert the expanded macro contents before resuming the real + file processing. + + There is some hopus-pocus to deal with line_number when transiting from + one config to the other. +*/ +typedef struct +{ + int index; /* current element */ + int char_index; /* current char in element */ + int length; /* cached length of the current line */ + apr_array_header_t *contents; /* array of char * */ + ap_configfile_t *next; /* next config once this one is processed */ + ap_configfile_t **upper; /* hack: where to update it if needed */ +} array_contents_t; + +/* + Get next config if any. + this may be called several times if there are continuations. +*/ +static int next_one(array_contents_t * ml) +{ + if (ml->next) { + ap_assert(ml->upper); + *(ml->upper) = ml->next; + return 1; + } + return 0; +} + +/* + returns next char if possible + this may involve switching to enclosing config. +*/ +static apr_status_t array_getch(char *ch, void *param) +{ + array_contents_t *ml = (array_contents_t *) param; + char **tab = (char **) ml->contents->elts; + + while (ml->char_index >= ml->length) { + if (ml->index >= ml->contents->nelts) { + /* maybe update */ + if (ml->next && ml->next->getch && next_one(ml)) { + apr_status_t rc = ml->next->getch(ch, ml->next->param); + if (*ch==LF) + ml->next->line_number++; + return rc; + } + return APR_EOF; + } + ml->index++; + ml->char_index = 0; + ml->length = ml->index >= ml->contents->nelts ? + 0 : strlen(tab[ml->index]); + } + + *ch = tab[ml->index][ml->char_index++]; + return APR_SUCCESS; +} + +/* + returns a buf a la fgets. + no more than a line at a time, otherwise the parsing is too much ahead... + NULL at EOF. +*/ +static apr_status_t array_getstr(void *buf, size_t bufsize, void *param) +{ + array_contents_t *ml = (array_contents_t *) param; + char *buffer = (char *) buf; + char next = '\0'; + size_t i = 0; + apr_status_t rc = APR_SUCCESS; + + /* read chars from stream, stop on newline */ + while (i < bufsize - 1 && next != LF && + ((rc = array_getch(&next, param)) == APR_SUCCESS)) { + buffer[i++] = next; + } + + if (rc == APR_EOF) { + /* maybe update to next, possibly a recursion */ + if (next_one(ml)) { + ap_assert(ml->next->getstr); + /* keep next line count in sync! the caller will update + the current line_number, we need to forward to the next */ + ml->next->line_number++; + return ml->next->getstr(buf, bufsize, ml->next->param); + } + /* else that is really all we can do */ + return APR_EOF; + } + + buffer[i] = '\0'; + + return APR_SUCCESS; +} + +/* + close the array stream? +*/ +static apr_status_t array_close(void *param) +{ + array_contents_t *ml = (array_contents_t *) param; + /* move index at end of stream... */ + ml->index = ml->contents->nelts; + ml->char_index = ml->length; + return APR_SUCCESS; +} + +/* + create an array config stream insertion "object". + could be exported. +*/ +static ap_configfile_t *make_array_config(apr_pool_t * pool, + apr_array_header_t * contents, + const char *where, + ap_configfile_t * cfg, + ap_configfile_t ** upper) +{ + array_contents_t *ls = + (array_contents_t *) apr_palloc(pool, sizeof(array_contents_t)); + ap_assert(ls!=NULL); + + ls->index = 0; + ls->char_index = 0; + ls->contents = contents; + ls->length = ls->contents->nelts < 1 ? + 0 : strlen(((char **) ls->contents->elts)[0]); + ls->next = cfg; + ls->upper = upper; + + return ap_pcfg_open_custom(pool, where, (void *) ls, + array_getch, array_getstr, array_close); +} + + +/********************************************************** KEYWORD HANDLING */ + +/* + handles: <Macro macroname arg1 arg2 ...> any trash there is ignored... +*/ +static const char *macro_section(cmd_parms * cmd, + void *dummy, const char *arg) +{ + apr_pool_t *pool; + char *endp, *name, *where; + const char *errmsg; + ap_macro_t *macro; + + debug(fprintf(stderr, "macro_section: arg='%s'\n", arg)); + + /* lazy initialization */ + if (ap_macros == NULL) { + pool = cmd->pool; + ap_macros = apr_hash_make(pool); + ap_assert(ap_macros != NULL); + apr_pool_cleanup_register(pool, &ap_macros, + ap_pool_cleanup_set_null, + apr_pool_cleanup_null); + } + else { + pool = apr_hash_pool_get(ap_macros); + } + + endp = (char *) ap_strrchr_c(arg, '>'); + + if (endp == NULL) { + return BEGIN_MACRO "> directive missing closing '>'"; + } + + if (endp == arg) { + return BEGIN_MACRO " macro definition: empty name"; + } + + warn_if_non_blank(APLOGNO(02801) "non blank chars found after " + BEGIN_MACRO " closing '>'", + endp+1, cmd->config_file); + + /* coldly drop '>[^>]*$' out */ + *endp = '\0'; + + /* get lowercase macro name */ + name = ap_getword_conf(pool, &arg); + if (empty_string_p(name)) { + return BEGIN_MACRO " macro definition: name not found"; + } + + ap_str_tolower(name); + macro = apr_hash_get(ap_macros, name, APR_HASH_KEY_STRING); + + if (macro != NULL) { + /* already defined: warn about the redefinition */ + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02802) + "macro '%s' multiply defined: " + "%s, redefined on line %d of \"%s\"", + macro->name, macro->location, + cmd->config_file->line_number, cmd->config_file->name); + } + else { + /* allocate a new macro */ + macro = (ap_macro_t *) apr_palloc(pool, sizeof(ap_macro_t)); + macro->name = name; + } + + debug(fprintf(stderr, "macro_section: name=%s\n", name)); + + /* get macro arguments */ + macro->location = apr_psprintf(pool, + "defined on line %d of \"%s\"", + cmd->config_file->line_number, + cmd->config_file->name); + debug(fprintf(stderr, "macro_section: location=%s\n", macro->location)); + + where = + apr_psprintf(pool, "macro '%s' (%s)", macro->name, macro->location); + + if (looks_like_an_argument(name)) { + ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(02803) + "%s better prefix a macro name with any of '%s'", + where, ARG_PREFIX); + } + + /* get macro parameters */ + macro->arguments = get_arguments(pool, arg); + + errmsg = check_macro_arguments(cmd->temp_pool, macro); + + if (errmsg) { + return errmsg; + } + + errmsg = get_lines_till_end_token(pool, cmd->config_file, + END_MACRO, BEGIN_MACRO, + where, ¯o->contents); + + if (errmsg) { + return apr_psprintf(cmd->temp_pool, + "%s" APR_EOL_STR "\tcontents error: %s", + where, errmsg); + } + + errmsg = check_macro_contents(cmd->temp_pool, macro); + + if (errmsg) { + return apr_psprintf(cmd->temp_pool, + "%s" APR_EOL_STR "\tcontents checking error: %s", + where, errmsg); + } + + /* store the new macro */ + apr_hash_set(ap_macros, name, APR_HASH_KEY_STRING, macro); + + return NULL; +} + +/* + handles: Use name value1 value2 ... +*/ +static const char *use_macro(cmd_parms * cmd, void *dummy, const char *arg) +{ + char *name, *recursion, *where; + const char *errmsg; + ap_macro_t *macro; + apr_array_header_t *replacements; + apr_array_header_t *contents; + + debug(fprintf(stderr, "use_macro -%s-\n", arg)); + + /* must be initialized, or no macros has been defined */ + if (ap_macros == NULL) { + return "no macro defined before " USE_MACRO; + } + + /* get lowercase macro name */ + name = ap_getword_conf(cmd->temp_pool, &arg); + ap_str_tolower(name); + + if (empty_string_p(name)) { + return "no macro name specified with " USE_MACRO; + } + + /* get macro definition */ + macro = apr_hash_get(ap_macros, name, APR_HASH_KEY_STRING); + + if (!macro) { + return apr_psprintf(cmd->temp_pool, "macro '%s' undefined", name); + } + + /* recursion is detected here by looking at the config file name, + * which may already contains "macro 'foo'". Ok, it looks like a hack, + * but otherwise it is uneasy to keep this data available somewhere... + * the name has just the needed visibility and liveness. + */ + recursion = + apr_pstrcat(cmd->temp_pool, "macro '", macro->name, "'", NULL); + + if (ap_strstr((char *) cmd->config_file->name, recursion)) { + return apr_psprintf(cmd->temp_pool, + "recursive use of macro '%s' is invalid", + macro->name); + } + + /* get macro arguments */ + replacements = get_arguments(cmd->temp_pool, arg); + + if (macro->arguments->nelts != replacements->nelts) { + return apr_psprintf(cmd->temp_pool, + "macro '%s' (%s) used " + "with %d arguments instead of %d", + macro->name, macro->location, + replacements->nelts, macro->arguments->nelts); + } + + where = apr_psprintf(cmd->temp_pool, + "macro '%s' (%s) used on line %d of \"%s\"", + macro->name, macro->location, + cmd->config_file->line_number, + cmd->config_file->name); + + check_macro_use_arguments(where, replacements); + + errmsg = process_content(cmd->temp_pool, macro, replacements, + NULL, &contents); + + if (errmsg) { + return apr_psprintf(cmd->temp_pool, + "%s error while substituting: %s", + where, errmsg); + } + + /* the current "config file" is replaced by a string array... + at the end of processing the array, the initial config file + will be returned there (see next_one) so as to go on. */ + cmd->config_file = make_array_config(cmd->temp_pool, contents, where, + cmd->config_file, &cmd->config_file); + + return NULL; +} + +static const char *undef_macro(cmd_parms * cmd, void *dummy, const char *arg) +{ + char *name; + ap_macro_t *macro; + + /* must be initialized, or no macros has been defined */ + if (ap_macros == NULL) { + return "no macro defined before " UNDEF_MACRO; + } + + if (empty_string_p(arg)) { + return "no macro name specified with " UNDEF_MACRO; + } + + /* check that the macro is defined */ + name = apr_pstrdup(cmd->temp_pool, arg); + ap_str_tolower(name); + macro = apr_hash_get(ap_macros, name, APR_HASH_KEY_STRING); + if (macro == NULL) { + /* could be a warning? */ + return apr_psprintf(cmd->temp_pool, + "cannot remove undefined macro '%s'", name); + } + + /* free macro: cannot do that */ + /* remove macro from hash table */ + apr_hash_set(ap_macros, name, APR_HASH_KEY_STRING, NULL); + + return NULL; +} + +/************************************************************* EXPORT MODULE */ + +/* + macro module commands. + configuration file macro stuff + they are processed immediately when found, hence the EXEC_ON_READ. +*/ +static const command_rec macro_cmds[] = { + AP_INIT_RAW_ARGS(BEGIN_MACRO, macro_section, NULL, EXEC_ON_READ | OR_ALL, + "Beginning of a macro definition section."), + AP_INIT_RAW_ARGS(USE_MACRO, use_macro, NULL, EXEC_ON_READ | OR_ALL, + "Use of a macro."), + AP_INIT_TAKE1(UNDEF_MACRO, undef_macro, NULL, EXEC_ON_READ | OR_ALL, + "Remove a macro definition."), + + {NULL} +}; + +/* + Module hooks are request-oriented thus it does not suit configuration + file utils a lot. I haven't found any clean hook to apply something + before then after configuration file processing. Also what about + .htaccess files? + + Thus I think that server/util.c or server/config.c + would be a better place for this stuff. +*/ + +AP_DECLARE_MODULE(macro) = { + STANDARD20_MODULE_STUFF, /* common stuff */ + NULL, /* create per-directory config */ + NULL, /* merge per-directory config structures */ + NULL, /* create per-server config structure */ + NULL, /* merge per-server config structures */ + macro_cmds, /* configuration commands */ + NULL /* register hooks */ +}; |