/*
 * ModSecurity, http://www.modsecurity.org/
 * Copyright (c) 2015 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.
 *
 */

#ifdef __cplusplus
#include <ctime>
#include <fstream>
#include <iomanip>
#include <iostream>
#include <list>
#include <map>
#include <sstream>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
#include <memory>
#endif

#include <stdlib.h>
#include <stddef.h>

#ifndef HEADERS_MODSECURITY_TRANSACTION_H_
#define HEADERS_MODSECURITY_TRANSACTION_H_

#ifndef __cplusplus
typedef struct ModSecurity_t ModSecurity;
typedef struct Transaction_t Transaction;
typedef struct Rules_t Rules;
#endif

#include "modsecurity/anchored_set_variable.h"
#include "modsecurity/anchored_variable.h"
#include "modsecurity/intervention.h"
#include "modsecurity/collection/collections.h"
#include "modsecurity/variable_value.h"
#include "modsecurity/collection/collection.h"
#include "modsecurity/variable_origin.h"

#ifndef NO_LOGS
#define ms_dbg(b, c) \
  do { \
      if (m_rules && m_rules->m_debugLog && m_rules->m_debugLog->m_debugLevel >= b) { \
          m_rules->debug(b, m_id, m_uri, c); \
      } \
  } while (0);
#else
#define ms_dbg(b, c) \
  do { } while (0);
#endif

#ifndef NO_LOGS
#define ms_dbg_a(t, b, c) \
  do { \
      if (t && t->m_rules && t->m_rules->m_debugLog && t->m_rules->m_debugLog->m_debugLevel >= b) { \
          t->debug(b, c); \
      } \
  } while (0);
#else
#define ms_dbg_a(t, b, c) \
    do { } while (0);
#endif


#define LOGFY_ADD(a, b) \
    yajl_gen_string(g, reinterpret_cast<const unsigned char*>(a), strlen(a)); \
    if (b == NULL) { \
      yajl_gen_string(g, reinterpret_cast<const unsigned char*>(""), \
          strlen("")); \
    } else { \
      yajl_gen_string(g, reinterpret_cast<const unsigned char*>(b), \
          strlen(b)); \
    }


#define LOGFY_ADD_INT(a, b) \
    yajl_gen_string(g, reinterpret_cast<const unsigned char*>(a), strlen(a)); \
    yajl_gen_number(g, reinterpret_cast<const char*>(b), strlen(b));

#define LOGFY_ADD_NUM(a, b) \
    yajl_gen_string(g, reinterpret_cast<const unsigned char*>(a), strlen(a)); \
    yajl_gen_integer(g, b);

#ifdef __cplusplus

