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

#include "modsecurity/rule.h"

#include <stdio.h>

#include <cassert>
#include <algorithm>
#include <iostream>
#include <string>
#include <cstring>
#include <list>
#include <utility>
#include <memory>
#include <cassert>

#include "modsecurity/rules_set.h"
#include "src/operators/operator.h"
#include "modsecurity/actions/action.h"
#include "modsecurity/modsecurity.h"
#include "src/actions/transformations/none.h"
#include "src/actions/tag.h"
#include "src/utils/string.h"
#include "modsecurity/rule_message.h"
#include "modsecurity/rule_with_actions.h"
#include "src/actions/msg.h"
#include "src/actions/log_data.h"
#include "src/actions/severity.h"
#include "src/actions/capture.h"
#include "src/actions/multi_match.h"
#include "src/actions/set_var.h"
#include "src/actions/block.h"
#include "src/variables/variable.h"


namespace modsecurity {

using operators::Operator;
using actions::Action;
using variables::Variable;
using actions::transformations::None;
using actions::transformations::Transformation;



RuleWithActions::RuleWithActions(
    Actions *actions,
    Transformations *transformations,
    const std::string &fileName,
    int lineNumber)
    : Rule(fileName, lineNumber),
    m_rev(""),
    m_ver(""),
    m_accuracy(0),
    m_maturity(0),
    m_ruleId(0),
    m_chainedRuleChild(nullptr),
    m_chainedRuleParent(nullptr),
    m_disruptiveAction(nullptr),
    m_logData(nullptr),
    m_msg(nullptr),
    m_severity(nullptr),
    m_actionsRuntimePos(),
    m_actionsSetVar(),
    m_actionsTag(),
    m_transformations(transformations != NULL ? *transformations : Transformations()),
    m_containsCaptureAction(false),
    m_containsMultiMatchAction(false),
    m_containsStaticBlockAction(false),
    m_isChained(false) {

    if (transformations != NULL) {
        delete transformations;
    }

    if (actions) {
        for (Action *a : *actions) {
            switch (a->action_kind) {
                case Action::Kind::ConfigurationKind:
                    a->evaluate(this, NULL);
                    delete a;
                    break;
                case Action::Kind::RunTimeOnlyIfMatchKind:
                    if (dynamic_cast<actions::Capture *>(a)) {
                        m_containsCaptureAction = true;
                        delete a;
                    } else if (dynamic_cast<actions::MultiMatch *>(a)) {
                        m_containsMultiMatchAction = true;
                        delete a;
                    } else if (auto sa = dynamic_cast<actions::Severity *>(a)) {
                        m_severity = sa;
                    } else if (auto lda = dynamic_cast<actions::LogData *>(a)) { // cppcheck-suppress unreadVariable ; false positive
                        m_logData = lda;
                    } else if (auto ma = dynamic_cast<actions::Msg *>(a)) { // cppcheck-suppress unreadVariable ; false positive
                        m_msg = ma;
                    } else if (auto sva = dynamic_cast<actions::SetVar *>(a)) {
                        m_actionsSetVar.push_back(sva);
                    } else if (auto ta = dynamic_cast<actions::Tag *>(a)) {
                        m_actionsTag.push_back(ta);
                    } else if (dynamic_cast<actions::Block *>(a)) {
                        m_actionsRuntimePos.push_back(a);
                        m_containsStaticBlockAction = true;
                    } else if (a->isDisruptive() == true) {
                        if (m_disruptiveAction != nullptr) {
                            delete m_disruptiveAction;
                            m_disruptiveAction = nullptr;
                        }
                        m_disruptiveAction = a;
                    } else {
                        m_actionsRuntimePos.push_back(a);
                    }
                    break;
                default:
                    std::cout << "General failure, action: " << a->m_name;
                    std::cout << " has an unknown type." << std::endl;
                    delete a;
                    #ifdef NDEBUG
                    break;
                    #else
                    assert(false);
                    #endif
            }
        }
        delete actions;
    }
}

RuleWithActions::~RuleWithActions() {
    if (m_severity) {
        delete m_severity;
        m_severity = nullptr;
    }
    if (m_logData) {
        delete m_logData;
        m_logData = nullptr;
    }
    if (m_msg) {
        delete m_msg;
        m_msg = nullptr;
    }
    while (m_transformations.empty() == false) {
        auto *a = m_transformations.back();
        m_transformations.pop_back();
        delete a;
    }
    while (m_actionsRuntimePos.empty() == false) {
        auto *a = m_actionsRuntimePos.back();
        m_actionsRuntimePos.pop_back();
        delete a;
    }
    while (m_actionsSetVar.empty() == false) {
        auto *a = m_actionsSetVar.back();
        m_actionsSetVar.pop_back();
        delete a;
    }
    while (m_actionsTag.empty() == false) {
        auto *a = m_actionsTag.back();
        m_actionsTag.pop_back();
        delete a;
    }
    if (m_disruptiveAction != nullptr) {
        delete m_disruptiveAction;
        m_disruptiveAction = nullptr;
    }
}


bool RuleWithActions::evaluate(Transaction *transaction) {
    RuleMessage rm(*this, *transaction);
    return evaluate(transaction, rm);
}


bool RuleWithActions::evaluate(Transaction *transaction,
    RuleMessage &ruleMessage) {

    /* Rule evaluate is pure virtual.
     *
     * Rule::evaluate(transaction, ruleMessage);
     */

    /* Matched vars needs to be clear at every new rule execution */
    transaction->m_matched.clear();

    return true;
}


void RuleWithActions::executeActionsIndependentOfChainedRuleResult(Transaction *trans,
    bool *containsBlock, RuleMessage &ruleMessage) {

    for (actions::SetVar *a : m_actionsSetVar) {
        ms_dbg_a(trans, 4, "Running [independent] (non-disruptive) " \
            "action: " + *a->m_name.get());

        a->evaluate(this, trans);
    }

    for (auto &b :
        trans->m_rules->m_exceptions.m_action_pre_update_target_by_id) {
        if (m_ruleId != b.first) {
            continue;
        }
        actions::Action *a = b.second.get();
        if (a->isDisruptive() == true && *a->m_name.get() == "block") {
            ms_dbg_a(trans, 9, "Rule contains a `block' action");
                *containsBlock = true;
        } else if (*a->m_name.get() == "setvar") {
            ms_dbg_a(trans, 4, "Running [independent] (non-disruptive) " \
                "action: " + *a->m_name.get());
            a->evaluate(this, trans, ruleMessage);
        }
    }

    if (m_containsMultiMatchAction && m_chainedRuleParent == nullptr) {
        if (m_severity) {
            m_severity->evaluate(this, trans, ruleMessage);
        }
        if (m_logData) {
            m_logData->evaluate(this, trans, ruleMessage);
        }
        if (m_msg) {
            m_msg->evaluate(this, trans, ruleMessage);
        }
        for (actions::Tag *a : m_actionsTag) {
            a->evaluate(this, trans, ruleMessage);
        }
    }

}


void RuleWithActions::executeActionsAfterFullMatch(Transaction *trans,
    bool containsBlock, RuleMessage &ruleMessage) {
    bool disruptiveAlreadyExecuted = false;

    for (const auto &a : trans->m_rules->m_defaultActions[getPhase()]) { // cppcheck-suppress ctunullpointer
        if (a.get()->action_kind != actions::Action::Kind::RunTimeOnlyIfMatchKind) {
            continue;
        }
        if (!a.get()->isDisruptive()) {
            executeAction(trans, containsBlock, ruleMessage, a.get(), true);
        }
    }

    for (actions::Tag *a : this->m_actionsTag) {
        ms_dbg_a(trans, 4, "Running (non-disruptive) action: " \
            + *a->m_name.get());
        a->evaluate(this, trans, ruleMessage);
    }

    for (auto &b :
        trans->m_rules->m_exceptions.m_action_pos_update_target_by_id) {
        if (m_ruleId != b.first) {
            continue;
        }
        actions::Action *a = b.second.get();
        executeAction(trans, containsBlock, ruleMessage, a, false);
        disruptiveAlreadyExecuted = true;
    }
    if (m_severity) {
        m_severity->evaluate(this, trans, ruleMessage);
    }

    if (m_logData) {
        m_logData->evaluate(this, trans, ruleMessage);
    }

    if (m_msg) {
        m_msg->evaluate(this, trans, ruleMessage);
    }
    for (Action *a : this->m_actionsRuntimePos) {
        if (!a->isDisruptive()
                && !(disruptiveAlreadyExecuted
                && dynamic_cast<actions::Block *>(a))) {
            executeAction(trans, containsBlock, ruleMessage, a, false);
        }
    }
    if (!disruptiveAlreadyExecuted && m_disruptiveAction != nullptr) {
        executeAction(trans, containsBlock, ruleMessage,
            m_disruptiveAction, false);
    }
}


void RuleWithActions::executeAction(Transaction *trans,
    bool containsBlock, RuleMessage &ruleMessage,
    Action *a, bool defaultContext) {
    if (a->isDisruptive() == false && *a->m_name.get() != "block") {
        ms_dbg_a(trans, 9, "Running " \
            "action: " + *a->m_name.get());
        a->evaluate(this, trans, ruleMessage);
        return;
    }

    if (defaultContext && !containsBlock) {
        ms_dbg_a(trans, 4, "Ignoring action: " + *a->m_name.get() + \
            " (rule does not cotains block)");
        return;
    }

    if (trans->getRuleEngineState() == RulesSet::EnabledRuleEngine) {
        ms_dbg_a(trans, 4, "Running (disruptive)     action: " + *a->m_name.get() + \
            ".");
        a->evaluate(this, trans, ruleMessage);
        return;
    }

    ms_dbg_a(trans, 4, "Not running any disruptive action (or block): " \
        + *a->m_name.get() + ". SecRuleEngine is not On.");
}


inline void RuleWithActions::executeTransformation(
    const actions::transformations::Transformation &a,
    std::string &value,
    const Transaction *trans,
    TransformationResults &ret,
    std::string &path,
    int &nth) const {

    if (a.transform(value, trans) &&
        m_containsMultiMatchAction) {
        ret.emplace_back(value, a.m_name);
        nth++;
    }

    if (path.empty()) {
        path.append(*a.m_name.get());
    } else {
        path.append("," + *a.m_name.get());
    }

    ms_dbg_a(trans, 9, " T (" + \
        std::to_string(nth) + ") " + \
        *a.m_name.get() + ": \"" + \
        utils::string::limitTo(80, value) +"\"");
}

void RuleWithActions::executeTransformations(
    const Transaction *trans, const std::string &in, TransformationResults &ret) {
    int none = 0;
    int transformations = 0;
    std::string path;
    auto value = in;

    if (m_containsMultiMatchAction == true) {
        /* keep the original value */
        ret.emplace_back(value,
                       std::make_shared<std::string>(path));
    }

    for (const Action *a : m_transformations) {
        if (a->m_isNone) {
            none++;
        }
    }

    // Check for transformations on the SecDefaultAction
    // Notice that first we make sure that won't be a t:none
    // on the target rule.
    if (none == 0) {
        for (auto &a : trans->m_rules->m_defaultActions[getPhase()]) {
            if (a->action_kind \
                != actions::Action::Kind::RunTimeBeforeMatchAttemptKind) {
                continue;
            }

            // FIXME: here the object needs to be a transformation already.
            auto t = dynamic_cast<const Transformation*>(a.get());
            assert(t != nullptr);
            executeTransformation(*t, value, trans, ret, path,
                transformations);
        }
    }

    for (const Transformation *a : m_transformations) {
        assert(a != nullptr);
        if (none == 0) {
            executeTransformation(*a, value, trans, ret, path,
                transformations);
        }
        if (a->m_isNone) {
            none--;
        }
    }

    // FIXME: It can't be something different from transformation. Sort this
    //        on rules compile time.
    for (auto &b :
        trans->m_rules->m_exceptions.m_action_pre_update_target_by_id) {
        if (m_ruleId != b.first) {
            continue;
        }
        auto a = dynamic_cast<const Transformation*>(b.second.get());
        assert(a != nullptr);
        if (a->m_isNone) {
            none++;
        }
    }

    for (auto &b :
        trans->m_rules->m_exceptions.m_action_pre_update_target_by_id) {
        if (m_ruleId != b.first) {
            continue;
        }
        auto a = dynamic_cast<const Transformation*>(b.second.get());
        assert(a != nullptr);
        if (none == 0) {
            executeTransformation(*a, value, trans, ret, path,
                transformations);
        }
        if (a->m_isNone) {
            none--;
        }
    }

    if (m_containsMultiMatchAction == true) {
        ms_dbg_a(trans, 9, "multiMatch is enabled. " \
            + std::to_string(ret.size()) + \
            " values to be tested.");
    }

    if (!m_containsMultiMatchAction) {
        ret.emplace_back(value,
                       std::make_shared<std::string>(path));
    }
}


bool RuleWithActions::containsTag(const std::string& name, Transaction *t) {
    for (auto &tag : m_actionsTag) {
        if (tag != NULL && tag->getName(t) == name) {
            return true;
        }
    }
    return false;
}


bool RuleWithActions::containsMsg(const std::string& name, Transaction *t) {
    return m_msg && m_msg->data(t) == name;
}


std::vector<actions::Action *> RuleWithActions::getActionsByName(const std::string& name,
    const Transaction *trans) {
    std::vector<actions::Action *> ret;
    for (auto &z : m_actionsRuntimePos) {
        if (*z->m_name.get() == name) {
            ret.push_back(z);
        }
    }
    for (auto &z : m_transformations) {
        if (*z->m_name.get() == name) {
            ret.push_back(z);
        }
    }
    for (auto &b :
        trans->m_rules->m_exceptions.m_action_pre_update_target_by_id) {
        if (m_ruleId != b.first) {
            continue;
        }
        actions::Action *z = b.second.get();
        if (*z->m_name.get() == name) {
            ret.push_back(z);
        }
    }
    for (auto &b :
        trans->m_rules->m_exceptions.m_action_pos_update_target_by_id) {
        if (m_ruleId != b.first) {
            continue;
        }
        actions::Action *z = b.second.get();
        if (*z->m_name.get() == name) {
            ret.push_back(z);
        }
    }
    return ret;
}

void RuleWithActions::performLogging(Transaction *trans,
    RuleMessage &ruleMessage,
    bool lastLog,
    bool chainedParentNull) const {

    /* last rule in the chain. */
    bool isItToBeLogged = ruleMessage.m_saveMessage;

    /**
    *
    * RuleMessage is stacked allocated for the rule execution,
    * anything beyond this may lead to invalid pointer access.
    *
    * In case of a warning, o set of messages is saved to be read
    * at audit log generation. Therefore demands a copy here.
    *
    * FIXME: Study an way to avoid the copy.
    *
    **/
    if (lastLog) {
        if (chainedParentNull) {
            isItToBeLogged = (ruleMessage.m_saveMessage && (m_chainedRuleParent == nullptr));
            if (isItToBeLogged && !hasMultimatch()) {
                /* warn */
                trans->m_rulesMessages.push_back(ruleMessage);

                /* error */
                if (!ruleMessage.m_isDisruptive) {
                    trans->serverLog(ruleMessage);
                }
            }
        } else if (hasBlockAction() && !hasMultimatch()) {
            /* warn */
            trans->m_rulesMessages.push_back(ruleMessage);
            /* error */
            if (!ruleMessage.m_isDisruptive) {
                trans->serverLog(ruleMessage);
            }
        } else {
            if (isItToBeLogged && !hasMultimatch()
                && !ruleMessage.m_message.empty()) {
                /* warn */
                trans->m_rulesMessages.push_back(ruleMessage);

                /* error */
                if (!ruleMessage.m_isDisruptive) {
                    trans->serverLog(ruleMessage);
                }
            }
        }
    } else {
        if (hasMultimatch() && isItToBeLogged) {
            /* warn */
            trans->m_rulesMessages.push_back(ruleMessage);

            /* error */
            if (!ruleMessage.m_isDisruptive) {
                trans->serverLog(ruleMessage);
            }

            ruleMessage.reset(false);
        }
    }
}

std::string RuleWithActions::logData(Transaction *t) { return m_logData->data(t); }
std::string RuleWithActions::msg(Transaction *t) { return m_msg->data(t); }
int RuleWithActions::severity() const { return m_severity->m_severity; }


}  // namespace modsecurity
