<commit_before>/*****************************************************************************
 * Licensed to Qualys, Inc. (QUALYS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * QUALYS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); 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
 *
 * 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.
 *****************************************************************************/

/**
 * @file
 * @brief IronBee
 *
 * @author <NAME> <<EMAIL>>
 */

#include "ironbee_config_auto.h"

#include <assert.h>

#include <ironbee/bytestr.h>
#include <ironbee/rule_engine.h>
#include <ironbee/util.h>
#include <ironbee/field.h>
#include <ironbee/debug.h>
#include <ironbee/mpool.h>
#include <ironbee/transformation.h>
#include <ironbee/operator.h>
#include <ironbee/action.h>
#include <ironbee/rule_engine.h>

#include <ironbee/debug.h>
#include <ironbee/mpool.h>

#include "ironbee_private.h"


/**
 * Phase Flags
 */
#define PHASE_FLAG_NONE          (0x0)     /**< No phase flags */
#define PHASE_FLAG_IS_VALID      (1 << 0)  /**< Phase is valid */
#define PHASE_FLAG_IS_STREAM     (1 << 1)  /**< Phase is steam inspection */
#define PHASE_FLAG_ALLOW_CHAIN   (1 << 2)  /**< Rule allows chaining */
#define PHASE_FLAG_ALLOW_TFNS    (1 << 3)  /**< Rule allows transformations */

/**
 * Max # of data types (IB_DTYPE_*) per rule phase
 */
#define MAX_PHASE_DATA_TYPES 4

/**
 * Data on each rule phase, one per phase.
 */
struct ib_rule_phase_meta_t {
    ib_bool_t                   is_stream;
    ib_rule_phase_t             phase_num;
    ib_state_hook_type_t        hook_type;
    ib_flags_t                  flags;
    const char                 *name;
    ib_flags_t                  required_op_flags;
    ib_state_event_type_t       event;
};

/* Rule definition data */
static const ib_rule_phase_meta_t rule_phase_meta[] =
{
    {
        IB_FALSE,
        PHASE_NONE,
        (ib_state_hook_type_t) -1,
        (PHASE_FLAG_ALLOW_CHAIN | PHASE_FLAG_ALLOW_TFNS),
        "Generic 'Phase' Rule",
        IB_OP_FLAG_PHASE,
        (ib_state_event_type_t) -1
    },
    {
        IB_FALSE,
        PHASE_REQUEST_HEADER,
        IB_STATE_HOOK_TX,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_ALLOW_CHAIN | PHASE_FLAG_ALLOW_TFNS),
        "Request Header",
        IB_OP_FLAG_PHASE,
        handle_request_header_event
    },
    {
        IB_FALSE,
        PHASE_REQUEST_BODY,
        IB_STATE_HOOK_TX,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_ALLOW_CHAIN | PHASE_FLAG_ALLOW_TFNS),
        "Request Body",
        IB_OP_FLAG_PHASE,
        handle_request_event
    },
    {
        IB_FALSE,
        PHASE_RESPONSE_HEADER,
        IB_STATE_HOOK_TX,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_ALLOW_CHAIN | PHASE_FLAG_ALLOW_TFNS),
        "Response Header",
        IB_OP_FLAG_PHASE,
        handle_response_header_event
    },
    {
        IB_FALSE,
        PHASE_RESPONSE_BODY,
        IB_STATE_HOOK_TX,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_ALLOW_CHAIN | PHASE_FLAG_ALLOW_TFNS),
        "Response Body",
        IB_OP_FLAG_PHASE,
        handle_response_event
    },
    {
        IB_FALSE,
        PHASE_POSTPROCESS,
        IB_STATE_HOOK_TX,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_ALLOW_CHAIN | PHASE_FLAG_ALLOW_TFNS),
        "Post Process",
        IB_OP_FLAG_PHASE,
        handle_postprocess_event
    },

    /* Stream rule phases */
    {
        IB_TRUE,
        PHASE_NONE,
        (ib_state_hook_type_t) -1,
        (PHASE_FLAG_IS_STREAM),
        "Generic 'Stream Inspection' Rule",
        IB_OP_FLAG_STREAM,
        (ib_state_event_type_t) -1
    },
    {
        IB_TRUE,
        PHASE_STR_REQUEST_HEADER,
        IB_STATE_HOOK_TX,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_IS_STREAM),
        "Request Header Stream",
        IB_OP_FLAG_STREAM,
        handle_context_tx_event
    },
    {
        IB_TRUE,
        PHASE_STR_REQUEST_BODY,
        IB_STATE_HOOK_TXDATA,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_IS_STREAM),
        "Request Body Stream",
        IB_OP_FLAG_STREAM,
        request_body_data_event
    },
    {
        IB_TRUE,
        PHASE_STR_RESPONSE_HEADER,
        IB_STATE_HOOK_HEADER,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_IS_STREAM),
        "Response Header Stream",
        IB_OP_FLAG_STREAM,
        response_header_data_event
    },
    {
        IB_TRUE,
        PHASE_STR_RESPONSE_BODY,
        IB_STATE_HOOK_TXDATA,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_IS_STREAM),
        "Response Body Stream",
        IB_OP_FLAG_STREAM,
        response_body_data_event
    },
    {
        IB_FALSE,
        PHASE_INVALID,
        (ib_state_hook_type_t) -1,
        PHASE_FLAG_NONE,
        "Invalid",
        IB_OP_FLAG_NONE,
        (ib_state_event_type_t) -1
    }
};