namespace modsecurity {

class ModSecurity;
class Transaction;
class Rules;
class RuleMessage;
namespace actions {
class Action;
namespace disruptive {
enum AllowType : int;
}
}
namespace RequestBodyProcessor {
class XML;
class JSON;
}
namespace operators {
class Operator;
}


class TransactionAnchoredVariables {
 public:
    explicit TransactionAnchoredVariables(Transaction *t)
        : m_variableArgsNames(t, "ARGS_NAMES"),
        m_variableArgsGetNames(t, "ARGS_GET_NAMES"),
        m_variableArgsPostNames(t, "ARGS_POST_NAMES"),
        m_variableRequestHeadersNames(t, "REQUEST_HEADERS_NAMES"),
        m_variableResponseContentType(t, "RESPONSE_CONTENT_TYPE"),
        m_variableResponseHeadersNames(t, "RESPONSE_HEADERS_NAMES"),
        m_variableARGScombinedSize(t, "ARGS_COMBINED_SIZE"),
        m_variableAuthType(t, "AUTH_TYPE"),
        m_variableFilesCombinedSize(t, "FILES_COMBINED_SIZE"),
        m_variableFullRequest(t, "FULL_REQUEST"),
        m_variableFullRequestLength(t, "FULL_REQUEST_LENGTH"),
        m_variableInboundDataError(t, "INBOUND_DATA_ERROR"),
        m_variableMatchedVar(t, "MATCHED_VAR"),
        m_variableMatchedVarName(t, "MATCHED_VAR_NAME"),
        m_variableMultipartBoundaryQuoted(t, "MULTIPART_BOUNDARY_QUOTED"),
        m_variableMultipartBoundaryWhiteSpace(t,
            "MULTIPART_BOUNDARY_WHITESPACE"),
        m_variableMultipartCrlfLFLines(t, "MULTIPART_CRLF_LF_LINES"),
        m_variableMultipartDataAfter(t, "MULTIPART_DATA_AFTER"),
        m_variableMultipartDataBefore(t, "MULTIPART_DATA_BEFORE"),
        m_variableMultipartFileLimitExceeded(t,
            "MULTIPART_FILE_LIMIT_EXCEEDED"),
        m_variableMultipartHeaderFolding(t, "MULTIPART_HEADER_FOLDING"),
        m_variableMultipartInvalidHeaderFolding(t,
            "MULTIPART_INVALID_HEADER_FOLDING"),
        m_variableMultipartInvalidPart(t, "MULTIPART_INVALID_PART"),
        m_variableMultipartInvalidQuoting(t, "MULTIPART_INVALID_QUOTING"),
        m_variableMultipartLFLine(t, "MULTIPART_LF_LINE"),
        m_variableMultipartMissingSemicolon(t, "MULTIPART_MISSING_SEMICOLON"),
        m_variableMultipartStrictError(t, "MULTIPART_STRICT_ERROR"),
        m_variableMultipartUnmatchedBoundary(t,
            "MULTIPART_UNMATCHED_BOUNDARY"),
        m_variableOutboundDataError(t, "OUTBOUND_DATA_ERROR"),
        m_variablePathInfo(t, "PATH_INFO"),
        m_variableQueryString(t, "QUERY_STRING"),
        m_variableRemoteAddr(t, "REMOTE_ADDR"),
        m_variableRemoteHost(t, "REMOTE_HOST"),
        m_variableRemotePort(t, "REMOTE_PORT"),
        m_variableReqbodyError(t, "REQBODY_ERROR"),
        m_variableReqbodyErrorMsg(t, "REQBODY_ERROR_MSG"),
        m_variableReqbodyProcessorError(t, "REQBODY_PROCESSOR_ERROR"),
        m_variableReqbodyProcessorErrorMsg(t, "REQBODY_PROCESSOR_ERROR_MSG"),
        m_variableReqbodyProcessor(t, "REQBODY_PROCESSOR"),
        m_variableRequestBasename(t, "REQUEST_BASENAME"),
        m_variableRequestBody(t, "REQUEST_BODY"),
        m_variableRequestBodyLength(t, "REQUEST_BODY_LENGTH"),
        m_variableRequestFilename(t, "REQUEST_FILENAME"),
        m_variableRequestLine(t, "REQUEST_LINE"),
        m_variableRequestMethod(t, "REQUEST_METHOD"),
        m_variableRequestProtocol(t, "REQUEST_PROTOCOL"),
        m_variableRequestURI(t, "REQUEST_URI"),
        m_variableRequestURIRaw(t, "REQUEST_URI_RAW"),
        m_variableResource(t, "RESOURCE"),
        m_variableResponseBody(t, "RESPONSE_BODY"),
        m_variableResponseContentLength(t, "RESPONSE_CONTENT_LENGTH"),
        m_variableResponseProtocol(t, "RESPONSE_PROTOCOL"),
        m_variableResponseStatus(t, "RESPONSE_STATUS"),
        m_variableServerAddr(t, "SERVER_ADDR"),
        m_variableServerName(t, "SERVER_NAME"),
        m_variableServerPort(t, "SERVER_PORT"),
        m_variableSessionID(t, "SESSIONID"),
        m_variableUniqueID(t, "UNIQUE_ID"),
        m_variableUrlEncodedError(t, "URLENCODED_ERROR"),
        m_variableUserID(t, "USERID"),
        m_variableArgs(t, "ARGS"),
        m_variableArgsGet(t, "ARGS_GET"),
        m_variableArgsPost(t, "ARGS_POST"),
        m_variableFilesSizes(t, "FILES_SIZES"),
        m_variableFilesNames(t, "FILES_NAMES"),
        m_variableFilesTmpContent(t, "FILES_TMP_CONTENT"),
        m_variableMultipartFileName(t, "MULTIPART_FILENAME"),
        m_variableMultipartName(t, "MULTIPART_NAME"),
        m_variableMatchedVarsNames(t, "MATCHED_VARS_NAMES"),
        m_variableMatchedVars(t, "MATCHED_VARS"),
        m_variableFiles(t, "FILES"),
        m_variableRequestCookies(t, "REQUEST_COOKIES"),
        m_variableRequestHeaders(t, "REQUEST_HEADERS"),
        m_variableResponseHeaders(t, "RESPONSE_HEADERS"),
        m_variableGeo(t, "GEO"),
        m_variableRequestCookiesNames(t, "REQUEST_COOKIES_NAMES"),
        m_variableFilesTmpNames(t, "FILES_TMPNAMES"),
        m_variableOffset(0)
        { }

