// Copyright (C) 2022 Check Point Software Technologies Ltd. All rights reserved.

// Licensed under the Apache License, Version 2.0 (the "License");
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

#include "Waf2Engine.h"
#include "waap.h"
#include "WaapAssetState.h"
#include "CidrMatch.h"
#include "ParserRaw.h"
#include "ParserUrlEncode.h"
#include "ParserMultipartForm.h"
#include "ParserXML.h"
#include "ParserJson.h"
#include "ContentTypeParser.h"
#include "Waf2Util.h"
#include "debug.h"
#include "DeepAnalyzer.h"
#include "WaapConfigApplication.h"
#include "WaapConfigApi.h"
#include "WaapDefines.h"
#include "WaapTrigger.h"
#include "WaapScores.h"
#include "WaapDecision.h"
#include "WaapConversions.h"
#include "WaapResultJson.h"
#include "WaapAssetStatesManager.h"
#include "log_generator.h"
#include "config.h"
#include "WaapOverrideFunctor.h"
#include "WaapOpenRedirect.h"
#include "WaapOpenRedirectPolicy.h"
#include "WaapErrorDisclosurePolicy.h"
#include <boost/algorithm/string.hpp>
#include <boost/regex.hpp>
#include "generic_rulebase/parameters_config.h"
#include <iostream>
#include "ParserDelimiter.h"
#include "OpenRedirectDecision.h"
#include "config.h"
#include "LogGenWrapper.h"
#include "reputation_features_events.h"
#include "telemetry.h"
#include "agent_core_utilities.h"

USE_DEBUG_FLAG(D_WAAP);
USE_DEBUG_FLAG(D_WAAP_ULIMITS);
USE_DEBUG_FLAG(D_WAAP_BOT_PROTECTION);
USE_DEBUG_FLAG(D_OA_SCHEMA_UPDATER);
USE_DEBUG_FLAG(D_WAAP_HEADERS);


using namespace ReportIS;
using namespace std;

#define MAX_REQUEST_BODY_SIZE (2*1024)
#define MAX_RESPONSE_BODY_SIZE (2*1024)
#define MAX_RESPONSE_BODY_SIZE_ERR_DISCLOSURE (2*1024)
#define OVERRIDE_ACCEPT "Accept"
#define OVERRIDE_DROP "Drop"
#define OVERRIDE_IGNORE "Ignore"

// Score threshold below which the match won't be considered
#define SCORE_THRESHOLD (1.4f)

void Waf2Transaction::learnScore(ScoreBuilderData& data, const std::string &poolName)
{
    m_pWaapAssetState->scoreBuilder.analyzeFalseTruePositive(data, poolName, !m_ignoreScore);

    if (m_ignoreScore) // check if we are in building scores state
    {
        // Set the relative reputation to max to ensure learning fp in score builder
        data.m_relativeReputation = MAX_RELATIVE_REPUTATION;
    }
    m_pWaapAssetState->scoreBuilder.checkBadSourcesForLearning(
        data.m_relativeReputation,
        data.m_sourceIdentifier,
        data.m_userAgent);
}

void Waf2Transaction::start_response(int response_status, int http_version)
{
    dbgTrace(D_WAAP) << "[transaction:" << this << "] start_response(response_status=" << response_status
        << "," << " http_version=" << http_version << ")";
    m_responseStatus = response_status;

    if(m_responseStatus == 404)
    {
        // Create error limiting policy (lazy, on first request)
        if(m_siteConfig != NULL) {
            const std::shared_ptr<Waap::RateLimiting::Policy> errorLimitingPolicy =
                m_siteConfig->get_ErrorLimitingPolicy();

            if (errorLimitingPolicy && errorLimitingPolicy->getRateLimitingEnforcementStatus()) {
                if (m_pWaapAssetState->getErrorLimitingState() == nullptr) {
                    m_pWaapAssetState->createErrorLimitingState(errorLimitingPolicy);
                    dbgTrace(D_WAAP) << "Waf2Transaction::start_response: Create Error Limiting State";
                }

                bool errorLimitingLog = false;
                bool blockDueToErrorLimiting = Waap::ErrorLimiting::enforce
                    (m_source_identifier, m_uriPath, m_pWaapAssetState, errorLimitingLog);

                dbgTrace(D_WAAP) <<
                    "Waf2Transaction::start_response: response code: 404 :: Error Limiting Block : " <<
                        blockDueToErrorLimiting;

                auto decision = m_waapDecision.getDecision(ERROR_LIMITING_DECISION);
                decision->setLog(errorLimitingLog);
                decision->setBlock(blockDueToErrorLimiting);
            }
        }
    }
}

void Waf2Transaction::start_response_hdrs()
{
    dbgTrace(D_WAAP) << "[transaction:" << this << "] start_response_hdrs";
}

void Waf2Transaction::add_response_hdr(const char* name, int name_len, const char* value, int value_len)
{
    dbgTrace(D_WAAP) << "[transaction:" << this << "] add_response_hdr(name='" << std::string(name, name_len) <<
        "', value='" << std::string(value, value_len) << "')";

    // Detect location header and remember it's value
    static const char location[] = "location";

    auto openRedirectPolicy = m_siteConfig ? m_siteConfig->get_OpenRedirectPolicy() : NULL;
    if (openRedirectPolicy && openRedirectPolicy->enable &&
        memcaseinsensitivecmp(name, name_len, location, sizeof(location) - 1)) {
        std::string redirectUrl = std::string(value, value_len);
        dbgTrace(D_WAAP) << "Detected the redirect 'Location' header: '" << redirectUrl << "'";

        if (m_responseStatus >= 300 && m_responseStatus < 400 && m_openRedirectState.testRedirect(redirectUrl)) {
            dbgTrace(D_WAAP) << "Waf2Transaction::decideResponse: openRedirect detected (enforce=" <<
                openRedirectPolicy->enforce << ")";
            auto decision = std::dynamic_pointer_cast<OpenRedirectDecision>(
                m_waapDecision.getDecision(OPEN_REDIRECT_DECISION));
            decision->setLog(true);
            decision->setBlock(openRedirectPolicy->enforce);
            decision->setLink(redirectUrl);
        }
    }

    if (m_responseStatus >= 400 && m_responseStatus <= 599) {
        auto errorDisclosurePolicy = m_siteConfig ? m_siteConfig->get_ErrorDisclosurePolicy() : NULL;
        if (errorDisclosurePolicy && errorDisclosurePolicy->enable) {
            // Scan response header values
            Waf2ScanResult res;
            if (m_pWaapAssetState->apply(std::string(value, value_len), res, "resp_header")) {
                // Found some signatures in response!
                delete m_scanResult;
                m_scanResult = new Waf2ScanResult(res);
                dbgTrace(D_WAAP) << "found indicators in response header";
                auto decision = m_waapDecision.getDecision(ERROR_DISCLOSURE_DECISION);
                decision->setLog(true);
                decision->setBlock(errorDisclosurePolicy->enforce);
            }
        }
    }
}

void Waf2Transaction::end_response_hdrs()
{
    dbgTrace(D_WAAP) << "[transaction:" << this << "] end_response_hdrs";

    // Enable response body processing only if response scanning is enabled in policy
    auto errorDisclosurePolicy = m_siteConfig ? m_siteConfig->get_ErrorDisclosurePolicy() : NULL;
    m_responseInspectReasons.setErrorDisclosure(errorDisclosurePolicy && errorDisclosurePolicy->enable);

    // OpenRedirect is only interested to see response headers, the body
    m_responseInspectReasons.setOpenRedirect(false);
}

void Waf2Transaction::start_response_body()
{
    dbgTrace(D_WAAP) << "[transaction:" << this << "] start_response_body";
    m_response_body_bytes_received = 0;
    m_response_body.clear();
}

void Waf2Transaction::add_response_body_chunk(const char* data, int data_len)
{
    dbgTrace(D_WAAP) << "[transaction:" << this << "] add_response_body_chunk (" << data_len << " bytes)";
    m_response_body_bytes_received += data_len;

    auto errorDisclosurePolicy = m_siteConfig ? m_siteConfig->get_ErrorDisclosurePolicy() : NULL;
    if (errorDisclosurePolicy && errorDisclosurePolicy->enable &&
        (m_responseStatus >= 400 && m_responseStatus <= 599)) {
        // Collect up to MAX_RESPONSE_BODY_SIZE_ERR_DISCLOSURE of input data for each response
        if (m_response_body_err_disclosure.length() + data_len <= MAX_RESPONSE_BODY_SIZE_ERR_DISCLOSURE) {
            m_response_body_err_disclosure.append(data, (size_t)data_len);
        }
        else if (m_response_body_err_disclosure.length() < MAX_RESPONSE_BODY_SIZE_ERR_DISCLOSURE) {
            size_t piece = MAX_RESPONSE_BODY_SIZE_ERR_DISCLOSURE - m_response_body_err_disclosure.length();
            // Note: piece is guaranteed to be > data_len, so the write below is safe.
            m_response_body_err_disclosure.append(data, piece);
        }
        else {
            m_responseInspectReasons.setErrorDisclosure(false);
        }
    }

    if (m_response_body_err_disclosure.length() <= MAX_RESPONSE_BODY_SIZE_ERR_DISCLOSURE) {
        // Scan now, buffer is filled up.
        scanErrDisclosureBuffer();
    }

    // Collect up to MAX_RESPONSE_BODY_SIZE of input data for each response
    if (m_response_body.length() + data_len <= MAX_RESPONSE_BODY_SIZE) {
        m_response_body.append(data, (size_t)data_len);
    }
    else if (m_response_body.length() < MAX_RESPONSE_BODY_SIZE) {
        size_t piece = MAX_RESPONSE_BODY_SIZE - m_response_body.length();
        // Note: piece is guaranteed to be > data_len, so the write below is safe.
        m_response_body.append(data, piece);
    }
    else {
        // No more need to collect response body for log (got enough data - up to MAX_RESPONSE_BODY_SIZE collected)
        m_responseInspectReasons.setCollectResponseForLog(false);
    }
}

void Waf2Transaction::end_response_body()
{
    dbgTrace(D_WAAP) << "[transaction:" << this << "] end_response_body";
}

const std::string &
Waf2Transaction::getResponseBody()
{
    return m_response_body;
}

void Waf2Transaction::scanErrDisclosureBuffer()
{
    if (m_responseStatus >= 400 && m_responseStatus <= 599) {
        auto errorDisclosurePolicy = m_siteConfig ? m_siteConfig->get_ErrorDisclosurePolicy() : NULL;
        if (errorDisclosurePolicy && errorDisclosurePolicy->enable) {
                // Scan response body chunks.
                Waf2ScanResult res;
                if (m_pWaapAssetState->apply(std::string(m_response_body_err_disclosure.data(),
                    m_response_body_err_disclosure.size()), res, "resp_body")) {
                    // Found some signatures in response!
                    delete m_scanResult;
                    m_scanResult = new Waf2ScanResult(res);
                    dbgTrace(D_WAAP) << "found indicators in response body";
                    auto decision = m_waapDecision.getDecision(ERROR_DISCLOSURE_DECISION);
                    decision->setLog(true);
                    decision->setBlock(errorDisclosurePolicy->enforce);
                }
        }
    }
    m_responseInspectReasons.setErrorDisclosure(false);
}

void Waf2Transaction::end_response()
{
    dbgTrace(D_WAAP) << "[transaction:" << this << "] end_response";
}


void Waf2Transaction::setCurrentAssetState(IWaapConfig* sitePolicy)
{
    I_WaapAssetStatesManager* pWaapAssetStatesManager =
        Singleton::Consume<I_WaapAssetStatesManager>::by<WaapComponent>();
    std::shared_ptr<WaapAssetState> pCurrentWaapAssetState =
        pWaapAssetStatesManager->getWaapAssetStateById(sitePolicy->get_AssetId());

    if (!pCurrentWaapAssetState || pCurrentWaapAssetState->getSignatures()->fail())
    {
        dbgWarning(D_WAAP) << "[transaction:" << this << "] "
            "couldn't set waapAssetState for asset... using original waapAssetState";
        return;
    }
    m_pWaapAssetState = pCurrentWaapAssetState;
}

void Waf2Transaction::clearRequestParserState() {
    if (m_requestBodyParser != NULL) {
        delete m_requestBodyParser;
        m_requestBodyParser = NULL;
    }
}

Waf2Transaction::Waf2Transaction() :
    TableOpaqueSerialize<Waf2Transaction>(this),
    m_pWaapAssetState(NULL),
    m_ignoreScore(false),
    m_remote_port(0),
    m_local_port(0),
    m_csrfState(),
    m_userLimitsState(nullptr),
    m_siteConfig(NULL),
    m_contentType(Waap::Util::CONTENT_TYPE_UNKNOWN),
    m_requestBodyParser(NULL),
    m_tagHist{0},
    m_tagHistPos(0),
    m_isUrlValid(false),
    m_scanner(this),
    m_deepParser(m_pWaapAssetState, m_scanner, this),
    m_deepParserReceiver(m_deepParser),
    m_scanResult(NULL),
    m_response_body(),
    m_request_body_bytes_received(0),
    m_response_body_bytes_received(0),
    m_processedUri(false),
    m_processedHeaders(false),
    m_isHeaderOverrideScanRequired(false),
    m_isScanningRequired(false),
    m_responseStatus(0),
    m_responseInspectReasons(),
    m_responseInjectReasons(),
    m_practiceSubType("Web Application"),
    m_index(-1),
    m_triggerReport(false),
    is_schema_validation(false),
    m_waf2TransactionFlags(),
    m_temperature_detected(false)
{
    m_overrideOriginalMaxScore[OVERRIDE_ACCEPT] = 0;
    I_TimeGet *timeGet = Singleton::Consume<I_TimeGet>::by<Waf2Transaction>();
    m_entry_time = chrono::duration_cast<chrono::milliseconds>(timeGet->getMonotonicTime());
}

