/*****************************************************************************
*    Open LiteSpeed is an open source HTTP server.                           *
*    Copyright (C) 2013 - 2018  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 "cacheconfig.h"
#include "cachectrl.h"
#include "cacheentry.h"
#include "cachehash.h"
#include "dirhashcachestore.h"

#include <limits.h>
#include <ls.h>
#include <lsr/ls_confparser.h>
#include <util/autostr.h>
#include <util/datetime.h>
#include <util/stringtool.h>
#include <util/ni_fio.h>

#include <errno.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <unistd.h>
#include <util/gpath.h>

#include <http/httpserverconfig.h>
#include <http/httpreq.h>
#include <http/httpheader.h>
#include <http/httpsession.h>
#include <http/httpvhost.h>
#include <http/httpmime.h>
#include <http/serverprocessconfig.h>
#include <util/autostr.h>
#include <sys/uio.h>
#include <zlib.h>



#define MAX_CACHE_CONTROL_LENGTH    128
#define MAX_RESP_HEADERS_NUMBER     50
#define MNAME                       cache
#define ModuleNameStr               "CACHE"
#define CACHEMODULEKEY              "_lsi_module_cache_handler__"
#define CACHEMODULEKEYLEN           (sizeof(CACHEMODULEKEY) - 1)
#define CACHEMODULEROOT             "cachedata/"

#define MODULE_VERSION_INFO         "1.66"

//The below info should be gotten from the configuration file
#define max_file_len        4096
#define VALMAXSIZE          4096
#define MAX_HEADER_LEN      16384
#define Z_BUF_SIZE          16384
#define CACHE_STORE_PATH_RESERVE (5 /* priv/ */ + 6 /* x/x/x/ */ \
                                  + 2 * HASH_KEY_LEN + 4 /* .tmp */ + 1)

/////////////////////////////////////////////////////////////////////////////
extern lsi_module_t MNAME;

static const char s_x_cached[] = "X-LiteSpeed-Cache";
static const char *s_hits[3] = { "hit", "hit,private", "hit,litemage" };
static int s_hitsLen[3] = { 3, 11, 12 };

static const char *cache_env_key = "cache-control";
static int icache_env_key = 13;

/**
 * if s_x_vary is set in resp header, ENV "vary=...." should be removed
 * then we should use this valus instead.
 */
static const char *s_x_vary = "X-LiteSpeed-Vary";
static const char *bypass_vary[] = {"Origin", };


/* IF WANT TO USE THE RECV REQ HEADER HOOK
 * THEN DEFINE USE_RECV_REQ_HEADER_HOOK in cacheentry.h
 * But now it seems this will cuase some issues about using rewrite
 * pagespeed and wordpress and so on
 * We may remove it later
 * Right now, just disable it
 */
//#define USE_RECV_REQ_HEADER_HOOK


/***
 * 11/6/2017 changes: Make sure save the compressed copy to disk
 * Serv need to check if OK to serv with gzipped
 *
 */


enum
{
    CE_STATE_NOCACHE = 0,
    CE_STATE_HAS_PRIVATE_CACHE,
    CE_STATE_HAS_PUBLIC_CACHE,
    CE_STATE_UPDATE_STALE,
    CE_STATE_WILLCACHE,
    CE_STATE_CACHED,
    CE_STATE_CACHEFAILED,
};

enum HTTP_METHOD
{
    HTTP_UNKNOWN = 0,
    HTTP_OPTIONS,
    HTTP_GET ,
    HTTP_HEAD,
    HTTP_POST,
    HTTP_PUT ,
    HTTP_DELETE,
    HTTP_TRACE,
    HTTP_CONNECT,
    HTTP_MOVE,
    DAV_PROPFIND,
    DAV_PROPPATCH,
    DAV_MKCOL,
    DAV_COPY,
    DAV_LOCK,
    DAV_UNLOCK,
    DAV_VERSION_CONTROL,
    DAV_REPORT,
    DAV_CHECKIN,
    DAV_CHECKOUT,
    DAV_UNCHECKOUT,
    DAV_UPDATE,
    DAV_MKWORKSPACE,
    DAV_LABEL,
    DAV_MERGE,
    DAV_BASELINE_CONTROL,
    DAV_MKACTIVITY,
    DAV_BIND,
    DAV_SEARCH,
    HTTP_PURGE,
    HTTP_REFRESH,
    HTTP_METHOD_END,
};

struct MyMData
{
    CacheConfig    *pConfig;
    CacheEntry     *pEntry;
    char           *pOrgUri;

    /**
     * Save the list of rewrite ENV of "cache-vary:...."
     */
    AutoStr2        *pCacheCtrlVary;

    /**
     * Save the rewrite ENV of "cache-control: vary=...."
     */
    AutoStr2        *pCacheVary;

    /**
     * Keeps a copy of rewritten query string if necessary
     */
    AutoStr2        qsBuf;

    // If there is no query string, this list is not constructed.
    class CacheKeyModList *pCacheKeyModList;

    unsigned char   iCacheState;
    unsigned char   iMethod;
    unsigned char   iHaveAddedHook;
    unsigned char   iCacheSendBody;
    CacheCtrl       cacheCtrl;
    CacheHash       cePublicHash;
    CacheHash       cePrivateHash;
    CacheKey        cacheKey;
    uint16_t        orgUriLen;
    uint16_t        hostPortLen;
    uint8_t         hkptIndex;
    uint8_t         hasCacheFrontend;
    uint8_t         reqCompressType; //0, no, 1: gzip, 2:br
    uint8_t         saveFailed;
    XXH64_state_t   contentState;
    z_stream       *zstream;
    off_t           orgFileLength;


};


static lsi_config_key_t paramArray[] =
{
    /***
     * The id is base on 0, added some just for reference
     */
    {"enableCache",             0, 0},  //0
    {"enablePrivateCache",      1, 0},
    {"checkPublicCache",        2, 0},
    {"checkPrivateCache",       3, 0}, //3
    {"qsCache",                 4, 0},
    {"reqCookieCache",          5, 0},

    {"ignoreReqCacheCtrl",      6, 0},
    {"ignoreRespCacheCtrl",     7, 0}, //7

    {"respCookieCache",         8, 0},

    {"expireInSeconds",         9, 0},
    {"privateExpireInSeconds",  10, 0},//10
    {"maxStaleAge",             11, 0},
    {"maxCacheObjSize",         12, 0},

    {"storagepath",             13, 0},//13 "cacheStorePath"

    //Below 5 are newly added
    {"noCacheDomain",           14, 0},//14
    {"noCacheUrl",              15, 0},//15

    {"no-vary",                 16, 0},//},16
    {"addEtag",                 17, 0},
    {"purgeUri",                18, 0},
    {"reqHeaderVary",           19, 0},
    {"CacheKeyModify",          20, 0},

    {NULL, 0, 0} //Must have NULL in the last item
};

//Update permission of the dest to the same of the src
static void matchDirectoryPermissions(const char *dir)
{
    ServerProcessConfig &procConfig = ServerProcessConfig::getInstance();
    chown(dir, procConfig.getUid(), procConfig.getGid());
    chmod(dir, S_ISGID | 0770);
}


static int createCachePath(const char *path, int mode)
{
    struct stat st;
    if (stat(path, &st) == -1)
    {
        if (GPath::createMissingPath((char *)path, mode) == -1)
            return LS_FAIL;
    }
    return LS_OK;
}


static void house_keeping_cb(const void *p)
{
    DirHashCacheStore *pStore = (DirHashCacheStore *)p;
    if (pStore)
    {
        pStore->houseKeeping();
        pStore->cleanByTracking(100, 100);
        //g_api->log(NULL, LSI_LOG_DEBUG, "[%s]house_keeping_cb with store %p.\n",
        //           ModuleNameStr, pStore);
    }
}


static int parseStoragePath(CacheConfig *pConfig, const char *pValStr,
                            int valLen, int level, const char *name)
{
    if (level != LSI_CFG_CONTEXT)
    {
        char *pBak = new char[valLen + 1];
        if (!pBak)
            return -1;
        memcpy(pBak, pValStr, valLen);
        pBak[valLen] = 0x00;
        pValStr = pBak;

        char pTmp[max_file_len]  = {0};
        char cachePath[max_file_len]  = {0};

        //check if contains $
        if (strchr(pValStr, '$'))
        {
            int ret = g_api->expand_current_server_variable(level, pValStr, pTmp,
                      max_file_len);
            if (ret >= 0)
            {
                pValStr = pTmp;
                valLen = ret;
            }
            else
            {
                g_api->log(NULL, LSI_LOG_ERROR,
                           "[%s]parseConfig failed to expand_current_server_"
                           "variable[%s], default will be in use.\n",
                           ModuleNameStr, pValStr);

                delete []pBak;
                return -1;
            }
        }

        if (pValStr[0] != '/')
            lstrncpy(cachePath, g_api->get_server_root(), max_file_len);

        /**
         * Since pValStr is not null terminated, so do a length checking here
         *
         */
        const size_t iCachePathLen = strlen(cachePath);
        const size_t storeRootLen = iCachePathLen + (size_t)valLen + 1;
        if (storeRootLen + CACHE_STORE_PATH_RESERVE > max_file_len)
        {
            g_api->log(NULL, LSI_LOG_ERROR,
                           "[%s]parseConfig failed to set the storagepath "
                           "due to [%.*s]too long.\n",
                           ModuleNameStr, valLen, pValStr);

            delete []pBak;
            return -1;
        }

        memcpy(cachePath + iCachePathLen, pValStr, valLen);
        cachePath[iCachePathLen + valLen + 0] = '/';
        cachePath[iCachePathLen + valLen + 1] = '\0';

        if (createCachePath(cachePath, 0770) == -1)
        {
            g_api->log(NULL, LSI_LOG_ERROR,
                       "[%s]parseConfig failed to create directory [%s].\n",
                       ModuleNameStr, cachePath);
        }
        else
        {
            matchDirectoryPermissions(cachePath);
            pConfig->setStore(new DirHashCacheStore);
            pConfig->getStore()->setStorageRoot(cachePath);
            pConfig->getStore()->initManager();
            pConfig->setOwnStore(1);
            g_api->set_timer(10*1000, 1, house_keeping_cb, pConfig->getStore());

            g_api->log(NULL, LSI_LOG_DEBUG,
                       "[%s]parseConfig setStoragePath [%s] for level %d[name: %s].\n",
                       ModuleNameStr, cachePath, level, name);
        }
        delete []pBak;
    }
    else
        g_api->log(NULL, LSI_LOG_INFO,
                   "[%s]context [%s] shouldn't have 'storagepath' parameter.\n",
                   ModuleNameStr, name);

    return 0;
}


static int parseNoCacheDomain(CacheConfig *pConfig, const char *pValStr,
                              int valLen, int level, const char *name)
{
    if (level != LSI_CFG_SERVER)
    {
        g_api->log(NULL, LSI_LOG_INFO,
                   "[%s][%s] Only SERVER level can have 'noCacheDomain' "
                   "parameter.\n", ModuleNameStr, name);
        return 0;
    }

    if (!pConfig->getVHostMapExclude())
        pConfig->setVHostMapExclude(new VHostMap());
    pConfig->getVHostMapExclude()->mapDomainList(
        HttpServerConfig::getInstance().getGlobalVHost(), pValStr);

    g_api->log(NULL, LSI_LOG_DEBUG, "[%s]noCacheDomain [%.*s] added.\n",
               ModuleNameStr, valLen, pValStr);

    return 0;
}


static int parseNoCacheUrl(CacheConfig *pConfig, const char *pValStr,
                           int valLen, int level, const char *name)
{
    if (level != LSI_CFG_SERVER && level != LSI_CFG_VHOST)
    {
        g_api->log(NULL, LSI_LOG_INFO,
                   "[%s][%s] Only SERVER and VHOST level can have 'noCacheUrl' "
                   "parameter.\n", ModuleNameStr, name);
        return 0;
    }

    if (valLen == 0)
        return 0;

    bool bClear = (valLen == 7 && strncasecmp(pValStr, "<clear>", 7) == 0);
    if (bClear)
        pConfig->setOnlyUseOwnUrlExclude(1);

    if (!pConfig->getUrlExclude())
        pConfig->setUrlExclude(new Aho(1));

    pConfig->getUrlExclude()->addPattern(pValStr, valLen, NULL);
    return 0;
}

void parseNoCacheUrlFinal(CacheConfig *pConfig)
{
    if (pConfig->getUrlExclude())
    {
        pConfig->getUrlExclude()->makeTree();
        pConfig->getUrlExclude()->optimizeTree();
    }
}


// Parses the key and value given.  If key is storagepath, returns 1, otherwise returns 0
static int parseLine(CacheConfig *pConfig, int param_id,
                     const char *val, int valLen)
{
    int i = param_id;
    int bit;
    int minValue = 0;
    int maxValue = 1;
    int defValue = 0;

    switch (i)
    {
    case 0:
        bit = CACHE_ENABLE_PUBLIC;
        break;
    case 1:
        bit = CACHE_ENABLE_PRIVATE;
        break;
    case 2:
        bit = CACHE_CHECK_PUBLIC;
        defValue = 1;
        break;
    case 3:
        bit = CACHE_CHECK_PRIVATE;
        defValue = 1;
        break;
    case 4:
        bit = CACHE_QS_CACHE;
        defValue = 1;
        break;
    case 5:
        bit = CACHE_REQ_COOKIE_CACHE;
        defValue = 1;
        break;
    case 6:
        bit = CACHE_IGNORE_REQ_CACHE_CTRL_HEADER;
        defValue = 1;
        break;
    case 7:
        bit = CACHE_IGNORE_RESP_CACHE_CTRL_HEADER;
        defValue = 0;
        break;
    case 8:
        bit = CACHE_RESP_COOKIE_CACHE;
        defValue = 1;
        break;
    case 9:
        bit = CACHE_MAX_AGE_SET;
        maxValue = INT_MAX;
        break;
    case 10:
        bit = CACHE_PRIVATE_AGE_SET;
        maxValue = INT_MAX;
        break;
    case 11:
        bit = CACHE_STALE_AGE_SET;
        maxValue = INT_MAX;
        break;
    case 12:
        bit = CACHE_MAX_OBJ_SIZE;
        maxValue = INT_MAX;
        defValue = 10000000; //10M
        break;

    case 13: //storagepath
    case 14:
    case 15:
    case 18:
    case 19:
    case 20:
        return i; //return the index for next step parsing

    case 16:
        bit = CACHE_NO_VARY;
        break;

    case 17:
        bit = CACHE_ADD_ETAG;
        maxValue = 2;
        defValue = 0;
        break;

    default:
        return 0;
    }

    int64_t value = strtoll(val, NULL, 10);

    if (value < minValue || value > maxValue)
        value = defValue;

    switch (bit)
    {
    case CACHE_MAX_AGE_SET:
        pConfig->setDefaultAge(value);
        break;
    case CACHE_STALE_AGE_SET:
        pConfig->setMaxStale(value);
        break;
    case CACHE_PRIVATE_AGE_SET:
        pConfig->setPrivateAge(value);
        break;
    case CACHE_MAX_OBJ_SIZE:
        pConfig->setMaxObjSize(value);
        break;
    case CACHE_ADD_ETAG:
        pConfig->setAddEtagType(value);
        break;
    default:
        break;
    }

    pConfig->setConfigBit(bit, value);
    return 0;
}


static void verifyStoreReady(CacheConfig *pConfig)
{
    if (pConfig->getStore())
        return ;

    pConfig->setStore(new DirHashCacheStore);
    if (pConfig->getStore())
    {
        char defaultCachePath[max_file_len];
        snprintf(defaultCachePath, sizeof(defaultCachePath), "%s%s",
                 g_api->get_server_root(), CACHEMODULEROOT);
        pConfig->getStore()->setStorageRoot((const char *)defaultCachePath);
        pConfig->getStore()->initManager();
        pConfig->setOwnStore(1);
        g_api->set_timer(10*1000, 1, house_keeping_cb, pConfig->getStore());
    }
    else
        g_api->log(NULL, LSI_LOG_ERROR,
                   "Cache verifyStoreReady failed to alloc memory.\n");
}

/**
 * return -1 error, ootherwise an index,
 * standard one is less than ot equal to HttpHeader::H_TRANSFER_ENCODING
 */
