mirror of
https://github.com/openappsec/openappsec.git
synced 2025-09-29 19:24:26 +03:00
My 11th 2023 update
This commit is contained in:
@@ -1,3 +1,4 @@
|
||||
add_subdirectory(http_transaction_data)
|
||||
add_subdirectory(ip_utilities)
|
||||
add_subdirectory(keywords)
|
||||
add_subdirectory(pm)
|
||||
|
2
components/utils/keywords/CMakeLists.txt
Normal file
2
components/utils/keywords/CMakeLists.txt
Normal file
@@ -0,0 +1,2 @@
|
||||
add_library(keywords keywords_rule.cc single_keyword.cc data_keyword.cc pcre_keyword.cc length_keyword.cc byte_extract_keyword.cc compare_keyword.cc jump_keyword.cc stateop_keyword.cc no_match_keyword.cc)
|
||||
add_subdirectory(keywords_ut)
|
319
components/utils/keywords/byte_extract_keyword.cc
Normal file
319
components/utils/keywords/byte_extract_keyword.cc
Normal file
@@ -0,0 +1,319 @@
|
||||
#include "single_keyword.h"
|
||||
#include "output.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <map>
|
||||
#include <strings.h>
|
||||
#include "limits.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_KEYWORD);
|
||||
|
||||
class ByteExtractKeyword : public SingleKeyword
|
||||
{
|
||||
public:
|
||||
explicit ByteExtractKeyword(const vector<KeywordAttr> &attr, VariablesMapping &vars);
|
||||
MatchStatus isMatch(const I_KeywordRuntimeState *prev) const override;
|
||||
|
||||
private:
|
||||
enum class BaseId
|
||||
{
|
||||
BIN,
|
||||
HEX = 16,
|
||||
DEC = 10,
|
||||
OCT = 8
|
||||
};
|
||||
|
||||
void
|
||||
setOffset(const KeywordAttr &attr, const VariablesMapping &vars)
|
||||
{
|
||||
offset.setAttr(attr, vars, "byte_extract");
|
||||
}
|
||||
|
||||
void
|
||||
setRelative(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
is_relative.setAttr(attr, "byte_extract");
|
||||
}
|
||||
|
||||
void
|
||||
setLittleEndian(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
is_little_end.setAttr(attr, "byte_extract");
|
||||
}
|
||||
|
||||
void
|
||||
setDataType(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
if (data_type != BaseId::BIN) {
|
||||
throw KeywordError("Double definition of the data type in the 'byte_extract' keyword");
|
||||
}
|
||||
|
||||
auto &vec = attr.getParams();
|
||||
if (vec.size() != 2) throw KeywordError("Malformed data type in the 'byte_extract' keyword");
|
||||
|
||||
if (vec[1] == "hex") {
|
||||
data_type = BaseId::HEX;
|
||||
} else if (vec[1] == "dec") {
|
||||
data_type = BaseId::DEC;
|
||||
} else if (vec[1] == "oct") {
|
||||
data_type = BaseId::OCT;
|
||||
} else {
|
||||
throw KeywordError("Unknown data type in the 'byte_extract' keyword: " + vec[1]);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
setContext(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
ctx.setAttr(attr, "byte_extract");
|
||||
}
|
||||
|
||||
void
|
||||
setAlign(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
if (align != 1) throw KeywordError("Double definition of the 'align' in the 'byte_extract' keyword");
|
||||
auto &vec = attr.getParams();
|
||||
if (vec.size() != 2) throw KeywordError("Malformed 'align' in the 'byte_extract' keyword");
|
||||
|
||||
if (vec[1] == "2") {
|
||||
align = 2;
|
||||
} else if (vec[1] == "4") {
|
||||
align = 4;
|
||||
} else {
|
||||
throw KeywordError("Unknown 'align' in the 'byte_extract' keyword: " + vec[1]);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
isConstant() const
|
||||
{
|
||||
return !is_relative && bytes.isConstant() && offset.isConstant();
|
||||
}
|
||||
|
||||
pair<uint, uint> getStartOffsetAndLength(uint buf_size, const I_KeywordRuntimeState *prev) const;
|
||||
uint applyAlignment(uint value) const;
|
||||
Maybe<uint> readValue(uint start, uint length, const Buffer &buf) const;
|
||||
Maybe<uint> readStringValue(uint start, uint length, const Buffer &buf) const;
|
||||
|
||||
NumericAttr bytes;
|
||||
uint var_id;
|
||||
NumericAttr offset;
|
||||
BoolAttr is_relative;
|
||||
BoolAttr is_little_end;
|
||||
BaseId data_type = BaseId::BIN;
|
||||
int align = 1;
|
||||
CtxAttr ctx;
|
||||
|
||||
static const map<string, void(ByteExtractKeyword::*)(const KeywordAttr &, const VariablesMapping &)> setops;
|
||||
};
|
||||
|
||||
const map<string, void(ByteExtractKeyword::*)(const KeywordAttr &, const VariablesMapping &)>
|
||||
ByteExtractKeyword::setops = {
|
||||
{ "offset", &ByteExtractKeyword::setOffset },
|
||||
{ "relative", &ByteExtractKeyword::setRelative },
|
||||
{ "little_endian", &ByteExtractKeyword::setLittleEndian },
|
||||
{ "string", &ByteExtractKeyword::setDataType },
|
||||
{ "part", &ByteExtractKeyword::setContext },
|
||||
{ "align", &ByteExtractKeyword::setAlign }
|
||||
};
|
||||
|
||||
ByteExtractKeyword::ByteExtractKeyword(const vector<KeywordAttr> &attrs, VariablesMapping &vars)
|
||||
:
|
||||
offset()
|
||||
{
|
||||
//two requied attributes - number of bytes and var name
|
||||
if (attrs.size() < 2) throw KeywordError("Invalid number of attributes in the 'byte_extract' keyword");
|
||||
|
||||
//parisng first attribute (Required) - number of bytes
|
||||
auto &bytes_param = attrs[0].getParams();
|
||||
if (bytes_param.size() != 1) {
|
||||
throw KeywordError("More than one element in the 'bytes' in the 'byte_extract' keyword");
|
||||
}
|
||||
bytes.setAttr("bytes", bytes_param[0], vars, "byte_extract", static_cast<uint>(BaseId::DEC), true);
|
||||
if (bytes.isConstant() && bytes.evalAttr(nullptr) == 0) {
|
||||
throw KeywordError("Number of bytes is zero in the 'byte_extract' keyword");
|
||||
}
|
||||
|
||||
//parisng second attribute (Required) - variable name
|
||||
auto &var_name_param = attrs[1].getParams();
|
||||
if (var_name_param.size() != 1) {
|
||||
throw KeywordError("More than one element in the variable name in the 'byte_extract' keyword");
|
||||
}
|
||||
const string &var_name = var_name_param[0];
|
||||
auto curr = setops.find(var_name);
|
||||
if (curr != setops.end()) {
|
||||
throw KeywordError("'" + var_name + "' cannot be the variable name in the 'byte_extract' keyword");
|
||||
}
|
||||
if (isdigit(var_name[0]) || var_name[0] == '-') {
|
||||
throw KeywordError("Malformed variable name in the 'byte_extract' keyword");
|
||||
}
|
||||
|
||||
var_id = vars.addNewVariable(var_name);
|
||||
|
||||
//parsing the other optional attributes
|
||||
for (uint i = 2; i < attrs.size(); i++) {
|
||||
auto curr = setops.find(attrs[i].getAttrName());
|
||||
if (curr == setops.end()) {
|
||||
throw KeywordError("Unknown attribute '" + attrs[i].getAttrName() + "' in the 'byte_extract' keyword");
|
||||
}
|
||||
auto set_func = curr->second;
|
||||
(this->*set_func)(attrs[i], vars);
|
||||
}
|
||||
|
||||
if (data_type == BaseId::BIN) {
|
||||
if (!bytes.isConstant()) {
|
||||
throw KeywordError("Data type is binary, but the 'bytes' is not constant in the 'byte_extract' keyword");
|
||||
}
|
||||
int num_bytes = bytes.evalAttr(nullptr);
|
||||
if (num_bytes != 1 && num_bytes != 2 && num_bytes != 4) {
|
||||
throw KeywordError("Data type is binary, but the 'bytes' is not constant in the 'byte_extract' keyword");
|
||||
}
|
||||
if (is_little_end && num_bytes == 1) {
|
||||
throw KeywordError(
|
||||
"Little endian is set, "
|
||||
"but the number of bytes is invalid in the 'byte_extract' keyword"
|
||||
);
|
||||
}
|
||||
if (align != 1) {
|
||||
throw KeywordError("The 'align' is set and data type is binary in the 'byte_extract' keyword");
|
||||
}
|
||||
} else {
|
||||
if (is_little_end) {
|
||||
throw KeywordError("Little endian is set, but the data type is not binary in the 'byte_extract' keyword");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static uint
|
||||
addOffset(uint offset, int add)
|
||||
{
|
||||
if (add < 0 && offset < static_cast<uint>(-add)) {
|
||||
dbgWarning(D_KEYWORD)
|
||||
<< "The offset was set to 0 "
|
||||
<< "due to an attempt to jump before the beginning of the buffer in the 'jump' keyword";
|
||||
return 0;
|
||||
}
|
||||
return offset + add;
|
||||
}
|
||||
|
||||
pair<uint, uint>
|
||||
ByteExtractKeyword::getStartOffsetAndLength(uint buf_size, const I_KeywordRuntimeState *prev) const
|
||||
{
|
||||
uint relative_offset = is_relative ? prev->getOffset(ctx) : 0;
|
||||
int offset_attr = offset.evalAttr(prev);
|
||||
uint start_offset = addOffset(relative_offset, offset_attr);
|
||||
|
||||
if (start_offset >= buf_size) return make_pair(0, 0);
|
||||
|
||||
uint length = buf_size - start_offset;
|
||||
return make_pair(start_offset, length);
|
||||
}
|
||||
|
||||
Maybe<uint>
|
||||
ByteExtractKeyword::readValue(uint start, uint length, const Buffer &buf) const
|
||||
{
|
||||
if (data_type != BaseId::BIN) return readStringValue(start, length, buf);
|
||||
|
||||
uint res = 0;
|
||||
for (uint i = 0; i < length; i++) {
|
||||
uint ch = buf[start + i];
|
||||
if (is_little_end) {
|
||||
ch <<= 8*i;
|
||||
res += ch;
|
||||
} else {
|
||||
res <<= 8;
|
||||
res += ch;
|
||||
}
|
||||
}
|
||||
return res;
|
||||
}
|
||||
|
||||
Maybe<uint>
|
||||
ByteExtractKeyword::readStringValue(uint start, uint length, const Buffer &buf) const
|
||||
{
|
||||
const u_char *data = buf.getPtr(start, length).unpack(); // start and length were checked outside of the function
|
||||
string val_str(reinterpret_cast<const char *>(data), length);
|
||||
|
||||
uint base = static_cast<uint>(data_type);
|
||||
try {
|
||||
size_t idx;
|
||||
auto res = stoul(val_str, &idx, base);
|
||||
if (idx != val_str.length()) throw invalid_argument("");
|
||||
if (res > INT_MAX) {
|
||||
throw out_of_range("");
|
||||
}
|
||||
return res;
|
||||
}
|
||||
catch (invalid_argument &) {
|
||||
return genError("Unable to convert the \"" + val_str + "\" to a number due to an invalid argument");
|
||||
}
|
||||
catch (out_of_range &) {
|
||||
return genError(
|
||||
"Unable to convert the \""
|
||||
+ val_str
|
||||
+ "\" to a number. The maximum is: "
|
||||
+ to_string(INT_MAX)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
uint
|
||||
ByteExtractKeyword::applyAlignment(uint value) const
|
||||
{
|
||||
int reminder = value % align;
|
||||
if (reminder != 0) {
|
||||
value += (align - reminder);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
MatchStatus
|
||||
ByteExtractKeyword::isMatch(const I_KeywordRuntimeState *prev) const
|
||||
{
|
||||
auto part = Singleton::Consume<I_Environment>::by<KeywordComp>()->get<Buffer>(static_cast<string>(ctx));
|
||||
|
||||
if (!part.ok()) return MatchStatus::NoMatchFinal;
|
||||
|
||||
uint bytes_to_extr = bytes.evalAttr(prev);
|
||||
if (bytes_to_extr == 0) {
|
||||
dbgDebug(D_KEYWORD) << "Number of bytes is zero in the 'byte_extract' keyword";
|
||||
return MatchStatus::NoMatch; //the case of constant number of bytes was checked during compilation
|
||||
}
|
||||
|
||||
uint start_offset, length_to_end;
|
||||
tie(start_offset, length_to_end) = getStartOffsetAndLength((*part).size(), prev);
|
||||
|
||||
uint offset_after_extracted_bytes = applyAlignment(start_offset + bytes_to_extr);
|
||||
|
||||
if (length_to_end == 0 || offset_after_extracted_bytes > (*part).size()) {
|
||||
dbgDebug(D_KEYWORD)
|
||||
<< "Offset after the number of bytes to extract exceeds the buffer size in the 'byte_extract' keyword";
|
||||
return isConstant() ? MatchStatus::NoMatchFinal : MatchStatus::NoMatch;
|
||||
}
|
||||
|
||||
auto res = readValue(start_offset, bytes_to_extr, *part);
|
||||
if (!res.ok()) {
|
||||
dbgDebug(D_KEYWORD) << "Trying to store an invalid value in the 'byte_extract' keyword: " + res.getErr();
|
||||
return isConstant() ? MatchStatus::NoMatchFinal : MatchStatus::NoMatch;
|
||||
}
|
||||
|
||||
uint extracted_val = res.unpack();
|
||||
|
||||
if (extracted_val > INT_MAX) {
|
||||
dbgDebug(D_KEYWORD) << "Value exceeds the maximum in the 'byte_extract' keyword";
|
||||
return isConstant() ? MatchStatus::NoMatchFinal : MatchStatus::NoMatch;
|
||||
}
|
||||
|
||||
//add variable and move offset after number of extracted bytes
|
||||
VariableRuntimeState new_var(prev, var_id, extracted_val);
|
||||
OffsetRuntimeState new_offset(&new_var, ctx, offset_after_extracted_bytes);
|
||||
return runNext(&new_offset);
|
||||
}
|
||||
|
||||
unique_ptr<SingleKeyword>
|
||||
genByteExtractKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
|
||||
{
|
||||
return make_unique<ByteExtractKeyword>(attr, known_vars);
|
||||
}
|
70
components/utils/keywords/compare_keyword.cc
Normal file
70
components/utils/keywords/compare_keyword.cc
Normal file
@@ -0,0 +1,70 @@
|
||||
#include "single_keyword.h"
|
||||
#include "output.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <map>
|
||||
#include <strings.h>
|
||||
#include "limits.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_KEYWORD);
|
||||
|
||||
class CompareKeyword : public SingleKeyword
|
||||
{
|
||||
public:
|
||||
explicit CompareKeyword(const vector<KeywordAttr> &attr, const VariablesMapping &vars);
|
||||
MatchStatus isMatch(const I_KeywordRuntimeState* prev) const override;
|
||||
|
||||
private:
|
||||
bool
|
||||
isConstant() const
|
||||
{
|
||||
return first_val.isConstant() && second_val.isConstant();
|
||||
}
|
||||
|
||||
NumericAttr first_val;
|
||||
NumericAttr second_val;
|
||||
ComparisonAttr comparison;
|
||||
};
|
||||
|
||||
CompareKeyword::CompareKeyword(const vector<KeywordAttr> &attrs, const VariablesMapping &vars)
|
||||
{
|
||||
if (attrs.size() != 3) throw KeywordError("Invalid number of attributes in the 'compare' keyword");
|
||||
|
||||
auto &first_val_param = attrs[0].getParams();
|
||||
if (first_val_param.size() != 1) {
|
||||
throw KeywordError("More than one element in the first value in the 'compare' keyword");
|
||||
}
|
||||
first_val.setAttr("first_val", first_val_param[0], vars, "compare");
|
||||
|
||||
auto &comparison_param = attrs[1].getParams();
|
||||
if (comparison_param.size() != 1) {
|
||||
throw KeywordError("More than one element in the comparison operator in the 'compare' keyword");
|
||||
}
|
||||
comparison.setAttr(comparison_param[0], "compare");
|
||||
|
||||
auto &second_val_param = attrs[2].getParams();
|
||||
if (second_val_param.size() != 1) {
|
||||
throw KeywordError("More than one element in the second value in the 'compare' keyword");
|
||||
}
|
||||
second_val.setAttr("second_val", second_val_param[0], vars, "compare");
|
||||
}
|
||||
|
||||
MatchStatus
|
||||
CompareKeyword::isMatch(const I_KeywordRuntimeState *prev) const
|
||||
{
|
||||
int keyword_first_val = first_val.evalAttr(prev);
|
||||
int keyword_second_val = second_val.evalAttr(prev);
|
||||
|
||||
if (comparison(keyword_first_val, keyword_second_val)) return runNext(prev);
|
||||
|
||||
// If there was no matches and the keyword is effected by other keywords, then we know that the rule won't match
|
||||
return isConstant() ? MatchStatus::NoMatchFinal : MatchStatus::NoMatch;
|
||||
}
|
||||
|
||||
unique_ptr<SingleKeyword>
|
||||
genCompareKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
|
||||
{
|
||||
return make_unique<CompareKeyword>(attr, known_vars);
|
||||
}
|
409
components/utils/keywords/data_keyword.cc
Normal file
409
components/utils/keywords/data_keyword.cc
Normal file
@@ -0,0 +1,409 @@
|
||||
#include "single_keyword.h"
|
||||
#include "output.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <map>
|
||||
#include <strings.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_KEYWORD);
|
||||
|
||||
class DataKeyword : public SingleKeyword
|
||||
{
|
||||
public:
|
||||
explicit DataKeyword(const vector<KeywordAttr> &attr, const VariablesMapping &vars);
|
||||
MatchStatus isMatch(const I_KeywordRuntimeState* prev) const override;
|
||||
|
||||
private:
|
||||
void
|
||||
setOffset(const KeywordAttr &attr, const VariablesMapping &vars)
|
||||
{
|
||||
offset.setAttr(attr, vars, "data");
|
||||
}
|
||||
|
||||
void
|
||||
setDepth(const KeywordAttr &attr, const VariablesMapping &vars)
|
||||
{
|
||||
depth.setAttr(attr, vars, "data");
|
||||
}
|
||||
|
||||
void
|
||||
setCaret(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
is_caret.setAttr(attr, "data");
|
||||
}
|
||||
|
||||
void
|
||||
setRelative(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
is_relative.setAttr(attr, "data");
|
||||
}
|
||||
|
||||
void
|
||||
setCaseInsensitive(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
is_case_insensitive.setAttr(attr, "data");
|
||||
}
|
||||
|
||||
void
|
||||
setContext(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
ctx.setAttr(attr, "data");
|
||||
}
|
||||
|
||||
void parseString(const string &str);
|
||||
|
||||
void
|
||||
addChar(char ch)
|
||||
{
|
||||
pattern.push_back(static_cast<unsigned char>(ch));
|
||||
}
|
||||
|
||||
void calcTables();
|
||||
|
||||
pair<uint, uint> getStartAndEndOffsets(uint buf_size, const I_KeywordRuntimeState *prev) const;
|
||||
uint bytesMatched(const Buffer&, uint) const;
|
||||
|
||||
uint
|
||||
moveOnMatch() const
|
||||
{
|
||||
return pattern.size();
|
||||
}
|
||||
|
||||
uint
|
||||
moveOnNoMatch(uint offset_from_end, unsigned char first_unmatched_byte) const
|
||||
{
|
||||
dbgAssert(shift.size() > offset_from_end) << "Shift table of the 'data' keyword is shorter than the offset";
|
||||
|
||||
uint skip_size;
|
||||
if (skip[first_unmatched_byte]>offset_from_end) {
|
||||
skip_size = skip[first_unmatched_byte]-offset_from_end;
|
||||
} else {
|
||||
skip_size = 1;
|
||||
}
|
||||
|
||||
return max(shift[offset_from_end], skip_size);
|
||||
}
|
||||
|
||||
bool
|
||||
isConstant() const
|
||||
{
|
||||
return !is_relative && offset.isConstant() && depth.isConstant();
|
||||
}
|
||||
|
||||
vector<unsigned char> pattern;
|
||||
uint skip[256];
|
||||
vector<uint> shift;
|
||||
|
||||
NumericAttr offset;
|
||||
NumericAttr depth;
|
||||
BoolAttr is_negative;
|
||||
BoolAttr is_caret;
|
||||
BoolAttr is_relative;
|
||||
BoolAttr is_case_insensitive;
|
||||
CtxAttr ctx;
|
||||
|
||||
static const map<string, void(DataKeyword::*)(const KeywordAttr &, const VariablesMapping &)> setops;
|
||||
};
|
||||
|
||||
const map<string, void(DataKeyword::*)(const KeywordAttr &, const VariablesMapping &)> DataKeyword::setops = {
|
||||
{ "relative", &DataKeyword::setRelative },
|
||||
{ "offset", &DataKeyword::setOffset },
|
||||
{ "depth", &DataKeyword::setDepth },
|
||||
{ "caret", &DataKeyword::setCaret },
|
||||
{ "nocase", &DataKeyword::setCaseInsensitive },
|
||||
{ "part", &DataKeyword::setContext }
|
||||
};
|
||||
|
||||
DataKeyword::DataKeyword(const vector<KeywordAttr> &attrs, const VariablesMapping &vars)
|
||||
:
|
||||
offset(),
|
||||
depth()
|
||||
{
|
||||
auto &pattern_param = attrs[0].getParams();
|
||||
|
||||
if (pattern_param.size() != 1) throw KeywordError("More than one element in the 'data' keyword pattern");
|
||||
const string &string_pattern = pattern_param[0];
|
||||
|
||||
if (string_pattern.length() == 0) throw KeywordError("No input for the 'data' keyword");
|
||||
|
||||
uint start = 0;
|
||||
if (string_pattern[0] == '!') {
|
||||
is_negative.setAttr("data", "negative");
|
||||
start++;
|
||||
}
|
||||
if (string_pattern[start] != '"') throw KeywordError("The data pattern does not begin with '\"'");
|
||||
|
||||
uint end = string_pattern.length()-1;
|
||||
if (string_pattern[end] != '"') throw KeywordError("The data pattern does not end with '\"'");
|
||||
|
||||
if (start+1 >= end) throw KeywordError("No input for the 'data' keyword");
|
||||
|
||||
parseString(string_pattern.substr(start+1, end-(start+1)));
|
||||
|
||||
for (uint i = 1; i<attrs.size(); i++) {
|
||||
auto curr = setops.find(attrs[i].getAttrName());
|
||||
if (curr == setops.end()) {
|
||||
throw KeywordError("Unknown attribute '" + attrs[i].getAttrName() + "' in the 'data' keyword");
|
||||
}
|
||||
auto set_func = curr->second;
|
||||
(this->*set_func)(attrs[i], vars);
|
||||
}
|
||||
|
||||
calcTables();
|
||||
}
|
||||
|
||||
void
|
||||
DataKeyword::calcTables()
|
||||
{
|
||||
if (is_case_insensitive) {
|
||||
for (auto &ch : pattern) {
|
||||
if (isupper(ch)) {
|
||||
ch = tolower(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize skip table - when we meet a charecter that isn't in the pattern, we skip the whole pattern
|
||||
for (auto &ch_skip : skip) {
|
||||
ch_skip = pattern.size();
|
||||
}
|
||||
|
||||
// Go over the charecters in the pattern.
|
||||
// We can skip from a charecter to the end of the pattern.
|
||||
// If a charecter appear more than once, the latest occurence take precedent.
|
||||
for (uint index = 0; index<pattern.size(); index++) {
|
||||
unsigned char ch = pattern[index];
|
||||
|
||||
uint dist_to_end = pattern.size()-(index+1);
|
||||
if (is_case_insensitive && islower(ch)) {
|
||||
skip[toupper(ch)] = dist_to_end;
|
||||
}
|
||||
skip[ch] = dist_to_end;
|
||||
}
|
||||
|
||||
// Initialize the shift table.
|
||||
shift.resize(pattern.size(), 0);
|
||||
|
||||
uint end_offset = pattern.size()-1;
|
||||
// Go over all the suffixes (from the empty to the pattern-1)
|
||||
for (size_t suffix_len = 0; suffix_len<pattern.size(); suffix_len++) {
|
||||
// Find the smallest shift, so when shifting the suffix left:
|
||||
// 1. All chars overlapping between pattern and shifted suffix match.
|
||||
// 2. If the character before the shifted suffix overlaps the pattern, it doesn't match.
|
||||
// pattern = "hellololo", suff=2 (must match "[^o]lo"), shift=4 ("hel(lo)lolo")
|
||||
// pattern = "olo" suff=2 (must match "[^o]lo"), shift=2 ("(.o)lo")
|
||||
// characters before the patterns are considered wild.
|
||||
for (uint shift_offset = 1; shift_offset<=pattern.size(); shift_offset++) {
|
||||
// Verify that in the current offset matches the suffix
|
||||
size_t num_of_overlapping_char;
|
||||
unsigned char *suffix_start_ptr;
|
||||
unsigned char *shifted_suffix_start_ptr;
|
||||
if (shift_offset+suffix_len <= pattern.size()) {
|
||||
// Shifted suffix doesn't exceed the pattern. Compare the whole suffix.
|
||||
num_of_overlapping_char = suffix_len;
|
||||
suffix_start_ptr = pattern.data() + pattern.size() - suffix_len;
|
||||
shifted_suffix_start_ptr = suffix_start_ptr - shift_offset;
|
||||
} else {
|
||||
// Shifted suffix exceeds the pattern. Compare only the overlaping charecters.
|
||||
num_of_overlapping_char = pattern.size() - shift_offset;
|
||||
suffix_start_ptr = pattern.data() + shift_offset;
|
||||
shifted_suffix_start_ptr = pattern.data();
|
||||
}
|
||||
|
||||
if (bcmp(suffix_start_ptr, shifted_suffix_start_ptr, num_of_overlapping_char) != 0) continue;
|
||||
|
||||
// Verify that what comes after the suffix doesn't match
|
||||
if (shift_offset+suffix_len < pattern.size()) {
|
||||
if (pattern[end_offset-suffix_len] == pattern[end_offset-(shift_offset+suffix_len)]) continue;
|
||||
}
|
||||
|
||||
// Set the currect shift offset
|
||||
shift[suffix_len] = shift_offset;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
DataKeyword::parseString(const string &str)
|
||||
{
|
||||
string hex;
|
||||
bool hex_mode = false;
|
||||
bool after_bslash = false;
|
||||
|
||||
for (auto ch : str) {
|
||||
if (after_bslash) {
|
||||
if (!isprint(ch)) {
|
||||
throw KeywordError(
|
||||
"Illegal backslash character '" +
|
||||
dumpHexChar(ch) +
|
||||
"' in the pattern in the 'data' keyword"
|
||||
);
|
||||
}
|
||||
addChar(ch);
|
||||
after_bslash = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (ch) {
|
||||
case '|': {
|
||||
if (!hex_mode) {
|
||||
hex = "";
|
||||
hex_mode = true;
|
||||
} else {
|
||||
if (hex.size()>0) throw KeywordError("Stoping in the middle of hex string in the 'data' keyword");
|
||||
hex_mode = false;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case '\\': {
|
||||
if (hex_mode) throw KeywordError("Backslash in hex string in the 'data' keyword");
|
||||
after_bslash = true;
|
||||
break;
|
||||
}
|
||||
case '"': {
|
||||
throw KeywordError("Unescaped double quotation mark in the 'data' keyword");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (hex_mode) {
|
||||
if (!isxdigit(ch)) {
|
||||
if (ch != ' ') {
|
||||
throw KeywordError(
|
||||
"Illegal character '" +
|
||||
dumpHexChar(ch) +
|
||||
"' in the hex string in the 'data' keyword"
|
||||
);
|
||||
}
|
||||
if (hex.size()>0) {
|
||||
throw KeywordError("Space separating nibbles in the hex string in the 'data' keyword");
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
hex += ch;
|
||||
|
||||
if (hex.size()>=2) {
|
||||
addChar(stol(hex, nullptr, 16));
|
||||
hex = "";
|
||||
}
|
||||
} else {
|
||||
if (!isprint(ch)) {
|
||||
throw KeywordError(
|
||||
"Illegal character '" +
|
||||
dumpHexChar(ch) +
|
||||
"' in the pattern in the 'data' keyword"
|
||||
);
|
||||
}
|
||||
addChar(ch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ( hex_mode || after_bslash ) {
|
||||
throw KeywordError("The 'data' keyword's pattern has ended in the middle of the parsing");
|
||||
}
|
||||
}
|
||||
|
||||
static uint
|
||||
addOffset(uint offset, int add)
|
||||
{
|
||||
if (add<0 && offset<static_cast<uint>(-add)) return 0;
|
||||
return offset + add;
|
||||
}
|
||||
|
||||
pair<uint, uint>
|
||||
DataKeyword::getStartAndEndOffsets(uint buf_size, const I_KeywordRuntimeState *prev) const
|
||||
{
|
||||
uint relative_offset = is_relative?prev->getOffset(ctx):0;
|
||||
int offset_attr = offset.evalAttr(prev);
|
||||
uint start_offset = addOffset(relative_offset, offset_attr);
|
||||
|
||||
if (depth.isSet()) {
|
||||
uint depth_size = addOffset(start_offset, depth.evalAttr(prev));
|
||||
buf_size = std::min(buf_size, depth_size);
|
||||
}
|
||||
if (is_caret) {
|
||||
buf_size = std::min(buf_size, start_offset+static_cast<uint>(pattern.size()));
|
||||
}
|
||||
|
||||
return make_pair(start_offset, buf_size);
|
||||
}
|
||||
|
||||
uint
|
||||
DataKeyword::bytesMatched(const Buffer &buf, uint offset) const
|
||||
{
|
||||
if (is_case_insensitive) {
|
||||
for (uint i = 0; i<pattern.size(); i++) {
|
||||
if (pattern[pattern.size()-(i+1)] != tolower(buf[offset-(i+1)])) return i;
|
||||
}
|
||||
} else {
|
||||
for (uint i = 0 ; i < pattern.size() ; i++ ) {
|
||||
if (pattern[pattern.size()-(i+1)] != buf[offset-(i+1)] ) return i;
|
||||
}
|
||||
}
|
||||
return pattern.size();
|
||||
}
|
||||
|
||||
MatchStatus
|
||||
DataKeyword::isMatch(const I_KeywordRuntimeState *prev) const
|
||||
{
|
||||
dbgAssert(pattern.size()>0) << "Trying to run on an uninitialized keyword data";
|
||||
|
||||
dbgDebug(D_KEYWORD) << "Searching for " << dumpHex(pattern);
|
||||
|
||||
auto part = Singleton::Consume<I_Environment>::by<KeywordComp>()->get<Buffer>(static_cast<string>(ctx));
|
||||
if (!part.ok()) {
|
||||
if (is_negative) return runNext(prev);
|
||||
return MatchStatus::NoMatchFinal;
|
||||
}
|
||||
|
||||
const auto &buf = part.unpack();
|
||||
|
||||
dbgTrace(D_KEYWORD) << "Full buffer: " << dumpHex(buf);
|
||||
|
||||
uint offset, max_offset;
|
||||
|
||||
tie(offset, max_offset) = getStartAndEndOffsets(buf.size(), prev);
|
||||
offset += pattern.size();
|
||||
|
||||
bool match_found = false;
|
||||
while (offset<=max_offset) {
|
||||
// Short circuit for the common, simple case where the last byte doesn't match
|
||||
if (skip[buf[offset-1]]) {
|
||||
offset += skip[buf[offset - 1]];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Full search Boyer-Moore
|
||||
uint match_size = bytesMatched(buf, offset);
|
||||
if (match_size == pattern.size()) {
|
||||
if (is_negative) {
|
||||
return isConstant()?MatchStatus::NoMatchFinal:MatchStatus::NoMatch;
|
||||
}
|
||||
match_found = true;
|
||||
OffsetRuntimeState new_offset(prev, ctx, offset);
|
||||
auto next_keyword_result = runNext(&new_offset);
|
||||
if (next_keyword_result!=MatchStatus::NoMatch) return next_keyword_result;
|
||||
offset += moveOnMatch();
|
||||
} else {
|
||||
offset += moveOnNoMatch(match_size, buf[offset-(match_size+1)]);
|
||||
}
|
||||
}
|
||||
|
||||
// No matchs is a success for negative keywords
|
||||
if (is_negative && !match_found) return runNext(prev);
|
||||
|
||||
// If there were no matchs and the keyword is an effected by other keywords, then we know that the rule won't match
|
||||
if (isConstant() && !match_found) return MatchStatus::NoMatchFinal;
|
||||
|
||||
return MatchStatus::NoMatch;
|
||||
}
|
||||
|
||||
unique_ptr<SingleKeyword>
|
||||
genDataKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
|
||||
{
|
||||
return make_unique<DataKeyword>(attr, known_vars);
|
||||
}
|
174
components/utils/keywords/jump_keyword.cc
Normal file
174
components/utils/keywords/jump_keyword.cc
Normal file
@@ -0,0 +1,174 @@
|
||||
#include "single_keyword.h"
|
||||
#include "output.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <map>
|
||||
#include <strings.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_KEYWORD);
|
||||
|
||||
class jumpKeyword : public SingleKeyword
|
||||
{
|
||||
public:
|
||||
explicit jumpKeyword(const vector<KeywordAttr> &attr, const VariablesMapping &vars);
|
||||
MatchStatus isMatch(const I_KeywordRuntimeState* prev) const override;
|
||||
|
||||
private:
|
||||
enum class JumpFromId
|
||||
{
|
||||
RELATIVE,
|
||||
FROM_BEGINNING,
|
||||
FROM_END
|
||||
};
|
||||
|
||||
void
|
||||
setContext(const KeywordAttr &attr)
|
||||
{
|
||||
ctx.setAttr(attr, "byte_extract");
|
||||
}
|
||||
|
||||
void
|
||||
setAlign(const KeywordAttr &attr)
|
||||
{
|
||||
if (align != 1) throw KeywordError("Double definition of the 'align' in the 'jump' keyword");
|
||||
auto &vec = attr.getParams();
|
||||
if (vec.size() != 2) throw KeywordError("Malformed 'align' in the 'jump' keyword");
|
||||
|
||||
if (vec[1] == "2") {
|
||||
align = 2;
|
||||
} else if (vec[1] == "4") {
|
||||
align = 4;
|
||||
} else {
|
||||
throw KeywordError("Unknown 'align' in the 'jump' keyword: " + vec[1]);
|
||||
}
|
||||
}
|
||||
|
||||
bool
|
||||
isConstant() const
|
||||
{
|
||||
return jumping_from != JumpFromId::RELATIVE && jumping_val.isConstant();
|
||||
}
|
||||
|
||||
JumpFromId jumping_from;
|
||||
NumericAttr jumping_val;
|
||||
int align = 1;
|
||||
CtxAttr ctx;
|
||||
|
||||
static const map<string, void(jumpKeyword::*)(const KeywordAttr &)> setops;
|
||||
uint getStartOffset(uint buf_size, const I_KeywordRuntimeState *prev) const;
|
||||
uint applyAlignment(uint value) const;
|
||||
uint addOffset(uint offset, int add) const;
|
||||
};
|
||||
|
||||
const map<string, void(jumpKeyword::*)(const KeywordAttr &)> jumpKeyword::setops = {
|
||||
{ "part", &jumpKeyword::setContext },
|
||||
{ "align", &jumpKeyword::setAlign }
|
||||
};
|
||||
|
||||
jumpKeyword::jumpKeyword(const vector<KeywordAttr> &attrs, const VariablesMapping &vars)
|
||||
{
|
||||
|
||||
//two requied attributes - jumping value and jumping from
|
||||
if (attrs.size() < 2) throw KeywordError("Invalid number of attributes in the 'jump' keyword");
|
||||
|
||||
//parisng first attribute (Required) - jumping value
|
||||
auto &jumping_val_param = attrs[0].getParams();
|
||||
if (jumping_val_param.size() != 1) {
|
||||
throw KeywordError("More than one element in the jumping value in the 'jump' keyword");
|
||||
}
|
||||
jumping_val.setAttr("jumping value", jumping_val_param[0], vars, "jump");
|
||||
|
||||
//parisng second attribute (Required) - jumping from
|
||||
auto &jumping_from_param = attrs[1].getParams();
|
||||
if (jumping_from_param.size() != 1) {
|
||||
throw KeywordError("More than one element in the jumping 'from' parameter in the 'jump' keyword");
|
||||
}
|
||||
|
||||
if (jumping_from_param[0] == "from_beginning") {
|
||||
jumping_from = JumpFromId::FROM_BEGINNING;
|
||||
} else if (jumping_from_param[0] == "from_end") {
|
||||
jumping_from = JumpFromId::FROM_END;
|
||||
} else if (jumping_from_param[0] == "relative") {
|
||||
jumping_from = JumpFromId::RELATIVE;
|
||||
} else {
|
||||
throw KeywordError("Unknown jumping 'from' parameter in the 'jump' keyword: " + jumping_from_param[0]);
|
||||
}
|
||||
|
||||
//parisng optional attributes
|
||||
for (uint i = 2; i < attrs.size(); i++) {
|
||||
auto curr = setops.find(attrs[i].getAttrName());
|
||||
if (curr == setops.end()) {
|
||||
throw KeywordError("Unknown attribute " + attrs[i].getAttrName() + " in the 'jump' keyword");
|
||||
}
|
||||
auto set_func = curr->second;
|
||||
(this->*set_func)(attrs[i]);
|
||||
}
|
||||
}
|
||||
|
||||
uint
|
||||
jumpKeyword::applyAlignment(uint value) const
|
||||
{
|
||||
int reminder = value % align;
|
||||
if (reminder != 0) {
|
||||
value += (align - reminder);
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
uint
|
||||
jumpKeyword::addOffset(uint offset, int add) const
|
||||
{
|
||||
if (add < 0 && offset < static_cast<uint>(-add)) {
|
||||
dbgWarning(D_KEYWORD)
|
||||
<< "The offset was set to 0 "
|
||||
<< "due to an attempt to jump before the beginning of the buffer in the 'jump' keyword";
|
||||
return 0;
|
||||
}
|
||||
return applyAlignment(offset + add);
|
||||
}
|
||||
|
||||
uint
|
||||
jumpKeyword::getStartOffset(uint buf_size, const I_KeywordRuntimeState *prev) const
|
||||
{
|
||||
switch (jumping_from) {
|
||||
case JumpFromId::FROM_BEGINNING: {
|
||||
return 0;
|
||||
}
|
||||
case JumpFromId::FROM_END: {
|
||||
return buf_size;
|
||||
}
|
||||
case JumpFromId::RELATIVE: {
|
||||
return prev->getOffset(ctx);
|
||||
}
|
||||
}
|
||||
dbgAssert(false) << "Invalid jumping 'from' parameter";
|
||||
return 0;
|
||||
}
|
||||
|
||||
MatchStatus
|
||||
jumpKeyword::isMatch(const I_KeywordRuntimeState *prev) const
|
||||
{
|
||||
auto part = Singleton::Consume<I_Environment>::by<KeywordComp>()->get<Buffer>(static_cast<string>(ctx));
|
||||
|
||||
if (!part.ok()) return MatchStatus::NoMatchFinal;
|
||||
|
||||
uint start_offset = getStartOffset((*part).size(), prev);
|
||||
|
||||
uint offset_to_jump = addOffset(start_offset, jumping_val.evalAttr(prev));
|
||||
|
||||
if (offset_to_jump > (*part).size()) {
|
||||
dbgDebug(D_KEYWORD) << "New offset exceeds the buffer size in the 'jump' keyword";
|
||||
return isConstant() ? MatchStatus::NoMatchFinal : MatchStatus::NoMatch;
|
||||
}
|
||||
|
||||
OffsetRuntimeState new_offset(prev, ctx, offset_to_jump);
|
||||
return runNext(&new_offset);
|
||||
}
|
||||
|
||||
unique_ptr<SingleKeyword>
|
||||
genJumpKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
|
||||
{
|
||||
return make_unique<jumpKeyword>(attr, known_vars);
|
||||
}
|
171
components/utils/keywords/keywords_rule.cc
Normal file
171
components/utils/keywords/keywords_rule.cc
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "keyword_comp.h"
|
||||
|
||||
#include <vector>
|
||||
#include "sentinel_runtime_state.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
static const string whitespaces = " \t";
|
||||
|
||||
static string
|
||||
getSubStrNoPadding(const string &str, uint start, uint end)
|
||||
{
|
||||
auto r_start = str.find_first_not_of(whitespaces, start);
|
||||
auto r_end = str.find_last_not_of(whitespaces, end-1);
|
||||
|
||||
if (r_end==string::npos || r_start==string::npos || r_start>r_end) {
|
||||
throw KeywordError("Found an empty section in the '"+ str + "'");
|
||||
}
|
||||
|
||||
return str.substr(r_start, r_end-r_start+1);
|
||||
}
|
||||
|
||||
static vector<string>
|
||||
split(const string &str, const string &delim, uint start = 0)
|
||||
{
|
||||
vector<string> res;
|
||||
uint part_start = start;
|
||||
bool escape = false;
|
||||
bool in_string = false;
|
||||
|
||||
for (uint index = start; index<str.size(); index++) {
|
||||
if (escape) {
|
||||
escape = false;
|
||||
continue;
|
||||
}
|
||||
|
||||
switch (str[index]) {
|
||||
case '\\': {
|
||||
escape = true;
|
||||
break;
|
||||
}
|
||||
case '"': {
|
||||
in_string = !in_string;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
if (!in_string && delim.find(str[index])!=string::npos) {
|
||||
res.push_back(getSubStrNoPadding(str, part_start, index));
|
||||
part_start = index+1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (escape||in_string) throw KeywordError("Split has ended in the middle of the parsing");
|
||||
|
||||
if (str.find_first_not_of(whitespaces, part_start)!=string::npos) {
|
||||
res.push_back(getSubStrNoPadding(str, part_start, str.size()));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
KeywordAttr::KeywordAttr(const string &str) : params(split(str, whitespaces))
|
||||
{
|
||||
}
|
||||
|
||||
KeywordParsed::KeywordParsed(string const &keyword) {
|
||||
auto index = keyword.find_first_of(':');
|
||||
if (index!=string::npos) {
|
||||
for (auto &str : split(keyword, ",", index+1)) {
|
||||
attr.push_back(KeywordAttr(str));
|
||||
}
|
||||
} else {
|
||||
index = keyword.size();
|
||||
}
|
||||
|
||||
name = getSubStrNoPadding(keyword, 0, index);
|
||||
if (name.find_first_of(whitespaces)!=string::npos) {
|
||||
throw KeywordError("'" + name + "' - cannot be a keyword name");
|
||||
}
|
||||
}
|
||||
|
||||
uint
|
||||
SentinelRuntimeState::getOffset(const std::string &) const
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
|
||||
// LCOV_EXCL_START Reason: this function is tested in one_element_list_negative_test but marked as not covered.
|
||||
uint
|
||||
SentinelRuntimeState::getVariable(uint var_id) const
|
||||
{
|
||||
dbgAssert(false) << "Could not find the variable ID: " << var_id;
|
||||
return 0;
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
class SentinelKeyword : public SingleKeyword
|
||||
{
|
||||
public:
|
||||
MatchStatus
|
||||
isMatch() const
|
||||
{
|
||||
SentinelRuntimeState curr_state;
|
||||
return runNext(&curr_state);
|
||||
}
|
||||
|
||||
private:
|
||||
// LCOV_EXCL_START Reason: Unreachable function.
|
||||
MatchStatus
|
||||
isMatch(const I_KeywordRuntimeState *state) const override
|
||||
{
|
||||
return runNext(state);
|
||||
}
|
||||
// LCOV_EXCL_STOP
|
||||
};
|
||||
|
||||
class KeywordComp::Impl : Singleton::Provide<I_KeywordsRule>::From<KeywordComp>
|
||||
{
|
||||
public:
|
||||
Maybe<shared_ptr<VirtualRule>>
|
||||
genRule(const string &rule)
|
||||
{
|
||||
shared_ptr<VirtualRule> res;
|
||||
try {
|
||||
res = KeywordsRuleImpl::genRule(rule);
|
||||
} catch (const KeywordError &e) {
|
||||
return genError(e.getErr());;
|
||||
}
|
||||
return move(res);
|
||||
}
|
||||
|
||||
private:
|
||||
class KeywordsRuleImpl : public VirtualRule
|
||||
{
|
||||
public:
|
||||
bool isMatch() const override { return start.isMatch() == MatchStatus::Match; }
|
||||
|
||||
static unique_ptr<KeywordsRuleImpl>
|
||||
genRule(const string &rule)
|
||||
{
|
||||
auto res = make_unique<KeywordsRuleImpl>();
|
||||
|
||||
auto pos = rule.find_last_not_of(whitespaces);
|
||||
if (pos==string::npos) {
|
||||
// Empty rule
|
||||
return res;
|
||||
}
|
||||
|
||||
if (rule[pos]!=';') throw KeywordError(rule + " - end of text pass rule");
|
||||
|
||||
VariablesMapping known_vars;
|
||||
|
||||
auto key_vec = split(rule, ";");
|
||||
for (auto &keyword : key_vec) {
|
||||
res->start.appendKeyword(getKeywordByName(keyword, known_vars));
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
private:
|
||||
SentinelKeyword start;
|
||||
};
|
||||
};
|
||||
|
||||
KeywordComp::KeywordComp() : Component("KeywordComp"), pimpl(make_unique<KeywordComp::Impl>()) {}
|
||||
|
||||
KeywordComp::~KeywordComp() {}
|
||||
|
||||
string I_KeywordsRule::keywords_tag = "keywords_rule_tag";
|
5
components/utils/keywords/keywords_ut/CMakeLists.txt
Normal file
5
components/utils/keywords/keywords_ut/CMakeLists.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
add_unit_test(
|
||||
keywords_ut
|
||||
"keywords_ut.cc;single_keyword_ut.cc"
|
||||
"keywords;pcre2-8;buffers;singleton;table;event_is;metric;-lboost_regex"
|
||||
)
|
996
components/utils/keywords/keywords_ut/keywords_ut.cc
Normal file
996
components/utils/keywords/keywords_ut/keywords_ut.cc
Normal file
@@ -0,0 +1,996 @@
|
||||
#include "keyword_comp.h"
|
||||
#include "environment.h"
|
||||
#include "mock/mock_table.h"
|
||||
#include "cptest.h"
|
||||
#include "mock/mock_time_get.h"
|
||||
#include "mock/mock_mainloop.h"
|
||||
#include "config.h"
|
||||
#include "config_component.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
class KeywordsRuleTest : public ::testing::Test
|
||||
{
|
||||
public:
|
||||
void
|
||||
appendBuffer(const string &id, const string &str)
|
||||
{
|
||||
buffers[id] += Buffer(str);
|
||||
}
|
||||
|
||||
string
|
||||
ruleCompileFail(const string &_rule)
|
||||
{
|
||||
auto rule = Singleton::Consume<I_KeywordsRule>::from(comp)->genRule(_rule);
|
||||
EXPECT_FALSE(rule.ok()) << "Compile supposed to fail";
|
||||
return rule.getErr();
|
||||
}
|
||||
|
||||
bool
|
||||
ruleRun(const string &_rule, const string &default_ctx = "default")
|
||||
{
|
||||
auto rule = Singleton::Consume<I_KeywordsRule>::from(comp)->genRule(_rule);
|
||||
EXPECT_TRUE(rule.ok()) << "Compile not supposed to fail: " << rule.getErr();
|
||||
ScopedContext ctx;
|
||||
ctx.registerValue(I_KeywordsRule::getKeywordsRuleTag(), default_ctx);
|
||||
for (auto &value : buffers) {
|
||||
ctx.registerValue(value.first, value.second);
|
||||
}
|
||||
return (*rule)->isMatch();
|
||||
}
|
||||
|
||||
private:
|
||||
KeywordComp comp;
|
||||
::testing::NiceMock<MockMainLoop> mock_mainloop;
|
||||
::testing::NiceMock<MockTimeGet> mock_timer;
|
||||
Environment env;
|
||||
map<string, Buffer> buffers;
|
||||
};
|
||||
|
||||
TEST_F(KeywordsRuleTest, data_basic_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "123456789");
|
||||
|
||||
EXPECT_TRUE(ruleRun("data: \"234\" , part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("data: \"234\";", "HTTP_RESPONSE_BODY"));
|
||||
EXPECT_FALSE(ruleRun("data: \"75\", part HTTP_RESPONSE_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, data_relative_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(ruleRun("data: \"567\", part HTTP_RESPONSE_BODY; data: \"234\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(
|
||||
ruleRun("data: \"567\", part HTTP_RESPONSE_BODY; data: \"234\", part HTTP_RESPONSE_BODY, relative;")
|
||||
);
|
||||
EXPECT_TRUE(ruleRun("data: \"234\", part HTTP_RESPONSE_BODY; data: \"567\", part HTTP_RESPONSE_BODY, relative;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, data_depth_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(ruleRun("data: \"345\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("data: \"345\", depth 5, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("data: \"345\", depth 4, part HTTP_RESPONSE_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, data_nocase_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "abcdefg");
|
||||
|
||||
EXPECT_TRUE(ruleRun("data: \"cde\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("data: \"CDE\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("data: \"CDE\", nocase, part HTTP_RESPONSE_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, data_offset_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(ruleRun("data: \"345\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("data: \"345\", offset 2, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("data: \"345\", offset 3, part HTTP_RESPONSE_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, data_caret_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(ruleRun("data: \"345\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("data: \"345\", part HTTP_RESPONSE_BODY, caret;"));
|
||||
EXPECT_TRUE(ruleRun("data: \"345\", caret, part HTTP_RESPONSE_BODY, offset 2;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, data_negative_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_FALSE(ruleRun("data: !\"345\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("data: !\"365\", part HTTP_RESPONSE_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, data_part_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
appendBuffer("HTTP_REQUEST_BODY", "abcdefg");
|
||||
|
||||
EXPECT_TRUE(ruleRun("data: \"345\", part HTTP_RESPONSE_BODY; data: \"cde\", part HTTP_REQUEST_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("data: \"345\", part HTTP_RESPONSE_BODY; data: \"cde\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("data: \"345\", part HTTP_REQUEST_BODY; data: \"cde\", part HTTP_REQUEST_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, pcre_basic_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(ruleRun("pcre: \"/5.7/\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("pcre: \"/5..7/\", part HTTP_RESPONSE_BODY;"));
|
||||
}
|
||||
|
||||
|
||||
TEST_F(KeywordsRuleTest, pcre_relative_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(ruleRun("pcre: \"/5.7/\", part HTTP_RESPONSE_BODY; pcre: \"/2.4/\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("pcre: \"/5.7/\", part HTTP_RESPONSE_BODY; pcre: \"/2.4/R\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(
|
||||
ruleRun("pcre: \"/5.7/\", part HTTP_RESPONSE_BODY; pcre: \"/2.4/\", relative, part HTTP_RESPONSE_BODY;")
|
||||
);
|
||||
EXPECT_TRUE(ruleRun("pcre: \"/2.4/\", part HTTP_RESPONSE_BODY; pcre: \"/5.7/R\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(
|
||||
ruleRun("pcre: \"/2.4/\", part HTTP_RESPONSE_BODY; pcre: \"/5.7/\", relative, part HTTP_RESPONSE_BODY;")
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, pcre_depth_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(ruleRun("pcre: \"/3.5/\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("pcre: \"/3.5/\", depth 5, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("pcre: \"/3.5/\", depth 4, part HTTP_RESPONSE_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, pcre_nocase_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "abcdefg");
|
||||
|
||||
EXPECT_TRUE(ruleRun("pcre: \"/c.e/\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("pcre: \"/C.E/\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("pcre: \"/C.E/i\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("pcre: \"/C.E/\", nocase, part HTTP_RESPONSE_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, pcre_offset_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(ruleRun("pcre: \"/3.5/\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("pcre: \"/3.5/\", offset 2, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("pcre: \"/3.5/\", offset 300, part HTTP_RESPONSE_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, pcre_part_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
appendBuffer("HTTP_REQUEST_BODY", "abcdefg");
|
||||
|
||||
EXPECT_TRUE(ruleRun("pcre: \"/3.5/\", part HTTP_RESPONSE_BODY; pcre: \"/c.e/\", part HTTP_REQUEST_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("pcre: \"/3.5/\", part HTTP_RESPONSE_BODY; pcre: \"/c.e/\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("pcre: \"/3.5/\", part HTTP_REQUEST_BODY; pcre: \"/c.e/\", part HTTP_REQUEST_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, pcre_negative_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_FALSE(ruleRun("pcre: !\"/3.5/\", part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("pcre: !\"/3..5/\", part HTTP_RESPONSE_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, compare_comparison_test) {
|
||||
EXPECT_TRUE(ruleRun("compare: 0, =, 0;"));
|
||||
EXPECT_TRUE(ruleRun("compare: -1, =, -1;"));
|
||||
EXPECT_FALSE(ruleRun("compare: 0, =, 1;"));
|
||||
EXPECT_FALSE(ruleRun("compare: -1, =, -2;"));
|
||||
EXPECT_FALSE(ruleRun("compare: 1, =, -1;"));
|
||||
EXPECT_FALSE(ruleRun("compare: -1, =, 1;"));
|
||||
EXPECT_TRUE(ruleRun("compare: 2, !=, 3;"));
|
||||
EXPECT_TRUE(ruleRun("compare: 2, <=, 3;"));
|
||||
EXPECT_TRUE(ruleRun("compare: 2, <, 3;"));
|
||||
EXPECT_FALSE(ruleRun("compare: 2, >, 3;"));
|
||||
EXPECT_FALSE(ruleRun("compare: 2, >=, 3;"));
|
||||
EXPECT_TRUE(ruleRun("compare: -2, !=, -3;"));
|
||||
EXPECT_TRUE(ruleRun("compare: -2, >=, -3;"));
|
||||
EXPECT_TRUE(ruleRun("compare: -2, >, -3;"));
|
||||
EXPECT_FALSE(ruleRun("compare: -2, <, -3;"));
|
||||
EXPECT_FALSE(ruleRun("compare: -2, <=, -3;"));
|
||||
EXPECT_TRUE(ruleRun("compare: -2, !=, 3;"));
|
||||
EXPECT_TRUE(ruleRun("compare: -2, <=, 3;"));
|
||||
EXPECT_TRUE(ruleRun("compare: -2, <, 3;"));
|
||||
EXPECT_FALSE(ruleRun("compare: -2, >, 3;"));
|
||||
EXPECT_FALSE(ruleRun("compare: -2, >=, 3;"));
|
||||
EXPECT_TRUE(ruleRun("compare: 2, !=, -3;"));
|
||||
EXPECT_TRUE(ruleRun("compare: 2, >=, -3;"));
|
||||
EXPECT_TRUE(ruleRun("compare: 2, >, -3;"));
|
||||
EXPECT_FALSE(ruleRun("compare: 2, <, -3;"));
|
||||
EXPECT_FALSE(ruleRun("compare: 2, <=, -3;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, compare_compile_fail_test) {
|
||||
EXPECT_EQ(ruleCompileFail("compare: 0;"), "Invalid number of attributes in the 'compare' keyword");
|
||||
EXPECT_EQ(ruleCompileFail("compare: 0, =;"), "Invalid number of attributes in the 'compare' keyword");
|
||||
EXPECT_EQ(ruleCompileFail("compare: 0, =, 0, 0;"), "Invalid number of attributes in the 'compare' keyword");
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("compare: 0 1, =, 0;"),
|
||||
"More than one element in the first value in the 'compare' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("compare: 0, = =, 0;"),
|
||||
"More than one element in the comparison operator in the 'compare' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("compare: 0, =, 0 1;"),
|
||||
"More than one element in the second value in the 'compare' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("compare: 0, ==, 0;"),
|
||||
"Unknown comparison operator in the 'compare' keyword: Could not find the operator: =="
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, length_basic_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "123456789");
|
||||
appendBuffer("HTTP_REQUEST_BODY", "");
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"length: length_var, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 9;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"length: length_var, part HTTP_REQUEST_BODY;"
|
||||
"compare: length_var, =, 0;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"length: length_var, part HTTP_REQUEST_BODY;"
|
||||
"compare: length_var, =, 1;"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, length_part_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(ruleRun("length: length_var, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("length: length_var, part HTTP_REQUEST_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, length_relative_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "123456789");
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"data: \"234\", part HTTP_RESPONSE_BODY;"
|
||||
"length: relative_length_var, part HTTP_RESPONSE_BODY, relative;"
|
||||
"compare: relative_length_var, =, 5;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"data: \"234\", part HTTP_RESPONSE_BODY;"
|
||||
"length: relative_length_var, part HTTP_RESPONSE_BODY;"
|
||||
"compare: relative_length_var, =, 5;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"data: \"89\", part HTTP_RESPONSE_BODY;"
|
||||
"length: zero_length_var, part HTTP_RESPONSE_BODY, relative;"
|
||||
"compare: zero_length_var, =, 0;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"data: \"89\", part HTTP_RESPONSE_BODY;"
|
||||
"length: zero_length_var, part HTTP_RESPONSE_BODY;"
|
||||
"compare: zero_length_var, =, 0;"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, length_compare_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "123");
|
||||
EXPECT_FALSE(ruleRun("length: 6, min, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("length: 6, exact, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("length: 6, max, part HTTP_RESPONSE_BODY;"));
|
||||
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "456");
|
||||
EXPECT_TRUE(ruleRun("length: 6, min, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("length: 6, exact, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(ruleRun("length: 6, max, part HTTP_RESPONSE_BODY;"));
|
||||
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "789");
|
||||
EXPECT_TRUE(ruleRun("length: 6, min, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("length: 6, exact, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("length: 6, max, part HTTP_RESPONSE_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, length_compile_fail_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "123456789");
|
||||
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("length: two_elem 2, part HTTP_RESPONSE_BODY;"),
|
||||
"More than one element in the variable name in the 'length' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("length: relative, part HTTP_RESPONSE_BODY;"),
|
||||
"The 'relative' cannot be the variable name in the 'length' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("length: part, part HTTP_RESPONSE_BODY;"),
|
||||
"The 'part' cannot be the variable name in the 'length' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("length: -minus, part HTTP_RESPONSE_BODY;"),
|
||||
"Malformed variable name in the 'length' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("length: 1digit, part HTTP_RESPONSE_BODY;"),
|
||||
"Malformed variable name in the 'length' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("length: bad_attr, partt HTTP_RESPONSE_BODY;"),
|
||||
"Unknown attribute 'partt' in the 'length' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("length:;"),
|
||||
"Invalid number of attributes in the 'length' keyword"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, byte_extract_dec_string_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234");
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, dec_var, string dec, part HTTP_RESPONSE_BODY;"
|
||||
"data: \"234\", offset dec_var, part HTTP_RESPONSE_BODY;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, dec_var, string dec, part HTTP_RESPONSE_BODY;"
|
||||
"data: \"123\", offset dec_var, part HTTP_RESPONSE_BODY;"
|
||||
)
|
||||
);
|
||||
|
||||
appendBuffer("HTTP_REQUEST_BODY", "A");
|
||||
|
||||
EXPECT_FALSE(ruleRun("byte_extract: 1, bad_dec_var, string dec, part HTTP_REQUEST_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, byte_extract_hex_string_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "A123");
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, hex_var, string hex, part HTTP_RESPONSE_BODY;"
|
||||
"compare: hex_var, =, 10;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 2, hex_var, string hex, part HTTP_RESPONSE_BODY;"
|
||||
"compare: hex_var, =, 161;"
|
||||
)
|
||||
);
|
||||
|
||||
appendBuffer("HTTP_REQUEST_BODY", "10G");
|
||||
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"byte_extract: 2, hex_var, string hex, part HTTP_REQUEST_BODY;"
|
||||
"compare: hex_var, =, 10;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(ruleRun("byte_extract: 3, bad_hex_var, string oct, part HTTP_REQUEST_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, byte_extract_oct_string_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "13ABC");
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 2, oct_var, string oct, part HTTP_RESPONSE_BODY;"
|
||||
"compare: oct_var, =, 11;"
|
||||
)
|
||||
);
|
||||
|
||||
appendBuffer("HTTP_REQUEST_BODY", "118");
|
||||
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"byte_extract: 2, oct_var, string oct, part HTTP_REQUEST_BODY;"
|
||||
"compare: oct_var, =, 13;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(ruleRun("byte_extract: 3, bad_oct_var, string oct, part HTTP_REQUEST_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, byte_extract_binary_data_test) {
|
||||
string one_byte_binary_data = {10};
|
||||
appendBuffer("HTTP_RESPONSE_BODY", one_byte_binary_data);
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, binary_data_var, part HTTP_RESPONSE_BODY;"
|
||||
"compare: binary_data_var, =, 10;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, dec_data_var, offset 2, string dec, part HTTP_RESPONSE_BODY;"
|
||||
"compare: dec_data_var, =, 10;"
|
||||
)
|
||||
);
|
||||
|
||||
string two_bytes_binary_data = {1, 0, 0};
|
||||
appendBuffer("HTTP_REQUEST_BODY", two_bytes_binary_data);
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 2, binary_data_var, part HTTP_REQUEST_BODY;"
|
||||
"compare: binary_data_var , =, 256;"
|
||||
)
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 3, not1/2/4, part HTTP_REQUEST_BODY;"),
|
||||
"Data type is binary, but the 'bytes' is not constant in the 'byte_extract' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail(
|
||||
"byte_extract: 1, no_constant, part HTTP_REQUEST_BODY;"
|
||||
"byte_extract: no_constant, var, part HTTP_REQUEST_BODY;"
|
||||
),
|
||||
"Data type is binary, but the 'bytes' is not constant in the 'byte_extract' keyword"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, byte_extract_bad_num_of_bytes_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "0");
|
||||
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 0, zero_bytes_var, string dec, part HTTP_RESPONSE_BODY;"),
|
||||
"Number of bytes is zero in the 'byte_extract' keyword"
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, one_byte_var, string dec, part HTTP_RESPONSE_BODY;"
|
||||
"byte_extract: one_byte_var, zero_bytes_var, string dec, part HTTP_RESPONSE_BODY;"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, byte_extract_part_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "123");
|
||||
EXPECT_TRUE(ruleRun("byte_extract: 1, part_var, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("byte_extract: 1, part_var, part HTTP_REQUEST_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, byte_extract_offset_test) {
|
||||
appendBuffer("HTTP_REQUEST_BODY", "1A23456789hello");
|
||||
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, hex_var, offset 1, string hex, part HTTP_REQUEST_BODY; "
|
||||
"data: \"9hell\", offset hex_var, part HTTP_REQUEST_BODY;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, hex_var, offset 1, string hex, part HTTP_REQUEST_BODY;"
|
||||
"data: \"hell\", offset hex_var, part HTTP_REQUEST_BODY;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, dec_var, offset -1, string dec, part HTTP_REQUEST_BODY;"
|
||||
"data: \"1A2\", offset dec_var, part HTTP_REQUEST_BODY;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, dec_var, offset -1, string dec, part HTTP_REQUEST_BODY;"
|
||||
"data: \"A2\", offset dec_var, part HTTP_REQUEST_BODY;"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, byte_extract_relative_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "123456789");
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"data: \"12\", part HTTP_RESPONSE_BODY;"
|
||||
"byte_extract: 1, relative_var, relative, string dec, part HTTP_RESPONSE_BODY;"
|
||||
"compare: relative_var, =, 3;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"data: \"12\", part HTTP_RESPONSE_BODY;"
|
||||
"byte_extract: 1, non_relative_var, string dec, part HTTP_RESPONSE_BODY;"
|
||||
"compare: non_relative_var, =, 3;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"data: \"89\", part HTTP_RESPONSE_BODY;"
|
||||
"byte_extract: 1, relative_var, string dec, relative, part HTTP_RESPONSE_BODY;"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, byte_extract_endianness_test) {
|
||||
string little_end_test_str = {8, 0, 0};
|
||||
appendBuffer("HTTP_RESPONSE_BODY", little_end_test_str);
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 2, lit_end_var, little_endian, part HTTP_RESPONSE_BODY;"
|
||||
"compare: lit_end_var, =, 8;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"byte_extract: 2, big_end_var, part HTTP_RESPONSE_BODY;"
|
||||
"compare: big_end_var, =, 8;"
|
||||
)
|
||||
);
|
||||
|
||||
little_end_test_str[1] = 0;
|
||||
little_end_test_str[2] = 1;
|
||||
appendBuffer("HTTP_REQUEST_BODY", little_end_test_str);
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 2, lit_end_with_offset_var,"
|
||||
"offset 1, little_endian, part HTTP_REQUEST_BODY;"
|
||||
"compare: lit_end_with_offset_var, =, 256;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"byte_extract: 2, big_end_with_offset_var, offset 1, part HTTP_REQUEST_BODY;"
|
||||
"compare: big_end_with_offset_var, =, 256;"
|
||||
)
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, var, little_endian, part HTTP_REQUEST_BODY;"),
|
||||
"Little endian is set, but the number of bytes is invalid in the 'byte_extract' keyword"
|
||||
);
|
||||
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 2, no_binary, little_endian, string dec, part HTTP_REQUEST_BODY;"),
|
||||
"Little endian is set, but the data type is not binary in the 'byte_extract' keyword"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, byte_extract_align_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234");
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, align2_var, align 2, string dec, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 2;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, align4_var, align 4, string dec, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 0;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, align2_var, offset 3, align 2, string dec, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 0;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, align4_var, offset 3, align 4, string dec, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 0;"
|
||||
)
|
||||
);
|
||||
|
||||
appendBuffer("HTTP_REQUEST_BODY", "123");
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: 1, align2_var, offset 1, align 2, string dec, part HTTP_REQUEST_BODY;"
|
||||
"length: length_var, relative, part HTTP_REQUEST_BODY;"
|
||||
"compare: length_var, =, 1;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(ruleRun("byte_extract: 1, align4_var, align 4, string dec, part HTTP_REQUEST_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("byte_extract: 1, align2_var, offset 2, align 2, string dec, part HTTP_REQUEST_BODY;"));
|
||||
|
||||
string binary_data_str = { 1 };
|
||||
appendBuffer("HTTP_REQUEST_BODY", binary_data_str);
|
||||
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, align_binary_var, align 2, part HTTP_REQUEST_BODY;"),
|
||||
"The 'align' is set and data type is binary in the 'byte_extract' keyword"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, byte_extract_overflow_test) {
|
||||
string overflow_dec_data_str = to_string((uint)INT_MAX + 1);
|
||||
appendBuffer("HTTP_RESPONSE_BODY", overflow_dec_data_str);
|
||||
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"byte_extract: " + to_string(overflow_dec_data_str.length()) + ","
|
||||
"overflow_var, string dec, part HTTP_RESPONSE_BODY;"
|
||||
)
|
||||
);
|
||||
|
||||
string max_value_dec_data_str = to_string(INT_MAX);
|
||||
appendBuffer("HTTP_REQUEST_BODY", max_value_dec_data_str);
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"byte_extract: " + to_string(max_value_dec_data_str.length()) + ","
|
||||
"max_var, string dec, part HTTP_REQUEST_BODY;"
|
||||
"compare: max_var, =, " + max_value_dec_data_str + ";"
|
||||
)
|
||||
);
|
||||
|
||||
string overflow_binary_data_str = { 0x7f, 0x7f, 0x7f, 0x7f, 0 };
|
||||
appendBuffer("HTTP_REQUEST_HEADERS", overflow_binary_data_str);
|
||||
|
||||
EXPECT_FALSE(ruleRun("byte_extract: 5 ,overflow_num_var, string dec, part HTTP_REQUEST_HEADERS;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, byte_extract_compile_fail_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1 2, dec_var, string dec, part HTTP_RESPONSE_BODY;"),
|
||||
"More than one element in the 'bytes' in the 'byte_extract' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, dec_var 1, string dec, part HTTP_RESPONSE_BODY;"),
|
||||
"More than one element in the variable name in the 'byte_extract' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, align, string dec, part HTTP_RESPONSE_BODY;"),
|
||||
"'align' cannot be the variable name in the 'byte_extract' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, -1, string dec, part HTTP_RESPONSE_BODY;"),
|
||||
"Malformed variable name in the 'byte_extract' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, bad_data_type, string dechex, part HTTP_RESPONSE_BODY;"),
|
||||
"Unknown data type in the 'byte_extract' keyword: dechex"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, 1var, string dec, part HTTP_RESPONSE_BODY;"),
|
||||
"Malformed variable name in the 'byte_extract' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, bad_align, align 3, part HTTP_RESPONSE_BODY;"),
|
||||
"Unknown 'align' in the 'byte_extract' keyword: 3"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, bad_constant, offset 0x;"),
|
||||
"Malformed constant '0x' in the 'offset' in the 'byte_extract' keyword"
|
||||
);
|
||||
EXPECT_EQ(ruleCompileFail("byte_extract: 1;"), "Invalid number of attributes in the 'byte_extract' keyword");
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, bad_attr, offset;"),
|
||||
"Malformed offset' in the 'byte_extract' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, bad_attr, string hex dec;"),
|
||||
"Malformed data type in the 'byte_extract' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, bad_attr, ofset 5;"),
|
||||
"Unknown attribute 'ofset' in the 'byte_extract' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("byte_extract: 1, bad_align, align 2 4;"),
|
||||
"Malformed 'align' in the 'byte_extract' keyword"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, jump_from_beginning_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 9;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"
|
||||
"jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 9;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 10;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: -1, from_beginning, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 10;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: 10, from_beginning, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 0;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(ruleRun("jump: 11, from_beginning, part HTTP_RESPONSE_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, jump_relative_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"data: \"1\", part HTTP_RESPONSE_BODY;"
|
||||
"jump: 1, relative, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 8;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"data: \"1\", part HTTP_RESPONSE_BODY;"
|
||||
"jump: 1, relative, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 9;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"data: \"1\", part HTTP_RESPONSE_BODY;"
|
||||
"jump: -2, relative, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 10;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"data: \"1\", part HTTP_RESPONSE_BODY;"
|
||||
"jump: 9, relative, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 0;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"data: \"1\", part HTTP_RESPONSE_BODY;"
|
||||
"jump: 10, relative, part HTTP_RESPONSE_BODY;"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, jump_from_end_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_FALSE(ruleRun("jump: 1, from_end, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: -1, from_end, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 1;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: -10, from_end, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 10;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: -11, from_end, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 10;"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, combined_jumps_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"
|
||||
"jump: -1, from_end, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 1;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"
|
||||
"jump: -1, relative, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 10;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: -1, from_end, part HTTP_RESPONSE_BODY;"
|
||||
"jump: 1, relative, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 0;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: -1, from_end, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 1;"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
TEST_F(KeywordsRuleTest, jump_alignment_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: 1, from_beginning, align 2, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 8;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: 1, from_beginning, align 4, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 6;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: 3, from_beginning, part HTTP_RESPONSE_BODY;"
|
||||
"jump: 2, relative, align 2, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 4;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"jump: 3, from_beginning, part HTTP_RESPONSE_BODY;"
|
||||
"jump: 2, relative, align 2, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 7;"
|
||||
)
|
||||
);
|
||||
EXPECT_FALSE(
|
||||
ruleRun(
|
||||
"jump: 3, from_beginning, part HTTP_RESPONSE_BODY;"
|
||||
"jump: 2, relative, align 4, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 3;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: 2, from_beginning, align 2, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 8;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: 4, from_beginning, align 4, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 6;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: 0, from_beginning, align 2, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 10;"
|
||||
)
|
||||
);
|
||||
EXPECT_TRUE(
|
||||
ruleRun(
|
||||
"jump: 0, from_beginning, align 4, part HTTP_RESPONSE_BODY;"
|
||||
"length: length_var, relative, part HTTP_RESPONSE_BODY;"
|
||||
"compare: length_var, =, 10;"
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, jump_part_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_TRUE(ruleRun("jump: 1, from_beginning, part HTTP_RESPONSE_BODY;"));
|
||||
EXPECT_FALSE(ruleRun("jump: 1, from_beginning, part HTTP_REQUEST_BODY;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, jump_compile_fail_test) {
|
||||
appendBuffer("HTTP_RESPONSE_BODY", "1234567890");
|
||||
|
||||
EXPECT_EQ(ruleCompileFail("jump: 1;"), "Invalid number of attributes in the 'jump' keyword");
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("jump: 2 1, from_beginning;"),
|
||||
"More than one element in the jumping value in the 'jump' keyword"
|
||||
);
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("jump: 2, from_relative;"),
|
||||
"Unknown jumping 'from' parameter in the 'jump' keyword: from_relative"
|
||||
);
|
||||
EXPECT_EQ(ruleCompileFail("jump: 2, relative, align 3;"), "Unknown 'align' in the 'jump' keyword: 3");
|
||||
EXPECT_EQ(ruleCompileFail("jump: 2, relative, align 1;"), "Unknown 'align' in the 'jump' keyword: 1");
|
||||
EXPECT_EQ(ruleCompileFail("jump: 2, relative, align2 2;"), "Unknown attribute align2 in the 'jump' keyword");
|
||||
EXPECT_EQ(ruleCompileFail("jump: 2, relative, align 2 4;"), "Malformed 'align' in the 'jump' keyword");
|
||||
EXPECT_EQ(
|
||||
ruleCompileFail("jump: 2, from_beginning relative;"),
|
||||
"More than one element in the jumping 'from' parameter in the 'jump' keyword"
|
||||
);
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, stateop)
|
||||
{
|
||||
using testing::_;
|
||||
|
||||
ConfigComponent conf;
|
||||
testing::StrictMock<MockTable> table;
|
||||
|
||||
std::unique_ptr<TableOpaqueBase> opq;
|
||||
TableOpaqueBase *opq_ptr;
|
||||
bool has_stage = false;
|
||||
EXPECT_CALL(table, createStateRValueRemoved(_, _))
|
||||
.WillOnce(testing::DoAll(
|
||||
testing::Invoke(
|
||||
[&] (const type_index &, std::unique_ptr<TableOpaqueBase> &other)
|
||||
{
|
||||
opq = std::move(other);
|
||||
opq_ptr = opq.get();
|
||||
has_stage = true;
|
||||
}
|
||||
),
|
||||
testing::Return(true)
|
||||
));
|
||||
EXPECT_CALL(table, getState(_)).WillRepeatedly(testing::ReturnPointee(&opq_ptr));
|
||||
EXPECT_CALL(table, hasState(_)).WillRepeatedly(testing::ReturnPointee(&has_stage));
|
||||
|
||||
EXPECT_FALSE(ruleRun("stateop: state sss, isset;"));
|
||||
|
||||
EXPECT_TRUE(ruleRun("stateop: state sss, unset;"));
|
||||
EXPECT_FALSE(ruleRun("stateop: state sss, isset;"));
|
||||
|
||||
EXPECT_TRUE(ruleRun("stateop: state sss, set;"));
|
||||
EXPECT_TRUE(ruleRun("stateop: state sss, isset;"));
|
||||
EXPECT_FALSE(ruleRun("stateop: state dd, isset;"));
|
||||
|
||||
EXPECT_TRUE(ruleRun("stateop: state sss, unset;"));
|
||||
EXPECT_FALSE(ruleRun("stateop: state sss, isset;"));
|
||||
}
|
||||
|
||||
TEST_F(KeywordsRuleTest, no_match)
|
||||
{
|
||||
EXPECT_FALSE(ruleRun("no_match;"));
|
||||
}
|
171
components/utils/keywords/keywords_ut/single_keyword_ut.cc
Normal file
171
components/utils/keywords/keywords_ut/single_keyword_ut.cc
Normal file
@@ -0,0 +1,171 @@
|
||||
#include "../sentinel_runtime_state.h"
|
||||
#include "../single_keyword.h"
|
||||
#include "cptest.h"
|
||||
#include <list>
|
||||
|
||||
using namespace std;
|
||||
using namespace testing;
|
||||
|
||||
#define FIRST_VARIABLE_ID 1
|
||||
#define FIRST_VARIABLE_VAL 2u
|
||||
#define SECOND_VARIABLE_ID 3
|
||||
#define SECOND_VARIABLE_VAL 4u
|
||||
#define THIRD_VARIABLE_ID 5
|
||||
#define THIRD_VARIABLE_VAL 6u
|
||||
|
||||
#define FIRST_OFFSET 4u
|
||||
#define SECOND_OFFSET 5u
|
||||
#define THIRD_OFFSET 6u
|
||||
|
||||
const static unsigned int zero = 0;
|
||||
|
||||
class I_KeywordRuntimeStateTest : public Test
|
||||
{
|
||||
public:
|
||||
// Constructs SentinelKeyword as head because it is the only I_KeywordRuntimeState implementation
|
||||
// that does not hold I_KeywordRuntimeState *prev
|
||||
I_KeywordRuntimeStateTest() : list_head(&sentinel) {}
|
||||
|
||||
uint
|
||||
getOffset(const string &id)
|
||||
{
|
||||
return list_head->getOffset(id);
|
||||
}
|
||||
|
||||
uint
|
||||
getVariable(uint requested_var_id)
|
||||
{
|
||||
return list_head->getVariable(requested_var_id);
|
||||
}
|
||||
|
||||
void
|
||||
addOffsetState(const string &_ctx, uint _offset)
|
||||
{
|
||||
offset_list.push_front(make_unique<OffsetRuntimeState>(list_head, _ctx, _offset));
|
||||
list_head = offset_list.front().get();
|
||||
}
|
||||
|
||||
void
|
||||
addVariableState(uint _var_id, uint _val)
|
||||
{
|
||||
variable_list.push_front(make_unique<VariableRuntimeState>(list_head, _var_id, _val));
|
||||
list_head = variable_list.front().get();
|
||||
}
|
||||
|
||||
private:
|
||||
list<unique_ptr<VariableRuntimeState>> variable_list;
|
||||
list<unique_ptr<OffsetRuntimeState>> offset_list;
|
||||
SentinelRuntimeState sentinel;
|
||||
I_KeywordRuntimeState *list_head;
|
||||
};
|
||||
|
||||
TEST_F(I_KeywordRuntimeStateTest, one_element_list_positive_test) {
|
||||
EXPECT_EQ(getOffset("HTTP_METHOD"), zero);
|
||||
EXPECT_EQ(getOffset("HTTP_REQ_COOKIE"), zero);
|
||||
EXPECT_EQ(getOffset("HTTP_REQUEST_HEADERS"), zero);
|
||||
}
|
||||
|
||||
TEST_F(I_KeywordRuntimeStateTest, one_variable_state_list_positive_test) {
|
||||
addVariableState(FIRST_VARIABLE_ID, FIRST_VARIABLE_VAL);
|
||||
EXPECT_EQ(getOffset("HTTP_COMPLETE_URL_ENCODED"), zero);
|
||||
EXPECT_EQ(getOffset("HTTP_METHOD"), zero);
|
||||
EXPECT_EQ(getVariable(FIRST_VARIABLE_ID), FIRST_VARIABLE_VAL);
|
||||
}
|
||||
|
||||
TEST_F(I_KeywordRuntimeStateTest, one_offset_state_list_positive_test) {
|
||||
EXPECT_EQ(getOffset("HTTP_REQUEST_HEADERS"), zero);
|
||||
addOffsetState("HTTP_REQUEST_HEADERS", FIRST_OFFSET);
|
||||
EXPECT_EQ(getOffset("HTTP_REQUEST_HEADERS"), FIRST_OFFSET);
|
||||
}
|
||||
|
||||
TEST_F(I_KeywordRuntimeStateTest, one_element_list_negative_test) {
|
||||
cptestPrepareToDie();
|
||||
EXPECT_DEATH(getVariable(FIRST_OFFSET), "");
|
||||
EXPECT_DEATH(getVariable(SECOND_OFFSET), "");
|
||||
EXPECT_DEATH(getVariable(THIRD_OFFSET), "");
|
||||
}
|
||||
|
||||
TEST_F(I_KeywordRuntimeStateTest, variable_runtime_state_list_positive_test) {
|
||||
// Notice that variables ids and values are different
|
||||
addVariableState(FIRST_VARIABLE_ID, FIRST_VARIABLE_VAL);
|
||||
addVariableState(SECOND_VARIABLE_ID, SECOND_VARIABLE_VAL);
|
||||
addVariableState(THIRD_VARIABLE_ID, THIRD_VARIABLE_VAL);
|
||||
|
||||
EXPECT_EQ(getOffset("HTTP_METHOD"), zero);
|
||||
EXPECT_EQ(getOffset("HTTP_COMPLETE_URL_ENCODED"), zero);
|
||||
EXPECT_EQ(getOffset("HTTP_REQ_COOKIE"), zero);
|
||||
EXPECT_EQ(getOffset("HTTP_REQUEST_HEADERS"), zero);
|
||||
EXPECT_EQ(getOffset("HTTP_REQUEST_BODY"), zero);
|
||||
|
||||
EXPECT_EQ(getVariable(FIRST_VARIABLE_ID), FIRST_VARIABLE_VAL);
|
||||
EXPECT_EQ(getVariable(SECOND_VARIABLE_ID), SECOND_VARIABLE_VAL);
|
||||
EXPECT_EQ(getVariable(THIRD_VARIABLE_ID), THIRD_VARIABLE_VAL);
|
||||
}
|
||||
|
||||
TEST_F(I_KeywordRuntimeStateTest, OffsetRuntimeState_list_negative_test) {
|
||||
addOffsetState("HTTP_COMPLETE_URL_ENCODED", FIRST_OFFSET);
|
||||
addOffsetState("HTTP_REQ_COOKIE", SECOND_OFFSET);
|
||||
addOffsetState("HTTP_METHOD", THIRD_OFFSET);
|
||||
|
||||
cptestPrepareToDie();
|
||||
EXPECT_DEATH(getVariable(FIRST_OFFSET), "");
|
||||
EXPECT_DEATH(getVariable(SECOND_OFFSET), "");
|
||||
EXPECT_DEATH(getVariable(THIRD_OFFSET), "");
|
||||
}
|
||||
|
||||
TEST_F(I_KeywordRuntimeStateTest, offset_runtime_state_list_positive_test) {
|
||||
EXPECT_EQ(getOffset("HTTP_REQUEST_HEADERS"), zero);
|
||||
EXPECT_EQ(getOffset("HTTP_REQ_COOKIE"), zero);
|
||||
|
||||
addOffsetState("HTTP_REQUEST_HEADERS", FIRST_OFFSET);
|
||||
addOffsetState("HTTP_REQ_COOKIE", SECOND_OFFSET);
|
||||
|
||||
EXPECT_EQ(getOffset("HTTP_REQUEST_HEADERS"), FIRST_OFFSET);
|
||||
EXPECT_EQ(getOffset("HTTP_REQ_COOKIE"), SECOND_OFFSET);
|
||||
}
|
||||
|
||||
TEST_F(I_KeywordRuntimeStateTest, mixed_types_list_positive_test) {
|
||||
EXPECT_EQ(getOffset("HTTP_COMPLETE_URL_ENCODED"), zero);
|
||||
EXPECT_EQ(getOffset("HTTP_METHOD"), zero);
|
||||
|
||||
addOffsetState("HTTP_COMPLETE_URL_ENCODED", FIRST_OFFSET);
|
||||
addVariableState(SECOND_VARIABLE_ID, SECOND_VARIABLE_VAL);
|
||||
addOffsetState("HTTP_METHOD", THIRD_OFFSET);
|
||||
|
||||
EXPECT_EQ(getOffset("HTTP_COMPLETE_URL_ENCODED"), FIRST_OFFSET);
|
||||
EXPECT_EQ(getOffset("HTTP_METHOD"), THIRD_OFFSET);
|
||||
|
||||
EXPECT_EQ(getVariable(SECOND_VARIABLE_ID), SECOND_VARIABLE_VAL);
|
||||
}
|
||||
|
||||
TEST_F(I_KeywordRuntimeStateTest, mixed_types_list_negative_test) {
|
||||
addOffsetState("HTTP_COMPLETE_URL_ENCODED", FIRST_OFFSET);
|
||||
addVariableState(SECOND_VARIABLE_ID, SECOND_VARIABLE_VAL);
|
||||
addOffsetState("HTTP_METHOD", THIRD_OFFSET);
|
||||
|
||||
cptestPrepareToDie();
|
||||
EXPECT_DEATH(getVariable(FIRST_OFFSET), "");
|
||||
EXPECT_DEATH(getVariable(THIRD_OFFSET), "");
|
||||
}
|
||||
|
||||
TEST_F(I_KeywordRuntimeStateTest, mixed_types_list_offset_shadowing_test) {
|
||||
addOffsetState("HTTP_COMPLETE_URL_ENCODED", FIRST_OFFSET);
|
||||
|
||||
EXPECT_EQ(getOffset("HTTP_COMPLETE_URL_ENCODED"), FIRST_OFFSET);
|
||||
|
||||
addVariableState(SECOND_VARIABLE_ID, SECOND_VARIABLE_VAL);
|
||||
addOffsetState("HTTP_COMPLETE_URL_ENCODED", THIRD_OFFSET);
|
||||
|
||||
EXPECT_EQ(getOffset("HTTP_COMPLETE_URL_ENCODED"), THIRD_OFFSET);
|
||||
}
|
||||
|
||||
TEST_F(I_KeywordRuntimeStateTest, mixed_types_list_variable_shadowing_test) {
|
||||
addVariableState(FIRST_VARIABLE_ID, FIRST_VARIABLE_VAL);
|
||||
|
||||
EXPECT_EQ(getVariable(FIRST_VARIABLE_ID), FIRST_VARIABLE_VAL);
|
||||
|
||||
addOffsetState("HTTP_COMPLETE_URL_ENCODED", SECOND_OFFSET);
|
||||
addVariableState(FIRST_VARIABLE_ID, THIRD_VARIABLE_VAL);
|
||||
|
||||
EXPECT_EQ(getVariable(FIRST_VARIABLE_ID), THIRD_VARIABLE_VAL);
|
||||
}
|
162
components/utils/keywords/length_keyword.cc
Normal file
162
components/utils/keywords/length_keyword.cc
Normal file
@@ -0,0 +1,162 @@
|
||||
#include "single_keyword.h"
|
||||
#include "output.h"
|
||||
#include "debug.h"
|
||||
#include "flags.h"
|
||||
|
||||
#include <map>
|
||||
#include <strings.h>
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_KEYWORD);
|
||||
|
||||
class LengthKeyword : public SingleKeyword
|
||||
{
|
||||
public:
|
||||
explicit LengthKeyword(const vector<KeywordAttr> &attr, VariablesMapping &vars);
|
||||
MatchStatus isMatch(const I_KeywordRuntimeState* prev) const override;
|
||||
|
||||
private:
|
||||
enum class Mode { EXACT, MIN, MAX, COUNT };
|
||||
using ModeFlags = Flags<Mode>;
|
||||
|
||||
void
|
||||
setRelative(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
is_relative.setAttr(attr, "length");
|
||||
}
|
||||
|
||||
void
|
||||
setExact(const KeywordAttr &, const VariablesMapping &)
|
||||
{
|
||||
if (!mode.empty()) throw KeywordError("Redefining 'length' keyword operation");
|
||||
mode.setFlag(Mode::EXACT);
|
||||
}
|
||||
|
||||
void
|
||||
setMin(const KeywordAttr &, const VariablesMapping &)
|
||||
{
|
||||
if (!mode.empty()) throw KeywordError("Redefining 'length' keyword operation");
|
||||
mode.setFlag(Mode::MIN);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
setMax(const KeywordAttr &, const VariablesMapping &)
|
||||
{
|
||||
if (!mode.empty()) throw KeywordError("Redefining 'length' keyword operation");
|
||||
mode.setFlag(Mode::MAX);
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
setContext(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
ctx.setAttr(attr, "length");
|
||||
}
|
||||
|
||||
bool
|
||||
isConstant() const
|
||||
{
|
||||
return !is_relative && compare_size.isConstant();
|
||||
}
|
||||
|
||||
BoolAttr is_relative;
|
||||
ModeFlags mode;
|
||||
CtxAttr ctx;
|
||||
uint var_id;
|
||||
NumericAttr compare_size;
|
||||
|
||||
static const map<string, void(LengthKeyword::*)(const KeywordAttr &, const VariablesMapping &)> setops;
|
||||
};
|
||||
|
||||
const map<string, void(LengthKeyword::*)(const KeywordAttr &, const VariablesMapping &)> LengthKeyword::setops = {
|
||||
{ "relative", &LengthKeyword::setRelative },
|
||||
{ "exact", &LengthKeyword::setExact },
|
||||
{ "min", &LengthKeyword::setMin },
|
||||
{ "max", &LengthKeyword::setMax },
|
||||
{ "part", &LengthKeyword::setContext }
|
||||
};
|
||||
|
||||
LengthKeyword::LengthKeyword(const vector<KeywordAttr> &attrs, VariablesMapping &vars)
|
||||
{
|
||||
if (attrs.size() == 0) throw KeywordError("Invalid number of attributes in the 'length' keyword");
|
||||
|
||||
//parisng first attribute (Required) - variable name
|
||||
auto &var_name_param = attrs[0].getParams();
|
||||
|
||||
if (var_name_param.size() != 1) {
|
||||
throw KeywordError("More than one element in the variable name in the 'length' keyword");
|
||||
}
|
||||
|
||||
const string &string_var_name = var_name_param[0];
|
||||
|
||||
if (string_var_name == "relative") {
|
||||
throw KeywordError("The 'relative' cannot be the variable name in the 'length' keyword");
|
||||
}
|
||||
if (string_var_name == "part") {
|
||||
throw KeywordError("The 'part' cannot be the variable name in the 'length' keyword");
|
||||
}
|
||||
if (string_var_name == "exact") {
|
||||
throw KeywordError("The 'exact' cannot be the variable name in the 'length' keyword");
|
||||
}
|
||||
if (string_var_name == "min") {
|
||||
throw KeywordError("The 'min' cannot be the variable name in the 'length' keyword");
|
||||
}
|
||||
if (string_var_name == "max") {
|
||||
throw KeywordError("The 'max' cannot be the variable name in the 'length' keyword");
|
||||
}
|
||||
|
||||
//parsing the other optional attributes
|
||||
for (uint i = 1; i < attrs.size(); i++) {
|
||||
auto curr = setops.find(attrs[i].getAttrName());
|
||||
if (curr == setops.end()) {
|
||||
throw KeywordError("Unknown attribute '" + attrs[i].getAttrName() + "' in the 'length' keyword");
|
||||
}
|
||||
auto set_func = curr->second;
|
||||
(this->*set_func)(attrs[i], vars);
|
||||
}
|
||||
|
||||
if (mode.empty()) {
|
||||
if (isdigit(string_var_name[0]) || string_var_name[0] == '-') {
|
||||
throw KeywordError("Malformed variable name in the 'length' keyword");
|
||||
}
|
||||
|
||||
var_id = vars.addNewVariable(string_var_name);
|
||||
} else {
|
||||
compare_size.setAttr("length value", string_var_name, vars, "length", 10, true);
|
||||
}
|
||||
}
|
||||
|
||||
MatchStatus
|
||||
LengthKeyword::isMatch(const I_KeywordRuntimeState *prev) const
|
||||
{
|
||||
auto part = Singleton::Consume<I_Environment>::by<KeywordComp>()->get<Buffer>(static_cast<string>(ctx));
|
||||
|
||||
if (!part.ok()) return MatchStatus::NoMatchFinal;
|
||||
|
||||
uint offset = is_relative ? prev->getOffset(ctx) : 0;
|
||||
uint size = (*part).size();
|
||||
|
||||
if (offset <= size) {
|
||||
if (mode.isSet(Mode::EXACT)) {
|
||||
if (size - offset == static_cast<uint>(compare_size.evalAttr(prev))) return runNext(prev);
|
||||
} else if (mode.isSet(Mode::MIN)) {
|
||||
if (size - offset >= static_cast<uint>(compare_size.evalAttr(prev))) return runNext(prev);
|
||||
} else if (mode.isSet(Mode::MAX)) {
|
||||
if (size - offset <= static_cast<uint>(compare_size.evalAttr(prev))) return runNext(prev);
|
||||
} else {
|
||||
VariableRuntimeState new_length_var(prev, var_id, size-offset);
|
||||
return runNext(&new_length_var);
|
||||
}
|
||||
}
|
||||
|
||||
// If there was no matches and the keyword is effected by other keywords, then we know that the rule won't match
|
||||
return isConstant() ? MatchStatus::NoMatchFinal : MatchStatus::NoMatch;
|
||||
}
|
||||
|
||||
unique_ptr<SingleKeyword>
|
||||
genLengthKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
|
||||
{
|
||||
return make_unique<LengthKeyword>(attr, known_vars);
|
||||
}
|
29
components/utils/keywords/no_match_keyword.cc
Normal file
29
components/utils/keywords/no_match_keyword.cc
Normal file
@@ -0,0 +1,29 @@
|
||||
#include "single_keyword.h"
|
||||
#include "table_opaque.h"
|
||||
#include "debug.h"
|
||||
|
||||
#include <map>
|
||||
#include <strings.h>
|
||||
|
||||
#include "cereal/types/set.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_KEYWORD);
|
||||
|
||||
class NoMatchKeyword : public SingleKeyword
|
||||
{
|
||||
public:
|
||||
explicit NoMatchKeyword(const vector<KeywordAttr> &attr, VariablesMapping &)
|
||||
{
|
||||
if (!attr.empty()) throw KeywordError("The 'no_match' keyword doesn't take attributes");
|
||||
}
|
||||
|
||||
MatchStatus isMatch(const I_KeywordRuntimeState *) const override { return MatchStatus::NoMatchFinal; }
|
||||
};
|
||||
|
||||
unique_ptr<SingleKeyword>
|
||||
genNoMatchKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
|
||||
{
|
||||
return make_unique<NoMatchKeyword>(attr, known_vars);
|
||||
}
|
372
components/utils/keywords/pcre_keyword.cc
Normal file
372
components/utils/keywords/pcre_keyword.cc
Normal file
@@ -0,0 +1,372 @@
|
||||
#include "single_keyword.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
#define PCRE2_CODE_UNIT_WIDTH 8
|
||||
#include <pcre2.h>
|
||||
|
||||
#include "output.h"
|
||||
#include "debug.h"
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_KEYWORD);
|
||||
|
||||
class PCREKeyword : public SingleKeyword
|
||||
{
|
||||
public:
|
||||
explicit PCREKeyword(const vector<KeywordAttr> &attr, const VariablesMapping &known_vars);
|
||||
MatchStatus isMatch(const I_KeywordRuntimeState *prev) const override;
|
||||
|
||||
private:
|
||||
void
|
||||
setOffset(const KeywordAttr &attr, const VariablesMapping &vars)
|
||||
{
|
||||
offset.setAttr(attr, vars, "pcre");
|
||||
}
|
||||
|
||||
void
|
||||
setDepth(const KeywordAttr &attr, const VariablesMapping &vars)
|
||||
{
|
||||
depth.setAttr(attr, vars, "pcre");
|
||||
}
|
||||
|
||||
void
|
||||
setRelative(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
is_relative.setAttr(attr, "pcre");
|
||||
}
|
||||
|
||||
void
|
||||
setCaseInsensitive(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
is_case_insensitive.setAttr(attr, "pcre");
|
||||
}
|
||||
|
||||
void
|
||||
setContext(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
ctx.setAttr(attr, "pcre");
|
||||
}
|
||||
|
||||
string parseString(const string &str);
|
||||
pair<string, string> findExprInStr(const string &str, size_t start, size_t end);
|
||||
void parseOptions(const string &str);
|
||||
void compilePCRE(const string &str);
|
||||
|
||||
pair<uint, uint> getStartOffsetAndLength(uint buf_size, const I_KeywordRuntimeState *prev) const;
|
||||
|
||||
bool
|
||||
isConstant() const
|
||||
{
|
||||
return !is_relative && offset.isConstant() && depth.isConstant();
|
||||
}
|
||||
|
||||
class PCREDelete
|
||||
{
|
||||
public:
|
||||
void
|
||||
operator()(pcre2_code *ptr)
|
||||
{
|
||||
pcre2_code_free(ptr);
|
||||
}
|
||||
};
|
||||
unique_ptr<pcre2_code, PCREDelete> pcre_machine;
|
||||
|
||||
class PCREResultDelete
|
||||
{
|
||||
public:
|
||||
void
|
||||
operator()(pcre2_match_data *ptr)
|
||||
{
|
||||
pcre2_match_data_free(ptr);
|
||||
}
|
||||
};
|
||||
unique_ptr<pcre2_match_data, PCREResultDelete> pcre_result;
|
||||
|
||||
NumericAttr offset;
|
||||
NumericAttr depth;
|
||||
BoolAttr is_negative;
|
||||
BoolAttr is_relative;
|
||||
|
||||
BoolAttr is_case_insensitive;
|
||||
BoolAttr is_multiline;
|
||||
BoolAttr is_dotall;
|
||||
BoolAttr is_extended;
|
||||
BoolAttr is_dollar_endonly;
|
||||
BoolAttr is_anchor;
|
||||
BoolAttr is_ungreedy;
|
||||
|
||||
CtxAttr ctx;
|
||||
|
||||
string pcre_expr;
|
||||
|
||||
static const map<string, void(PCREKeyword::*)(const KeywordAttr &, const VariablesMapping &)> setops;
|
||||
};
|
||||
|
||||
const map<string, void(PCREKeyword::*)(const KeywordAttr&, const VariablesMapping&)> PCREKeyword::setops = {
|
||||
{ "relative", &PCREKeyword::setRelative },
|
||||
{ "offset", &PCREKeyword::setOffset },
|
||||
{ "depth", &PCREKeyword::setDepth },
|
||||
{ "nocase", &PCREKeyword::setCaseInsensitive },
|
||||
{ "part", &PCREKeyword::setContext },
|
||||
};
|
||||
|
||||
PCREKeyword::PCREKeyword(const vector<KeywordAttr> &attrs, const VariablesMapping &known_vars)
|
||||
:
|
||||
offset(),
|
||||
depth()
|
||||
{
|
||||
auto &expr_param = attrs[0].getParams();
|
||||
if (expr_param.size() != 1) throw KeywordError("More than one element in the 'pcre' keyword pattern");
|
||||
auto expr = parseString(expr_param[0]);
|
||||
dbgDebug(D_KEYWORD) << "Creating a new 'pcre' expression: " << expr;
|
||||
|
||||
for (uint i = 1; i<attrs.size(); i++) {
|
||||
auto curr = setops.find(attrs[i].getAttrName());
|
||||
if (curr == setops.end()) {
|
||||
throw KeywordError("Unknown attribute '" + attrs[i].getAttrName() + "' in the 'pcre' keyword");
|
||||
}
|
||||
auto set_func = curr->second;
|
||||
(this->*set_func)(attrs[i], known_vars);
|
||||
}
|
||||
|
||||
compilePCRE(expr);
|
||||
}
|
||||
|
||||
string
|
||||
PCREKeyword::parseString(const string &str)
|
||||
{
|
||||
size_t start_offset = 0, end_offset = str.size();
|
||||
|
||||
if (start_offset<end_offset && str[start_offset]=='!') {
|
||||
is_negative.setAttr("pcre", "negative");
|
||||
start_offset++;
|
||||
}
|
||||
|
||||
if (start_offset+1>=end_offset || str[start_offset]!='"' || str[end_offset-1]!='"') {
|
||||
throw KeywordError("The 'pcre' expression should be enclosed in quotation marks");
|
||||
}
|
||||
start_offset++;
|
||||
end_offset--;
|
||||
|
||||
string expr, options;
|
||||
tie(expr, options) = findExprInStr(str, start_offset, end_offset);
|
||||
|
||||
parseOptions(options);
|
||||
|
||||
return expr;
|
||||
}
|
||||
|
||||
pair<string, string>
|
||||
PCREKeyword::findExprInStr(const string &str, size_t start, size_t end)
|
||||
{
|
||||
if (start>=end) throw KeywordError("The 'pcre' string is empty");
|
||||
|
||||
// There are two way to write the regular expression:
|
||||
// Either between '/' charecters: "/regexp/" (this is the default delimiter)
|
||||
// Or use 'm' to set a delimiter: "mDregexpD" (here 'D' is used as the delimiter)
|
||||
// The switch will set the parameter 'start' so the 'str[start]' is the first delimiter.
|
||||
switch (str[start]) {
|
||||
case '/': {
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
start++;
|
||||
if (start>=end) {
|
||||
throw KeywordError("Failed to detect a delimiter in the 'pcre' keyword regular expression");
|
||||
}
|
||||
break;
|
||||
}
|
||||
default:
|
||||
KeywordError("Bad start for the 'pcre' regular expression");
|
||||
}
|
||||
|
||||
size_t expr_end = str.find_last_of(str[start], end-1);
|
||||
start++;
|
||||
if (expr_end<=start) throw KeywordError("The 'pcre' regular expression is empty");
|
||||
|
||||
auto options_start = expr_end+1;
|
||||
return make_pair(str.substr(start, expr_end-start), str.substr(options_start, end-options_start));
|
||||
}
|
||||
|
||||
void
|
||||
PCREKeyword::parseOptions(const string &options)
|
||||
{
|
||||
for (auto ch : options) {
|
||||
switch (ch) {
|
||||
case 'i': {
|
||||
is_case_insensitive.setAttr("pcre", "nocase");
|
||||
break;
|
||||
}
|
||||
case 'R': {
|
||||
is_relative.setAttr("pcre", "relative");
|
||||
break;
|
||||
}
|
||||
case 'm': {
|
||||
is_multiline.setAttr("pcre", "multiline");
|
||||
break;
|
||||
}
|
||||
case 's': {
|
||||
is_dotall.setAttr("pcre", "dotall");
|
||||
break;
|
||||
}
|
||||
case 'x': {
|
||||
is_extended.setAttr("pcre", "extended");
|
||||
break;
|
||||
}
|
||||
case 'E': {
|
||||
is_dollar_endonly.setAttr("pcre", "dollar_endonly");
|
||||
break;
|
||||
}
|
||||
case 'A': {
|
||||
is_anchor.setAttr("pcre", "anchor");
|
||||
break;
|
||||
}
|
||||
case 'G': {
|
||||
is_ungreedy.setAttr("pcre", "ungreedy");
|
||||
break;
|
||||
}
|
||||
default:
|
||||
throw KeywordError("Unknown option " + dumpHexChar(ch) + " in the 'pcre' keyword");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
PCREKeyword::compilePCRE(const string &expr)
|
||||
{
|
||||
uint32_t options = PCRE2_NO_AUTO_CAPTURE;
|
||||
if (is_case_insensitive) options |= PCRE2_CASELESS;
|
||||
if (is_multiline) options |= PCRE2_MULTILINE;
|
||||
if (is_dotall) options |= PCRE2_DOTALL;
|
||||
if (is_extended) options |= PCRE2_EXTENDED;
|
||||
if (is_dollar_endonly) options |= PCRE2_DOLLAR_ENDONLY;
|
||||
if (is_anchor) options |= PCRE2_ANCHORED;
|
||||
if (is_ungreedy) options |= PCRE2_UNGREEDY;
|
||||
|
||||
int error;
|
||||
PCRE2_SIZE error_offset;
|
||||
auto pattern = reinterpret_cast<PCRE2_SPTR>(expr.c_str());
|
||||
pcre_machine.reset(pcre2_compile(pattern, expr.size(), options, &error, &error_offset, nullptr));
|
||||
if (pcre_machine == nullptr) {
|
||||
vector<u_char> msg;
|
||||
msg.reserve(128);
|
||||
pcre2_get_error_message(error, msg.data(), msg.capacity());
|
||||
|
||||
throw KeywordError(
|
||||
"Failed to compile the 'pcre' at offset "
|
||||
+ to_string(error_offset)
|
||||
+ " with error: "
|
||||
+ reinterpret_cast<char *>(msg.data())
|
||||
);
|
||||
}
|
||||
|
||||
pcre_result.reset(pcre2_match_data_create_from_pattern(pcre_machine.get(), nullptr));
|
||||
if (pcre_result == nullptr) {
|
||||
throw KeywordError("Failed to allocate PCRE results container");
|
||||
}
|
||||
|
||||
pcre_expr = expr;
|
||||
}
|
||||
|
||||
static uint
|
||||
addOffset(uint offset, int add)
|
||||
{
|
||||
if (add<0 && offset<static_cast<uint>(-add)) return 0;
|
||||
return offset + add;
|
||||
}
|
||||
|
||||
pair<uint, uint>
|
||||
PCREKeyword::getStartOffsetAndLength(uint buf_size, const I_KeywordRuntimeState *prev) const
|
||||
{
|
||||
uint keyword_offset = is_relative?prev->getOffset(ctx):0;
|
||||
uint start_offset = addOffset(keyword_offset, offset.evalAttr(prev));
|
||||
|
||||
if (start_offset>=buf_size) return make_pair(0, 0);
|
||||
|
||||
uint length = buf_size-start_offset;
|
||||
|
||||
if (depth.isSet()) {
|
||||
length = min(length, static_cast<uint>(depth.evalAttr(prev)));
|
||||
}
|
||||
|
||||
return make_pair(start_offset, length);
|
||||
}
|
||||
|
||||
MatchStatus
|
||||
PCREKeyword::isMatch(const I_KeywordRuntimeState *prev) const
|
||||
{
|
||||
dbgAssert(pcre_machine!=nullptr) << "Trying to run on an uninitialized keyword 'pcre'";
|
||||
|
||||
auto part = Singleton::Consume<I_Environment>::by<KeywordComp>()->get<Buffer>(static_cast<string>(ctx));
|
||||
|
||||
if (!part.ok()) {
|
||||
if (is_negative) {
|
||||
return runNext(prev);
|
||||
}
|
||||
return MatchStatus::NoMatchFinal;
|
||||
}
|
||||
|
||||
uint offset, length;
|
||||
tie(offset, length) = getStartOffsetAndLength((*part).size(), prev);
|
||||
auto buf = (*part).getPtr(offset, length);
|
||||
|
||||
if (!buf.ok()) {
|
||||
dbgTrace(D_KEYWORD) << "Could not get the buffer for the 'pcre' keyword";
|
||||
return MatchStatus::NoMatchFinal;
|
||||
}
|
||||
const unsigned char *ptr = *buf;
|
||||
|
||||
bool match_found = false;
|
||||
uint buf_offset_found;
|
||||
for (uint buf_pos = 0; buf_pos<length; buf_pos = buf_offset_found) {
|
||||
dbgDebug(D_KEYWORD) << "Looking for expression: " << pcre_expr;
|
||||
dbgTrace(D_KEYWORD) << "Running pcre_exec for expression: " << ptr;
|
||||
int result = pcre2_match(
|
||||
pcre_machine.get(),
|
||||
ptr,
|
||||
length,
|
||||
buf_pos,
|
||||
0,
|
||||
pcre_result.get(),
|
||||
nullptr
|
||||
);
|
||||
|
||||
if (result<0) {
|
||||
// No match (possiblely due to an error)
|
||||
dbgDebug(D_KEYWORD) << "No match, possiblely due to an error in 'pcre_exec'";
|
||||
break;
|
||||
} else {
|
||||
dbgDebug(D_KEYWORD) << "Match found";
|
||||
}
|
||||
|
||||
if (is_negative) {
|
||||
return isConstant()?MatchStatus::NoMatchFinal:MatchStatus::NoMatch;
|
||||
}
|
||||
match_found = true;
|
||||
buf_offset_found = pcre2_get_ovector_pointer(pcre_result.get())[0];
|
||||
OffsetRuntimeState new_offset(prev, ctx, offset+buf_offset_found);
|
||||
auto next_keyword_result = runNext(&new_offset);
|
||||
if (next_keyword_result!=MatchStatus::NoMatch) return next_keyword_result;
|
||||
if (buf_offset_found<=buf_pos) buf_offset_found = buf_pos+1; // Deal with empty matches
|
||||
}
|
||||
|
||||
// No matchs is a success for negative keywords
|
||||
if (is_negative && !match_found) {
|
||||
return runNext(prev);
|
||||
}
|
||||
|
||||
// If there were no matchs and the keyword is an effected by other keywords, then we know that the rule won't match
|
||||
if (isConstant() && !match_found) {
|
||||
return MatchStatus::NoMatchFinal;
|
||||
}
|
||||
|
||||
return MatchStatus::NoMatch;
|
||||
}
|
||||
|
||||
unique_ptr<SingleKeyword>
|
||||
genPCREKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
|
||||
{
|
||||
return make_unique<PCREKeyword>(attr, known_vars);
|
||||
}
|
13
components/utils/keywords/sentinel_runtime_state.h
Normal file
13
components/utils/keywords/sentinel_runtime_state.h
Normal file
@@ -0,0 +1,13 @@
|
||||
#ifndef ___SENTINEL_RUNTIME_STATE_H__
|
||||
#define ___SENTINEL_RUNTIME_STATE_H__
|
||||
|
||||
#include "single_keyword.h"
|
||||
|
||||
class SentinelRuntimeState : public I_KeywordRuntimeState
|
||||
{
|
||||
public:
|
||||
uint getOffset(const std::string &) const override;
|
||||
uint getVariable(uint) const override;
|
||||
};
|
||||
|
||||
#endif // ___SENTINEL_RUNTIME_STATE_H__
|
276
components/utils/keywords/single_keyword.cc
Normal file
276
components/utils/keywords/single_keyword.cc
Normal file
@@ -0,0 +1,276 @@
|
||||
#include "single_keyword.h"
|
||||
|
||||
#include <algorithm>
|
||||
|
||||
using namespace std;
|
||||
|
||||
void
|
||||
SingleKeyword::appendKeyword(unique_ptr<SingleKeyword> &&_next)
|
||||
{
|
||||
if (next==nullptr) {
|
||||
next = move(_next);
|
||||
} else {
|
||||
next->appendKeyword(move(_next));
|
||||
}
|
||||
}
|
||||
|
||||
MatchStatus
|
||||
SingleKeyword::runNext(const I_KeywordRuntimeState *curr) const
|
||||
{
|
||||
if (next==nullptr) {
|
||||
return MatchStatus::Match;
|
||||
}
|
||||
return next->isMatch(curr);
|
||||
}
|
||||
|
||||
OffsetRuntimeState::OffsetRuntimeState(
|
||||
const I_KeywordRuntimeState *_p,
|
||||
const string &_ctx,
|
||||
uint _offset)
|
||||
:
|
||||
prev(_p),
|
||||
ctx(_ctx),
|
||||
offset(_offset)
|
||||
{
|
||||
}
|
||||
|
||||
uint
|
||||
OffsetRuntimeState::getOffset(const string &requested_ctx) const
|
||||
{
|
||||
if (ctx==requested_ctx) return offset;
|
||||
return prev->getOffset(requested_ctx);
|
||||
}
|
||||
|
||||
uint
|
||||
OffsetRuntimeState::getVariable(uint requested_var_id) const
|
||||
{
|
||||
return prev->getVariable(requested_var_id);
|
||||
}
|
||||
|
||||
VariableRuntimeState::VariableRuntimeState(
|
||||
const I_KeywordRuntimeState *_p,
|
||||
uint _var_id,
|
||||
uint _val)
|
||||
:
|
||||
prev(_p),
|
||||
var_id(_var_id),
|
||||
value(_val)
|
||||
{
|
||||
}
|
||||
|
||||
uint
|
||||
VariableRuntimeState::getOffset(const string &requested_ctx) const
|
||||
{
|
||||
return prev->getOffset(requested_ctx);
|
||||
}
|
||||
|
||||
uint
|
||||
VariableRuntimeState::getVariable(uint requested_var_id) const
|
||||
{
|
||||
if (var_id==requested_var_id) return value;
|
||||
return prev->getVariable(requested_var_id);
|
||||
}
|
||||
|
||||
uint
|
||||
VariablesMapping::addNewVariable(const string ¶m)
|
||||
{
|
||||
auto iter = mapping.find(param);
|
||||
if (iter==mapping.end()) {
|
||||
mapping[param] = mapping.size();
|
||||
}
|
||||
return mapping[param];
|
||||
}
|
||||
|
||||
Maybe<uint>
|
||||
VariablesMapping::getVariableId(const string ¶m) const
|
||||
{
|
||||
auto iter = mapping.find(param);
|
||||
if (iter==mapping.end()) {
|
||||
return genError(string("Unknown parameter ")+param);
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
void
|
||||
NumericAttr::setAttr(
|
||||
const KeywordAttr &attr,
|
||||
const VariablesMapping &known_vars,
|
||||
const string &keyword_name,
|
||||
const uint base,
|
||||
bool is_unsigned_val)
|
||||
{
|
||||
auto &vec = attr.getParams();
|
||||
if (vec.size()!= 2) {
|
||||
throw KeywordError("Malformed " + attr.getAttrName() + "' in the '" + keyword_name + "' keyword");
|
||||
}
|
||||
|
||||
setAttr(attr.getAttrName(), vec[1], known_vars, keyword_name, base, is_unsigned_val);
|
||||
}
|
||||
|
||||
void
|
||||
NumericAttr::setAttr(
|
||||
const string &attr_name,
|
||||
const string ¶m,
|
||||
const VariablesMapping &known_vars,
|
||||
const string &keyword_name,
|
||||
const uint base,
|
||||
bool is_unsigned_val)
|
||||
{
|
||||
if (isSet()) {
|
||||
throw KeywordError("Double definition of the '" + attr_name + "' in the '" + keyword_name + "' keyword");
|
||||
}
|
||||
|
||||
if (is_unsigned_val && param[0]=='-') {
|
||||
throw KeywordError(
|
||||
"Negative constant '" +
|
||||
param +
|
||||
"' in the '" +
|
||||
attr_name +
|
||||
"' in the '" +
|
||||
keyword_name +
|
||||
"' keyword"
|
||||
);
|
||||
}
|
||||
|
||||
if (isdigit(param[0]) || param[0] == '-') {
|
||||
status = Status::Const;
|
||||
try {
|
||||
size_t idx;
|
||||
val = stol(param, &idx, base);
|
||||
if (idx != param.length()) throw invalid_argument("");
|
||||
}
|
||||
catch (...) {
|
||||
throw KeywordError(
|
||||
"Malformed constant '" +
|
||||
param +
|
||||
"' in the '" +
|
||||
attr_name +
|
||||
"' in the '" +
|
||||
keyword_name +
|
||||
"' keyword"
|
||||
);
|
||||
}
|
||||
} else {
|
||||
status = Status::Var;
|
||||
val = known_vars.getVariableId(param).unpack<KeywordError>(
|
||||
"In " + keyword_name +
|
||||
" in " + attr_name + ": "
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
int
|
||||
NumericAttr::evalAttr(const I_KeywordRuntimeState *prev) const
|
||||
{
|
||||
if (status==Status::Var) {
|
||||
return prev->getVariable(val);
|
||||
}
|
||||
return val;
|
||||
}
|
||||
|
||||
void
|
||||
BoolAttr::setAttr(const KeywordAttr &attr, const string &keyword_name)
|
||||
{
|
||||
if (attr.getParams().size()!=1) {
|
||||
throw KeywordError("Malformed " + attr.getAttrName() + "' in the '" + keyword_name + "' keyword");
|
||||
}
|
||||
|
||||
val = true;
|
||||
}
|
||||
|
||||
void
|
||||
BoolAttr::setAttr(const string &keyword_name, const string &attr_name)
|
||||
{
|
||||
if (val) throw KeywordError("Double definition of the '" + attr_name + "' in the '" + keyword_name + "' keyword");
|
||||
|
||||
val = true;
|
||||
}
|
||||
|
||||
void
|
||||
CtxAttr::setAttr(const KeywordAttr &attr, const string &keyword_name)
|
||||
{
|
||||
if (is_set) throw KeywordError("Double definition of the 'part' in the '" + keyword_name + "' keyword");
|
||||
is_set = true;
|
||||
auto vec = attr.getParams();
|
||||
if (vec.size()!=2) throw KeywordError("Malformed 'part' in the '" + keyword_name + "' keyword");
|
||||
ctx = vec[1];
|
||||
}
|
||||
|
||||
const map<string, ComparisonAttr::CompId> ComparisonAttr::name_to_operator {
|
||||
{ "=", CompId::EQUAL },
|
||||
{ "!=", CompId::NOT_EQUAL },
|
||||
{ "<", CompId::LESS_THAN },
|
||||
{ ">", CompId::GREATER_THAN },
|
||||
{ "<=", CompId::LESS_THAN_OR_EQUAL },
|
||||
{ ">=", CompId::GREATER_THAN_OR_EQUAL }
|
||||
};
|
||||
|
||||
Maybe<ComparisonAttr::CompId>
|
||||
ComparisonAttr::getComparisonByName(const string &name)
|
||||
{
|
||||
auto iter = name_to_operator.find(name);
|
||||
if (iter == name_to_operator.end()) {
|
||||
return genError("Could not find the operator: " + name);
|
||||
}
|
||||
return iter->second;
|
||||
}
|
||||
|
||||
void
|
||||
ComparisonAttr::setAttr(const string ¶m, const string &keyword_name)
|
||||
{
|
||||
if (isSet()) {
|
||||
throw KeywordError("Double definition of the comparison opearator in the '" + keyword_name + "' keyword");
|
||||
}
|
||||
is_set = true;
|
||||
comp_val = getComparisonByName(param).unpack<KeywordError>(
|
||||
"Unknown comparison operator in the '" + keyword_name + "' keyword: "
|
||||
);
|
||||
}
|
||||
|
||||
bool
|
||||
ComparisonAttr::operator()(int first_val, int second_val) const
|
||||
{
|
||||
switch (comp_val) {
|
||||
case ComparisonAttr::CompId::EQUAL: {
|
||||
return first_val == second_val;
|
||||
}
|
||||
case ComparisonAttr::CompId::NOT_EQUAL: {
|
||||
return first_val != second_val;
|
||||
}
|
||||
case ComparisonAttr::CompId::LESS_THAN: {
|
||||
return first_val < second_val;
|
||||
}
|
||||
case ComparisonAttr::CompId::GREATER_THAN: {
|
||||
return first_val > second_val;
|
||||
}
|
||||
case ComparisonAttr::CompId::LESS_THAN_OR_EQUAL: {
|
||||
return first_val <= second_val;
|
||||
}
|
||||
case ComparisonAttr::CompId::GREATER_THAN_OR_EQUAL: {
|
||||
return first_val >= second_val;
|
||||
}
|
||||
}
|
||||
dbgAssert(false) << "ComparisonAttr::operator found an invalid comparison operator";
|
||||
return false;
|
||||
}
|
||||
|
||||
using InitFunc = unique_ptr<SingleKeyword>(*)(const vector<KeywordAttr> &, VariablesMapping &);
|
||||
|
||||
const map<string, InitFunc> initializers = {
|
||||
{"data", genDataKeyword },
|
||||
{"pcre", genPCREKeyword },
|
||||
{"length", genLengthKeyword },
|
||||
{"byte_extract", genByteExtractKeyword },
|
||||
{"compare", genCompareKeyword },
|
||||
{"stateop", genStateopKeyword },
|
||||
{"no_match", genNoMatchKeyword },
|
||||
{"jump", genJumpKeyword }
|
||||
};
|
||||
|
||||
unique_ptr<SingleKeyword>
|
||||
getKeywordByName(const KeywordParsed &keyword, VariablesMapping &known_vars)
|
||||
{
|
||||
auto iter = initializers.find(keyword.getName());
|
||||
if (iter==initializers.end()) throw KeywordError(keyword.getName() + " - unknown keyword type");
|
||||
return iter->second(keyword.getAttr(), known_vars);
|
||||
}
|
300
components/utils/keywords/single_keyword.h
Normal file
300
components/utils/keywords/single_keyword.h
Normal file
@@ -0,0 +1,300 @@
|
||||
#ifndef ___SINGLE_KEYWORD_H__
|
||||
#define ___SINGLE_KEYWORD_H__
|
||||
|
||||
#include <map>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#include <memory>
|
||||
|
||||
#include "buffer.h"
|
||||
#include "keyword_comp.h"
|
||||
#include "debug.h"
|
||||
|
||||
USE_DEBUG_FLAG(D_KEYWORD);
|
||||
|
||||
enum class MatchStatus { Match, NoMatch, NoMatchFinal };
|
||||
|
||||
class KeywordError
|
||||
{
|
||||
public:
|
||||
KeywordError(const std::string &str) : err(str)
|
||||
{
|
||||
}
|
||||
|
||||
const std::string &
|
||||
getErr() const
|
||||
{
|
||||
return err;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string err;
|
||||
};
|
||||
|
||||
class KeywordAttr
|
||||
{
|
||||
public:
|
||||
KeywordAttr(const std::string &str);
|
||||
|
||||
const std::string&
|
||||
getAttrName() const
|
||||
{
|
||||
return params[0];
|
||||
}
|
||||
|
||||
const std::vector<std::string> &
|
||||
getParams() const
|
||||
{
|
||||
return params;
|
||||
}
|
||||
|
||||
private:
|
||||
std::vector<std::string> params;
|
||||
};
|
||||
|
||||
class KeywordParsed
|
||||
{
|
||||
public:
|
||||
KeywordParsed(const std::string &keyword);
|
||||
|
||||
const std::string &
|
||||
getName() const
|
||||
{
|
||||
return name;
|
||||
}
|
||||
|
||||
const std::vector<KeywordAttr> &
|
||||
getAttr() const
|
||||
{
|
||||
return attr;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string name;
|
||||
std::vector<KeywordAttr> attr;
|
||||
};
|
||||
|
||||
class I_KeywordRuntimeState
|
||||
{
|
||||
public:
|
||||
virtual uint getOffset(const std::string &ctx) const = 0;
|
||||
virtual uint getVariable(uint requested_var_id) const = 0;
|
||||
protected:
|
||||
virtual ~I_KeywordRuntimeState()
|
||||
{
|
||||
}
|
||||
};
|
||||
|
||||
class OffsetRuntimeState : public I_KeywordRuntimeState
|
||||
{
|
||||
public:
|
||||
OffsetRuntimeState(const I_KeywordRuntimeState *prev, const std::string &ctx, uint offset);
|
||||
virtual ~OffsetRuntimeState()
|
||||
{
|
||||
}
|
||||
uint getOffset(const std::string &requested_ctx_id) const override;
|
||||
uint getVariable(uint requested_var_id) const override;
|
||||
|
||||
private:
|
||||
const I_KeywordRuntimeState *prev;
|
||||
std::string ctx;
|
||||
uint offset;
|
||||
};
|
||||
|
||||
class VariableRuntimeState : public I_KeywordRuntimeState
|
||||
{
|
||||
public:
|
||||
VariableRuntimeState(const I_KeywordRuntimeState *prev, uint var_id, uint val);
|
||||
virtual ~VariableRuntimeState()
|
||||
{
|
||||
}
|
||||
|
||||
uint getOffset(const std::string &requested_ctx_id) const override;
|
||||
uint getVariable(uint requested_var_id) const override;
|
||||
|
||||
private:
|
||||
const I_KeywordRuntimeState *prev;
|
||||
uint var_id;
|
||||
uint value;
|
||||
};
|
||||
|
||||
class VariablesMapping
|
||||
{
|
||||
public:
|
||||
uint addNewVariable(const std::string &name);
|
||||
Maybe<uint> getVariableId(const std::string &name) const;
|
||||
|
||||
private:
|
||||
std::map<std::string, uint> mapping;
|
||||
};
|
||||
|
||||
class NumericAttr
|
||||
{
|
||||
enum class Status { Unset, Const, Var };
|
||||
public:
|
||||
void setAttr(
|
||||
const KeywordAttr &attr,
|
||||
const VariablesMapping &known_vars,
|
||||
const std::string &keyword_name,
|
||||
const uint base = 10,
|
||||
bool is_unsigned_val = false);
|
||||
|
||||
void setAttr(
|
||||
const std::string &attr_name,
|
||||
const std::string ¶m,
|
||||
const VariablesMapping &known_vars,
|
||||
const std::string &keyword_name,
|
||||
const uint base = 10,
|
||||
bool is_unsigned_val = false);
|
||||
|
||||
int evalAttr(const I_KeywordRuntimeState *prev) const;
|
||||
|
||||
bool
|
||||
isConstant() const
|
||||
{
|
||||
return status!=Status::Var;
|
||||
}
|
||||
|
||||
bool
|
||||
isSet() const
|
||||
{
|
||||
return status!=Status::Unset;
|
||||
}
|
||||
|
||||
private:
|
||||
Status status = Status::Unset;
|
||||
int val = 0;
|
||||
};
|
||||
|
||||
class BoolAttr
|
||||
{
|
||||
public:
|
||||
void setAttr(const KeywordAttr &attr, const std::string &keyword_name);
|
||||
void setAttr(const std::string &keyword_name, const std::string &attr_name);
|
||||
|
||||
operator bool() const
|
||||
{
|
||||
return val;
|
||||
}
|
||||
|
||||
private:
|
||||
bool val = false;
|
||||
};
|
||||
|
||||
class CtxAttr
|
||||
{
|
||||
public:
|
||||
void setAttr(const KeywordAttr &attr, const std::string &keyword_name);
|
||||
|
||||
operator std::string() const
|
||||
{
|
||||
if (!is_set) {
|
||||
auto env = Singleton::Consume<I_Environment>::by<KeywordComp>();
|
||||
auto default_ctx = env->get<std::string>(I_KeywordsRule::getKeywordsRuleTag());
|
||||
if (default_ctx.ok()) return *default_ctx;
|
||||
dbgError(D_KEYWORD) << "Running keyword rule without specific context and without default";
|
||||
return "Missing Default Context";
|
||||
}
|
||||
return ctx;
|
||||
}
|
||||
|
||||
private:
|
||||
std::string ctx;
|
||||
bool is_set = false;
|
||||
};
|
||||
|
||||
class ComparisonAttr
|
||||
{
|
||||
public:
|
||||
enum class CompId
|
||||
{
|
||||
EQUAL,
|
||||
NOT_EQUAL,
|
||||
LESS_THAN,
|
||||
GREATER_THAN,
|
||||
LESS_THAN_OR_EQUAL,
|
||||
GREATER_THAN_OR_EQUAL
|
||||
};
|
||||
|
||||
void setAttr(const std::string ¶m, const std::string &keyword_name);
|
||||
bool operator()(int first_val, int second_val) const;
|
||||
|
||||
bool
|
||||
isSet() const
|
||||
{
|
||||
return is_set;
|
||||
}
|
||||
|
||||
private:
|
||||
Maybe<CompId> getComparisonByName(const std::string &name);
|
||||
|
||||
const static std::map<std::string, CompId> name_to_operator;
|
||||
bool is_set = false;
|
||||
CompId comp_val;
|
||||
};
|
||||
|
||||
class SingleKeyword
|
||||
{
|
||||
public:
|
||||
SingleKeyword()
|
||||
{
|
||||
}
|
||||
|
||||
virtual ~SingleKeyword()
|
||||
{
|
||||
}
|
||||
|
||||
MatchStatus runNext(const I_KeywordRuntimeState *curr) const;
|
||||
void appendKeyword(std::unique_ptr<SingleKeyword> &&_next);
|
||||
virtual MatchStatus isMatch(const I_KeywordRuntimeState *prev) const = 0;
|
||||
|
||||
private:
|
||||
std::unique_ptr<SingleKeyword> next;
|
||||
};
|
||||
|
||||
std::unique_ptr<SingleKeyword> genDataKeyword(
|
||||
const std::vector<KeywordAttr> &attr,
|
||||
VariablesMapping &vars
|
||||
);
|
||||
|
||||
std::unique_ptr<SingleKeyword> genPCREKeyword(
|
||||
const std::vector<KeywordAttr> &attr,
|
||||
VariablesMapping &vars
|
||||
);
|
||||
|
||||
std::unique_ptr<SingleKeyword> genLengthKeyword(
|
||||
const std::vector<KeywordAttr> &attr,
|
||||
VariablesMapping &vars
|
||||
);
|
||||
|
||||
std::unique_ptr<SingleKeyword> genByteExtractKeyword(
|
||||
const std::vector<KeywordAttr> &attr,
|
||||
VariablesMapping &vars
|
||||
);
|
||||
|
||||
std::unique_ptr<SingleKeyword> genCompareKeyword(
|
||||
const std::vector<KeywordAttr> &attr,
|
||||
VariablesMapping &vars
|
||||
);
|
||||
|
||||
std::unique_ptr<SingleKeyword> genStateopKeyword(
|
||||
const std::vector<KeywordAttr> &attr,
|
||||
VariablesMapping &vars
|
||||
);
|
||||
|
||||
std::unique_ptr<SingleKeyword> genNoMatchKeyword(
|
||||
const std::vector<KeywordAttr> &attr,
|
||||
VariablesMapping &vars
|
||||
);
|
||||
|
||||
std::unique_ptr<SingleKeyword> genJumpKeyword(
|
||||
const std::vector<KeywordAttr> &attr,
|
||||
VariablesMapping &vars
|
||||
);
|
||||
|
||||
std::unique_ptr<SingleKeyword> getKeywordByName(
|
||||
const KeywordParsed &parsed_data,
|
||||
VariablesMapping &vars
|
||||
);
|
||||
|
||||
#endif // ___SINGLE_KEYWORD_H__
|
145
components/utils/keywords/stateop_keyword.cc
Normal file
145
components/utils/keywords/stateop_keyword.cc
Normal file
@@ -0,0 +1,145 @@
|
||||
#include "single_keyword.h"
|
||||
#include "table_opaque.h"
|
||||
#include "debug.h"
|
||||
#include "flags.h"
|
||||
|
||||
#include <map>
|
||||
#include <strings.h>
|
||||
|
||||
#include "cereal/types/set.hpp"
|
||||
|
||||
using namespace std;
|
||||
|
||||
USE_DEBUG_FLAG(D_KEYWORD);
|
||||
|
||||
class StateopKeyword : public SingleKeyword
|
||||
{
|
||||
public:
|
||||
explicit StateopKeyword(const vector<KeywordAttr> &attr, VariablesMapping &vars);
|
||||
MatchStatus isMatch(const I_KeywordRuntimeState* prev) const override;
|
||||
|
||||
private:
|
||||
enum class Operation { ISSET, SET, UNSET, COUNT };
|
||||
using OpFlags = Flags<Operation>;
|
||||
|
||||
void
|
||||
setState(const KeywordAttr &attr, const VariablesMapping &)
|
||||
{
|
||||
auto &var_name_param = attr.getParams();
|
||||
|
||||
if (var_name_param.size() != 2) {
|
||||
throw KeywordError("More than one element in the state name in the 'stateop' keyword");
|
||||
}
|
||||
|
||||
var_name = var_name_param[1];
|
||||
}
|
||||
|
||||
void
|
||||
setTesting(const KeywordAttr &, const VariablesMapping &)
|
||||
{
|
||||
if (!mode.empty()) throw KeywordError("Redefining 'stateop' keyword operation");
|
||||
mode.setFlag(Operation::ISSET);
|
||||
}
|
||||
|
||||
void
|
||||
setSetting(const KeywordAttr &, const VariablesMapping &)
|
||||
{
|
||||
if (!mode.empty()) throw KeywordError("Redefining 'stateop' keyword operation");
|
||||
mode.setFlag(Operation::SET);
|
||||
}
|
||||
|
||||
void
|
||||
setUnsetting(const KeywordAttr &, const VariablesMapping &)
|
||||
{
|
||||
if (!mode.empty()) throw KeywordError("Redefining 'stateop' keyword operation");
|
||||
mode.setFlag(Operation::UNSET);
|
||||
}
|
||||
|
||||
string var_name;
|
||||
OpFlags mode;
|
||||
|
||||
static const map<string, void(StateopKeyword::*)(const KeywordAttr &, const VariablesMapping &)> setops;
|
||||
};
|
||||
|
||||
const map<string, void(StateopKeyword::*)(const KeywordAttr &, const VariablesMapping &)> StateopKeyword::setops = {
|
||||
{ "isset", &StateopKeyword::setTesting },
|
||||
{ "set", &StateopKeyword::setSetting },
|
||||
{ "unset", &StateopKeyword::setUnsetting },
|
||||
{ "state", &StateopKeyword::setState }
|
||||
};
|
||||
|
||||
StateopKeyword::StateopKeyword(const vector<KeywordAttr> &attrs, VariablesMapping &vars)
|
||||
{
|
||||
if (attrs.size() != 2) throw KeywordError("Invalid number of attributes in the 'stateop' keyword");
|
||||
|
||||
for (uint i = 0; i < attrs.size(); i++) {
|
||||
auto curr = setops.find(attrs[i].getAttrName());
|
||||
if (curr == setops.end()) {
|
||||
throw KeywordError("Unknown attribute '" + attrs[i].getAttrName() + "' in the 'stateop' keyword");
|
||||
}
|
||||
auto set_func = curr->second;
|
||||
(this->*set_func)(attrs[i], vars);
|
||||
}
|
||||
|
||||
if (var_name == "" || mode.empty()) {
|
||||
throw KeywordError("Bad 'stateop' attribute configuration");
|
||||
}
|
||||
}
|
||||
|
||||
class KeywordStateop : public TableOpaqueSerialize<KeywordStateop>
|
||||
{
|
||||
public:
|
||||
KeywordStateop() : TableOpaqueSerialize<KeywordStateop>(this) {}
|
||||
|
||||
bool hasVariable(const string &state) { return states.find(state) != states.end(); }
|
||||
void addVariable(const string &state) { states.insert(state); }
|
||||
void removeVariable(const string &state) { states.erase(state); }
|
||||
|
||||
// LCOV_EXCL_START - sync functions, can only be tested once the sync module exists
|
||||
template <typename T>
|
||||
void
|
||||
serialize(T &ar, uint32_t)
|
||||
{
|
||||
ar(states);
|
||||
}
|
||||
|
||||
static std::string name() { return "KeywordStateop"; }
|
||||
static std::unique_ptr<TableOpaqueBase> prototype() { return std::make_unique<KeywordStateop>(); }
|
||||
static uint currVer() { return 0; }
|
||||
static uint minVer() { return 0; }
|
||||
// LCOV_EXCL_STOP
|
||||
|
||||
private:
|
||||
set<string> states;
|
||||
};
|
||||
|
||||
MatchStatus
|
||||
StateopKeyword::isMatch(const I_KeywordRuntimeState *prev) const
|
||||
{
|
||||
auto table = Singleton::Consume<I_Table>::by<KeywordComp>();
|
||||
|
||||
if (mode.isSet(Operation::ISSET)) {
|
||||
if (!table->hasState<KeywordStateop>()) return MatchStatus::NoMatchFinal;
|
||||
auto &state = table->getState<KeywordStateop>();
|
||||
if (state.hasVariable(var_name)) return runNext(prev);
|
||||
else return MatchStatus::NoMatchFinal;
|
||||
} else if (mode.isSet(Operation::SET)) {
|
||||
if (!table->hasState<KeywordStateop>()) table->createState<KeywordStateop>();
|
||||
table->getState<KeywordStateop>().addVariable(var_name);
|
||||
return runNext(prev);
|
||||
} else if (mode.isSet(Operation::UNSET)) {
|
||||
if (table->hasState<KeywordStateop>()) table->getState<KeywordStateop>().removeVariable(var_name);
|
||||
return runNext(prev);
|
||||
} else {
|
||||
dbgAssert(false) << "Impossible 'stateop' keyword without operation";
|
||||
}
|
||||
|
||||
// If there was no matches and the keyword is effected by other keywords, then we know that the rule won't match
|
||||
return MatchStatus::NoMatchFinal;
|
||||
}
|
||||
|
||||
unique_ptr<SingleKeyword>
|
||||
genStateopKeyword(const vector<KeywordAttr> &attr, VariablesMapping &known_vars)
|
||||
{
|
||||
return make_unique<StateopKeyword>(attr, known_vars);
|
||||
}
|
Reference in New Issue
Block a user