Waf2Transaction::Waf2Transaction(std::shared_ptr<WaapAssetState> pWaapAssetState) :
    TableOpaqueSerialize<Waf2Transaction>(this),
    m_pWaapAssetState(pWaapAssetState),
    m_ignoreScore(false),
    m_remote_port(0),
    m_local_port(0),
    m_csrfState(),
    m_userLimitsState(nullptr),
    m_siteConfig(NULL),
    m_contentType(Waap::Util::CONTENT_TYPE_UNKNOWN),
    m_requestBodyParser(NULL),
    m_tagHist{0},
    m_tagHistPos(0),
    m_isUrlValid(false),
    m_scanner(this),
    m_deepParser(m_pWaapAssetState, m_scanner, this),
    m_deepParserReceiver(m_deepParser),
    m_scanResult(NULL),
    m_response_body(),
    m_request_body_bytes_received(0),
    m_response_body_bytes_received(0),
    m_processedUri(false),
    m_processedHeaders(false),
    m_isHeaderOverrideScanRequired(false),
    m_isScanningRequired(false),
    m_responseStatus(0),
    m_responseInspectReasons(),
    m_responseInjectReasons(),
    m_practiceSubType("Web Application"),
    m_index(-1),
    m_triggerReport(false),
    is_schema_validation(false),
    m_waf2TransactionFlags(),
    m_temperature_detected(false)
{
    I_TimeGet *timeGet = Singleton::Consume<I_TimeGet>::by<Waf2Transaction>();
    m_entry_time = chrono::duration_cast<chrono::milliseconds>(timeGet->getMonotonicTime());
}


Waf2Transaction::~Waf2Transaction() {
    dbgTrace(D_WAAP) << "Waf2Transaction::~Waf2Transaction: deleting m_requestBodyParser";
    delete m_requestBodyParser;
    dbgTrace(D_WAAP) << "Waf2Transaction::~Waf2Transaction: deleting m_scanResult";
    delete m_scanResult;
}

HeaderType Waf2Transaction::detectHeaderType(const char* name, int name_len) {
    // Detect host header
    static const char host[] = "host";
    static const char user_agent[] = "user-agent";
    static const char content_type[] = "content-Type";
    static const char cookie[] = "cookie";
    static const char referer[] = "referer";
    static const char authorization[] = "authorization";
    dbgTrace(D_WAAP_HEADERS) << "Detecting header type for header >>>" << std::string(name, name_len) << "<<<";

    if (memcaseinsensitivecmp(name, name_len, host, sizeof(host) - 1)) {
        return HeaderType::HOST_HEADER;
    }
    if (memcaseinsensitivecmp(name, name_len, user_agent, sizeof(user_agent) - 1)) {
        return HeaderType::USER_AGENT_HEADER;
    }
    if (memcaseinsensitivecmp(name, name_len, content_type, sizeof(content_type) - 1)) {
        return HeaderType::CONTENT_TYPE_HEADER;
    }
    if (memcaseinsensitivecmp(name, name_len, cookie, sizeof(cookie) - 1)) {
        return HeaderType::COOKIE_HEADER;
    }
    if (memcaseinsensitivecmp(name, name_len, referer, sizeof(referer) - 1)) {
        return HeaderType::REFERER_HEADER;
    }
    if (memcaseinsensitivecmp(name, name_len, authorization, sizeof(authorization) - 1)) {
        return HeaderType::AUTHORIZATION_HEADER;
    }
    return UNKNOWN_HEADER;
}

HeaderType Waf2Transaction::checkCleanHeader(const char* name, int name_len, const char* value, int value_len) const
{
    if (m_pWaapAssetState != nullptr) {
        for (auto it = m_pWaapAssetState->getSignatures()->headers_re.begin();
            it != m_pWaapAssetState->getSignatures()->headers_re.end();
            ++it) {
            const std::string& reHeaderName = it->first;
            Regex* pRegex = it->second;
            if (memcaseinsensitivecmp(name, name_len, reHeaderName.data(), reHeaderName.size())) {
                dbgTrace(D_WAAP) << "[transaction:" << this << "] special header '" << std::string(name, name_len) <<
                    "' - scan with regex '" << pRegex->getName().c_str() << "' to determine cleanliness ...";
                if(pRegex->hasMatch(std::string(value, value_len))) {
                    dbgTrace(D_WAAP) << "[transaction:" << this << "] special header '" <<
                        std::string(name, name_len) << " is clean";
                    return CLEAN_HEADER;
                }
                return OTHER_KNOWN_HEADERS;
            }
        }

        static const std::string x_newrelic_id("x-newrelic-id");
        static const std::string x_newrelic_transaction("x-newrelic-transaction");
        if (memcaseinsensitivecmp(name, name_len, x_newrelic_id.data(), x_newrelic_id.size()) ||
                memcaseinsensitivecmp(name, name_len, x_newrelic_transaction.data(), x_newrelic_transaction.size())) {
            dbgTrace(D_WAAP) << "[transaction:" << this << "] special header '" << std::string(name, name_len) <<
                "' - detect base64 to determine cleanliness ...";

            std::string result;
            int decodedCount = 0;
            int deletedCount = 0;

            // Detect potential base64 matches
            Waap::Util::b64Decode(std::string(value, value_len), b64DecodeChunk, decodedCount, deletedCount, result);

            if (result.empty() && (decodedCount + deletedCount == 1)) {
                // Decoded 1 base64 chunk and nothing left behind it
                dbgTrace(D_WAAP) << "[transaction:" << this << "] special header '" <<
                    std::string(name, name_len) << " is clean";
                return CLEAN_HEADER;
            }
        }
    }
    return UNKNOWN_HEADER;
}

void Waf2Transaction::parseAuthorization(const char* value, int value_len, const std::string &header_name)
{
#ifdef NO_HEADERS_SCAN
    return;
#endif
    static const std::string authorization("authorization");

    dbgTrace(D_WAAP_HEADERS) << "[transaction:" << this
        << "] special header '" << header_name;

    std::string result;
    std::string v(value, value_len);
    int decodedCount;
    int deletedCount;
    static const char *bearer = "bearer ";
    static const std::string bearer_str(bearer);
    if (memcaseinsensitivecmp(value, sizeof(bearer) - 1, bearer, sizeof(bearer) - 1)) {
        dbgTrace(D_WAAP_HEADERS) << "Checking JWT chunk for alg = none" << v;
        // parse buffer v till first dot,
        // convert this chunk from base 64 to json string and get value for "alg" key
        // if alg == "none" then this is JWT with no signature  and we should block it
        // if alg != "none" then this is JWT with signature and we should not block it
        char* p = (char*)v.c_str() + bearer_str.size();
        char* p1 = strchr(p, '.');
        if (p1 != NULL) {
            *p1 = '\0';
            std::string header(p);
            alignBase64Chunk(header);
            Waap::Util::b64Decode(header, b64DecodeChunk, decodedCount, deletedCount, result);
            if (!result.empty()) {
                dbgTrace(D_WAAP_HEADERS) << "[transaction:" << this << "] special header '" << header_name
                    << " base64 encoded for JWT alg testing";
                // parse JSON string and get value for "alg" key and check its value to be equal to "none"
                // if alg == "none" then this is JWT with no signature  and we should block it
                boost::algorithm::to_lower(result);
                static bool err = false;
                static const SingleRegex invalid_jwt_alg_re(
                    "\"alg\"\\s*:\\s*\"none\"",
                    err,
                    "invalid_jwt_alg_"
                );
                if  (invalid_jwt_alg_re.hasMatch(result)) {
                    dbgTrace(D_WAAP_HEADERS) << "[transaction:" << this << "] special header '" << header_name
                        << " bad JWT algotitm detected, blocking";
                    Waf2ScanResult new_res = Waf2ScanResult();
                    new_res.location = "header";
                    new_res.param_name = header_name;
                    new_res.unescaped_line = header_name + ": " + result;
                    new_res.score = 9.0;
                    new_res.keyword_matches.push_back("alg:none");
                    new_res.scoreArray.push_back(9.0);
                    new_res.attack_types.insert("JWT");
                    if (m_scanResult != NULL) {
                        dbgTrace(D_WAAP_HEADERS) << "LastScanResult = " << m_scanResult;
                        m_scanResult->mergeFrom(new_res);
                    } else {
                        dbgTrace(D_WAAP_HEADERS) << "LastScanResult is NULL";
                        m_scanResult = new Waf2ScanResult(new_res);
                    }
                    return;
                }
                dbgTrace(D_WAAP_HEADERS) << "[transaction:" << this << "] special header '" << header_name
                    << " clean JWT algotitm detected";
                return;
            } else {
                parseGenericHeaderValue(header_name, value, value_len);
            }
        }
        return;
    }

    static const char *negotiate = "negotiate ";
    static const std::string negotiate_str(negotiate);
    if (memcaseinsensitivecmp(value, sizeof(negotiate) - 1, negotiate, sizeof(negotiate) - 1)) {
        v = v.substr(negotiate_str.size(), v.size() - negotiate_str.size());

        // Detect potential base64 match after the "Negotiate " prefix
        alignBase64Chunk(v);
        Waap::Util::b64Decode(v, b64DecodeChunk, decodedCount, deletedCount, result);
        if (result.empty() && (deletedCount + decodedCount == 1)) {
            // Decoded 1 base64 chunk and nothing left behind it
            dbgTrace(D_WAAP_HEADERS) << "[transaction:" << this << "] special header '" << header_name
                << " is clean - clean base64 for negotiate";
            return;
        } else {
            parseGenericHeaderValue(header_name, value, value_len);
        }
    }
}

// Methods below are callbacks that are called during HTTP transaction processing by the front-end server/proxy
void Waf2Transaction::start() {
    dbgTrace(D_WAAP) << "[Waf2Transaction::start():" << this << "] start";
    // TODO:: maybe guard against double call of this function by buggy client.
    m_contentType = Waap::Util::CONTENT_TYPE_UNKNOWN;
    m_remote_addr.clear();
    m_remote_port = 0;
    m_local_addr.clear();
    m_local_port = 0;
    m_request_body_bytes_received = 0;
    m_response_body_bytes_received = 0;
    m_requestBodyParser = NULL;
    m_methodStr.clear();
    m_uriStr.clear();
    m_uriPath.clear();
    m_uriReferer.clear();
    m_uriQuery.clear();
    m_contentTypeStr.clear();
    m_hostStr.clear();
    m_userAgentStr.clear();
    m_cookieStr.clear();
    m_notes.clear();
    m_source_identifier.clear();
    // TODO:: remove this! refactor extraction of kv_pairs!
    m_deepParser.clear();
    hdrs_map.clear();
    m_request_body.clear();
    m_response_body.clear();
    m_overrideStateByPractice.clear();
    m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION] = Waap::Override::State();
}

void Waf2Transaction::set_transaction_time(const char* log_time) {
    dbgTrace(D_WAAP) << "[transaction:" << this << "] set_transaction_time(log_time='" << log_time << "')";
    m_log_time = log_time;
}

void Waf2Transaction::set_transaction_remote(const char* remote_addr, int remote_port) {
    dbgTrace(D_WAAP) << "[transaction:" << this << "] set_transaction_remote('" << remote_addr << ":" << remote_port <<
        "')";
    m_remote_addr = remote_addr;
    m_remote_port = remote_port;
    m_source_identifier = remote_addr;
}

void Waf2Transaction::set_transaction_local(const char* local_addr, int local_port) {
    dbgTrace(D_WAAP) << "[transaction:" << this << "] set_transaction_local('" << local_addr << ":" << local_port <<
        "')";
    m_local_addr = local_addr;
    m_local_port = local_port;
}

void Waf2Transaction::set_method(const char* method) {

    dbgTrace(D_WAAP) << "[transaction:" << this << "] set_method('" << method << "')";
    m_methodStr = method;
}


bool Waf2Transaction::checkIsScanningRequired()
{
    bool result = false;

    if (WaapConfigAPI::getWaapAPIConfig(m_ngenAPIConfig)) {
        m_siteConfig = &m_ngenAPIConfig;
        auto rateLimitingPolicy = m_siteConfig ? m_siteConfig->get_RateLimitingPolicy() : NULL;
        result |= m_siteConfig->get_WebAttackMitigation();
        if(rateLimitingPolicy) {
            result |= m_siteConfig->get_RateLimitingPolicy()->getRateLimitingEnforcementStatus();
        }

        auto userLimitsPolicy = m_siteConfig ? m_siteConfig->get_UserLimitsPolicy() : nullptr;
        if (userLimitsPolicy) {
            result = true;
        }
    } else if (WaapConfigApplication::getWaapSiteConfig(m_ngenSiteConfig)) {
        m_siteConfig = &m_ngenSiteConfig;
        auto rateLimitingPolicy = m_siteConfig ? m_siteConfig->get_RateLimitingPolicy() : NULL;
        auto errorLimitingPolicy = m_siteConfig ? m_siteConfig->get_ErrorLimitingPolicy() : NULL;
        auto csrfPolicy = m_siteConfig ? m_siteConfig->get_CsrfPolicy() : NULL;
        auto userLimitsPolicy = m_siteConfig ? m_siteConfig->get_UserLimitsPolicy() : nullptr;
        result |= m_siteConfig->get_WebAttackMitigation();

        if (rateLimitingPolicy) {
            result |= m_siteConfig->get_RateLimitingPolicy()->getRateLimitingEnforcementStatus();
        }
        if (errorLimitingPolicy) {
            result |= m_siteConfig->get_ErrorLimitingPolicy()->getRateLimitingEnforcementStatus();
        }
        if (csrfPolicy) {
            result |= m_siteConfig->get_CsrfPolicy()->enable;
        }
        if (userLimitsPolicy) {
            result = true;
        }
    }
    return result;
}

