use super::error::DetectError;
use super::structured_path::PathComponent as StructuredPathComponent;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum ParseError {
UnknownSelector(String),
UnknownOperator(String),
InvalidStructuredPath {
format: String,
path: String,
reason: String,
},
UnknownStructuredFormat { format: String },
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum StructuredSelectorError {
UnknownFormat { format: String },
InvalidPath {
format: String,
path: String,
reason: String,
},
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum AliasError {
UnknownAlias(String),
Structured(StructuredSelectorError),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StringOperator {
Equals, NotEquals, Matches, Contains, In, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumericOperator {
Equals, NotEquals, Greater, GreaterOrEqual, Less, LessOrEqual, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TemporalOperator {
Equals, NotEquals, Before, After, BeforeOrEqual, AfterOrEqual, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EnumOperator {
Equals, NotEquals, In, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum PathComponent {
Full, Name, Stem, Extension, Parent, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StringSelector {
Path(PathComponent),
Contents, }
impl StringSelector {
pub fn canonical_name(&self) -> &'static str {
match self {
StringSelector::Path(PathComponent::Full) => "path",
StringSelector::Path(PathComponent::Name) => "name",
StringSelector::Path(PathComponent::Stem) => "basename",
StringSelector::Path(PathComponent::Extension) => "ext",
StringSelector::Path(PathComponent::Parent) => "dir",
StringSelector::Contents => "content",
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum NumericSelector {
Size, Depth, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TemporalSelector {
Modified, Created, Accessed, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EnumSelector {
Type, }
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum DataFormat {
Yaml,
Json,
Toml,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum SelectorCategory {
String(StringSelector),
Numeric(NumericSelector),
Temporal(TemporalSelector),
Enum(EnumSelector),
StructuredData(DataFormat, Vec<StructuredPathComponent>),
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum TypedSelector {
String(StringSelector, StringOperator),
Numeric(NumericSelector, NumericOperator),
Enum(EnumSelector, EnumOperator),
Temporal(TemporalSelector, TemporalOperator),
StructuredData(DataFormat, Vec<StructuredPathComponent>, StructuredOperator),
StructuredDataString(DataFormat, Vec<StructuredPathComponent>, StringOperator),
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructuredOperator {
Equals, NotEquals, Greater, GreaterOrEqual, Less, LessOrEqual, }
pub fn parse_structured_selector(
s: &str,
) -> Result<Option<(DataFormat, Vec<StructuredPathComponent>)>, StructuredSelectorError> {
let Some((prefix, path_str)) = s.split_once(':') else {
return Ok(None);
};
let (format_name, format) = match prefix.to_lowercase().as_str() {
"yaml" => ("yaml", DataFormat::Yaml),
"json" => ("json", DataFormat::Json),
"toml" => ("toml", DataFormat::Toml),
_ => {
return Err(StructuredSelectorError::UnknownFormat {
format: prefix.to_string(),
});
}
};
let components = super::structured_path::parse_path(path_str).map_err(|e| {
StructuredSelectorError::InvalidPath {
format: format_name.to_string(),
path: path_str.to_string(),
reason: e.to_string(),
}
})?;
Ok(Some((format, components)))
}
pub fn recognize_selector(s: &str) -> Result<SelectorCategory, ParseError> {
match parse_structured_selector(s) {
Ok(Some((format, components))) => {
return Ok(SelectorCategory::StructuredData(format, components));
}
Ok(None) => {
}
Err(StructuredSelectorError::UnknownFormat { format }) => {
return Err(ParseError::UnknownStructuredFormat { format });
}
Err(StructuredSelectorError::InvalidPath {
format,
path,
reason,
}) => {
return Err(ParseError::InvalidStructuredPath {
format,
path,
reason,
});
}
}
match s {
"name" | "filename" => Ok(SelectorCategory::String(StringSelector::Path(
PathComponent::Name,
))),
"basename" | "stem" => Ok(SelectorCategory::String(StringSelector::Path(
PathComponent::Stem,
))),
"ext" | "extension" => Ok(SelectorCategory::String(StringSelector::Path(
PathComponent::Extension,
))),
"path" => Ok(SelectorCategory::String(StringSelector::Path(
PathComponent::Full,
))),
"dir" | "parent" | "directory" => Ok(SelectorCategory::String(StringSelector::Path(
PathComponent::Parent,
))),
"size" | "filesize" | "bytes" => Ok(SelectorCategory::Numeric(NumericSelector::Size)),
"type" | "filetype" => Ok(SelectorCategory::Enum(EnumSelector::Type)),
"depth" => Ok(SelectorCategory::Numeric(NumericSelector::Depth)),
"modified" | "mtime" => Ok(SelectorCategory::Temporal(TemporalSelector::Modified)),
"created" | "ctime" => Ok(SelectorCategory::Temporal(TemporalSelector::Created)),
"accessed" | "atime" => Ok(SelectorCategory::Temporal(TemporalSelector::Accessed)),
"content" | "contents" | "text" => Ok(SelectorCategory::String(StringSelector::Contents)),
_ => Err(ParseError::UnknownSelector(s.to_string())),
}
}
pub fn parse_string_operator(s: &str) -> Result<StringOperator, ParseError> {
let s_lower = s.to_lowercase();
match s_lower.as_str() {
"==" | "=" | "eq" => Ok(StringOperator::Equals),
"!=" | "<>" | "ne" | "neq" => Ok(StringOperator::NotEquals),
"~=" | "=~" | "~" | "matches" | "regex" => Ok(StringOperator::Matches),
"contains" | "has" | "includes" => Ok(StringOperator::Contains),
"in" => Ok(StringOperator::In),
_ => Err(ParseError::UnknownOperator(s.to_string())),
}
}
pub fn parse_numeric_operator(s: &str) -> Result<NumericOperator, ParseError> {
let s_lower = s.to_lowercase();
match s_lower.as_str() {
"==" | "=" | "eq" => Ok(NumericOperator::Equals),
"!=" | "<>" | "ne" | "neq" => Ok(NumericOperator::NotEquals),
">" | "gt" => Ok(NumericOperator::Greater),
">=" | "=>" | "gte" | "ge" => Ok(NumericOperator::GreaterOrEqual),
"<" | "lt" => Ok(NumericOperator::Less),
"<=" | "=<" | "lte" | "le" => Ok(NumericOperator::LessOrEqual),
_ => Err(ParseError::UnknownOperator(s.to_string())),
}
}
pub fn parse_temporal_operator(s: &str) -> Result<TemporalOperator, ParseError> {
let s_lower = s.to_lowercase();
match s_lower.as_str() {
"==" | "=" | "eq" | "on" => Ok(TemporalOperator::Equals),
"!=" | "<>" | "ne" | "neq" => Ok(TemporalOperator::NotEquals),
"<" | "before" | "lt" => Ok(TemporalOperator::Before),
">" | "after" | "gt" => Ok(TemporalOperator::After),
"<=" | "=<" | "lte" | "le" => Ok(TemporalOperator::BeforeOrEqual),
">=" | "=>" | "gte" | "ge" => Ok(TemporalOperator::AfterOrEqual),
_ => Err(ParseError::UnknownOperator(s.to_string())),
}
}
pub fn parse_enum_operator(s: &str) -> Result<EnumOperator, ParseError> {
let s_lower = s.to_lowercase();
match s_lower.as_str() {
"==" | "=" | "eq" => Ok(EnumOperator::Equals),
"!=" | "<>" | "ne" | "neq" => Ok(EnumOperator::NotEquals),
"in" => Ok(EnumOperator::In),
_ => Err(ParseError::UnknownOperator(s.to_string())),
}
}
pub fn parse_structured_operator(s: &str) -> Result<StructuredOperator, ParseError> {
let s_lower = s.to_lowercase();
match s_lower.as_str() {
"==" | "=" | "eq" => Ok(StructuredOperator::Equals),
"!=" | "<>" | "ne" | "neq" => Ok(StructuredOperator::NotEquals),
">" | "gt" => Ok(StructuredOperator::Greater),
">=" | "=>" | "gte" | "ge" => Ok(StructuredOperator::GreaterOrEqual),
"<" | "lt" => Ok(StructuredOperator::Less),
"<=" | "=<" | "lte" | "le" => Ok(StructuredOperator::LessOrEqual),
_ => Err(ParseError::UnknownOperator(s.to_string())),
}
}
pub fn is_string_operator(s: &str) -> bool {
let s_lower = s.to_lowercase();
matches!(
s_lower.as_str(),
"~=" | "=~" | "~" | "matches" | "regex" | "contains"
)
}
pub fn parse_selector_operator(
selector_str: &str,
selector_span: pest::Span,
operator_str: &str,
operator_span: pest::Span,
source: &str,
) -> Result<TypedSelector, DetectError> {
use crate::parser::error::SpanExt;
let selector_category = recognize_selector(selector_str).map_err(|e| match e {
ParseError::InvalidStructuredPath {
format,
path,
reason,
} => DetectError::InvalidStructuredPath {
format,
path,
span: selector_span.to_source_span(),
reason,
src: source.to_string(),
},
ParseError::UnknownStructuredFormat { format } => DetectError::UnknownStructuredFormat {
format,
span: selector_span.to_source_span(),
src: source.to_string(),
suggestions: Some("Valid formats: yaml, json, toml".to_string()),
},
ParseError::UnknownSelector(_) => DetectError::UnknownSelector {
selector: selector_str.to_string(),
span: selector_span.to_source_span(),
src: source.to_string(),
},
ParseError::UnknownOperator(_) => {
unreachable!("recognize_selector should not return UnknownOperator")
}
})?;
let operator_lower = operator_str.to_lowercase();
let is_known_operator = parse_string_operator(&operator_lower).is_ok()
|| parse_numeric_operator(&operator_lower).is_ok()
|| parse_temporal_operator(&operator_lower).is_ok()
|| parse_structured_operator(&operator_lower).is_ok();
match selector_category {
SelectorCategory::Enum(selector) => {
let operator = parse_enum_operator(operator_str).map_err(|_| {
if is_known_operator {
DetectError::IncompatibleOperator {
selector: selector_str.to_string(),
operator: operator_str.to_string(),
selector_span: selector_span.to_source_span(),
operator_span: operator_span.to_source_span(),
src: source.to_string(),
}
} else {
DetectError::UnknownOperator {
operator: operator_str.to_string(),
span: operator_span.to_source_span(),
src: source.to_string(),
}
}
})?;
Ok(TypedSelector::Enum(selector, operator))
}
SelectorCategory::String(selector) => {
let operator = parse_string_operator(operator_str).map_err(|_| {
if is_known_operator {
DetectError::IncompatibleOperator {
selector: selector_str.to_string(),
operator: operator_str.to_string(),
selector_span: selector_span.to_source_span(),
operator_span: operator_span.to_source_span(),
src: source.to_string(),
}
} else {
DetectError::UnknownOperator {
operator: operator_str.to_string(),
span: operator_span.to_source_span(),
src: source.to_string(),
}
}
})?;
if matches!(selector, StringSelector::Contents) {
match operator {
StringOperator::In | StringOperator::NotEquals => {
return Err(DetectError::IncompatibleOperator {
selector: selector_str.to_string(),
operator: operator_str.to_string(),
selector_span: selector_span.to_source_span(),
operator_span: operator_span.to_source_span(),
src: source.to_string(),
});
}
_ => {}
}
}
Ok(TypedSelector::String(selector, operator))
}
SelectorCategory::Numeric(selector) => {
let operator = parse_numeric_operator(operator_str).map_err(|_| {
if is_known_operator {
DetectError::IncompatibleOperator {
selector: selector_str.to_string(),
operator: operator_str.to_string(),
selector_span: selector_span.to_source_span(),
operator_span: operator_span.to_source_span(),
src: source.to_string(),
}
} else {
DetectError::UnknownOperator {
operator: operator_str.to_string(),
span: operator_span.to_source_span(),
src: source.to_string(),
}
}
})?;
Ok(TypedSelector::Numeric(selector, operator))
}
SelectorCategory::Temporal(selector) => {
let operator = parse_temporal_operator(operator_str).map_err(|_| {
if is_known_operator {
DetectError::IncompatibleOperator {
selector: selector_str.to_string(),
operator: operator_str.to_string(),
selector_span: selector_span.to_source_span(),
operator_span: operator_span.to_source_span(),
src: source.to_string(),
}
} else {
DetectError::UnknownOperator {
operator: operator_str.to_string(),
span: operator_span.to_source_span(),
src: source.to_string(),
}
}
})?;
Ok(TypedSelector::Temporal(selector, operator))
}
SelectorCategory::StructuredData(format, components) => {
if is_string_operator(operator_str) {
let string_op = parse_string_operator(operator_str).map_err(|_| {
if is_known_operator {
DetectError::IncompatibleOperator {
selector: selector_str.to_string(),
operator: operator_str.to_string(),
selector_span: selector_span.to_source_span(),
operator_span: operator_span.to_source_span(),
src: source.to_string(),
}
} else {
DetectError::UnknownOperator {
operator: operator_str.to_string(),
span: operator_span.to_source_span(),
src: source.to_string(),
}
}
})?;
Ok(TypedSelector::StructuredDataString(
format, components, string_op,
))
} else {
let operator = parse_structured_operator(operator_str).map_err(|_| {
if is_known_operator {
DetectError::IncompatibleOperator {
selector: selector_str.to_string(),
operator: operator_str.to_string(),
selector_span: selector_span.to_source_span(),
operator_span: operator_span.to_source_span(),
src: source.to_string(),
}
} else {
DetectError::UnknownOperator {
operator: operator_str.to_string(),
span: operator_span.to_source_span(),
src: source.to_string(),
}
}
})?;
Ok(TypedSelector::StructuredData(format, components, operator))
}
}
}
}
impl NumericSelector {
pub fn canonical_name(&self) -> &'static str {
match self {
NumericSelector::Size => "size",
NumericSelector::Depth => "depth",
}
}
}
impl TemporalSelector {
pub fn canonical_name(&self) -> &'static str {
match self {
TemporalSelector::Modified => "modified",
TemporalSelector::Created => "created",
TemporalSelector::Accessed => "accessed",
}
}
}