/*
 * ModSecurity for Apache 2.x, http://www.modsecurity.org/
 * Copyright (c) 2004-2006 Thinking Stone (http://www.thinkingstone.com)
 *
 * $Id: re_variables.c,v 1.7 2007/01/23 16:08:15 ivanr Exp $
 *
 * You should have received a copy of the licence along with this
 * program (stored in the file "LICENSE"). If the file is missing,
 * or if you have any other questions related to the licence, please
 * write to Thinking Stone at contact@thinkingstone.com.
 *
 */
#include "http_core.h"

#include "modsecurity.h"
#include "apache2.h"
#include "re.h"
#include "msc_util.h"

#ifdef WITH_LIBXML2
#include "libxml/xpathInternals.h"
#endif

/**
 * Generates a variable from a string and a length.
 */
static int var_simple_generate_ex(msre_var *var, apr_table_t *vartab, apr_pool_t *mptmp,
    const char *value, int value_len)
{
    msre_var *rvar = NULL;

    if (value == NULL) return 0;

    rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
    rvar->value = value;
    rvar->value_len = value_len;
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/**
 * Generates a variable from a NULL-terminated string.
 */
static int var_simple_generate(msre_var *var, apr_table_t *vartab, apr_pool_t *mptmp,
    const char *value)
{
    if (value == NULL) return 0;
    return var_simple_generate_ex(var, vartab, mptmp, value, strlen(value));
}

/**
 * Validate that a target parameter is valid. We only need to take
 * care of the case when the parameter is a regular expression.
 */
static char *var_generic_list_validate(msre_ruleset *ruleset, msre_var *var) {
    /* It's OK if there's no parameter. */
    if (var->param == NULL) return NULL;

    /* Is it a regular expression? */
    if ((strlen(var->param) > 2)&&(var->param[0] == '/')
        &&(var->param[strlen(var->param) - 1] == '/'))
    { /* Regex. */
        msc_regex_t *regex = NULL;
        const char *errptr = NULL;
        const char *pattern = NULL;
        int erroffset;

        pattern = apr_pstrmemdup(ruleset->mp, var->param + 1, strlen(var->param + 1) - 1);
        if (pattern == NULL) return FATAL_ERROR;

        regex = msc_pregcomp(ruleset->mp, pattern, PCRE_DOTALL | PCRE_CASELESS | PCRE_DOLLAR_ENDONLY, &errptr, &erroffset);
        if (regex == NULL) {
            return apr_psprintf(ruleset->mp, "Error compiling pattern (pos %i): %s",
                erroffset, errptr);
        }

        /* Store the compiled regex for later. */
        var->param_data = regex;
    }

    /* Simple string */
    return NULL;
}

/* Custom parameter validation functions */

/* ARGS */

static int var_args_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;

    /* Loop through the arguments. */
    arr = apr_table_elts(msr->arguments);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        msc_arg *arg = (msc_arg *)te[i].val;
        int match = 0;

        /* Figure out if we want to include this argument. */
        if (var->param == NULL) match = 1; /* Unconditional inclusion. */
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                /* Run the regex against the argument name. */
                if (!(msc_regexec((msc_regex_t *)var->param_data, arg->name,
                    arg->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(arg->name, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = arg->value;
            rvar->value_len = arg->value_len;
            rvar->name = apr_psprintf(mptmp, "ARGS:%s", log_escape_nq(mptmp, arg->name));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }
    
    return count;
}

/* ARGS_COMBINED_SIZE */

static int var_args_combined_size_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    unsigned int combined_size = 0;
    int i;
    msre_var *rvar = NULL;

    arr = apr_table_elts(msr->arguments);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        msc_arg *arg = (msc_arg *)te[i].val;
        combined_size += arg->name_len;
        combined_size += arg->value_len;
    }
    
    rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
    rvar->value = apr_psprintf(mptmp, "%u", combined_size);    
    rvar->value_len = strlen(rvar->value);
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* ARGS_NAMES */

static int var_args_names_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;

    arr = apr_table_elts(msr->arguments);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        msc_arg *arg = (msc_arg *)te[i].val;
        int match = 0;

        /* Figure out if we want to include this variable. */
        if (var->param == NULL) match = 1; /* Unconditional inclusion. */
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, arg->name,
                    arg->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(arg->name, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = arg->name;
            rvar->value_len = arg->name_len;
            rvar->name = apr_psprintf(mptmp, "ARGS_NAMES:%s", log_escape_nq(mptmp, arg->name));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }
    
    return count;
}

/* RULE */

static int var_rule_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_actionset *actionset = NULL;

    if (rule == NULL) return 0;
    actionset = rule->actionset;
    if (rule->chain_starter != NULL) actionset = rule->chain_starter->actionset;

    if ((strcasecmp(var->param, "id") == 0)&&(actionset->id != NULL)) {
        return var_simple_generate(var, vartab, mptmp, actionset->id);
    } else
    if ((strcasecmp(var->param, "rev") == 0)&&(actionset->rev != NULL)) {
        return var_simple_generate(var, vartab, mptmp, actionset->rev);
    } else
    if ((strcasecmp(var->param, "severity") == 0)&&(actionset->severity != -1)) {
        char *value = apr_psprintf(mptmp, "%i", actionset->severity);
        return var_simple_generate(var, vartab, mptmp, value);
    } else
    if ((strcasecmp(var->param, "msg") == 0)&&(actionset->msg != NULL)) {
        return var_simple_generate(var, vartab, mptmp, actionset->msg);
    }

    return 0;
}

/* ENV */

static char *var_env_validate(msre_ruleset *ruleset, msre_var *var) {
    if ((strlen(var->param) > 2)&&(var->param[0] == '/')
        &&(var->param[strlen(var->param) - 1] == '/'))
    {
        return apr_psprintf(ruleset->mp, "Regular expressions not supported in ENV.");
    }
    return NULL;
}