static int getVaryKeyIndex(CacheConfig *pConfig, const char *key, int len)
{
    int id = HttpHeader::getIndex(key, len);
    if (id > HttpHeader::H_TRANSFER_ENCODING)
    {
        id = -1; //ERROR case
        int offset = 0;
        StringList::iterator iter;
        if (pConfig->getVaryList())
        {
            for (iter = pConfig->getVaryList()->begin();
                 iter != pConfig->getVaryList()->end();
                 ++iter)
            {
                ++offset;
                if ((*iter)->len() == len
                    && strncasecmp(key, (*iter)->c_str(), len) == 0)
                {
                    id = HttpHeader::H_TRANSFER_ENCODING + offset;
                    break;
                }
            }
        }
    }
    return id;
}


static int setVaryList(CacheConfig *pConfig, const char *val, int valLen)
{
    StringList * pStringList = new StringList;
    if (!pStringList)
        return -1;

    pStringList->split(val, val + valLen, ",");

    int count = pStringList->size();
    if (count > 0)
    {
        /**
         * remove standard header from the list before save to config
         */
        StringList::iterator iter;
        for (iter = pStringList->begin(); iter != pStringList->end();)
        {
            int id = HttpHeader::getIndex((*iter)->c_str(), (*iter)->len());
            if (id <= HttpHeader::H_TRANSFER_ENCODING)
            {
                g_api->log(NULL, LSI_LOG_INFO, "[%s] needn't to add \"%.*s\" to"
                           " config which is standard request header.\n",
                           ModuleNameStr, (*iter)->len(), (*iter)->c_str());
                pStringList->erase(iter);
            }
            else
                 ++iter;
        }
        if (pStringList->size() > 0)
        {
            pConfig->setVaryList(pStringList);
            if (HttpHeader::H_TRANSFER_ENCODING + pStringList->size() > 32)
            {
                /**
                 * We use int32(32 bits) to indicate the flag of each bit, if
                 * total is more than 32 flags, it will lose those indexes
                 * over 32 flags
                 */
                g_api->log(NULL, LSI_LOG_ERROR, "[%s] config too many customised"
                            " varies in reqHeaderVary.\n", ModuleNameStr);
            }
            return 0;
        }
    }

    delete pStringList;
    pConfig->setVaryList(NULL);
    return 0;
}


static void *ParseConfig(module_param_info_t *param, int param_count,
                         void *_initial_config, int level, const char *name)
{
    CacheConfig *pInitConfig = (CacheConfig *)_initial_config;
    CacheConfig *pConfig = new CacheConfig;
    if (!pConfig)
        return NULL;

    pConfig->setLevel(level);
    pConfig->inherit(pInitConfig);
    if (!param || param_count == 0)
    {
        verifyStoreReady(pConfig);
        return (void *)pConfig;
    }

    for (int i=0 ;i<param_count; ++i)
    {
        int ret = parseLine(pConfig, param[i].key_index,
                            param[i].val, param[i].val_len);

        //Only server level need to match CHECKXXXX seetings
        if (ret == 2 && level == LSI_CFG_SERVER)
            pConfig->setConfigBit(CACHE_CHECK_PUBLIC,
                                  pConfig->isSet(CACHE_ENABLE_PUBLIC));
        else if (ret == 3 && level == LSI_CFG_SERVER)
            pConfig->setConfigBit(CACHE_CHECK_PRIVATE,
                              pConfig->isSet(CACHE_ENABLE_PRIVATE));
        else if (ret == 13)
            parseStoragePath(pConfig, param[i].val, param[i].val_len, level, name);
        else if (ret == 15)
            parseNoCacheUrl(pConfig, param[i].val, param[i].val_len, level, name);
        else if (ret == 14)
            parseNoCacheDomain(pConfig, param[i].val, param[i].val_len, level, name);
        else if (ret == 18)
            pConfig->setPurgeUri(param[i].val, param[i].val_len);
        else if (ret == 19)
            setVaryList(pConfig, param[i].val, param[i].val_len);
        else if (ret == 20)
            pConfig->parseCacheKeyMod(param[i].val, param[i].val_len);

    }

    parseNoCacheUrlFinal(pConfig);
    verifyStoreReady(pConfig);
    return (void *)pConfig;
}


static void FreeConfig(void *_config)
{
    delete(CacheConfig *)_config;
}


/**
 * Comments: tag at least must contains 16 bytes
 * needQS should be 0 when the purge is from header, the uri already have the QS
 * and it should be 1 when it is from a PURGE / REFRESH request
 */
static int uriToTag(const lsi_session_t *session,
                    const char *uri, int uriLen,
                    int needQS, char *tag)
{
    const int maxUrlLength = 16384;
    char url[maxUrlLength] = { 0 };
    int urlLen = 0;
    char httpstr[4] = {0};

    if (uriLen < 0)
    {
        g_api->log(session, LSI_LOG_ERROR, "uriToTag() uri length error.");
        return LS_FAIL;
    }

    int len = g_api->get_req_var_by_id(session, LSI_VAR_HTTPS,
                                        httpstr, 4);
    if (len == 2 && httpstr[1] == 'n')
    {
        memcpy(url + urlLen, "https://", 8);
        urlLen += 8;
    }
    else
    {
        memcpy(url + urlLen, "http://", 7);
        urlLen += 7;
    }

    len = g_api->get_req_var_by_id(session, LSI_VAR_SERVER_NAME,
                                    url + urlLen, maxUrlLength - urlLen);
    if (len < 0 || len >= maxUrlLength - urlLen)
    {
        g_api->log(session, LSI_LOG_ERROR, "uriToTag() server name length error.");
        return LS_FAIL;
    }
    urlLen += len;

    memcpy(url + urlLen, ":", 1);
    ++urlLen;

    len = g_api->get_req_var_by_id(session, LSI_VAR_SERVER_PORT,
                                    url + urlLen, maxUrlLength - urlLen);
    if (len < 0 || len >= maxUrlLength - urlLen)
    {
        g_api->log(session, LSI_LOG_ERROR, "uriToTag() server port length error.");
        return LS_FAIL;
    }
    urlLen += len;

    if (maxUrlLength - urlLen > uriLen)
    {
        /**
            * The copy will copy the URI and the QS
            */
        memcpy(url + urlLen, uri, uriLen);
        urlLen += uriLen;
    }
    else
    {
        g_api->log(session, LSI_LOG_ERROR, "uriToTag() uri length error.");
        return LS_FAIL;
    }

    if (needQS)
    {
        const char *pQs = g_api->get_req_query_string(session, &len);
        if (len < 0)
        {
            g_api->log(session, LSI_LOG_ERROR,
                       "uriToTag() query string length error.");
            return LS_FAIL;
        }
        if (len > 0)
        {
            if (maxUrlLength - urlLen <= len)
            {
                g_api->log(session, LSI_LOG_ERROR,
                           "uriToTag() query string length error.");
                return LS_FAIL;
            }
            *(url + urlLen) = '?';
            ++urlLen;
            memcpy(url + urlLen, pQs, len);
            urlLen += len;
        }
    }

    XXH64_state_t state;
    XXH64_reset(&state, 0);
    XXH64_update(&state, url, urlLen);
    unsigned long long md = XXH64_digest(&state);
    StringTool::hexEncode((const char *)&md, 8, tag);
    tag[16] = 0x00;  //Must add null terminate
    g_api->log(session, LSI_LOG_DEBUG, "[CACHE] URI: %.*s to TAG: %s\n",
               urlLen, url, tag);

    return LS_OK;
}


static int testFlagWithShm(const lsi_session_t *session,
                           MyMData *myData, int32_t flag)
{
    CacheManager *pManager = myData->pConfig->getStore()->getManager();
    int32_t id = pManager->getUrlVaryId(myData->pOrgUri,
                                        myData->orgUriLen + myData->hostPortLen);

    g_api->log(session, LSI_LOG_DEBUG,
               "[%s] testFlagWithShm() flag %d, id in shm %d.\n",
               ModuleNameStr, flag, id);
    /**
     * For not exist case (-1), treat as 0
     */
    if (id == -1)
        id = 0;

    if (flag == id)
        return 0;

    if (flag <= 0)
        pManager->delUrlVary(myData->pOrgUri,
                             myData->orgUriLen + myData->hostPortLen);
    else
        pManager->addUrlVary(myData->pOrgUri,
                             myData->orgUriLen + myData->hostPortLen, flag);
    return 1;
}

static bool isVaryHdrBypassed(const char *key, unsigned int len)
{
    for (unsigned int i = 0; i < sizeof(bypass_vary) / sizeof(char*); ++i)
    {
        if (len == strlen(bypass_vary[i]) && strncasecmp(key, bypass_vary[i], len) == 0)
            return true;
    }
    return false;
}


static int32_t getVaryFlag(const lsi_session_t *session, CacheConfig *pConfig)
{
    int32_t flag = 0;
    struct iovec iov[1] = {{NULL, 0}};
    int count = g_api->get_resp_header(session, LSI_RSPHDR_VARY, NULL,
                                       0, iov,1);
    if (count == 1 && iov[0].iov_len > 0)
    {
        StringList stringList;
        stringList.split((char *)iov[0].iov_base,
                         (char *)iov[0].iov_base + iov[0].iov_len, ",");

        StringList::iterator iter;
        for (iter = stringList.begin(); iter != stringList.end(); ++iter)
        {
            int index = getVaryKeyIndex(pConfig, (*iter)->c_str(),
                                        (*iter)->len());
            if (index <= -1)
            {
                if (isVaryHdrBypassed((*iter)->c_str(), (*iter)->len()))
                    continue;

                g_api->log(session, LSI_LOG_INFO, "[%s] vary request header"
                            " \"%.*s\" not defined!\n",
                           ModuleNameStr, (*iter)->len(), (*iter)->c_str());
                continue;
            }
            if (index == LSI_HDR_COOKIE)
                g_api->log(session, LSI_LOG_DEBUG, "[%s] skip vary on full cookie header",
                       ModuleNameStr);
            else
            {    g_api->log(session, LSI_LOG_DEBUG, "[%s] add vary request header"
                       " \"%.*s\" with index %d.\n",
                       ModuleNameStr, (*iter)->len(), (*iter)->c_str(), index);
                flag |= (1 << index);
            }
        }
    }

    return flag;
}



static int isUrlExclude(const lsi_session_t *session, CacheConfig *pConfig,
                        const char *url, int urlLen)
{
    Aho *ahos[2] = {pConfig->getUrlExclude(), pConfig->getParentUrlExclude()};
    int count = 2;
    if (pConfig->isOnlyUseOwnUrlExclude())
        count = 1;

    for (int i = 0; i < count ; ++i)
    {
        if (ahos[i])
        {
            size_t out_start, out_end;
            AhoState *out_last_state;
            int ret = ahos[i]->search(NULL, url, urlLen, 0,
                                      &out_start, &out_end,
                                      &out_last_state, NULL);
            if (ret)
            {
                g_api->log(session, LSI_LOG_INFO, "Cache excluded by URL: %.*s\n",
                           urlLen, url);
                return 1;
            }
        }
    }
    return 0;
}


static int uninitZstream(z_stream *zstream, bool compress)
{
    if (compress)
        deflateEnd(zstream);
    else
        inflateEnd(zstream);
    delete zstream;
    return 0;
}


/**
 * return 0 for OK, -1 for error
 */
static int initZstream(z_stream *zstream, bool compress)
{
    zstream->opaque = Z_NULL;
    zstream->zalloc = Z_NULL;
    zstream->zfree = Z_NULL;
    zstream->avail_in = 0;
    zstream->next_in = Z_NULL;
    if (compress)
    {
        if (deflateInit2(zstream, Z_BEST_COMPRESSION, Z_DEFLATED,
                16 + MAX_WBITS, 8, Z_DEFAULT_STRATEGY) != Z_OK)
        {
            return -1;
        }
    }
    else
    {
        if (inflateInit2(zstream, 32 + MAX_WBITS) != Z_OK)
        {
            return -1;
        }
    }

    return 0;
}


static int compressbuf(z_stream *zstream, bool compress, unsigned char *buff,
                    int buffLen, int fd, int eof)
{
    unsigned char buf[Z_BUF_SIZE];
    zstream->next_in = buff;
    zstream->avail_in = buffLen;
    int ret = 0;
    int z_ret;

    do
    {
        zstream->next_out = buf;
        zstream->avail_out = Z_BUF_SIZE;

        if (compress)
            z_ret = deflate(zstream, eof ? Z_FINISH : Z_SYNC_FLUSH);
        else
            z_ret = inflate(zstream, eof ? Z_FINISH : Z_PARTIAL_FLUSH);

        if (z_ret == Z_STREAM_ERROR)
            return LS_FAIL;

        if (z_ret == Z_BUF_ERROR)
            z_ret = Z_OK;

        if (z_ret >= Z_OK)
            ret += write(fd, buf, Z_BUF_SIZE - zstream->avail_out);
    } while (z_ret != Z_STREAM_END);

    return ret;
}

static int isDomainExclude(const lsi_session_t *session, CacheConfig *pConfig)
{
    if (pConfig->getVHostMapExclude())
    {
        HttpSession *pSession = (HttpSession *)session;
        HttpReq *pReq = pSession->getReq();
        const HttpVHost *pVHost = pReq->matchVHost(pConfig->getVHostMapExclude());
        if (pVHost)
        {
            g_api->log(session, LSI_LOG_INFO, "Cache excluded by domain.\n");
            return 1;
        }
    }
    return 0;
}


static char *copyCookie0(char *p, char *pDestEnd, const char *pCookie, int n)
{
    if (n > (pDestEnd - p - 1))
        n = pDestEnd - p - 1;
    if (n > 0)
    {
        memmove(p, pCookie, n);
        p += n;
        *p++ = ';';
    }
    return p;
}


static char *copyCookie(char *p, char *pDestEnd,
                        const cookieval_t *pIndex, const char *pCookie)
{
    int n = pIndex->valLen + pIndex->valOff - pIndex->keyOff;
    return copyCookie0(p, pDestEnd, pCookie, n);
}


int getPrivateCacheCookie(HttpReq *pReq, char *pDest, char *pDestEnd)
{
    const cookieval_t *pIndex;
    pReq->parseCookies();
    if (pReq->getCookieList().size() == 0)
    {
        *pDest = 0;
        return 0;
    }

    char *p = pDest;
    if (pReq->getCookieList().isSessIdxSet())
    {
        pIndex = pReq->getCookieList().getObj(pReq->getCookieList().getSessIdx());
        //assert( (pIndex->flag & COOKIE_FLAG_SESSID) != 0 );
        if (!pIndex)
        {
            g_api->log(NULL, LSI_LOG_ERROR, "[%s]CookieList error, idx %d sizenow %d, objsize %d\n",
                       ModuleNameStr,
                       pReq->getCookieList().getSessIdx(),
                       pReq->getCookieList().size(),
                       pReq->getCookieList().getObjSize());
            *pDest = 0;
            return 0;
        }

        p = copyCookie(p, pDestEnd, pIndex,
                       pReq->getHeaderBuf().getp(pIndex->keyOff));
        *p = 0;
        return p - pDest;
    }
    pIndex = pReq->getCookieList().begin();
    const char *pCookie;
    int skip;
    while ((pIndex < pReq->getCookieList().end()) && (p < pDestEnd))
    {
        pCookie = pReq->getHeaderBuf().getp(pIndex->keyOff);
        skip = 0;
        if ((*pCookie == '_') && (*(pCookie + 1) == '_'))
            skip = 1;
        else if ((strncmp("has_js=", pCookie, 7) == 0)
                 || (strncmp("_lscache_vary", pCookie, 13) == 0)
                 || (strncmp("wordpress_test_cookie", pCookie, 21) == 0)
                 || (strncmp("ls_smartpush", pCookie, 12) == 0)
                 || (strncmp("bb_forum_view=", pCookie, 14) == 0))
            skip = 1;
        else if (strncmp("frontend=", pCookie, 9) == 0)
        {
            int n = pIndex->valLen + pIndex->keyLen + 2;
            if ((p - pDest >= n)
                && (memcmp(pCookie, p - n, n - 1) == 0))
                skip = 1;
        }
        if (!skip)
            p = copyCookie(p, pDestEnd, pIndex, pCookie);
        ++pIndex;
    }
    *p = 0;
    return p - pDest;
}


char *appendVaryCookie(HttpReq *pReq, const char *pCookeName, int len,
                       char *pDest, char *pDestEnd)
{
    cookieval_t *pIndex = pReq->getCookie(pCookeName, len);
    if (pIndex)
    {
        const char *pCookie = pReq->getHeaderBuf().getp(pIndex->keyOff);
        pDest = copyCookie(pDest, pDestEnd, pIndex, pCookie);
    }
    else
    {
        pDest = copyCookie0(pDest, pDestEnd, pCookeName, len);
    }
    return pDest;
}


