| /* 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 */ | |
| /* Global warning modifiers */ | |
| int ignore_empty = FALSE; /* no warning about empty argument */ | |
| int ignore_bad_nesting = FALSE; /* no warning about bad nesting */ | |
| /* | |
| 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 */ | |
| int ignore_empty; /* no warning about empty argument */ | |
| int ignore_bad_nesting; /* no warning about bad nesting */ | |
| } ap_macro_t; | |
| /* configuration tokens. | |
| */ | |
| #define BEGIN_MACRO "<Macro" | |
| #define END_MACRO "</Macro>" | |
| #define USE_MACRO "Use" | |
| #define UNDEF_MACRO "UndefMacro" | |
| #define IGNORE_EMPTY_MACRO_FLAG "/IgnoreEmptyArgs" | |
| #define IGNORE_BAD_NESTING_MACRO_FLAG "/IgnoreBadNesting" | |
| #define IGNORE_EMPTY_MACRO_DIRECTIVE "MacroIgnoreEmptyArgs" | |
| #define IGNORE_BAD_NESTING_MACRO_DIRECTIVE "MacroIgnoreBadNesting" | |
| /* | |
| 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, | |
| int ignore_nesting) | |
| { | |
| 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 (!ignore_nesting && (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 (!ignore_nesting && 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); | |
| } | |
| if ((tab[i][0] == '$') && (tab[i][1] == '{')) { | |
| ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(03484) | |
| "macro '%s' (%s) " | |
| "argument name '%s' (#%d) clashes with 'Define' syntax '${...}', better use '$(...)'.", | |
| macro->name, macro->location, tab[i], i + 1); | |
| } | |
| 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 */ | |
| if (strlen(((char**)contents->elts)[i]) >= sizeof(line)) { | |
| return apr_psprintf(pool, | |
| "while processing line %d of macro '%s' (%s) %s", | |
| i + 1, macro->name, macro->location, "macro too long"); | |
| } | |
| apr_cpystrn(line, ((char **) contents->elts)[i], sizeof(line)); | |
| errmsg = substitute_macro_args(line, sizeof(line), | |
| 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/remove macro modifiers from parameters */ | |
| #define CHECK_MACRO_FLAG(arg_, flag_str, flag_val) if (!strncasecmp(arg_, flag_str, strlen(flag_str))) { flag_val = TRUE; arg_ += strlen(flag_str); if (!*arg) break;} | |
| while (*arg == '/') { | |
| CHECK_MACRO_FLAG(arg, IGNORE_EMPTY_MACRO_FLAG, macro->ignore_empty); | |
| CHECK_MACRO_FLAG(arg, IGNORE_BAD_NESTING_MACRO_FLAG, macro->ignore_bad_nesting); | |
| if (*arg != ' ') { | |
| char *c = ap_strchr((char *)arg, ' '); | |
| if (c) *c = '\0'; | |
| ap_log_error(APLOG_MARK, APLOG_WARNING, 0, NULL, APLOGNO(03483) | |
| "%s: unknown flag '%s'", where, arg); | |
| if (c) { | |
| *c = ' '; | |
| arg = c; | |
| } | |
| } | |
| arg++; | |
| } | |
| /* 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, ignore_bad_nesting || macro->ignore_bad_nesting); | |
| 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); | |
| if (!ignore_empty && !macro->ignore_empty) | |
| 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; | |
| } | |
| static const char *macro_ignore_empty(cmd_parms * cmd, void *dummy) | |
| { | |
| ignore_empty = TRUE; | |
| return NULL; | |
| } | |
| static const char *macro_ignore_bad_nesting(cmd_parms * cmd, void *dummy) | |
| { | |
| ignore_bad_nesting = TRUE; | |
| 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."), | |
| AP_INIT_NO_ARGS(IGNORE_EMPTY_MACRO_DIRECTIVE, macro_ignore_empty, NULL, EXEC_ON_READ | OR_ALL, | |
| "Globally ignore warnings about empty arguments."), | |
| AP_INIT_NO_ARGS(IGNORE_BAD_NESTING_MACRO_DIRECTIVE, macro_ignore_bad_nesting, NULL, EXEC_ON_READ | OR_ALL, | |
| "Globally ignore warnings about bad nesting."), | |
| {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 */ | |
| }; |