static int var_env_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = get_env_var(msr->r, (char *)var->param);
    if (value != NULL) {
        return var_simple_generate(var, vartab, mptmp, value);
    }
    return 0;
}

/* REQUEST_URI_RAW */

static int var_request_uri_raw_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    return var_simple_generate(var, vartab, mptmp, msr->r->unparsed_uri);
}

/* REQUEST_URI */

static int var_request_uri_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = NULL;

    if (msr->r->parsed_uri.query == NULL) value = msr->r->parsed_uri.path;
    else value = apr_pstrcat(mptmp,  msr->r->parsed_uri.path, "?", msr->r->parsed_uri.query, NULL);

    return var_simple_generate(var, vartab, mptmp, value);
}

/* REQBODY_PROCESSOR */

static int var_reqbody_processor_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

    if (msr->msc_reqbody_processor == NULL) {
        rvar->value = apr_pstrdup(mptmp, "");
        rvar->value_len = 0;
    } else {
        rvar->value = apr_pstrdup(mptmp, msr->msc_reqbody_processor);
        rvar->value_len = strlen(rvar->value);
    }

    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* REQBODY_ERROR */

static int var_reqbody_processor_error_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

    rvar->value = apr_psprintf(mptmp, "%i", msr->msc_reqbody_error);
    rvar->value_len = strlen(rvar->value);
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* REQBODY_ERROR_MSG */

static int var_reqbody_processor_error_msg_generate(modsec_rec *msr, msre_var *var,
    msre_rule *rule, apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

    if (msr->msc_reqbody_error_msg == NULL) {
        rvar->value = apr_pstrdup(mptmp, "");
        rvar->value_len = 0;
    } else {
        rvar->value = apr_psprintf(mptmp, "%s", msr->msc_reqbody_error_msg);
        rvar->value_len = strlen(rvar->value);
    }

    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

#ifdef WITH_LIBXML2

/* XML */

static char *var_xml_validate(msre_ruleset *ruleset, msre_var *var) {
    /* It's OK if there's no parameter. */
    if (var->param == NULL) return NULL;

    /* ENH validate XPath expression in advance. */

    return NULL;
}

static int var_xml_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *tarr;
    const apr_table_entry_t *telts;
    xmlXPathContextPtr xpathCtx;
    xmlXPathObjectPtr xpathObj;
    xmlNodeSetPtr nodes;
    const xmlChar* xpathExpr = NULL;
    int i, count;

    /* Is there an XML document tree at all? */
    if ((msr->xml == NULL)||(msr->xml->doc == NULL)) {
        /* Sorry, we've got nothing to give! */
        return 0;
    }

    if (var->param == NULL) {
        /* Invocation without an XPath expression makes sense
         * with functions that manipulate the document tree.
         */
        msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

        rvar->value = apr_pstrdup(mptmp, "[XML document tree]");
        rvar->value_len = strlen(rvar->value);
        apr_table_addn(vartab, rvar->name, (void *)rvar);

        return 1;
    }

    /* Process the XPath expression. */

    count = 0;
    xpathExpr = (const xmlChar*)var->param;

    xpathCtx = xmlXPathNewContext(msr->xml->doc);
    if (xpathCtx == NULL) {
        msr_log(msr, 1, "XML: Unable to create new XPath context.");
        return -1;
    }

    /* Look through the actionset of the associated rule
     * for the namespace information. Register them if any are found.
     */
    tarr = apr_table_elts(rule->actionset->actions);
    telts = (const apr_table_entry_t*)tarr->elts;
    for (i = 0; i < tarr->nelts; i++) {
        msre_action *action = (msre_action *)telts[i].val;

        if (strcasecmp(action->metadata->name, "xmlns") == 0) {
            char *prefix, *href;

            if (parse_name_eq_value(mptmp, action->param, &prefix, &href) < 0) return -1;
            if ((prefix == NULL)||(href == NULL)) return -1;

            if(xmlXPathRegisterNs(xpathCtx, (const xmlChar*)prefix, (const xmlChar*)href) != 0) {
                msr_log(msr, 1, "Failed to register XML namespace href \"%s\" prefix \"%s\".",
                    log_escape(mptmp, prefix), log_escape(mptmp, href));
                return -1;
            }

            msr_log(msr, 4, "Registered XML namespace href \"%s\" prefix \"%s\".",
                log_escape(mptmp, prefix), log_escape(mptmp, href));
        }
    }

    /* Initialise XPath expression. */
    xpathObj = xmlXPathEvalExpression(xpathExpr, xpathCtx);
    if (xpathObj == NULL) {
        msr_log(msr, 1, "XML: Unable to evaluate xpath expression.");
        xmlXPathFreeContext(xpathCtx); 
        return -1;
    }

    /* Evaluate XPath expression. */
    nodes = xpathObj->nodesetval;
    if (nodes == NULL) {
        xmlXPathFreeObject(xpathObj);
        xmlXPathFreeContext(xpathCtx);
        return 0;
    }

    /* Create one variable for each node in the result. */
    for(i = 0; i < nodes->nodeNr; i++) {
        msre_var *rvar = NULL;
        char *content = NULL;

        content = (char *)xmlNodeGetContent(nodes->nodeTab[i]);
        if (content != NULL) {
            rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
            rvar->value = apr_pstrdup(mptmp, content);
            xmlFree(content);
            rvar->value_len = strlen(rvar->value);
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
         }
    }

    xmlXPathFreeObject(xpathObj);
    xmlXPathFreeContext(xpathCtx);    

    return count;
}
#endif

/* WEBSERVER_ERROR_LOG */