char *scanVaryOnList(HttpReq *pReq, const char *pListBegin,
                     const char *pListEnd, char *pDest, char *pDestEnd)
{
    while (pListBegin < pListEnd)
    {
        const char *pVary = pListBegin;
        while (pVary < pListEnd && isspace(*pVary))
            ++pVary;
        if (strncasecmp(pVary, "cookie=", 7) == 0)
            pVary += 7;
        const char *pVaryEnd = strchr(pVary, ',');
        if (!pVaryEnd)
        {
            pVaryEnd = pListEnd;
            pListBegin = pListEnd;
        }
        else
            pListBegin = pVaryEnd + 1;
        if (pVaryEnd - pVary > 0)
        {
            pDest = appendVaryCookie(pReq, pVary, pVaryEnd - pVary,
                                     pDest, pDestEnd);
        }
    }
    return pDest;
}


int getCacheVaryCookie(const lsi_session_t *session, HttpReq *pReq,
                       char *pDest, char *pDestEnd)
{
    pReq->parseCookies();

    char *p = pDest;
    p = appendVaryCookie(pReq, "_lscache_vary", 13, p, pDestEnd);

    MyMData *myData = (MyMData *)g_api->get_module_data(session, &MNAME,
                      LSI_DATA_HTTP);
    if (myData)
    {
        const char *pBegin = NULL;
        const char *pEnd = NULL;
        if (myData->pCacheCtrlVary)
        {
            pBegin = myData->pCacheCtrlVary->c_str();
            pEnd = myData->pCacheCtrlVary->c_str() + myData->pCacheCtrlVary->len();
            p = scanVaryOnList(pReq, pBegin, pEnd, p, pDestEnd);
        }
        if (myData->pCacheVary)
        {
            pBegin = myData->pCacheVary->c_str();
            pEnd = myData->pCacheVary->c_str() + myData->pCacheVary->len();
            p = scanVaryOnList(pReq, pBegin, pEnd, p, pDestEnd);
        }
    }

    *p = '\0';
    return p - pDest;
}


void calcCacheHash2(const lsi_session_t *session, CacheKey *pKey,
                    CacheHash *pHash, CacheHash *pPrivateHash)
{
//    CACHE_PRIVATE_KEY;
    XXH64_state_t state;
    int len;
    const char *host = g_api->get_req_header_by_id(session, LSI_HDR_HOST,
                       &len);
    XXH64_reset(&state, 0);
    XXH64_update(&state, host, len);

    char port[32] = ":";
    g_api->get_req_var_by_id(session, LSI_VAR_SERVER_PORT, port + 1, 10);
    XXH64_update(&state, port, strlen(port));

//     len = g_api->get_req_var_by_id(session, LSI_VAR_SSL_VERSION, env, 12);
//     if (len > 3)  //SSL
//         XXH64_update(&state, "!", 1);

    XXH64_update(&state, pKey->m_pUri, pKey->m_iUriLen);

    if (pKey->m_iQsLen > 0)
    {
        XXH64_update(&state, "?", 1);
        XXH64_update(&state, pKey->m_pQs, pKey->m_iQsLen);
    }

    if (pKey->m_iCookieVary > 0)
    {
        XXH64_update(&state, "#", 1);
        XXH64_update(&state, pKey->m_sCookie.c_str(), pKey->m_iCookieVary);
    }
    pHash->setKey(XXH64_digest(&state));



    if (pKey->m_iCookiePrivate > 0)
    {
        XXH64_update(&state, "~", 1);
        XXH64_update(&state, pKey->m_sCookie.c_str() + pKey->m_iCookieVary,
                        pKey->m_iCookiePrivate);
    }
    if (pKey->m_pIP)
    {
        XXH64_update(&state, "@", 1);
        XXH64_update(&state, pKey->m_pIP, pKey->m_ipLen);
    }
    pPrivateHash->setKey(XXH64_digest(&state));
}


// void calcCacheHash(const lsi_session_t *session, CacheKey *pKey,
//                    CacheHash *pHash, CacheHash *pPrivateHash)
// {
//     HttpSession *pSession = (HttpSession *)session;
//     HttpReq *pReq = pSession->getReq();
//     if (pReq->getContextState(CACHE_PRIVATE_KEY))
//         return;
//     if ((!pKey->m_pIP) && (pReq->getContextState(CACHE_KEY)))
//         return;
//
//     calcCacheHash2(session, pKey, pHash, pPrivateHash);
//     if (pKey->m_pIP)
//         pReq->orContextState(CACHE_PRIVATE_KEY | CACHE_KEY);
//     else
//         pReq->orContextState(CACHE_KEY);
// }


int appBufToCacheKey(const lsi_session_t *session, const char *buf,
                     int len, char *buffer, int maxLen)
{
    if (buf ==NULL || len <= 0)
        return 0;

    if (len <= maxLen)
        memcpy(buffer, buf, len);
    else
    {
        g_api->log(session, LSI_LOG_ERROR,
                   "[CACHE] buildCacheKey exceed limit of CookieBuf.\n");
        return -1;
    }

    return 0;
}


#define MAX_QS_MOD_COUNT 1000

struct QsInfo
{
    const char *qs_index[MAX_QS_MOD_COUNT];
    const char **qs_index_end;
    const char *pQsEnd;
    char qs_remove[MAX_QS_MOD_COUNT];
    int remove_cnt;
    int remove_size;
};


static void applyOneModList(const lsi_session_t *session, QsInfo *pQsInfo,
                                            const CacheKeyModList *pModList)
{
    if (pModList->getParent())
        applyOneModList(session, pQsInfo, pModList->getParent());

    int count = pQsInfo->qs_index_end - pQsInfo->qs_index;
    char *p_qs_remove;
    const char **p_qs_idx;
    for (auto iter = pModList->begin(); iter != pModList->end(); ++iter)
    {
        if (iter->m_operator > CACHE_KEY_QS_DEL_PREFIX)
            continue;
        for(p_qs_idx = &pQsInfo->qs_index[0], p_qs_remove = pQsInfo->qs_remove;
            p_qs_idx < pQsInfo->qs_index_end; ++p_qs_idx, ++p_qs_remove)
        {
            if (*p_qs_remove == 1)
                continue;
            if (*(p_qs_idx + 1) - *p_qs_idx < (int)iter->m_str.len
                || memcmp(*p_qs_idx, iter->m_str.ptr, iter->m_str.len) != 0)
                continue;
            if (iter->m_operator == CACHE_KEY_QS_DEL_EXACT)
            {
                if (*p_qs_idx + iter->m_str.len < pQsInfo->pQsEnd
                    && *(*p_qs_idx + iter->m_str.len) != '=')
                    continue;
                g_api->log(session, LSI_LOG_DEBUG,
                    "[CACHE] Remove exact matched QS key [%.*s],\n",
                       (int)iter->m_str.len, iter->m_str.ptr);
            }
            else
                g_api->log(session, LSI_LOG_DEBUG,
                    "[CACHE] Remove prefix matched QS key [%.*s],\n",
                       (int)iter->m_str.len, iter->m_str.ptr);
            *p_qs_remove = 1;
            ++pQsInfo->remove_cnt;
            pQsInfo->remove_size += *(p_qs_idx + 1) - *p_qs_idx;
        }
        if (pQsInfo->remove_cnt >= count)
            break;
    }
}


enum QSAction { QSA_UNMODIFIED, QSA_DELETED, QSA_UPDATED, };

static enum QSAction
applyModListToQueryString(const lsi_session_t *session,
            const char *pQs, const CacheKeyModList *pModList, AutoStr2 *qsBuf)
{
    QsInfo qsInfo;
    qsInfo.qs_index_end = &qsInfo.qs_index[0];
    *qsInfo.qs_index_end++ = pQs;
    qsInfo.pQsEnd = pQs + strlen(pQs);
    const char *p = pQs;
    while(p < qsInfo.pQsEnd)
    {
        p = (const char *)memchr(p, '&', qsInfo.pQsEnd - p);
        if (!p)
            break;
        p++;
        if (p != qsInfo.pQsEnd)
        {
            *qsInfo.qs_index_end++ = p;
            if (qsInfo.qs_index_end >= &qsInfo.qs_index[MAX_QS_MOD_COUNT - 1])
                break;
        }
    }
    *qsInfo.qs_index_end = qsInfo.pQsEnd;

    memset(qsInfo.qs_remove, 0, qsInfo.qs_index_end - qsInfo.qs_index);
    qsInfo.remove_cnt = 0;
    qsInfo.remove_size = 0;

    applyOneModList(session, &qsInfo, pModList);

    if (qsInfo.remove_cnt == 0)
        return QSA_UNMODIFIED;
    if (qsInfo.remove_cnt >= qsInfo.qs_index_end - qsInfo.qs_index)
        return QSA_DELETED;

    qsBuf->setStr(pQs);
    const char **p_qs_idx;
    char *p_qs_remove;
    char *pNewQs = qsBuf->buf();
    char *pKeyBufEnd = pNewQs + qsBuf->len();
    for(p_qs_idx = &qsInfo.qs_index[0], p_qs_remove = qsInfo.qs_remove;
        p_qs_idx < qsInfo.qs_index_end; ++p_qs_idx, ++p_qs_remove)
    {
        if (*p_qs_remove == 0)
        {
            int len = *(p_qs_idx + 1) - *p_qs_idx;
            if (len > pKeyBufEnd - pNewQs)
                len = pKeyBufEnd - pNewQs;
            memmove(pNewQs, *p_qs_idx, len);
            pNewQs += len;
        }
    }
    assert(pNewQs > qsBuf->buf());
    if (pNewQs[-1] == '&')
        --pNewQs;
    *pNewQs = 0;
    qsBuf->setLen(pNewQs - qsBuf->buf());
    g_api->log(session, LSI_LOG_DEBUG,
        "[CACHE] modified QS in cache key is [%s],\n", qsBuf->c_str());
    return QSA_UPDATED;
}


void buildCacheKey(const lsi_session_t *session, const char *uri, int uriLen,
                   int noVary, MyMData *myData)
{
    const int useCurQS = 1;
    CacheKey *const pKey = &myData->cacheKey;
    int iQSLen = 0;
    int ipLen = 0;
    char pCookieBuf[MAX_HEADER_LEN+8] = {0};
    char *pCookieBufEnd = pCookieBuf + MAX_HEADER_LEN;
    const char *pIp = g_api->get_client_ip(session, &ipLen);
    const char *pQs = useCurQS ? g_api->get_req_query_string(session, &iQSLen) : "";
    HttpSession *pSession = (HttpSession *)session;
    HttpReq *pReq = pSession->getReq();
    CacheConfig *pConfig = (CacheConfig *)g_api->get_config(session, &MNAME);

    pKey->m_pIP = pIp;
    pKey->m_ipLen = ipLen;
    if (noVary)
        pKey->m_iCookieVary = 0;
    else
    {
        pKey->m_iCookieVary = getCacheVaryCookie(session, pReq, pCookieBuf,
                              pCookieBufEnd);

        int len;
        const char *buf;
        CacheManager *pManager = pConfig->getStore()->getManager();
        int32_t flag = pManager->getUrlVaryId(uri, uriLen);

        if (flag > 0)
        {
            if (flag % (1 << HttpHeader::H_TRANSFER_ENCODING) != 0)
            {
                for (int id = 0; id < HttpHeader::H_TRANSFER_ENCODING; ++id)
                {
                    if (flag & 1 << id)
                    {
                        //HttpHeader::getHeaderName(id)
                        buf = g_api->get_req_header_by_id(session, id, &len);
                        if (len > 0 && buf)
                        {
                            int ret = appBufToCacheKey(session,
                                                       buf,
                                                       len,
                                                       pCookieBuf + pKey->m_iCookieVary,
                                                       MAX_HEADER_LEN - pKey->m_iCookieVary);
                            if (ret)
                            {
                                /**
                                 * If not enough space, bypass the part and continue
                                 */
                                break;
                            }
                            else
                                pKey->m_iCookieVary += len;
                        }
                    }
                }
            }

            if (pConfig->getVaryList() && flag >> HttpHeader::H_TRANSFER_ENCODING != 0)
            {
                int id = HttpHeader::H_TRANSFER_ENCODING;
                StringList::iterator iter;
                for (iter = pConfig->getVaryList()->begin();
                     iter != pConfig->getVaryList()->end();
                     ++iter)
                {
                    if (flag & 1 << id)
                    {
                        buf = g_api->get_req_header(session, (*iter)->c_str(),
                                                            (*iter)->len(), &len);
                        int ret = appBufToCacheKey(session, buf,len,
                                                   pCookieBuf + pKey->m_iCookieVary,
                                                   MAX_HEADER_LEN - pKey->m_iCookieVary);
                        if (ret)
                        {
                            /**
                             * If not enough space, bypass the part and continue
                             */
                            break;
                        }
                        else
                            pKey->m_iCookieVary += len;
                    }

                    ++id;
                }
            }
        }
    }

    if (pIp)
        pKey->m_iCookiePrivate = getPrivateCacheCookie(pReq,
                                 &pCookieBuf[pKey->m_iCookieVary],
                                 pCookieBufEnd);
    else
        pKey->m_iCookiePrivate = 0;

    if (pQs && pQs[0]
            && (myData->pCacheKeyModList || pConfig->getKeyModList()))
    {
        CacheKeyModList *pCacheKeyModList;

        if (myData->pCacheKeyModList)
        {
            pCacheKeyModList = myData->pCacheKeyModList;
            pCacheKeyModList->setParent(pConfig->getKeyModList());
        }
        else
            pCacheKeyModList = pConfig->getKeyModList();

        switch (applyModListToQueryString(session, pQs, pCacheKeyModList,
                                                            &myData->qsBuf))
        {
        case QSA_DELETED:
            pQs = NULL;
            iQSLen = 0;
            break;
        case QSA_UPDATED:
            pQs = myData->qsBuf.c_str();
            iQSLen = myData->qsBuf.len();
            break;
        default:
            // QSA_UNMODIFIED: do nothing
            break;
        }
    }

    pKey->m_pUri = uri;
    pKey->m_iUriLen = uriLen;
    pKey->m_pQs = pQs;
    pKey->m_iQsLen = iQSLen;
    pKey->m_sCookie.setStr(pCookieBuf);
}


static void dumpCacheKey(const lsi_session_t *session, const CacheKey *pKey)
{
    g_api->log(session, LSI_LOG_DEBUG,
           "[CACHE] CacheKey data: URI [%.*s], "
           "QS [%.*s], Vary Cookie [%.*s], "
           "Private Cookie [%.*s], IP [%s]\n",
           pKey->m_iUriLen, pKey->m_pUri,
           pKey->m_iQsLen,  pKey->m_pQs ? pKey->m_pQs : "",
           pKey->m_iCookieVary, pKey->m_sCookie.c_str() ? pKey->m_sCookie.c_str() : "",
           pKey->m_iCookiePrivate,
           pKey->m_sCookie.c_str() ? pKey->m_sCookie.c_str() + pKey->m_iCookieVary : "",
           pKey->m_pIP);
}


static void dumpCacheHash(const lsi_session_t *session, const char *desc,
                          const CacheHash *pHash)
{
    g_api->log(session, LSI_LOG_DEBUG,
           "[CACHE] %s, hash: [%02x%02x%02x%02x%02x%02x%02x%02x]\n",
           desc,
           pHash->getKey()[0], pHash->getKey()[1],
           pHash->getKey()[2], pHash->getKey()[3],
           pHash->getKey()[4], pHash->getKey()[5],
           pHash->getKey()[6], pHash->getKey()[7]
          );
}


off_t getEntryContentLength(MyMData *myData)
{
    int part1offset = myData->pEntry->getPart1Offset();
    int part2offset = myData->pEntry->getPart2Offset();
    if (part2offset - part1offset > 0)
    {
        off_t length = myData->pEntry->getContentTotalLen() -
                       (part2offset - part1offset);
        return length;
    }
    else
        return -1;
}


static void setCacheEntry(MyMData *data, CacheEntry *pEntry)
{
    if (!data || pEntry == data->pEntry)
        return;
    if (data->pEntry)
        data->pEntry->decRef();
    if (pEntry)
        pEntry->incRef();
    data->pEntry = pEntry;
}


