/*
 * ModSecurity for Apache 2.x, http://www.modsecurity.org/
 * Copyright (c) 2004-2022 Trustwave Holdings, Inc. (http://www.trustwave.com/)
 *
 * You may not use this file except in compliance with
 * the License.  You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * If any of the files related to licensing are missing or if you have any
 * other questions related to licensing please contact Trustwave Holdings, Inc.
 * directly using the email address security@modsecurity.org.
 */

#include "msc_crypt.h"
#include "msc_util.h"
#include "apr_sha1.h"
#include "apr_uri.h"
#include "apr_base64.h"
#include "acmp.h"
#include "libxml/HTMLtree.h"
#include "libxml/uri.h"
#include <string.h>

/**
 * \brief Normalize path in URI
 *
 * \param msr ModSecurity transaction resource
 * \param input The URI to be normalized
 *
 * \retval input normalized input
 * \retval NULL on fail
 */
char *normalize_path(modsec_rec *msr, char *input) {
    assert(msr != NULL);
    assert(input != NULL);
    xmlURI *uri = NULL;
    char *parsed_content = NULL;
    char *content = NULL;

    uri = xmlParseURI(input);

    if(uri != NULL && uri->path)    {
        if(uri->scheme) {
            content = apr_psprintf(msr->mp, "%s://", uri->scheme);
            parsed_content = apr_pstrcat(msr->mp, content, NULL);
        }

        if(uri->server) {
            content = apr_psprintf(msr->mp, "%s", uri->server);
            if(parsed_content)
                parsed_content = apr_pstrcat(msr->mp, parsed_content, content, NULL);
            else
                parsed_content = apr_pstrcat(msr->mp, content, NULL);
        }

        if(uri->port)   {
            content = apr_psprintf(msr->mp, ":%d", uri->port);
            if(parsed_content)
                parsed_content = apr_pstrcat(msr->mp, parsed_content, content, NULL);
            else
                parsed_content = apr_pstrcat(msr->mp, content, NULL);
        }

        if(uri->path)   {
            char *Uri = NULL;
            /*int i;*/
            char *abs_link = NULL;
            char *filename = NULL;
            char *abs_path = NULL;
            char *abs_uri = NULL;

            if (uri->path[0] != '/') {
                /* uri->path is relative. make it absolute */
                filename = file_basename(msr->mp, msr->r->parsed_uri.path);

                if(filename == NULL || (strlen(msr->r->parsed_uri.path) - strlen(filename) < 0))
                    return NULL;

                abs_path = apr_pstrndup(msr->mp, msr->r->parsed_uri.path, strlen(msr->r->parsed_uri.path) - strlen(filename));
                abs_uri = apr_pstrcat(msr->mp, abs_path, uri->path, NULL);

                abs_link = apr_pstrdup(msr->mp, abs_uri);
            }
            else {
                abs_link = apr_pstrdup(msr->mp, uri->path);
            }

            xmlNormalizeURIPath(abs_link);

            Uri = apr_pstrdup(msr->mp, abs_link);

/*
            for(i = 0; i < (int)strlen(Uri); i++)    {
                if(Uri[i] != '.' && Uri[i] != '/')  {
                    if (i - 1 < 0)
                        i = 0;
                    else
                        i--;
                    if(Uri[i] == '/')
                        --bytes;
                    break;
                }   else    {
                    bytes++;
                }
            }

            if(bytes >= (int)strlen(uri->path))
                return NULL;
*/

            content = apr_psprintf(msr->mp, "%s", Uri);

            if(parsed_content)
                parsed_content = apr_pstrcat(msr->mp, parsed_content, content, NULL);
            else
                parsed_content = apr_pstrcat(msr->mp, content, NULL);

        }

        if(uri->query_raw)  {
            content = apr_psprintf(msr->mp, "?%s", uri->query_raw);
            if(parsed_content)
                parsed_content = apr_pstrcat(msr->mp, parsed_content, content, NULL);
            else
                parsed_content = apr_pstrcat(msr->mp, content, NULL);
        }

        if(uri->fragment)   {
            content = apr_psprintf(msr->mp, "#%s", uri->fragment);
            if(parsed_content)
                parsed_content = apr_pstrcat(msr->mp, parsed_content, content, NULL);
            else
                parsed_content = apr_pstrcat(msr->mp, content, NULL);
        }
        xmlFreeURI(uri);
        return apr_pstrdup(msr->mp, parsed_content);
    }

    if(uri != NULL) xmlFreeURI(uri);
    return apr_pstrdup(msr->mp, input);
}

/**
 * \brief Create a random password
 *
 * \param mp ModSecurity transaction memory pool
 *
 * \retval key random key
 */
char *getkey(apr_pool_t *mp) {
    unsigned char digest[APR_SHA1_DIGESTSIZE];
    char *sig, *key, *value;
    apr_sha1_ctx_t ctx;
    char salt[64];

    apr_generate_random_bytes(salt, sizeof(salt));
    key = apr_psprintf(mp,"%.*s",(int)sizeof(salt),salt);

    apr_sha1_init (&ctx);
    apr_sha1_update (&ctx, (const char*)key, strlen(key));
    apr_sha1_update (&ctx, "\0", 1);

    apr_generate_random_bytes(salt, sizeof(salt));
    value = apr_psprintf(mp,"%.*s",(int)sizeof(salt),salt);

    apr_sha1_update (&ctx, value, strlen (value));
    apr_sha1_final (digest, &ctx);

    sig = apr_pcalloc (mp, apr_base64_encode_len (sizeof (digest)));
    apr_base64_encode (sig, (const char*)digest, sizeof (digest));

    return sig;
}


/**
 * \brief Generate the MAC for a given message
 *
 * \param msr ModSecurity transaction resource
 * \param key The key used within HMAC
 * \param key_len Key length
 * \param msg The message to generate the MAC
 * \param msglen The message length
 *
 * \retval hex_digest The MAC
 */