static int var_webserver_error_log_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = NULL;
    int i, count = 0;

    for(i = 0; i < msr->error_messages->nelts; i++) {
        error_message *em = (((error_message**)msr->error_messages->elts)[i]);
        char *fem = NULL;

        fem = format_error_log_message(mptmp, em);
        if (fem != NULL) {
            rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
            rvar->value = apr_pstrdup(mptmp, fem);
            rvar->value_len = strlen(rvar->value);
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }

    return count;
}

/* REMOTE_ADDR */

static int var_remote_addr_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    return var_simple_generate(var, vartab, mptmp, msr->remote_addr);
}

/* REMOTE_HOST */

static int var_remote_host_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const char *value1 = ap_get_remote_host(msr->r->connection, msr->r->per_dir_config,
        REMOTE_NAME, NULL);
    return var_simple_generate(var, vartab, mptmp, value1);
}

/* REMOTE_PORT */

static int var_remote_port_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = apr_psprintf(mptmp, "%i", msr->remote_port);
    return var_simple_generate(var, vartab, mptmp, value);
}

/* REMOTE_USER */

static int var_remote_user_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    return var_simple_generate(var, vartab, mptmp, msr->remote_user);
}

/* TX */

static int var_tx_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;

    arr = apr_table_elts(msr->tx_vars);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        msc_string *str = (msc_string *)te[i].val;
        int match;

        /* Figure out if we want to include this variable. */
        match = 0;
        if (var->param == NULL) match = 1; /* Unconditional inclusion. */
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, str->name,
                    str->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(str->name, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = str->value;
            rvar->value_len = str->value_len;
            rvar->name = apr_psprintf(mptmp, "TX:%s", log_escape_nq(mptmp, str->name));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }

    return count;
}

/* IP */

static int var_ip_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;
    apr_table_t *target_col = NULL;

    target_col = (apr_table_t *)apr_table_get(msr->collections, "ip");
    if (target_col == NULL) return 0;

    arr = apr_table_elts(target_col);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        msc_string *str = (msc_string *)te[i].val;
        int match;
        
        /* Figure out if we want to include this variable. */
        match = 0;
        if (var->param == NULL) match = 1; /* Unconditional inclusion. */
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, str->name,
                    str->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(str->name, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = str->value;
            rvar->value_len = str->value_len;
            rvar->name = apr_psprintf(mptmp, "IP:%s", log_escape_nq(mptmp, str->name));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }

    return count;
}

/* SESSION */

static int var_session_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;
    apr_table_t *target_col = NULL;

    target_col = (apr_table_t *)apr_table_get(msr->collections, "session");
    if (target_col == NULL) return 0;

    arr = apr_table_elts(target_col);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        msc_string *str = (msc_string *)te[i].val;
        int match;

        /* Figure out if we want to include this variable. */
        match = 0;
        if (var->param == NULL) match = 1; /* Unconditional inclusion. */
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, str->name,
                    str->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(str->name, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = str->value;
            rvar->value_len = str->value_len;
            rvar->name = apr_psprintf(mptmp, "SESSION:%s", log_escape_nq(mptmp, str->name));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }

    return count;
}

/* USER */

static int var_user_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;
    apr_table_t *target_col = NULL;

    target_col = (apr_table_t *)apr_table_get(msr->collections, "user");
    if (target_col == NULL) return 0;

    arr = apr_table_elts(target_col);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        msc_string *str = (msc_string *)te[i].val;
        int match;

        /* Figure out if we want to include this variable. */
        match = 0;
        if (var->param == NULL) match = 1; /* Unconditional match. */
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, str->name,
                    str->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(str->name, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = str->value;
            rvar->value_len = str->value_len;
            rvar->name = apr_psprintf(mptmp, "USER:%s", log_escape_nq(mptmp, str->name));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }

    return count;
}

/* GLOBAL */

static int var_global_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;
    apr_table_t *target_col = NULL;

    target_col = (apr_table_t *)apr_table_get(msr->collections, "global");
    if (target_col == NULL) return 0;

    arr = apr_table_elts(target_col);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        msc_string *str = (msc_string *)te[i].val;
        int match;

        /* Figure out if we want to include this variable. */
        match = 0;
        if (var->param == NULL) match = 1; /* Unconditional match. */
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, str->name,
                    str->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(str->name, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = str->value;
            rvar->value_len = str->value_len;
            rvar->name = apr_psprintf(mptmp, "GLOBAL:%s", log_escape_nq(mptmp, str->name));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }

    return count;
}

/* RESOURCE */

static int var_resource_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;
    apr_table_t *target_col = NULL;

    target_col = (apr_table_t *)apr_table_get(msr->collections, "resource");
    if (target_col == NULL) return 0;

    arr = apr_table_elts(target_col);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        msc_string *str = (msc_string *)te[i].val;
        int match;

        /* Figure out if we want to include this variable. */
        match = 0;
        if (var->param == NULL) match = 1; /* Unconditional match. */
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, str->name,
                    str->name_len, &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(str->name, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = str->value;
            rvar->value_len = str->value_len;
            rvar->name = apr_psprintf(mptmp, "RESOURCE:%s", log_escape_nq(mptmp, str->name));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }

    return count;
}

/* FILES_TMPNAMES */