short lookUpCache(lsi_param_t *rec, MyMData *myData, int no_vary,
                  const char *uri, int uriLen,
                  DirHashCacheStore *pDirHashCacheStore,
                  CacheHash *cePublicHash, CacheHash *cePrivateHash,
                  CacheConfig *pConfig, bool doPublic)
{
    buildCacheKey(rec->session, uri, uriLen, no_vary, myData);
    dumpCacheKey(rec->session, &myData->cacheKey);
    calcCacheHash2(rec->session, &myData->cacheKey, cePublicHash,
                  cePrivateHash);
    dumpCacheHash(rec->session, "Public hash", cePublicHash);
    dumpCacheHash(rec->session, "Private hash", cePrivateHash);

    CacheEntry *pEntry;
    long lastCacheFlush = (long)g_api->get_module_data(rec->session, &MNAME,
                          LSI_DATA_IP);

    pEntry = pDirHashCacheStore->getCacheEntry(*cePrivateHash,
              &myData->cacheKey, pConfig->getMaxStale(), lastCacheFlush);
    setCacheEntry(myData, pEntry);
    if (pEntry && (!pEntry->isStale() || pEntry->isUpdating())
        && !pEntry->isUnderConstruct())
        return CE_STATE_HAS_PRIVATE_CACHE;

    if (doPublic)
    {
        //Second, check if can use public one
        //Attemp to set the ipLen to negative number for checking public cache
        int savedIpLen = myData->cacheKey.m_ipLen;
        myData->cacheKey.m_ipLen = 0 - savedIpLen;
        pEntry = pDirHashCacheStore->getCacheEntry(*cePublicHash,
                  &myData->cacheKey, pConfig->getMaxStale(), -1);
        setCacheEntry(myData, pEntry);
        myData->cacheKey.m_ipLen = savedIpLen;
        if (pEntry)
        {
            if (pEntry->isStale() && !pEntry->isUpdating())
            {
                CacheEntry *pNewEntry = myData->pConfig->getStore()->
                                        createCacheEntry(myData->cePublicHash,
                                        &myData->cacheKey);
                if (pNewEntry)
                {
                    setCacheEntry(myData, pNewEntry);
                    return CE_STATE_UPDATE_STALE;
                }
                else
                {
                    g_api->log(rec->session, LSI_LOG_ERROR,
                               "[%s] createEntry failed for update stale.\n",
                               ModuleNameStr);
                }
            }

            if (!pEntry->isUnderConstruct())
                return CE_STATE_HAS_PUBLIC_CACHE;
            else
                return CE_STATE_NOCACHE;
        }
    }

    if (pEntry)
        pEntry->setStale(1);

    return CE_STATE_NOCACHE;
}


static int releaseMData(void *data)
{
    MyMData *myData = (MyMData *)data;
    if (myData)
    {
        if (myData->pEntry)
            myData->pEntry->decRef();

        if (myData->pOrgUri)
            delete []myData->pOrgUri;

        if (myData->zstream)
        {
            deflateEnd(myData->zstream);
            delete myData->zstream;
        }
        if (myData->pCacheCtrlVary)
            delete myData->pCacheCtrlVary;

        if (myData->pCacheVary)
            delete myData->pCacheVary;

        if (myData->pCacheKeyModList)
            delete myData->pCacheKeyModList;

        myData->qsBuf.release();
        delete myData;
    }
    return 0;
}


int checkBypassHeader(const char *header, int len)
{
    const char *headersBypass[] =
    {
        "last-modified",
        "etag",
        "date",
        "content-length",
        "transfer-encoding",
        "content-encoding",
        "set-cookie",
        "x-litespeed-cache", //bypass itself, otherwise always value is 'miss'
        "connection",  //https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Connection
    };
    int8_t headersBypassLen[] = {  13, 4, 4, 14, 17, 16, 10, 17, 10,};

    int count = sizeof(headersBypass) / sizeof(const char *);
    for (int i = 0; i < count ; ++i)
    {
        if (len == headersBypassLen[i]
            && strncasecmp(headersBypass[i], header, headersBypassLen[i]) == 0)
            return 1;
    }

    return 0;
}


int writeHttpHeader(int fd, AutoStr2 *str, const char *key, int key_len,
                    const char *val, int val_len)
{
    IOVec iov;
    iov.append(key, key_len);
    iov.append(": ", 2);
    if (val_len > 0)
        iov.append(val, val_len);
    iov.append("\r\n", 2);
    writev(fd, iov.get(), iov.len());

#ifdef CACHE_RESP_HEADER
    str->append(key, key_len);
    str->append(": ", 2);
    str->append(val, val_len);
    str->append("\r\n", 2);

#endif

    return key_len + val_len + 4;
}

static void parseEnv(const lsi_session_t *session, CacheCtrl &cacheCtrl)
{
    char cacheEnv[MAX_CACHE_CONTROL_LENGTH] = {0};
    int cacheEnvLen = g_api->get_req_env(session, cache_env_key,
                                         icache_env_key,
                                         cacheEnv,
                                         MAX_CACHE_CONTROL_LENGTH);
    if (cacheEnvLen > 0)
        cacheCtrl.parse((const char *)cacheEnv, cacheEnvLen);

}

void getRespHeader(const lsi_session_t *session, int header_index, char **buf,
                   int *length)
{
    struct iovec iov[1] = {{NULL, 0}};
    int iovCount = g_api->get_resp_header(session, header_index, NULL, 0, iov,
                                          1);
    if (iovCount == 1)
    {
        *buf = (char *)iov[0].iov_base;
        *length = iov[0].iov_len;
    }
    else
    {
        *buf = NULL;
        *length = 0;
    }
}

static int bypassUrimapHook(lsi_param_t *rec, MyMData *myData)
{
#ifdef USE_RECV_REQ_HEADER_HOOK
    if (g_api->get_hook_level(rec) == LSI_HKPT_RCVD_REQ_HEADER)
    {
        int disableHkpt = LSI_HKPT_URI_MAP;
        g_api->enable_hook(rec->session, &MNAME, 0, &disableHkpt, 1);
    }
#endif

    if (myData)
        g_api->free_module_data(rec->session, &MNAME, LSI_DATA_HTTP, releaseMData);
    return 0;
}

static void disableRcvdRespHeaderFilter(const lsi_session_t *session)
{
    int disableHkpt = LSI_HKPT_RCVD_RESP_HEADER;
    g_api->enable_hook(session, &MNAME, 0, &disableHkpt, 1);
}


void clearHooksOnly(const lsi_session_t *session)
{
    int aHkpts[4], iHkptCount = 0;
    MyMData *myData = (MyMData *) g_api->get_module_data(session, &MNAME,
                      LSI_DATA_HTTP);
    if (myData)
    {
        if (myData->iHaveAddedHook == 1 || myData->iHaveAddedHook == 2)
        {
            if (myData->iHaveAddedHook == 2)
                aHkpts[iHkptCount++] = myData->hkptIndex;
            aHkpts[iHkptCount++] = LSI_HKPT_RCVD_RESP_HEADER;
            g_api->enable_hook(session, &MNAME, 0, aHkpts, iHkptCount);
            myData->iHaveAddedHook = 0;
        }
    }
}


void clearHooks(const lsi_session_t *session)
{
    clearHooksOnly(session);
    g_api->free_module_data(session, &MNAME, LSI_DATA_HTTP, releaseMData);
}


static int cancelCache(lsi_param_t *rec)
{
    MyMData *myData = (MyMData *)g_api->get_module_data(rec->session, &MNAME,
                      LSI_DATA_HTTP);
    if (myData != NULL)
    {
        if (myData->iCacheState == CE_STATE_WILLCACHE
            || myData->iCacheState == CE_STATE_UPDATE_STALE)
        {
            g_api->log(rec->session, LSI_LOG_DEBUG, "[%s] cache cancelled.\n",
                       ModuleNameStr);
            CacheEntry * pEntry = myData->pEntry;
            setCacheEntry(myData, NULL);
            myData->pConfig->getStore()->cancelEntry(pEntry, 1);
        }
        if (myData->zstream)
        {
            deflateEnd(myData->zstream);
            delete myData->zstream;
            myData->zstream = NULL;
        }
    }
    clearHooks(rec->session);
    return 0;
}

//Return the byts number wriiten to file fd
static int deflateBufAndWriteToFile(MyMData *myData, unsigned char *pBuf,
                                    int len, int eof, int fd)
{
    unsigned char buf[Z_BUF_SIZE];
    int ret = 0, rc;
    if (myData->zstream)
    {
        myData->zstream->avail_in = len;
        myData->zstream->next_in = pBuf;

        do
        {
            myData->zstream->avail_out = Z_BUF_SIZE;
            myData->zstream->next_out = buf;
            int z_ret = deflate(myData->zstream, eof ? Z_FINISH : Z_SYNC_FLUSH);
            if ((z_ret == Z_OK) || (z_ret == Z_STREAM_END))
            {
                rc = write(fd, buf, Z_BUF_SIZE - myData->zstream->avail_out);
                if (rc > 0)
                    ret += rc;
                else if (rc < 0)
                {
                    ret = -1;
                    break;
                }

            }
        } while (myData->zstream->avail_out == 0);
    }
    else
    {
        rc = write(fd, pBuf, len);
        if (rc > 0)
            ret += rc;
        else if (rc < 0)
            ret = -1;
    }

    return ret;
}


static int endCache(lsi_param_t *rec)
{
    MyMData *myData = (MyMData *)g_api->get_module_data(rec->session, &MNAME,
                      LSI_DATA_HTTP);
    if (myData)
    {
        if (myData->hkptIndex)
        {
            //check if static file not optmized, or 0 byte content
            if (myData->orgFileLength == 0 ||
                myData->saveFailed ||
                (myData->pEntry->getHeader().m_lenStxFilePath > 0 &&
                 myData->orgFileLength == myData->pEntry->getHeader().m_lSize))
            {
                //Check if file optimized, if not, do not store it
                if (myData->orgFileLength == 0)
                    g_api->log(rec->session, LSI_LOG_DEBUG,
                           "[%s] Cache body size is zero, cancelled.\n",
                           ModuleNameStr);
                else
                    g_api->log(rec->session, LSI_LOG_DEBUG,
                           "[%s] cache cancelled due to error occurred "
                           "or no optimization.\n",
                           ModuleNameStr);
            }
            else if (myData->pEntry && myData->iCacheState == CE_STATE_WILLCACHE)
            {
                int fd = myData->pEntry->getFdStore();
                if (fd < 0)
                {
                    g_api->log(rec->session, LSI_LOG_ERROR,
                           "[%s] cache cancelled due to FdStore not initialized.\n",
                           ModuleNameStr);
                    return cancelCache(rec);
                }


                lseek(fd, 0, SEEK_END);
                deflateBufAndWriteToFile(myData, NULL, 0, 1, fd);

                if (myData->pConfig->getAddEtagType() == 2)
                {
                    char s[17] = {0};
                    snprintf(s, 17, "%llx",
                             (long long)XXH64_digest(&myData->contentState));
                    //Update the HASH64

                    /**
                     * Add 1 to offset because Hash has a \" in the beginning
                     */
                    nio_lseek(fd, myData->pEntry->getPart1Offset() + 1,
                              SEEK_SET);
                    write(fd, s, 16);
                }

                myData->pConfig->getStore()->publish(myData->pEntry);
                myData->pConfig->getStore()->getManager()->addTracking(myData->pEntry);
                myData->iCacheState = CE_STATE_CACHED;  //Succeed
                g_api->log(NULL, LSI_LOG_DEBUG,
                           "[%s] published %s, content length %ld.\n",
                           ModuleNameStr, myData->pOrgUri,
                           myData->orgFileLength);
            }
        }
        return cancelCache(rec);
    }
    else
        return 0;
}


static int getControlFlag(CacheConfig *pConfig)
{
    int flag = CacheCtrl::max_age | CacheCtrl::max_stale;
    if (pConfig->isSet(CACHE_ENABLE_PUBLIC))
        flag |= CacheCtrl::cache_public;
    if (pConfig->isSet(CACHE_ENABLE_PRIVATE))
        flag |= CacheCtrl::cache_private;
    if (pConfig->isSet(CACHE_NO_VARY))
        flag |= CacheCtrl::no_vary;

    if ((flag & CacheCtrl::cache_public) == 0 &&
        (flag & CacheCtrl::cache_private) == 0)
        flag |= CacheCtrl::no_cache;

    return flag;
}


static void processPurge(const lsi_session_t *session,
                         const char *pValue, int valLen);

static int createEntry(lsi_param_t *rec)
{
    //If have special cache headers, handle them here even if myData is NULL.
    struct iovec iov[100];
    int count = g_api->get_resp_header(rec->session,
                                       LSI_RSPHDR_LITESPEED_PURGE,
                                       NULL, 0, iov, sizeof(iov)/sizeof(iov[0]));
    for (int i = 0; i < count; ++i)
    {
        int valLen = iov[i].iov_len;
        const char *pVal = (const char *)iov[i].iov_base;
        if (pVal && valLen > 0)
            processPurge(rec->session, pVal, valLen);
    }

    count = g_api->get_resp_header(rec->session,
                                   LSI_RSPHDR_LITESPEED_PURGE2,
                                   NULL, 0, iov, sizeof(iov)/sizeof(iov[0]));
    for (int i = 0; i < count; ++i)
    {
        int valLen = iov[i].iov_len;
        const char *pVal = (const char *)iov[i].iov_base;
        if (pVal && valLen > 0)
            processPurge(rec->session, pVal, valLen);
    }

    MyMData *myData = (MyMData *)g_api->get_module_data(rec->session, &MNAME,
                                                        LSI_DATA_HTTP);
    if (myData == NULL || myData->iHaveAddedHook == 0)
    {
        clearHooks(rec->session);
        //Update to use default logger since when internal error occured, session logger may be messy up
        g_api->log(NULL, LSI_LOG_DEBUG,
                   "[%s] createEntry quit due to internal error.\n",
                   ModuleNameStr);
        return 0;
    }

    if (count > 0 && myData->hasCacheFrontend == 0)
        g_api->remove_resp_header(rec->session, LSI_RSPHDR_LITESPEED_PURGE,
                                  NULL, 0);


    CacheConfig *pContextConfig = (CacheConfig *)g_api->get_config(
                                      rec->session, &MNAME);
    if ((pContextConfig != NULL) && pContextConfig != myData->pConfig)
    {
        int flag = getControlFlag(pContextConfig);
        myData->cacheCtrl.update(flag, pContextConfig->getDefaultAge(),
                                 pContextConfig->getMaxStale());
        myData->pConfig = pContextConfig;
    }


    count = g_api->get_resp_header(rec->session,
                    LSI_RSPHDR_LITESPEED_CACHE_CONTROL, NULL, 0, iov, 3);
    for (int i = 0; i < count; ++i)
        myData->cacheCtrl.parse((char *)iov[i].iov_base, iov[i].iov_len);

    /**
     * If no LSI_RSPHDR_LITESPEED_CACHE_CONTROL, then we can check
     * expireInSeconds = 0 case and check the normal cache-contrl max-age
     * and s-maxage
     */
    if (count == 0 && myData->cacheCtrl.getMaxAge() == 0)
    {
        count = g_api->get_resp_header(rec->session,
                    LSI_RSPHDR_CACHE_CTRL, NULL, 0, iov, 3);
        for (int i = 0; i < count; ++i)
            myData->cacheCtrl.parse((char *)iov[i].iov_base, iov[i].iov_len);
    }

    if (myData->cacheCtrl.isCacheOff())
    {
        clearHooks(rec->session);
        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] createEntry abort due to cache is set to OFF.\n",
                   ModuleNameStr);
        return 0;
    }



    //For a HEAD request, do not save it
    if (myData->iMethod == HTTP_HEAD)
    {
        clearHooks(rec->session);
        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] cacheTofile to be cancelled for HEAD request.\n",
                   ModuleNameStr);
        return 0;
    }

    //if no LSI_RSPHDR_LITESPEED_CACHE_CONTROL and not 200, do nothing
    //if 304, do nothing
    int code = g_api->get_status_code(rec->session);
    if (code == 304 || (code != 200 && count == 0))
    {
        clearHooks(rec->session);
        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] cacheTofile to be cancelled for error page, code=%d.\n",
                   ModuleNameStr, code);
        return 0;
    }

    /**
     * TODO  should we do all case for test vary?
     */
    bool needRebuildCacheKey = false;
    int32_t iVaryFlag = getVaryFlag(rec->session, myData->pConfig);
    if (iVaryFlag == LS_FAIL)
    {
        /**
         * will stop the hook chain
         */
        clearHooks(rec->session);
        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] getVaryFlag failure, cancel cache.\n",
                   ModuleNameStr);
        return 0;
    }

    count = g_api->get_resp_header(rec->session, -1, s_x_vary,
                                   sizeof(s_x_vary) - 1, iov, 1);
    if (iov[0].iov_len > 0 && count == 1)
    {
        g_api->set_req_env(rec->session, cache_env_key, icache_env_key,
                           (char *)iov[0].iov_base, iov[0].iov_len);
        needRebuildCacheKey = true;
    }

    /**
     * testFlagWithShm must to be called to update the SHM
     * even needRebuildCacheKey is TRUE
     */
    if (testFlagWithShm(rec->session, myData, iVaryFlag))
    {
        needRebuildCacheKey = true;
    }

    count = g_api->get_resp_header(rec->session, LSI_RSPHDR_SET_COOKIE, NULL,
                                   0, iov, 1);
    if (iov[0].iov_len > 0 && count == 1)
    {
        if (!myData->pConfig->isSet(CACHE_RESP_COOKIE_CACHE))
        {
            clearHooks(rec->session);
            g_api->log(rec->session, LSI_LOG_DEBUG,
                       "[%s] cacheTofile to be cancelled for having respcookie.\n",
                       ModuleNameStr);
            return 0;
        }
        else
            needRebuildCacheKey = true;
    }

    if (needRebuildCacheKey)
    {
        //since have set-cookie header, re-calculate the hash keys
        buildCacheKey(rec->session, myData->cacheKey.m_pUri,
                      myData->cacheKey.m_iUriLen,
                      myData->cacheCtrl.getFlags() & CacheCtrl::no_vary,
                      myData);
        calcCacheHash2(rec->session, &myData->cacheKey,
                      &myData->cePublicHash, &myData->cePrivateHash);

        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] CacheKey and hash updated.\n", ModuleNameStr);
        dumpCacheHash(rec->session, "Updated Public hash",
                      &myData->cePublicHash);
        dumpCacheHash(rec->session, "Updated Private hash",
                      &myData->cePrivateHash);
    }


    myData->hkptIndex = LSI_HKPT_RCVD_RESP_BODY;
    const char *phandlerType = g_api->get_req_handler_type(rec->session);
    if (phandlerType && strlen(phandlerType) == 6
        && memcmp("static", phandlerType, 6) == 0)
    {
        int flag1 = g_api->get_hook_flag(rec->session, LSI_HKPT_RECV_RESP_BODY);
        int flag2 = g_api->get_hook_flag(rec->session, LSI_HKPT_SEND_RESP_BODY);
        if (flag2 & (LSI_FLAG_TRANSFORM | LSI_FLAG_PROCESS_STATIC))
            myData->hkptIndex = LSI_HKPT_SEND_RESP_BODY;
        else  if (flag1 & (LSI_FLAG_TRANSFORM | LSI_FLAG_PROCESS_STATIC))
            myData->hkptIndex = LSI_HKPT_RCVD_RESP_BODY;
        else
        {
            clearHooks(rec->session);
            g_api->log(rec->session, LSI_LOG_DEBUG,
                       "[%s] cacheTofile to be cancelled for static file type.\n",
                       ModuleNameStr);
            return 0;
        }
    }


    if (myData->cacheCtrl.isCacheOff())
    {
        clearHooks(rec->session);
        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] createEntry abort due to cache is off.\n", ModuleNameStr);
        return 0;
    }

    CacheHash *hash = NULL;
    if (myData->cacheCtrl.isPrivateCacheable())
        hash = &myData->cePrivateHash;
    else if (myData->cacheCtrl.isPublicCacheable())
    {
        if (myData->iCacheState != CE_STATE_UPDATE_STALE)
            hash = &myData->cePublicHash;
    }

    if (hash)
    {
        setCacheEntry(myData, myData->pConfig->getStore()->createCacheEntry(
                                 *hash, &myData->cacheKey));
        if (myData->pEntry == NULL)
        {
            int error = myData->pConfig->getStore()->getLastError();
            if (error == EPERM)
                g_api->log(rec->session, LSI_LOG_ERROR,
                   "[%s] createEntry failed, may due to no permission to write file.\n", ModuleNameStr);
            else
                g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] createEntry is being created by another worker.\n", ModuleNameStr);

        }
    }

    if (myData->pEntry == NULL)
    {
        clearHooks(rec->session);
        return 0;
    }

    dumpCacheKey(rec->session, &myData->cacheKey);
    dumpCacheHash(rec->session, "Create cache with hash",
                  &myData->pEntry->getHashKey());

    //Now we can store it
    myData->iCacheState = CE_STATE_WILLCACHE;

    int ids = myData->hkptIndex;
    g_api->enable_hook(rec->session, &MNAME, 1, &ids, 1);
    myData->iHaveAddedHook = 2;
    //When will cache store it, add miss header
    g_api->set_resp_header(rec->session, LSI_RSPHDR_UNKNOWN,
                           s_x_cached, sizeof(s_x_cached) - 1,
                           "miss", 4, LSI_HEADEROP_SET);

    return 0;
}


