/*****************************************************************************
*    Open LiteSpeed is an open source HTTP server.                           *
*    Copyright (C) 2013 - 2022  LiteSpeed Technologies, Inc.                 *
*                                                                            *
*    This program is free software: you can redistribute it and/or modify    *
*    it under the terms of the GNU General Public License as published by    *
*    the Free Software Foundation, either version 3 of the License, or       *
*    (at your option) any later version.                                     *
*                                                                            *
*    This program is distributed in the hope that it will be useful,         *
*    but WITHOUT ANY WARRANTY; without even the implied warranty of          *
*    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the            *
*    GNU General Public License for more details.                            *
*                                                                            *
*    You should have received a copy of the GNU General Public License       *
*    along with this program. If not, see http://www.gnu.org/licenses/.      *
*****************************************************************************/
#include "rewriteengine.h"

#include <http/clientinfo.h>
#include <http/expression.h>
#include <http/handlertype.h>
#include <http/handlerfactory.h>
#include <http/httpcontext.h>
#include <http/httpresourcemanager.h>
#include <http/httpsession.h>
#include <http/mtsessdata.h>
#include <http/httpstatuscode.h>
#include <http/requestvars.h>
#include <http/rewritemap.h>
#include <http/rewriterule.h>
#include <http/rewriterulelist.h>
#include <log4cxx/logger.h>
#include <lsr/ls_fileio.h>
#include <util/accessdef.h>
#include <util/httputil.h>
#include <util/stringtool.h>

#include <main/configctx.h>
#include <extensions/proxy/proxyworker.h>
#include <extensions/proxy/proxyconfig.h>

#include <ctype.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <sys/mman.h>
#include <fcntl.h>


LS_SINGLETON(RewriteEngine);


RewriteEngine::RewriteEngine()
    : m_pSourceURL(NULL)
    , m_sourceURLLen(0)
    , m_pQS(NULL)
    , m_qsLen(0)
    , m_pOrgSourceURL(NULL)
    , m_orgSourceURLLen(0)
    , m_rewritten(0)
    , m_ruleMatches(0)
    , m_condMatches(0)
    , m_pDestURLLen(0)
    , m_iScriptLen(0)
    , m_iPathInfoLen(0)
    , m_iFilePathLen(0)
    , m_flag(0)
    , m_action(0)
    , m_statusCode(0)
    , m_logLevel(0)
    , m_pDestURL(NULL)
    , m_pCondBuf(NULL)
    , m_pFreeBuf(NULL)
    , m_pContext(NULL)
    , m_pBase(NULL)
    , m_pStrip(NULL)
    , m_pLastCondStr(NULL)
    , m_pLastTestStr(NULL)
    , m_lastTestStrLen(0)
    , m_noStat(0)
    , m_stripLen(0)
{
    memset(&m_st, 0, sizeof(m_st));
    memset(m_ruleVec, 0, sizeof(m_ruleVec));
    memset(m_condVec, 0, sizeof(m_condVec));
    memset(m_rewriteBuf, 0, sizeof(m_rewriteBuf));
    memset(m_qsBuf, 0, sizeof(m_qsBuf));
}


RewriteEngine::~RewriteEngine()
{
}

int RewriteEngine::loadRewriteFile(char *path, RewriteRuleList *pRuleList,
                                   const RewriteMapList *pMaps,
                                   HttpContext *pContext)
{
    char pathBuf[4096] = {0};

    /*
     * If path not start with $ and /, will add $DOC_ROOT to it
     * start with $ will be exapnded
     */
    if (*path != '/' )
    {
        AutoStr2 sPath = "";
        if (*path != '$' )
            sPath = "$DOC_ROOT/";
        sPath.append(path, strlen(path));
        ConfigCtx::getCurConfigCtx()->getAbsoluteFile(pathBuf, sPath.c_str());
        path = pathBuf;
    }

    int fd = open(path, O_RDONLY);
    if (fd == -1)
    {
        LS_INFO("Rewrite file [%s] can not open.", path);
        return LS_FAIL;
    }

    struct stat sb;
    if (fstat(fd, &sb) == -1) {
        LS_INFO("Rewrite file [%s] cannot be open.", path);
        close(fd);
        return LS_FAIL;
    }

    off_t len = sb.st_size;
    char *buf = new char[len + 1];
    char *p = buf;
    len = read(fd, buf, len);
    close(fd);
    buf[len] = 0;
    int ret = parseRules(p, pRuleList, pMaps, pContext);
    delete []buf;

    return ret;
}


int RewriteEngine::parseRules(char *&pRules, RewriteRuleList *pRuleList,
                              const RewriteMapList *pMapList,
                              HttpContext *pContext)
{
    int ret;
    int skip = 0;
    LinkedObj *pLast = pRuleList->tail();
    if (!pLast)
        pLast = pRuleList->head();
    while (*pRules)
    {
        while (isspace(*pRules))
            ++pRules;
        if (!*pRules)
            break;
        char *pLineEnd = strchr(pRules, '\n');
        if (strncasecmp(pRules, "<IfModule", 9) == 0)
        {
            if (!pLineEnd)
                break;
            if (skip > 0)
                ++skip;
            else
            {
                const char *p = pRules + 9;
                while(isspace(*p))
                    ++p;
                if (memmem(p, pLineEnd - p, "mod_headers", 11) != NULL)
                {
                    ++skip;
                    LS_DBG("Skip begin: '%.*s'",
                           (int)(pLineEnd - pRules), pRules);
                }
            }
            pRules = pLineEnd + 1;
            continue;

        }
        else if (strncasecmp(pRules, "</IfModule>", 11) == 0)
        {
            if (skip > 0)
            {
                --skip;
                if (skip == 0)
                    LS_DBG("Skip ends");
            }
            if (!pLineEnd)
                break;
            pRules = pLineEnd + 1;
            continue;
        }
        if (skip)
        {
            if (!pLineEnd)
                break;
            if (*pRules != '#' && pLineEnd > pRules)
                 LS_DBG("Skip: '%.*s'",
                        (int)(pLineEnd - pRules), pRules);
            pRules = pLineEnd + 1;
            continue;
        }
        if (((strncasecmp(pRules, "RewriteCond", 11) == 0) &&
             (isspace(*(pRules + 11)))) ||
            ((strncasecmp(pRules, "CacheKeyModify", 14) == 0) &&
             (isspace(*(pRules + 14)))) ||
            ((strncasecmp(pRules, "RewriteRule", 11) == 0) &&
             (isspace(*(pRules + 11)))))
        {
            RewriteRule *pRule = new RewriteRule();
            if (!pRule)
            {
                LS_ERR_NO_MEM("Malloc new RewriteRule() failed.\n");
                return LS_FAIL;
            }
            int ret = pRule->parse(pRules, pMapList);
            if (ret)
            {
                delete pRule;
                return 0;
            }
            pLast->addNext(pRule);
            pLast = pRule;
        }
        else
        {
            if (pLineEnd)
                *pLineEnd = 0;

            //Case of "RewriteFile" and "RewriteBase"
            int type = 0;
            if ( strlen(pRules) > 11 && (isspace(*(pRules + 11))) &&
                ((type = (strncasecmp(pRules, "RewriteFile", 11) == 0 ? 1 : 0))
                ||
                (type = (strncasecmp(pRules, "RewriteBase", 11) == 0 ? 2 : 0))))
            {
                pRules += 12;
                while (isspace(*pRules))
                    ++pRules;

                int len = strlen(pRules);
                while(len > 1 && isspace(pRules[len - 1]))
                {
                    pRules[len - 1] = 0x00;
                    --len;
                }
                if (type == 1)
                {
                    ret = loadRewriteFile(pRules, pRuleList, pMapList, pContext);
                    pLast = pRuleList->tail(); //Must update pLast, otherwise cause mess up
                    /**
                     * In case the .htaccess is empty. so that the pLast can be NULL
                     */
                    if (!pLast)
                        pLast = pRuleList->head();
                    LS_INFO("RewriteFile [%s] parsed, return %d.", pRules, ret);
                }
                else //type == 2
                {
                    pContext->setRewriteBase(pRules);
                    LS_INFO("RewriteBase [%s] parsed, set to HttpContext %p.",
                            pRules, pContext);
                }
            }
            else if (strncasecmp(pRules, "RewriteEngine", 13) == 0)
            {
                pRules += 14;
                while (isspace(*pRules))
                    ++pRules;

                int len = 0;
                if (pLineEnd)
                    len = pLineEnd - pRules;
                else
                    len = strlen(pRules);

                if (len >= 2 && strncasecmp(pRules, "on", 2) == 0)
                {
                    pContext->enableRewrite(1);
                }
                else if (len >= 3 && strncasecmp(pRules, "off", 3) == 0)
                {
                    pContext->enableRewrite(0);
                }
                else
                    LS_ERROR("Invalid value of RewriteEngine: %.*s", len, pRules);
            }
            else if (*pRules != '#')
                LS_INFO("Invalid rewrite directive: %s", pRules);

            if (pLineEnd)
                *pLineEnd = '\n';
            else
                break;

            pRules = pLineEnd + 1;
        }
    }
    return 0;
}