bool Waf2Transaction::setCurrentAssetContext()
{
    // the return value tells me if I need to scan traffic
    bool result = false;
    m_siteConfig = NULL;

    result |= checkIsScanningRequired();

    if (!m_siteConfig) {
        dbgDebug(D_WAAP) << "[transaction:" << this << "] "
            "Failed to set sitePolicy for asset... using the original signatures";
        return result;
    }

    setCurrentAssetState(m_siteConfig);
    m_deepParser.setWaapAssetState(m_pWaapAssetState);
    m_pWaapAssetState->updateFilterManagerPolicy(m_siteConfig);
    m_pWaapAssetState->clearFilterVerbose();

    return result;
}

void Waf2Transaction::processUri(const std::string &uri, const std::string& scanStage) {
    m_processedUri = true;
    size_t uriSize = uri.length();
    const char* p = uri.c_str();
    const char* uriEnd = p+uriSize;
    std::string baseUri;
    char querySep = '?';
    char paramSep = '&';

    // TODO:: refactor out this block to method, and the next block (parsing url parameters), too.
    {
        bool pushed = false;
        bool firstPush = true;

        // Parse URL
        ParserRaw urlParser(m_deepParserReceiver, 0, scanStage);

        // Scan the uri until '?' or ';' character found, whichever comes first (or until end of the uri string),
        // Do not account for last character as valid separator
        do {
            const char* q = strpbrk(p, "?;");

            if (q != NULL && q < uriEnd-1) {
                querySep = *q;

                // Handle special case found in customer traffic where instead of '?' there was a ';' character.
                if (querySep == ';') {
                    // Check that after ';' the parameter name is valid and terminated with '='. This would normally be
                    // the case in legit traffic, but not in attacks. This covers a case of "sap login".
                    const char *qq;
                    for (qq = q + 1;
                            qq < uriEnd && (isalpha(*qq) || isdigit(*qq) || *qq=='-' || *qq=='_' || *qq=='*');
                            ++qq);
                    if (*qq != '=') {
                        // Assume it might be attack and cancel the separation by the ';' character (scan whole URL)
                        q = NULL;
                    }
                    else {
                        const char *qqSep = strpbrk(qq, "&;");
                        // Handle special case (deprecated standard) where instead of '&' there was a ';' separator,
                        // Do not account for last character as valid separator
                        if (qqSep && qqSep < uriEnd-1) {
                            paramSep = *qqSep;
                        }
                    }
                }
            }

            if (q == NULL) {
                dbgTrace(D_WAAP) << "Query separator not found, use entire uri as baseUri";
                baseUri = std::string(uri);
                if (scanStage == "url") {
                    m_uriPath = baseUri;
                }
                if (firstPush) {
                    dbgTrace(D_WAAP) << "[transaction:" << this << "] scanning the " << scanStage.c_str();
                    firstPush = false;
                }

                // Push the last piece to URL scanner
                pushed = true;
                std::string url(uri);

                urlParser.push(url.data(), url.size());

                // We found no '?' character so set p to NULL to prevent parameters scan below.
                p = NULL;
                break;
            }

            baseUri = std::string(p, q - p);
            if (scanStage == "url") {
                m_uriPath = baseUri;
            }

            // Push data between last point (p) and the character we found ('?'), not includig the character.
            if (q != p) {
                // Just so we print this trace message only once
                if (firstPush) {
                    dbgTrace(D_WAAP) << "[transaction:" << this << "] scanning the " << scanStage.c_str();
                    firstPush = false;
                }

                pushed = true;
                std::string url(p, q-p);
                urlParser.push(url.data(), url.size());
            }

            // If we hit the '?' character, finish parsing the URL and continue parsing URL
            // parameters from the character next to '?'
            p = q + 1;
            break;
        } while (p && p < uriEnd);

        if (pushed) {
            urlParser.finish();
            m_notes.push_back(scanStage + "_scanned");
        }
    }
    // in case we found any indication in one of the URI segments and there is not one that starts with /
    // scan the whole URI
    if (m_scanResult && m_scanResult->score != 0 && (m_scanResult->location == scanStage) &&
        std::find_if(m_scanResult->keyword_matches.begin(),
            m_scanResult->keyword_matches.end(), [](std::string keyword) { return keyword[0] == '/'; }) ==
        m_scanResult->keyword_matches.end())
    {
        auto scanResultBackup = m_scanResult;
        m_scanResult = nullptr;
        bool ignoreScore = m_ignoreScore;
        m_ignoreScore = true;
        m_deepParser.m_key.push(scanStage.c_str(), scanStage.size());
        ParserDelimiter uriSegmentsParser(m_deepParserReceiver, 0, '/', scanStage);
        std::string baseUriUnescaped(baseUri);
        Waap::Util::decodePercentEncoding(baseUriUnescaped);
        uriSegmentsParser.push(baseUriUnescaped.c_str(), baseUriUnescaped.length());
        uriSegmentsParser.finish();
        m_deepParser.m_key.pop(scanStage.c_str());
        m_ignoreScore = ignoreScore;
        if (uriSegmentsParser.error())
        {
            // handle special case where there is no / in the URI - can happen in attackes
            m_deepParserReceiver.clear();
            delete m_scanResult;
            m_scanResult = scanResultBackup;
        }
        else {
            if (m_scanResult)
            {
                // keep original scan of the whole URL
                delete m_scanResult;
                m_scanResult = scanResultBackup;
            }
            else
            {
                // scan result is empty when we parsing each segments
                // i.e. scan result from using (acceptable) irregular format in the URI - discarding the original scan
                delete scanResultBackup;
            }
        }
    }
    // at this point, p can either be NULL (if there are no URL parameters),
    // or point to the parameters string (right after the '?' character)

    if (p && p < uriEnd && *p) {
        // Decode URLEncoded data and send decoded key/value pairs to deep inspection
        dbgTrace(D_WAAP) << "[transaction:" << this << "] scanning the " << scanStage.c_str() << " parameters";

        if (scanStage == "url") {
            m_uriQuery = std::string(p);
        }

        dbgTrace(D_WAAP) << "Query separator='" << querySep << "', " << "Param separator='" << paramSep << "'";

        std::string tag = scanStage + "_param";
        m_deepParser.m_key.push(tag.data(), tag.size());
        size_t buff_len = uriEnd - p;

        bool should_decode = checkUrlEncoded(p, buff_len);
        if (should_decode) {
            std::string url_as_string(p, buff_len);
            should_decode = should_decode && (Waap::Util::testUrlBadUtf8Evasion(url_as_string) != true);
       }
        dbgTrace(D_WAAP) << "should_decode % = " << should_decode;

        ParserUrlEncode up(m_deepParserReceiver, 0, paramSep, should_decode, false);
        up.push(p, buff_len);
        up.finish();
        m_deepParser.m_key.pop(tag.c_str());
        m_notes.push_back(scanStage + "_params_scanned");
    }
}

void Waf2Transaction::parseContentType(const char* value, int value_len)
{
    // content type header parser
    ContentTypeParser ctp;

    ctp.push(value, value_len);
    ctp.finish();

    dbgTrace(D_WAAP_HEADERS) << "[transaction:" << this << "] ctp detected content type: '" <<
        ctp.contentTypeDetected.c_str() << "'";
    // The above fills m_contentTypeDetected
    m_contentType = Waap::Util::detectContentType(ctp.contentTypeDetected.c_str());

    // extract boundary string required for parsing multipart-form-data stream
    if (m_contentType == Waap::Util::CONTENT_TYPE_MULTIPART_FORM) {
        dbgTrace(D_WAAP_HEADERS) << "content_type detected: " << Waap::Util::getContentTypeStr(m_contentType) <<
            "; boundary='" << ctp.boundaryFound.c_str() << "'";
        m_deepParser.setMultipartBoundary(ctp.boundaryFound);
    }
    else {
        dbgTrace(D_WAAP) << "content_type detected: " << Waap::Util::getContentTypeStr(m_contentType);
    }

    std::string contentTypeFull(value, value_len);
    // Use content-type trimmed by the first ';' character
    m_contentTypeStr = contentTypeFull.substr(0, contentTypeFull.find(";"));
}

void Waf2Transaction::parseCookie(const char* value, int value_len)
{
    m_cookieStr = std::string(value, value_len);

#ifdef NO_HEADERS_SCAN
    return;
#endif

    if (value_len > 0) {
        dbgTrace(D_WAAP_HEADERS) << "[transaction:" << this << "] scanning the cookie value";
        m_deepParser.m_key.push("cookie", 6);
        ParserUrlEncode cookieValueParser(m_deepParserReceiver, 0, ';', false, false);
        cookieValueParser.push(value, value_len);
        cookieValueParser.finish();
        m_deepParser.m_key.pop("cookie");
        m_notes.push_back("cookie_scanned");
    }
}

void Waf2Transaction::parseReferer(const char* value, int value_len)
{
#ifdef NO_HEADERS_SCAN
    return;
#endif
    dbgTrace(D_WAAP_HEADERS) << "Parsed Referer. Referer URI: " << m_uriReferer;

    std::string referer(value, value_len);
    std::vector<RegexMatch> regex_matches;
    size_t uriParsedElements =
        m_pWaapAssetState->getSignatures()->uri_parser_regex.findAllMatches(referer, regex_matches);
    if(uriParsedElements > 0)
    {
        RegexMatch::MatchGroup& uriPathGroup = regex_matches[0].groups[3];
        m_uriReferer = uriPathGroup.value;
        m_uriReferer = normalize_uri(m_uriReferer);
    }
    // Parse referer value as if it was a URL
    if (value_len > 0)
    {
        processUri(std::string(value, value_len), "referer");
    }
}

void Waf2Transaction::parseUnknownHeaderName(const char* name, int name_len)
{
#ifdef NO_HEADERS_SCAN
    return;
#endif
    // Apply signatures on all other, header names, unless they are considered "good" ones to skip scanning them.
    if (name_len &&
        !m_pWaapAssetState->getSignatures()->good_header_name_re.hasMatch(std::string(name, name_len))) {
        dbgTrace(D_WAAP_HEADERS) << "[transaction:" << this << "] scanning the header name";
        m_deepParser.m_key.push("header", 6);
        ParserRaw headerNameParser(m_deepParserReceiver, 0, std::string(name,  name_len));
        headerNameParser.push(name, name_len);
        headerNameParser.finish();
        m_deepParser.m_key.pop("header name");
        m_notes.push_back("hn:" + std::string(name, name_len));
    }
}

void Waf2Transaction::parseGenericHeaderValue(const std::string &headerName, const char* value, int value_len)
{
#ifdef NO_HEADERS_SCAN
    return;
#endif
    if (value_len == 0) {
        return;
    }

    dbgTrace(D_WAAP_HEADERS) << "[transaction:" << this << "] scanning the header value";
    m_deepParser.m_key.push("header", 6);
    ParserRaw headerValueParser(m_deepParserReceiver, 0, headerName);
    headerValueParser.push(value, value_len);
    headerValueParser.finish();
    m_deepParser.m_key.pop("header value");
    m_notes.push_back("hv:" + headerName);
};

// Scan relevant headers to detect attacks inside them
void Waf2Transaction::scanSpecificHeader(const char* name, int name_len, const char* value, int value_len)
{
    HeaderType header_t = detectHeaderType(name, name_len);
    std::string headerName = std::string(name, name_len);
    dbgTrace(D_WAAP_HEADERS) << "Processing the header " << headerName;
    switch (header_t)
    {
    case HeaderType::COOKIE_HEADER:
        parseCookie(value, value_len);
        break;
    case HeaderType::REFERER_HEADER:
        parseReferer(value, value_len);
        break;
    case HeaderType::AUTHORIZATION_HEADER:
        parseAuthorization(value, value_len, headerName);
        break;
    case HeaderType::UNKNOWN_HEADER: {
        HeaderType headerType = checkCleanHeader(name, name_len, value, value_len);
        if(headerType == HeaderType::CLEAN_HEADER) {
            break;
        }
        // Scan names of all unknown headers
        parseUnknownHeaderName(name, name_len);
        // Scan unknown headers whose values do not match "clean generic header" pattern.
        // Note that we do want to process special header named x-chkp-csrf-token header - it is treated specially.
        parseGenericHeaderValue(headerName, value, value_len);
        break;
    }
    case HeaderType::USER_AGENT_HEADER: {
        HeaderType headerType = checkCleanHeader(name, name_len, value, value_len);
        if(headerType == HeaderType::CLEAN_HEADER) {
            break;
        }
        // In case the user agent header contains a known regex match, remove the match before scanning
        std::string hdrValue(value, value_len);
        hdrValue = NGEN::Regex::regexReplace(
            __FILE__,
            __LINE__,
            hdrValue,
            m_pWaapAssetState->getSignatures()->user_agent_prefix_re,
            ""
        );
        parseGenericHeaderValue(headerName, hdrValue.data(), hdrValue.size());
        break;
    }
    case HeaderType::CONTENT_TYPE_HEADER: {
        HeaderType headerType = checkCleanHeader(name, name_len, value, value_len);
        if(headerType == HeaderType::CLEAN_HEADER) {
            break;
        }
        // Parsing of a known header will only take place if its value does not match strict rules and is therefore
        // suspected to contain an attack
        parseGenericHeaderValue(headerName, value, value_len);
        break;
    }
    default:
        break;
    }
};