    AnchoredSetVariable m_variableArgsNames;
    AnchoredSetVariable m_variableArgsGetNames;
    AnchoredSetVariable m_variableArgsPostNames;
    AnchoredSetVariable m_variableRequestHeadersNames;
    AnchoredVariable m_variableResponseContentType;
    AnchoredSetVariable m_variableResponseHeadersNames;
    AnchoredVariable m_variableARGScombinedSize;
    AnchoredVariable m_variableAuthType;
    AnchoredVariable m_variableFilesCombinedSize;
    AnchoredVariable m_variableFullRequest;
    AnchoredVariable m_variableFullRequestLength;
    AnchoredVariable m_variableInboundDataError;
    AnchoredVariable m_variableMatchedVar;
    AnchoredVariable m_variableMatchedVarName;
    AnchoredVariable m_variableMultipartBoundaryQuoted;
    AnchoredVariable m_variableMultipartBoundaryWhiteSpace;
    AnchoredVariable m_variableMultipartCrlfLFLines;
    AnchoredVariable m_variableMultipartDataAfter;
    AnchoredVariable m_variableMultipartDataBefore;
    AnchoredVariable m_variableMultipartFileLimitExceeded;
    AnchoredVariable m_variableMultipartHeaderFolding;
    AnchoredVariable m_variableMultipartInvalidHeaderFolding;
    AnchoredVariable m_variableMultipartInvalidPart;
    AnchoredVariable m_variableMultipartInvalidQuoting;
    AnchoredVariable m_variableMultipartLFLine;
    AnchoredVariable m_variableMultipartMissingSemicolon;
    AnchoredVariable m_variableMultipartStrictError;
    AnchoredVariable m_variableMultipartUnmatchedBoundary;
    AnchoredVariable m_variableOutboundDataError;
    AnchoredVariable m_variablePathInfo;
    AnchoredVariable m_variableQueryString;
    AnchoredVariable m_variableRemoteAddr;
    AnchoredVariable m_variableRemoteHost;
    AnchoredVariable m_variableRemotePort;
    AnchoredVariable m_variableReqbodyError;
    AnchoredVariable m_variableReqbodyErrorMsg;
    AnchoredVariable m_variableReqbodyProcessorError;
    AnchoredVariable m_variableReqbodyProcessorErrorMsg;
    AnchoredVariable m_variableReqbodyProcessor;
    AnchoredVariable m_variableRequestBasename;
    AnchoredVariable m_variableRequestBody;
    AnchoredVariable m_variableRequestBodyLength;
    AnchoredVariable m_variableRequestFilename;
    AnchoredVariable m_variableRequestLine;
    AnchoredVariable m_variableRequestMethod;
    AnchoredVariable m_variableRequestProtocol;
    AnchoredVariable m_variableRequestURI;
    AnchoredVariable m_variableRequestURIRaw;
    AnchoredVariable m_variableResource;
    AnchoredVariable m_variableResponseBody;
    AnchoredVariable m_variableResponseContentLength;
    AnchoredVariable m_variableResponseProtocol;
    AnchoredVariable m_variableResponseStatus;
    AnchoredVariable m_variableServerAddr;
    AnchoredVariable m_variableServerName;
    AnchoredVariable m_variableServerPort;
    AnchoredVariable m_variableSessionID;
    AnchoredVariable m_variableUniqueID;
    AnchoredVariable m_variableUrlEncodedError;
    AnchoredVariable m_variableUserID;