int RewriteEngine::appendUnparsedRule(AutoStr2 &sDirective,
                                      char *pBegin, char *pEnd)
{
    if (m_qsLen + sDirective.len() + (pEnd - pBegin) + 2 > REWRITE_BUF_SIZE *
        4)
        return LS_FAIL;
    char *pBuf = m_rewriteBuf[0];
    memmove(&pBuf[m_qsLen], sDirective.c_str(), sDirective.len());
    m_qsLen += sDirective.len();
    pBuf[m_qsLen++] = ' ';
    memmove(&pBuf[m_qsLen], pBegin, pEnd - pBegin);
    m_qsLen += pEnd - pBegin;
    pBuf[m_qsLen++] = '\n';
    pBuf[m_qsLen] = 0;
    return 0;
}


// int RewriteEngine::parseUnparsedRules(RewriteRuleList *pRuleList,
//                                       const RewriteMapList *pMapList)
// {
//     char *pRules = m_rewriteBuf[0];
//     int ret = parseRules(pRules, pRuleList, pMapList);
//     if (pRules - m_rewriteBuf[0] >= m_qsLen)
//         m_qsLen = 0;
//     else
//     {
//         memmove(m_rewriteBuf[0], pRules, m_qsLen - (pRules - m_rewriteBuf[0]));
//         m_qsLen -= pRules - m_rewriteBuf[0];
//     }
//     return ret;
//
// }


static const char s_hex[17] = "0123456789ABCDEF";


static char *escape_uri(char *p, const char *pURI, int uriLen, int bufLen,
                        int escape_flags, const char *no_escape)
{
    if (bufLen <= 1)
        return p;

    const char *pURIEnd = pURI + uriLen;
    char *pEnd = p + bufLen - 1;
    char ch;
    while (pURI < pURIEnd && p < pEnd)
    {
        ch = *pURI++;
        if (isalnum(ch) || (ch == '_') || (no_escape && strchr(no_escape, ch)))
            *p++ = ch;
        else if (ch == ' ' && !(escape_flags & RULE_FLAG_BNP))
            *p++ = '+';
        else
        {
            if (!(escape_flags & RULE_FLAG_BCTLS) || iscntrl(ch))
            {
                if (pEnd - p < 3)
                    break;
                *p++ = '%';
                *p++ = s_hex[((unsigned char)ch) >> 4 ];
                *p++ = s_hex[ ch & 0xf ];
            }
            else
                *p++ = ch;
        }
    }
    return p;
}


static int escape_unsafe_3f(char *pValue, int &valLen, char *pBufEnd)
{
    char *p = pValue;
    char *pEnd = pValue + valLen;
    int count = 0;

    while ((p = (char *)memchr(p, '?', pEnd - p)) != NULL)
    {
        ++count;
        ++p;
    }

    if (count == 0)
        return 0;

    if (pValue + valLen + count * 2 > pBufEnd)
        return LS_FAIL;

    p = pValue + valLen;
    char *pNewEnd = p + count * 2;
    while (pNewEnd > p)
    {
        --p;
        if (*p == '?')
        {
            *--pNewEnd = 'F';
            *--pNewEnd = '3';
            *--pNewEnd = '%';
        }
        else
            *--pNewEnd = *p;
    }
    valLen += count * 2;
    return 0;
}


static int getSubstr(const char *pSource, const int *ovector, int matches,
                     int i,
                     char *&pValue, int bufLen, int escape_flags,
                     const char *no_escape)
{
    if (i < matches)
    {
        const int *pParam = ovector + (i << 1);
        if (!escape_flags)
        {
            pValue = (char *)pSource + *pParam;
            return *(pParam + 1) - *pParam;
        }
        else
        {
            char *pEnd = pValue;
            pEnd = escape_uri(pEnd, (char *)pSource + *pParam,
                              *(pParam + 1) - *pParam, bufLen, escape_flags,
                              no_escape);
            return pEnd - pValue;
        }
    }
    return 0;
}