/* The rule engine uses recursion to walk through lists and chains.  These
 * define the limits to the depth of those recursions. */
#define MAX_LIST_RECURSION   (5)       /**< Max list recursion limit */
#define MAX_CHAIN_RECURSION  (10)      /**< Max chain recursion limit */

/**
 * Test the validity of a phase number
 * @internal
 *
 * @param phase_num Phase number to check
 *
 * @returns IB_TRUE if @phase_num is valid, IB_FALSE if not.
 */
static inline ib_bool_t is_phase_num_valid(ib_rule_phase_t phase_num)
{
    IB_FTRACE_INIT();
    IB_FTRACE_RET_UINT(
        ( (phase_num >= PHASE_NONE) && (phase_num < IB_RULE_PHASE_COUNT) )
        ? IB_TRUE : IB_FALSE);
}

/**
 * Find a rule's matching phase meta data, matching the only the phase number.
 * @internal
 *
 * @param[in] phase_num Phase number (PHASE_xxx)
 * @param[out] phase_meta Matching rule phase meta-data
 *
 * @returns Status code
 */
static ib_status_t find_phase_meta(ib_rule_phase_t phase_num,
                                   const ib_rule_phase_meta_t **phase_meta)
{
    IB_FTRACE_INIT();
    const ib_rule_phase_meta_t *meta;

    assert (phase_meta != NULL);
    assert (is_phase_num_valid(phase_num) == IB_TRUE);

    /* Loop through all parent rules */
    for (meta = rule_phase_meta;  meta->phase_num != PHASE_INVALID;  ++meta)
    {
        if (meta->phase_num == phase_num) {
            *phase_meta = meta;
            IB_FTRACE_RET_STATUS(IB_OK);
        }
    }
    IB_FTRACE_RET_STATUS(IB_ENOENT);
}

/**
 * Find a rule's matching phase meta data, matching the phase number and
 * the phase's stream type.
 * @internal
 *
 * @param[in] is_stream IB_TRUE if this is a "stream inspection" rule
 * @param[in] phase_num Phase number (PHASE_xxx)
 * @param[out] phase_meta Matching rule phase meta-data
 *
 * @note If @a is_stream is IB_TRI_UNSET, this function will return the
 * first phase meta-data object that matches the phase number.
 *
 * @returns Status code
 */
static ib_status_t find_phase_stream_meta(
    ib_bool_t is_stream,
    ib_rule_phase_t phase_num,
    const ib_rule_phase_meta_t **phase_meta)
{
    IB_FTRACE_INIT();
    const ib_rule_phase_meta_t *meta;

    assert (phase_meta != NULL);
    assert (is_phase_num_valid(phase_num) == IB_TRUE);

    /* Loop through all parent rules */
    for (meta = rule_phase_meta;  meta->phase_num != PHASE_INVALID;  ++meta)
    {
        if ( (meta->phase_num == phase_num) &&
             (is_stream == meta->is_stream) )
        {
            *phase_meta = meta;
            IB_FTRACE_RET_STATUS(IB_OK);
        }
    }
    IB_FTRACE_RET_STATUS(IB_ENOENT);
}

/**
 * Execute a field's transformations.
 * @internal
 *
 * @param[in] ib Engine
 * @param[in] tx Transaction
 * @param[in] target Target field
 * @param[in] value Initial value of the target field
 * @param[out] result Pointer to field in which to store the result
 *
 * @returns Status code
 */
static ib_status_t execute_field_tfns(ib_engine_t *ib,
                                      ib_tx_t *tx,
                                      ib_rule_target_t *target,
                                      ib_field_t *value,
                                      ib_field_t **result)
{
    IB_FTRACE_INIT();
    ib_status_t     rc;
    ib_num_t        n = 0;
    ib_list_node_t *node = NULL;
    ib_field_t     *in_field;
    ib_field_t     *out = NULL;

    assert(ib != NULL);
    assert(tx != NULL);
    assert(target != NULL);
    assert(result != NULL);

    /* No transformations?  Do nothing. */
    if (IB_LIST_ELEMENTS(target->tfn_list) == 0) {
        *result = value;
        ib_log_debug3_tx(tx,
                         "No transformations for field %s", target->field_name);
        IB_FTRACE_RET_STATUS(IB_OK);
    }
    else if (value == NULL) {
        IB_FTRACE_RET_STATUS(IB_OK);
    }

    ib_log_debug3_tx(tx,
                     "Executing %d transformations on field %s",
                     IB_LIST_ELEMENTS(target->tfn_list), target->field_name);

    /*
     * Loop through all of the field operators.
     */
    in_field = value;
    IB_LIST_LOOP(target->tfn_list, node) {
        ib_tfn_t  *tfn = (ib_tfn_t *)node->data;
        ib_flags_t flags = 0;

        /* Run it */
        ++n;
        ib_log_debug3_tx(tx,
                         "Executing field transformation #%d '%s' on '%s'",
                         n, tfn->name, target->field_name);
        ib_log_rule_field(ib, "before tfn", in_field);
        rc = ib_tfn_transform(ib, tx->mp, tfn, in_field, &out, &flags);
        if (rc != IB_OK) {
            ib_log_error_tx(tx,
                            "Error executing field operator #%d field %s: %s",
                            n, target->field_name, ib_status_to_string(rc));
            IB_FTRACE_RET_STATUS(rc);
        }
        ib_log_rule_field(ib, "after tfn", out);

        /* Verify that out isn't NULL */
        if (out == NULL) {
            ib_log_error_tx(tx,
                            "Field operator #%d field %s returned NULL",
                            n, target->field_name);
            IB_FTRACE_RET_STATUS(IB_EINVAL);
        }

        /* The output of the operator is now input for the next field op. */
        in_field = out;
    }

    /* The output of the final operator is the result */
    *result = out;

    /* Done. */
    IB_FTRACE_RET_STATUS(IB_OK);
}