int cacheHeader(lsi_param_t *rec, MyMData *myData)
{
    myData->pEntry->setMaxStale(myData->pConfig->getMaxStale());
    int fd = myData->pEntry->getFdStore();
    g_api->log(rec->session,
               (fd != -1 ? LSI_LOG_DEBUG : LSI_LOG_ERROR),
               "[%s] save to %s cachestore by cacheHeader(), uri:%s\n", ModuleNameStr,
               ((myData->cacheCtrl.isPrivateCacheable()) ? "private" : "public"),
               myData->pOrgUri);

    if (fd == -1)
        return LS_FAIL;

    char *sLastMod = NULL;
    char *sETag = NULL;
    int nLastModLen = 0;
    int nETagLen = 0;
    int headersBufSize = 0;
    char sTmpEtag[128] = { 0 };
    CeHeader &CeHeader = myData->pEntry->getHeader();
    CeHeader.m_tmCreated = (int32_t)DateTime_s_curTime;
    CeHeader.m_msCreated = (int32_t)DateTime_s_curTimeMs;
    CeHeader.m_tmExpire = CeHeader.m_tmCreated + myData->cacheCtrl.getMaxAge();


#ifdef USE_RECV_REQ_HEADER_HOOK
    char cacheEnv[MAX_CACHE_CONTROL_LENGTH] = {0};
    int cacheEnvLen = g_api->get_req_env(rec->session, "HAVE_REWITE", 11,
                                         cacheEnv, MAX_CACHE_CONTROL_LENGTH);
    if (cacheEnvLen >= 1 && strncasecmp(cacheEnv, "1", 1) ==  0){
        myData->pEntry->setNeedDelay(1);
    }
#endif

    getRespHeader(rec->session, LSI_RSPHDR_LAST_MODIFIED, &sLastMod,
                  &nLastModLen);
    if (sLastMod)
        CeHeader.m_tmLastMod = DateTime::parseHttpTime(sLastMod, nLastModLen);


    //Check if it is a static file caching
    char path[4096];
    int cur_uri_len;
    struct stat sb;
    const char *cur_uri = g_api->get_req_uri(rec->session, &cur_uri_len);
    int pathLen = g_api->get_file_path_by_uri(rec->session, cur_uri,
                  cur_uri_len, path, 4096);
    if (pathLen > 0 && stat(path, &sb) != -1)
        CeHeader.m_lenStxFilePath = pathLen;
    else
    {
        CeHeader.m_lenStxFilePath = 0;
        memset(&sb, 0, sizeof(struct stat));
    }

    CeHeader.m_lenETag = 0;
    CeHeader.m_offETag = 0;
    getRespHeader(rec->session, LSI_RSPHDR_ETAG, &sETag, &nETagLen);
    if (sETag && nETagLen > 0)
    {
        if (nETagLen > VALMAXSIZE)
            nETagLen = VALMAXSIZE;
        CeHeader.m_lenETag = nETagLen;
    }
    else if (myData->pConfig->getAddEtagType() == 1)  //size-mtime
    {
        if (CeHeader.m_lenStxFilePath > 0)
        {
            snprintf(sTmpEtag, 127, "\"%llx-%llx;;;\"",
                     (long long)sb.st_size, (long long)sb.st_mtime);
            sETag = sTmpEtag;
            nETagLen = strlen(sETag);
            CeHeader.m_lenETag = nETagLen;
        }
    }
    else if (myData->pConfig->getAddEtagType() == 2)  //XXH64_digest
    {
        sETag = (char *)"\"0000000000000000;;;\"";
        nETagLen = 16 + 5;
        CeHeader.m_lenETag = nETagLen;
        XXH64_reset(&myData->contentState, 0);
    }

    char *pKey = NULL;
    int keyLen;
    getRespHeader(rec->session, LSI_RSPHDR_LITESPEED_TAG, &pKey, &keyLen);
    char * tag = new char[(keyLen > 0 ? keyLen : 0) + 18];
    int offset = 0;
    if (pKey && keyLen > 0)
    {
        memcpy(tag, pKey, keyLen);
        memcpy(tag + keyLen, ",", 1);
        offset = keyLen + 1;
    }

    if (uriToTag(rec->session, myData->pOrgUri + myData->hostPortLen,
                 myData->orgUriLen, 1, tag + offset) != LS_OK)
    {
        delete []tag;
        return LS_FAIL;
    }
    g_api->log(rec->session, LSI_LOG_DEBUG,
               "[%s] cacheHeader() set cache TAG to %.*s\n",
               ModuleNameStr, offset + 16, tag);

    myData->pEntry->setTag(tag, offset + 16);
    delete []tag;

    CeHeader.m_statusCode = g_api->get_status_code(rec->session);

    //Right now, set wrong numbers, and this will be fixed when publish
    myData->pEntry->setPart1Len(0); //->setContentLen(0, 0);
    myData->pEntry->setPart2Len(0);

    /**
     * If already gzipped, no need to gzip
     */
    int needGzip = (g_api->get_resp_buffer_compress_method(rec->session) == 0);

    /**
     * If the response not gzipped, and check if req need gzip,
     * if not needed, do not gzip it.
     */
    if (needGzip)
    {
        needGzip = (myData->reqCompressType == 1);
    }

    /**
     * If need to gzip but it is a small static file, no need
     * Or it is not compressible, no need
     */
    if (needGzip)
    {
        int contentTypelen;
        char *pContentType = NULL;
        getRespHeader(rec->session, LSI_RSPHDR_CONTENT_TYPE, &pContentType,
                      &contentTypelen);

        if (pContentType && contentTypelen > 0)
        {
            char ch = pContentType[contentTypelen];
            pContentType[contentTypelen] = 0;
            char compressible = HttpMime::getMime()->compressible(pContentType);
            pContentType[contentTypelen] = ch;
            if (!compressible)
                needGzip = false;
        }
    }

    const char *phandlerType = g_api->get_req_handler_type(rec->session);
    if (needGzip && phandlerType && strlen(phandlerType) == 6 &&
        memcmp("static", phandlerType, 6) == 0 &&
        sb.st_size > 0 && sb.st_size < 200)
        needGzip = false;

    /***
     * if it isn't a static file but has small size, no need to gzip
     */
    if (needGzip)
    {
        char *pVal = NULL;
        int valLen;
        getRespHeader(rec->session, LSI_RSPHDR_CONTENT_LENGTH, &pVal, &valLen);
        if (pVal && valLen > 0)
        {
            int len = 0;
            len = atoi(pVal);
            if (len >= 0 && len < 200)
                needGzip = false;
        }
    }

    if (needGzip)
    {
        myData->zstream = new z_stream;
        if (myData->zstream)
        {
            if (initZstream(myData->zstream, 1))
            {
                delete myData->zstream;
                myData->zstream = NULL;
                needGzip = false;
            }
        }
        else
            needGzip = false;
    }

    int compress_method = g_api->get_resp_buffer_compress_method(rec->session);
    if (compress_method == 0 && needGzip)
        compress_method  = 1;
    myData->pEntry->markReady(compress_method);

    myData->pEntry->saveCeHeader();

    if (CeHeader.m_lenETag > 0)
    {
        write(fd, sETag, CeHeader.m_lenETag);

#ifdef CACHE_RESP_HEADER
        myData->m_pEntry->m_sRespHeader.append(sETag, CeHeader.m_lenETag);
#endif
    }
    if (CeHeader.m_lenStxFilePath > 0)
        write(fd, path, CeHeader.m_lenStxFilePath);

    CeHeader.m_lSize = sb.st_size;
    CeHeader.m_inode = sb.st_ino;
    CeHeader.m_tmFileLastMod = sb.st_mtime;

    //Stat to write akll other headers

    int count = g_api->get_resp_headers_count(rec->session);
    if (count >= MAX_RESP_HEADERS_NUMBER)
        g_api->log(rec->session, LSI_LOG_WARN,
                   "[%s] too many resp headers [=%d]\n",
                   ModuleNameStr, count);

    struct iovec iov_key[MAX_RESP_HEADERS_NUMBER];
    struct iovec iov_val[MAX_RESP_HEADERS_NUMBER];
    count = g_api->get_resp_headers(rec->session, iov_key, iov_val,
                                    MAX_RESP_HEADERS_NUMBER);
    for (int i = 0; i < count; ++i)
    {
        //check if need to bypass
        if (!checkBypassHeader((const char *)iov_key[i].iov_base,
                               iov_key[i].iov_len))
        {
            pKey = (char *)iov_key[i].iov_base;
            //If no frontend, no need to save some cache headers
//             if (myData->hasCacheFrontend == 0)
//             {
//                 if (iov_key[i].iov_len > 12 &&
//                     strncasecmp("X-LiteSpeed-", pKey, 12) == 0)
//                 {
//                     continue;
//                 }
//
//                 //if it is lsc-cookie, then change to Set-Cookie
//                 if (iov_key[i].iov_len == 10 &&
//                     strncasecmp(pKey, "lsc-cookie", 10) == 0)
//                     pKey = (char *) "Set-Cookie";
//             }

#ifdef CACHE_RESP_HEADER
            headersBufSize += writeHttpHeader(fd, &(myData->m_pEntry->m_sRespHeader),
                                              pKey, iov_key[i].iov_len,
                                              iov_val[i].iov_base, iov_val[i].iov_len);
#else
            headersBufSize += writeHttpHeader(fd, NULL,
                                              pKey, iov_key[i].iov_len,
                                              (char *)iov_val[i].iov_base, iov_val[i].iov_len);
#endif
        }
    }

#ifdef CACHE_RESP_HEADER
    if (myData->m_pEntry->m_sRespHeader.len() > 4096)
        myData->m_pEntry->m_sRespHeader.prealloc(0);
#endif

    myData->pEntry->setPart1Len(CeHeader.m_lenETag + CeHeader.m_lenStxFilePath
                                +
                                headersBufSize);
    return 0;
}


int cacheTofile(lsi_param_t *rec)
{
    MyMData *myData = (MyMData *)g_api->get_module_data(rec->session, &MNAME,
                      LSI_DATA_HTTP);
    if (!myData)
        return 0;

    if (myData->iCacheSendBody == 0) //uninit
    {
        myData->iCacheSendBody = 1; //need cache
        cacheHeader(rec, myData);
    }

    int fd = myData->pEntry->getFdStore();

    long iCahcedSize = 0;
    off_t offset = 0;
    const char *pBuf;
    int len = 0;
    void *pRespBodyBuf = g_api->get_resp_body_buf(rec->session);
    long maxObjSz = myData->pConfig->getMaxObjSize();
    off_t bodyBufSize = g_api->get_body_buf_size(pRespBodyBuf);
    g_api->log(rec->session, LSI_LOG_DEBUG,
        "[%s] cacheTofile() response body size: %ld, max cache object size: %ld\n",
        ModuleNameStr, bodyBufSize, maxObjSz);

    if (maxObjSz > 0 && bodyBufSize > maxObjSz)
    {
        cancelCache(rec);
        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] cacheTofile() cache cancelled, body buffer size %ld > maxObjSize %ld\n",
                   ModuleNameStr, bodyBufSize, maxObjSz);
        return 0;
    }

    int ret;
    while (fd != -1 && !myData->saveFailed && !g_api->is_body_buf_eof(pRespBodyBuf, offset))
    {
        len = 0;
        pBuf = g_api->acquire_body_buf_block(pRespBodyBuf, offset, &len);
        if (!pBuf || len <= 0)
            break;

        ret = deflateBufAndWriteToFile(myData, (unsigned char *)pBuf, len, 0, fd);
        g_api->log(rec->session, LSI_LOG_DEBUG,
               "[%s] cacheTofile() save response body, offset: %ld, buf: %p, size: %d, ret: %d, cache size: %ld\n",
               ModuleNameStr, offset, pBuf, len, ret, iCahcedSize + ret);

        if (ret == -1)
        {
            myData->saveFailed = 1;
            break;
        }
        if (myData->pConfig->getAddEtagType() == 2)
            XXH64_update(&myData->contentState, pBuf, len);
        g_api->release_body_buf_block(pRespBodyBuf, offset);
        offset += len;
        iCahcedSize += ret;
        myData->orgFileLength += len;
    }

    if (!myData->saveFailed && fd != -1)
    {
        ret = deflateBufAndWriteToFile(myData, NULL, 0, 1, fd);
        iCahcedSize += ret;
        myData->pEntry->setPart2Len(iCahcedSize);
        g_api->log(rec->session, LSI_LOG_DEBUG,
               "[%s] cacheTofile() stored, size %ld\n",
               ModuleNameStr, offset);
    }
    else
        g_api->log(rec->session, LSI_LOG_ERROR,
               "[%s] cacheTofile() Failed due to write file error!\n",
               ModuleNameStr);
    endCache(rec);

    return 0;
}