    AnchoredSetVariable m_variableArgs;
    AnchoredSetVariable m_variableArgsGet;
    AnchoredSetVariable m_variableArgsPost;
    AnchoredSetVariable m_variableFilesSizes;
    AnchoredSetVariable m_variableFilesNames;
    AnchoredSetVariable m_variableFilesTmpContent;
    AnchoredSetVariable m_variableMultipartFileName;
    AnchoredSetVariable m_variableMultipartName;
    AnchoredSetVariable m_variableMatchedVarsNames;
    AnchoredSetVariable m_variableMatchedVars;
    AnchoredSetVariable m_variableFiles;
    AnchoredSetVariable m_variableRequestCookies;
    AnchoredSetVariable m_variableRequestHeaders;
    AnchoredSetVariable m_variableResponseHeaders;
    AnchoredSetVariable m_variableGeo;
    AnchoredSetVariable m_variableRequestCookiesNames;
    AnchoredSetVariable m_variableFilesTmpNames;

    int m_variableOffset;
};


/** @ingroup ModSecurity_CPP_API */
class Transaction : public TransactionAnchoredVariables {
 public:
    Transaction(ModSecurity *transaction, Rules *rules, void *logCbData);
    Transaction(ModSecurity *transaction, Rules *rules, char *id,
        void *logCbData);
    ~Transaction();

    /** TODO: Should be an structure that fits an IP address */
    int processConnection(const char *client, int cPort,
        const char *server, int sPort);
    int processURI(const char *uri, const char *protocol,
        const char *http_version);

    /**
     * Types of request body that ModSecurity may give a special treatment
     * for the data.
     */
    enum RequestBodyType {
      /**
       *
       */
      UnknownFormat,
      /**
       *
       */
      MultiPartRequestBody,
      /**
       *
       */
      WWWFormUrlEncoded,
      /**
       *
       */
      JSONRequestBody,
      /**
       *
       */
      XMLRequestBody
    };

    int processRequestHeaders();
    int addRequestHeader(const std::string& key, const std::string& value);
    int addRequestHeader(const unsigned char *key, const unsigned char *value);
    int addRequestHeader(const unsigned char *key, size_t len_key,
        const unsigned char *value, size_t len_value);

    int processRequestBody();
    int appendRequestBody(const unsigned char *body, size_t size);
    int requestBodyFromFile(const char *path);

    int processResponseHeaders(int code, const std::string& proto);
    int addResponseHeader(const std::string& key, const std::string& value);
    int addResponseHeader(const unsigned char *key, const unsigned char *value);
    int addResponseHeader(const unsigned char *key, size_t len_key,
        const unsigned char *value, size_t len_value);

    int processResponseBody();
    int appendResponseBody(const unsigned char *body, size_t size);

    int processLogging();
    int updateStatusCode(int status);

    bool intervention(ModSecurityIntervention *it);

    bool addArgument(const std::string& orig, const std::string& key,
        const std::string& value, size_t offset);
    bool extractArguments(const std::string &orig, const std::string& buf,
        size_t offset);

    const char *getResponseBody();
    size_t getResponseBodyLength();
    size_t getRequestBodyLength();

#ifndef NO_LOGS
    void debug(int, std::string) const;
#endif
    void serverLog(std::shared_ptr<RuleMessage> rm);

    int getRuleEngineState();

    std::string toJSON(int parts);
    std::string toOldAuditLogFormat(int parts, const std::string &trailer);
    std::string toOldAuditLogFormatIndex(const std::string &filename,
        double size, const std::string &md5);

    /**
     * Filled during the class instantiation, this variable can be later
     * used to fill the SecRule variable `duration'. The variable `duration'
     * is dynamic calculated, it is always relative to the value found in
     * m_creationTimeStamp.
     *
     * @note There is space for performance improvement. This value don't
	 *       need to be filled if there is no rule using the variable
	 *       `duration'.
     */
    clock_t m_creationTimeStamp;