#ifdef __NetBSD__
char *mschmac(modsec_rec *msr, const char *key, int key_len,
        unsigned char *msg, int msglen) {
#else
char *hmac(modsec_rec *msr, const char *key, int key_len,
        unsigned char *msg, int msglen) {
#endif
    assert(msr != NULL);
    assert(msg != NULL);
    apr_sha1_ctx_t ctx;
    unsigned char digest[APR_SHA1_DIGESTSIZE];
    unsigned char hmac_ipad[HMAC_PAD_SIZE], hmac_opad[HMAC_PAD_SIZE];
    unsigned char nkey[APR_SHA1_DIGESTSIZE];
    unsigned char *hmac_key = (unsigned char *) key;
    char hex_digest[APR_SHA1_DIGESTSIZE * 2 + 1], *hmac_digest;
    const char hex[] = "0123456789abcdef";
    int i;

    if (key_len > HMAC_PAD_SIZE-1) {
        hmac_key = nkey;
        key_len = APR_SHA1_DIGESTSIZE;
    }

    memset ((void *) hmac_ipad, 0, sizeof (hmac_ipad));
    memset ((void *) hmac_opad, 0, sizeof (hmac_opad));
    memmove (hmac_ipad, hmac_key, key_len);
    memmove (hmac_opad, hmac_key, key_len);

    for (i = 0; i < HMAC_PAD_SIZE-1; i++)  {
        hmac_ipad[i] ^= 0x36;
        hmac_opad[i] ^= 0x5c;
    }

    apr_sha1_init (&ctx);
    apr_sha1_update_binary (&ctx, hmac_ipad, HMAC_PAD_SIZE-1);
    apr_sha1_update_binary (&ctx, (const unsigned char *) msg, msglen);
    apr_sha1_final (digest, &ctx);

    apr_sha1_init (&ctx);
    apr_sha1_update_binary (&ctx, hmac_opad, HMAC_PAD_SIZE-1);
    apr_sha1_update_binary (&ctx, digest, sizeof (digest));
    apr_sha1_final (digest, &ctx);

    hmac_digest = hex_digest;
    for (i = 0; i < sizeof (digest); i++) {
        *hmac_digest++ = hex[digest[i] >> 4];
        *hmac_digest++ = hex[digest[i] & 0xF];
    }

    *hmac_digest = '\0';

    return apr_pstrdup (msr->mp, hex_digest);
}


/**
 * \brief Init html response body parser
 *
 * \param msr ModSecurity transaction resource
 *
 * \retval 1 on success
 * \retval -1 on fail
 */
int init_response_body_html_parser(modsec_rec *msr)   {
    char *charset = NULL;
    char *final_charset = NULL;
    char sep;

    if(msr == NULL) return -1;

    if(msr->r == NULL) return -1;

    if(msr->r->content_type == NULL) return -1;

    if(msr->crypto_html_tree != NULL){
        xmlFreeDoc(msr->crypto_html_tree);
        msr->crypto_html_tree = NULL;
    }

    if((strncmp("text/html",msr->r->content_type,  9) != 0)){
        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4,
                    "init_response_body_html_parser: skipping html_tree generation for Content[%s].", msr->r->content_type);
        if(msr->crypto_html_tree != NULL){
            xmlFreeDoc(msr->crypto_html_tree);
            msr->crypto_html_tree = NULL;
        }
        return -1;
    }

    if (msr->resbody_length == 0) {
        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4, "init_response_body_html_parser: skipping html_tree generation for zero length respomse body.");
        msr->crypto_html_tree = NULL;
        return 0;
    }

    if((msr->r->content_encoding == NULL)||(apr_strnatcasecmp(msr->r->content_encoding,"(null)")==0)){
        charset=m_strcasestr(msr->r->content_type,"charset=");
        if(charset == NULL){
            if (msr->txcfg->debuglog_level >= 4)
                msr_log(msr, 4, "init_response_body_html_parser: assuming ISO-8859-1.");
            msr->crypto_html_tree = htmlReadMemory(msr->resbody_data, msr->resbody_length, NULL,
                    "ISO-8859-1", HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
            htmlSetMetaEncoding ((htmlDocPtr) msr->crypto_html_tree, (const xmlChar *) "ISO-8859-1");
        }
        else{
            charset+=8;
            final_charset=strchr(charset,' ');
            if(final_charset == NULL) final_charset = strchr(charset,';');
            if(final_charset != NULL) {
                sep = *final_charset;
                *final_charset = '\0';
            }

            if (msr->txcfg->debuglog_level >= 4)
                msr_log(msr, 4,
                        "init_response_body_html_parser: Charset[%s]",charset);
            msr->crypto_html_tree = htmlReadMemory(msr->resbody_data, msr->resbody_length, NULL,
                    charset, HTML_PARSE_RECOVER| HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
            htmlSetMetaEncoding ((htmlDocPtr) msr->crypto_html_tree, (const xmlChar *)charset);
            if(final_charset != NULL) *final_charset=sep;
        }

    }
    else{
        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4,"init_response_body_html_parser: Enconding[%s].",msr->r->content_encoding);
        msr->crypto_html_tree = htmlReadMemory(msr->resbody_data, msr->resbody_length, NULL,
                msr->r->content_encoding, HTML_PARSE_RECOVER | HTML_PARSE_NOBLANKS | HTML_PARSE_NOERROR | HTML_PARSE_NOWARNING);
        htmlSetMetaEncoding ((htmlDocPtr) msr->crypto_html_tree, (const xmlChar *)msr->r->content_encoding);
    }
    if(msr->crypto_html_tree == NULL){
        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4,
                    "init_response_body_html_parser: Failed to parse response body.");
        return -1;
    }
    else {
        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4,
                    "init_response_body_html_parser: Successfully html parser generated.");
        return 1;
    }

    return 1;
}

/**
 * \brief Execute all hash methods
 *
 * \param msr ModSecurity transaction resource
 * \param link The html attr value to be checked
 * \param type The hash method type
 *
 * \retval 1 Match
 * \retval 0 No Match
 * \retval -1 on fail
 */
int do_hash_method(modsec_rec *msr, char *link, int type)   {
    assert(msr != NULL);
    assert(link != NULL);
    hash_method **em = NULL;
    int i = 0;
    char *error_msg = NULL;
    char *my_error_msg = NULL;
    int ovector[33];
    int rc;

    if(msr == NULL) return -1;

    em = (hash_method **)msr->txcfg->hash_method->elts;

    if(msr->txcfg->hash_method->nelts == 0)
        return 1;

    for (i = 0; i < msr->txcfg->hash_method->nelts; i++) {

        if(em[i] != NULL && em[i]->param_data != NULL){

            switch(type)    {
                case HASH_URL_HREF_HASH_PM:
                    if(em[i]->type == HASH_URL_HREF_HASH_PM)   {
                        const char *match = NULL;
                        apr_status_t rc = 0;
                        ACMPT pt;

                        pt.parser = (ACMP *)em[i]->param_data;
                        pt.ptr = NULL;

                        rc = acmp_process_quick(&pt, &match, link, strlen(link));

                        if (rc) {
                            return 1;
                        } else  {
                            return 0;
                        }
                    }
                    break;
                case HASH_URL_HREF_HASH_RX:
                    if(em[i]->type == HASH_URL_HREF_HASH_RX)   {
                        rc = msc_regexec_capture(em[i]->param_data, link, strlen(link), ovector, 30, &my_error_msg);
#ifndef WITH_PCRE
                        if ((rc == PCRE2_ERROR_MATCHLIMIT) || (rc == PCRE2_ERROR_RECURSIONLIMIT)) {
#else
                        if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) {
#endif
                            msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));

                            if (s == NULL) return -1;
                            s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED");
                            if (s->name == NULL) return -1;
                            s->name_len = strlen(s->name);
                            s->value = apr_pstrdup(msr->mp, "1");
                            if (s->value == NULL) return -1;
                            s->value_len = 1;
                            apr_table_setn(msr->tx_vars, s->name, (void *)s);

                            error_msg = apr_psprintf(msr->mp,
                                    "Execution error - "
                                    "PCRE limits exceeded for Hash regex [%s] (%d): %s",
                                    em[i]->param,rc, my_error_msg);

                            if (msr->txcfg->debuglog_level >= 4)
                                msr_log(msr, 4, "%s.", error_msg);

                            return 0; /* No match. */
                        }
                        else if (rc < -1) {
                            error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s",
                                    rc, my_error_msg);
                            if (msr->txcfg->debuglog_level >= 4)
                                msr_log(msr, 4, "%s.", error_msg);
                            return -1;
                        }
#ifndef WITH_PCRE
                        if (rc != PCRE2_ERROR_NOMATCH) { /* Match. */
#else
                        if (rc != PCRE_ERROR_NOMATCH) { /* Match. */
#endif
                            return 1;
                        }
                    }
                    break;
                case HASH_URL_FACTION_HASH_PM:
                    if(em[i]->type == HASH_URL_FACTION_HASH_PM)   {
                       const char *match = NULL;
                        apr_status_t rc = 0;
                        ACMPT pt;

                        pt.parser = (ACMP *)em[i]->param_data;
                        pt.ptr = NULL;

                        rc = acmp_process_quick(&pt, &match, link, strlen(link));

                        if (rc) {
                            return 1;
                        } else  {
                            return 0;
                        }
                    }
                    break;
                case HASH_URL_FACTION_HASH_RX:
                    if(em[i]->type == HASH_URL_FACTION_HASH_RX)   {
                        rc = msc_regexec_capture(em[i]->param_data, link, strlen(link), ovector, 30, &my_error_msg);
#ifndef WITH_PCRE
                        if ((rc == PCRE2_ERROR_MATCHLIMIT) || (rc == PCRE2_ERROR_RECURSIONLIMIT)) {
#else
                        if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) {
#endif
                            msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));

                            if (s == NULL) return -1;
                            s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED");
                            if (s->name == NULL) return -1;
                            s->name_len = strlen(s->name);
                            s->value = apr_pstrdup(msr->mp, "1");
                            if (s->value == NULL) return -1;
                            s->value_len = 1;
                            apr_table_setn(msr->tx_vars, s->name, (void *)s);

                            error_msg = apr_psprintf(msr->mp,
                                    "Execution error - "
                                    "PCRE limits exceeded for Hash regex [%s] (%d): %s",
                                    em[i]->param,rc, my_error_msg);

                            if (msr->txcfg->debuglog_level >= 4)
                                msr_log(msr, 4, "%s.", error_msg);

                            return 0; /* No match. */
                        }
                        else if (rc < -1) {
                            error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s",
                                    rc, my_error_msg);
                            if (msr->txcfg->debuglog_level >= 4)
                                msr_log(msr, 4, "%s.", error_msg);
                            return -1;
                        }