int cacheTofileFilter(lsi_param_t *rec)
{
    char cacheEnv[MAX_CACHE_CONTROL_LENGTH] = {0};
    int cacheEnvLen = g_api->get_req_env(rec->session, "cache-control", 13,
                                         cacheEnv, MAX_CACHE_CONTROL_LENGTH);
    if (cacheEnvLen == 8
        && strncasecmp(cacheEnv, "no-cache", cacheEnvLen) ==  0)
        return rec->len1;

    //Because Pagespeed module uses the non-blocking way to check if can handle
    //The optimized cache, it will have an eventCb to set the reqVar to notice
    //cache module to start to cahce, So have to check it here
    MyMData *myData = (MyMData *)g_api->get_module_data(rec->session, &MNAME,
                      LSI_DATA_HTTP);
    if (!myData || myData->saveFailed)
        return rec->len1;

    if (myData->iCacheSendBody == 0) //uninit
    {
        myData->iCacheSendBody = 1; //need cache
        cacheHeader(rec, myData);
    }
    int fd = myData->pEntry->getFdStore();
    int part2Len = myData->pEntry->getPart2Len();
    int ret = g_api->stream_write_next(rec, (const char *) rec->ptr1,
                                       rec->len1);
    if (ret > 0)
    {
        long maxObjSz = myData->pConfig->getMaxObjSize();
        if (maxObjSz > 0 && part2Len + ret > maxObjSz)
        {
            cancelCache(rec);
            g_api->log(rec->session, LSI_LOG_DEBUG,
                       "[%s] cacheTofileFilter() cache cancelled, current size to cache %d > maxObjSize %ld\n",
                       ModuleNameStr, part2Len + ret, maxObjSz);
            return ret;
        }


        int len = deflateBufAndWriteToFile(myData, (unsigned char *)rec->ptr1, ret, 0, fd);
        if (len == -1)
        {
            myData->saveFailed = 1;
            g_api->log(rec->session, LSI_LOG_ERROR,
               "[%s] cacheTofileFilter() Failed due a write error!\n",
               ModuleNameStr);
            return rec->len1;
        }
        else
        {
            if (myData->pConfig->getAddEtagType() == 2)
                XXH64_update(&myData->contentState, rec->ptr1, ret);

            myData->pEntry->setPart2Len(part2Len + len);
            myData->orgFileLength += ret;
            g_api->log(rec->session, LSI_LOG_DEBUG,
                    "[%s] cacheTofileFilter() stored, size %d, now part2len %d\n",
                    ModuleNameStr, len, part2Len + len);
        }
    }
    return ret; //rec->len1;
}


static int isReqCacheable(lsi_param_t *rec, CacheConfig *pConfig)
{
    if (!pConfig->isSet(CACHE_QS_CACHE))
    {
        int iQSLen;
        const char *pQS = g_api->get_req_query_string(rec->session, &iQSLen);
        if (pQS && iQSLen > 0)
        {
            g_api->log(rec->session, LSI_LOG_DEBUG,
                       "[%s] isReqCacheable return 0 for has QS but qscache disabled.\n",
                       ModuleNameStr);
            return 0;
        }
    }

    if (!pConfig->isSet(CACHE_REQ_COOKIE_CACHE))
    {
        int cookieLen;
        const char *pCookie = g_api->get_req_cookies(rec->session, &cookieLen);
        if (pCookie && cookieLen > 0)
        {
            g_api->log(rec->session, LSI_LOG_DEBUG,
                       "[%s] isReqCacheable return 0 for has reqcookie but reqcookie disabled.\n",
                       ModuleNameStr);
            return 0;
        }
    }

    return 1;
}


// int setCacheUserData(lsi_param_t *rec)
// {
//     const char *p = (char *)rec->ptr1;
//     int lenTotal = rec->len1;
//     const char *pEnd = p + lenTotal;
//
//     if (lenTotal < 8)
//         return LS_FAIL;
//
//
//     //Check the kvPair length matching
//     int len;
//     while (p < pEnd)
//     {
//         memcpy(&len, p, 4);
//         if (len < 0 || len > lenTotal)
//             return LS_FAIL;
//
//         lenTotal -= (4 + len);
//         p += (4 + len);
//     }
//
//     if (lenTotal != 0)
//         return LS_FAIL;
//
//
//     MyMData *myData = (MyMData *)g_api->get_module_data(rec->session, &MNAME,
//                       LSI_DATA_HTTP);
//     if (myData == NULL) //|| myData->iCacheState != CE_STATE_CACHED)
//         return LS_FAIL;
//
//     if (rec->len1 < 8)
//         return LS_FAIL;
//
//     myData->pEntry->m_sPart3Buf.append((char *)rec->ptr1, rec->len1);
//
//     g_api->log(rec->session, LSI_LOG_DEBUG,
//                "[%s:setCacheUserData] written %d\n", ModuleNameStr,
//                rec->len1);
//     return 0;
// }
//
//
// //get the part3 data
// int getCacheUserData(lsi_param_t *rec)
// {
//     MyMData *myData = (MyMData *)g_api->get_module_data(rec->session, &MNAME,
//                       LSI_DATA_HTTP);
//     if (myData == NULL)
//         return LS_FAIL;
//
//
// //     char **buf = (char **)rec->_param;
// //     int *len = (int *)(long)rec->_param_count;
// //     *buf = (char *)myData->pEntry->m_sPart3Buf.c_str();
// //     *len = myData->pEntry->m_sPart3Buf.len();
//
//     return 0;
// }


int getHttpMethod(lsi_param_t *rec, char *httpMethod)
{
    int methodLen = g_api->get_req_var_by_id(rec->session,
                    LSI_VAR_REQ_METHOD, httpMethod, 10);
    int method = HTTP_UNKNOWN;
    switch (methodLen)
    {
    case 3:
        if ((httpMethod[0] | 0x20) == 'g')   //"GET"
            method = HTTP_GET;
        break;
    case 4:
        if (strncasecmp(httpMethod, "HEAD", 4) == 0)
            method = HTTP_HEAD;
        else if (strncasecmp(httpMethod, "POST", 4) == 0)
            method = HTTP_POST;
        break;
    case 5:
        if (strncasecmp(httpMethod, "PURGE", 5) == 0)
            method = HTTP_PURGE;
        break;
    case 7:
        if (strncasecmp(httpMethod, "REFRESH", 7) == 0)
            method = HTTP_REFRESH;
        break;
    default:
        break;
    }

    return method;
}


static void checkFileUpdateWithCache(lsi_param_t *rec, MyMData *myData)
{
    CeHeader &CeHeader = myData->pEntry->getHeader();
    if (CeHeader.m_lenStxFilePath <= 0)
        return ;

    char path[4096] = {0};
    int fd = myData->pEntry->getFdStore();
    int ret = lseek(fd, myData->pEntry->getPart1Offset() + CeHeader.m_lenETag, 0);
    if (ret != -1)
        ret = read(fd, path, CeHeader.m_lenStxFilePath);
    if (ret != -1)
    {
        struct stat sb;
        if ((ret = stat(path, &sb)) != -1 &&
            CeHeader.m_lSize == sb.st_size &&
            CeHeader.m_inode == sb.st_ino &&
            CeHeader.m_tmFileLastMod == sb.st_mtime)
            return ;
    }
    myData->pConfig->getStore()->purge(myData->pEntry);
    g_api->log(rec->session, LSI_LOG_DEBUG,
               "[%s]cache destroyed for file [%s] %s.\n",
               ModuleNameStr, path, (ret == -1) ? "cache error" : "file changed");
    myData->iCacheState = CE_STATE_NOCACHE;
}

MyMData *createMData(lsi_param_t *rec)
{
    MyMData *myData = new MyMData;
    assert(myData);

    memset((void *) myData, 0, sizeof(MyMData));
    g_api->set_module_data(rec->session, &MNAME, LSI_DATA_HTTP, (void *)myData);

    CacheConfig *pConfig = (CacheConfig *)g_api->get_config(
                               rec->session, &MNAME);
    if (!pConfig)
    {
        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] cache module is not configured, skip.\n",
                   ModuleNameStr);
        return NULL;
    }

    int flag = getControlFlag(pConfig);
    myData->cacheCtrl.init(flag, pConfig->getDefaultAge(), pConfig->getMaxStale());
    myData->pConfig = pConfig;
    int uriLen = g_api->get_req_org_uri(rec->session, NULL, 0);
    if (uriLen > 0)
    {
        char host[512] = {0};
        int hostLen = g_api->get_req_var_by_id(rec->session,
                                               LSI_VAR_SERVER_NAME, host, 512);
        char port[12] = {0};
        int portLen = g_api->get_req_var_by_id(rec->session,
                                               LSI_VAR_SERVER_PORT, port, 12);

        //host:port uri
        char *uri = new char[uriLen + hostLen + portLen + 2];
        memcpy(uri, host, hostLen);
        uri[hostLen] = ':';
        memcpy(uri + hostLen + 1, port, portLen);
        g_api->get_req_org_uri(rec->session, uri + hostLen + 1 + portLen,
                               uriLen + 1);
        myData->orgUriLen = uriLen;
        myData->hostPortLen = hostLen + portLen + 1;
        uriLen += (hostLen + 1 + portLen); //Set the the right uriLen
        uri[uriLen] = 0x00; //NULL terminated
        myData->pOrgUri = uri;
    }

    return myData;
}


static int sessionBegin(lsi_param_t *rec)
{
    MyMData *myData = (MyMData *) g_api->get_module_data(rec->session, &MNAME,
                      LSI_DATA_HTTP);
    if (myData)
    {
        g_api->free_module_data(rec->session, &MNAME, LSI_DATA_HTTP, releaseMData);
        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] sessionBegin released previous module data.\n",
                   ModuleNameStr);
    }
    return 0;
}

static int checkAssignHandler(lsi_param_t *rec)
{
    char val[3] = {0};
    if (g_api->get_req_env(rec->session, "modcache", 8, val, 1) > 0
        && *val == '0')
    {
        int aHkpts[] = {LSI_HKPT_URI_MAP, LSI_HKPT_HTTP_END,
                    LSI_HKPT_HANDLER_RESTART, LSI_HKPT_RCVD_RESP_HEADER};
        g_api->enable_hook(rec->session, &MNAME, 0, aHkpts, 4);

        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] checkAssignHandler turn off modcache.\n",
                   ModuleNameStr);
        return 0;
    }

    MyMData *myData = (MyMData *) g_api->get_module_data(rec->session, &MNAME,
                      LSI_DATA_HTTP);

    CacheConfig *pConfig = (CacheConfig *)g_api->get_config(
                               rec->session, &MNAME);
    if (!pConfig)
    {
        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] checkAssignHandler no configuration.\n", ModuleNameStr);
        return bypassUrimapHook(rec, myData);
    }

    int uriLen = g_api->get_req_org_uri(rec->session, NULL, 0);
    if (uriLen <= 0)
    {
        g_api->log(rec->session, LSI_LOG_ERROR,
                   "[%s] checkAssignHandler get uri length error.\n", ModuleNameStr);
        return bypassUrimapHook(rec, myData);
    }

    int cur_uri_len;
    const char *cur_uri = g_api->get_req_uri(rec->session, &cur_uri_len);
    if (isUrlExclude(rec->session, pConfig, cur_uri, cur_uri_len))
        return bypassUrimapHook(rec, myData);

    if (isDomainExclude(rec->session, pConfig))
        return bypassUrimapHook(rec, myData);

    char httpMethod[10] = {0};
    int method = getHttpMethod(rec, httpMethod);
    if (method == HTTP_UNKNOWN || method == HTTP_POST)
    {
        CacheConfig *pConfig = (CacheConfig *)g_api->get_config(
                            rec->session, &MNAME);
        int flag = getControlFlag(pConfig);
        CacheCtrl cacheCtrl;
        cacheCtrl.init(flag, pConfig->getDefaultAge(), pConfig->getMaxStale());
        parseEnv(rec->session, cacheCtrl);

        //Do not store POST method for handling, only store the timestamp
        if (method == HTTP_POST && cacheCtrl.isPrivateAutoFlush())
        {
            g_api->set_module_data(rec->session, &MNAME, LSI_DATA_IP,
                                   (void *)(long)DateTime_s_curTime);
        }
        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] checkAssignHandler returned, method %s[%d].\n",
                   ModuleNameStr, httpMethod, method);
        return bypassUrimapHook(rec, myData);
    }

    //If it is range request, quit
    int rangeRequestLen = 0;
    const char *rangeRequest = g_api->get_req_header_by_id(rec->session,
                               LSI_HDR_RANGE, &rangeRequestLen);
    if (rangeRequest && rangeRequestLen > 0)
    {
        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] checkAssignHandler returned, not support rangeRequest [%.*s].\n",
                   ModuleNameStr, rangeRequestLen, rangeRequest);
        return bypassUrimapHook(rec, myData);
    }

    if (method == HTTP_GET || method == HTTP_HEAD)
    {
        if (!pConfig->isCheckPublic() && !pConfig->isPrivateCheck())
        {
            if (!isReqCacheable(rec, pConfig))
            {
                g_api->log(rec->session, LSI_LOG_DEBUG,
                           "[%s] checkAssignHandler returned, no check and no cache.\n",
                           ModuleNameStr);
                return 0;  //Do not bypass Urimap hook for may use it next
            }
        }
    }


    if (myData == NULL)
        myData = createMData(rec);

    myData->pConfig = pConfig;
    myData->iMethod = method;

    if (myData->iMethod == HTTP_PURGE || myData->iMethod == HTTP_REFRESH)
    {
        g_api->log(rec->session, LSI_LOG_DEBUG,
                       "[%s] checkAssignHandler get HTTP PURGE/REFRESH.\n",
                       ModuleNameStr);

        if (LS_OK != g_api->register_req_handler(rec->session, &MNAME, 0))
        {
            g_api->log(rec->session, LSI_LOG_WARN,
                       "[%s] checkAssignHandler register_req_handler failed.\n",
                       ModuleNameStr);
            g_api->free_module_data(rec->session, &MNAME, LSI_DATA_HTTP,
                                    releaseMData);
        }
        disableRcvdRespHeaderFilter(rec->session);
        return bypassUrimapHook(rec, NULL);
    }

    CacheCtrl &cacheCtrl = myData->cacheCtrl;
    parseEnv(rec->session, cacheCtrl);

    if (rec->ptr1 != NULL && rec->len1 > 0)
        cacheCtrl.parse((const char *)rec->ptr1, rec->len1);

    if (!pConfig->isSet(CACHE_IGNORE_REQ_CACHE_CTRL_HEADER))
    {
        int bufLen;
        const char *buf = g_api->get_req_header_by_id(rec->session,
                          LSI_HDR_CACHE_CTRL, &bufLen);
        if (buf && bufLen > 0)
            cacheCtrl.parse(buf, bufLen);
    }

    //Set to true but not the below just for not to re-check cache state or
    //re-store it
    //bool doPublic = cacheCtrl.isPublicCacheable() || myData->pConfig->isCheckPublic();
    bool doPublic = true;
    int encodingLen = 0;
    char *encoding = (char *)g_api->get_req_header_by_id(rec->session,
                                                       LSI_HDR_ACC_ENCODING,
                                                       &encodingLen);
    if (!encoding)
        myData->reqCompressType = LSI_NO_COMPRESS;
    else
    {
        char orgChar = encoding[encodingLen];
        encoding[encodingLen] = 0;
        myData->reqCompressType = (encodingLen >= 4 && strcasestr(encoding, "gzip"));
        if (myData->reqCompressType == LSI_NO_COMPRESS &&
            encodingLen >= 2 && strcasestr(encoding, "br"))
            myData->reqCompressType = LSI_BR_COMPRESS;
        encoding[encodingLen] = orgChar;
    }

    myData->iCacheState = lookUpCache(rec, myData,
                                   cacheCtrl.getFlags() & CacheCtrl::no_vary,
                                   myData->pOrgUri,
                                   myData->orgUriLen + myData->hostPortLen,
                                   myData->pConfig->getStore(),
                                   &myData->cePublicHash,
                                   &myData->cePrivateHash,
                                   myData->pConfig,
                                   doPublic);

    g_api->log(rec->session, LSI_LOG_DEBUG,
               "[%s] checkAssignHandler lookUpCache, myData %p entry %p state %d.\n",
               ModuleNameStr, myData, myData->pEntry, myData->iCacheState);

    if (g_api->get_req_env(rec->session, "LSCACHE_FRONTEND", 16, val, 2) > 0
        && strncasecmp(val, "1", 1) ==  0)
    {
        myData->hasCacheFrontend = 1;
    }

    if (myData->iCacheState != CE_STATE_NOCACHE)
        checkFileUpdateWithCache(rec, myData);//may change state


    if (myData->iCacheState != CE_STATE_NOCACHE && myData->iCacheState != CE_STATE_UPDATE_STALE)
    {
        if (((myData->iCacheState == CE_STATE_HAS_PRIVATE_CACHE &&
             (myData->pConfig->isCheckPublic() || myData->pConfig->isPrivateCheck()))
            ||
            (myData->iCacheState == CE_STATE_HAS_PUBLIC_CACHE
             && myData->pConfig->isCheckPublic()))
#ifdef USE_RECV_REQ_HEADER_HOOK
            && myData->pEntry->getNeedDelay() == 0
#endif
            )
        {
            assert(myData->pEntry);
            assert(myData->pEntry->getFdStore() != -1);

            if (LS_OK != g_api->register_req_handler(rec->session, &MNAME, 0))
            {
                g_api->log(rec->session, LSI_LOG_WARN,
                           "[%s] checkAssignHandler register_req_handler failed.\n",
                           ModuleNameStr);
                g_api->free_module_data(rec->session, &MNAME, LSI_DATA_HTTP,
                                        releaseMData);
            }
            else
            {
                //Since it is served from cache, so bypass modsec checking
                //Becaue when cache entry created, it was checked.
                g_api->set_req_env(rec->session,  "modsecurity", 11, "off", 3);

                //myData->pEntry->incHits();
                myData->iHaveAddedHook = 3; //state of using handler
                g_api->log(rec->session, LSI_LOG_DEBUG,
                           "[%s] checkAssignHandler register_req_handler OK.\n",
                           ModuleNameStr);
            }
            disableRcvdRespHeaderFilter(rec->session);
            bypassUrimapHook(rec, NULL);
        }
        else
        {
            g_api->log(rec->session, LSI_LOG_INFO,
                       "[%s] checkAssignHandler found cachestate %d but set to not check.\n",
                       ModuleNameStr, myData->iCacheState);
#ifdef USE_RECV_REQ_HEADER_HOOK
            //may have another chance to check
            if (myData->pEntry->getNeedDelay())
                myData->pEntry->setNeedDelay(0);
#endif
        }
    }
    else if (myData->iMethod == HTTP_GET && isReqCacheable(rec, pConfig))
    {
        if (!myData->cacheCtrl.isCacheOff()
            || (myData->pConfig->isCheckPublic() || myData->pConfig->isPrivateCheck()))
        {
            myData->iHaveAddedHook = 1;

            //g_api->set_session_hook_flag( rec->_session, LSI_HKPT_RCVD_RESP_BODY, &MNAME, 1 );
            g_api->log(rec->session, LSI_LOG_DEBUG,
                       "[%s] checkAssignHandler Add Hooks.\n", ModuleNameStr);
            bypassUrimapHook(rec, NULL);
        }
        else
        {
#ifdef USE_RECV_REQ_HEADER_HOOK
            //Do not free myData because later may have cache-contrl env enable it
            myData->iHaveAddedHook = 0;
#else
            clearHooksOnly(rec->session);
            myData->iHaveAddedHook = 0;
            g_api->free_module_data(rec->session, &MNAME, LSI_DATA_HTTP,
                                        releaseMData);
#endif
        }
    }

    return 0;
}