/**
 * Perform a block operation by signaling an error to the server.
 *
 * The server is signaled with
 * ib_server_error_response(ib_server_t *, ib_context_t *, int) using
 * @a tx and @a ib to provide the @c ib_context_t* and @c ib_server_t*,
 * respectively.
 *
 * @param ib IronBee engine containing the server callbacks.
 * @param tx Transaction containing the active context.
 *
 * @returns The result of calling ib_server_error_response().
 */
static ib_status_t report_block_to_server(ib_engine_t *ib, ib_tx_t *tx)
{
    IB_FTRACE_INIT();

    /* Store the final return code here. */
    ib_status_t rc;

    assert(ib);
    assert(ib->server);
    assert(tx);
    assert(tx->ctx);

    ib_log_debug_tx(tx, "Setting HTTP error response: status=%d",
                    tx->block_status);
    rc = ib_server_error_response(ib->server, tx, tx->block_status);
    if (rc == IB_DECLINED) {
        ib_log_notice_tx(tx,
                         "Server not willing to set HTTP error response.");
    }
    else if (rc != IB_OK) {
        ib_log_error_tx(tx,
                        "Server failed to set HTTP error response: %s",
                        ib_status_to_string(rc));
    }

    IB_FTRACE_RET_STATUS(rc);
}

/**
 * Execute a rule on a list of values
 * @internal
 *
 * @param[in] ib Engine
 * @param[in] tx Transaction
 * @param[in] opinst Operator instance
 * @param[in] fname Field name
 * @param[in] value Field value to operate on
 * @param[in] recursion Recursion limit -- won't recurse if recursion is zero
 * @param[out] rule_result Pointer to number in which to store the result
 *
 * @returns Status code
 */
static ib_status_t execute_rule_operator(ib_engine_t *ib,
                                         ib_tx_t *tx,
                                         ib_operator_inst_t *opinst,
                                         const char *fname,
                                         ib_field_t *value,
                                         ib_num_t recursion,
                                         ib_num_t *rule_result)
{
    IB_FTRACE_INIT();

    assert(ib != NULL);
    assert(tx != NULL);
    assert(opinst != NULL);
    assert(fname != NULL);

    ib_status_t rc;

    /* Limit recursion */
    --recursion;
    if (recursion <= 0) {
        ib_log_error_tx(tx, "Rule engine: List recursion limit reached");
        IB_FTRACE_RET_STATUS(IB_EOTHER);
    }

    /* Handle a list by looping through it */
    if ( (value != NULL) && (value->type == IB_FTYPE_LIST) ) {
        // @todo Remove mutable once list is const correct.
        ib_list_t *vlist;
        rc = ib_field_mutable_value(value, ib_ftype_list_mutable_out(&vlist));
        if (rc != IB_OK) {
            IB_FTRACE_RET_STATUS(rc);
        }

        ib_list_node_t *node = NULL;
        ib_num_t n = 0;

        IB_LIST_LOOP(vlist, node) {
            ib_field_t *nvalue = (ib_field_t *)ib_list_node_data(node);
            ++n;

            rc = execute_rule_operator(
                ib, tx, opinst, fname, nvalue, recursion, rule_result);
            if (rc != IB_OK) {
                ib_log_debug_tx(tx,
                             "Error executing %s on list element #%d: %s",
                             opinst->op->name, n, ib_status_to_string(rc));
            }
        }
        ib_log_debug3_tx(tx, "Operator %s, target %s (list %zd) => %d",
                     opinst->op->name, fname, vlist->nelts, *rule_result);
    }
    else {
        /* Execute the operator */
        ib_num_t result;
        rc = ib_operator_execute(ib, tx, opinst, value, &result);
        if (rc != IB_OK) {
            ib_log_debug_tx(tx,
                         "Operator %s returned an error for field %s: %s",
                         opinst->op->name, fname, ib_status_to_string(rc));
            IB_FTRACE_RET_STATUS(rc);
        }

        /* Store the result */
        if (result != 0) {
            *rule_result = result;
        }
    }

    IB_FTRACE_RET_STATUS(IB_OK);
}