int RewriteEngine::getSubstValue(const RewriteSubstItem *pItem,
                                 HttpSession *pSession,
                                 char *&pValue, int bufLen)
{
    HttpReq *pReq = pSession->getReq();
    int type = pItem->getType();
    int i;
    if (type < REF_STRING)
    {
        pValue = (char *)pReq->getHeader(type);
        if (*pValue)
            return pReq->getHeaderLen(type);
        else
            return 0;
    }

    /*
        if ( type >= REF_TIME )
        {
            time_t t = time(NULL);
            struct tm *tm = localtime(&t);
            switch( type )
            {
            case REF_TIME:
                i = snprintf( pValue, bufLen,
                            "%04d%02d%02d%02d%02d%02d", tm->tm_year + 1900,
                            tm->tm_mon+1, tm->tm_mday,
                            tm->tm_hour, tm->tm_min, tm->tm_sec);
                break;
            case REF_TIME_YEAR:
                i = snprintf( pValue, bufLen, "%04d", tm->tm_year + 1900);
                break;
            case REF_TIME_MON:
                i = snprintf( pValue, bufLen, "%02d", tm->tm_mon+1 );
                break;
            case REF_TIME_DAY:
                i = snprintf( pValue, bufLen, "%02d", tm->tm_mday);
                break;
            case REF_TIME_HOUR:
                i = snprintf( pValue, bufLen, "%02d", tm->tm_hour);
                break;
            case REF_TIME_MIN:
                i = snprintf( pValue, bufLen, "%02d", tm->tm_min);
                break;
            case REF_TIME_SEC:
                i = snprintf( pValue, bufLen, "%02d", tm->tm_sec);
                break;
            case REF_TIME_WDAY:
                i = snprintf( pValue, bufLen, "%d", tm->tm_wday);
                break;
            default:
                return 0;
            }
            return i;
        }
    */
    switch (type)
    {
    case REF_STRING:
        pValue = (char *)pItem->getStr()->c_str();
        return pItem->getStr()->len();
    case REF_MAP:
        {
            MapRefItem *pRef = pItem->getMapRef();
            int len = 1024;
            char achBuf[1024];
            if (buildString(pRef->getKeyFormat(), pSession, achBuf, len) == NULL)
                return 0;
            if ((len = pRef->getMap()->lookup(achBuf, len, pValue, bufLen)) == -1)
            {
                if (pRef->getDefaultFormat())
                {
                    if (buildString(pRef->getDefaultFormat(), pSession,
                                    pValue, bufLen) == NULL)
                        return 0;
                    len = bufLen;
                }
                else
                    len = 0;
            }
            return len;
        }
        break;
    case REF_RULE_SUBSTR:
        return getSubstr(m_pSourceURL, m_ruleVec, m_ruleMatches, pItem->getIndex(),
                         pValue, bufLen, m_flag & RULE_FLAGS_ESCAPE, m_pNoEscape);
    case REF_COND_SUBSTR:
        return getSubstr(m_pCondBuf, m_condVec, m_condMatches, pItem->getIndex(),
                         pValue, bufLen, m_flag & RULE_FLAGS_ESCAPE, m_pNoEscape);
    case REF_ENV:
        pValue = (char *)RequestVars::getEnv(pSession, pItem->getStr()->c_str(),
                                             pItem->getStr()->len(), i);
        if (!pValue)
            i = 0;
        return i;
    case REF_HTTP_HEADER:
        pValue = (char *)pReq->getHeader(pItem->getStr()->c_str(),
                                         pItem->getStr()->len(), i);
        if (!pValue)
            i = 0;
        return i;
    case REF_REQUEST_FN:
    case REF_SCRIPTFILENAME:
        if (m_iScriptLen == -1)
        {
            pReq->checkPathInfo(m_pSourceURL, m_sourceURLLen, m_iFilePathLen,
                                m_iScriptLen, m_iPathInfoLen, m_pContext);
        }
        pValue = HttpResourceManager::getGlobalBuf();
        //if ( m_pStrip )
        //    return m_iFilePathLen;
        //else

        //   return m_iFilePathLen + m_iPathInfoLen;
        return m_iFilePathLen;
    case REF_PATH_INFO:
        if (m_iScriptLen == -1)
        {
            pReq->checkPathInfo(m_pSourceURL, m_sourceURLLen, m_iFilePathLen,
                                m_iScriptLen, m_iPathInfoLen, m_pContext);
        }
        pValue = &HttpResourceManager::getGlobalBuf()[ m_iFilePathLen ];
        return m_iPathInfoLen;
    case REF_SCRIPT_NAME:
        if (m_iScriptLen == -1)
        {
            pReq->checkPathInfo(m_pSourceURL, m_sourceURLLen, m_iFilePathLen,
                                m_iScriptLen, m_iPathInfoLen, m_pContext);
        }
        pValue = (char *)pReq->getOrgReqURL();
        return m_iScriptLen;
    case REF_REQ_URI:   //in rewrite rule, this does not include Query String part
        if (pReq->getRedirects() == 0)
        {
            pValue = (char *)pReq->getOrgReqURL();
            return pReq->getOrgReqURILen();
        }
        if (m_pStrip)
        {
            int totalLen = m_pStrip->len() + m_sourceURLLen;
            if (bufLen > 0)
            {
                int stripLen = m_pStrip->len();
                int copyLen = (stripLen < bufLen) ? stripLen : bufLen;
                if (copyLen > 0)
                    memmove(pValue, m_pStrip->c_str(), copyLen);
                if (copyLen < bufLen)
                {
                    int urlLen = m_sourceURLLen;
                    if (urlLen > bufLen - copyLen)
                        urlLen = bufLen - copyLen;
                    if (urlLen > 0)
                        memmove(pValue + copyLen, m_pSourceURL, urlLen);
                    copyLen += urlLen;
                }
                if (copyLen < bufLen)
                    pValue[copyLen] = 0;
            }
            return totalLen;
        }
    //fall through
    case REF_CUR_REWRITE_URI:
        pValue = (char *)m_pSourceURL;
        return m_sourceURLLen;
    case REF_QUERY_STRING:
        pValue = (char *)m_pQS;
        return m_qsLen;
    default:
        return RequestVars::getReqVar(pSession, type, pValue, bufLen);
    }
    return 0;
}


extern int transform_urlDecode(const char *org, int org_len, char *dest,
                               int dest_len, int *changed);


int RewriteEngine::appendSubst(const RewriteSubstItem *pItem,
                               HttpSession *pSession,
                               char *&pBegin, char *pBufEnd, int &esc_uri, int noDupSlash)
{
    char *pValue = pBegin;
    int valLen = getSubstValue(pItem, pSession, pValue, pBufEnd - pValue);
    if (valLen <= 0)
        return valLen;
    if (pBufEnd <= pBegin + valLen)
        return 0;
    if (pValue != pBegin)
    {
        if ((*pValue == '/') && (noDupSlash) && (pBegin[-1] == '/'))
            --pBegin;
        memmove(pBegin, pValue, valLen);
    }
    else
    {
        if ((*pValue == '/') && (noDupSlash) && (pBegin[-1] == '/'))
        {
            --valLen;
            memmove(pBegin, pBegin + 1, valLen);
        }
    }
    if (esc_uri && (valLen > 0))
    {
        if (!(m_flag & RULE_FLAG_UNSAFE_ALLOW3F)
            && pItem->needsUnsafe3FEscape())
        {
            if (escape_unsafe_3f(pBegin, valLen, pBufEnd) == LS_FAIL)
                return -1;
        }
        else
        {
            int len = valLen;
            char *pEnd = (char *)memchr(pBegin, '?', valLen);
            if (pEnd)
            {
                esc_uri = 0;
                len = pEnd - pBegin;
            }
            if ((len > 0) && (pItem->needUrlDecode()))
            {
                int c;
                //int l1 = transform_urlDecode( pBegin, len, pBegin, len, &c );
                int l1 = HttpUtil::unescape(pBegin, len, pBegin, len);
                if (l1 < 0)
                    return -1;
                c = len - l1;
                if (c > 0)
                {
                    if (len < valLen)
                        memmove(pBegin + l1, pBegin + len, valLen - len);
                    valLen -= c;
                }
            }
        }
    }
    pBegin += valLen;
    return 0;
}