// Read headers to extract information from them (like hostname from the Host: header). Do not scan them for attacks.
void Waf2Transaction::detectSpecificHeader(const char* name, int name_len, const char* value, int value_len)
{
    HeaderType header_t = detectHeaderType(name, name_len);

    switch (header_t)
    {
    case HeaderType::CONTENT_TYPE_HEADER:
        parseContentType(value, value_len);
        break;
    case HeaderType::HOST_HEADER:
        m_hostStr = std::string(value, value_len);
        break;
    case HeaderType::USER_AGENT_HEADER:
        m_userAgentStr = std::string(value, value_len);
        break;
    default:
        break;
    }
}

void Waf2Transaction::detectHeaders()
{
    if (isUrlLimitReached(m_uriStr.size())) {
        dbgTrace(D_WAAP_ULIMITS) << "[USER LIMITS] Url limit exceeded";
        return;
    }
    else if (!isPreventModeValidMethod(getMethod())) {
        dbgTrace(D_WAAP_ULIMITS) << "[USER LIMITS] Invalid http method: " << getMethod();
        return;
    }

    for (auto it = hdrs_map.begin(); it != hdrs_map.end(); ++it)
    {
        if (isHttpHeaderLimitReached(it->first, it->second)) {
            dbgTrace(D_WAAP_ULIMITS) << "[USER LIMITS] Http header limit exceeded";
            return;
        }
        detectSpecificHeader(it->first.c_str(), it->first.size(),
            it->second.c_str(), it->second.size());
    }
}

void Waf2Transaction::scanHeaders()
{
    m_processedHeaders = true;

    // Scan relevant headers for attacks
    for (auto it = hdrs_map.begin(); it != hdrs_map.end(); ++it)
    {
        scanSpecificHeader(it->first.c_str(), it->first.size(),
            it->second.c_str(), it->second.size());
    }
}

void Waf2Transaction::set_uri(const char* uri) {
    dbgTrace(D_WAAP) << "[transaction:" << this << "] set_uri('" << uri << "')";
    m_uriStr = uri;
}

void Waf2Transaction::set_host(const char* host) {
    dbgTrace(D_WAAP) << "[transaction:" << this << "] set_host('" << host << "')";
    m_hostStr = host;
}

void Waf2Transaction::start_request_hdrs() {
    dbgTrace(D_WAAP) << "[transaction:" << this << "] start_request_hdrs";
    // Clear all things that will be filled by the incoming request headers that will follow
    m_contentType = Waap::Util::CONTENT_TYPE_UNKNOWN;
    m_requestBodyParser = NULL;
}

void Waf2Transaction::add_request_hdr(const char* name, int name_len, const char* value, int value_len) {
    dbgTrace(D_WAAP) << "[transaction:" << this << "] add_request_hdr(name='" << std::string(name, name_len) <<
        "', value='" << std::string(value, value_len) << "')";
    std::string header_name(name, name_len);
    boost::algorithm::to_lower(header_name);
    hdrs_map[header_name] = std::string(value, value_len);
    if (header_name == "host") {
        m_hostStr = hdrs_map[header_name];
    }
}

void Waf2Transaction::end_request_hdrs() {
    dbgFlow(D_WAAP) << "[transaction:" << this << "] end_request_hdrs";
    m_isScanningRequired = setCurrentAssetContext();

    extractEnvSourceIdentifier();

    m_pWaapAssetState->m_requestsMonitor->logSourceHit(m_source_identifier);
    IdentifiersEvent ids(m_source_identifier, m_pWaapAssetState->m_assetId);
    ids.notify();
    // Read relevant headers and extract meta information such as host name
    // Do this before scanning the URL because scanning URL might require this information.
    if (m_isScanningRequired) {
        createUserLimitsState();
        detectHeaders();
    }
    // Scan URL and url query
    if (m_isScanningRequired && !m_processedUri) {
        processUri(m_uriStr, "url");
    }
    // Scan relevant headers for attacks
    if (m_isScanningRequired && !m_processedHeaders) {
        scanHeaders();
    }

    // Chack after scanning the URL and headers so we have the valuse for state.
    if (m_siteConfig != NULL)
    {
        m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION] = getOverrideState(m_siteConfig);
    }

    if(m_isScanningRequired && isUserLimitReached()) {
        return;
    }


    if(m_siteConfig != NULL) {
        // Create rate limiting policy (lazy, on first request)
        const std::shared_ptr<Waap::RateLimiting::Policy> rateLimitingPolicy = m_siteConfig->get_RateLimitingPolicy();
        if(rateLimitingPolicy && rateLimitingPolicy->getRateLimitingEnforcementStatus())
        {
            if (m_pWaapAssetState->getRateLimitingState() == nullptr)
            {
                m_pWaapAssetState->createRateLimitingState(rateLimitingPolicy);
            }
            dbgTrace(D_WAAP) << "(Waf2Engine::end_request_hdrs): RateLimiting check starts.";

            // Get current clock time
            I_TimeGet* timer = Singleton::Consume<I_TimeGet>::by<WaapComponent>();

            // The rate limiting state tracks rate limiting information for all sources
            std::shared_ptr<Waap::RateLimiting::State> rateLimitingState = m_pWaapAssetState->getRateLimitingState();

            std::chrono::seconds now = std::chrono::duration_cast<std::chrono::seconds>(timer->getMonotonicTime());

            bool logRateLimiting = false;
            if (rateLimitingState && (rateLimitingState->execute
                (m_source_identifier, m_uriPath, now, logRateLimiting) == false))
            {
                dbgTrace(D_WAAP) << "(Waf2Engine::end_request_hdrs): RateLimiting  decision: Block.";
                // block request due to rate limiting
                auto decision = m_waapDecision.getDecision(RATE_LIMITING_DECISION);
                decision->setBlock(true);
                decision->setLog(logRateLimiting);
            }
        }
        else {
            dbgTrace(D_WAAP) << "(Waf2Engine::end_request_hdrs): No rate limiting policy.";
        }
    }
}

void Waf2Transaction::start_request_body() {
    dbgTrace(D_WAAP) << "[transaction:" << this << "] start_request_body: m_contentType=" << m_contentType;

    clearRequestParserState();


    m_requestBodyParser = new ParserRaw(m_deepParserReceiver, 0, "body");

    m_request_body_bytes_received = 0;
    m_request_body.clear();
}

void Waf2Transaction::add_request_body_chunk(const char* data, int data_len) {
    dbgTrace(D_WAAP) << "[transaction:" << this << "] add_request_body_chunk (" << data_len << " bytes): parser='" <<
        (m_requestBodyParser ? m_requestBodyParser->name() : "none") << "': '" << std::string(data, data_len) << "'";

    if (isHttpBodyLimitReached(data_len)) {
        dbgTrace(D_WAAP_ULIMITS) << "[USER LIMITS] Http body limit exceeded";
        return;
    }
    m_request_body_bytes_received += data_len;
    size_t maxSizeToScan = m_request_body_bytes_received;

    if (m_siteConfig != NULL)
    {
        auto waapParams = m_siteConfig->get_WaapParametersPolicy();
        if (waapParams != nullptr)
        {
            std::string maxSizeToScanStr = waapParams->getParamVal("max_body_size", "");
            if (maxSizeToScanStr != "")
            {
                maxSizeToScan = std::stoul(maxSizeToScanStr.c_str());
            }
        }
    }

    if (m_isScanningRequired && m_request_body_bytes_received <= maxSizeToScan)
    {
        if (m_requestBodyParser != NULL) {
            m_requestBodyParser->push(data, data_len);
            if (isObjectDepthLimitReached(m_deepParser.getLocalMaxObjectDepth())) {
                dbgTrace(D_WAAP_ULIMITS) << "[USER LIMITS] Object depth limit exceeded";
                return;
            }
        }
        else {
            dbgWarning(D_WAAP) << "[transaction:" << this << "] add_request_body_chunk (" << data_len <<
                " bytes): parser='NONE'. This is most probably a bug. "
                "Some parser MUST be installed for this transaction!";
        }
    }

    // Collect up to MAX_REQUEST_BODY_SIZE of input data for each request
    if (m_request_body.length() + data_len <= MAX_REQUEST_BODY_SIZE) {
        m_request_body.append(data, (size_t)data_len);
    }
    else if (m_request_body.length() < MAX_REQUEST_BODY_SIZE) {
        size_t piece = MAX_REQUEST_BODY_SIZE - m_request_body.length();
        // Note: piece is guaranteed to be > data_len, so the write below is safe.
        m_request_body.append(data, piece);
    }
}

void Waf2Transaction::end_request_body() {
    dbgTrace(D_WAAP) << "[transaction:" << this << "] end_request_body";

    if (m_requestBodyParser != NULL) {
        m_requestBodyParser->finish();
        if (isObjectDepthLimitReached(m_deepParser.getLocalMaxObjectDepth())) {
            dbgTrace(D_WAAP_ULIMITS) << "[USER LIMITS] Object depth limit exceeded";
        }

        if (m_contentType != Waap::Util::CONTENT_TYPE_UNKNOWN && m_request_body.length() > 0) {
            m_deepParser.m_key.pop("body");
        }
    }

    // Check and output [ERROR] message if keyStack is not empty (it should be empty here).
    if (!m_deepParser.m_key.empty()) {
        dbgWarning(D_WAAP) << "[transaction:" << this << "] end_request_body: parser='" <<
            (m_requestBodyParser ? m_requestBodyParser->name() : "<NONE>") <<
            "'. ERROR: m_key is not empty. full key='" << m_deepParser.m_key.c_str() << "'";
    }

    clearRequestParserState();
}

void Waf2Transaction::end_request() {
    dbgTrace(D_WAAP) << "[transaction:" << this << "] end_request";
    clearRequestParserState();

    // Enable response headers processing only if values parsed from request contained at least one URL
    auto openRedirectPolicy = m_siteConfig ? m_siteConfig->get_OpenRedirectPolicy() : NULL;
    if (openRedirectPolicy && openRedirectPolicy->enable && !m_openRedirectState.empty()) {
        m_responseInspectReasons.setOpenRedirect(true);
    }

    auto errorLimitingPolicy = m_siteConfig ? m_siteConfig->get_ErrorLimitingPolicy() : NULL;
    if (errorLimitingPolicy && errorLimitingPolicy->getRateLimitingEnforcementStatus()) {
        m_responseInspectReasons.setErrorLimiter(true);
    }

    auto rateLimitingPolicy = m_siteConfig ? m_siteConfig->get_RateLimitingPolicy() : NULL;
    if (rateLimitingPolicy && rateLimitingPolicy->getRateLimitingEnforcementStatus()) {
        m_responseInspectReasons.setRateLimiting(true);
    }

    auto securityHeadersPolicy = m_siteConfig ? m_siteConfig->get_SecurityHeadersPolicy() : NULL;
    if (securityHeadersPolicy && securityHeadersPolicy->m_securityHeaders.enable) {
        m_responseInjectReasons.setSecurityHeaders(true);
        if (m_pWaapAssetState->getSecurityHeadersState() == nullptr)
        {
            m_pWaapAssetState->createSecurityHeadersState(securityHeadersPolicy);
        }
        dbgTrace(D_WAAP) << "(Waf2Engine::end_request): Security Headers State was created";
    }


    // Enable response headers processing if response scanning is enabled in policy
    auto errorDisclosurePolicy = m_siteConfig ? m_siteConfig->get_ErrorDisclosurePolicy() : NULL;
    m_responseInspectReasons.setErrorDisclosure(errorDisclosurePolicy && errorDisclosurePolicy->enable);
}

void Waf2Transaction::extractEnvSourceIdentifier()
{
    auto env = Singleton::Consume<I_Environment>::by<WaapComponent>();
    auto env_source_identifiers = env->get<std::string>("sourceIdentifiers");
    if (!env_source_identifiers.ok() || env_source_identifiers.unpack().empty()) {
        dbgInfo(D_WAAP) << "Could not extract source identifier from the environment";
        return;
    }

    // Take the first source identifier in set provided by the environment
    dbgTrace(D_WAAP) << "Set source identifier from the Environment";
    m_source_identifier = *(env_source_identifiers);
}

void Waf2Transaction::finish() {
    dbgTrace(D_WAAP) << "[transaction:" << this << "] finish";
    clearRequestParserState();
}

void Waf2Transaction::set_ignoreScore(bool ignoreScore) {
    m_ignoreScore = ignoreScore;
}

bool Waf2Transaction::get_ignoreScore() const
{
    auto maybe_override_ignore_score = getProfileAgentSetting<std::string>("agent.waap.alwaysReportScore");
    if (maybe_override_ignore_score.ok()) {
        return maybe_override_ignore_score.unpack() == "true";
    }
    return m_ignoreScore;
}