#ifndef WITH_PCRE
                        if (rc != PCRE2_ERROR_NOMATCH) { /* Match. */
#else
                        if (rc != PCRE_ERROR_NOMATCH) { /* Match. */
#endif
                            return 1;
                        }
                    }
                    break;
                case HASH_URL_LOCATION_HASH_PM:
                    if(em[i]->type == HASH_URL_LOCATION_HASH_PM)   {
                       const char *match = NULL;
                        apr_status_t rc = 0;
                        ACMPT pt;

                        pt.parser = (ACMP *)em[i]->param_data;
                        pt.ptr = NULL;

                        rc = acmp_process_quick(&pt, &match, link, strlen(link));

                        if (rc) {
                            return 1;
                        } else  {
                            return 0;
                        }
                    }
                    break;
                case HASH_URL_LOCATION_HASH_RX:
                    if(em[i]->type == HASH_URL_LOCATION_HASH_RX)   {
                        rc = msc_regexec_capture(em[i]->param_data, link, strlen(link), ovector, 30, &my_error_msg);
#ifndef WITH_PCRE
                        if ((rc == PCRE2_ERROR_MATCHLIMIT) || (rc == PCRE2_ERROR_RECURSIONLIMIT)) {
#else
                        if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) {
#endif
                            msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));

                            if (s == NULL) return -1;
                            s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED");
                            if (s->name == NULL) return -1;
                            s->name_len = strlen(s->name);
                            s->value = apr_pstrdup(msr->mp, "1");
                            if (s->value == NULL) return -1;
                            s->value_len = 1;
                            apr_table_setn(msr->tx_vars, s->name, (void *)s);

                            error_msg = apr_psprintf(msr->mp,
                                    "Execution error - "
                                    "PCRE limits exceeded for Hash regex [%s] (%d): %s",
                                    em[i]->param,rc, my_error_msg);

                            if (msr->txcfg->debuglog_level >= 4)
                                msr_log(msr, 4, "%s.", error_msg);

                            return 0; /* No match. */
                        }
                        else if (rc < -1) {
                            error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s",
                                    rc, my_error_msg);
                            if (msr->txcfg->debuglog_level >= 4)
                                msr_log(msr, 4, "%s.", error_msg);
                            return -1;
                        }
#ifndef WITH_PCRE
                        if (rc != PCRE2_ERROR_NOMATCH) { /* Match. */
#else
                        if (rc != PCRE_ERROR_NOMATCH) { /* Match. */
#endif
                            return 1;
                        }
                    }
                    break;
                case HASH_URL_IFRAMESRC_HASH_PM:
                    if(em[i]->type == HASH_URL_IFRAMESRC_HASH_PM)   {
                       const char *match = NULL;
                        apr_status_t rc = 0;
                        ACMPT pt;

                        pt.parser = (ACMP *)em[i]->param_data;
                        pt.ptr = NULL;

                        rc = acmp_process_quick(&pt, &match, link, strlen(link));

                        if (rc) {
                            return 1;
                        } else  {
                            return 0;
                        }
                    }
                    break;
                case HASH_URL_IFRAMESRC_HASH_RX:
                    if(em[i]->type == HASH_URL_IFRAMESRC_HASH_RX)   {
                        rc = msc_regexec_capture(em[i]->param_data, link, strlen(link), ovector, 30, &my_error_msg);
#ifndef WITH_PCRE
                        if ((rc == PCRE2_ERROR_MATCHLIMIT) || (rc == PCRE2_ERROR_RECURSIONLIMIT)) {
#else
                        if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) {
#endif
                            msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));

                            if (s == NULL) return -1;
                            s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED");
                            if (s->name == NULL) return -1;
                            s->name_len = strlen(s->name);
                            s->value = apr_pstrdup(msr->mp, "1");
                            if (s->value == NULL) return -1;
                            s->value_len = 1;
                            apr_table_setn(msr->tx_vars, s->name, (void *)s);

                            error_msg = apr_psprintf(msr->mp,
                                    "Execution error - "
                                    "PCRE limits exceeded for Hash regex [%s] (%d): %s",
                                    em[i]->param,rc, my_error_msg);

                            if (msr->txcfg->debuglog_level >= 4)
                                msr_log(msr, 4, "%s.", error_msg);

                            return 0; /* No match. */
                        }
                        else if (rc < -1) {
                            error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s",
                                    rc, my_error_msg);
                            if (msr->txcfg->debuglog_level >= 4)
                                msr_log(msr, 4, "%s.", error_msg);
                            return -1;
                        }