static int var_files_tmpnames_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    multipart_part **parts = NULL;
    int i, count = 0;

    if (msr->mpd == NULL) return 0;

    parts = (multipart_part **)msr->mpd->parts->elts;
    for(i = 0; i < msr->mpd->parts->nelts; i++) {
        if ((parts[i]->type == MULTIPART_FILE)&&(parts[i]->tmp_file_name != NULL)) {
            int match = 0;

            /* Figure out if we want to include this variable. */
            if (var->param == NULL) match = 1;
            else {
                if (var->param_data != NULL) { /* Regex. */
                    char *my_error_msg = NULL;
                    if (!(msc_regexec((msc_regex_t *)var->param_data, parts[i]->name,
                        strlen(parts[i]->name), &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
                } else { /* Simple comparison. */
                    if (strcasecmp(parts[i]->name, var->param) == 0) match = 1;
                }
            }

            /* If we had a match add this argument to the collection. */
            if (match) {            
                msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

                rvar->value = parts[i]->tmp_file_name;
                rvar->value_len = strlen(rvar->value);
                rvar->name = apr_psprintf(mptmp, "FILES_TMPNAMES:%s",
                    log_escape_nq(mptmp, parts[i]->name));
                apr_table_addn(vartab, rvar->name, (void *)rvar);

                count++;
            }
        }
    }

    return count;
}

/* FILES */

static int var_files_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    multipart_part **parts = NULL;
    int i, count = 0;

    if (msr->mpd == NULL) return 0;

    parts = (multipart_part **)msr->mpd->parts->elts;
    for(i = 0; i < msr->mpd->parts->nelts; i++) {
        if (parts[i]->type == MULTIPART_FILE) {
            int match = 0;

            /* Figure out if we want to include this variable. */
            if (var->param == NULL) match = 1;
            else {
                if (var->param_data != NULL) { /* Regex. */
                    char *my_error_msg = NULL;
                    if (!(msc_regexec((msc_regex_t *)var->param_data, parts[i]->name,
                        strlen(parts[i]->name), &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
                } else { /* Simple comparison. */
                    if (strcasecmp(parts[i]->name, var->param) == 0) match = 1;
                }
            }

            /* If we had a match add this argument to the collection. */
            if (match) {            
                msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

                rvar->value = parts[i]->filename;
                rvar->value_len = strlen(rvar->value);
                rvar->name = apr_psprintf(mptmp, "FILES:%s",
                    log_escape_nq(mptmp, parts[i]->name));
                apr_table_addn(vartab, rvar->name, (void *)rvar);

                count++;
            }
        }
    }

    return count;
}

/* FILES_SIZES */

static int var_files_sizes_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    multipart_part **parts = NULL;
    int i, count = 0;

    if (msr->mpd == NULL) return 0;

    parts = (multipart_part **)msr->mpd->parts->elts;
    for(i = 0; i < msr->mpd->parts->nelts; i++) {
        if (parts[i]->type == MULTIPART_FILE) {
            int match = 0;

            /* Figure out if we want to include this variable. */
            if (var->param == NULL) match = 1;
            else {
                if (var->param_data != NULL) { /* Regex. */
                    char *my_error_msg = NULL;
                    if (!(msc_regexec((msc_regex_t *)var->param_data, parts[i]->name,
                        strlen(parts[i]->name), &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
                } else { /* Simple comparison. */
                    if (strcasecmp(parts[i]->name, var->param) == 0) match = 1;
                }
            }

            /* If we had a match add this argument to the collection. */
            if (match) {            
                msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

                rvar->value = apr_psprintf(mptmp, "%u", parts[i]->tmp_file_size);
                rvar->value_len = strlen(rvar->value);
                rvar->name = apr_psprintf(mptmp, "FILES_SIZES:%s",
                    log_escape_nq(mptmp, parts[i]->name));
                apr_table_addn(vartab, rvar->name, (void *)rvar);

                count++;
            }
        }
    }

    return count;
}

/* FILES_NAMES */

static int var_files_names_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    multipart_part **parts = NULL;
    int i, count = 0;

    if (msr->mpd == NULL) return 0;

    parts = (multipart_part **)msr->mpd->parts->elts;
    for(i = 0; i < msr->mpd->parts->nelts; i++) {
        if (parts[i]->type == MULTIPART_FILE) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = parts[i]->name;
            rvar->value_len = strlen(rvar->value);
            rvar->name = apr_psprintf(mptmp, "FILES_NAMES:%s",
                log_escape_nq(mptmp, parts[i]->name));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }

    return count;
}

/* FILES_COMBINED_SIZE */

static int var_files_combined_size_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    multipart_part **parts = NULL;
    msre_var *rvar = NULL;
    unsigned int combined_size = 0;
    int i;

    if (msr->mpd != NULL) {
        parts = (multipart_part **)msr->mpd->parts->elts;
        for(i = 0; i < msr->mpd->parts->nelts; i++) {
            if (parts[i]->type == MULTIPART_FILE) {
                combined_size += parts[i]->tmp_file_size;
            }
        }
    }

    rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
    rvar->value = apr_psprintf(mptmp, "%u", combined_size);
    rvar->value_len = strlen(rvar->value);
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* TIME */

static int var_time_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = NULL;
    struct tm *tm;
    time_t tc;

    tc = time(NULL);
    tm = localtime(&tc);
    rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
    rvar->value = apr_psprintf(mptmp, "%02d%02d%02d%02d%02d%02d%02d",
        (tm->tm_year / 100) + 19, (tm->tm_year % 100),
         tm->tm_mon + 1, tm->tm_mday, tm->tm_hour, tm->tm_min,
         tm->tm_sec);
    rvar->value_len = strlen(rvar->value);
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* TIME_YEAR */

static int var_time_year_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = NULL;
    struct tm *tm;
    time_t tc;

    tc = time(NULL);
    tm = localtime(&tc);
    rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
    rvar->value = apr_psprintf(mptmp, "%02d%02d",
        (tm->tm_year / 100) + 19,
         tm->tm_year % 100);
    rvar->value_len = strlen(rvar->value);
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* TIME_WDAY */

static int var_time_wday_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = NULL;
    struct tm *tm;
    time_t tc;

    tc = time(NULL);
    tm = localtime(&tc);
    rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
    rvar->value = apr_psprintf(mptmp, "%d", tm->tm_wday);
    rvar->value_len = strlen(rvar->value);
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* TIME_SEC */

static int var_time_sec_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = NULL;
    struct tm *tm;
    time_t tc;

    tc = time(NULL);
    tm = localtime(&tc);
    rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
    rvar->value = apr_psprintf(mptmp, "%02d", tm->tm_sec);
    rvar->value_len = strlen(rvar->value);
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* TIME_MIN */

static int var_time_min_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = NULL;
    struct tm *tm;
    time_t tc;

    tc = time(NULL);
    tm = localtime(&tc);
    rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
    rvar->value = apr_psprintf(mptmp, "%02d", tm->tm_min);
    rvar->value_len = strlen(rvar->value);
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* TIME_HOUR */
static int var_time_hour_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = NULL;
    struct tm *tm;
    time_t tc;

    tc = time(NULL);
    tm = localtime(&tc);
    rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
    rvar->value = apr_psprintf(mptmp, "%02d", tm->tm_hour);
    rvar->value_len = strlen(rvar->value);
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* TIME_MON */

static int var_time_mon_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = NULL;
    struct tm *tm;
    time_t tc;

    tc = time(NULL);
    tm = localtime(&tc);
    rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
    rvar->value = apr_psprintf(mptmp, "%02d", tm->tm_mon + 1);
    rvar->value_len = strlen(rvar->value);
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* TIME_DAY */

static int var_time_day_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = NULL;
    struct tm *tm;
    time_t tc;

    tc = time(NULL);
    tm = localtime(&tc);
    rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
    rvar->value = apr_psprintf(mptmp, "%02d", tm->tm_mday);
    rvar->value_len = strlen(rvar->value);
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* TIME_EPOCH */

static int var_time_epoch_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    msre_var *rvar = NULL;
    struct tm *tm;
    time_t tc;

    tc = time(NULL);
    tm = localtime(&tc);
    rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));
    rvar->value = apr_psprintf(mptmp, "%i", (int)tc);
    rvar->value_len = strlen(rvar->value);
    apr_table_addn(vartab, rvar->name, (void *)rvar);

    return 1;
}

/* QUERY_STRING */

static int var_query_string_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    return var_simple_generate(var, vartab, mptmp, msr->query_string);
}

/* REQUEST_BASENAME */

static int var_request_basename_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = file_basename(mptmp, msr->r->parsed_uri.path);
    return var_simple_generate(var, vartab, mptmp, value);
}