char *RewriteEngine::buildString(const RewriteSubstFormat *pFormat,
                                 HttpSession *pSession,
                                 char *pBuf, int &len, int esc_uri, int noDupSlash)
{
    if (len <= 0)
    {
        len = 0;
        return NULL;
    }
    char *pBegin = pBuf;
    char *pBufEnd = pBuf + len - 1;
    const RewriteSubstItem *pItem = pFormat->begin();

//    if ( !pItem->next() )
//    {
//        int valLen = getSubstValue( pItem, pSession, pBegin, pBufEnd );
//        return valLen;
//        //only one variable, no need to copy to buffer
//    }

    while (pItem)
    {
        if (appendSubst(pItem, pSession, pBegin, pBufEnd, esc_uri,
                        (pBegin > pBuf) ? noDupSlash : 0) == -1)
            return NULL;
        pItem = (const RewriteSubstItem *)pItem->next();
    }
    *pBegin = 0;
    len = pBegin - pBuf;
    return pBuf;
}


int RewriteEngine::processCond(const RewriteCond *pCond,
                               HttpSession *pSession)
{
    int len = REWRITE_BUF_SIZE - 1;
    char *pTest;
    if (m_pLastCondStr
        && m_pLastCondStr->equal(* pCond->getTestStringFormat()))
    {
        pTest = m_pLastTestStr;
        len = m_lastTestStrLen;
    }
    else
    {
        pTest = buildString(pCond->getTestStringFormat(),
                            pSession, m_pFreeBuf, len, 1, 1);
        m_pLastCondStr = pCond->getTestStringFormat();
        m_pLastTestStr = pTest;
        m_lastTestStrLen = len;
        m_noStat = 1;
    }
    int ret = 0;
    if (!pTest)
        return LS_FAIL;
    int condVec[ MAX_REWRITE_MATCH * 3 ];
    int condMatches = 0;
    //m_condMatches = 0;
    int code = pCond->getOpcode();
    if (code == COND_OP_REGEX)
    {
        ret = pCond->getRegex()->exec(pTest, len, 0, 0, condVec,
                                      MAX_REWRITE_MATCH * 3);
        if (m_logLevel > 2)
            LS_INFO(pSession,
                    "[REWRITE] Cond: Match '%s' with pattern '%s', result: %d",
                    pTest, pCond->getPattern(), ret);
        if (ret == 0)
            condMatches = 10;
        else
            condMatches = ret;
        ret = (ret < 0);
    }
    else if (code == COND_OP_EXPR)
    {
        ExprRunTime runtime;
        ret = pCond->getExpr()->eval(pSession, &runtime, "Rewrite Condition");
        if (m_logLevel > 2)
            LS_INFO(pSession->getLogSession(),
                    "[REWRITE] Cond: expr '%s', result: %d",
                    pCond->getPattern(), ret);
        ret = (ret <= 0);
    }
    else if ((code >= COND_OP_LESS) && (code <= COND_OP_LE))
    {
        if (pCond->getFlag() & COND_FLAG_NOCASE)
            ret = strcasecmp(pTest, pCond->getPattern());
        else
            ret = strcmp(pTest, pCond->getPattern());
        if (m_logLevel > 2)
            LS_INFO(pSession,
                    "[REWRITE] Cond: String compare '%s' with pattern '%s', result: %d",
                    pTest, pCond->getPattern(), ret);
        switch (code)
        {
        case COND_OP_LESS:
            ret = (ret >= 0);
            break;
        case COND_OP_GREATER:
            ret = (ret <= 0);
            break;
        case COND_OP_EQ:
            ret = (ret != 0);
            break;
        case COND_OP_LE:
            ret = (ret > 0);
            break;
        case COND_OP_GE:
            ret = (ret < 0);
            break;
        }
    }
    else if ((code >= COND_OP_LESS_NUM) && (code <= COND_OP_NE_NUM))
    {
        long long test_num;
        long long pattern_num;
        test_num = strtoll(pTest, NULL, 10);
        pattern_num = strtoll(pCond->getPattern(), NULL, 10);
        long long comp_ret = test_num - pattern_num;
        if (m_logLevel > 2)
            LS_INFO(pSession->getLogSession(),
                    "[REWRITE] Cond: Numeric compare '%s'(%lld) with pattern '%s'(%lld), result: %lld",
                    pTest, test_num, pCond->getPattern(), pattern_num, comp_ret);
        switch (code)
        {
        case COND_OP_LESS_NUM:
            ret = (comp_ret >= 0);
            break;
        case COND_OP_GREATER_NUM:
            ret = (comp_ret <= 0);
            break;
        case COND_OP_EQ_NUM:
            ret = (comp_ret != 0);
            break;
        case COND_OP_LE_NUM:
            ret = (comp_ret > 0);
            break;
        case COND_OP_GE_NUM:
            ret = (comp_ret < 0);
            break;
        case COND_OP_NE_NUM:
            ret = (comp_ret == 0);
            break;
        }
    }
    else if ((code >= COND_OP_DIR) && (code <= COND_OP_FILE_ACC))
    {
        if (m_noStat == 1)
            m_noStat = pSession->getReq()->fileStat(pTest, &m_st);
        if (m_noStat == 0)
        {
            switch (code)
            {
            case COND_OP_DIR:
                ret = !S_ISDIR(m_st.st_mode);
                break;
            case COND_OP_FILE:
                ret = !S_ISREG(m_st.st_mode);
                break;
            case COND_OP_SIZE:
                ret = (!S_ISREG(m_st.st_mode) || (m_st.st_size == 0));
                break;
            case COND_OP_SYM:
                ret = !S_ISLNK(m_st.st_mode);
                break;
            case COND_OP_EXEC:
                ret = !(S_ISREG(m_st.st_mode) && m_st.st_mode & S_IXUSR);
                break;
            case COND_OP_FILE_ACC:
                ret = !S_ISREG(m_st.st_mode);
                break;
            default:
                ret = -1;
            }
            if (m_logLevel > 2)
                LS_INFO(pSession,
                        "[REWRITE] Cond: test '%s' with pattern '%s', result: %d",
                        pTest, pCond->getPattern(), ret);
        }
        else
        {
            if (m_logLevel > 2)
                LS_INFO(pSession,
                        "[REWRITE] stat( %s ) failed ", pTest);
            ret = -1;
        }

    }
    else if (code == COND_OP_URL_ACC)
    {
        //do nothing.
    }
    else
        assert(0);
    if (!(pCond->getFlag() & COND_FLAG_NOMATCH))
    {
        if (condMatches > 0)
        {
            if (pTest == m_pFreeBuf)
            {
                m_pFreeBuf = m_pCondBuf;
                m_pCondBuf = pTest;
            }
            m_condMatches = condMatches;
            memmove(m_condVec, condVec, condMatches * 3 * sizeof(int));
        }
        return ret;
    }
    else
        return !ret;

}


