/* ====================================================================
 * Copyright (c) 1995-1998 The Apache Group.  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 the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * 4. The names "Apache Server" and "Apache Group" must not be used to
 *    endorse or promote products derived from this software without
 *    prior written permission. For written permission, please contact
 *    apache@apache.org.
 *
 * 5. Products derived from this software may not be called "Apache"
 *    nor may "Apache" appear in their names without prior written
 *    permission of the Apache Group.
 *
 * 6. Redistributions of any form whatsoever must retain the following
 *    acknowledgment:
 *    "This product includes software developed by the Apache Group
 *    for use in the Apache HTTP server project (http://www.apache.org/)."
 *
 * THIS SOFTWARE IS PROVIDED BY THE APACHE GROUP ``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.
 * ====================================================================
 *
 * This software consists of voluntary contributions made by many
 * individuals on behalf of the Apache Group and was originally based
 * on public domain software written at the National Center for
 * Supercomputing Applications, University of Illinois, Urbana-Champaign.
 * For more information on the Apache Group and the Apache HTTP server
 * project, please see <http://www.apache.org/>.
 *
 */

/*
**  mod_define.c -- Apache module for configuration defines ($xxx)
**
**  Written in December 1998 by 
**      Ralf S. Engelschall <rse@apache.org> and
**      Christian Reiber <chrei@en.muc.de>
*/

#include "ap_config.h"
#include "ap_ctype.h"
#include "httpd.h"
#include "http_config.h"
#include "http_conf_globals.h"
#include "http_core.h"
#include "http_log.h"

#ifndef EAPI
#error "This module requires the Extended API (EAPI) facilities."
#endif

/*
 * Our per-server and per-directory configuration record
 */
typedef struct {
    table *tDefines;
} DefineConfig;

/* 
 * Forward declaration
 */
static int   DollarIndex(pool *p, DefineConfig *sc, DefineConfig *dc, char *cpLine, int *pos, int *len, char **cpVar);
static char *DollarFetch(pool *p, DefineConfig *sc, DefineConfig *dc, char *cpVar);
static char *DollarExpand(pool *p, char *cpToken, int len, char *cpVal);
static char *hook_rewritecmd(cmd_parms *cmd, void *config, const char *line);
module MODULE_VAR_EXPORT define_module;

/*
 * Character classes for scanner function
 */
typedef enum {
    CC_ESCAPE,  CC_DOLLAR, CC_BRACEOPEN, CC_BRACECLOSE, 
    CC_IDCHAR1, CC_IDCHAR, CC_OTHER, CC_EOS   
} CharClass;

/*
 * Scanner states for scanner function
 */
typedef enum {
    SS_NONE, SS_SKIP, SS_DOLLAR, SS_TOKEN_BRACED,
    SS_TOKEN_UNBRACED, SS_ERROR, SS_FOUND
} ScanState;

/*
 * Default meta characters
 */
#define DEFAULT_MC_ESCAPE      "\\"
#define DEFAULT_MC_DOLLAR      "$"
#define DEFAULT_MC_BRACEOPEN   "{"
#define DEFAULT_MC_BRACECLOSE  "}"

/*
 * Global optimization flag
 */
static int bOnceSeenADefine = FALSE;

/*
 * Scan for variables
 */