static ib_status_t execute_phase_rule_targets_operators(
    ib_engine_t *ib,
    ib_tx_t *tx,
    ib_rule_target_t *target,
    ib_operator_inst_t  *opinst,
    ib_field_t *value,
    ib_num_t *rule_result,
    ib_list_t *target_results)
{
    IB_FTRACE_INIT();

    ib_status_t rc;
    ib_num_t result;
    ib_field_t *tfnvalue = NULL;  /* Value after transformations */

    const char *fname = target->field_name;

        /* Execute the field operators */
        rc = execute_field_tfns(ib, tx, target, value, &tfnvalue);
        if (rc != IB_OK) {
            ib_log_error_tx(tx,
                            "Error executing transformation for %s on %s: %s",
                            opinst->op->name, fname, ib_status_to_string(rc));
            IB_FTRACE_RET_STATUS(rc);
        }

        /* Execute the rule operator */
        rc = execute_rule_operator(ib,
                                   tx,
                                   opinst,
                                   fname,
                                   tfnvalue,
                                   MAX_LIST_RECURSION,
                                   &result);
        if (rc != IB_OK) {
            ib_log_error_tx(tx,
                            "Operator %s returned an error for field %s: %s",
                            opinst->op->name, fname, ib_status_to_string(rc));
            IB_FTRACE_RET_STATUS(rc);
        }
        ib_log_debug3_tx(tx, "Operator %s, field %s => %d",
                         opinst->op->name, fname, result);

        /* Store the result */
        if (result != 0) {
            *rule_result = result;
        }

        /* Create a rule target execution result object */
        if (target_results != NULL) {
            ib_rule_target_result_t *target_result =
                (ib_rule_target_result_t *)
                ib_mpool_alloc(tx->mp, sizeof(*target_result));
            if (target_result != NULL) {
                target_result->target = target;
                target_result->original = value;
                target_result->transformed = tfnvalue;
                target_result->result = result;
                ib_list_push(target_results, target_result);
            }
        }
    IB_FTRACE_RET_STATUS(IB_OK);
}

/**
 * Execute a single rule's operator on all target fields.
 * @internal
 *
 * @param[in] ib Engine
 * @param[in] rule Rule to execute
 * @param[in,out] tx Transaction
 * @param[out] rule_result Pointer to number in which to store the result
 *
 * @returns Status code
 */