static int checkVaryEnv(lsi_param_t *rec)
{
    if (!rec->ptr1 || rec->len1 <= 0)
        return 0;
    MyMData *myData = (MyMData *) g_api->get_module_data(rec->session, &MNAME,
                      LSI_DATA_HTTP);
    if (myData == NULL)
    {
        myData = createMData(rec);
        if (!myData)
            return -1;
    }

    if (myData->pCacheVary == NULL)
        myData->pCacheVary = new AutoStr2;

    myData->pCacheVary->append((const char *)rec->ptr1, rec->len1);
    myData->pCacheVary->append(",", 1);
    return 0;
}



static int checkCtrlEnv(lsi_param_t *rec)
{
    MyMData *myData = (MyMData *) g_api->get_module_data(rec->session, &MNAME,
                      LSI_DATA_HTTP);

    if (myData == NULL)
    {
        myData = createMData(rec);
        if (!myData)
            return -1;
    }

    if (myData->pCacheCtrlVary == NULL)
        myData->pCacheCtrlVary = new AutoStr2;
    else
    {
        //11/11/2020 added, if exist, just append so will keep the old one
        myData->pCacheCtrlVary->append(";", 1);
    }

    if (rec->len1 > 5 && strncasecmp((const char *)rec->ptr1, "vary=", 5) == 0)
    {
        myData->pCacheCtrlVary->append((const char *)rec->ptr1 + 5,
                                       rec->len1 - 5);

        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] checkCtrlEnv append %.*s.\n", ModuleNameStr, rec->len1,
                   (const char *)rec->ptr1);
        return 0;
    }

    CacheCtrl &cacheCtrl = myData->cacheCtrl;
    if (rec->ptr1 != NULL && rec->len1 > 0)
        cacheCtrl.parse((const char *)rec->ptr1, rec->len1);

    if (cacheCtrl.isCacheOff() && myData->iHaveAddedHook == 1)
    {
        clearHooksOnly(rec->session);
        myData->iHaveAddedHook = 0;
    }
    else if (!cacheCtrl.isCacheOff() && myData->iHaveAddedHook == 0)
    {
        int hkpt = LSI_HKPT_RCVD_RESP_HEADER;
        g_api->enable_hook(rec->session, &MNAME, 1, &hkpt, 1);
        myData->iHaveAddedHook = 1;
        g_api->log(rec->session, LSI_LOG_DEBUG,
                   "[%s] checkEnv Add Hooks.\n", ModuleNameStr);
    }

    return 0;
}


static int checkKeyMod(lsi_param_t *rec)
{
    MyMData *myData = (MyMData *) g_api->get_module_data(rec->session, &MNAME,
                      LSI_DATA_HTTP);
    const char *const keyModStr = (const char *) rec->ptr1;
    const int         keyModLen = rec->len1;
    int len_unused;

    if (!(keyModLen && keyModStr))
        return -1;

    if (!g_api->get_req_query_string(rec->session, &len_unused))
        return 0;   // No query string: nothing to modify

    if (myData == NULL)
    {
        myData = createMData(rec);
        if (!myData)
            return -1;
    }

    if (!myData->pCacheKeyModList)
    {
        myData->pCacheKeyModList = new CacheKeyModList();
        if (!myData->pCacheKeyModList)
            return -1;
    }

    myData->pCacheKeyModList->parseAppend(keyModStr, keyModLen);
    g_api->log(rec->session, LSI_LOG_DEBUG,
        "[%s] %s: after appending `%.*s', mod list has %d elements.\n",
        ModuleNameStr, __func__, keyModLen, keyModStr,
        myData->pCacheKeyModList->size());

    return 0;
}


int releaseIpCounter(void *data)
{
    //No malloc, needn't free, but functions must be presented.
    return 0;
}


static lsi_serverhook_t serverHooks[] =
{
    {LSI_HKPT_HTTP_BEGIN,       sessionBegin,       LSI_HOOK_FIRST,  LSI_FLAG_ENABLED},
#ifdef USE_RECV_REQ_HEADER_HOOK
    {LSI_HKPT_RCVD_REQ_HEADER,  checkAssignHandler, LSI_HOOK_EARLY,     LSI_FLAG_ENABLED},
#endif
    {LSI_HKPT_URI_MAP,          checkAssignHandler, LSI_HOOK_FIRST, LSI_FLAG_ENABLED},
    {LSI_HKPT_HTTP_END,         endCache,           LSI_HOOK_LAST + 1,  LSI_FLAG_ENABLED},
    {LSI_HKPT_HANDLER_RESTART,  cancelCache,        LSI_HOOK_LAST + 1,  LSI_FLAG_ENABLED},
    {LSI_HKPT_RCVD_RESP_HEADER, createEntry,        LSI_HOOK_LAST + 1,  LSI_FLAG_ENABLED},


    {LSI_HKPT_RCVD_RESP_BODY,   cacheTofile,        LSI_HOOK_LAST + 1,  0},
    {LSI_HKPT_SEND_RESP_BODY,   cacheTofileFilter,  LSI_HOOK_LAST + 1,  0},
    LSI_HOOK_END   //Must put this at the end position
};

static int init(lsi_module_t *pModule)
{
    g_api->init_module_data(pModule, releaseMData, LSI_DATA_HTTP);
    g_api->init_module_data(pModule, releaseIpCounter, LSI_DATA_IP);
    g_api->register_env_handler("cache-control", 13, checkCtrlEnv);
    g_api->register_env_handler("cache-vary",    10, checkVaryEnv);
    g_api->register_env_handler("cache-key-mod", 13, checkKeyMod);

//     g_api->register_env_handler("setcachedata", 12, setCacheUserData);
//     g_api->register_env_handler("getcachedata", 12, getCacheUserData);

    return 0;
}


int isModified(const lsi_session_t *session, CeHeader &CeHeader, char *etag,
               int etagLen, AutoStr2 &str)
{
    int len;
    const char *buf = NULL;

    if (etagLen > 0)
    {
        buf = g_api->get_req_header(session, "If-None-Match", 13, &len);
        if (buf && len == etagLen &&
            memcmp(etag, buf, etagLen > 3 ? etagLen - 3 : etagLen) == 0)
        {
            str.setStr(buf, len);
            return 0;
        }
    }

    buf = g_api->get_req_header(session, "If-Modified-Since", 17, &len);
    if (!*buf && len >= RFC_1123_TIME_LEN &&
        CeHeader.m_tmLastMod <= DateTime::parseHttpTime(buf, len))
        return 0;

    return 1;
}


static void purgeAllByTag(const lsi_session_t *session, MyMData *myData,
                          const char *pValue, int valLen, int stale)
{
    CacheStore *pStore = myData->pConfig->getStore();
    if (!pStore)
        return ;

    CacheKey key;
    int ipLen;
    key.m_pIP = g_api->get_client_ip(session, &ipLen);
    key.m_ipLen = ipLen;
    key.m_iCookieVary = 0;
    key.m_iCookiePrivate = 0;
    key.m_sCookie.setStr("");


    pStore->getManager()->processPrivatePurgeCmd(&key,
            pValue, valLen, DateTime::s_curTime, DateTime::s_curTimeUs / 1000, stale);
    g_api->log(session, LSI_LOG_DEBUG,
               "[%s] PURGE private cache for [%s]: %.*s\n",
               ModuleNameStr, key.m_pIP, valLen, pValue);



    pStore->getManager()->processPurgeCmd(
        pValue, valLen, DateTime::s_curTime, DateTime::s_curTimeUs / 1000, stale);
    g_api->log(session, LSI_LOG_DEBUG,  "[%s] PURGE public cache: %.*s\n",
               ModuleNameStr, valLen, pValue);
}

static void processPurge2(const lsi_session_t *session,
                         const char *pValue, int valLen)
{
    CacheStore *pStore = NULL;
    MyMData *myData = (MyMData *)g_api->get_module_data(session, &MNAME,
                      LSI_DATA_HTTP);
    if (myData)
        pStore = myData->pConfig->getStore();
    else
    {
        CacheConfig *pContextConfig = (CacheConfig *)g_api->get_config(
                                          session, &MNAME);
        if (pContextConfig)
            pStore = pContextConfig->getStore();
    }
    if (!pStore)
        return ;

    const char *pValueEnd = pValue + valLen;
    CacheKey key;
    bool isPriv = false;
    if (valLen >= 8 && strncmp(pValue, "private,", 8) == 0)
    {
        int ipLen;
        char pCookieBuf[MAX_HEADER_LEN+8] = {0};
        char *pCookieBufEnd = pCookieBuf + MAX_HEADER_LEN;
        key.m_pIP = g_api->get_client_ip(session, &ipLen);
        key.m_ipLen = ipLen;
        key.m_iCookieVary = 0;

        HttpSession *pSession = (HttpSession *)session;
        HttpReq *pReq = pSession->getReq();
        key.m_iCookiePrivate = getPrivateCacheCookie(pReq,
                               &pCookieBuf[key.m_iCookieVary],
                               pCookieBufEnd);
        key.m_sCookie.setStr(pCookieBuf);

        pValue += 8;
        isPriv = true;
    }
    else
    {
        if (valLen >= 7 && strncmp(pValue, "public,", 7) == 0)
            pValue += 7;
    }


    while (pValue < pValueEnd && isspace(*pValue))
        ++pValue;
    if (pValue >= pValueEnd)
        return;

    int stale = 0;
    if (pValueEnd - pValue >= 6 && strncasecmp(pValue, "stale,", 6 ) == 0)
    {
        stale = 1;
        pValue += 6;
    }

    while (pValue < pValueEnd && isspace(*pValue))
        ++pValue;
    if (pValue >= pValueEnd)
        return;

    valLen = pValueEnd - pValue;

    char tag[17] = {0};
    if (*pValue == '/')
    {
        if (uriToTag(session, pValue, valLen, 0, tag) != LS_OK)
            return;
        pValue = tag;
        valLen = 16;
    }

    if (isPriv)
    {
        pStore->getManager()->processPrivatePurgeCmd(&key,
                pValue, valLen, DateTime::s_curTime, DateTime::s_curTimeUs / 1000, stale);
        g_api->log(session, LSI_LOG_DEBUG,
                   "[%s] PURGE private cache for [%s]: %.*s\n",
                   ModuleNameStr, key.m_pIP, valLen, pValue);
    }
    else
    {
        pStore->getManager()->processPurgeCmd(
            pValue, valLen, DateTime::s_curTime, DateTime::s_curTimeUs / 1000, stale);
        g_api->log(session, LSI_LOG_DEBUG,  "[%s] PURGE public cache: %.*s\n",
                   ModuleNameStr, valLen, pValue);
    }

}


static void processPurge(const lsi_session_t *session, const char *pValue,
                         int valLen)
{
    const char *pBegin = pValue;
    const char *pEnd = pValue + valLen;
    const char *p;
    while(pBegin < pEnd)
    {
        while(pBegin < pEnd && isspace(*pBegin))
            ++pBegin;
        p = (const char *)memchr(pBegin, ';', pEnd - pBegin);
        if (!p)
            p = pEnd;
        if (p > pBegin)
        {
            processPurge2(session, pBegin, p - pBegin);
        }
        pBegin = (p < pEnd) ? p + 1 : pEnd;
    }
    g_api->log(session, LSI_LOG_DEBUG, "[%s] processPurge: %.*s\n",
               ModuleNameStr, valLen, pValue);
}


static void updateCacheEntry(CacheConfig *pConfig, CacheEntry *pEntry, int tmpfd,
                             char *tmppath, int compressType, off_t length)
{
    //length change
    pEntry->setPart2Len(length);

    //Update compressType
    pEntry->markReady(compressType);

    //Update fd
    close(pEntry->getFdStore());
    pEntry->setFdStore(tmpfd);

    //since the filepath is org path + .tmp, so just publish it to use it
    pConfig->getStore()->publish(pEntry);
}

static void closeTmpFile(int fd, const char *path)
{
    close(fd);
    unlink(path);
}