#ifndef WITH_PCRE
                        if (rc != PCRE2_ERROR_NOMATCH) { /* Match. */
#else
                        if (rc != PCRE_ERROR_NOMATCH) { /* Match. */
#endif
                            return 1;
                        }
                    }
                    break;
                case HASH_URL_FRAMESRC_HASH_PM:
                    if(em[i]->type == HASH_URL_FRAMESRC_HASH_PM)   {
                       const char *match = NULL;
                        apr_status_t rc = 0;
                        ACMPT pt;

                        pt.parser = (ACMP *)em[i]->param_data;
                        pt.ptr = NULL;

                        rc = acmp_process_quick(&pt, &match, link, strlen(link));

                        if (rc) {
                            return 1;
                        } else  {
                            return 0;
                        }
                    }
                    break;
                case HASH_URL_FRAMESRC_HASH_RX:
                    if(em[i]->type == HASH_URL_FRAMESRC_HASH_RX)   {
                        rc = msc_regexec_capture(em[i]->param_data, link, strlen(link), ovector, 30, &my_error_msg);
#ifndef WITH_PCRE
                        if ((rc == PCRE2_ERROR_MATCHLIMIT) || (rc == PCRE2_ERROR_RECURSIONLIMIT)) {
#else
                        if ((rc == PCRE_ERROR_MATCHLIMIT) || (rc == PCRE_ERROR_RECURSIONLIMIT)) {
#endif
                            msc_string *s = (msc_string *)apr_pcalloc(msr->mp, sizeof(msc_string));

                            if (s == NULL) return -1;
                            s->name = apr_pstrdup(msr->mp, "MSC_PCRE_LIMITS_EXCEEDED");
                            if (s->name == NULL) return -1;
                            s->name_len = strlen(s->name);
                            s->value = apr_pstrdup(msr->mp, "1");
                            if (s->value == NULL) return -1;
                            s->value_len = 1;
                            apr_table_setn(msr->tx_vars, s->name, (void *)s);

                            error_msg = apr_psprintf(msr->mp,
                                    "Execution error - "
                                    "PCRE limits exceeded for Hash regex [%s] (%d): %s",
                                    em[i]->param,rc, my_error_msg);

                            if (msr->txcfg->debuglog_level >= 4)
                                msr_log(msr, 4, "%s.", error_msg);

                            return 0; /* No match. */
                        }
                        else if (rc < -1) {
                            error_msg = apr_psprintf(msr->mp, "Regex execution failed (%d): %s",
                                    rc, my_error_msg);
                            if (msr->txcfg->debuglog_level >= 4)
                                msr_log(msr, 4, "%s.", error_msg);
                            return -1;
                        }
#ifndef WITH_PCRE
                        if (rc != PCRE2_ERROR_NOMATCH) { /* Match. */
#else
                        if (rc != PCRE_ERROR_NOMATCH) { /* Match. */
#endif
                            return 1;
                        }
                    }
                    break;
            }
        }

    }
    return 0;
}

/**
 * \brief Hash the html elements
 *
 * \param msr ModSecurity transaction resource
 *
 * \retval 1 On success
 * \retval 0 No element was changed
 * \retval -1 On fail
 */