static ib_status_t execute_phase_rule_targets(ib_engine_t *ib,
                                              ib_rule_t *rule,
                                              ib_tx_t *tx,
                                              ib_num_t *rule_result,
                                              ib_list_t *target_results)
{
    IB_FTRACE_INIT();
    assert(ib);
    assert(rule);
    assert(tx);
    assert(rule_result);
    ib_list_node_t      *node = NULL;
    ib_operator_inst_t  *opinst = rule->opinst;

    /* Log what we're going to do */
    ib_log_debug3_tx(tx, "Executing rule %s", rule->meta.id);

    /* Special case: External rules */
    if ( (rule->flags & IB_RULE_FLAG_EXTERNAL) != 0) {
        ib_status_t rc;

        /* Execute the operator */
        ib_log_debug3_tx(tx, "Executing external rule");
        rc = ib_operator_execute(ib, tx, opinst, NULL, rule_result);
        if (rc != IB_OK) {
            ib_log_error_tx(tx,
                         "External operator %s returned an error: %s",
                         opinst->op->name, ib_status_to_string(rc));
        }
        IB_FTRACE_RET_STATUS(rc);
    }

    /*
     * Loop through all of the fields.
     *
     * @todo The current behavior is to keep running even after an operator
     * returns an error.  This needs further discussion to determine what the
     * correct behavior should be.
     */
    IB_LIST_LOOP(rule->target_fields, node) {
        ib_rule_target_t *target = (ib_rule_target_t *)node->data;
        assert(target != NULL);
        const char       *fname = target->field_name;
        assert(fname != NULL);
        ib_field_t       *value = NULL;     /* Value from the DPI */
        ib_status_t       rc = IB_OK;

        /* Get the field value */
        rc = ib_data_get(tx->dpi, fname, &value);
        if (rc == IB_ENOENT) {
            ib_bool_t allow_null =
                ib_flags_all(opinst->op->flags, IB_OP_FLAG_ALLOW_NULL);
            if (allow_null == IB_FALSE) {
                continue;
            }
        }
        else if (rc != IB_OK) {
            ib_log_error_tx(tx, "Error getting field %s: %s",
                            fname, ib_status_to_string(rc));
            continue;
        }

        if ( value->type == IB_FTYPE_LIST ) {
            ib_list_t *value_list;
            ib_list_node_t *value_node;
            rc = ib_field_value(value, (void *)&value_list);

            if (rc!=IB_OK) {
                ib_log_error_tx(tx, "Error getting field value %s: %s",
                                value->name, rc);
                continue;
            }

            /* Run operations on each list element. */
            IB_LIST_LOOP(value_list, value_node) {
                rc = execute_phase_rule_targets_operators(
                    ib,
                    tx,
                    target,
                    opinst,
                    (ib_field_t *) value_node->data,
                    rule_result,
                    target_results);

                if (rc!=IB_OK) {
                    continue;
                }
            }
        }
        else {
            rc = execute_phase_rule_targets_operators(ib,
                                                      tx,
                                                      target,
                                                      opinst,
                                                      value,
                                                      rule_result,
                                                      target_results);
        }<commit_msg>Iterate through Rule Targets that are Lists<commit_after>/*****************************************************************************
 * Licensed to Qualys, Inc. (QUALYS) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * QUALYS licenses this file to You under the Apache License, Version 2.0
 * (the "License"); 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
 *
 * 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.
 *****************************************************************************/

/**
 * @file
 * @brief IronBee
 *
 * @author <NAME> <<EMAIL>>
 */

#include "ironbee_config_auto.h"

#include <assert.h>

#include <ironbee/bytestr.h>
#include <ironbee/rule_engine.h>
#include <ironbee/util.h>
#include <ironbee/field.h>
#include <ironbee/debug.h>
#include <ironbee/mpool.h>
#include <ironbee/transformation.h>
#include <ironbee/operator.h>
#include <ironbee/action.h>
#include <ironbee/rule_engine.h>

#include <ironbee/debug.h>
#include <ironbee/mpool.h>

#include "ironbee_private.h"


/**
 * Phase Flags
 */
#define PHASE_FLAG_NONE          (0x0)     /**< No phase flags */
#define PHASE_FLAG_IS_VALID      (1 << 0)  /**< Phase is valid */
#define PHASE_FLAG_IS_STREAM     (1 << 1)  /**< Phase is steam inspection */
#define PHASE_FLAG_ALLOW_CHAIN   (1 << 2)  /**< Rule allows chaining */
#define PHASE_FLAG_ALLOW_TFNS    (1 << 3)  /**< Rule allows transformations */

/**
 * Max # of data types (IB_DTYPE_*) per rule phase
 */
#define MAX_PHASE_DATA_TYPES 4

/**
 * Data on each rule phase, one per phase.
 */
struct ib_rule_phase_meta_t {
    ib_bool_t                   is_stream;
    ib_rule_phase_t             phase_num;
    ib_state_hook_type_t        hook_type;
    ib_flags_t                  flags;
    const char                 *name;
    ib_flags_t                  required_op_flags;
    ib_state_event_type_t       event;
};

/* Rule definition data */
static const ib_rule_phase_meta_t rule_phase_meta[] =
{
    {
        IB_FALSE,
        PHASE_NONE,
        (ib_state_hook_type_t) -1,
        (PHASE_FLAG_ALLOW_CHAIN | PHASE_FLAG_ALLOW_TFNS),
        "Generic 'Phase' Rule",
        IB_OP_FLAG_PHASE,
        (ib_state_event_type_t) -1
    },
    {
        IB_FALSE,
        PHASE_REQUEST_HEADER,
        IB_STATE_HOOK_TX,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_ALLOW_CHAIN | PHASE_FLAG_ALLOW_TFNS),
        "Request Header",
        IB_OP_FLAG_PHASE,
        handle_request_header_event
    },
    {
        IB_FALSE,
        PHASE_REQUEST_BODY,
        IB_STATE_HOOK_TX,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_ALLOW_CHAIN | PHASE_FLAG_ALLOW_TFNS),
        "Request Body",
        IB_OP_FLAG_PHASE,
        handle_request_event
    },
    {
        IB_FALSE,
        PHASE_RESPONSE_HEADER,
        IB_STATE_HOOK_TX,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_ALLOW_CHAIN | PHASE_FLAG_ALLOW_TFNS),
        "Response Header",
        IB_OP_FLAG_PHASE,
        handle_response_header_event
    },
    {
        IB_FALSE,
        PHASE_RESPONSE_BODY,
        IB_STATE_HOOK_TX,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_ALLOW_CHAIN | PHASE_FLAG_ALLOW_TFNS),
        "Response Body",
        IB_OP_FLAG_PHASE,
        handle_response_event
    },
    {
        IB_FALSE,
        PHASE_POSTPROCESS,
        IB_STATE_HOOK_TX,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_ALLOW_CHAIN | PHASE_FLAG_ALLOW_TFNS),
        "Post Process",
        IB_OP_FLAG_PHASE,
        handle_postprocess_event
    },

    /* Stream rule phases */
    {
        IB_TRUE,
        PHASE_NONE,
        (ib_state_hook_type_t) -1,
        (PHASE_FLAG_IS_STREAM),
        "Generic 'Stream Inspection' Rule",
        IB_OP_FLAG_STREAM,
        (ib_state_event_type_t) -1
    },
    {
        IB_TRUE,
        PHASE_STR_REQUEST_HEADER,
        IB_STATE_HOOK_TX,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_IS_STREAM),
        "Request Header Stream",
        IB_OP_FLAG_STREAM,
        handle_context_tx_event
    },
    {
        IB_TRUE,
        PHASE_STR_REQUEST_BODY,
        IB_STATE_HOOK_TXDATA,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_IS_STREAM),
        "Request Body Stream",
        IB_OP_FLAG_STREAM,
        request_body_data_event
    },
    {
        IB_TRUE,
        PHASE_STR_RESPONSE_HEADER,
        IB_STATE_HOOK_HEADER,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_IS_STREAM),
        "Response Header Stream",
        IB_OP_FLAG_STREAM,
        response_header_data_event
    },
    {
        IB_TRUE,
        PHASE_STR_RESPONSE_BODY,
        IB_STATE_HOOK_TXDATA,
        (PHASE_FLAG_IS_VALID | PHASE_FLAG_IS_STREAM),
        "Response Body Stream",
        IB_OP_FLAG_STREAM,
        response_body_data_event
    },
    {
        IB_FALSE,
        PHASE_INVALID,
        (ib_state_hook_type_t) -1,
        PHASE_FLAG_NONE,
        "Invalid",
        IB_OP_FLAG_NONE,
        (ib_state_event_type_t) -1
    }
};

/* The rule engine uses recursion to walk through lists and chains.  These
 * define the limits to the depth of those recursions. */
#define MAX_LIST_RECURSION   (5)       /**< Max list recursion limit */
#define MAX_CHAIN_RECURSION  (10)      /**< Max chain recursion limit */

