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::validate::{check_condition, check_detection, check_detection_item};
10use crate::value::SigmaValue;
11
12use super::{
13 collect_custom_attributes, get_str, get_str_list, parse_enum_with_warn, parse_logsource,
14 parse_related, parse_sigma_version, val_key,
15};
16
17pub(super) fn parse_detection_rule(value: &Value, warnings: &mut Vec<String>) -> Result<SigmaRule> {
30 let m = value
31 .as_mapping()
32 .ok_or_else(|| SigmaParserError::InvalidRule("Expected a YAML mapping".into()))?;
33
34 let title = get_str(m, "title")
35 .ok_or_else(|| SigmaParserError::MissingField("title".into()))?
36 .to_string();
37
38 let sigma_version = parse_sigma_version(m, warnings);
39
40 let detection_val = m
41 .get(val_key("detection"))
42 .ok_or_else(|| SigmaParserError::MissingField("detection".into()))?;
43 let detection = parse_detections(
44 detection_val,
45 crate::version::array_matching_enabled(sigma_version),
46 )?;
47
48 let logsource = parse_logsource(
49 m.get(val_key("logsource"))
50 .ok_or_else(|| SigmaParserError::MissingField("logsource".into()))?,
51 )?;
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 if seq.is_empty() {
133 return Err(SigmaParserError::InvalidDetection(
134 "condition list must not be empty".into(),
135 ));
136 }
137 let mut strings = Vec::with_capacity(seq.len());
138 for v in seq {
139 match v.as_str() {
140 Some(s) => strings.push(s.to_string()),
141 None => {
142 return Err(SigmaParserError::InvalidDetection(format!(
143 "condition list items must be strings, got: {v:?}"
144 )));
145 }
146 }
147 }
148 strings
149 }
150 _ => {
151 return Err(SigmaParserError::InvalidDetection(
152 "condition must be a string or list of strings".into(),
153 ));
154 }
155 };
156
157 let conditions: Vec<ConditionExpr> = condition_strings
159 .iter()
160 .map(|s| parse_condition(s))
161 .collect::<Result<Vec<_>>>()?;
162
163 let timeframe = get_str(m, "timeframe").map(|s| s.to_string());
165
166 let mut named = HashMap::new();
168 for (key, val) in m {
169 let key_str = key.as_str().unwrap_or("");
170 if key_str == "condition" || key_str == "timeframe" {
171 continue;
172 }
173 let detection = parse_detection(val, array_matching)?;
174 check_detection(key_str, &detection)?;
175 named.insert(key_str.to_string(), detection);
176 }
177
178 let names: Vec<&str> = named.keys().map(String::as_str).collect();
179 for condition in &conditions {
180 check_condition(condition, &names)?;
181 }
182
183 Ok(Detections {
184 named,
185 conditions,
186 condition_strings,
187 timeframe,
188 })
189}
190
191fn parse_detection(value: &Value, array_matching: bool) -> Result<Detection> {
200 match value {
201 Value::Mapping(m) => {
202 let mut items: Vec<DetectionItem> = Vec::new();
211 let mut blocks: Vec<Detection> = Vec::new();
212 for (k, v) in m.iter() {
213 match parse_map_entry(k.as_str().unwrap_or(""), v, array_matching)? {
214 ParsedEntry::Item(item) => items.push(item),
215 ParsedEntry::Block(block) => blocks.push(block),
216 }
217 }
218 Ok(combine_entries(items, blocks))
219 }
220 Value::Sequence(seq) => {
221 if seq.iter().any(Value::is_sequence) {
222 return Err(SigmaParserError::InvalidDetection(
223 "a detection list must not contain nested lists".into(),
224 ));
225 }
226 let all_plain = seq.iter().all(|v| !v.is_mapping() && !v.is_sequence());
228 if all_plain {
229 let values = seq.iter().map(SigmaValue::from_yaml).collect();
231 Ok(Detection::Keywords(values))
232 } else {
233 let subs: Vec<Detection> = seq
235 .iter()
236 .map(|v| parse_detection(v, array_matching))
237 .collect::<Result<Vec<_>>>()?;
238 Ok(Detection::AnyOf(subs))
239 }
240 }
241 _ => Ok(Detection::Keywords(vec![SigmaValue::from_yaml(value)])),
243 }
244}
245
246fn parse_detection_item(key: &str, value: &Value) -> Result<DetectionItem> {
255 build_item(parse_field_spec(key)?, value)
256}
257
258fn build_item(field: FieldSpec, value: &Value) -> Result<DetectionItem> {
261 let scalars = match value {
262 Value::Sequence(seq) => seq.as_slice(),
263 _ => std::slice::from_ref(value),
264 };
265 if scalars.iter().any(|v| v.is_sequence() || v.is_mapping()) {
266 let subject = field.name.as_deref().unwrap_or("keyword");
267 return Err(SigmaParserError::InvalidValue(format!(
268 "'{subject}' takes a value or a list of values, not a nested list or mapping"
269 )));
270 }
271 let values = scalars.iter().map(|v| to_sigma_value(v, &field)).collect();
272 let item = DetectionItem { field, values };
273 check_detection_item(&item)?;
274 Ok(item)
275}
276
277struct PathSegment {
299 name: String,
300 index: Option<i64>,
303 quantifier: Option<ArrayQuantifier>,
305}
306
307impl PathSegment {
308 fn path_str(&self) -> String {
312 match self.index {
313 Some(i) => format!("{}[{i}]", self.name),
314 None => self.name.clone(),
315 }
316 }
317}
318
319enum ParsedEntry {
322 Item(DetectionItem),
323 Block(Detection),
324}
325
326fn combine_entries(items: Vec<DetectionItem>, blocks: Vec<Detection>) -> Detection {
330 if blocks.is_empty() {
331 Detection::AllOf(items)
332 } else if items.is_empty() && blocks.len() == 1 {
333 blocks.into_iter().next().expect("len checked")
334 } else {
335 let mut parts: Vec<Detection> = Vec::new();
336 if !items.is_empty() {
337 parts.push(Detection::AllOf(items));
338 }
339 parts.extend(blocks);
340 Detection::And(parts)
341 }
342}
343
344fn parse_map_entry(key: &str, value: &Value, array_matching: bool) -> Result<ParsedEntry> {
347 let (field_part, modifier_part) = match key.split_once('|') {
349 Some((f, m)) => (f, Some(m)),
350 None => (key, None),
351 };
352
353 if field_part.is_empty() {
356 return Ok(ParsedEntry::Item(parse_detection_item(key, value)?));
357 }
358
359 if !array_matching {
364 let escaped = escape_brackets(field_part);
365 let plain_key = match modifier_part {
366 Some(m) => format!("{escaped}|{m}"),
367 None => escaped.into_owned(),
368 };
369 return Ok(ParsedEntry::Item(parse_detection_item(&plain_key, value)?));
370 }
371
372 let segments = parse_field_path(field_part)?;
373 match segments.iter().position(|s| s.quantifier.is_some()) {
374 Some(idx) => {
375 let quantifier = segments[idx]
376 .quantifier
377 .expect("position found a quantifier");
378 let array_field = segments[..=idx]
381 .iter()
382 .map(PathSegment::path_str)
383 .collect::<Vec<_>>()
384 .join(".");
385 let body =
386 build_block_body(&segments[idx + 1..], modifier_part, value, array_matching)?;
387 Ok(ParsedEntry::Block(Detection::ArrayMatch {
388 field: array_field,
389 quantifier,
390 body: Box::new(body),
391 }))
392 }
393 None => {
397 let has_index = segments.iter().any(|s| s.index.is_some());
398 if value.is_mapping() && has_index {
399 let prefix = reconstruct_key(&segments, None);
400 Ok(ParsedEntry::Block(parse_block_with_prefix(
401 &prefix,
402 value,
403 array_matching,
404 )?))
405 } else {
406 Ok(ParsedEntry::Item(parse_detection_item(key, value)?))
407 }
408 }
409 }
410}
411
412fn build_block_body(
414 remaining: &[PathSegment],
415 modifier_part: Option<&str>,
416 value: &Value,
417 array_matching: bool,
418) -> Result<Detection> {
419 if remaining.is_empty() {
420 match value {
422 Value::Mapping(m) => {
424 if modifier_part.is_some() {
425 return Err(SigmaParserError::InvalidFieldSpec(
426 "value modifiers cannot be applied to an array object-scope block; \
427 move the modifier onto a field inside the block"
428 .into(),
429 ));
430 }
431 if m.iter().any(|(k, _)| k.as_str() == Some("condition")) {
435 parse_extended_block_body(value, array_matching)
436 } else {
437 parse_detection(value, array_matching)
438 }
439 }
440 _ => {
443 let field = FieldSpec::new(None, parse_modifiers(modifier_part)?);
444 Ok(Detection::AllOf(vec![build_item(field, value)?]))
445 }
446 }
447 } else if value.is_mapping() {
448 let prefix = reconstruct_key(remaining, None);
451 parse_block_with_prefix(&prefix, value, array_matching)
452 } else {
453 let remaining_key = reconstruct_key(remaining, modifier_part);
456 match parse_map_entry(&remaining_key, value, array_matching)? {
457 ParsedEntry::Item(item) => Ok(Detection::AllOf(vec![item])),
458 ParsedEntry::Block(block) => Ok(block),
459 }
460 }
461}
462
463fn parse_extended_block_body(value: &Value, array_matching: bool) -> Result<Detection> {
467 let m = value.as_mapping().ok_or_else(|| {
468 SigmaParserError::InvalidDetection("extended array block body must be a mapping".into())
469 })?;
470 let mut named: HashMap<String, Detection> = HashMap::new();
471 let mut condition: Option<ConditionExpr> = None;
472 for (k, v) in m.iter() {
473 let key = k.as_str().ok_or_else(|| {
474 SigmaParserError::InvalidDetection("non-string key in array block body".into())
475 })?;
476 if key == "condition" {
477 condition = Some(parse_block_condition(v)?);
478 } else {
479 named.insert(key.to_string(), parse_detection(v, array_matching)?);
480 }
481 }
482 let condition = condition.ok_or_else(|| {
483 SigmaParserError::InvalidDetection("extended array block requires a 'condition'".into())
484 })?;
485 if named.is_empty() {
486 return Err(SigmaParserError::InvalidDetection(
487 "extended array block has a 'condition' but no named sub-selections".into(),
488 ));
489 }
490 Ok(Detection::Conditional { named, condition })
491}
492
493fn parse_block_condition(value: &Value) -> Result<ConditionExpr> {
496 match value {
497 Value::String(s) => parse_condition(s),
498 Value::Sequence(seq) => {
499 if seq.is_empty() {
500 return Err(SigmaParserError::InvalidDetection(
501 "array block 'condition' list must not be empty".into(),
502 ));
503 }
504 let exprs = seq
505 .iter()
506 .map(|x| {
507 let s = x.as_str().ok_or_else(|| {
508 SigmaParserError::InvalidDetection(
509 "array block 'condition' list items must be strings".into(),
510 )
511 })?;
512 parse_condition(s)
513 })
514 .collect::<Result<Vec<_>>>()?;
515 Ok(ConditionExpr::Or(exprs))
516 }
517 _ => Err(SigmaParserError::InvalidDetection(
518 "array block 'condition' must be a string or list of strings".into(),
519 )),
520 }
521}
522
523fn parse_block_with_prefix(prefix: &str, value: &Value, array_matching: bool) -> Result<Detection> {
526 let m = value.as_mapping().ok_or_else(|| {
527 SigmaParserError::InvalidDetection("array block body must be a mapping".into())
528 })?;
529 let mut items: Vec<DetectionItem> = Vec::new();
530 let mut blocks: Vec<Detection> = Vec::new();
531 for (k, v) in m.iter() {
532 let sub = k.as_str().unwrap_or("");
533 let key = format!("{prefix}.{sub}");
534 match parse_map_entry(&key, v, array_matching)? {
535 ParsedEntry::Item(item) => items.push(item),
536 ParsedEntry::Block(block) => blocks.push(block),
537 }
538 }
539 Ok(combine_entries(items, blocks))
540}
541
542fn parse_field_path(field_part: &str) -> Result<Vec<PathSegment>> {
550 let mut segments = Vec::new();
551 for raw in field_part.split('.') {
552 if let Some(open) = first_unescaped(raw, b'[')
556 && ends_with_unescaped(raw, b']')
557 {
558 let name = &raw[..open];
559 let token = &raw[open + 1..raw.len() - 1];
560 if name.is_empty() {
561 return Err(SigmaParserError::InvalidFieldSpec(format!(
562 "array selector without a field name in '{field_part}'"
563 )));
564 }
565 let (index, quantifier) = match token {
566 "any" => (None, Some(ArrayQuantifier::Any)),
567 "all" => (None, Some(ArrayQuantifier::All)),
568 "all_or_empty" => (None, Some(ArrayQuantifier::AllOrEmpty)),
569 "none" => (None, Some(ArrayQuantifier::None)),
570 _ => match token.parse::<i64>() {
571 Ok(n) => (Some(n), None),
572 Err(_) => {
573 return Err(SigmaParserError::InvalidFieldSpec(format!(
574 "unknown array selector '[{token}]' in field '{field_part}'; \
575 only [any], [all], [all_or_empty], [none], and an integer index \
576 [N] (negative counts from the end) are supported; \
577 escape a literal bracket as \\[ or \\]"
578 )));
579 }
580 },
581 };
582 segments.push(PathSegment {
583 name: name.to_string(),
584 index,
585 quantifier,
586 });
587 } else {
588 segments.push(PathSegment {
589 name: raw.to_string(),
590 index: None,
591 quantifier: None,
592 });
593 }
594 }
595 Ok(segments)
596}
597
598fn parse_modifiers(modifier_part: Option<&str>) -> Result<Vec<Modifier>> {
600 let mut modifiers = Vec::new();
601 if let Some(part) = modifier_part {
602 for mod_str in part.split('|') {
603 if mod_str == "not" {
607 return Err(SigmaParserError::NotIsNotAModifier);
608 }
609 let m = mod_str
610 .parse::<Modifier>()
611 .map_err(|_| SigmaParserError::UnknownModifier(mod_str.to_string()))?;
612 if modifiers.contains(&m) {
613 return Err(SigmaParserError::DuplicateModifier(mod_str.to_string()));
614 }
615 modifiers.push(m);
616 }
617 }
618 Ok(modifiers)
619}
620
621fn reconstruct_key(segments: &[PathSegment], modifier_part: Option<&str>) -> String {
624 let path = segments
625 .iter()
626 .map(|s| match s.quantifier {
627 Some(q) => format!("{}[{q}]", s.name),
628 None => s.path_str(),
629 })
630 .collect::<Vec<_>>()
631 .join(".");
632 match modifier_part {
633 Some(m) => format!("{path}|{m}"),
634 None => path,
635 }
636}
637
638fn to_sigma_value(v: &Value, field: &FieldSpec) -> SigmaValue {
642 if field.has_modifier(Modifier::Re)
643 && let Value::String(s) = v
644 {
645 return SigmaValue::from_raw_string(s);
646 }
647 SigmaValue::from_yaml(v)
648}
649
650pub fn parse_field_spec(key: &str) -> Result<FieldSpec> {
654 if key.is_empty() {
655 return Ok(FieldSpec::new(None, Vec::new()));
656 }
657
658 let (field_name, modifier_part) = match key.split_once('|') {
659 Some((f, m)) => (f, Some(m)),
660 None => (key, None),
661 };
662 let field = if field_name.is_empty() || field_name == "." {
668 None
669 } else {
670 Some(field_name.to_string())
671 };
672
673 Ok(FieldSpec::new(field, parse_modifiers(modifier_part)?))
674}