int hash_response_body_links(modsec_rec *msr)   {
    int lsize = 0, fsize = 0, lcount = 0, fcount = 0, i;
    int isize = 0, icount = 0, frsize = 0, frcount = 0;
    int bytes = 0;
    xmlXPathContextPtr  xpathCtx = NULL;
    xmlXPathObjectPtr   xpathObj = NULL;
    xmlChar *content_option = NULL;
    char *mac_link = NULL;
    int rc, elts = 0;

    if(msr == NULL)
        return -1;

    if (msr->crypto_html_tree == NULL) {
        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4, "hash_response_body_links: Cannot parse NULL html tree");
        return -1;
    }

    if(msr->txcfg->crypto_hash_href_rx == 0 && msr->txcfg->crypto_hash_href_pm == 0
            && msr->txcfg->crypto_hash_faction_rx == 0 && msr->txcfg->crypto_hash_faction_pm == 0
            && msr->txcfg->crypto_hash_iframesrc_rx == 0 && msr->txcfg->crypto_hash_iframesrc_pm == 0
            && msr->txcfg->crypto_hash_framesrc_rx == 0 && msr->txcfg->crypto_hash_framesrc_pm == 0)
        return -1;

    xpathCtx = xmlXPathNewContext(msr->crypto_html_tree);
    if(xpathCtx == NULL) {
        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4, "hash_response_body_links: Unable to create Xpath context.");
        goto ctx_error;
    }

    lcount=fcount=0;

    if(msr->txcfg->crypto_hash_href_rx == 1 || msr->txcfg->crypto_hash_href_pm == 1)    {

        xpathObj = xmlXPathEvalExpression((xmlChar*)"//*[@href]", xpathCtx);
        if(xpathObj == NULL) {
            if (msr->txcfg->debuglog_level >= 4)
                msr_log(msr, 4,
                        "hash_response_body_links: Unable to evaluate xpath expression.");
            goto obj_error;
        }

        lsize = (xpathObj->nodesetval) ? xpathObj->nodesetval->nodeNr : 0;
        for(i = lsize - 1; i >=0; i--) {
            register xmlNodePtr cur;

            cur = xpathObj->nodesetval->nodeTab[i];
            if(cur != NULL){
                xmlChar *href = xmlGetProp(cur, (const xmlChar *) "href");
                char *content_href = normalize_path(msr, (char *)href);

                if(content_href != NULL && strstr(content_href,msr->txcfg->crypto_param_name) == NULL) {
                    if(msr->txcfg->crypto_hash_href_rx == 1)    {
                        rc = do_hash_method(msr, (char *)content_href, HASH_URL_HREF_HASH_RX);
                        if(rc > 0)  {
                            mac_link = NULL;
                            mac_link = do_hash_link(msr, (char *)content_href, FULL_LINK);
                            if(mac_link != NULL) {
                                xmlSetProp(cur, (const xmlChar *) "href", (const xmlChar *) mac_link);
                                lcount++;
                                bytes += strlen(mac_link);
                                msr->of_stream_changed = 1;
                            }
                            mac_link = NULL;
                            if(href != NULL)
                                xmlFree(href);
                            continue;
                        }
                    }
                    if(msr->txcfg->crypto_hash_href_pm == 1)    {
                        rc = do_hash_method(msr, (char *)content_href, HASH_URL_HREF_HASH_PM);
                        if(rc > 0)  {
                            mac_link = NULL;
                            mac_link = do_hash_link(msr, (char *)content_href, FULL_LINK);
                            if(mac_link != NULL) {
                                xmlSetProp(cur, (const xmlChar *) "href", (const xmlChar *) mac_link);
                                lcount++;
                                bytes += strlen(mac_link);
                                msr->of_stream_changed = 1;
                            }
                            mac_link = NULL;
                            if(href != NULL)
                                xmlFree(href);
                            continue;
                        }
                    }
                }

                if(href != NULL)    {
                    xmlFree(href);
                    href = NULL;
                }
            }
        }

        if(xpathObj != NULL)
            xmlXPathFreeObject(xpathObj);
    }

    if(msr->txcfg->crypto_hash_faction_rx == 1 || msr->txcfg->crypto_hash_faction_pm == 1) {
        xpathObj = xmlXPathEvalExpression((xmlChar*)"//form", xpathCtx);
        if(xpathObj == NULL) {
            if (msr->txcfg->debuglog_level >= 4)
                msr_log(msr, 4,
                        "hash_response_body_links: Unable to evaluate xpath expression.");
            goto obj_error;
        }

        fsize = (xpathObj->nodesetval) ? xpathObj->nodesetval->nodeNr : 0;
        for(i = fsize - 1; i >=0; i--) {
            register xmlNodePtr cur;

            cur = xpathObj->nodesetval->nodeTab[i];
            if((cur != NULL)){
                xmlChar *action = NULL;
                char *content_action = NULL;

                if(content_option)
                    xmlFree(content_option);

                action = xmlGetProp(cur, (const xmlChar *) "action");
                content_action = normalize_path(msr, (char *)action);
                content_option = xmlGetProp(cur, (const xmlChar *) "option");

                if(content_action != NULL && content_option == NULL && strstr(content_action,msr->txcfg->crypto_param_name) == NULL) {
                    if(msr->txcfg->crypto_hash_faction_rx == 1) {
                        rc = do_hash_method(msr, (char *)content_action, HASH_URL_FACTION_HASH_RX);
                        if(rc > 0)  {
                            mac_link = NULL;
                            mac_link = do_hash_link(msr, (char *)content_action, FULL_LINK);
                            if(mac_link != NULL) {
                                xmlSetProp(cur, (const xmlChar *) "action", (const xmlChar *) mac_link);
                                fcount++;
                                bytes += strlen(mac_link);
                                msr->of_stream_changed = 1;
                            }
                            mac_link = NULL;
                            if(action != NULL)
                                xmlFree(action);
                            continue;
                        }
                    }
                    if(msr->txcfg->crypto_hash_faction_pm == 1) {
                        rc = do_hash_method(msr, (char *)content_action, HASH_URL_FACTION_HASH_PM);
                        if(rc > 0)  {
                            mac_link = NULL;
                            mac_link = do_hash_link(msr, (char *)content_action, FULL_LINK);
                            if(mac_link != NULL) {
                                xmlSetProp(cur, (const xmlChar *) "action", (const xmlChar *) mac_link);
                                fcount++;
                                bytes += strlen(mac_link);
                                msr->of_stream_changed = 1;
                            }
                            mac_link = NULL;
                            if(action != NULL)
                                xmlFree(action);
                            continue;
                        }
                    }
                }

                if(action != NULL)  {
                    xmlFree(action);
                    action = NULL;
                }

                if(content_option)  {
                    xmlFree(content_option);
                    content_option = NULL;
                }
            }
        }

        if(xpathObj != NULL)
            xmlXPathFreeObject(xpathObj);
    }

    if(msr->txcfg->crypto_hash_iframesrc_rx == 1 || msr->txcfg->crypto_hash_iframesrc_pm == 1) {
        xpathObj = xmlXPathEvalExpression((xmlChar*)"//iframe", xpathCtx);
        if(xpathObj == NULL) {
            if (msr->txcfg->debuglog_level >= 4)
                msr_log(msr, 4,
                        "hash_response_body_links: Unable to evaluate xpath expression.");
            goto obj_error;
        }

        isize = (xpathObj->nodesetval) ? xpathObj->nodesetval->nodeNr : 0;
        for(i = isize - 1; i >=0; i--) {
            register xmlNodePtr cur;

            cur = xpathObj->nodesetval->nodeTab[i];
            if((cur != NULL)){

                xmlChar *src = xmlGetProp(cur, (const xmlChar *) "src");
                char *content_src = normalize_path(msr, (char *)src);

                if(content_src != NULL && strstr(content_src,msr->txcfg->crypto_param_name) == NULL) {
                    if(msr->txcfg->crypto_hash_iframesrc_rx == 1) {
                        rc = do_hash_method(msr, (char *)content_src, HASH_URL_IFRAMESRC_HASH_RX);
                        if(rc > 0)  {
                            mac_link = NULL;
                            mac_link = do_hash_link(msr, (char *)content_src, FULL_LINK);
                            if(mac_link != NULL) {
                                xmlSetProp(cur, (const xmlChar *) "src", (const xmlChar *) mac_link);
                                icount++;
                                bytes += strlen(mac_link);
                                msr->of_stream_changed = 1;
                            }
                            mac_link = NULL;
                            if(src != NULL)
                                xmlFree(src);
                            continue;
                        }
                    }
                    if(msr->txcfg->crypto_hash_iframesrc_pm == 1) {
                        rc = do_hash_method(msr, (char *)content_src, HASH_URL_IFRAMESRC_HASH_PM);
                        if(rc > 0)  {
                            mac_link = NULL;
                            mac_link = do_hash_link(msr, (char *)content_src, FULL_LINK);
                            if(mac_link != NULL) {
                                xmlSetProp(cur, (const xmlChar *) "src", (const xmlChar *) mac_link);
                                icount++;
                                bytes += strlen(mac_link);
                                msr->of_stream_changed = 1;
                            }
                            mac_link = NULL;
                            if(src != NULL)
                                xmlFree(src);
                            continue;
                        }
                    }
                }

                if(src != NULL) {
                    xmlFree(src);
                    src = NULL;
                }
            }
        }

        if(xpathObj != NULL)
            xmlXPathFreeObject(xpathObj);
    }

    if(msr->txcfg->crypto_hash_framesrc_rx == 1 || msr->txcfg->crypto_hash_framesrc_pm == 1) {
        xpathObj = xmlXPathEvalExpression((xmlChar*)"//frame", xpathCtx);
        if(xpathObj == NULL) {
            if (msr->txcfg->debuglog_level >= 4)
                msr_log(msr, 4,
                        "hash_response_body_links: Unable to evaluate xpath expression.");
            goto obj_error;
        }

        frsize = (xpathObj->nodesetval) ? xpathObj->nodesetval->nodeNr : 0;
        for(i = frsize - 1; i >=0; i--) {
            register xmlNodePtr cur;

            cur = xpathObj->nodesetval->nodeTab[i];
            if((cur != NULL)){

                xmlChar *src = xmlGetProp(cur, (const xmlChar *) "src");
                char *content_src = normalize_path(msr, (char *)src);

                if(content_src != NULL && strstr(content_src,msr->txcfg->crypto_param_name) == NULL) {
                    if(msr->txcfg->crypto_hash_framesrc_rx == 1) {
                        rc = do_hash_method(msr, (char *)content_src, HASH_URL_FRAMESRC_HASH_RX);
                        if(rc > 0)  {
                            mac_link = NULL;
                            mac_link = do_hash_link(msr, (char *)content_src, FULL_LINK);
                            if(mac_link != NULL) {
                                xmlSetProp(cur, (const xmlChar *) "src", (const xmlChar *) mac_link);
                                frcount++;
                                bytes += strlen(mac_link);
                                msr->of_stream_changed = 1;
                            }
                            mac_link = NULL;
                            if(src != NULL)
                                xmlFree(src);
                            continue;
                        }
                    }
                    if(msr->txcfg->crypto_hash_framesrc_pm == 1) {
                        rc = do_hash_method(msr, (char *)content_src, HASH_URL_FRAMESRC_HASH_PM);
                        if(rc > 0)  {
                            mac_link = NULL;
                            mac_link = do_hash_link(msr, (char *)content_src, FULL_LINK);
                            if(mac_link != NULL) {
                                xmlSetProp(cur, (const xmlChar *) "src", (const xmlChar *) mac_link);
                                frcount++;
                                bytes += strlen(mac_link);
                                msr->of_stream_changed = 1;
                            }
                            mac_link = NULL;
                            if(src != NULL)
                                xmlFree(src);
                            continue;
                        }
                    }
                }

                if(src != NULL) {
                    xmlFree(src);
                    src = NULL;
                }
            }
        }

        if(xpathObj != NULL)
            xmlXPathFreeObject(xpathObj);
    }

    if(xpathCtx != NULL)
        xmlXPathFreeContext(xpathCtx);

    if (msr->txcfg->debuglog_level >= 4)    {
        msr_log(msr, 4, "hash_response_body_links: Processed [%d] iframe src, [%d] hashed.",isize, icount);
        msr_log(msr, 4, "hash_response_body_links: Processed [%d] frame src, [%d] hashed.",frsize, frcount);
        msr_log(msr, 4, "hash_response_body_links: Processed [%d] form actions, [%d] hashed.",fsize, fcount);
        msr_log(msr, 4, "hash_response_body_links: Processed [%d] links, [%d] hashed.",lsize, lcount);
    }

    if(msr->of_stream_changed == 0) {
        if(msr->crypto_html_tree != NULL)   {
            xmlFreeDoc(msr->crypto_html_tree);
            msr->crypto_html_tree = NULL;
        }
        return 0;
    }

    elts = (icount+frcount+fcount+lcount);

    if((elts >= INT32_MAX) || (elts < 0))
        return 0;

    return bytes;