void
Waf2Transaction::decide(
    bool& bForceBlock,
    bool& bForceException,
    int mode)
{
    dbgTrace(D_WAAP) << "[transaction:" << this << "] decide (m_scanResult=" << m_scanResult << ")...";

    int bSendResponse = false;

    // If WAF stage1 found suspicious request - send it to stage2 and wait for decision.
    if (m_scanResult) {
        bSendResponse = true;
    }

    // If mode == 2 - don't send all traffic to stage2 (it won't be logged)
    if (mode == 2) {
        bSendResponse = false;
    }

    // Normalize URL
    std::string normalizedUri = normalize_uri(m_uriStr);

    std::string json = buildWaapResultJson(
        m_scanResult,
        *this,
        bSendResponse,
        normalizedUri,
        m_uriStr,
        bForceBlock,
        bForceException
    );
    m_waapDecision.setJson(json);
}

bool
Waf2Transaction::isHtmlType(const char* data, int data_len){
    if(m_uriPath.find(".js") != std::string::npos || m_uriPath.find(".css") != std::string::npos)
    {
        dbgTrace(D_WAAP) << "Waf2Transaction::isHtmlType: false";
        return false;
    }
    std::string body(data, data_len);
    if(!m_pWaapAssetState->getSignatures()->html_regex.hasMatch(body))
    {
        dbgTrace(D_WAAP) << "Waf2Transaction::isHtmlType: false. Html regex not matched.";
        return false;
    }
    dbgTrace(D_WAAP) << "Waf2Transaction::isHtmlType: true";
    return true;
}

// Search for <head> html tag - return true if found and update the injection correct position.
bool
Waf2Transaction::findHtmlTagToInject(const char* data, int data_len, int& pos)
{
    bool headFound = false;
    static const char tag[] = "<head>";
    static size_t tagSize = sizeof(tag) - 1;

    // Searching tag <head> by iterating over data and always check last 6 bytes against the required tag.
    for (pos = 0; pos<data_len && !headFound; ++pos) {
        m_tagHist[m_tagHistPos] = data[pos];
        m_tagHistPos++;
        // wrap
        if (m_tagHistPos >= tagSize) {
            m_tagHistPos = 0;
        }
        // check
        bool tagMatches = true;
        size_t tagHistPosCheck = m_tagHistPos;
        for (size_t i=0; i < tagSize; ++i) {
            if (tag[i] != ::tolower(m_tagHist[tagHistPosCheck])) {
                if (i == tagSize - 1 && m_tagHist[tagHistPosCheck] == ' ') {
                    // match regex on head element with attributes
                    string dataStr = Waap::Util::charToString(data + pos, data_len - pos);
                    dataStr = dataStr.substr(0, dataStr.find('>')+1);
                    tagMatches = NGEN::Regex::regexMatch(
                        __FILE__,
                        __LINE__,
                        dataStr,
                        boost::regex("(?:\\s+[a-zA-Z_:][-a-zA-Z0-9_:.]*(?:\\s*=\\s*(\"[^\"]*\"|'[^']*'|[^\\s\"'>]*))?)*\\s*>")
                        );
                        pos += dataStr.length() - 1;
                        dbgTrace(D_WAAP_BOT_PROTECTION) << "matching head element with attributes: " << dataStr << ". match: " << tagMatches;
                    break;
                }
                tagMatches = false;
                break;
            }
            tagHistPosCheck++;
            if (tagHistPosCheck >= tagSize) {
                tagHistPosCheck = 0;
            }
        }
        if (tagMatches) {
            headFound = true;
        }
    }

    dbgTrace(D_WAAP_BOT_PROTECTION) << "head element tag found: " << headFound;
    return headFound;
}

void
Waf2Transaction::completeInjectionResponseBody(std::string& strInjection)
{
    if (m_responseInjectReasons.shouldInjectAntibot()) {
        dbgTrace(D_WAAP_BOT_PROTECTION) <<
            "Waf2Transaction::completeInjectionResponseBody(): Injecting data (antiBot)";
        strInjection += "<script src=\"cp-ab.js\"></script>";
        // No need to inject more than once
        m_responseInjectReasons.setAntibot(false);
    }

    if(m_responseInjectReasons.shouldInjectCaptcha()) {
        dbgTrace(D_WAAP_BOT_PROTECTION) <<
            "Waf2Transaction::completeInjectionResponseBody(): Injecting data (captcha)";
        //todo add captcha script
        strInjection += "<script src=\"cp-cp.js\"></script>";
        // No need to inject more than once
        m_responseInjectReasons.setCaptcha(false);
    }

    if (m_responseInjectReasons.shouldInjectCsrf()) {
        dbgTrace(D_WAAP) << "Waf2Transaction::completeInjectionResponseBody(): Injecting data (csrf)";
        strInjection += "<script src=\"cp-csrf.js\"></script>";
        // No need to inject more than once
        m_responseInjectReasons.setCsrf(false);
    }
}

void
Waf2Transaction::handleSecurityHeadersInjection(std::vector<std::pair<std::string, std::string>>& injectHeaderStrs){
    auto securityHeadersPolicy = m_siteConfig ? m_siteConfig->get_SecurityHeadersPolicy() : NULL;
    if (securityHeadersPolicy) {
        if (!securityHeadersPolicy->m_securityHeaders.enable) {
            dbgTrace(D_WAAP) <<
                "(Waf2Engine::handleSecurityHeadersInjection): Security Headers Disabled";
        }
        else if (m_pWaapAssetState->getSecurityHeadersState() == nullptr) {
            dbgDebug(D_WAAP) <<
                "(Waf2Engine::handleSecurityHeadersInjection): Security Headers State was not created as expected";
        }
        else {
            injectHeaderStrs = m_pWaapAssetState->getSecurityHeadersState()->headersInjectStrs;
        }
    }
}

bool Waf2Transaction::shouldInjectCSRF()
{
    return m_responseInjectReasons.shouldInjectCsrf();
}

void Waf2Transaction::disableShouldInjectSecurityHeaders() {
    m_responseInjectReasons.setSecurityHeaders(false);
}

bool Waf2Transaction::shouldInjectSecurityHeaders()
{
    return m_responseInjectReasons.shouldInjectSecurityHeaders();
}

void
Waf2Transaction::checkShouldInject()
{
    dbgTrace(D_WAAP) << "Waf2Transaction::checkShouldInject(): starts";
    std::string uri = m_uriPath;
    std::string low_method = m_methodStr;
    std::transform(low_method.begin(), low_method.end(), low_method.begin(), ::tolower);

    auto csrfPolicy = m_siteConfig ? m_siteConfig->get_CsrfPolicy() : NULL;
    bool csrf = false;
    dbgTrace(D_WAAP) << "Waf2Transaction::checkShouldInject(): received the relevant Application configuration "
        "from the I/S";
    if (csrfPolicy && csrfPolicy->enable) {
        csrf = true;
    }
    else
    {
        dbgTrace(D_WAAP) << "Waf2Transaction::checkShouldInject(): Should not inject CSRF scripts.";
    }

    if(csrf) {
        dbgTrace(D_WAAP) << "Waf2Transaction::checkShouldInject(): Should inject CSRF script";
        m_responseInjectReasons.setCsrf(true);
    }
    return;
}


bool
Waf2Transaction::decideAfterHeaders()
{
    dbgFlow(D_WAAP) << "Waf2Transaction::decideAfterHeaders()";

    WaapConfigAPI ngenAPIConfig;
    WaapConfigApplication ngenSiteConfig;
    IWaapConfig *sitePolicy = NULL; // will be NULL or point to either API or SITE config.

    if (WaapConfigAPI::getWaapAPIConfig(ngenAPIConfig)) {
        dbgTrace(D_WAAP) << "Waf2Transaction::decideAfterHeaders(): got relevant API configuration from the I/S";
        sitePolicy = &ngenAPIConfig;
    }
    else if (WaapConfigApplication::getWaapSiteConfig(ngenSiteConfig)) {
        dbgTrace(D_WAAP) <<
            "Waf2Transaction::decideAfterHeaders(): got relevant Application configuration from the I/S";
        sitePolicy = &ngenSiteConfig;
    }

    if (!sitePolicy) {
        dbgTrace(D_WAAP) << "Waf2Transaaction::decideAfterHeaders(): no policy - do not block";
        return false;
    }

    m_isHeaderOverrideScanRequired = true;
    m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION] = getOverrideState(sitePolicy);

    // Select scores pool by location (but use forced pool when forced)
    std::string realPoolName =
        (m_scanResult) ?
            Waap::Scores::getScorePoolNameByLocation(m_scanResult->location) :
            KEYWORDS_SCORE_POOL_BASE;

    // Autonomus Security
    AnalysisResult analysisResult;
    bool shouldBlock = decideAutonomousSecurity(
        *sitePolicy,
        1,
        true,
        analysisResult,
        realPoolName,
        UNKNOWN_TYPE
    );

    return shouldBlock;
}


// Note: the only user of the transactionResult structure filled by this method is waap_automation.
// TODO: Consider removing this parameter (and provide access to this information by other means)
int
Waf2Transaction::decideFinal(
    int mode,
    AnalysisResult &transactionResult,
    const std::string &poolName,
    PolicyCounterType fpClassification)
{
    dbgFlow(D_WAAP) << "Waf2Transaction::decideFinal(): starts";

    // Select scores pool by location (but use forced pool when forced)
    std::string realPoolName =
        (poolName == KEYWORDS_SCORE_POOL_BASE && m_scanResult) ?
            Waap::Scores::getScorePoolNameByLocation(m_scanResult->location) :
            poolName;

    // decision of (either) API or Application module
     bool shouldBlock = false;
    // TODO:: base class for both, with common inteface
    WaapConfigAPI ngenAPIConfig;
    WaapConfigApplication ngenSiteConfig;
    IWaapConfig *sitePolicy = NULL; // will be NULL or point to either API or SITE config.

    // API config is more specific, hence if it exists it overrides anything from WaapConfigApplication
    if (WaapConfigAPI::getWaapAPIConfig(ngenAPIConfig)) {
        dbgTrace(D_WAAP) << "Waf2Transaction::decideFinal(): got relevant API configuration from the I/S";
        sitePolicy = &ngenAPIConfig;
        m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION] = getOverrideState(sitePolicy);

        // User limits
        shouldBlock = (getUserLimitVerdict() == ServiceVerdict::TRAFFIC_VERDICT_DROP);
    }
    else if (WaapConfigApplication::getWaapSiteConfig(ngenSiteConfig)) {
        dbgTrace(D_WAAP) << "Waf2Transaction::decideFinal(): got relevant Application configuration from the I/S";
        sitePolicy = &ngenSiteConfig;
        m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION] = getOverrideState(sitePolicy);

        shouldBlock = decideAutonomousSecurity(
            *sitePolicy,
            mode,
            false,
            transactionResult,
            realPoolName,
            fpClassification
        );

        // CSRF Protection
        auto csrfPolicy = m_siteConfig ? m_siteConfig->get_CsrfPolicy() : nullptr;
        if(csrfPolicy && csrfPolicy->enable) {
            shouldBlock |= m_csrfState.decide(m_methodStr, m_waapDecision, csrfPolicy);
        }
        // User limits
        shouldBlock |= (getUserLimitVerdict() == ServiceVerdict::TRAFFIC_VERDICT_DROP);
    }

    if (mode == 2) {
        decide(
            m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bForceBlock,
            m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bForceException,
            mode
        );
        shouldBlock = isSuspicious();
    }

    dbgTrace(D_WAAP) << "Waf2Transaction::decideFinal(): returning shouldBlock: " << shouldBlock;
    return shouldBlock;
}