/* REQUEST_BODY */

static int var_request_body_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    if (msr->msc_reqbody_buffer != NULL) {
        return var_simple_generate_ex(var, vartab, mptmp,
            msr->msc_reqbody_buffer, msr->msc_reqbody_length);
    }
    return 0;
}

/* REQUEST_COOKIES */

static int var_request_cookies_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;

    arr = apr_table_elts(msr->request_cookies);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        int match = 0;

        /* Figure out if we want to include this variable. */
        if (var->param == NULL) match = 1;
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, te[i].key,
                    strlen(te[i].key), &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(te[i].key, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = te[i].val;
            rvar->value_len = strlen(rvar->value);
            rvar->name = apr_psprintf(mptmp, "REQUEST_COOKIES:%s",
                log_escape_nq(mptmp, te[i].key));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }
    
    return count;
}

/* REQUEST_COOKIES_NAMES */

static int var_request_cookies_names_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;

    arr = apr_table_elts(msr->request_cookies);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        int match = 0;

        /* Figure out if we want to include this variable. */
        if (var->param == NULL) match = 1;
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, te[i].key,
                    strlen(te[i].key), &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(te[i].key, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = te[i].key;
            rvar->value_len = strlen(rvar->value);
            rvar->name = apr_psprintf(mptmp, "REQUEST_COOKIES_NAMES:%s",
                log_escape_nq(mptmp, te[i].key));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }
    
    return count;
}

/* REQUEST_HEADERS */

static int var_request_headers_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;

    arr = apr_table_elts(msr->request_headers);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        int match = 0;

        /* Figure out if we want to include this variable. */
        if (var->param == NULL) match = 1;
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, te[i].key,
                    strlen(te[i].key), &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(te[i].key, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = te[i].val;
            rvar->value_len = strlen(rvar->value);
            rvar->name = apr_psprintf(mptmp, "REQUEST_HEADERS:%s",
                log_escape_nq(mptmp, te[i].key));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }
    
    return count;
}

/* REQUEST_HEADERS_NAMES */

static int var_request_headers_names_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;

    arr = apr_table_elts(msr->request_headers);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        int match = 0;

        /* Figure out if we want to include this variable. */
        if (var->param == NULL) match = 1;
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, te[i].key,
                    strlen(te[i].key), &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(te[i].key, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = te[i].key;
            rvar->value_len = strlen(rvar->value);
            rvar->name = apr_psprintf(mptmp, "REQUEST_HEADERS_NAMES:%s",
                log_escape_nq(mptmp, te[i].key));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }
    
    return count;
}

/* REQUEST_FILENAME */

static int var_request_filename_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    return var_simple_generate(var, vartab, mptmp, msr->r->parsed_uri.path);
}

/* REQUEST_LINE */

static int var_request_line_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    return var_simple_generate(var, vartab, mptmp, msr->request_line);
}

/* REQUEST_METHOD */

static int var_request_method_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    return var_simple_generate(var, vartab, mptmp, msr->request_method);
}

/* REQUEST_PROTOCOL */

static int var_request_protocol_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    return var_simple_generate(var, vartab, mptmp, msr->request_protocol);
}

/* SERVER_ADDR */

static int var_server_addr_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    return var_simple_generate(var, vartab, mptmp, msr->local_addr);
}

/* SERVER_NAME */

static int var_server_name_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    return var_simple_generate(var, vartab, mptmp, msr->hostname);
}

/* SERVER_PORT */

static int var_server_port_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = apr_psprintf(mptmp, "%i", msr->local_port);
    return var_simple_generate(var, vartab, mptmp, value);
}

/* SCRIPT_BASENAME */