static void toggleGzipState(MyMData *myData, int needCompressType)
{
    if (needCompressType != LSI_NO_COMPRESS &&
        needCompressType != LSI_GZIP_COMPRESS)
        return ;

    /**
     * Too small content needn't to be compressed
     */
    if (needCompressType == LSI_GZIP_COMPRESS &&
        getEntryContentLength(myData) < 200)
        return ;

    /***
     * Because in child process, mydata maybe can not be accessed when it is
     * released, so save the config and entry now.
     */
    CacheConfig *pConfig = myData->pConfig;
    CacheEntry *pEntry = myData->pEntry;

    /***
     * Should not block the current processing
     */
    pid_t pid = fork();
    if (pid < 0)
    {
        g_api->log(NULL, LSI_LOG_ERROR, "[%s] toggleGzipState fork failed.\n",
               ModuleNameStr);
        return ;
    }

    if (pid > 0)
    {
        g_api->log(NULL, LSI_LOG_DEBUG,
               "[%s] toggleGzipState fork pid %d to processing.\n",
               ModuleNameStr, pid);
        return;
    }
    else //child process
    {
        char tmppath[4100] = {0};
        int len = 4096;
        pConfig->getStore()->getEntryFilePath(pEntry, tmppath, len);
        lstrncat(tmppath, ".tmp", sizeof(tmppath));

        /**
         * If the the file exists more than 10 minutes, should be something wrong
         * Even the state is updating, try to remove it
         */
        struct stat sb;
        if (stat(tmppath, &sb) != -1)
        {
            if((long)DateTime_s_curTime - (long)sb.st_ctime < 360)
                exit (0);
            else
            {
                g_api->log(NULL, LSI_LOG_DEBUG,
                           "[%s] toggleGzipState processing too long %ld seconds.\n",
                           ModuleNameStr,
                           (long)DateTime_s_curTime - (long)sb.st_ctime);
                unlink(tmppath);
            }
        }

        int tmpfd = ::open(tmppath, O_RDWR | O_CREAT | O_TRUNC | O_EXCL, 0760);
        if (tmpfd == -1)
        {
            g_api->log(NULL, LSI_LOG_DEBUG,
                           "[%s] toggleGzipState can not open file %s.\n",
                           ModuleNameStr, tmppath);
            exit (0);
        }

        ::fcntl(tmpfd, F_SETFD, FD_CLOEXEC);

        z_stream *zstream = new z_stream;
        if (!zstream)
        {
            g_api->log(NULL, LSI_LOG_ERROR, "[%s] toggleGzipState alloc"
                        " memory for zstream error.\n", ModuleNameStr);
            closeTmpFile(tmpfd, tmppath);
            exit (0);
        }

        bool compress = (needCompressType == LSI_GZIP_COMPRESS);
        if (initZstream(zstream, compress))
        {
            delete zstream;
            closeTmpFile(tmpfd, tmppath);
            g_api->log(NULL, LSI_LOG_ERROR, "[%s]toggleGzipState initZstream"
                        " error.\n", ModuleNameStr);
            exit (0);
        }

        int fd = pEntry->getFdStore();
        int part1offset = pEntry->getPart1Offset();
        int part2offset = pEntry->getPart2Offset();
        off_t length = pEntry->getContentTotalLen() -
                           (part2offset - part1offset);

        unsigned char *buff  = (unsigned char *)mmap((caddr_t)0,
                                                     part2offset + length,
                                                     PROT_READ, MAP_SHARED,
                                                     fd, 0);
        if (buff == (unsigned char *)(-1))
        {
            uninitZstream(zstream, compress);
            closeTmpFile(tmpfd, tmppath);
            g_api->log(NULL, LSI_LOG_ERROR, "[%s] toggleGzipState mmap"
                        " error.\n", ModuleNameStr);
            exit (0);
        }




        /**
         * Now set the updating flag
         */
        write(tmpfd, buff, part2offset);
        buff+= part2offset;

        off_t ret = compressbuf(zstream, compress, buff, length, tmpfd, 1);
        g_api->log(NULL, LSI_LOG_DEBUG,
                   "[%s] toggleGzipState write %lld bytes to file %s.\n",
                   ModuleNameStr, (long long)ret, tmppath);

        if (ret <= 0)
        {
            //failed
            closeTmpFile(tmpfd, tmppath);
            g_api->log(NULL, LSI_LOG_ERROR, "[%s] toggleGzipState compressbuf"
                        " error.\n", ModuleNameStr);
        }
        else
        {
            updateCacheEntry(pConfig, pEntry, tmpfd, tmppath, needCompressType, ret);
            g_api->log(NULL, LSI_LOG_DEBUG, "[%s] toggleGzipState updated"
                        " the cache entry.\n", ModuleNameStr);
        }
        uninitZstream(zstream, compress);

        munmap((caddr_t)buff, part2offset + length);
        exit(0);
    }
}


static int handlerProcess(const lsi_session_t *session)
{
    MyMData *myData = (MyMData *)g_api->get_module_data(session, &MNAME,
                      LSI_DATA_HTTP);
    if (!myData)
    {
        g_api->log(session, LSI_LOG_ERROR,
                   "[%s] internal error during handlerProcess.\n", ModuleNameStr);
        return 500;
    }

    //Disable RECV_RESP header here, to avoid cache myself
    disableRcvdRespHeaderFilter(session);

    if (myData->iMethod == HTTP_PURGE || myData->iMethod == HTTP_REFRESH)
    {
        /**
         * 1, if the context is protected by realm, need to pass the http Authorization
         * 2, if the context is not protected by realm, the IP must be defined as trust
         * 3, if the purgeUri is defined, and match the uri, purge
         * by the request purge header
         * 4, else purge all the entry with a tag which is caculated from the
         * url, no matter the qs, cookie and vary and so on.
         */

        //Comments: REFRESH is stale purge
        int stale = myData->iMethod == HTTP_REFRESH ? 1 : 0;
        char httpAuthEnv[3] = {0};
        int httpAuthEnvLen = g_api->get_req_env(session, "HttpAuth", 8,
                                                    httpAuthEnv, 2);
        if (httpAuthEnvLen <= 0 || *httpAuthEnv != '1')
        {
            int len;
            const char *pIP = g_api->get_client_ip(session, &len);
            if (g_api->get_client_access_level(session) != LSI_ACL_TRUST &&
                (!pIP || strncmp(pIP, "127.0.0.1", 9) != 0))
            {
                g_api->free_module_data(session, &MNAME, LSI_DATA_HTTP, releaseMData);
                g_api->log(session, LSI_LOG_DEBUG,
                   "[%s] Processed PURGE http AUTH error.\n", ModuleNameStr);
                return 405;
            }
        }


        bool bPurgeTags = false;
        char *pConfUri = myData->pConfig->getPurgeUri();

        if (pConfUri && strcasecmp(pConfUri, myData->pOrgUri) == 0)
            bPurgeTags = true;

        if (bPurgeTags)
        {
            int valLen = 0;
            const char *pVal = g_api->get_req_header_by_id(session,
                                                            LSI_HDR_LITESPEED_PURGE,
                                                            &valLen);
            if (pVal && valLen > 0)
            {
                processPurge(session, pVal, valLen);
                g_api->set_resp_content_length(session, valLen + 24);
                g_api->append_resp_body(session, "Processed PURGE by \"", 20);
                g_api->append_resp_body(session, pVal, valLen);
                g_api->append_resp_body(session, "\".\r\n", 4);
            }
            else
                g_api->append_resp_body(session, "PURGE tag not defined.\n",
                                        23);
        }
        else
        {
            //g_api->append_resp_body(session, "PURGE uri not match.\n", 21);
            /**
             * Now, will puge all cache with this URL
             */

            char tag[17] = {0};
            if (uriToTag(session, myData->pOrgUri + myData->hostPortLen,
                         myData->orgUriLen, 1, tag) != LS_OK)
            {
                g_api->free_module_data(session, &MNAME, LSI_DATA_HTTP, releaseMData);
                return 500;
            }
            purgeAllByTag(session, myData, tag, 16, stale);

            g_api->set_resp_content_length(session, myData->orgUriLen + 24);
            g_api->append_resp_body(session, "Processed ", 10);
            g_api->append_resp_body(session,
                                    stale ? "REFRESH" : "PURGE",
                                    stale ? 7 : 5);

            g_api->append_resp_body(session, " by \"", 5);
            g_api->append_resp_body(session, myData->pOrgUri + myData->hostPortLen,
                                    myData->orgUriLen);
            g_api->append_resp_body(session, "\".\r\n", 4);
        }

        g_api->log(NULL, LSI_LOG_DEBUG,
                    "[%s] Did %s %s with tag: %d.\n", ModuleNameStr,
                    stale ? "REFRESH" : "PURGE",
                    myData->pOrgUri, bPurgeTags);

        g_api->end_resp(session);
        g_api->free_module_data(session, &MNAME, LSI_DATA_HTTP, releaseMData);
        return 0;
    }

    myData->pEntry->setLastAccess(DateTime::s_curTime);

    char tmBuf[RFC_1123_TIME_LEN + 1];
    int len;
    int fd = myData->pEntry->getFdStore();
    CeHeader &CeHeader = myData->pEntry->getHeader();
    int compressType = myData->pEntry->getCompressType();

    int hitIdx = (myData->iCacheState == CE_STATE_HAS_PRIVATE_CACHE) ? 1 : 0;

    ((HttpSession *)session)->incStatsCacheHits(1 + hitIdx);

    char *buff = NULL;
    char *pBuffOrg = NULL;
    int part1offset = myData->pEntry->getPart1Offset();
    int part2offset = myData->pEntry->getPart2Offset();
    if (part2offset - part1offset > 0)
    {
#ifdef CACHE_RESP_HEADER
        if (myData->m_pEntry->m_sRespHeader.len() > 0) //has it
            buff = (char *)(myData->m_pEntry->m_sRespHeader.c_str());
        else
#endif
        {
            buff  = (char *)mmap((caddr_t)0, part2offset,
                                 PROT_READ, MAP_SHARED, fd, 0);
            if (buff == (char *)(-1))
            {
                g_api->log(session, LSI_LOG_ERROR,
                           "[%s] mmap() failed, fd: %d, size: %d, error: %s, "
                           "handlerProcess return 500.\n",
                           ModuleNameStr, fd, part2offset, strerror(errno));
                g_api->free_module_data(session, &MNAME, LSI_DATA_HTTP, releaseMData);
                return 500;
            }
            pBuffOrg = buff;
            buff += part1offset;
        }

        if (CeHeader.m_lenETag > 0)
        {
            char *pEtag = buff;
            AutoStr2 str;
            if (!isModified(session, CeHeader, buff, CeHeader.m_lenETag, str))
            {
                if (CeHeader.m_lenETag > 0)
                    g_api->set_resp_header(session, LSI_RSPHDR_ETAG, NULL, 0,
                                           str.c_str(), CeHeader.m_lenETag,
                                           LSI_HEADEROP_SET);

                g_api->set_resp_header(session, LSI_RSPHDR_UNKNOWN,
                                       s_x_cached, sizeof(s_x_cached) - 1,
                                       s_hits[hitIdx], s_hitsLen[hitIdx],
                                       LSI_HEADEROP_SET);

                g_api->set_status_code(session, 304);
                if (pBuffOrg)
                    munmap((caddr_t)pBuffOrg, part2offset);
                g_api->end_resp(session);
                g_api->free_module_data(session, &MNAME, LSI_DATA_HTTP, releaseMData);
                g_api->log(session, LSI_LOG_DEBUG,
                           "[%s] handlerProcess return 304.\n", ModuleNameStr);
                return 0;
            }

            if (CeHeader.m_lenETag > 4)
            {
                str.setStr(buff, CeHeader.m_lenETag);
                pEtag = (char *)str.c_str();

                char *pUpdate = pEtag + str.len() - 4;
                if (*pUpdate++ == ';')
                {
                    if (compressType == 0)
                    {
                        *pUpdate++ = ';';
                        *pUpdate++ = ';';
                    }
                    else if (compressType == 1)
                    {
                        *pUpdate++ = 'g';
                        *pUpdate++ = 'z';
                    }
                    else if (compressType == 2)
                    {
                        *pUpdate++ = 'b';
                        *pUpdate++ = 'r';
                    }
                }
            }

            g_api->set_resp_header(session, LSI_RSPHDR_ETAG, NULL, 0, pEtag,
                                   CeHeader.m_lenETag, LSI_HEADEROP_SET);
            g_api->log(session, LSI_LOG_DEBUG,
                       "[%s] handlerProcess add etag %s.\n",
                       ModuleNameStr, pEtag);
        }

        buff += CeHeader.m_lenETag + CeHeader.m_lenStxFilePath;
        len = part2offset - part1offset -
              CeHeader.m_lenETag - CeHeader.m_lenStxFilePath;
        g_api->set_resp_header2(session, buff, len, LSI_HEADEROP_SET);


        if (myData->hasCacheFrontend == 0)
        {
            g_api->remove_resp_header(session, LSI_RSPHDR_LITESPEED_CACHE_CONTROL,
                                      NULL, 0);
            g_api->remove_resp_header(session, LSI_RSPHDR_LITESPEED_TAG,
                                      NULL, 0);
            g_api->remove_resp_header(session, LSI_RSPHDR_LITESPEED_VARY,
                                      NULL, 0);
            g_api->remove_resp_header(session, LSI_RSPHDR_LITESPEED_PURGE,
                                      NULL, 0);
            g_api->remove_resp_header(session, -1, "X-LiteSpeed-Purge2", 18);

        }
    }

    if (CeHeader.m_tmLastMod != 0)
    {
        g_api->set_resp_header(session, LSI_RSPHDR_LAST_MODIFIED, NULL, 0,
                               DateTime::getRFCTime(CeHeader.m_tmLastMod, tmBuf),
                               RFC_1123_TIME_LEN, LSI_HEADEROP_SET);
    }

    g_api->set_resp_header(session, LSI_RSPHDR_UNKNOWN,
                           s_x_cached, sizeof(s_x_cached) - 1,
                           s_hits[hitIdx], s_hitsLen[hitIdx],
                           LSI_HEADEROP_SET);

    g_api->set_status_code(session, CeHeader.m_statusCode);
    g_api->log(session, LSI_LOG_DEBUG,
                       "[%s] handlerProcess set_status_code %d.\n",
                       ModuleNameStr, CeHeader.m_statusCode);

    //assert(strcasestr(myData->pOrgUri, "fonts/ProximaNova-Regular.woff") == NULL);

    int ret  = 0;
    if (myData->iMethod == HTTP_GET)
    {
        off_t length = myData->pEntry->getContentTotalLen() -
                       (part2offset - part1offset);

        if (compressType == LSI_NO_COMPRESS)
        {
            if (myData->reqCompressType == LSI_GZIP_COMPRESS)
                myData->pEntry->incHits();
            else if (myData->reqCompressType == compressType)
                myData->pEntry->clearHits();
        }
        else if (compressType == LSI_GZIP_COMPRESS)
        {
            g_api->set_resp_header(session, LSI_RSPHDR_CONTENT_ENCODING,
                                   NULL, 0, "gzip", 4, LSI_HEADEROP_SET);
            g_api->log(session, LSI_LOG_DEBUG,
                       "[%s] set_resp_header [Content-Encoding: gzip].\n",
                       ModuleNameStr);

            if (myData->reqCompressType == LSI_NO_COMPRESS)
                myData->pEntry->incHits();
            else if (myData->reqCompressType == compressType)
                myData->pEntry->clearHits();

        }
        else if (compressType == LSI_BR_COMPRESS)
        {
            g_api->set_resp_header(session, LSI_RSPHDR_CONTENT_ENCODING,
                                   NULL, 0, "br", 2, LSI_HEADEROP_SET);
            g_api->log(session, LSI_LOG_DEBUG,
                       "[%s] set_resp_header [Content-Encoding: br].\n",
                       ModuleNameStr);
        }
        g_api->set_resp_buffer_compress_method(session, compressType);


        g_api->set_resp_content_length(session, length);
        //int fd = myData->pEntry->getFdStore();

        g_api->log(session, LSI_LOG_DEBUG,
                   "[%s] handlerProcess fd %d, offset %d, length %ld\n",
                   ModuleNameStr, fd, part2offset, length);

        if (g_api->send_file2(session, fd, part2offset, length) == 0)
            g_api->end_resp(session);
        else
            ret = 500;

        /**
         * For testing, disable the code
         */
        if (myData->pEntry->getHits() >= 10)
        {
            g_api->log(session, LSI_LOG_DEBUG,
                       "[%s] handlerProcess check entry hit %ld times, "
                       "will change to compressType from %d to %d.\n",
                       ModuleNameStr, myData->pEntry->getHits(),
                       compressType, myData->reqCompressType);
            toggleGzipState(myData, myData->reqCompressType);
        }
    }
    else //HEAD
        g_api->end_resp(session);

    if (pBuffOrg)
        munmap((caddr_t)pBuffOrg, part2offset);
    g_api->free_module_data(session, &MNAME, LSI_DATA_HTTP, releaseMData);
    return ret;
}

lsi_reqhdlr_t cache_handler = { handlerProcess, NULL, NULL, NULL,
                                NULL, NULL, NULL,  };
lsi_confparser_t cacheDealConfig = { ParseConfig, FreeConfig, paramArray };
lsi_module_t cache = { LSI_MODULE_SIGNATURE, init, &cache_handler,
                       &cacheDealConfig, MODULE_VERSION_INFO, serverHooks, {0}
                     };