void Waf2Transaction::appendCommonLogFields(LogGen& waapLog,
    const LogTriggerConf &triggerLog,
    bool shouldBlock,
    const std::string& logOverride,
    const std::string& incidentType,
    DecisionType practiceType) const
{
    auto env = Singleton::Consume<I_Environment>::by<WaapComponent>();
    auto active_id = env->get<std::string>("ActiveTenantId");
    if (active_id.ok()) waapLog.addToOrigin(LogField("tenantId", *active_id));
    auto proxy_ip = env->get<std::string>(HttpTransactionData::proxy_ip_ctx);
    if (proxy_ip.ok() && m_remote_addr != proxy_ip.unpack())
    {
        waapLog << LogField("proxyIP", static_cast<std::string>(proxy_ip.unpack()));
    }
    waapLog << LogField("sourceIP", m_remote_addr);
    waapLog << LogField("httpSourceId", m_source_identifier);
    if (getProfileAgentSettingWithDefault<bool>(false, "agent.saasProfile.ignoreSourceIP")){
        dbgTrace(D_WAAP) << "ignoring remote port in nexus log";
    } else {
        waapLog << LogField("sourcePort", m_remote_port);
    }
    waapLog << LogField("httpHostName", m_hostStr);
    waapLog << LogField("httpMethod", m_methodStr);
    if (!m_siteConfig->get_AssetId().empty()) waapLog << LogField("assetId", m_siteConfig->get_AssetId());
    if (!m_siteConfig->get_AssetName().empty()) waapLog << LogField("assetName", m_siteConfig->get_AssetName());

    const auto& decision = m_waapDecision.getDecision(practiceType);
    bool send_extended_log = shouldSendExtendedLog(triggerLog, practiceType);
    if (triggerLog.isWebLogFieldActive(LogTriggerConf::WebLogFields::webUrlPath) || decision->shouldForceLog()) {
        std::string httpUriPath = m_uriPath;

        if (httpUriPath.length() > MAX_LOG_FIELD_SIZE)
        {
            httpUriPath.resize(MAX_LOG_FIELD_SIZE);
        }

        waapLog << LogField("httpUriPath", httpUriPath, LogFieldOption::XORANDB64);
    }
    if (triggerLog.isWebLogFieldActive(LogTriggerConf::WebLogFields::webUrlQuery) || decision->shouldForceLog()) {
        std::string uriQuery = m_uriQuery;
        if (uriQuery.length() > MAX_LOG_FIELD_SIZE)
        {
            uriQuery.resize(MAX_LOG_FIELD_SIZE);
        }
        waapLog << LogField("httpUriQuery", uriQuery, LogFieldOption::XORANDB64);
    }
    if (triggerLog.isWebLogFieldActive(LogTriggerConf::WebLogFields::webHeaders) || decision->shouldForceLog()) {
        waapLog << LogField("httpRequestHeaders", logHeadersStr(), LogFieldOption::XORANDB64);
    }
    // Log http response code if it is known
    if (m_responseStatus != 0 && send_extended_log && triggerLog.isWebLogFieldActive(LogTriggerConf::WebLogFields::responseCode)) {
        waapLog << LogField("httpResponseCode", std::to_string(m_responseStatus));
    }

    // Count of bytes available to send to the log
    std::string requestBodyToLog = (triggerLog.isWebLogFieldActive(LogTriggerConf::WebLogFields::webBody)) ?
        m_request_body : std::string();
    std::string responseBodyToLog = m_response_body;
    if (!shouldBlock && responseBodyToLog.empty())
    {
        responseBodyToLog = "<EMPTY RESPONSE BODY>";
    }

    if (!requestBodyToLog.empty()) {
        size_t requestBodyMaxSize = MAX_LOG_FIELD_SIZE - std::min(MIN_RESP_BODY_LOG_FIELD_SIZE,
            responseBodyToLog.size());
        // Limit request body log field size
        if (requestBodyToLog.length() > requestBodyMaxSize)
        {
            requestBodyToLog.resize(requestBodyMaxSize);
        }
    }

    if (!m_response_body.empty()) {
        size_t responseBodyMaxSize = MAX_LOG_FIELD_SIZE - requestBodyToLog.size();
        // Limit response body log field size
        if (responseBodyToLog.length() > responseBodyMaxSize)
        {
            responseBodyToLog.resize(responseBodyMaxSize);
        }
    }

    if (!requestBodyToLog.empty())
    {
        waapLog << LogField("httpRequestBody", requestBodyToLog, LogFieldOption::XORANDB64);
    }

    if (!responseBodyToLog.empty() && send_extended_log && triggerLog.isWebLogFieldActive(LogTriggerConf::WebLogFields::responseBody))
    {
        waapLog << LogField("httpResponseBody", responseBodyToLog, LogFieldOption::XORANDB64);
    }

    waapLog << LogField("ruleId", m_siteConfig->get_RuleId());
    waapLog << LogField("securityAction", shouldBlock ? "Prevent" : "Detect");
    waapLog << LogField("waapOverride", logOverride);
    waapLog << LogField("practiceType", "Threat Prevention");
    waapLog << LogField("practiceSubType", m_practiceSubType);
    waapLog << LogField("ruleName", m_siteConfig->get_RuleName());
    waapLog << LogField("practiceId", m_siteConfig->get_PracticeIdByPactice(practiceType));
    waapLog << LogField("practiceName", m_siteConfig->get_PracticeNameByPactice(practiceType));
    waapLog << LogField("waapIncidentType", incidentType);

    // Registering this value would append the list of matched override IDs to the unified log
    if (!m_matchedOverrideIds.empty()) {
        // Convert set to vector and send to log as a list
        std::vector<std::string> vOverrideIds(m_matchedOverrideIds.size());
        std::copy(m_matchedOverrideIds.begin(), m_matchedOverrideIds.end(), vOverrideIds.begin());
        waapLog.addToOrigin(LogField("exceptionIdList", vOverrideIds));
        if (!m_effectiveOverrideIds.empty()) {
            std::vector<std::string> vEffectiveOverrideIds(m_effectiveOverrideIds.size());
            std::copy(m_effectiveOverrideIds.begin(), m_effectiveOverrideIds.end(), vEffectiveOverrideIds.begin());
            waapLog.addToOrigin(LogField("effectiveExceptionIdList", vEffectiveOverrideIds));
        }
        if (!m_exceptionLearned.empty()) {
            std::vector<std::string> vLearningAffected(m_exceptionLearned.size());
            std::copy(m_exceptionLearned.begin(), m_exceptionLearned.end(), vLearningAffected.begin());
            waapLog.addToOrigin(LogField("redundantExceptionIdList", vLearningAffected));
        }
    }
}


void
Waf2Transaction::sendLog()
{
    dbgFlow(D_WAAP) << "send log";
    m_waapDecision.orderDecisions();
    if (m_siteConfig == NULL) {
        dbgWarning(D_WAAP) <<
            "Waf2Transaction::sendLog: no site policy associated with transaction - not sending a log";
        return;
    }
    DecisionType decision_type = m_waapDecision.getHighestPriorityDecisionToLog();
    dbgTrace(D_WAAP) << "send log got decision type: " << decision_type;
    auto final_decision = m_waapDecision.getDecision(decision_type);
    if (!final_decision) {
        dbgTrace(D_WAAP) << "send log no decision found, using AUTONOMOUS_SECURITY_DECISION";
        final_decision = m_waapDecision.getDecision(AUTONOMOUS_SECURITY_DECISION);
        decision_type = AUTONOMOUS_SECURITY_DECISION;
    }

    std::string attackTypes = buildAttackTypes();
    std::string logOverride = "None";
    DecisionTelemetryData telemetryData;
    std::string assetId = m_siteConfig->get_AssetId();

    I_TimeGet *timeGet = Singleton::Consume<I_TimeGet>::by<Waf2Transaction>();
    auto finish = timeGet->getMonotonicTime();
    auto diff = chrono::duration_cast<chrono::milliseconds>(finish) - m_entry_time;
    telemetryData.elapsedTime = diff.count();
    dbgTrace(D_WAAP) << "latency updated, time: " << diff.count() << "ms";

    if (m_methodStr == "POST") {
        telemetryData.method = POST;
    } else if (m_methodStr == "GET") {
        telemetryData.method = GET;
    } else if (m_methodStr == "PUT") {
        telemetryData.method = PUT;
    } else if (m_methodStr == "PATCH") {
        telemetryData.method = PATCH;
    } else if (m_methodStr == "DELETE") {
        telemetryData.method = DELETE;
    } else {
        telemetryData.method = OTHER;
    }

    if (m_responseStatus != 0) {
        telemetryData.responseCode = m_responseStatus;
    }

    telemetryData.source = getSourceIdentifier();
    telemetryData.assetName = m_siteConfig->get_AssetName();
    telemetryData.practiceId = m_siteConfig->get_PracticeIdByPactice(AUTONOMOUS_SECURITY_DECISION);
    telemetryData.practiceName = m_siteConfig->get_PracticeNameByPactice(AUTONOMOUS_SECURITY_DECISION);
    telemetryData.temperatureDetected = wasTemperatureDetected();
    switch (decision_type)
    {
    case USER_LIMITS_DECISION: {
        telemetryData.blockType = LIMIT_BLOCK;
        break;
    }
    case CSRF_DECISION: {
        telemetryData.blockType = CSRF_BLOCK;
        break;
    }
    case AUTONOMOUS_SECURITY_DECISION: {
        telemetryData.blockType = WAF_BLOCK;
        break;
    }
    default:
        telemetryData.blockType = NOT_BLOCKING;
        break;
    }

    bool shouldBlock = false;
    if (final_decision->shouldForceBlock()) {
        telemetryData.blockType = FORCE_BLOCK;
        // If override forces "reject" decision, mention it in the "override" log field.
        logOverride = OVERRIDE_DROP;
        shouldBlock = true;
    }
    else if (final_decision->shouldForceAllow()) {
        telemetryData.blockType = FORCE_EXCEPTION;
        // If override forces "allow" decision, mention it in the "override" log field.
        logOverride = OVERRIDE_ACCEPT;
    } else if (decision_type == AUTONOMOUS_SECURITY_DECISION && m_scanner.getIgnoreOverride()) {
        // skip exception detected by scanner
        dbgTrace(D_WAAP) << "should ignore override";
        logOverride = OVERRIDE_IGNORE;
    }

    WaapTelemetryEvent(assetId, telemetryData).notify();
    auto it = m_overrideStateByPractice.find(decision_type);
    if ((it != m_overrideStateByPractice.end() && it->second.bSupressLog) ||
        (it == m_overrideStateByPractice.end() && m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bSupressLog)) {
        dbgTrace(D_WAAP) << "Waf2Transaction::sendLog: override is to ignore log - not sending a log";
        return;
    }

    // Get triggers
    const std::shared_ptr<Waap::Trigger::Policy> triggerPolicy = m_siteConfig->get_TriggerPolicy();

    if (!triggerPolicy || triggerPolicy->triggers.empty()) {
        dbgTrace(D_WAAP) << "Waf2Transaction::sendLog: found no triggers (or triggers are absent) - not sending a log";
        return;
    }

    auto maybeTriggerLog = getTriggerLog(triggerPolicy, decision_type);
    // If there were no triggers of type Log - do not send log
    if (!maybeTriggerLog.ok()) {
        dbgTrace(D_WAAP) << "Waf2Transaction::sendLog: found no triggers of type 'Log' - not sending a log";
        return;
    }
    auto triggerLog = maybeTriggerLog.unpack();

    if(final_decision->shouldForceLog() &&
        triggerLog.shouldIgnoreExceptionLog(LogTriggerConf::SecurityType::ThreatPrevention)) {
        // If we should ignore exception log, we need to handle it
        dbgTrace(D_WAAP) << "Waf2Transaction::sendLog: ignoring exception log";
        return;
    }

    bool send_extended_log = shouldSendExtendedLog(triggerLog, decision_type);
    shouldBlock |= m_waapDecision.getShouldBlockFromHighestPriorityDecision();
    // Do not send Detect log if trigger disallows it
    // and we do not override log(for ignore exception)
    if (!send_extended_log && shouldBlock == false &&
        !triggerLog.isDetectLogActive(LogTriggerConf::SecurityType::ThreatPrevention) &&
        !final_decision->shouldForceLog())
    {
        dbgTrace(D_WAAP) << "Waf2Transaction::sendLog: not sending Detect log (triggers)";
        return;
    }

    // Do not send Prevent log if trigger disallows it
    if (!send_extended_log && shouldBlock == true &&
        !triggerLog.isPreventLogActive(LogTriggerConf::SecurityType::ThreatPrevention) &&
        !final_decision->shouldForceLog())
    {
        dbgTrace(D_WAAP) << "Waf2Transaction::sendLog: not sending Prevent log (triggers)";
        return;
    }

    // In case no decision to block or log - send log if extend log or override
    if (!m_waapDecision.anyDecisionsToLogOrBlock())
    {
        if (send_extended_log || final_decision->shouldForceLog())
        {
            sendAutonomousSecurityLog(triggerLog, shouldBlock, logOverride, attackTypes);
            dbgTrace(D_WAAP) << "Waf2Transaction::sendLog()::" <<
                "sending autonomous security log due to either extended log or an override";
        }
        else
        {
            dbgTrace(D_WAAP) << "Waf2Transaction::sendLog: no decision to log";
        }
        return;
    }

    if (decision_type == DecisionType::NO_WAAP_DECISION) {
        if (send_extended_log || final_decision->shouldForceLog()) {
            sendAutonomousSecurityLog(triggerLog, shouldBlock, logOverride, attackTypes);
        }
        dbgTrace(D_WAAP) << "Waf2Transaction::sendLog: decisions marked for block only";
        return;
    }

    ReportIS::Severity severity =
        decision_type == USER_LIMITS_DECISION ? ReportIS::Severity::HIGH : ReportIS::Severity::CRITICAL;
    if (final_decision->shouldForceLog()) {
        if (logOverride == OVERRIDE_DROP) {
            severity = ReportIS::Severity::MEDIUM;
        } else if (logOverride == OVERRIDE_ACCEPT) {
            severity = ReportIS::Severity::INFO;
        }
    }

    switch (decision_type)
    {
    case USER_LIMITS_DECISION: {
        std::string incidentDetails;
        std::string incidentType;
        if (isIllegalMethodViolation()) {
            incidentDetails += "Http method received: ";
            incidentDetails +=  getMethod();
            incidentType += "Illegal http method violation";
        }
        else {
            auto strData = getViolatedUserLimitStrData();
            incidentDetails += "Http request ";
            incidentDetails += strData.type;
            incidentDetails += " (";
            incidentDetails += strData.policy;
            incidentDetails += ")";
            incidentType += "Http limit violation";
        }

        LogGenWrapper logGenWrapper(
                                maybeTriggerLog,
                                "Web Request",
                                ReportIS::Audience::SECURITY,
                                LogTriggerConf::SecurityType::ThreatPrevention,
                                severity,
                                Priority::HIGH,
                                shouldBlock);

        LogGen& waap_log = logGenWrapper.getLogGen();
        appendCommonLogFields(waap_log, triggerLog, shouldBlock, logOverride, incidentType, decision_type);
        waap_log << LogField("waapIncidentDetails", incidentDetails);
        waap_log << LogField("eventConfidence", "High");
        break;
    }
    case OPEN_REDIRECT_DECISION:
    case ERROR_LIMITING_DECISION:
    case RATE_LIMITING_DECISION:
    case ERROR_DISCLOSURE_DECISION: {
        LogGenWrapper logGenWrapper(
                                maybeTriggerLog,
                                "API Request",
                                ReportIS::Audience::SECURITY,
                                LogTriggerConf::SecurityType::ThreatPrevention,
                                severity,
                                Priority::HIGH,
                                shouldBlock);

        LogGen& waap_log = logGenWrapper.getLogGen();
        waap_log << LogField("eventConfidence", "Very High");

        std::string incidentDetails;
        std::string incidentType;
        m_waapDecision.getIncidentLogFields(
            std::to_string(m_responseStatus),
            incidentDetails,
            incidentType
        );

        if (decision_type == ERROR_DISCLOSURE_DECISION) {
            waap_log << LogField("waapFoundIndicators", getKeywordMatchesStr(), LogFieldOption::XORANDB64);
        }

        appendCommonLogFields(waap_log, triggerLog, shouldBlock, logOverride, incidentType, decision_type);

        waap_log << LogField("waapIncidentDetails", incidentDetails);
        break;
    }
    case CSRF_DECISION: {
        LogGenWrapper logGenWrapper(
                                maybeTriggerLog,
                                "CSRF Protection",
                                ReportIS::Audience::SECURITY,
                                LogTriggerConf::SecurityType::ThreatPrevention,
                                severity,
                                Priority::HIGH,
                                shouldBlock);

        LogGen& waap_log = logGenWrapper.getLogGen();
        appendCommonLogFields(
            waap_log, triggerLog, shouldBlock, logOverride, "Cross Site Request Forgery", decision_type
        );
        waap_log << LogField("waapIncidentDetails", "CSRF Attack discovered.");
        break;
    }
    case AUTONOMOUS_SECURITY_DECISION: {
        if (triggerLog.isWebLogFieldActive(LogTriggerConf::WebLogFields::webRequests) ||
            send_extended_log ||
            std::dynamic_pointer_cast<AutonomousSecurityDecision>(final_decision)
                ->getThreatLevel() != ThreatLevel::NO_THREAT ||
            final_decision->shouldForceLog()) {
            sendAutonomousSecurityLog(triggerLog, shouldBlock, logOverride, attackTypes);
        }
        break;
    }
    default:
        static_assert(true, "Illegal DecisionType enum value");
        break;
    } // end switch
}