int RewriteEngine::processRule(const RewriteRule *pRule,
                               HttpSession *pSession, AutoStr2 &cacheCtlStr)
{
    m_ruleMatches = 0;
    int ret = pRule->getRegex()->exec(m_pSourceURL, m_sourceURLLen, 0,
                                      0, m_ruleVec, MAX_REWRITE_MATCH * 3);
    if (m_logLevel > 1)
        LS_INFO(pSession,
                "[REWRITE] Rule: Match '%s' with pattern '%s', result: %d",
                m_pSourceURL, pRule->getPattern(), ret);
    if (ret < 0)
    {
        if (!(pRule->getFlag() & RULE_FLAG_NOMATCH))
            return LS_FAIL;
    }
    else
    {
        if ((pRule->getFlag() & RULE_FLAG_NOMATCH))
            return LS_FAIL;
        if (ret == 0)
            m_ruleMatches = 10;
        else
            m_ruleMatches = ret;
    }
    m_pLastCondStr = NULL;
    m_condMatches = 0;
    const RewriteCond *pCond = pRule->getFirstCond();
    while (pCond)
    {
        if (processCond(pCond, pSession))
        {
            if (!((pCond->getFlag() & COND_FLAG_OR)
                //&& pCond->next()
                ))
                return LS_FAIL;
        }
        else
        {
            while ((pCond->getFlag() & COND_FLAG_OR) && (pCond->next()))
                pCond = (RewriteCond *)pCond->next();
        }
        pCond = (RewriteCond *)pCond->next();
    }
    return processRewrite(pRule, pSession, cacheCtlStr);
}


static int isAbsoluteURI(const char *pURI, int len)
{
    if ((*pURI == '/') || (len < 6))
        return 0;

    switch (*pURI++)
    {
    case 'f':
    case 'F':
        if (strncasecmp(pURI, "tp://", 5) == 0)
            return 6;
        break;
    case 'g':
    case 'G':
        if (strncasecmp(pURI, "opher://", 8) == 0)
            return 9;
        break;
    case 'h':
    case 'H':
        if (strncasecmp(pURI, "ttp://", 6) == 0)
            return 7;
        if (strncasecmp(pURI, "ttps://", 7) == 0)
            return 8;
        break;
    case 'm':
    case 'M':
        if (strncasecmp(pURI, "ailto:", 6) == 0)
            return 7;
        break;
    case 'n':
    case 'N':
        if (strncasecmp(pURI, "ews:", 4) == 0)
            return 5;
        if (strncasecmp(pURI, "ntp://", 6) == 0)
            return 7;
        break;
    case 'w':
    case 'W':
        if (strncasecmp(pURI, "s://", 4) == 0)
            return 5;
        if (strncasecmp(pURI, "ss://", 5) == 0)
            return 6;
        break;
    }
    return 0;
}


int RewriteEngine::processQueryString(HttpSession *pSession, int flag)
{
    char *pBuf = (char *)strchr(m_pSourceURL, '?');
    if (flag & RULE_FLAG_QSDISCARD)
    {
        m_pQS = m_qsBuf;
        m_qsLen = 0;
        m_qsBuf[m_qsLen] = 0;
        pSession->getReq()->orContextState(REWRITE_QSD);
    }
    if (!pBuf)
        return 0;
    *pBuf = 0;
    int n = m_sourceURLLen - 1 - (pBuf - m_pSourceURL);
    m_sourceURLLen = pBuf - m_pSourceURL;
    pBuf++;
    if (n > 0)
    {
        if (n > REWRITE_BUF_SIZE - 2)
            n = REWRITE_BUF_SIZE - 2;
        if (flag & RULE_FLAG_QSAPPEND)
        {
            if (REWRITE_BUF_SIZE < n + m_qsLen + 3)
            {
                m_qsLen = REWRITE_BUF_SIZE - n - 3;
                if (m_qsLen < 0)
                    m_qsLen = 0;
            }
            if (m_qsLen > 0)
            {
                memmove(&m_qsBuf[n + 1], m_pQS, m_qsLen);
                m_qsBuf[n] = '&';
                ++m_qsLen;
            }
            if (m_logLevel > 1)
                LS_INFO(pSession,
                        "[REWRITE] append query string '%s'", pBuf);
        }
        else
        {
            if (m_logLevel > 1)
                LS_INFO(pSession,
                        "[REWRITE] replace current query string with '%s'",
                        pBuf);
            m_qsLen = 0;
        }
        memmove(m_qsBuf, pBuf, n);
        m_qsLen = n + m_qsLen;
        m_pQS = m_qsBuf;
        if (m_qsBuf[m_qsLen - 1] == '&')
            --m_qsLen;
        m_qsBuf[m_qsLen] = 0;
    }
    else
    {
        if (!(flag & RULE_FLAG_QSAPPEND))
        {
            if (m_logLevel > 1)
                LS_INFO(pSession,
                        "[REWRITE] remove current query string");
            m_pQS = m_qsBuf;
            m_qsLen = 0;
            m_qsBuf[m_qsLen] = 0;
        }
    }
    return 0;
}


int RewriteEngine::setCookie(char *pBuf, int len, HttpSession *pSession)
{
    char *pName;
    char *pVal;
    char *pDomain;
    char *pPath;
    char *pSave;
    int secure = 0;
    int httponly = 0;
    int age = 0;
    pName = strtok_r(pBuf, ":", &pSave);
    pVal = strtok_r(NULL, ":", &pSave);
    pDomain = strtok_r(NULL, ":", &pSave);
    char *p = strtok_r(NULL, ":", &pSave);
    if (p)
        age = atoi(p);
    pPath = strtok_r(NULL, ":", &pSave);
    if (pPath)
    {
        if (*pPath != '/')
            pPath = NULL;
    }
    p = strtok_r(NULL, ":", &pSave);
    if (p)
    {
        secure = ((*p == '1') || (strncasecmp("true", p, 4) == 0)
                  || (strncasecmp("secure", p, 6) == 0));
    }
    p = strtok_r(NULL, ":", &pSave);
    if (p)
    {
        httponly = ((*p == '1') || (strncasecmp("true", p, 4) == 0)
                    || (strncasecmp("HttpOnly", p, 8) == 0));
    }
    pSession->getReq()->setCookie(pName, strlen(pName), pVal, strlen(pVal));
    MtSessData * psd = pSession->getMtSessData();
    if (psd)
        ls_mutex_lock(&psd->m_respHeaderLock);

    int ret = pSession->getResp()->addCookie(pName, pVal, pPath, pDomain, age,
                                          secure, httponly);
    if (psd)
        ls_mutex_unlock(&psd->m_respHeaderLock);

    return ret;
}


// EEF: Expand Env Flag
#define EEF_CACHE_KEY_MOD (1 << 0)


