/*
 * The following license applies to "mod_macro" version 1.1.1.
 * It is a third-party module by Fabien Coelho <coelho@cri.ensmp.fr>
 * 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 <coelho@cri.ensmp.fr>
 *    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 <coelho@cri.ensmp.fr>.
 *
 * 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 <coelho@cri.ensmp.fr>
 *    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 <coelho@cri.ensmp.fr>.
  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 "<Macro"
#define END_MACRO "</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, "</", 2)) {
                any_nesting--;
#if !defined(MOD_MACRO_NO_WARNINGS)
                if (any_nesting<0) {
                    ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING, NULL,
                                 "bad (negative) nesting on line %d of %s\n",
                                 line_number, where);
                }
#endif
            }
            else if (!strncmp(first, "<", 1)) {
                any_nesting++;
            }

            if (!strcasecmp(first, end_token)) { /* okay! */
                macro_nesting--;
                if (!macro_nesting) {
#if !defined(MOD_MACRO_NO_WARNINGS)
                    if (any_nesting) {
                        ap_log_error(APLOG_MARK, APLOG_NOERRNO|APLOG_WARNING,
                                     NULL,
                                     "bad cumulated nesting (%+d) in %s\n",
                                     any_nesting, where);
                    }
#endif
                    *plines = lines;
                    return NULL;
                }
            }
            else if (begin_token && !strcasecmp(first, begin_token)) {
                macro_nesting++;
            }
        }
        /* free first. */
        new  = ap_push_array(lines);
        *new = ap_psprintf(p, "%s\n", line); /* put '\n' back */
    }

    return ap_psprintf(p, "expected token not found: %s", end_token);
}

/* returns whether it looks like an argument, i.e. prefixed by ARGUMENT_PREFIX.
 */
#define ARGUMENT_PREFIX "$%&@#"

/* characters allowed in an argument? not used yet.
 */
#define ARGUMENT_CONTENT \
    "abcdefghijklmnopqrstuvwxyz" \
    "ABCDEFGHIJKLMNOPQRSTUVWXYZ" \
    "0123456789_" ARGUMENT_PREFIX

static int looks_like_an_argument(const char * word)
{
    return (int)strchr(ARGUMENT_PREFIX, *word);
}

/* 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 ARGUMENT_PREFIX
*/
static const char * check_macro_arguments(ap_pool * p, const macro_t * macro)
{
    char ** tab = (char **)macro->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: <Macro macroname arg1 arg2 ...>
 */
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, &macro->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 </Macro> 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 */
};
