1use std::collections::HashMap;
2
3use yaml_serde::Value;
4
5use crate::ast::*;
6use crate::condition::parse_condition;
7use crate::error::{Result, SigmaParserError};
8use crate::fieldpath::{ends_with_unescaped, escape_brackets, first_unescaped};
9use crate::value::SigmaValue;
10
11use super::{
12 collect_custom_attributes, get_str, get_str_list, parse_enum_with_warn, parse_logsource,
13 parse_related, parse_sigma_version, val_key,
14};
15
16pub(super) fn parse_detection_rule(value: &Value, warnings: &mut Vec<String>) -> Result<SigmaRule> {
29 let m = value
30 .as_mapping()
31 .ok_or_else(|| SigmaParserError::InvalidRule("Expected a YAML mapping".into()))?;
32
33 let title = get_str(m, "title")
34 .ok_or_else(|| SigmaParserError::MissingField("title".into()))?
35 .to_string();
36
37 let sigma_version = parse_sigma_version(m, warnings);
38
39 let detection_val = m
40 .get(val_key("detection"))
41 .ok_or_else(|| SigmaParserError::MissingField("detection".into()))?;
42 let detection = parse_detections(
43 detection_val,
44 crate::version::array_matching_enabled(sigma_version),
45 )?;
46
47 let logsource = m
48 .get(val_key("logsource"))
49 .map(parse_logsource)
50 .transpose()?
51 .unwrap_or_default();
52
53 let standard_rule_keys: &[&str] = &[
58 "title",
59 "sigma-version",
60 "id",
61 "related",
62 "name",
63 "taxonomy",
64 "status",
65 "description",
66 "license",
67 "author",
68 "references",
69 "date",
70 "modified",
71 "logsource",
72 "detection",
73 "fields",
74 "falsepositives",
75 "level",
76 "tags",
77 "scope",
78 "custom_attributes",
79 ];
80 let custom_attributes = collect_custom_attributes(m, standard_rule_keys);
81
82 Ok(SigmaRule {
83 title,
84 logsource,
85 detection,
86 sigma_version,
87 id: get_str(m, "id").map(|s| s.to_string()),
88 name: get_str(m, "name").map(|s| s.to_string()),
89 related: parse_related(m.get(val_key("related")), warnings),
90 taxonomy: get_str(m, "taxonomy").map(|s| s.to_string()),
91 status: parse_enum_with_warn(get_str(m, "status"), "status", warnings),
92 description: get_str(m, "description").map(|s| s.to_string()),
93 license: get_str(m, "license").map(|s| s.to_string()),
94 author: get_str(m, "author").map(|s| s.to_string()),
95 references: get_str_list(m, "references"),
96 date: get_str(m, "date").map(|s| s.to_string()),
97 modified: get_str(m, "modified").map(|s| s.to_string()),
98 fields: get_str_list(m, "fields"),
99 falsepositives: get_str_list(m, "falsepositives"),
100 level: parse_enum_with_warn(get_str(m, "level"), "level", warnings),
101 tags: get_str_list(m, "tags"),
102 scope: get_str_list(m, "scope"),
103 custom_attributes,
104 })
105}
106
107pub(super) fn parse_detections(value: &Value, array_matching: bool) -> Result<Detections> {
120 let m = value.as_mapping().ok_or_else(|| {
121 SigmaParserError::InvalidDetection("Detection section must be a mapping".into())
122 })?;
123
124 let condition_val = m
126 .get(val_key("condition"))
127 .ok_or_else(|| SigmaParserError::MissingField("condition".into()))?;
128
129 let condition_strings = match condition_val {
130 Value::String(s) => vec![s.clone()],
131 Value::Sequence(seq) => {
132 let mut strings = Vec::with_capacity(seq.len());
133 for v in seq {
134 match v.as_str() {
135 Some(s) => strings.push(s.to_string()),
136 None => {
137 return Err(SigmaParserError::InvalidDetection(format!(
138 "condition list items must be strings, got: {v:?}"
139 )));
140 }
141 }
142 }
143 strings
144 }
145 _ => {
146 return Err(SigmaParserError::InvalidDetection(
147 "condition must be a string or list of strings".into(),
148 ));
149 }
150 };
151
152 let conditions: Vec<ConditionExpr> = condition_strings
154 .iter()
155 .map(|s| parse_condition(s))
156 .collect::<Result<Vec<_>>>()?;
157
158 let timeframe = get_str(m, "timeframe").map(|s| s.to_string());
160
161 let mut named = HashMap::new();
163 for (key, val) in m {
164 let key_str = key.as_str().unwrap_or("");
165 if key_str == "condition" || key_str == "timeframe" {
166 continue;
167 }
168 named.insert(key_str.to_string(), parse_detection(val, array_matching)?);
169 }
170
171 Ok(Detections {
172 named,
173 conditions,
174 condition_strings,
175 timeframe,
176 })
177}
178
179fn parse_detection(value: &Value, array_matching: bool) -> Result<Detection> {
188 match value {
189 Value::Mapping(m) => {
190 let mut items: Vec<DetectionItem> = Vec::new();
199 let mut blocks: Vec<Detection> = Vec::new();
200 for (k, v) in m.iter() {
201 match parse_map_entry(k.as_str().unwrap_or(""), v, array_matching)? {
202 ParsedEntry::Item(item) => items.push(item),
203 ParsedEntry::Block(block) => blocks.push(block),
204 }
205 }
206 Ok(combine_entries(items, blocks))
207 }
208 Value::Sequence(seq) => {
209 let all_plain = seq.iter().all(|v| !v.is_mapping() && !v.is_sequence());
211 if all_plain {
212 let values = seq.iter().map(SigmaValue::from_yaml).collect();
214 Ok(Detection::Keywords(values))
215 } else {
216 let subs: Vec<Detection> = seq
218 .iter()
219 .map(|v| parse_detection(v, array_matching))
220 .collect::<Result<Vec<_>>>()?;
221 Ok(Detection::AnyOf(subs))
222 }
223 }
224 _ => Ok(Detection::Keywords(vec![SigmaValue::from_yaml(value)])),
226 }
227}
228
229fn parse_detection_item(key: &str, value: &Value) -> Result<DetectionItem> {
238 let field = parse_field_spec(key)?;
239
240 let values = match value {
241 Value::Sequence(seq) => seq.iter().map(|v| to_sigma_value(v, &field)).collect(),
242 _ => vec![to_sigma_value(value, &field)],
243 };
244
245 Ok(DetectionItem { field, values })
246}
247
248struct PathSegment {
270 name: String,
271 index: Option<i64>,
274 quantifier: Option<ArrayQuantifier>,
276}
277
278impl PathSegment {
279 fn path_str(&self) -> String {
283 match self.index {
284 Some(i) => format!("{}[{i}]", self.name),
285 None => self.name.clone(),
286 }
287 }
288}
289
290enum ParsedEntry {
293 Item(DetectionItem),
294 Block(Detection),
295}
296
297fn combine_entries(items: Vec<DetectionItem>, blocks: Vec<Detection>) -> Detection {
301 if blocks.is_empty() {
302 Detection::AllOf(items)
303 } else if items.is_empty() && blocks.len() == 1 {
304 blocks.into_iter().next().expect("len checked")
305 } else {
306 let mut parts: Vec<Detection> = Vec::new();
307 if !items.is_empty() {
308 parts.push(Detection::AllOf(items));
309 }
310 parts.extend(blocks);
311 Detection::And(parts)
312 }
313}
314
315fn parse_map_entry(key: &str, value: &Value, array_matching: bool) -> Result<ParsedEntry> {
318 let (field_part, modifier_part) = match key.split_once('|') {
320 Some((f, m)) => (f, Some(m)),
321 None => (key, None),
322 };
323
324 if field_part.is_empty() {
327 return Ok(ParsedEntry::Item(parse_detection_item(key, value)?));
328 }
329
330 if !array_matching {
335 let escaped = escape_brackets(field_part);
336 let plain_key = match modifier_part {
337 Some(m) => format!("{escaped}|{m}"),
338 None => escaped.into_owned(),
339 };
340 return Ok(ParsedEntry::Item(parse_detection_item(&plain_key, value)?));
341 }
342
343 let segments = parse_field_path(field_part)?;
344 match segments.iter().position(|s| s.quantifier.is_some()) {
345 Some(idx) => {
346 let quantifier = segments[idx]
347 .quantifier
348 .expect("position found a quantifier");
349 let array_field = segments[..=idx]
352 .iter()
353 .map(PathSegment::path_str)
354 .collect::<Vec<_>>()
355 .join(".");
356 let body =
357 build_block_body(&segments[idx + 1..], modifier_part, value, array_matching)?;
358 Ok(ParsedEntry::Block(Detection::ArrayMatch {
359 field: array_field,
360 quantifier,
361 body: Box::new(body),
362 }))
363 }
364 None => {
368 let has_index = segments.iter().any(|s| s.index.is_some());
369 if value.is_mapping() && has_index {
370 let prefix = reconstruct_key(&segments, None);
371 Ok(ParsedEntry::Block(parse_block_with_prefix(
372 &prefix,
373 value,
374 array_matching,
375 )?))
376 } else {
377 Ok(ParsedEntry::Item(parse_detection_item(key, value)?))
378 }
379 }
380 }
381}
382
383fn build_block_body(
385 remaining: &[PathSegment],
386 modifier_part: Option<&str>,
387 value: &Value,
388 array_matching: bool,
389) -> Result<Detection> {
390 if remaining.is_empty() {
391 match value {
393 Value::Mapping(m) => {
395 if modifier_part.is_some() {
396 return Err(SigmaParserError::InvalidFieldSpec(
397 "value modifiers cannot be applied to an array object-scope block; \
398 move the modifier onto a field inside the block"
399 .into(),
400 ));
401 }
402 if m.iter().any(|(k, _)| k.as_str() == Some("condition")) {
406 parse_extended_block_body(value, array_matching)
407 } else {
408 parse_detection(value, array_matching)
409 }
410 }
411 _ => {
414 let modifiers = parse_modifiers(modifier_part)?;
415 let field = FieldSpec::new(None, modifiers);
416 let values = match value {
417 Value::Sequence(seq) => seq.iter().map(|v| to_sigma_value(v, &field)).collect(),
418 _ => vec![to_sigma_value(value, &field)],
419 };
420 Ok(Detection::AllOf(vec![DetectionItem { field, values }]))
421 }
422 }
423 } else if value.is_mapping() {
424 let prefix = reconstruct_key(remaining, None);
427 parse_block_with_prefix(&prefix, value, array_matching)
428 } else {
429 let remaining_key = reconstruct_key(remaining, modifier_part);
432 match parse_map_entry(&remaining_key, value, array_matching)? {
433 ParsedEntry::Item(item) => Ok(Detection::AllOf(vec![item])),
434 ParsedEntry::Block(block) => Ok(block),
435 }
436 }
437}
438
439fn parse_extended_block_body(value: &Value, array_matching: bool) -> Result<Detection> {
443 let m = value.as_mapping().ok_or_else(|| {
444 SigmaParserError::InvalidDetection("extended array block body must be a mapping".into())
445 })?;
446 let mut named: HashMap<String, Detection> = HashMap::new();
447 let mut condition: Option<ConditionExpr> = None;
448 for (k, v) in m.iter() {
449 let key = k.as_str().ok_or_else(|| {
450 SigmaParserError::InvalidDetection("non-string key in array block body".into())
451 })?;
452 if key == "condition" {
453 condition = Some(parse_block_condition(v)?);
454 } else {
455 named.insert(key.to_string(), parse_detection(v, array_matching)?);
456 }
457 }
458 let condition = condition.ok_or_else(|| {
459 SigmaParserError::InvalidDetection("extended array block requires a 'condition'".into())
460 })?;
461 if named.is_empty() {
462 return Err(SigmaParserError::InvalidDetection(
463 "extended array block has a 'condition' but no named sub-selections".into(),
464 ));
465 }
466 Ok(Detection::Conditional { named, condition })
467}
468
469fn parse_block_condition(value: &Value) -> Result<ConditionExpr> {
472 match value {
473 Value::String(s) => parse_condition(s),
474 Value::Sequence(seq) => {
475 let exprs = seq
476 .iter()
477 .map(|x| {
478 let s = x.as_str().ok_or_else(|| {
479 SigmaParserError::InvalidDetection(
480 "array block 'condition' list items must be strings".into(),
481 )
482 })?;
483 parse_condition(s)
484 })
485 .collect::<Result<Vec<_>>>()?;
486 Ok(ConditionExpr::Or(exprs))
487 }
488 _ => Err(SigmaParserError::InvalidDetection(
489 "array block 'condition' must be a string or list of strings".into(),
490 )),
491 }
492}
493
494fn parse_block_with_prefix(prefix: &str, value: &Value, array_matching: bool) -> Result<Detection> {
497 let m = value.as_mapping().ok_or_else(|| {
498 SigmaParserError::InvalidDetection("array block body must be a mapping".into())
499 })?;
500 let mut items: Vec<DetectionItem> = Vec::new();
501 let mut blocks: Vec<Detection> = Vec::new();
502 for (k, v) in m.iter() {
503 let sub = k.as_str().unwrap_or("");
504 let key = format!("{prefix}.{sub}");
505 match parse_map_entry(&key, v, array_matching)? {
506 ParsedEntry::Item(item) => items.push(item),
507 ParsedEntry::Block(block) => blocks.push(block),
508 }
509 }
510 Ok(combine_entries(items, blocks))
511}
512
513fn parse_field_path(field_part: &str) -> Result<Vec<PathSegment>> {
521 let mut segments = Vec::new();
522 for raw in field_part.split('.') {
523 if let Some(open) = first_unescaped(raw, b'[')
527 && ends_with_unescaped(raw, b']')
528 {
529 let name = &raw[..open];
530 let token = &raw[open + 1..raw.len() - 1];
531 if name.is_empty() {
532 return Err(SigmaParserError::InvalidFieldSpec(format!(
533 "array selector without a field name in '{field_part}'"
534 )));
535 }
536 let (index, quantifier) = match token {
537 "any" => (None, Some(ArrayQuantifier::Any)),
538 "all" => (None, Some(ArrayQuantifier::All)),
539 "all_or_empty" => (None, Some(ArrayQuantifier::AllOrEmpty)),
540 "none" => (None, Some(ArrayQuantifier::None)),
541 _ => match token.parse::<i64>() {
542 Ok(n) => (Some(n), None),
543 Err(_) => {
544 return Err(SigmaParserError::InvalidFieldSpec(format!(
545 "unknown array selector '[{token}]' in field '{field_part}'; \
546 only [any], [all], [all_or_empty], [none], and an integer index \
547 [N] (negative counts from the end) are supported; \
548 escape a literal bracket as \\[ or \\]"
549 )));
550 }
551 },
552 };
553 segments.push(PathSegment {
554 name: name.to_string(),
555 index,
556 quantifier,
557 });
558 } else {
559 segments.push(PathSegment {
560 name: raw.to_string(),
561 index: None,
562 quantifier: None,
563 });
564 }
565 }
566 Ok(segments)
567}
568
569fn parse_modifiers(modifier_part: Option<&str>) -> Result<Vec<Modifier>> {
571 let mut modifiers = Vec::new();
572 if let Some(part) = modifier_part {
573 for mod_str in part.split('|') {
574 if mod_str == "not" {
575 return Err(SigmaParserError::NotIsNotAModifier);
576 }
577 let m = mod_str
578 .parse::<Modifier>()
579 .map_err(|_| SigmaParserError::UnknownModifier(mod_str.to_string()))?;
580 modifiers.push(m);
581 }
582 }
583 Ok(modifiers)
584}
585
586fn reconstruct_key(segments: &[PathSegment], modifier_part: Option<&str>) -> String {
589 let path = segments
590 .iter()
591 .map(|s| match s.quantifier {
592 Some(q) => format!("{}[{q}]", s.name),
593 None => s.path_str(),
594 })
595 .collect::<Vec<_>>()
596 .join(".");
597 match modifier_part {
598 Some(m) => format!("{path}|{m}"),
599 None => path,
600 }
601}
602
603fn to_sigma_value(v: &Value, field: &FieldSpec) -> SigmaValue {
607 if field.has_modifier(Modifier::Re)
608 && let Value::String(s) = v
609 {
610 return SigmaValue::from_raw_string(s);
611 }
612 SigmaValue::from_yaml(v)
613}
614
615pub fn parse_field_spec(key: &str) -> Result<FieldSpec> {
619 if key.is_empty() {
620 return Ok(FieldSpec::new(None, Vec::new()));
621 }
622
623 let parts: Vec<&str> = key.split('|').collect();
624 let field_name = parts[0];
625 let field = if field_name.is_empty() || field_name == "." {
631 None
632 } else {
633 Some(field_name.to_string())
634 };
635
636 let mut modifiers = Vec::new();
637 for &mod_str in &parts[1..] {
638 if mod_str == "not" {
642 return Err(SigmaParserError::NotIsNotAModifier);
643 }
644 let m = mod_str
645 .parse::<Modifier>()
646 .map_err(|_| SigmaParserError::UnknownModifier(mod_str.to_string()))?;
647 modifiers.push(m);
648 }
649
650 Ok(FieldSpec::new(field, modifiers))
651}