int RewriteEngine::expandEnv(const RewriteRule *pRule,
                 HttpSession *pSession, AutoStr2 &cacheCtlStr, int *eef_flags)
{
    RewriteSubstFormat *pEnv = pRule->getEnv()->begin();
    const char *pKey;
    const char *pKeyEnd;
    const char *pValue;
    const char *pValEnd;
    int len = REWRITE_BUF_SIZE;
    char achBuf[REWRITE_BUF_SIZE];
    if (!pEnv)
        return 0;
    for (; pEnv; pEnv = (RewriteSubstFormat *)pEnv->next())
    {
        len = REWRITE_BUF_SIZE - 1;
        buildString(pEnv, pSession, achBuf, len);
        if (pEnv->isCookie())
        {
            //TODO: enable it later
            setCookie(achBuf, len, pSession);
            continue;
        }
        pKeyEnd = strchr(achBuf, ':');
        if (pKeyEnd)
        {
            pKey = achBuf;
            pValue = pKeyEnd + 1;
            pValEnd = &achBuf[len];
            StringTool::strTrim(pKey, pKeyEnd);
            StringTool::strTrim(pValue, pValEnd);
            *(char *)pKeyEnd = 0;
            *(char *)pValEnd = 0;
        }
        else
        {
            pKey = achBuf;
            pKeyEnd = &achBuf[len];
            pValue = pValEnd = pKeyEnd;
            StringTool::strTrim(pKey, pKeyEnd);
        }

        int blockBot = 0;
        if (strcasecmp(pKey, "verifycaptcha") == 0)
        {
            int ret = pSession->shouldAvoidRecaptcha();
            if (true == ret)
            {
                LS_DBG(pSession,
                    "[REWRITE] client is not subject to captcha verification");
                continue;
            }
            else
            {
                LS_DBG(pSession,
                        "[REWRITE] Verify against recaptcha!");
                int mode = 0;
                if (strcasecmp(pValue, "deny") == 0)
                    mode = 1;
                else if (strcasecmp(pValue, "drop") == 0)
                    mode = 2;

                ret = pSession->rewriteToRecaptcha(mode, HSPS_RESTART_PROCESS,
                                                "Rewrite");
                if ( ret != 0)
                {
                    if (ret == SC_403)
                    {
                        m_statusCode = SC_403;
                        m_action = RULE_ACTION_FORBID;
                    }
                    if (ret == LSI_SUSPEND)
                        return URL_RECAP_SUSPEND;
                    else if (ret == 1)
                        return URL_RECAP_REWRITE;
                    return URL_REWRITE_ABORT;
                }
            }
        }
        else if (strcasecmp(pKey, "blockbot") == 0)
        {
            LS_INFO(pSession,
                    "[REWRITE] detect bad robot, block!");
            blockBot = 1;
        }
        if (blockBot)
        {
            if (pSession->getClientInfo()->markAsBot(
                pSession->getReq()->getVhostName(), BOT_REWRITE_RULE) == 0)
            {
                pSession->getReq()->setStatusCode(SC_403);
                pSession->dropConnection();
                return URL_REWRITE_ABORT;
            }
        }
        if ((pRule->getAction() == RULE_ACTION_PROXY) &&
            (strcasecmp(pKey, "Proxy-Host") == 0))
        {
            pSession->getReq()->setNewHost(pValue,
                                            pValEnd - pValue);
            if (m_logLevel > 4)
                LS_INFO(pSession,
                        "[REWRITE] Set Proxy Host header to: '%s' ",
                        pValue);
        }
        else
        {
            bool needSet = true;
            if (strncasecmp(pKey, "cache-", 6) == 0)
            {
                if ((strcasecmp(pKey + 6, "control") == 0) ||
                    (strcasecmp(pKey + 6, "ctrl") == 0))
                {
                    if (strncasecmp(pValue, "vary=", 5) == 0)
                    {
                        pValue += 5;
                        if (m_logLevel > 4)
                            LS_INFO(pSession,
                                    "[REWRITE] set cache vary value: '%s'",
                                    pValue);
                        RequestVars::setEnv(pSession, "LSCACHE_VARY_VALUE",
                                            18, pValue, pValEnd - pValue);
                        //recover the pValue which need "vary="
                        pValue -= 5;
                    }
                    else if (pValEnd > pValue)
                    {
                        if (cacheCtlStr.len() > 0)
                            cacheCtlStr.append(",", 1);
                        cacheCtlStr.append(pValue, pValEnd - pValue);
                        needSet = false;
                    }
                }
                else if (strcasecmp(pKey + 6, "Vary") == 0)
                {
                    if (m_logLevel > 4)
                        LS_INFO(pSession,
                                "[REWRITE] set cache vary on: '%s'", pValue);
                    RequestVars::setEnv(pSession, "LSCACHE_VARY_COOKIE", 19,
                                        pValue, pValEnd - pValue);
                }
                else if (strcasecmp(pKey + 6, "Key-Mod") == 0)
                {
                    RequestVars::setEnv(pSession, "cache-key-mod", 13,
                        pValue, pValEnd - pValue);
                    needSet = false;
                    *eef_flags |= EEF_CACHE_KEY_MOD;
                }
            }
            else if (strcasecmp(pKey, "http-prio") == 0)
            {
                if (*pValue < '0' || *pValue > '7')
                {
                    if (m_logLevel > 4)
                        LS_INFO(pSession,
                                "[REWRITE] Bad HTTP priority value '%.*s', must between 0 - 7.",
                                (int)(pValEnd - pValue), pValue);
                }
                else
                {
                    if (pValEnd > pValue + 1 && *(pValue + 1) == 'i')
                        pSession->getStream()->setFlag(SS_FLAG_PRI_INCREMENTAL, 1);
                    pSession->getStream()->setPriority(*pValue - '0');
                }
            }

            if (needSet)
            {
                RequestVars::setEnv(pSession, pKey, pKeyEnd - pKey, pValue,
                                    pValEnd - pValue);
                if (m_logLevel > 4)
                    LS_INFO(pSession,
                            "[REWRITE] add ENV: '%s:%s' ", pKey, pValue);
            }
        }
    }

    return 0;
}


