use super::*;
use crate::parser::ast::StringFlags;
fn make_flagged_string_rule(pattern: &str, flags: StringFlags, op: Operator) -> MagicRule {
MagicRule {
offset: OffsetSpec::Absolute(0),
typ: TypeKind::String {
max_length: None,
flags,
},
op,
value: Value::String(pattern.to_string()),
message: format!("matched {pattern}"),
children: vec![],
level: 0,
strength_modifier: None,
value_transform: None,
}
}
#[test]
fn test_flagged_string_c_case_insensitive_matches_via_engine() {
let rule = make_flagged_string_rule(
"foo",
StringFlags::default().with_ignore_lowercase(true),
Operator::Equal,
);
let mut context = EvaluationContext::new(EvaluationConfig::default());
let matches = evaluate_rules(&[rule], b"FOObar", &mut context).unwrap();
assert_eq!(matches.len(), 1, "string/c foo should match FOObar");
assert_eq!(matches[0].message, "matched foo");
}
#[test]
fn test_flagged_string_c_does_not_match_when_uppercase_pattern_position_differs() {
let rule = make_flagged_string_rule(
"FoO",
StringFlags::default().with_ignore_lowercase(true),
Operator::Equal,
);
let mut context = EvaluationContext::new(EvaluationConfig::default());
let matches = evaluate_rules(&[rule], b"fOOrest", &mut context).unwrap();
assert!(
matches.is_empty(),
"string/c FoO must not match fOO (asymmetric /c contract)"
);
}
#[test]
fn test_flagged_string_default_flags_use_value_rule_fast_path() {
let rule = make_flagged_string_rule("foo", StringFlags::default(), Operator::Equal);
let mut context = EvaluationContext::new(EvaluationConfig::default());
let matches = evaluate_rules(&[rule], b"FOObar", &mut context).unwrap();
assert!(
matches.is_empty(),
"default-flag string must use case-sensitive matching"
);
}
#[test]
fn test_flagged_string_not_equal_inverts_match() {
let rule = make_flagged_string_rule(
"xyz",
StringFlags::default().with_ignore_lowercase(true),
Operator::NotEqual,
);
let mut context = EvaluationContext::new(EvaluationConfig::default());
let matches = evaluate_rules(&[rule], b"FOObar", &mut context).unwrap();
assert_eq!(matches.len(), 1, "NotEqual fires when pattern misses");
}
#[test]
fn test_flagged_string_ordering_operator_is_rejected() {
let rule = make_flagged_string_rule(
"foo",
StringFlags::default().with_ignore_lowercase(true),
Operator::GreaterThan,
);
let mut context = EvaluationContext::new(EvaluationConfig::default());
let result = evaluate_single_rule(&rule, b"FOObar", &mut context);
assert!(
matches!(result, Err(LibmagicError::EvaluationError(_))),
"expected EvaluationError for ordering operator on flagged string"
);
}
#[test]
fn test_flagged_string_w_whitespace_consumes_extra_file_bytes_for_anchor() {
let child = MagicRule {
offset: OffsetSpec::Relative(0),
typ: TypeKind::Byte { signed: false },
op: Operator::Equal,
value: Value::Uint(b'!'.into()),
message: "exclaim".to_string(),
children: vec![],
level: 1,
strength_modifier: None,
value_transform: None,
};
let parent = MagicRule {
offset: OffsetSpec::Absolute(0),
typ: TypeKind::String {
max_length: None,
flags: StringFlags::default().with_compact_whitespace(true),
},
op: Operator::Equal,
value: Value::String("a b".to_string()),
message: "match".to_string(),
children: vec![child],
level: 0,
strength_modifier: None,
value_transform: None,
};
let mut context =
EvaluationContext::new(EvaluationConfig::default().with_stop_at_first_match(false));
let matches = evaluate_rules(&[parent], b"a b!", &mut context).unwrap();
assert_eq!(matches.len(), 2, "parent + child must both match");
assert_eq!(matches[1].message, "exclaim");
}