/**
 * Test the validity of a phase number
 * @internal
 *
 * @param phase_num Phase number to check
 *
 * @returns IB_TRUE if @phase_num is valid, IB_FALSE if not.
 */
static inline ib_bool_t is_phase_num_valid(ib_rule_phase_t phase_num)
{
    IB_FTRACE_INIT();
    IB_FTRACE_RET_UINT(
        ( (phase_num >= PHASE_NONE) && (phase_num < IB_RULE_PHASE_COUNT) )
        ? IB_TRUE : IB_FALSE);
}

/**
 * Find a rule's matching phase meta data, matching the only the phase number.
 * @internal
 *
 * @param[in] phase_num Phase number (PHASE_xxx)
 * @param[out] phase_meta Matching rule phase meta-data
 *
 * @returns Status code
 */
static ib_status_t find_phase_meta(ib_rule_phase_t phase_num,
                                   const ib_rule_phase_meta_t **phase_meta)
{
    IB_FTRACE_INIT();
    const ib_rule_phase_meta_t *meta;

    assert (phase_meta != NULL);
    assert (is_phase_num_valid(phase_num) == IB_TRUE);

    /* Loop through all parent rules */
    for (meta = rule_phase_meta;  meta->phase_num != PHASE_INVALID;  ++meta)
    {
        if (meta->phase_num == phase_num) {
            *phase_meta = meta;
            IB_FTRACE_RET_STATUS(IB_OK);
        }
    }
    IB_FTRACE_RET_STATUS(IB_ENOENT);
}

/**
 * Find a rule's matching phase meta data, matching the phase number and
 * the phase's stream type.
 * @internal
 *
 * @param[in] is_stream IB_TRUE if this is a "stream inspection" rule
 * @param[in] phase_num Phase number (PHASE_xxx)
 * @param[out] phase_meta Matching rule phase meta-data
 *
 * @note If @a is_stream is IB_TRI_UNSET, this function will return the
 * first phase meta-data object that matches the phase number.
 *
 * @returns Status code
 */
static ib_status_t find_phase_stream_meta(
    ib_bool_t is_stream,
    ib_rule_phase_t phase_num,
    const ib_rule_phase_meta_t **phase_meta)
{
    IB_FTRACE_INIT();
    const ib_rule_phase_meta_t *meta;

    assert (phase_meta != NULL);
    assert (is_phase_num_valid(phase_num) == IB_TRUE);

    /* Loop through all parent rules */
    for (meta = rule_phase_meta;  meta->phase_num != PHASE_INVALID;  ++meta)
    {
        if ( (meta->phase_num == phase_num) &&
             (is_stream == meta->is_stream) )
        {
            *phase_meta = meta;
            IB_FTRACE_RET_STATUS(IB_OK);
        }
    }
    IB_FTRACE_RET_STATUS(IB_ENOENT);
}

/**
 * Execute a field's transformations.
 * @internal
 *
 * @param[in] ib Engine
 * @param[in] tx Transaction
 * @param[in] target Target field
 * @param[in] value Initial value of the target field
 * @param[out] result Pointer to field in which to store the result
 *
 * @returns Status code
 */
static ib_status_t execute_field_tfns(ib_engine_t *ib,
                                      ib_tx_t *tx,
                                      ib_rule_target_t *target,
                                      ib_field_t *value,
                                      ib_field_t **result)
{
    IB_FTRACE_INIT();
    ib_status_t     rc;
    ib_num_t        n = 0;
    ib_list_node_t *node = NULL;
    ib_field_t     *in_field;
    ib_field_t     *out = NULL;

    assert(ib != NULL);
    assert(tx != NULL);
    assert(target != NULL);
    assert(result != NULL);

    /* No transformations?  Do nothing. */
    if (IB_LIST_ELEMENTS(target->tfn_list) == 0) {
        *result = value;
        ib_log_debug3_tx(tx,
                         "No transformations for field %s", target->field_name);
        IB_FTRACE_RET_STATUS(IB_OK);
    }
    else if (value == NULL) {
        IB_FTRACE_RET_STATUS(IB_OK);
    }

    ib_log_debug3_tx(tx,
                     "Executing %d transformations on field %s",
                     IB_LIST_ELEMENTS(target->tfn_list), target->field_name);

    /*
     * Loop through all of the field operators.
     */
    in_field = value;
    IB_LIST_LOOP(target->tfn_list, node) {
        ib_tfn_t  *tfn = (ib_tfn_t *)node->data;
        ib_flags_t flags = 0;

        /* Run it */
        ++n;
        ib_log_debug3_tx(tx,
                         "Executing field transformation #%d '%s' on '%s'",
                         n, tfn->name, target->field_name);
        ib_log_rule_field(ib, "before tfn", in_field);
        rc = ib_tfn_transform(ib, tx->mp, tfn, in_field, &out, &flags);
        if (rc != IB_OK) {
            ib_log_error_tx(tx,
                            "Error executing field operator #%d field %s: %s",
                            n, target->field_name, ib_status_to_string(rc));
            IB_FTRACE_RET_STATUS(rc);
        }
        ib_log_rule_field(ib, "after tfn", out);

        /* Verify that out isn't NULL */
        if (out == NULL) {
            ib_log_error_tx(tx,
                            "Field operator #%d field %s returned NULL",
                            n, target->field_name);
            IB_FTRACE_RET_STATUS(IB_EINVAL);
        }

        /* The output of the operator is now input for the next field op. */
        in_field = out;
    }

    /* The output of the final operator is the result */
    *result = out;

    /* Done. */
    IB_FTRACE_RET_STATUS(IB_OK);
}