int RewriteEngine::processRewrite(const RewriteRule *pRule,
                                  HttpSession *pSession, AutoStr2 &cacheCtlStr)
{
    char *pBuf;
    int flag = pRule->getFlag();
    int eef_flags = 0;
    expandEnv(pRule, pSession, cacheCtlStr, &eef_flags);
    m_rewritten |= 1;
    if (eef_flags & EEF_CACHE_KEY_MOD)
        // This environment variable is only used by the callback set in the
        // cache module.  Do not pass it along.
        RequestVars::setEnv(pSession, "!cache-key-mod", 14, "", 0);
    if (!(flag & RULE_FLAG_NOREWRITE))
    {
        int len = REWRITE_BUF_SIZE - 1;
        pBuf = m_pDestURL;
        m_pDestURL = m_pFreeBuf;
        m_pFreeBuf = pBuf;
        m_flag = flag;
        if (m_flag & RULE_FLAG_BNE)
            m_pNoEscape = pRule->getNoEscape();
        else
            m_pNoEscape = NULL;
        pBuf = buildString(pRule->getTargetFmt(), pSession, m_pDestURL, len, 1, 1);
        // log rewrite result here
        if (!pBuf)
        {
            if (m_logLevel > 0)
                LS_ERROR(pSession,
                         "[REWRITE] Failed to build the target URI");
            return LS_FAIL;
        }

//         if ( strchr( pBuf, '%' ) )
//         {
//             int c;
//             int l, l1;
//             char * pEnd = strchr( pBuf, '?' );
//             if ( pEnd )
//                 l = pEnd - pBuf;
//             else
//                 l = len;
//             l1 = transform_urlDecode( pBuf, l, pBuf, l, &c );
//             len -= l - l1;
//         }

        if (m_logLevel > 0)
            LS_INFO(pSession,
                    "[REWRITE] Source URI: '%s' => Result URI: '%s'",
                    m_pSourceURL, pBuf);
        m_rewritten |= 2;
        m_pOrgSourceURL = m_pSourceURL;
        m_orgSourceURLLen = m_sourceURLLen;
        m_pSourceURL = pBuf;
        m_sourceURLLen = len;
        m_iScriptLen = -1;
        m_iPathInfoLen = 0;
        if (flag & (RULE_FLAG_WITHQS | RULE_FLAG_QSDISCARD))
            processQueryString(pSession, flag);
        const char *pCode;
        if (!m_statusCode)
            pCode = "200";
        else
            pCode = HttpStatusCode::getInstance().getCodeString(m_statusCode);
        pSession->getReq()->addEnv("REDIRECT_STATUS", 15, pCode, 3);
    }
    else if (m_logLevel > 0)
        LS_INFO(pSession, "[REWRITE] No substition");

    if (pRule->getAction() != RULE_ACTION_NONE)
    {
        m_action = pRule->getAction();
        m_statusCode = pRule->getStatusCode();
        return 0;
    }

    if (pRule->getMimeType())
    {
        if (m_logLevel > 4)
            LS_INFO(pSession,
                    "[REWRITE] set forced type: '%s'", pRule->getMimeType());
        pSession->getReq()->setForcedType(pRule->getMimeType());
    }
    return 0;
}


const RewriteRule *RewriteEngine::getNextRule(const RewriteRule *pRule,
        const HttpContext *&pContext, const HttpContext *&pRootContext)
{
    const RewriteRule *pNext = NULL;
    if (pRule)
        pNext = (const RewriteRule *) pRule->next();
    if (pNext)
        return pNext;
    if (!pContext
        && pRootContext)      //special case for inherit global rewrite rules
    {
        pContext = pRootContext;
        pRootContext = pContext->getParent();
        const RewriteRuleList *pList = pContext->getRewriteRules();
        if (pList)
            return pList->begin();
        return NULL;
    }
    while ((pContext && (pContext->getConfigBits() & BIT_REWRITE_INHERIT)) &&
           (pContext->getParent() != pRootContext))
    {
        pContext = pContext->getParent();
        if (!pContext->hasRewriteConfig())
            continue;
        const RewriteRuleList *pList = pContext->getRewriteRules();
        if (pList)
        {
            pNext = pList->begin();
            break;
        }
    }
    return pNext;
}