static int var_script_basename_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = file_basename(mptmp, msr->r->filename);
    return var_simple_generate(var, vartab, mptmp, value);
}

/* SCRIPT_FILENAME */

static int var_script_filename_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = msr->r->filename;
    return var_simple_generate(var, vartab, mptmp, value);
}

/* SCRIPT_GID */

static int var_script_gid_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = apr_psprintf(mptmp, "%i", msr->r->finfo.group);
    return var_simple_generate(var, vartab, mptmp, value);
}

/* SCRIPT_GROUPNAME */

static int var_script_groupname_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = NULL;
    if (apr_gid_name_get(&value, msr->r->finfo.group, mptmp) == APR_SUCCESS) {
        return var_simple_generate(var, vartab, mptmp, value);
    }
    return 0;
}

/* SCRIPT_MODE */

static int var_script_mode_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = apr_psprintf(mptmp, "%04x", msr->r->finfo.protection);
    return var_simple_generate(var, vartab, mptmp, value);
}

/* SCRIPT_UID */

static int var_script_uid_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = apr_psprintf(mptmp, "%i", msr->r->finfo.user);
    return var_simple_generate(var, vartab, mptmp, value);
}

/* SCRIPT_USERNAME */

static int var_script_username_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = NULL;
    if (apr_uid_name_get(&value, msr->r->finfo.user, mptmp) == APR_SUCCESS) {
        return var_simple_generate(var, vartab, mptmp, value);
    }
    return 0;
}

/* AUTH_TYPE */

static int var_auth_type_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    char *value = msr->r->ap_auth_type;
    return var_simple_generate(var, vartab, mptmp, value);
}

/* PATH_INFO */

static int var_path_info_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const char *value = msr->r->path_info;
    return var_simple_generate(var, vartab, mptmp, value);
}

/* RESPONSE_BODY */

static int var_response_body_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    if (msr->resbody_data != NULL) {
        return var_simple_generate_ex(var, vartab, mptmp,
            msr->resbody_data, msr->resbody_length);
    }

    return 0;
}

/* RESPONSE_HEADERS */

static int var_response_headers_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;

    if (msr->response_headers == NULL) return 0;

    arr = apr_table_elts(msr->response_headers);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        int match = 0;

        /* Figure out if we want to include this variable. */
        if (var->param == NULL) match = 1;
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, te[i].key,
                    strlen(te[i].key), &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(te[i].key, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = te[i].val;
            rvar->value_len = strlen(rvar->value);
            rvar->name = apr_psprintf(mptmp, "RESPONSE_HEADERS:%s",
                log_escape_nq(mptmp, te[i].key));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }
    
    return count;
}

/* RESPONSE_HEADERS_NAMES */

static int var_response_headers_names_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const apr_array_header_t *arr = NULL;
    const apr_table_entry_t *te = NULL;
    int i, count = 0;

    arr = apr_table_elts(msr->response_headers);
    te = (apr_table_entry_t *)arr->elts;
    for (i = 0; i < arr->nelts; i++) {
        int match = 0;

        /* Figure out if we want to include this variable. */
        if (var->param == NULL) match = 1;
        else {
            if (var->param_data != NULL) { /* Regex. */
                char *my_error_msg = NULL;
                if (!(msc_regexec((msc_regex_t *)var->param_data, te[i].key,
                    strlen(te[i].key), &my_error_msg) == PCRE_ERROR_NOMATCH)) match = 1;
            } else { /* Simple comparison. */
                if (strcasecmp(te[i].key, var->param) == 0) match = 1;
            }
        }

        /* If we had a match add this argument to the collection. */
        if (match) {
            msre_var *rvar = apr_pmemdup(mptmp, var, sizeof(msre_var));

            rvar->value = te[i].key;
            rvar->value_len = strlen(rvar->value);
            rvar->name = apr_psprintf(mptmp, "RESPONSE_HEADERS_NAMES:%s",
                log_escape_nq(mptmp, te[i].key));
            apr_table_addn(vartab, rvar->name, (void *)rvar);

            count++;
        }
    }
    
    return count;
}

/* STATUS_LINE */

static int var_status_line_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const char *value = msr->status_line;
    return var_simple_generate(var, vartab, mptmp, value);
}

/* RESPONSE_PROTOCOL */

static int var_response_protocol_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const char *value = msr->response_protocol;
    return var_simple_generate(var, vartab, mptmp, value);
}

/* RESPONSE_STATUS */

static int var_response_status_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const char *value = apr_psprintf(mptmp, "%i", msr->response_status);
    return var_simple_generate(var, vartab, mptmp, value);
}

/* USERID */

static int var_userid_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const char *value = msr->userid;
    return var_simple_generate(var, vartab, mptmp, value);
}

/* SESSIONID */

static int var_sessionid_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const char *value = msr->sessionid;
    return var_simple_generate(var, vartab, mptmp, value);
}

/* WEBAPPID */

static int var_webappid_generate(modsec_rec *msr, msre_var *var, msre_rule *rule,
    apr_table_t *vartab, apr_pool_t *mptmp)
{
    const char *value = msr->txcfg->webappid;
    return var_simple_generate(var, vartab, mptmp, value);
}

/* ---------------------------------------------- */

/**
 *
 */
static void msre_engine_variable_register(msre_engine *engine, const char *name, 
    unsigned int type, unsigned int argc_min, unsigned int argc_max,
    FN_VAR_VALIDATE(validate), FN_VAR_GENERATE(generate),
    unsigned int is_cacheable, unsigned int availability)
{
    msre_var_metadata *metadata = (msre_var_metadata *)apr_pcalloc(engine->mp,
        sizeof(msre_var_metadata));
    if (metadata == NULL) return;

    metadata->name = name;
    metadata->type = type;
    metadata->argc_min = argc_min;
    metadata->argc_max = argc_max;
    metadata->validate = validate;
    metadata->generate = generate;
    metadata->is_cacheable = is_cacheable;
    metadata->availability = availability;

    apr_table_setn(engine->variables, name, (void *)metadata);
}