/**
 * Perform a block operation by signaling an error to the server.
 *
 * The server is signaled with
 * ib_server_error_response(ib_server_t *, ib_context_t *, int) using
 * @a tx and @a ib to provide the @c ib_context_t* and @c ib_server_t*,
 * respectively.
 *
 * @param ib IronBee engine containing the server callbacks.
 * @param tx Transaction containing the active context.
 *
 * @returns The result of calling ib_server_error_response().
 */
static ib_status_t report_block_to_server(ib_engine_t *ib, ib_tx_t *tx)
{
    IB_FTRACE_INIT();

    /* Store the final return code here. */
    ib_status_t rc;

    assert(ib);
    assert(ib->server);
    assert(tx);
    assert(tx->ctx);

    ib_log_debug_tx(tx, "Setting HTTP error response: status=%d",
                    tx->block_status);
    rc = ib_server_error_response(ib->server, tx, tx->block_status);
    if (rc == IB_DECLINED) {
        ib_log_notice_tx(tx,
                         "Server not willing to set HTTP error response.");
    }
    else if (rc != IB_OK) {
        ib_log_error_tx(tx,
                        "Server failed to set HTTP error response: %s",
                        ib_status_to_string(rc));
    }

    IB_FTRACE_RET_STATUS(rc);
}

/**
 * Execute a rule on a list of values
 * @internal
 *
 * @param[in] ib Engine
 * @param[in] tx Transaction
 * @param[in] opinst Operator instance
 * @param[in] fname Field name
 * @param[in] value Field value to operate on
 * @param[in] recursion Recursion limit -- won't recurse if recursion is zero
 * @param[out] rule_result Pointer to number in which to store the result
 *
 * @returns Status code
 */
static ib_status_t execute_rule_operator(ib_engine_t *ib,
                                         ib_tx_t *tx,
                                         ib_operator_inst_t *opinst,
                                         const char *fname,
                                         ib_field_t *value,
                                         ib_num_t recursion,
                                         ib_num_t *rule_result)
{
    IB_FTRACE_INIT();

    assert(ib != NULL);
    assert(tx != NULL);
    assert(opinst != NULL);
    assert(fname != NULL);

    ib_status_t rc;

    /* Limit recursion */
    --recursion;
    if (recursion <= 0) {
        ib_log_error_tx(tx, "Rule engine: List recursion limit reached");
        IB_FTRACE_RET_STATUS(IB_EOTHER);
    }

    /* Handle a list by looping through it */
    if ( (value != NULL) && (value->type == IB_FTYPE_LIST) ) {
        // @todo Remove mutable once list is const correct.
        ib_list_t *vlist;
        rc = ib_field_mutable_value(value, ib_ftype_list_mutable_out(&vlist));
        if (rc != IB_OK) {
            IB_FTRACE_RET_STATUS(rc);
        }

        ib_list_node_t *node = NULL;
        ib_num_t n = 0;

        IB_LIST_LOOP(vlist, node) {
            ib_field_t *nvalue = (ib_field_t *)ib_list_node_data(node);
            ++n;

            rc = execute_rule_operator(
                ib, tx, opinst, fname, nvalue, recursion, rule_result);
            if (rc != IB_OK) {
                ib_log_debug_tx(tx,
                             "Error executing %s on list element #%d: %s",
                             opinst->op->name, n, ib_status_to_string(rc));
            }
        }
        ib_log_debug3_tx(tx, "Operator %s, target %s (list %zd) => %d",
                     opinst->op->name, fname, vlist->nelts, *rule_result);
    }
    else {
        /* Execute the operator */
        ib_num_t result;
        rc = ib_operator_execute(ib, tx, opinst, value, &result);
        if (rc != IB_OK) {
            ib_log_debug_tx(tx,
                         "Operator %s returned an error for field %s: %s",
                         opinst->op->name, fname, ib_status_to_string(rc));
            IB_FTRACE_RET_STATUS(rc);
        }

        /* Store the result */
        if (result != 0) {
            *rule_result = result;
        }
    }

    IB_FTRACE_RET_STATUS(IB_OK);
}

/**
 * Execute all operators on a single target.
 * @param[in] ib The IronBee engine.
 * @param[in] tx The transaction. Passed to execute_rule_operator.
 * @param[in] target Assigned to the target_result.
 * @param[in] opinst The operator instance.
 * @param[in] operand The field to operate on.
 * @param[out] rule_result The return code from the operator execution.
 * @param[out] target_results The results of the execution.
 * @returns IB_OK if the execution completed without error.
 *          If execute_field_tfns or execute_rule_operator fails
 *          then the return code from the failing call is returned 
 *          immediately.
 */