obj_error:
    if(xpathCtx != NULL)
    xmlXPathFreeContext(xpathCtx);
ctx_error:
    return -1;
}

/**
 * \brief Inject the new response body
 *
 * \param msr ModSecurity transaction resource
 * \param elts Number of hashed elements
 *
 * \retval 1 On success
 * \retval -1 On fail
 */
int inject_hashed_response_body(modsec_rec *msr, int elts) {
    assert(msr != NULL);
    xmlOutputBufferPtr output_buf = NULL;
    xmlCharEncodingHandlerPtr  handler = NULL;
    char *p = NULL;
    const char *ctype = NULL;
    const char *encoding = NULL;
    char *new_ct = NULL, *content_value = NULL;

    if(msr == NULL) return -1;

    if(msr->r == NULL) return -1;

    if (msr->crypto_html_tree == NULL) {
        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4, "inject_hashed_response_body: Cannot parse NULL html tree");
        return -1;
    }

    if(msr->r->content_type != NULL)
        ctype = msr->r->content_type;

    encoding = (const char *) htmlGetMetaEncoding(msr->crypto_html_tree);

    if (ctype && encoding == NULL) {
        if (ctype && (p = m_strcasestr(ctype, "charset=") , p != NULL)) {
            p += 8 ;
            encoding = apr_pstrndup(msr->mp, p, strcspn(p, " ;"));
            handler = xmlFindCharEncodingHandler(encoding);
        }
    } else  {
        if(encoding != NULL)    {
            handler = xmlFindCharEncodingHandler(encoding);
        }
    }

    if (msr->txcfg->debuglog_level >= 4)
        msr_log(msr, 4, "inject_hashed_response_body: Detected encoding type [%s].",
                encoding ? encoding : "(none)");

    if (handler == NULL) {
        handler = xmlFindCharEncodingHandler("UTF-8");
        encoding = apr_pstrdup(msr->mp, "UTF-8");
    }
    if (handler == NULL) {
        handler = xmlFindCharEncodingHandler("ISO-8859-1");
        encoding = apr_pstrdup(msr->mp, "ISO-8859-1");
    }
    if (handler == NULL) {
        handler = xmlFindCharEncodingHandler("HTML");
        encoding = apr_pstrdup(msr->mp, "HTML");
    }
    if (handler == NULL) {
        handler = xmlFindCharEncodingHandler("ascii");
        encoding = apr_pstrdup(msr->mp, "ascii");
    }

    if(handler == NULL) {
        xmlFreeDoc(msr->crypto_html_tree);
        return -1;
    }

    apr_table_unset(msr->r->headers_out,"Content-Type");
    new_ct = (char*)apr_psprintf(msr->mp, "text/html;%s",encoding);
    apr_table_set(msr->r->err_headers_out,"Content-Type",new_ct);

    if (msr->txcfg->debuglog_level >= 4)
        msr_log(msr, 4, "inject_hashed_response_body: Using content-type [%s].", encoding);

    output_buf = xmlAllocOutputBuffer(handler);
    if (output_buf == NULL) {
        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4, "inject_hashed_response_body: Unable to allocate memory buffer.");
        xmlFreeDoc(msr->crypto_html_tree);
        return -1;
    }

    htmlDocContentDumpFormatOutput(output_buf, msr->crypto_html_tree, NULL, 0);
    // Not necessary in 2.9.4+
    //xmlOutputBufferFlush(output_buf);

#ifdef  LIBXML2_NEW_BUFFER

    if (output_buf->conv == NULL || (output_buf->conv && xmlOutputBufferGetSize(output_buf) == 0)) {

        if(output_buf->buffer == NULL || xmlOutputBufferGetSize(output_buf) == 0)  {
            xmlOutputBufferClose(output_buf);
            xmlFreeDoc(msr->crypto_html_tree);
            msr->of_stream_changed = 0;
            msr_log(msr, 4, "inject_hashed_response_body: NEW_BUFFER Output buffer is null.");
            return -1;
        }

        if(msr->stream_output_data != NULL) {
            free(msr->stream_output_data);
            msr->stream_output_data =  NULL;
        }

        msr->stream_output_length = xmlOutputBufferGetSize(output_buf);
        msr->stream_output_data = (char *)malloc(msr->stream_output_length+1);

        if (msr->stream_output_data == NULL) {
            xmlOutputBufferClose(output_buf);
            xmlFreeDoc(msr->crypto_html_tree);
            msr_log(msr, 4, "inject_hashed_response_body: NEW BUFFER Stream Output is null.");
            return -1;
        }

        memcpy(msr->stream_output_data, xmlOutputBufferGetContent(output_buf), msr->stream_output_length);
        msr->stream_output_data[msr->stream_output_length] = '\0';

        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4, "inject_hashed_response_body: Copying XML tree from CONTENT to stream buffer [%zu] bytes.", xmlOutputBufferGetSize(output_buf));

    } else {

        if(output_buf->conv == NULL || xmlOutputBufferGetSize(output_buf) == 0)  {
            xmlOutputBufferClose(output_buf);
            xmlFreeDoc(msr->crypto_html_tree);
            msr->of_stream_changed = 0;
            msr_log(msr, 4, "inject_hashed_response_body: Conv is null.");
            return -1;
        }

        if(msr->stream_output_data != NULL) {
            free(msr->stream_output_data);
            msr->stream_output_data =  NULL;
        }

        msr->stream_output_length = xmlOutputBufferGetSize(output_buf);
        msr->stream_output_data = (char *)malloc(msr->stream_output_length+1);

        if (msr->stream_output_data == NULL) {
            msr_log(msr, 4, "inject_hashed_response_body: Stream Output data is NULL.");
            xmlOutputBufferClose(output_buf);
            xmlFreeDoc(msr->crypto_html_tree);
            return -1;
        }

        memcpy(msr->stream_output_data, xmlOutputBufferGetContent(output_buf), msr->stream_output_length);
        msr->stream_output_data[msr->stream_output_length] = '\0';

        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4, "inject_hashed_response_body: Copying XML tree from CONV to stream buffer [%zu] bytes.", xmlOutputBufferGetSize(output_buf));

    }