/**
 *
 */
void msre_engine_register_default_variables(msre_engine *engine) {

    /* ARGS */
    msre_engine_variable_register(engine,
        "ARGS",
        VAR_LIST,
        0, 1,
        var_generic_list_validate,
        var_args_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* ARGS_COMBINED_SIZE */
    msre_engine_variable_register(engine,
        "ARGS_COMBINED_SIZE",
        VAR_LIST,
        0, 0,
        NULL,
        var_args_combined_size_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* ARGS_NAMES */
    msre_engine_variable_register(engine,
        "ARGS_NAMES",
        VAR_LIST,
        0, 1,
        var_generic_list_validate,
        var_args_names_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* ENV */
    msre_engine_variable_register(engine,
        "ENV",
        VAR_LIST,
        0, 1,
        var_env_validate,
        var_env_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQBODY_PROCESSOR */
    msre_engine_variable_register(engine,
        "REQBODY_PROCESSOR",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_reqbody_processor_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQBODY_PROCESSOR_ERROR */
    msre_engine_variable_register(engine,
        "REQBODY_PROCESSOR_ERROR",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_reqbody_processor_error_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_BODY
    );

    /* REQBODY_PROCESSOR_ERROR_MSG */
    msre_engine_variable_register(engine,
        "REQBODY_PROCESSOR_ERROR_MSG",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_reqbody_processor_error_msg_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_BODY
    );

    #ifdef WITH_LIBXML2
    /* XML */
    msre_engine_variable_register(engine,
        "XML",
        VAR_LIST,
        0, 1,
        var_xml_validate,
        var_xml_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );
    #endif

    /* WEBSERVER_ERROR_LOG */
    msre_engine_variable_register(engine,
        "WEBSERVER_ERROR_LOG",
        VAR_LIST,
        0, 0,
        NULL,
        var_webserver_error_log_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REMOTE_ADDR */
    msre_engine_variable_register(engine,
        "REMOTE_ADDR",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_remote_addr_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REMOTE_HOST */
    msre_engine_variable_register(engine,
        "REMOTE_HOST",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_remote_host_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* REMOTE_PORT */
    msre_engine_variable_register(engine,
        "REMOTE_PORT",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_remote_port_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* REMOTE_USER */
    msre_engine_variable_register(engine,
        "REMOTE_USER",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_remote_user_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* TX */
    msre_engine_variable_register(engine,
        "TX",
        VAR_LIST,
        1, 1,
        var_generic_list_validate,
        var_tx_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* IP */
    msre_engine_variable_register(engine,
        "IP",
        VAR_LIST,
        1, 1,
        var_generic_list_validate,
        var_ip_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* SESSION */
    msre_engine_variable_register(engine,
        "SESSION",
        VAR_LIST,
        1, 1,
        var_generic_list_validate,
        var_session_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* USER */
    msre_engine_variable_register(engine,
        "USER",
        VAR_LIST,
        1, 1,
        var_generic_list_validate,
        var_user_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* GLOBAL */
    msre_engine_variable_register(engine,
        "GLOBAL",
        VAR_LIST,
        1, 1,
        var_generic_list_validate,
        var_global_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* RESOURCE */
    msre_engine_variable_register(engine,
        "RESOURCE",
        VAR_LIST,
        1, 1,
        var_generic_list_validate,
        var_resource_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* FILES */
    msre_engine_variable_register(engine,
        "FILES",
        VAR_LIST,
        0, 1,
        var_generic_list_validate,
        var_files_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* FILES_COMBINED_SIZE */
    msre_engine_variable_register(engine,
        "FILES_COMBINED_SIZE",
        VAR_LIST,
        0, 0,
        NULL,
        var_files_combined_size_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* FILES_NAMES */
    msre_engine_variable_register(engine,
        "FILES_NAMES",
        VAR_LIST,
        0, 0,
        NULL,
        var_files_names_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* FILES_SIZES */
    msre_engine_variable_register(engine,
        "FILES_SIZES",
        VAR_LIST,
        0, 1,
        var_generic_list_validate,
        var_files_sizes_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* FILES_TMPNAMES */
    msre_engine_variable_register(engine,
        "FILES_TMPNAMES",
        VAR_LIST,
        0, 1,
        var_generic_list_validate,
        var_files_tmpnames_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* TIME */
    msre_engine_variable_register(engine,
        "TIME",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_time_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* TIME_DAY */
    msre_engine_variable_register(engine,
        "TIME_DAY",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_time_day_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* TIME_EPOCH */
    msre_engine_variable_register(engine,
        "TIME_EPOCH",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_time_epoch_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* TIME_HOUR */
    msre_engine_variable_register(engine,
        "TIME_HOUR",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_time_hour_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* TIME_MIN */
    msre_engine_variable_register(engine,
        "TIME_MIN",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_time_min_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* TIME_MON */
    msre_engine_variable_register(engine,
        "TIME_MON",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_time_mon_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* TIME_SEC */
    msre_engine_variable_register(engine,
        "TIME_SEC",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_time_sec_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* TIME_WDAY */
    msre_engine_variable_register(engine,
        "TIME_WDAY",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_time_wday_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* TIME_YEAR */
    msre_engine_variable_register(engine,
        "TIME_YEAR",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_time_year_generate,
        VAR_DONT_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* QUERY_STRING */
    msre_engine_variable_register(engine,
        "QUERY_STRING",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_query_string_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQUEST_BASENAME */
    msre_engine_variable_register(engine,
        "REQUEST_BASENAME",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_request_basename_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQUEST_BODY */
    msre_engine_variable_register(engine,
        "REQUEST_BODY",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_request_body_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* REQUEST_COOKIES */
    msre_engine_variable_register(engine,
        "REQUEST_COOKIES",
        VAR_LIST,
        0, 1,
        var_generic_list_validate,
        var_request_cookies_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQUEST_COOKIES_NAMES */
    msre_engine_variable_register(engine,
        "REQUEST_COOKIES_NAMES",
        VAR_LIST,
        0, 1,
        var_generic_list_validate,
        var_request_cookies_names_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQUEST_FILENAME */
    msre_engine_variable_register(engine,
        "REQUEST_FILENAME",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_request_filename_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQUEST_HEADERS */
    msre_engine_variable_register(engine,
        "REQUEST_HEADERS",
        VAR_LIST,
        0, 1,
        var_generic_list_validate,
        var_request_headers_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQUEST_HEADERS_NAMES */
    msre_engine_variable_register(engine,
        "REQUEST_HEADERS_NAMES",
        VAR_LIST,
        0, 1,
        var_generic_list_validate,
        var_request_headers_names_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQUEST_LINE */
    msre_engine_variable_register(engine,
        "REQUEST_LINE",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_request_line_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQUEST_METHOD */
    msre_engine_variable_register(engine,
        "REQUEST_METHOD",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_request_method_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQUEST_PROTOCOL */
    msre_engine_variable_register(engine,
        "REQUEST_PROTOCOL",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_request_protocol_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQUEST_URI */
    msre_engine_variable_register(engine, 
        "REQUEST_URI",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_request_uri_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* REQUEST_URI_RAW */
    msre_engine_variable_register(engine, 
        "REQUEST_URI_RAW",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_request_uri_raw_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* SERVER_ADDR */
    msre_engine_variable_register(engine,
        "SERVER_ADDR",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_server_addr_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* SERVER_NAME */
    msre_engine_variable_register(engine,
        "SERVER_NAME",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_server_name_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* SERVER_PORT */
    msre_engine_variable_register(engine,
        "SERVER_PORT",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_server_port_generate,
        VAR_CACHE,
        PHASE_REQUEST_HEADERS
    );

    /* SCRIPT_GID */
    msre_engine_variable_register(engine,
        "SCRIPT_GID",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_script_gid_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* SCRIPT_BASENAME */
    msre_engine_variable_register(engine,
        "SCRIPT_BASENAME",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_script_basename_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* SCRIPT_FILENAME */
    msre_engine_variable_register(engine,
        "SCRIPT_FILENAME",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_script_filename_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* SCRIPT_GROUPNAME */
    msre_engine_variable_register(engine,
        "SCRIPT_GROUPNAME",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_script_groupname_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* SCRIPT_MODE */
    msre_engine_variable_register(engine,
        "SCRIPT_MODE",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_script_mode_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* SCRIPT_UID */
    msre_engine_variable_register(engine,
        "SCRIPT_UID",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_script_uid_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* SCRIPT_USERNAME */
    msre_engine_variable_register(engine,
        "SCRIPT_USERNAME",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_script_username_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* PATH_INFO */
    msre_engine_variable_register(engine,
        "PATH_INFO",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_path_info_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* AUTH_TYPE */
    msre_engine_variable_register(engine,
        "AUTH_TYPE",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_auth_type_generate,
        VAR_CACHE,
        PHASE_REQUEST_BODY
    );

    /* RESPONSE_BODY */
    msre_engine_variable_register(engine,
        "RESPONSE_BODY",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_response_body_generate,
        VAR_CACHE,
        PHASE_RESPONSE_BODY
    );

    /* RESPONSE_HEADERS */
    msre_engine_variable_register(engine,
        "RESPONSE_HEADERS",
        VAR_LIST,
        0, 1,
        var_generic_list_validate,
        var_response_headers_generate,
        VAR_CACHE,
        PHASE_RESPONSE_HEADERS
    );

    /* RESPONSE_HEADERS_NAMES */
    msre_engine_variable_register(engine,
        "RESPONSE_HEADERS_NAMES",
        VAR_LIST,
        0, 1,
        var_generic_list_validate,
        var_response_headers_names_generate,
        VAR_CACHE,
        PHASE_RESPONSE_HEADERS
    );

    /* STATUS_LINE */
    msre_engine_variable_register(engine,
        "STATUS_LINE",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_status_line_generate,
        VAR_CACHE,
        PHASE_RESPONSE_HEADERS
    );

    /* RESPONSE_PROTOCOL */
    msre_engine_variable_register(engine,
        "RESPONSE_PROTOCOL",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_response_protocol_generate,
        VAR_CACHE,
        PHASE_RESPONSE_HEADERS
    );

    /* RESPONSE_STATUS */
    msre_engine_variable_register(engine,
        "RESPONSE_STATUS",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_response_status_generate,
        VAR_CACHE,
        PHASE_RESPONSE_HEADERS
    );

    /* RULE */
    msre_engine_variable_register(engine,
        "RULE",
        VAR_LIST,
        1, 1,
        NULL,
        var_rule_generate,
        VAR_DONT_CACHE,
        PHASE_RESPONSE_HEADERS
    );

    /* USERID */
    msre_engine_variable_register(engine,
        "USERID",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_userid_generate,
        VAR_DONT_CACHE,
        PHASE_RESPONSE_HEADERS
    );

    /* SESSIONID */
    msre_engine_variable_register(engine,
        "SESSIONID",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_sessionid_generate,
        VAR_DONT_CACHE,
        PHASE_RESPONSE_HEADERS
    );

    /* WEBAPPID */
    msre_engine_variable_register(engine,
        "WEBAPPID",
        VAR_SIMPLE,
        0, 0,
        NULL,
        var_webappid_generate,
        VAR_DONT_CACHE,
        PHASE_RESPONSE_HEADERS
    );
}