static ib_status_t execute_phase_rule_targets_operators(
    ib_engine_t *ib,
    ib_tx_t *tx,
    ib_rule_target_t *target,
    ib_operator_inst_t  *opinst,
    ib_field_t *operand,
    ib_num_t *rule_result,
    ib_list_t *target_results)
{
    IB_FTRACE_INIT();

    ib_status_t rc;
    ib_num_t result;
    ib_field_t *tfnvalue = NULL;  /* Value after transformations */

    const char *fname = target->field_name;

    /* Execute the field operators */
    rc = execute_field_tfns(ib, tx, target, operand, &tfnvalue);
    if (rc != IB_OK) {
        ib_log_error_tx(tx,
                        "Error executing transformation for %s on %s: %s",
                        opinst->op->name, fname, ib_status_to_string(rc));
        IB_FTRACE_RET_STATUS(rc);
    }

    /* Execute the rule operator */
    rc = execute_rule_operator(ib,
                               tx,
                               opinst,
                               fname,
                               tfnvalue,
                               MAX_LIST_RECURSION,
                               &result);
    if (rc != IB_OK) {
        ib_log_error_tx(tx,
                        "Operator %s returned an error for field %s: %s",
                        opinst->op->name, fname, ib_status_to_string(rc));
        IB_FTRACE_RET_STATUS(rc);
    }
    ib_log_debug3_tx(tx, "Operator %s, field %s => %d",
                     opinst->op->name, fname, result);

    /* Store the result */
    if (result != 0) {
        *rule_result = result;
    }

    /* Create a rule target execution result object */
    if (target_results != NULL) {
        ib_rule_target_result_t *target_result =
            (ib_rule_target_result_t *)
            ib_mpool_alloc(tx->mp, sizeof(*target_result));
        if (target_result != NULL) {
            target_result->target = target;
            target_result->original = operand;
            target_result->transformed = tfnvalue;
            target_result->result = result;
            ib_list_push(target_results, target_result);
        }
    }

    IB_FTRACE_RET_STATUS(IB_OK);
}

/**
 * Execute a single rule's operator on all target fields.
 * @internal
 *
 * @param[in] ib Engine
 * @param[in] rule Rule to execute
 * @param[in,out] tx Transaction
 * @param[out] rule_result Pointer to number in which to store the result
 *
 * @returns Status code
 */
static ib_status_t execute_phase_rule_targets(ib_engine_t *ib,
                                              ib_rule_t *rule,
                                              ib_tx_t *tx,
                                              ib_num_t *rule_result,
                                              ib_list_t *target_results)
{
    IB_FTRACE_INIT();
    assert(ib);
    assert(rule);
    assert(tx);
    assert(rule_result);
    ib_list_node_t      *node = NULL;
    ib_operator_inst_t  *opinst = rule->opinst;

    /* Log what we're going to do */
    ib_log_debug3_tx(tx, "Executing rule %s", rule->meta.id);

    /* Special case: External rules */
    if ( (rule->flags & IB_RULE_FLAG_EXTERNAL) != 0) {
        ib_status_t rc;

        /* Execute the operator */
        ib_log_debug3_tx(tx, "Executing external rule");
        rc = ib_operator_execute(ib, tx, opinst, NULL, rule_result);
        if (rc != IB_OK) {
            ib_log_error_tx(tx,
                         "External operator %s returned an error: %s",
                         opinst->op->name, ib_status_to_string(rc));
        }
        IB_FTRACE_RET_STATUS(rc);
    }

    /*
     * Loop through all of the fields.
     *
     * @todo The current behavior is to keep running even after an operator
     * returns an error.  This needs further discussion to determine what the
     * correct behavior should be.
     */
    IB_LIST_LOOP(rule->target_fields, node) {
        ib_rule_target_t *target = (ib_rule_target_t *)node->data;
        assert(target != NULL);
        const char       *fname = target->field_name;
        assert(fname != NULL);
        ib_field_t       *value = NULL;     /* Value from the DPI */
        ib_status_t       rc = IB_OK;

        /* Get the field value */
        rc = ib_data_get(tx->dpi, fname, &value);
        if (rc == IB_ENOENT) {
            ib_bool_t allow_null =
                ib_flags_all(opinst->op->flags, IB_OP_FLAG_ALLOW_NULL);
            if (allow_null == IB_FALSE) {
                continue;
            }
        }
        else if (rc != IB_OK) {
            ib_log_error_tx(tx, "Error getting field %s: %s",
                            fname, ib_status_to_string(rc));
            continue;
        }

        if ( value->type == IB_FTYPE_LIST ) {
            ib_list_t *value_list;
            ib_list_node_t *value_node;
            rc = ib_field_value(value, (void *)&value_list);

            if (rc!=IB_OK) {
                ib_log_error_tx(tx, "Error getting field value %s: %s",
                                value->name, rc);
                continue;
            }

            /* Run operations on each list element. */
            IB_LIST_LOOP(value_list, value_node) {
                const ib_status_t tmp_rc =
                    execute_phase_rule_targets_operators(
                        ib,
                        tx,
                        target,
                        opinst,
                        (ib_field_t *) value_node->data,
                        rule_result,
                        target_results);

                /* Capture failure to report back to the caller. */
                if (tmp_rc!=IB_OK) {
                    rc = tmp_rc;
                }
            }
        }
        else {
            rc = execute_phase_rule_targets_operators(ib,
                                                      tx,
                                                      target,
                                                      opinst,
                                                      value,
                                                      rule_result,
                                                      target_results);
        }
{'fraction_non_alphanumeric': 0.10080205021119074, 'fraction_numerical': 0.0018508851027478526, 'mean_word_length': 1.8972226041523443, 'pattern_counts': {'":': 0, '<': 69, '<?xml version=': 0, '>': 151, 'https://': 0, 'lorem ipsum': 0, 'www.': 2, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 80, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6677393', 'n_tokens_mistral': 13490, 'n_tokens_neox': 12966, 'n_words': 3102}