/* * The following license applies to "mod_macro" version 1.1.1. * It is a third-party module by Fabien Coelho * for the Apache Http Server (http://www.apache.org/). * * ==================================================================== * Copyright (c) 1998-1999 Fabien Coelho. All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in * the documentation and/or other materials provided with the * distribution. * * 3. All advertising materials mentioning features or use of this * software must display the following acknowledgment: * "This product includes software developed by * Fabien Coelho * for use in the mod_macro project * (http://www.cri.ensmp.fr/~coelho/mod_macro/)." * * 4. The name "mod_macro" must not be used to * endorse or promote products derived from this software without * prior written permission. For written permission, please contact * Fabien Coelho . * * 5. Products derived from this software may not be called "mod_macro" * nor may "mod_macro" appear in their names without prior written * permission of Fabien Coelho. * * 6. Redistributions of any form whatsoever must retain the following * acknowledgment: * "This product includes software developed by * Fabien Coelho * for use in the mod_macro project * (http://www.cri.ensmp.fr/~coelho/mod_macro/)." * * 7. Any modification must be properly copyrighted by its author. * * THIS SOFTWARE IS PROVIDED BY FABIEN COELHO ``AS IS'' AND ANY * EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE APACHE GROUP OR * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED * OF THE POSSIBILITY OF SUCH DAMAGE. * ==================================================================== */ /* $Id: mod_macro.src.c 1.60 1999/01/15 10:18:43 coelho Exp $ mod_macro version 1.1.1. This modules allows the definition and use of macros within apache runtime configuration files. Patch suggestions may be sent to the author. Fabien Coelho . URL: http://www.cri.ensmp.fr/~coelho/ */ #include "httpd.h" #include "http_config.h" #include "http_log.h" /************************************************ COMPILE TIME DEBUG CONTROL */ /* debug: */ /* #define MOD_MACRO_DEBUG 1 */ /* no warnings: */ /* #define MOD_MACRO_NO_WARNINGS 1 */ /* #define MOD_MACRO_NO_CHAR_PREFIX_WARNINGS 1 */ /* no advertisement in version component */ #define MOD_MACRO_NO_ADVERTISEMENT 1 #if defined(debug) #undef debug #endif #if defined(MOD_MACRO_DEBUG) #define debug(stmt) stmt #else #define debug(stmt) #endif /************************************************************* ADVERTISEMENT */ module macro_module; #define MACRO_MODULE_NAME "mod_macro" #define MACRO_MODULE_VERSION "1.1.1" /* initializer for the macro module. * just advertise the module in apache server signature. */ static void macro_startup(server_rec * dummy1, pool * dummy2) { #if !defined(MOD_MACRO_NO_ADVERTISEMENT) /* advertise the macro module;-) */ ap_add_version_component(MACRO_MODULE_NAME "/" MACRO_MODULE_VERSION); #endif } /********************************************************** MACRO MANAGEMENT */ /* a macro: name, arguments, contents, location. */ typedef struct { char * name; /* case-insensitive name of the macro. */ array_header * arguments; /* of char* */ array_header * contents; /* of char* */ char * location; /* of the macro definition. */ } macro_t; /* configuration tokens. */ #define BEGIN_MACRO "" #define USE_MACRO "Use" #define empty_string_p(p) (!(p) || *(p) == '\0') /* macros are kept globally... they are not per-server or per-directory entities. I would need a hook BEFORE and AFTER configuration processing to initialize and close them properly. I would have such a hook if in the main/... I guess. The "initializer" does not seem to be called before. HACK: ----- I put them in the temp_pool. restarts are detected because the temp_pool has changed... note that there is always a restart implicitely to check for the syntax. */ static array_header * all_macros = NULL; /* returns the macro structure for name, or NULL if not found. */ static macro_t * get_macro_by_name(const array_header * macros, const char * name) { int i; macro_t ** tab; ap_assert(macros); tab = (macro_t **)macros->elts; for (i = 0; i < macros->nelts; i++) { if (!strcasecmp(name, tab[i]->name)) { return tab[i]; } } return NULL; } /* configuration state initialization. the state is simply an array_header which holds the macros. */ static void macro_init(pool * p) { static ap_pool * last_time_temp_pool_hack = NULL; debug(fprintf(stderr, "macro_init\n")); /* is it a restart? what about concurrent threads? */ if (last_time_temp_pool_hack != p) { last_time_temp_pool_hack = p; all_macros = ap_make_array(p, 1, sizeof(macro_t *)); debug(fprintf(stderr, "macro_init done for %p\n", p)); } } /*************************************************************** PARSE UTILS */ #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 array_header * get_arguments(ap_pool * p, const char * line) { array_header * args = ap_make_array(p, 1, sizeof(char *)); char * arg, ** new; trim(line); while (*line) { arg = ap_getword_conf(p, &line); new = ap_push_array(args); *new = arg; trim(line); } return args; } /* 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(ap_pool * p, configfile_t * config_file, const char * end_token, const char * begin_token, const char * where, array_header ** plines) { array_header * lines = ap_make_array(p, 1, sizeof(char *)); char ** new, * first, * ptr; char line[MAX_STRING_LEN]; /* sorry, but that is expected by getline. */ int macro_nesting = 1, any_nesting = 1, line_number = 0; while (!ap_cfg_getline(line, MAX_STRING_LEN, config_file)) { ptr = line; first = ap_getword_conf_nc(p, &ptr); line_number++; if (first) { /* nesting... */ if (!strncmp(first, "arguments->elts; int nelts = macro->arguments->nelts, i, j; size_t ltabi, ltabj; for (i = 0; i < nelts; i++) { ltabi = strlen(tab[i]); if (ltabi == 0) { return ap_psprintf(p, "macro '%s' (%s)\n\tempty argument #%d name\n", macro->name, macro->location, i + 1); } #if !defined(MOD_MACRO_NO_CHAR_PREFIX_WARNINGS) || \ !defined(MOD_MACRO_NO_WARNINGS) if (!looks_like_an_argument(tab[i])) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "macro '%s' (%s)\n" "\targument name '%s' (#%d) without expected prefix.\n" "\tit is good practice to prefix argument names with one of '%s'.\n", macro->name, macro->location, tab[i], i + 1, ARGUMENT_PREFIX); } #endif for (j = i + 1; j < nelts; j++) { ltabj = strlen(tab[j]); if (!strcmp(tab[i], tab[j])) { return ap_psprintf(p, "argument name conflict in macro '%s' (%s)\n" "\targument '%s': #%d and #%d\n" "\tchange argument names!", macro->name, macro->location, tab[i], i + 1, j + 1); } #if !defined(MOD_MACRO_NO_WARNINGS) if (!strncmp(tab[i], tab[j], ltabi < ltabj ? ltabi : ltabj)) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "macro '%s' (%s)\n" "\targument name prefix conflict (%s #%d and %s #%d)\n" "\tbe careful about your macro definition!\n", macro->name, macro->location, tab[i], i + 1, tab[j], j + 1); } #endif } } return NULL; } /* warn about empty strings in array. */ static void check_macro_use_arguments(const char * where, const array_header * array) { int i; char ** tab = (char **)array->elts; #if !defined(MOD_MACRO_NO_WARNINGS) for (i = 0; i < array->nelts; i++) { if (empty_string_p(tab[i])) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "%s\n\tempty argument #%d\n", where, i + 1); } } #endif } /******************************************************** SUBSTITUTION UTILS */ /* replace name by replacement at the beginning of buf of bufsize. returns an error message or NULL. */ static char * substitute(char * buf, int bufsize, const char * name, const char * replacement) { char tmp[MAX_STRING_LEN]; /* sorry. should I malloc and free? */ int lbuf = strlen(buf), lname = strlen(name), lrepl = strlen(replacement); ap_assert(!strncmp(buf, name, lname)); if (lbuf + lrepl - lname >= bufsize) { return "cannot substitute, buffer size too small"; } if (lbuf + lrepl - lname >= MAX_STRING_LEN) { return "cannot substitute, tmp size too small"; } /* simple. */ strcpy(tmp, replacement); strcpy(tmp + lrepl, buf + lname); strcpy(buf, tmp); return NULL; } /* find first occurence 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 array_header * args, int * whichone) { int i; char * chosen = NULL, * found, ** tab = (char **)args->elts; size_t lchosen = 0, lfound; for (i = 0; i < args->nelts; i++) { found = strstr(buf, tab[i]); 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 char * substitute_macro_args(char * buf, int bufsize, const macro_t * macro, const array_header * replacements, array_header * used) { char * ptr = buf, * errmsg, ** atab = (char **)macro->arguments->elts, ** rtab = (char **)replacements->elts; int whichone; if (used) { ap_assert(used->nalloc >= replacements->nelts); } while ((ptr = next_substitution(ptr, macro->arguments, &whichone))) { errmsg = substitute(ptr, buf - ptr + bufsize, atab[whichone], rtab[whichone]); if (errmsg) { return errmsg; } ptr += strlen(rtab[whichone]); if (used) { used->elts[whichone] = 1; } } 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(ap_pool * p, const macro_t * macro, const array_header * replacements, array_header * used, array_header ** result) { array_header * contents = macro->contents; char ** new, * errmsg, line[MAX_STRING_LEN]; /* sorry again. */ int i; if (result) { *result = ap_make_array(p, 1, sizeof(char *)); } for (i = 0; i < contents->nelts; i++) { strncpy(line, ((char **)contents->elts)[i], MAX_STRING_LEN - 1); errmsg = substitute_macro_args(line, MAX_STRING_LEN, macro, replacements, used); if (errmsg) { return ap_psprintf(p, "while processing line %d of macro '%s'" " (%s)\n\t%s", i + 1, macro->name, macro->location, errmsg); } if (result) { new = ap_push_array(*result); *new = ap_pstrdup(p, line); } } return NULL; } /* warn if some macro arguments are not used. */ static const char * check_macro_contents(ap_pool * p, const macro_t * macro) { #if !defined(MOD_MACRO_NO_WARNINGS) int nelts = macro->arguments->nelts, i; array_header * used; const char * errmsg; char ** names = (char **)macro->arguments->elts; if (macro->contents->nelts == 0) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "macro '%s' (%s)\n\tempty contents!\n", macro->name, macro->location); return NULL; /* no need to further warnings... */ } used = ap_make_array(p, nelts, sizeof(char)); for (i = 0; i < nelts; i++) { used->elts[i] = 0; } errmsg = process_content(p, macro, macro->arguments, used, NULL); if (errmsg) { /* free used. */ return errmsg; } for (i = 0; i < nelts; i++) { if (!used->elts[i]) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "macro '%s' (%s)\n\targument '%s' (#%d) never used\n", macro->name, macro->location, names[i], i + 1); } } /* free used. */ #endif return NULL; } /********************************************************* MACRO CONFIG FILE */ /* the expanded content of the macro is to be parsed as a configfile_t. the following struct stores the content. IMPORTANT NOTE: --------------- in http_config.c there is such a stuff made static, which does not implement getch(). maybe this should be moved to util.c ??? */ typedef struct { int index; /* current element. */ int char_index; /* current char in element. */ int length; /* cached length of the current line. */ array_header * contents; /* array of char * */ configfile_t * next; /* next config once this one is processed. */ configfile_t ** upper; /* hack: where to update it if needed. */ } array_contents_t; /* next config if any. */ 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 or -1. */ static int array_getch(void * param) { array_contents_t * ml = (array_contents_t *)param; char ** tab = (char **)ml->contents->elts; while (ml->char_index >= ml->length) { /* next element */ if (ml->index >= ml->contents->nelts) { /* maybe update. */ if (ml->next && ml->next->getch && next_one(ml)) { return ml->next->getch(ml->next->param); } return -1; } ml->index++; ml->char_index = 0; ml->length = ml->index >= ml->contents->nelts ? 0 : strlen(tab[ml->index]); } return tab[ml->index][ml->char_index++]; } /* 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 void * array_getstr(void * buf, size_t bufsize, void * param) { array_contents_t * ml = (array_contents_t *)param; char * buffer = (char *) buf; size_t i = 0; int next = 0; while (i < bufsize - 1 && next != '\n' && ((next = array_getch(param)) != -1)) { buffer[i++] = (char)next; } if (next == -1 && i == 0) { /* EOF */ /* maybe update to next. */ if (next_one(ml)) { ap_assert(ml->next->getstr); return ml->next->getstr(buf, bufsize, ml->next->param); } return NULL; } buffer[i] = '\0'; return buf; } /* close the array stream? */ static int array_close(void * param) { array_contents_t * ml = (array_contents_t *)param; ml->index = ml->contents->nelts; ml->char_index = ml->length; return 0; } /* this one could be exported. */ static configfile_t * make_array_config(ap_pool * p, array_header * contents, const char * where, configfile_t * cfg, configfile_t ** upper) { array_contents_t * ls = (array_contents_t *)ap_palloc(p, sizeof(array_contents_t)); 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(p, where, (void *)ls, array_getch, array_getstr, array_close); } /********************************************************** KEYWORD HANDLING */ /* handles: */ static const char * macro_section(cmd_parms * cmd, void * dummy, const char * arg) { const char * errmsg, * where; char ** new, * name, * endp = strrchr(arg, '>'); macro_t * macro, * old; debug(fprintf(stderr, "macro_section -%s-\n", arg)); macro_init(cmd->temp_pool); /* lazy... */ if (endp) { *endp = '\0'; } /* get name. */ name = ap_getword_conf(cmd->temp_pool, &arg); if (empty_string_p(name)) { return "macro definition: name not specified"; } old = get_macro_by_name(all_macros, name); if (old) { #if !defined(MOD_MACRO_NO_WARNINGS) /* already define: warn about the redefinition. */ ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "macro '%s' multiply defined.\n" "\t%s, redefined on line %d of %s", old->name, old->location, cmd->config_file->line_number, cmd->config_file->name); #endif macro = old; } else { macro = (macro_t *)ap_palloc(cmd->temp_pool, sizeof(macro_t)); } macro->name = name; /* get arguments. */ macro->location = ap_psprintf(cmd->temp_pool, "defined on line %d of %s", cmd->config_file->line_number, cmd->config_file->name); where = ap_psprintf(cmd->temp_pool, "macro '%s' (%s)", macro->name, macro->location); #if !defined(MOD_MACRO_NO_CHAR_PREFIX_WARNINGS) || \ !defined(MOD_MACRO_NO_WARNINGS) if (looks_like_an_argument(name)) { ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL, "%s\n\tit is good practice not " "to prefix a macro name with any of '%s'\n", where, ARGUMENT_PREFIX); } #endif macro->arguments = get_arguments(cmd->temp_pool, arg); errmsg = check_macro_arguments(cmd->temp_pool, macro); if (errmsg) { return errmsg; } /* get contents */ errmsg = get_lines_till_end_token(cmd->temp_pool, cmd->config_file, END_MACRO, BEGIN_MACRO, where, ¯o->contents); if (errmsg) { return ap_psprintf(cmd->temp_pool, "%s\n\tcontents error: %s", where, errmsg); } errmsg = check_macro_contents(cmd->temp_pool, macro); if (errmsg) { return ap_psprintf(cmd->temp_pool, "%s\n\tcontents checking error: %s", where, errmsg); } /* add the new macro. */ new = ap_push_array(all_macros); *new = (char *)macro; return NULL; } /* handles: Use name value1 value2 ... */ static const char * use_macro(cmd_parms * cmd, void * dummy, const char * arg) { char * name, * where, * recursion; const char * errmsg; array_header * contents, * replacements; macro_t * macro; debug(fprintf(stderr, "use_macro -%s-\n", arg)); macro_init(cmd->temp_pool); /* lazy... */ name = ap_getword_conf(cmd->temp_pool, &arg); if (empty_string_p(name)) { return "no macro name specified in " USE_MACRO; } macro = get_macro_by_name(all_macros, name); if (!macro) { return ap_psprintf(cmd->temp_pool, "macro '%s' is not defined", name); } /* recursion is detected 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 = ap_pstrcat(cmd->temp_pool, "macro '", macro->name, "'", NULL); where = ap_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 (strstr(cmd->config_file->name, recursion)) { return ap_psprintf(cmd->temp_pool, "%s\n\trecursive use of macro '%s' is invalid.", where, macro->name); } replacements = get_arguments(cmd->temp_pool, arg); if (macro->arguments->nelts != replacements->nelts) { return ap_psprintf(cmd->temp_pool, "use of macro '%s' %s\n" "\twith %d argument%s instead of %d", macro->name, macro->location, replacements->nelts, replacements->nelts > 1 ? "s" : "", /* grammar;-) */ macro->arguments->nelts); } check_macro_use_arguments(where, replacements); errmsg = process_content(cmd->temp_pool, macro, replacements, NULL, &contents); if (errmsg) { return ap_psprintf(cmd->temp_pool, "%s\n\terror while substituting:\n%s", where, errmsg); } /* fix??? why is it wrong? should I -- the new one? */ cmd->config_file->line_number++; cmd->config_file = make_array_config (cmd->temp_pool, contents, where, cmd->config_file, &cmd->config_file); return NULL; } /* handles: a lonely or other unexpected keyword. * such a function already exists? where? could be in util.c or config.c? */ static const char * unexpected_keyword(cmd_parms * parms, void * dummy1, const char * dummy2) { return ap_psprintf(parms->temp_pool, "unexpected %s encountered", (char *)parms->info); } /************************************ ERROR AND WARNING DURING CONFIGURATION */ /* maybe ConfigurationError and ConfigurationWarning could be used? */ #define ERROR_KEYWORD "Error" #define WARNING_KEYWORD "Warning" /* configuration generated erros or warnings. */ static const char * say_it(cmd_parms * parms, void * dummy, const char * arg) { int level = (int)parms->info; trim(arg); ap_log_error(APLOG_MARK, APLOG_NOERRNO|level, NULL, "on line %d of %s:\n\t%s\n", parms->config_file->line_number, parms->config_file->name, arg); return level & APLOG_ERR ? "configuration file processing aborted by " ERROR_KEYWORD "." : NULL; } /************************************************************* EXPORT MODULE */ /* macro module commands. */ static const command_rec macro_cmds[] = { /* configuration file macro stuff */ { BEGIN_MACRO, macro_section, NULL, OR_ALL, RAW_ARGS, "Beginning of a macro definition section." }, { END_MACRO, unexpected_keyword, (void *)END_MACRO, OR_ALL, NO_ARGS, "End of a macro definition section." }, { USE_MACRO, use_macro, NULL, OR_ALL, RAW_ARGS, "Use of a macro." }, /* configuration errors and warnings. */ { ERROR_KEYWORD, say_it, (void *)APLOG_ERR, OR_ALL, RAW_ARGS, "Error in a configuration file." }, { WARNING_KEYWORD, say_it, (void *)APLOG_WARNING, OR_ALL, RAW_ARGS, "Warning in a configuration file." }, { 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 main/http_co*.c would be a better place for this stuff. */ module MODULE_VAR_EXPORT macro_module = { STANDARD_MODULE_STUFF, macro_startup, /* initializer */ NULL, /* create per-dir config */ NULL, /* merge per-dir config */ NULL, /* server config */ NULL, /* merge server config */ macro_cmds, /* command table */ NULL, /* handlers */ NULL, /* filename translation */ NULL, /* check_user_id */ NULL, /* check auth */ NULL, /* check access */ NULL, /* type_checker */ NULL, /* fixups */ NULL, /* logger */ NULL, /* header parser */ NULL, /* child_init */ NULL, /* child_exit */ NULL /* post read-request */ };