static int DollarIndex(pool *p, DefineConfig *sc, DefineConfig *dc,
                       char *cpLine, int *pos, int *len, char **cpVar)
{
    int rc;
    char *cp;
    char *cp2;
    CharClass cc;
    char cEscape;
    char cDollar;
    char cBraceOpen;
    char cBraceClose;
    ScanState s;
    char *cpError;

    cEscape = DEFAULT_MC_ESCAPE[0];
    if ((cp = DollarFetch(p, sc, dc, "mod_define::escape")) != NULL)
        cEscape = cp[0];
    cDollar = DEFAULT_MC_DOLLAR[0];
    if ((cp = DollarFetch(p, sc, dc, "mod_define::dollar")) != NULL)
        cDollar = cp[0];
    cBraceOpen = DEFAULT_MC_BRACEOPEN[0];
    if ((cp = DollarFetch(p, sc, dc, "mod_define::braceopen")) != NULL)
        cBraceOpen = cp[0];
    cBraceClose = DEFAULT_MC_BRACECLOSE[0];
    if ((cp = DollarFetch(p, sc, dc, "mod_define::braceclose")) != NULL)
        cBraceClose = cp[0];

    rc = 0;
    *len = 0;
    cc = CC_OTHER;
    s = SS_NONE;
    for (cp = cpLine+(*pos); cc != CC_EOS; cp++) {
        if (*cp == cEscape)
            cc = CC_ESCAPE;
        else if (*cp == cDollar)
            cc = CC_DOLLAR;
        else if (*cp == cBraceOpen)
            cc = CC_BRACEOPEN;
        else if (*cp == cBraceClose)
            cc = CC_BRACECLOSE;
        else if (ap_isalpha(*cp))
            cc = CC_IDCHAR1;
        else if (ap_isdigit(*cp) || *cp == '_' || *cp == ':')
            cc = CC_IDCHAR;
        else if (*cp == '\0')
            cc = CC_EOS;
        else
            cc = CC_OTHER;
        switch (s) {
            case SS_NONE:
                switch (cc) {
                    case CC_ESCAPE: 
                        s = SS_SKIP;
                        break;
                    case CC_DOLLAR: 
                        s = SS_DOLLAR; 
                        break;
                    default:
                        break;
                }
                break;
            case SS_SKIP:
                s = SS_NONE;
                continue;
                break;
            case SS_DOLLAR:
                switch (cc) {
                    case CC_BRACEOPEN: 
                        s = SS_TOKEN_BRACED;   
                        *pos = cp-cpLine-1; 
                        (*len) = 2; 
                        *cpVar = cp+1; 
                        break;
                    case CC_IDCHAR1: 
                        s = SS_TOKEN_UNBRACED; 
                        *pos = cp-cpLine-1; 
                        (*len) = 2; 
                        *cpVar = cp; 
                        break;
                    case CC_ESCAPE:
                        s = SS_SKIP; 
                        break;
                    default: 
                        s = SS_NONE; 
                        break;
                }
                break;
            case SS_TOKEN_BRACED:
                switch (cc) {
                    case CC_IDCHAR1:
                    case CC_IDCHAR: 
                        (*len)++; 
                        break;
                    case CC_BRACECLOSE: 
                        (*len)++; 
                        cp2 = ap_palloc(p, cp-*cpVar+1);
                        ap_cpystrn(cp2, *cpVar, cp-*cpVar+1); 
                        *cpVar = cp2;
                        s = SS_FOUND; 
                        break;
                    default:
                        cpError = ap_psprintf(p, "Illegal character '%c' in identifier", *cp);
                        s = SS_ERROR;
                        break;
                }
                break;
            case SS_TOKEN_UNBRACED:
                switch (cc) {
                    case CC_IDCHAR1:
                    case CC_IDCHAR: 
                        (*len)++; 
                        break;
                    default:
                        cp2 = ap_palloc(p, cp-*cpVar+1);
                        ap_cpystrn(cp2, *cpVar, cp-*cpVar+1); 
                        *cpVar = cp2;
                        s = SS_FOUND; 
                        break;
                }
                break;
            case SS_FOUND:
            case SS_ERROR:
                break;
        }
        if (s == SS_ERROR) {
            fprintf(stderr, "Error\n");
            break;
        }
        else if (s == SS_FOUND) {
            rc = 1;
            break;
        }
    }
    return rc;
}

/*
 * Determine the value of a variable
 */
static char *DollarFetch(pool *p, DefineConfig *sc, DefineConfig *dc, char *cpVar)
{
    char *cpVal;

    if (dc != NULL && ((cpVal = (char *)ap_table_get(dc->tDefines, (char *)cpVar)) != NULL))
        return cpVal;
    if (sc != NULL && ((cpVal = (char *)ap_table_get(sc->tDefines, (char *)cpVar)) != NULL)) 
        return cpVal;
    if ((cpVal = getenv(cpVar)) != NULL)
        return cpVal;
    return NULL;
}

/*
 * Expand a variable
 */
static char *DollarExpand(pool *p, char *cpToken, int tok_len, char *cpVal)
{
    char *cp;
    int val_len, rest_len;

    val_len  = strlen(cpVal);
    rest_len = strlen(cpToken+tok_len);

    if (val_len < tok_len) 
        memcpy(cpToken+val_len, cpToken+tok_len, rest_len+1);
    else if (val_len > tok_len)
        for (cp = cpToken+strlen(cpToken); cp > cpToken+tok_len-1; cp--)  
            *(cp+(val_len-tok_len)) = *cp;

    memcpy(cpToken, cpVal, val_len);

    return NULL;
}

/*
 * The EAPI hook which is called after Apache has read a
 * configuration line and before it's processed.
 */