int RewriteEngine::processRuleSet(const RewriteRuleList *pRuleList,
                                  HttpSession *pSession,
                                  const HttpContext *pContext, const HttpContext *pRootContext)
{
    const RewriteRule *pRule = NULL;
    int loopCount = 0;
    int flag = 0;
    int ret;
    m_pContext = pContext;
    if (pRuleList)
        pRule = pRuleList->begin();
    else
        pRule = getNextRule(NULL, pContext, pRootContext);
    if (!pRule)
        return 0;
    HttpReq *pReq = pSession->getReq();
    const AutoStr2 *pBase = NULL;
    AutoStr2    sStrip;
    m_rewritten = 0;
    //initialize rewrite engine
    //strip prefix aka. RewriteBase
    m_logLevel = pReq->getRewriteLogLevel();
    m_pSourceURL = pReq->getURI();
    m_sourceURLLen = pReq->getURILen();
    m_pStrip = m_pBase = NULL;
    m_iScriptLen = -1;
    m_iPathInfoLen = 0;
    if (m_pContext)
    {
        pBase = m_pContext->getContextURI();
        if ((pBase) &&
            (strncmp(m_pSourceURL, pBase->c_str(), pBase->len()) == 0))
            m_pStrip = m_pBase = pBase;
        else
        {
            m_pBase = pBase = m_pContext->getRewriteBase();
            if ((pBase) &&
                (strncmp(m_pSourceURL, pBase->c_str(), pBase->len()) == 0))
                m_pStrip = m_pBase = pBase;

        }

        if (m_pContext->getRewriteBase())
            m_pBase = m_pContext->getRewriteBase();
        if (m_pStrip)
        {
            if (m_logLevel > 4)
                LS_INFO(pSession,
                        "[REWRITE] strip base: '%s' from URI: '%s'",
                        m_pStrip->c_str(), m_pSourceURL);
            m_pSourceURL += m_pStrip->len();
            m_sourceURLLen -= m_pStrip->len();
        }
        else
        {
            if (pSession->getReq()->isMatched())
            {
                const char *pURL;
                int len;
                pSession->getReq()->stripRewriteBase(m_pContext,
                                                     pURL, len);
                if ((len < m_sourceURLLen) && (strncmp(
                                                   m_pSourceURL + m_sourceURLLen - len, pURL, len) == 0))
                {
                    sStrip.setStr(m_pSourceURL, m_sourceURLLen - len);
                    m_pStrip = &sStrip;
                    if (!m_pBase)
                        m_pBase = m_pStrip;
                }
                m_pSourceURL = pURL;
                m_sourceURLLen = len;
            }
        }
    }

    m_pQS = pReq->getQueryString();
    m_qsLen = pReq->getQueryStringLen();

    m_pOrgSourceURL = m_pSourceURL;
    m_orgSourceURLLen = m_sourceURLLen;

    m_condMatches = 0;
    m_pDestURLLen = 0;
    m_pDestURL = m_rewriteBuf[0];
    m_pCondBuf = m_rewriteBuf[1];
    m_pFreeBuf = m_rewriteBuf[2];
    m_action   = RULE_ACTION_NONE;
    m_flag     = 0;
    m_statusCode = 0;
    AutoStr2 cacheCtlStr = "";


    while (pRule)
    {
        flag = pRule->getFlag();
//        if (( flag & RULE_FLAG_NOSUBREQ )&&( pReq->isSubReq() > 0 ))
//            ret = -1;
//        else
        ret = processRule(pRule, pSession, cacheCtlStr);
        if (pSession->isDropConnection())
            return SC_403;
        if (ret)
        {
            pRule = getNextRule(pRule, pContext, pRootContext);
            while (pRule && (flag & RULE_FLAG_CHAIN))
            {
                if (m_logLevel > 5)
                    LS_INFO(pSession,
                            "[REWRITE] skip chained rule: '%s'",
                            pRule->getPattern());
                flag = pRule->getFlag();
                pRule = getNextRule(pRule, pContext, pRootContext);
                //(const RewriteRule *) pRule->next();
            }
            continue;
        }
        if ((flag & RULE_FLAG_LAST) && !pRule->getSkip())
        {
            if (m_logLevel > 5)
                LS_INFO(pSession,
                        "[REWRITE] Last Rule, stop!");
            if (flag & RULE_FLAG_END)
            {
                if (m_logLevel > 5)
                    LS_INFO(pSession,
                            "[REWRITE] End rewrite!");
                pSession->getReq()->orContextState(SKIP_REWRITE);
            }
            break;
        }
NEXT_RULE:
        if (flag & RULE_FLAG_NEXT)
        {
            pContext = m_pContext;
            if (pRuleList)
                pRule = pRuleList->begin();
            else
                pRule = getNextRule(NULL, pContext, pRootContext);
            if (++loopCount > 10)
            {
                LS_ERROR(pSession,
                         "[REWRITE] Rules loop 10 times, possible infinite loop!");
                break;
            }
            if (m_logLevel > 5)
                LS_INFO(pSession,
                        "[REWRITE] Next round, restart from the first rule");
            continue;
        }
        if (!pRule)
            break;
        int n = pRule->getSkip() + 1;
        if ((n > 1) && (m_logLevel > 5))
            LS_INFO(pSession, "[REWRITE] skip next %d rules",
                    n - 1);
        while (pRule && n > 0)
        {
            pRule = getNextRule(pRule, pContext, pRootContext);
            //(const RewriteRule *) pRule->next();
            --n;
        }
    }

    if (cacheCtlStr.len() > 0)
    {
        if (m_logLevel > 4)
            LS_INFO(pSession,
                    "[REWRITE] apply cache-control: '%s'.", cacheCtlStr.c_str());
        RequestVars::setEnv(pSession, "cache-control", 13, cacheCtlStr.c_str(),
                            cacheCtlStr.len());
    }

    if (m_rewritten)
    {
        if ((m_action == RULE_ACTION_FORBID) ||
            (m_action == RULE_ACTION_GONE))
            return m_statusCode;
        if (m_rewritten & 2)
        {
            //set the final URL and query string
            if (! isAbsoluteURI(m_pSourceURL, m_sourceURLLen))
            {
                if (*m_pSourceURL != '/')
                {
                    // add missing prefix (RewriteBase)
                    char *pBuf = m_pFreeBuf;
                    int baseLen;
                    if (!m_pBase)
                    {
                        baseLen   = 1;
                        *pBuf = '/';
                    }
                    else
                    {
                        baseLen = m_pBase->len();
                        memmove(pBuf, m_pBase->c_str(), baseLen);
                    }
                    if (m_sourceURLLen > REWRITE_BUF_SIZE - 1 - baseLen)
                        m_sourceURLLen = REWRITE_BUF_SIZE - 1 - baseLen;
                    memmove(pBuf + baseLen, m_pSourceURL, m_sourceURLLen);
                    m_pFreeBuf = (char *)m_pSourceURL;
                    m_pSourceURL = pBuf;
                    m_sourceURLLen += baseLen;
                    pBuf[m_sourceURLLen] = 0;
                    if ((m_logLevel > 4) && (m_pBase))
                        LS_INFO(pSession,
                                "[REWRITE] prepend rewrite base: '%s', final URI: '%s'",
                                m_pBase->c_str(), m_pSourceURL);
                }
            }
            else if (m_action == RULE_ACTION_NONE)
            {
                m_action = RULE_ACTION_REDIRECT;
                m_statusCode = SC_302;
            }
            if (m_action == RULE_ACTION_NONE)
            {
                if (!pReq->getRedirects() || pBase)
                {
                    ret = pReq->saveCurURL();
                    if (ret)
                        return ret;
                }
                if (m_pQS == m_qsBuf)
                    pReq->setRewriteQueryString(m_pQS, m_qsLen);
                m_statusCode = -3;  //rewritten to another url
            }
            else if (m_action == RULE_ACTION_REDIRECT)
            {
                if (pReq->detectLoopRedirect((char *)m_pSourceURL, m_sourceURLLen,
                                             m_pQS, m_qsLen, pSession->isHttps()) == 0)
                {
                    pReq->setRewriteLocation((char *)m_pSourceURL, m_sourceURLLen,
                                             m_pQS, m_qsLen, (m_flag & RULE_FLAG_NOESCAPE) == 0);
                    pReq->orContextState(REWRITE_REDIR);
                }
                else
                {
                    LS_INFO(pSession,
                            "[REWRITE] detect external loop redirection with target URL: %s, skip.",
                            m_pSourceURL);
                    m_rewritten = m_statusCode = 0;
                    m_pSourceURL = m_pOrgSourceURL;
                    m_sourceURLLen = m_orgSourceURLLen ;

                    goto NEXT_RULE;
                }
            }
            else if (m_action == RULE_ACTION_PROXY)
            {
                int https = 0;
                if (strncasecmp(m_pSourceURL, "https://", 8) == 0)
                    https = 1;
                else if (strncasecmp(m_pSourceURL, "http://", 7) != 0)
                {
                    if (strncasecmp(m_pSourceURL, "ws://", 5) == 0
                        || strncasecmp(m_pSourceURL, "wss://", 6) == 0)
                    {
                        return URL_HANDLER_WEBSOCKET;
                    }
                    LS_ERROR(pSession, "[REWRITE] Absolute URL with leading 'http://' or 'https://' is "
                             "required for proxy, URL: %s", m_pSourceURL);
                    return SC_500;
                }
                char *pHost = (char *)m_pSourceURL + 7 + https;
                char *pHostEnd = strchr(pHost, '/');
                if ((!pHostEnd) || (pHostEnd == pHost))
                {
                    LS_ERROR(pSession, "[REWRITE] Can not determine proxy host name");
                    return SC_500;
                }
                *pHostEnd = 0;
                const HttpHandler *pHandler = HandlerFactory::getInstance(
                                                  HandlerType::HT_PROXY, pHost);
                if (!pHandler)
                {
                    LS_ERROR(pSession, "[REWRITE] Proxy target is not defined on "
                             "external application list, please add a 'web server'"
                             " with name '%s'", pHost);
                    return SC_500;
                }
                if (https && !((ProxyWorker *)pHandler)->getConfig().getSsl())
                {
                    LS_ERROR(pSession, "[REWRITE] Rewrite target require HTTPS, Proxy target is not HTTPS, "
                             "please change 'web server' '%s' using https://... address "
                             , pHost);
                    return SC_500;

                }
                *pHostEnd = '/';
                pReq->setHandler(pHandler);

                //TODO: change request header
                ret = pReq->internalRedirectURI(pHostEnd,
                                                m_pSourceURL + m_sourceURLLen - pHostEnd, 0,
                                                m_flag & RULE_FLAG_NOESCAPE);
                if (ret)
                    return SC_500;

                if (m_pQS == m_qsBuf)
                    pReq->setRewriteQueryString(m_pQS, m_qsLen);
            }
        }
        else if (m_action == RULE_ACTION_REDIRECT)
        {
            if ((m_statusCode >= SC_301) && (m_statusCode < SC_400))
                return 0;
        }
        else if (m_action == RULE_ACTION_PROXY)
            m_statusCode = 0;
        return m_statusCode;
    }
    return 0;
}