#else

    if (output_buf->conv == NULL || (output_buf->conv && output_buf->conv->use == 0)) {

        if(output_buf->buffer == NULL || output_buf->buffer->use == 0)  {
            xmlOutputBufferClose(output_buf);
            xmlFreeDoc(msr->crypto_html_tree);
            msr_log(msr, 4, "inject_hashed_response_body: Output buffer is null.");
            msr->of_stream_changed = 0;
            return -1;
        }

        if(msr->stream_output_data != NULL) {
            free(msr->stream_output_data);
            msr->stream_output_data =  NULL;
        }

        msr->stream_output_length = output_buf->buffer->use;
        msr->stream_output_data = (char *)malloc(msr->stream_output_length+1);

        if (msr->stream_output_data == NULL) {
            xmlOutputBufferClose(output_buf);
            xmlFreeDoc(msr->crypto_html_tree);
            msr_log(msr, 4, "inject_hashed_response_body: Stream Output is null.");
            return -1;
        }

        memcpy(msr->stream_output_data, (char *)xmlBufferContent(output_buf->buffer), msr->stream_output_length);
        //memcpy(msr->stream_output_data, output_buf->buffer->content, msr->stream_output_length);
        msr->stream_output_data[msr->stream_output_length] = '\0';

        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4, "inject_hashed_response_body: Copying XML tree from CONTENT to stream buffer [%d] bytes.", msr->stream_output_length);

    } else {

        if(output_buf->conv == NULL || output_buf->conv->use == 0)  {
            xmlOutputBufferClose(output_buf);
            xmlFreeDoc(msr->crypto_html_tree);
            msr->of_stream_changed = 0;
            msr_log(msr, 4, "inject_hashed_response_body: Stream Output is null.");
            return -1;
        }

        if(msr->stream_output_data != NULL) {
            free(msr->stream_output_data);
            msr->stream_output_data =  NULL;
        }

        msr->stream_output_length = output_buf->conv->use;
        msr->stream_output_data = (char *)malloc(msr->stream_output_length+1);

        if (msr->stream_output_data == NULL) {
            xmlOutputBufferClose(output_buf);
            xmlFreeDoc(msr->crypto_html_tree);
            msr_log(msr, 4, "inject_hashed_response_body: Stream Output Data is null.");
            return -1;
        }

        memcpy(msr->stream_output_data, (char *)xmlBufferContent(output_buf->conv), msr->stream_output_length);
        //memcpy(msr->stream_output_data, output_buf->conv->content, msr->stream_output_length);
        msr->stream_output_data[msr->stream_output_length] = '\0';

        if (msr->txcfg->debuglog_level >= 4)
            msr_log(msr, 4, "inject_hashed_response_body: Copying XML tree from CONV to stream buffer [%d] bytes.", msr->stream_output_length);

    }

#endif

    xmlOutputBufferClose(output_buf);

    content_value = (char*)apr_psprintf(msr->mp, "%"APR_SIZE_T_FMT, msr->stream_output_length);
    apr_table_unset(msr->r->headers_out,"Content-Length");

    if (msr->txcfg->debuglog_level >= 4)
        msr_log(msr, 4, "inject_hashed_response_body: Setting new content value %s", content_value);
    apr_table_set(msr->r->headers_out, "Content-Length", content_value);

    xmlFreeDoc(msr->crypto_html_tree);

    if (msr->txcfg->debuglog_level >= 4)
        msr_log(msr, 4, "inject_hashed_response_body: Stream buffer [%"APR_SIZE_T_FMT"]. Done",msr->stream_output_length);

    return 1;
}

/**
 * \brief Parse and MAC html elements
 *
 * \param msr ModSecurity transaction resource
 * \param link The html attr value to be checked
 * \param type The hash method type
 *
 * \retval mac_link MACed link
 * \retval NULL on fail
 */
char *do_hash_link(modsec_rec *msr, char *link, int type)  {
    assert(msr != NULL);
    assert(link != NULL);
    char  *mac_link = NULL;
    char *path_chunk = NULL;
    char *hash_value = NULL;
    char *qm = NULL;

    if(strlen(link) > 7 && strncmp("http:",(char*)link,5)==0){
        path_chunk = strchr(link+7,'/');
        if(path_chunk != NULL)  {
            if (msr->txcfg->debuglog_level >= 4)    {
                msr_log(msr, 4, "Signing data [%s]", path_chunk+1);
                }

            if(msr->txcfg->crypto_key_add == HASH_KEYONLY)
#ifdef __NetBSD__
                hash_value =  mschmac(msr, msr->txcfg->crypto_key, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#else
                hash_value =  hmac(msr, msr->txcfg->crypto_key, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#endif
            if(msr->txcfg->crypto_key_add == HASH_SESSIONID)  {
                if(msr->sessionid == NULL || strlen(msr->sessionid) == 0)   {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
                    const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip);
#else
                    const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip);
#endif
                    if (msr->txcfg->debuglog_level >= 4)
                        msr_log(msr, 4, "Session id is empty. Using REMOTE_IP");
                    msr->txcfg->crypto_key_len = strlen(new_pwd);
#ifdef __NetBSD__
                    hash_value = mschmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#else
                    hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#endif
                } else {
                    const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->sessionid);
                    if (msr->txcfg->debuglog_level >= 4)
                        msr_log(msr, 4, "Using session id [%s]", msr->sessionid);
                    msr->txcfg->crypto_key_len = strlen(new_pwd);
#ifdef __NetBSD__
                    hash_value = mschmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#else
                    hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#endif
                }
            }

            if(msr->txcfg->crypto_key_add == HASH_REMOTEIP)   {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
                const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip);
#else
                const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip);
#endif
                msr->txcfg->crypto_key_len = strlen(new_pwd);
#ifdef __NetBSD__
                hash_value = mschmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#else
                hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#endif
            }
        } else  {
            return NULL;
        }
    } else
        if(strlen(link) > 8 && strncmp("https",(char*)link,5)==0){
            path_chunk = strchr(link+8,'/');
            if(path_chunk != NULL)  {
                if (msr->txcfg->debuglog_level >= 4)    {
                    msr_log(msr, 4, "Signing data [%s]", path_chunk+1);
                }

                if(msr->txcfg->crypto_key_add == HASH_KEYONLY)
#ifdef __NetBSD__
                    hash_value =  mschmac(msr, msr->txcfg->crypto_key, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#else
                    hash_value =  hmac(msr, msr->txcfg->crypto_key, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#endif
                if(msr->txcfg->crypto_key_add == HASH_SESSIONID)  {
                    if(msr->sessionid == NULL || strlen(msr->sessionid) == 0)   {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
                        const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip);
#else
                        const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip);
#endif
                        if (msr->txcfg->debuglog_level >= 4)
                            msr_log(msr, 4, "Session id is empty. Using REMOTE_IP");
                        msr->txcfg->crypto_key_len = strlen(new_pwd);
#ifdef __NetBSD__
                        hash_value = mschmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#else
                        hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#endif
                    } else {
                        const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->sessionid);
                        if (msr->txcfg->debuglog_level >= 4)
                            msr_log(msr, 4, "Using session id [%s]", msr->sessionid);
                        msr->txcfg->crypto_key_len = strlen(new_pwd);
#ifdef __NetBSD__
                        hash_value = mschmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#else
                        hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#endif
                    }
                }

                if(msr->txcfg->crypto_key_add == HASH_REMOTEIP)   {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
                    const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip);
#else
                    const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip);
#endif
                    msr->txcfg->crypto_key_len = strlen(new_pwd);
#ifdef __NetBSD__
                    hash_value = mschmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#else
                    hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) path_chunk+1, strlen((char*)path_chunk)-1);