    /**
     * Holds the client IP address.
     */
    std::string m_clientIpAddress;

    /**
     * Holds the HTTP version: 1.2, 2.0, 3.0 and so on....
     */
    std::string m_httpVersion;

    /**
     * Holds the server IP Address
     */
    std::string m_serverIpAddress;

    /**
     * Holds the raw URI that was requested.
     */
    std::string m_uri;

    /**
     * Holds the URI that was requests (without the query string).
     */
    std::string m_uri_no_query_string_decoded;

    /**
     * Holds the combined size of all arguments, later used to fill the
     * variable  ARGS_COMBINED_SIZE.
     */
    double m_ARGScombinedSizeDouble;

    /**
     * Client tcp port.
     */
    int m_clientPort;

    /**
     * This variable is set by the action `severity' and later can be
     * consulted via the SecLanguage variable HIGHEST_SEVERITY.
     */
    int m_highestSeverityAction;

    /**
     * Holds the HTTP return code when it is known. If 0 nothing was
	 * set.
     */
    int m_httpCodeReturned;

    /**
     * Holds the server port.
     */
    int m_serverPort;

    /**
     * ModSecurity instance used to start this transaction. Basically used
     * to fill the server log whenever is needed.
     */
    ModSecurity *m_ms;

    /**
     * Holds the type of the request body, in case there is one.
     */
    RequestBodyType m_requestBodyType;

    /**
     * Holds the request body "processor"
     */
    RequestBodyType m_requestBodyProcessor;

    /**
     * Rules object utilized during this specific transaction.
     */
    Rules *m_rules;

    /**
     *
     */
    std::list<int > m_ruleRemoveById;
    std::list<std::pair<int, int> > m_ruleRemoveByIdRange;

    /**
     *
     */
    std::list<std::string> m_ruleRemoveByTag;

    /**
     *
     */
    std::list< std::pair<std::string, std::string> > m_ruleRemoveTargetByTag;

    /**
     *
     */
    std::list< std::pair<int, std::string> > m_ruleRemoveTargetById;

    /**
     *
     */
    int m_requestBodyAccess;

    /**
     * The list m_auditLogModifier contains modifications to the `auditlogs'
     * for this specific request, those modifications can happens via the
     * utilization of the action: `ctl:auditLogParts='
     *
     */
    std::list< std::pair<int, std::string> > m_auditLogModifier;

    /**
     * This variable holds all the messages asked to be save by the utilization
     * of the actions: `log_data' and `msg'. These should be included on the
     * auditlogs.
     */
    std::list<modsecurity::RuleMessage> m_rulesMessages;

    /**
     * Holds the request body, in case of any.
     */
    std::ostringstream m_requestBody;

    /**
     * Holds the response body, in case of any.
     */
    std::ostringstream m_responseBody;

    /**
     * Contains the unique ID of the transaction. Use by the variable
	 * `UNIQUE_ID'. This unique id is also saved as part of the AuditLog.
     */
    std::string m_id;

    /**
     * Holds the SecMarker name that this transaction should wait to perform
     * rules evaluation again.
     */
    std::string m_marker;

    /**
     * Holds the amount of rules that should be skipped. If bigger than 0 the
     * current rule should be skipped and the number needs to be decreased.
     */
    int m_skip_next;

    /**
     * If allow action was utilized, this variable holds the allow type.
     */
    modsecurity::actions::disruptive::AllowType m_allowType;

    /**
     * Holds the decode URI. Notice that m_uri holds the raw version
     * of the URI.
     */
    std::string m_uri_decoded;

    /**
     * Actions (disruptive?) that should be taken by the connector related to
     * that transaction.
     */
    std::vector<ModSecurityIntervention> m_actions;
    ModSecurityIntervention m_it;

    /**
     * Holds the creation time stamp, using std::time.
     *
     * TODO: m_timeStamp and m_creationTimeStamp may be merged into a single
     *       variable.
     */
    time_t m_timeStamp;