bool
Waf2Transaction::decideAutonomousSecurity(
    const IWaapConfig &sitePolicy,
    int mode,
    bool afterHeaders,
    AnalysisResult &transactionResult,
    const std::string &poolName,
    PolicyCounterType fpClassification)
{
    dbgFlow(D_WAAP) <<
        "Waf2Transaction::decideAutonomousSecurity(): " <<
        "mode=" << mode <<
        ", afterHeaders=" << afterHeaders <<
        ", poolName='" << poolName << "'";

    if (mode == 2)
    {
        return isSuspicious();
    }

    if (!sitePolicy.get_WebAttackMitigation()) {
        // Web security not enabled
        dbgTrace(D_WAAP) << "Autonomous security is not enabled in policy.";
        return false;
    }

    std::shared_ptr<AutonomousSecurityDecision> decision = std::dynamic_pointer_cast<AutonomousSecurityDecision>(
        m_waapDecision.getDecision(AUTONOMOUS_SECURITY_DECISION));

    // Do not call stage2 so it doesn't learn from exceptions.
    // Also do not call stage2 for attacks found in parameter name
    if (!m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bForceException &&
         !(m_scanResult && m_scanResult->m_isAttackInParam)) {
        if (!m_processedUri) {
            dbgWarning(D_WAAP) << "decideAutonomousSecurity(): processing URI although is was supposed "
                "to be processed earlier ...";
            processUri(m_uriStr, "url");
        }

        if (!m_processedHeaders) {
            dbgWarning(D_WAAP) << "decideAutonomousSecurity(): processing Headers although is was supposed "
                "to be processed earlier ...";
            scanHeaders();
        }

        dbgTrace(D_WAAP) << "decideAutonomousSecurity(): processing stage2 for final decision ...";

        // Call stage2
        transactionResult =
            Singleton::Consume<I_DeepAnalyzer>::by<WaapComponent>()->analyzeData(this, &sitePolicy);

        decision->setThreatLevel(transactionResult.threatLevel);

        decision->setBlock(transactionResult.shouldBlock);

        // Once these are known - fill the values to be included in the log
        decision->setRelativeReputation(transactionResult.d2Analysis.relativeReputation);
        decision->setFpMitigationScore(transactionResult.d2Analysis.fpMitigationScore);
        decision->setFinalScore(transactionResult.d2Analysis.finalScore);
        decision->setRelativeReputationMean(transactionResult.d2Analysis.reputationMean);
        decision->setVariance(transactionResult.d2Analysis.variance);

        dbgTrace(D_WAAP) << "decideAutonomousSecurity(): stage2 decision is: " <<
            decision->shouldBlock() << "; threatLevel: " << decision->getThreatLevel() <<
            "; blockingLevel: " << static_cast<std::underlying_type<BlockingLevel>::type>(
            sitePolicy.get_BlockingLevel());

        if (!afterHeaders || decision->shouldBlock()) {
            ScoreBuilderData sbData;

            sbData.m_fpClassification = transactionResult.d2Analysis.fpClassification;
            sbData.m_sourceIdentifier = getSourceIdentifier();
            sbData.m_keywordsCombinations = getKeywordsCombinations();
            sbData.m_keywordsMatches = getKeywordMatches();
            sbData.m_userAgent = getUserAgent();
            sbData.m_sample = getSample();
            sbData.m_relativeReputation = transactionResult.d2Analysis.relativeReputation;

            if (fpClassification != UNKNOWN_TYPE) {
                sbData.m_fpClassification = fpClassification;
            }

            learnScore(sbData, poolName);
        }
    }

    // Fill attack details for attacks found in parameter names
    if (!m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bForceException &&
         m_scanResult && m_scanResult->m_isAttackInParam) {
        // Since stage2 learning doesn't run in this case, assume stage1 score is the final score
        float finalScore = m_scanResult->score;
        ThreatLevel threat = Waap::Conversions::convertFinalScoreToThreatLevel(finalScore);
        bool shouldBlock = Waap::Conversions::shouldDoWafBlocking(&sitePolicy, threat);

        dbgTrace(D_WAAP) << "attack_in_param without stage2 analysis: final score: " << finalScore <<
            ", threat level: " << threat << "\nWAF2 decision to block: " <<
            (shouldBlock ? "block" : "pass");

        decision->setFinalScore(finalScore);
        decision->setThreatLevel(threat);
        decision->setBlock(shouldBlock);

        // Fill transactionResult
        transactionResult.d2Analysis.finalScore = finalScore;
        transactionResult.shouldBlock = shouldBlock;
        transactionResult.threatLevel = threat;
    } else if (m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bForceBlock &&
                decision->getThreatLevel() == ThreatLevel::NO_THREAT) {
        // If override forces block, set threat level to INFO
        decision->setThreatLevel(ThreatLevel::THREAT_INFO);
    }

    dbgTrace(D_WAAP_OVERRIDE) << "override ids count: " << m_matchedOverrideIds.size();
    // Apply overrides
    for (auto it = m_overridePostFilterMaxScore.begin(); it != m_overridePostFilterMaxScore.end(); it++) {
        const string id = it->first;
        if (m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].forceBlockIds.find(id) !=
            m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].forceBlockIds.end()) {
            // blocked effectivness is calculates later from the force block exception ids list
            continue;
        }
        ThreatLevel threat = Waap::Conversions::convertFinalScoreToThreatLevel(it->second);
        bool shouldBlock = Waap::Conversions::shouldDoWafBlocking(m_siteConfig, threat);
        dbgTrace(D_WAAP_OVERRIDE) << "checking effectivness of override: " << id << ", should have blocked: " << shouldBlock
            << ", scores: " << m_overridePostFilterMaxScore[id] << ", " << m_overrideOriginalMaxScore[id];
        if (shouldBlock) {
            m_effectiveOverrideIds.insert(id);
        } else {
            ThreatLevel threatNoFilter = Waap::Conversions::convertFinalScoreToThreatLevel(
                m_overrideOriginalMaxScore[id]
            );
            if (Waap::Conversions::shouldDoWafBlocking(m_siteConfig, threatNoFilter)) {
                m_exceptionLearned.insert(id);
            }
        }
    }

    if (m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bForceBlock) {
        dbgTrace(D_WAAP) << "decideAutonomousSecurity(): decision was " << decision->shouldBlock() <<
            " and override forces REJECT ...";
        if (!decision->shouldBlock()) {
            m_effectiveOverrideIds.insert(
                m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].forceBlockIds.begin(),
                m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].forceBlockIds.end()
            );
        }
        decision->setBlock(true);
        decision->setForceBlock(true);
        if (!m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bSupressLog)
        {
            decision->setForceLog(true);
        }
    }
    else if (m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bForceException) {
        dbgTrace(D_WAAP) << "decideAutonomousSecurity(): decision was " << decision->shouldBlock() <<
            " and override forces ALLOW ...";
        decision->setBlock(false);
        decision->setForceAllow(true);
        if (!m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bSupressLog)
        {
            decision->setForceLog(true);
        }
    } else if (!m_matchedOverrideIds.empty()) {
        if (!m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bSupressLog)
        {
            decision->setForceLog(true);
        }
    }
    dbgTrace(D_WAAP_OVERRIDE) <<
    "force exception: " <<
    m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bForceException <<
    " force block: " << m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bForceBlock <<
    " matched overrides count: " << m_matchedOverrideIds.size() <<
    " effective overrides count: " << m_effectiveOverrideIds.size() <<
    " learned overrides count: " << m_exceptionLearned.size();

    bool log_all = false;
    const std::shared_ptr<Waap::Trigger::Policy> triggerPolicy = sitePolicy.get_TriggerPolicy();
    if (triggerPolicy) {
        auto triggerLog = getTriggerLog(triggerPolicy, AUTONOMOUS_SECURITY_DECISION);
        if (triggerLog.ok() && triggerLog.unpack().isWebLogFieldActive(LogTriggerConf::WebLogFields::webRequests)) log_all = true;
        if (triggerLog.ok() && shouldSendExtendedLog(triggerLog.unpack())) m_responseInspectReasons.setCollectResponseForLog(true);
    }

    if(decision->getThreatLevel() <= ThreatLevel::THREAT_INFO && !log_all) {
        decision->setLog(false);
    } else {
        decision->setLog(true);
    }
    return decision->shouldBlock();
}

void Waf2Transaction::handleCsrfHeaderInjection(std::string& injectStr)
{
    m_csrfState.injectCookieHeader(injectStr);
}

// Disables response injection (masking any pending injection reasons such as from antibot or csrf)
void
Waf2Transaction::clearAllInjectionReasons() {
    m_responseInjectReasons.clear();
}

// Returns true if WAAP engine is interested in receiving more information about response for this transaction
bool Waf2Transaction::shouldInspectResponse()
{
    return m_responseInspectReasons.shouldInspect() || m_responseInjectReasons.shouldInject();
}
bool
Waf2Transaction::shouldLimitResponseHeadersInspection() {
    auto triggerPolicy = m_siteConfig ? m_siteConfig->get_TriggerPolicy() : NULL;
    if (!shouldInspectResponse() && isTriggerReportExists(triggerPolicy)) {
        return true;
    }
    return false;
}
bool Waf2Transaction::shouldInjectResponse()
{
    return m_responseInjectReasons.shouldInject();
}