#endif
                }
            } else  {
                return NULL;
            }
        }
        else if(*link=='/'){
            if (msr->txcfg->debuglog_level >= 4)    {
                msr_log(msr, 4, "Signing data [%s]", link+1);
                }

            if(msr->txcfg->crypto_key_add == HASH_KEYONLY)
#ifdef __NetBSD__
                hash_value = mschmac(msr, msr->txcfg->crypto_key, msr->txcfg->crypto_key_len, (unsigned char *) link+1, strlen((char*)link)-1);
#else
                hash_value = hmac(msr, msr->txcfg->crypto_key, msr->txcfg->crypto_key_len, (unsigned char *) link+1, strlen((char*)link)-1);
#endif
            if(msr->txcfg->crypto_key_add == HASH_SESSIONID)  {
                if(msr->sessionid == NULL || strlen(msr->sessionid) == 0)   {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
                    const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip);
#else
                    const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip);
#endif
                    if (msr->txcfg->debuglog_level >= 4)
                        msr_log(msr, 4, "Session id is empty. Using REMOTE_IP");
                    msr->txcfg->crypto_key_len = strlen(new_pwd);
#ifdef __NetBSD__
                    hash_value = mschmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) link+1, strlen((char*)link)-1);
#else
                    hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) link+1, strlen((char*)link)-1);
#endif
                } else  {
                    const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->sessionid);
                    if (msr->txcfg->debuglog_level >= 4)
                        msr_log(msr, 4, "Using session id [%s]", msr->sessionid);
                    msr->txcfg->crypto_key_len = strlen(new_pwd);
#ifdef __NetBSD__
                    hash_value = mschmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) link+1, strlen((char*)link)-1);
#else
                    hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) link+1, strlen((char*)link)-1);
#endif
                }
            }

            if(msr->txcfg->crypto_key_add == HASH_REMOTEIP)   {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
                const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip);
#else
                const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip);
#endif
                msr->txcfg->crypto_key_len = strlen(new_pwd);
#ifdef __NetBSD__
                hash_value = mschmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) link+1, strlen((char*)link)-1);
#else
                hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) link+1, strlen((char*)link)-1);
#endif
            }

        }
        else {
            char *relative_link = NULL;
            char *filename = NULL;
            char *relative_path = NULL;
            char *relative_uri = NULL;

            filename = file_basename(msr->mp, msr->r->parsed_uri.path);

            if(filename == NULL || (strlen(msr->r->parsed_uri.path) - strlen(filename) < 0))
                return NULL;

            relative_path = apr_pstrndup(msr->mp, msr->r->parsed_uri.path, strlen(msr->r->parsed_uri.path) - strlen(filename));
            relative_uri = apr_pstrcat(msr->mp, relative_path, link, NULL);

            relative_link = relative_uri+1;

            if (msr->txcfg->debuglog_level >= 4)    {
                msr_log(msr, 4, "Signing data [%s] size %zu", relative_link, strlen(relative_link));
                }

            if(msr->txcfg->crypto_key_add == HASH_KEYONLY)
#ifdef __NetBSD__
                hash_value = mschmac(msr, msr->txcfg->crypto_key, msr->txcfg->crypto_key_len, (unsigned char *) relative_link, strlen((char*)relative_link));
#else
                hash_value = hmac(msr, msr->txcfg->crypto_key, msr->txcfg->crypto_key_len, (unsigned char *) relative_link, strlen((char*)relative_link));
#endif

            if(msr->txcfg->crypto_key_add == HASH_SESSIONID)  {
                if(msr->sessionid == NULL || strlen(msr->sessionid) == 0)   {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
                    const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip);
#else
                    const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip);
#endif
                    if (msr->txcfg->debuglog_level >= 4)
                        msr_log(msr, 4, "Session id is empty. Using REMOTE_IP");
                    msr->txcfg->crypto_key_len = strlen(new_pwd);
#ifdef __NetBSD__
                    hash_value = mschmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) relative_link, strlen((char*)relative_link));
#else
                    hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) relative_link, strlen((char*)relative_link));
#endif
                } else {
                    const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->sessionid);
                    if (msr->txcfg->debuglog_level >= 4)
                        msr_log(msr, 4, "Using session id [%s]", msr->sessionid);
                    msr->txcfg->crypto_key_len = strlen(new_pwd);
#ifdef __NetBSD__
                    hash_value = mschmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) relative_link, strlen((char*)relative_link));
#else
                    hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) relative_link, strlen((char*)relative_link));
#endif
                }
            }

            if(msr->txcfg->crypto_key_add == HASH_REMOTEIP)   {
#if AP_SERVER_MAJORVERSION_NUMBER > 1 && AP_SERVER_MINORVERSION_NUMBER > 2
                const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->client_ip);
#else
                const char *new_pwd = apr_psprintf(msr->mp,"%s%s", msr->txcfg->crypto_key, msr->r->connection->remote_ip);
#endif
                msr->txcfg->crypto_key_len = strlen(new_pwd);
#ifdef __NetBSD__
                hash_value = mschmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) relative_link, strlen((char*)relative_link));
#else
                hash_value = hmac(msr, new_pwd, msr->txcfg->crypto_key_len, (unsigned char *) relative_link, strlen((char*)relative_link));
#endif
            }

        link = relative_uri;

        }

    if(hash_value == NULL) return NULL;

    if(type == HASH_ONLY)
        return hash_value;

    qm = strchr((char*)link,'?');
    if(qm == NULL){
        mac_link= (char*)apr_psprintf(msr->mp, "%s?%s=%s", link, msr->txcfg->crypto_param_name, (char *)hash_value);
    }
    else{
        mac_link= (char*)apr_psprintf(msr->mp, "%s&%s=%s", link, msr->txcfg->crypto_param_name, (char*)hash_value);
    }

    return mac_link;
}

/**
 * \brief Modify Location in case of status 302 and 301
 *
 * \param msr ModSecurity transaction resource
 *
 * \retval 1 On Success
 * \retval 0 on fail
 */
int modify_response_header(modsec_rec *msr) {
    char *mac_link = NULL;
    const char *location = NULL;
    int rc = 0;

    if(msr == NULL) return 0;

    if (msr->txcfg->debuglog_level >= 9)
        msr_log(msr, 4, "HTTP status (%d)", msr->response_status);

    if(msr->response_status != HTTP_MOVED_TEMPORARILY &&
            msr->response_status != HTTP_MOVED_PERMANENTLY)    {
        if (msr->txcfg->debuglog_level >= 9)
            msr_log(msr, 4, "Skipping status other than 302 an 301");
        return 0;
    }

    location = apr_table_get(msr->r->headers_out, "Location");

    if(location == NULL || strlen(location) == 0)
        return 0;

    if (msr->txcfg->debuglog_level >= 9)
        msr_log(msr, 4, "Processing reponse header location [%s]", location);

    if(msr->txcfg->crypto_hash_location_rx == 1) {
        rc = do_hash_method(msr, (char *)location, HASH_URL_LOCATION_HASH_RX);

        if(rc > 0)  {
            mac_link = NULL;
            mac_link = do_hash_link(msr, (char *)location, FULL_LINK);
        }   else    {
            return 0;
        }

    } else if(msr->txcfg->crypto_hash_location_pm == 1) {
        rc = do_hash_method(msr, (char *)location, HASH_URL_LOCATION_HASH_PM);

        if(rc > 0)  {
            mac_link = NULL;
            mac_link = do_hash_link(msr, (char *)location, FULL_LINK);
        }   else    {
            return 0;
        }

    }

    if(mac_link == NULL)
        return 0;

    if (msr->txcfg->debuglog_level >= 9)
        msr_log(msr, 4, "Setting new reponse header location [%s]", mac_link);

    if(rc > 0)  {
        apr_table_unset(msr->r->headers_out,"Location");
        apr_table_set(msr->r->headers_out, "Location",(char*)apr_psprintf(msr->mp,"%s", mac_link));
    }

    return 1;
}