static char *hook_rewritecmd(cmd_parms *cmd, void *config, const char *line)
{
    pool *p;
    char *cpBuf;
    char *cpLine;
    int pos;
    int len;
    char *cpError;
    char *cpVar;
    char *cpVal;
    DefineConfig *dc;
    DefineConfig *sc;
    server_rec *s;

    if (!bOnceSeenADefine)
        return NULL;

    p  = cmd->pool;
    s  = cmd->server;
    sc = ap_get_module_config(s->module_config, &define_module);
    dc = ap_get_module_config(config, &define_module);

    if (sc == NULL && dc == NULL)
        return NULL;

    /*
     * Search for:
     *  ....\$[a-zA-Z][:_a-zA-Z0-9]*....
     *  ....\${[a-zA-Z][:_a-zA-Z0-9]*}....
     */
    cpBuf = NULL;
    cpLine = (char *)line;
    pos = 0;
    while (DollarIndex(p, sc, dc, cpLine, &pos, &len, &cpVar)) {
#ifdef DEBUG
        {
        char prefix[1024];
        char marker[1024];
        int i;
        for (i = 0; i < pos; i++)
            prefix[i] = ' ';
        prefix[i] = '\0';
        for (i = 0; i < len; i++)
            marker[i] = '^';
        marker[i] = '\0';
        fprintf(stderr, 
                "Found variable `%s' (pos: %d, len: %d)\n"
                "  %s\n"
                "  %s%s\n",
                cpVar, pos, len, cpLine, prefix, marker);
        }
#endif
        if (cpBuf == NULL) {
            cpBuf = ap_palloc(p, MAX_STRING_LEN);
            ap_cpystrn(cpBuf, line, MAX_STRING_LEN);
            cpLine = cpBuf;
        }
        if ((cpVal = DollarFetch(p, sc, dc, cpVar)) == NULL) {
            ap_log_error(APLOG_MARK, APLOG_ERR, s, 
                         "mod_define: Variable '%s' not defined: file %s, line %d", 
                         cpVar, cmd->config_file->name,
                         cmd->config_file->line_number);
            cpBuf = NULL;
            break;
        }
        if ((cpError = DollarExpand(p, cpLine+pos, len, cpVal)) != NULL) {
            ap_log_error(APLOG_MARK, APLOG_ERR, s, 
                         "mod_define: %s: file %s, line %d", 
                         cpError, cmd->config_file->name,
                         cmd->config_file->line_number);
            cpBuf = NULL;
            break;
        }
    }
    return cpBuf;
}

/*
 * Implementation of the `Define' configuration directive
 */

static void *config_create(pool *p, server_rec *s)
{
    DefineConfig *sc;

    if ((sc = (DefineConfig *)ap_pcalloc(p, sizeof(DefineConfig))) == NULL) {
        fprintf(stderr, "mod_define: Cannot allocate memory\n");
        exit(1);
    }
    sc->tDefines = ap_make_table(p, 4);
    return (void *)sc;
}

static void *config_create_dir(pool *p, char *path)
{
    DefineConfig *sc;

    if ((sc = (DefineConfig *)ap_pcalloc(p, sizeof(DefineConfig))) == NULL) {
        fprintf(stderr, "mod_define: Cannot allocate memory\n");
        exit(1);
    }
    sc->tDefines = ap_make_table(p, 4);
    ap_table_set(sc->tDefines, "mod_define::escape", DEFAULT_MC_ESCAPE);
    ap_table_set(sc->tDefines, "mod_define::dollar", DEFAULT_MC_DOLLAR);
    ap_table_set(sc->tDefines, "mod_define::open",   DEFAULT_MC_BRACEOPEN);
    ap_table_set(sc->tDefines, "mod_define::close",  DEFAULT_MC_BRACECLOSE);
    return (void *)sc;
}

static void *config_merge(pool *p, void *basev, void *overridesv)
{
    DefineConfig *a;
    DefineConfig *base;
    DefineConfig *overrides;

    a         = (DefineConfig *)ap_pcalloc(p, sizeof(DefineConfig));
    base      = (DefineConfig *)basev;
    overrides = (DefineConfig *)overridesv;

    a->tDefines = ap_overlay_tables(p, overrides->tDefines, base->tDefines);

    return (void *)overrides;
}

static const char *cmd_define(cmd_parms *cmd, DefineConfig *dc, 
                              char *cpVar, char *cpVal)
{
    DefineConfig *sc;

    sc = (DefineConfig *)ap_get_module_config(cmd->server->module_config, 
                                              &define_module);
    ap_table_set(sc->tDefines, cpVar, cpVal);
    bOnceSeenADefine = TRUE;
    return NULL;
}

/* 
 * Module's API dispatch lists
 */

static const command_rec command_table[] = {
    { "Define", cmd_define, NULL, OR_ALL, TAKE2,
      "Define a configuration variable" },
    { NULL }
};

module MODULE_VAR_EXPORT define_module = {
    STANDARD_MODULE_STUFF, 
    NULL,                  /* module initializer                  */
    config_create_dir,     /* create per-dir    config structures */
    config_merge,          /* merge  per-dir    config structures */
    config_create,         /* create per-server config structures */
    config_merge,          /* merge  per-server config structures */
    command_table,         /* table of config file commands       */
    NULL,                  /* [#8] MIME-typed-dispatched handlers */
    NULL,                  /* [#1] URI to filename translation    */
    NULL,                  /* [#4] validate user id from request  */
    NULL,                  /* [#5] check if the user is ok _here_ */
    NULL,                  /* [#2] check access by host address   */
    NULL,                  /* [#6] determine MIME type            */
    NULL,                  /* [#7] pre-run fixups                 */
    NULL,                  /* [#9] log a transaction              */
    NULL,                  /* [#3] header parser                  */
    NULL,                  /* child_init                          */
    NULL,                  /* child_exit                          */
    NULL,                  /* [#0] post read-request              */
    NULL,                  /* EAPI: add_module                    */
    NULL,                  /* EAPI: del_module                    */
    hook_rewritecmd,       /* EAPI: rewrite_command               */
    NULL                   /* EAPI: new_connection                */
};

/*EOF*/