bool Waf2Transaction::decideResponse()
{
    dbgTrace(D_WAAP) << "Waf2Transaction::decideResponse()";

    if(m_waapDecision.getDecision(ERROR_LIMITING_DECISION)->shouldBlock()) {
        return false; // block
    }
    if(m_waapDecision.getDecision(RATE_LIMITING_DECISION)->shouldBlock()) {
        return false; // block
    }

    bool openRedirectBlock = m_waapDecision.getDecision(OPEN_REDIRECT_DECISION)->shouldBlock();
    bool errorDisclosureBlock = m_waapDecision.getDecision(ERROR_DISCLOSURE_DECISION)->shouldBlock();
    if (openRedirectBlock || errorDisclosureBlock) {
        dbgTrace(D_WAAP) << "Waf2Transaction::decideResponse(): blocking due to" <<
            " OpenRedirect:" << openRedirectBlock <<
            " ErrorDisclosure:" << errorDisclosureBlock;
        return false; // block
    }


    if (m_responseInspectReasons.getApplyOverride()) {
        WaapConfigApplication ngenSiteConfig;

        dbgTrace(D_WAAP_OVERRIDE) << "Checking exceptions for response";
        if (WaapConfigApplication::getWaapSiteConfig(ngenSiteConfig)) {
            dbgTrace(D_WAAP)
                    << "Waf2Transaction::decideResponse(): got relevant Application configuration from the I/S";
            m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION] = getOverrideState(&ngenSiteConfig);
            // Apply overrides
            if (m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bForceBlock) {
                dbgTrace(D_WAAP)
                        << "Waf2Transaction::decideResponse(): setting shouldBlock to true due to override";
                return false; // BLOCK
            }
            else if (m_overrideStateByPractice[AUTONOMOUS_SECURITY_DECISION].bForceException) {
                dbgTrace(D_WAAP)
                        << "Waf2Transaction::decideResponse(): setting shouldBlock to false due to override";
                return true; // PASS
            }
        }
    }

    if (m_siteConfig) {
        const std::shared_ptr<Waap::Trigger::Policy> triggerPolicy = m_siteConfig->get_TriggerPolicy();
        if (!triggerPolicy) {
            dbgTrace(D_WAAP) << "Trigger policy was not found. Returning true (accept)";
            return true; // accept
        }
        // response is only for WAF
        auto maybeTriggerLog = getTriggerLog(triggerPolicy, AUTONOMOUS_SECURITY_DECISION);
        if (!maybeTriggerLog.ok()) {
            dbgTrace(D_WAAP) << "Log trigger configuration was not found. Returning true (accept)";
            return true; // accept
        }
        auto triggerLog = maybeTriggerLog.unpack();
        auto env = Singleton::Consume<I_Environment>::by<Waf2Transaction>();
        auto http_chunk_type = env->get<AttachmentDataType>("HTTP Chunk type");
        bool should_send_extended_log = shouldSendExtendedLog(triggerLog) && http_chunk_type.ok();
        if (should_send_extended_log &&
            *http_chunk_type == AttachmentDataType::RESPONSE_CODE &&
            !triggerLog.isWebLogFieldActive(LogTriggerConf::WebLogFields::responseBody)
        ) {
            dbgTrace(D_WAAP) << "response body is not active. Disabling extended logging";
            should_send_extended_log = false;
        } else if (should_send_extended_log &&
            *http_chunk_type == AttachmentDataType::REQUEST_END &&
            !triggerLog.isWebLogFieldActive(LogTriggerConf::WebLogFields::responseCode) &&
            !triggerLog.isWebLogFieldActive(LogTriggerConf::WebLogFields::responseBody)
        ) {
            dbgTrace(D_WAAP) << "response code is not active. Disabling extended logging";
            should_send_extended_log = false;
        } else if (should_send_extended_log &&
            *http_chunk_type == AttachmentDataType::RESPONSE_BODY &&
            triggerLog.isWebLogFieldActive(LogTriggerConf::WebLogFields::responseBody) &&
            m_response_body.length() >= MAX_RESPONSE_BODY_SIZE)
        {
            dbgTrace(D_WAAP) << "response body collected (" << m_response_body.length() << " bytes). Disabling extended logging";
            should_send_extended_log = false;
        }

        dbgTrace(D_WAAP)
            << "Setting flag for collection of respond content logging to: "
            << (should_send_extended_log ? "True": "False");
        m_responseInspectReasons.setCollectResponseForLog(should_send_extended_log);
    }

    dbgTrace(D_WAAP) << "Waf2Transaction::decideResponse: returns true (accept)";
    return true; // accept
}

bool
Waf2Transaction::reportScanResult(const Waf2ScanResult &res) {
    if ((get_ignoreScore() || res.score >= SCORE_THRESHOLD) &&
        (m_scanResult == nullptr || res.score > m_scanResult->score))
    {
        dbgTrace(D_WAAP) << "Setting scan result. New score: " << res.score;
        // Forget any previous scan result and replace wit, h new
        delete m_scanResult;
        m_scanResult = new Waf2ScanResult(res);
        return true;
    }

    return false;
}

bool
Waf2Transaction::shouldIgnoreOverride(const Waf2ScanResult &res) {

    dbgTrace(D_WAAP_OVERRIDE) << "matching exceptions for should ignore";

    std::unordered_map<std::string, std::set<std::string>> exceptions_dict;
    std::set<ParameterBehavior> behaviors;
    std::set<std::string> &ignored_keywords = getAssetState()->m_filtersMngr->getMatchedOverrideKeywords();

    // collect param name
    exceptions_dict["paramName"].insert(res.param_name);
    exceptions_dict["paramName"].insert(IndicatorsFiltersManager::generateKey(res.location, res.param_name, this));

    std::set<std::string> param_name_set;
    param_name_set.insert(res.param_name);
    param_name_set.insert(IndicatorsFiltersManager::generateKey(res.location, res.param_name, this));

    // collect param value
    exceptions_dict["paramValue"].insert(res.unescaped_line);

    // collect param location
    exceptions_dict["paramLocation"].insert(res.location);

    ScopedContext ctx;
    ctx.registerValue<std::string>("paramValue", res.unescaped_line);
    ctx.registerValue<std::set<std::string>>("paramName", param_name_set);

    // collect sourceip, sourceIdentifier, url
    exceptions_dict["sourceIP"].insert(m_remote_addr);
    exceptions_dict["sourceIdentifier"].insert(m_source_identifier);
    exceptions_dict["url"].insert(m_uriPath);
    exceptions_dict["hostName"].insert(m_hostStr);
    exceptions_dict["method"].insert(m_methodStr);

    for (auto &keyword : res.keyword_matches) {
        exceptions_dict["indicator"].insert(keyword);
    }
    for (auto &it : res.found_patterns) {
        exceptions_dict["indicator"].insert(it.first);
    }

    bool isConfigExist = false;
    if (m_siteConfig &&
        (m_siteConfig->getType() == WaapConfigType::API || m_siteConfig->getType() == WaapConfigType::Application)) {
        dbgTrace(D_WAAP_OVERRIDE) << "waap config already exists";
        isConfigExist = true;
    } else if (WaapConfigApplication::getWaapSiteConfig(m_ngenSiteConfig)) {
        dbgTrace(D_WAAP_OVERRIDE) << "waap web application config found";
        m_siteConfig = &m_ngenSiteConfig;
        isConfigExist = true;
    }
    else if (WaapConfigAPI::getWaapAPIConfig(m_ngenAPIConfig)) {
        dbgTrace(D_WAAP_OVERRIDE) << "waap api config found";
        m_siteConfig = &m_ngenAPIConfig;
        isConfigExist = true;
    }

    std::vector<std::string> site_exceptions;
    if (isConfigExist) {
        dbgTrace(D_WAAP_OVERRIDE) << "config exists, get override policy";
        std::shared_ptr<Waap::Override::Policy> overridePolicy = m_siteConfig->get_OverridePolicy();
        if (overridePolicy) {
            site_exceptions = overridePolicy->getExceptionsByPractice()
                    .getExceptionsOfPractice(AUTONOMOUS_SECURITY_DECISION);
        }
    }

    if (!site_exceptions.empty()) {
        dbgTrace(D_WAAP_OVERRIDE) << "get behaviors by web app practice";
        I_GenericRulebase *i_rulebase = Singleton::Consume<I_GenericRulebase>::by<Waf2Transaction>();
        for (const auto &id : site_exceptions) {
            dbgTrace(D_WAAP_OVERRIDE) << "get parameter exception: " << id;
            auto params = i_rulebase->getParameterException(id).getBehavior(exceptions_dict, ignored_keywords);
            behaviors.insert(params.begin(), params.end());
        }
    } else {
        auto exceptions = getConfigurationWithCache<ParameterException>("rulebase", "exception");
        if (!exceptions.ok()) {
            dbgTrace(D_WAAP_OVERRIDE) << "matching exceptions error: " << exceptions.getErr();
            return false;
        }
        // calling behavior and check if there is a behavior that match to this specific param name.
        behaviors = exceptions.unpack().getBehavior(exceptions_dict, ignored_keywords);
    }
    dbgTrace(D_WAAP_OVERRIDE) << "got "<< behaviors.size() << " behaviors and " <<
        ignored_keywords.size() << " ignored keywords";

    for (const auto &behavior : behaviors) {
        dbgTrace(D_WAAP_OVERRIDE) << "got behavior: " << behavior.getId();
        if (!res.filtered_keywords.empty() || res.score > 0) {
            dbgTrace(D_WAAP_OVERRIDE) << "matched exceptions for param '" << res.param_name << "' with filtered indicators";
            std::string overrideId = behavior.getId();
            if (m_overrideOriginalMaxScore.find(overrideId) == m_overrideOriginalMaxScore.end()){
                m_overrideOriginalMaxScore[overrideId] = res.scoreNoFilter;
                m_overridePostFilterMaxScore[overrideId] = res.score;
            } else {
                if (res.scoreNoFilter > m_overrideOriginalMaxScore[overrideId]) {
                    m_overrideOriginalMaxScore[overrideId] = res.scoreNoFilter;
                }
                if (res.score > m_overridePostFilterMaxScore[overrideId]) {
                    m_overridePostFilterMaxScore[overrideId] = res.score;
                }
            }
            if (res.scoreNoFilter > m_overrideOriginalMaxScore[OVERRIDE_ACCEPT]) {
                m_overrideOriginalMaxScore[OVERRIDE_ACCEPT] = res.scoreNoFilter;
            }
        }
        if (behavior == action_ignore)
        {
            dbgTrace(D_WAAP_OVERRIDE) << "matched exceptions for param '" << res.param_name << "': should ignore.";
            std::string overrideId = behavior.getId();
            if (!overrideId.empty()) {
                m_matchedOverrideIds.insert(overrideId);
            }
            return true;
        }
    }
    dbgTrace(D_WAAP_OVERRIDE) << "should not ignore";
    return false;
}

const std::string Waf2Transaction::buildAttackTypes() const
{
    typedef std::map<std::string, std::vector<std::string>>::const_iterator attack_types_iter;
    if (m_scanResult)
    {
        for (const std::string &regex_name : m_found_patterns)
        {
            attack_types_iter attack_types_for_regex =
                m_pWaapAssetState->getSignatures()->m_attack_types.find(regex_name);
            if (attack_types_for_regex != m_pWaapAssetState->getSignatures()->m_attack_types.end())
            {
                for (const std::string &attack_type : attack_types_for_regex->second)
                {
                    m_scanResult->attack_types.insert(attack_type);
                }
            }
            else {m_scanResult->attack_types.insert("General");}
        }

        if (Waap::Util::vectorStringContain(m_scanResult->keyword_matches, "xml_entity")) {
            m_scanResult->attack_types.insert("XML External Entity");
        }

        if (Waap::Util::vectorStringContain(m_scanResult->keyword_matches, "url_instead_of_file")) {
            m_scanResult->attack_types.insert("URL instead of file");
        }

        auto csrfDecision = m_waapDecision.getDecision(CSRF_DECISION);
        if(csrfDecision && csrfDecision->shouldBlock()) {
            m_scanResult->attack_types.insert("Cross Site Request Forgery");
        }
        auto openRedirectDecision = m_waapDecision.getDecision(OPEN_REDIRECT_DECISION);
        if (openRedirectDecision && openRedirectDecision->shouldBlock()) {
            m_scanResult->attack_types.insert("Open Redirect");
        }

        if (m_scanResult->attack_types.find("General") != m_scanResult->attack_types.end()
                && m_scanResult->attack_types.size() > 1) {
            m_scanResult->attack_types.erase("General");
        }
        return Waap::Util::setToString(m_scanResult->attack_types, false);
    }

    return "";
}

void Waf2Transaction::collectFoundPatterns()
{
    if (m_scanResult)
    {
        for (const auto &found_pattern : m_scanResult->found_patterns)
        {
            const std::string &regex_name = found_pattern.first; // the regex name (key)
            m_found_patterns.insert(regex_name);
        }
    }
}

bool Waf2Transaction::shouldSendExtendedLog(const LogTriggerConf &trigger_log, DecisionType practiceType) const
{
    if (practiceType != AUTONOMOUS_SECURITY_DECISION)
    {
        dbgTrace(D_WAAP) << "Should not send extended logging. Practice type is: " << practiceType;
        return false;
    }
    auto extend_logging_severity = trigger_log.getExtendLoggingSeverity();
    if(extend_logging_severity == LogTriggerConf::extendLoggingSeverity::None)
    {
        dbgTrace(D_WAAP) << "Should not send extended log. Extended log is disabled.";
        return false;
    }

    auto autonomousSecurityDecision = std::dynamic_pointer_cast<AutonomousSecurityDecision>(
        m_waapDecision.getDecision(AUTONOMOUS_SECURITY_DECISION));
    ReportIS::Severity severity = Waap::Util::computeSeverityFromThreatLevel(
        autonomousSecurityDecision->getThreatLevel());

    if (extend_logging_severity == LogTriggerConf::extendLoggingSeverity::Critical)
    {
        if (severity == ReportIS::Severity::CRITICAL)
        {
            dbgTrace(D_WAAP) << "Should send extended logging. Min Severity Critical. Severity: " << (int) severity;
            return true;
        }
        dbgTrace(D_WAAP) << "Should not send extended logging. Min Severity Critical. Severity: " << (int) severity;
        return false;
    }
    else if (extend_logging_severity == LogTriggerConf::extendLoggingSeverity::High)
    {
        if (severity == ReportIS::Severity::CRITICAL || severity == ReportIS::Severity::HIGH)
        {
            dbgTrace(D_WAAP) << "Should send extended logging. Min Severity High. Severity: " << (int) severity;
            return true;
        }
        dbgTrace(D_WAAP) << "Should not send extended logging. Min Severity High. Severity: " << (int) severity;
        return false;
    }

    dbgTrace(D_WAAP) << "Should not send extended logging. Min Severity: " << (int) extend_logging_severity;
    return false;
}


void
Waf2Transaction::setTemperatureDetected(bool detected)
{
    m_temperature_detected = detected;
}

bool
Waf2Transaction::wasTemperatureDetected() const
{
    return m_temperature_detected;
}