    /**
     * Holds all the collections related to that transaction.
     */
    collection::Collections m_collections;

    /**
     * Holds the whatever matched in the operation utilization.
     * That variable will be further used by the capture action.
     *
     */
    std::list<std::string> m_matched;

    RequestBodyProcessor::XML *m_xml;
    RequestBodyProcessor::JSON *m_json;

    int m_secRuleEngine;

    std::string m_variableDuration;
    std::map<std::string, std::string> m_variableEnvs;
    std::string m_variableHighestSeverityAction;
    std::string m_variableRemoteUser;
    std::string m_variableTime;
    std::string m_variableTimeDay;
    std::string m_variableTimeEpoch;
    std::string m_variableTimeHour;
    std::string m_variableTimeMin;
    std::string m_variableTimeSec;
    std::string m_variableTimeWDay;
    std::string m_variableTimeYear;

 private:
    /**
     * Pointer to the callback function that will be called to fill
     * the web server (connector) log.
     */
    void *m_logCbData;
};


#endif

#ifdef __cplusplus
extern "C" {
#endif

/** @ingroup ModSecurity_C_API */
Transaction *msc_new_transaction(ModSecurity *ms,
    Rules *rules, void *logCbData);

/** @ingroup ModSecurity_C_API */
Transaction *msc_new_transaction_with_id(ModSecurity *ms,
    Rules *rules, char *id, void *logCbData);

/** @ingroup ModSecurity_C_API */
int msc_process_connection(Transaction *transaction,
    const char *client, int cPort, const char *server, int sPort);

/** @ingroup ModSecurity_C_API */
int msc_process_request_headers(Transaction *transaction);

/** @ingroup ModSecurity_C_API */
int msc_add_request_header(Transaction *transaction, const unsigned char *key,
    const unsigned char *value);

/** @ingroup ModSecurity_C_API */
int msc_add_n_request_header(Transaction *transaction,
    const unsigned char *key, size_t len_key, const unsigned char *value,
    size_t len_value);

/** @ingroup ModSecurity_C_API */
int msc_process_request_body(Transaction *transaction);

/** @ingroup ModSecurity_C_API */
int msc_append_request_body(Transaction *transaction,
    const unsigned char *body, size_t size);

/** @ingroup ModSecurity_C_API */
int msc_request_body_from_file(Transaction *transaction, const char *path);

/** @ingroup ModSecurity_C_API */
int msc_process_response_headers(Transaction *transaction, int code,
    const char* protocol);

/** @ingroup ModSecurity_C_API */
int msc_add_response_header(Transaction *transaction,
    const unsigned char *key, const unsigned char *value);

/** @ingroup ModSecurity_C_API */
int msc_add_n_response_header(Transaction *transaction,
    const unsigned char *key, size_t len_key, const unsigned char *value,
    size_t len_value);

/** @ingroup ModSecurity_C_API */
int msc_process_response_body(Transaction *transaction);

/** @ingroup ModSecurity_C_API */
int msc_append_response_body(Transaction *transaction,
    const unsigned char *body, size_t size);

/** @ingroup ModSecurity_C_API */
int msc_process_uri(Transaction *transaction, const char *uri,
    const char *protocol, const char *http_version);

/** @ingroup ModSecurity_C_API */
const char *msc_get_response_body(Transaction *transaction);

/** @ingroup ModSecurity_C_API */
size_t msc_get_response_body_length(Transaction *transaction);

/** @ingroup ModSecurity_C_API */
size_t msc_get_request_body_length(Transaction *transaction);

/** @ingroup ModSecurity_C_API */
void msc_transaction_cleanup(Transaction *transaction);

/** @ingroup ModSecurity_C_API */
int msc_intervention(Transaction *transaction, ModSecurityIntervention *it);

/** @ingroup ModSecurity_C_API */
int msc_process_logging(Transaction *transaction);

/** @ingroup ModSecurity_C_API */
int msc_update_status_code(Transaction *transaction, int status);

#ifdef __cplusplus
}
}  // namespace modsecurity
#endif


#endif  // HEADERS_MODSECURITY_TRANSACTION_H_
