use libmagic_rs::evaluator::{EvaluationContext, evaluate_rules};
use libmagic_rs::parser::ast::StringFlags;
use libmagic_rs::{EvaluationConfig, MagicRule, OffsetSpec, Operator, TypeKind, Value};
fn cfg() -> EvaluationConfig {
EvaluationConfig::default().with_stop_at_first_match(false)
}
fn rule(offset: i64, pattern: &str, flags: StringFlags, msg: &str) -> MagicRule {
MagicRule {
offset: OffsetSpec::Absolute(offset),
typ: TypeKind::String {
max_length: None,
flags,
},
op: Operator::Equal,
value: Value::String(pattern.to_string()),
message: msg.to_string(),
children: vec![],
level: 0,
strength_modifier: None,
value_transform: None,
}
}
#[test]
fn string_c_matches_exfat_any_case() {
let r = rule(
3,
"exfat",
StringFlags::default().with_ignore_lowercase(true),
"ExFAT filesystem",
);
for buf in [
&b"___EXFAT____"[..],
&b"___ExFaT____"[..],
&b"___exfat____"[..],
] {
let mut ctx = EvaluationContext::new(cfg());
let matches = evaluate_rules(std::slice::from_ref(&r), buf, &mut ctx).unwrap();
assert_eq!(matches.len(), 1, "string/c exfat should match {:?}", buf);
}
}
#[test]
fn string_w_matches_python_shebang_with_zero_spaces() {
let r = rule(
0,
"#! /usr/bin/python",
StringFlags::default().with_compact_optional_whitespace(true),
"Python script",
);
let buf = b"#!/usr/bin/python script.py";
let mut ctx = EvaluationContext::new(cfg());
let matches = evaluate_rules(&[r], buf, &mut ctx).unwrap();
assert_eq!(
matches.len(),
1,
"string/w must accept zero file whitespace"
);
}
#[test]
fn string_w_matches_python_shebang_with_multiple_spaces() {
let r = rule(
0,
"#! /usr/bin/python",
StringFlags::default().with_compact_optional_whitespace(true),
"Python script",
);
let buf = b"#! /usr/bin/python script.py";
let mut ctx = EvaluationContext::new(cfg());
let matches = evaluate_rules(&[r], buf, &mut ctx).unwrap();
assert_eq!(
matches.len(),
1,
"string/w must accept multiple file whitespace"
);
}
#[test]
fn string_b_matches_ftcomp_at_offset_24() {
let mut buffer = vec![0u8; 24];
buffer.extend_from_slice(b"FTCOMP_archive_data");
let r = rule(
24,
"FTCOMP",
StringFlags::default().with_bin_test(true),
"FTCOMP compressed archive",
);
let mut ctx = EvaluationContext::new(cfg());
let matches = evaluate_rules(&[r], &buffer, &mut ctx).unwrap();
assert_eq!(matches.len(), 1);
}
#[test]
fn string_capital_t_trims_pattern_whitespace() {
let r = rule(
0,
" hello ",
StringFlags::default().with_trim(true),
"hello",
);
let mut ctx = EvaluationContext::new(cfg());
let matches = evaluate_rules(&[r], b"hello world", &mut ctx).unwrap();
assert_eq!(
matches.len(),
1,
"trim should make ` hello ` match buffer `hello world`"
);
}
#[test]
fn string_f_requires_word_boundary_after_match() {
let r = rule(
0,
"int",
StringFlags::default().with_full_word(true),
"int keyword",
);
{
let mut ctx = EvaluationContext::new(cfg());
let m = evaluate_rules(std::slice::from_ref(&r), b"int x = 0", &mut ctx).unwrap();
assert_eq!(m.len(), 1);
}
{
let mut ctx = EvaluationContext::new(cfg());
let m = evaluate_rules(std::slice::from_ref(&r), b"integer x", &mut ctx).unwrap();
assert!(m.is_empty(), "`int` inside `integer` must NOT match /f");
}
{
let mut ctx = EvaluationContext::new(cfg());
let m = evaluate_rules(&[r], b"int_var", &mut ctx).unwrap();
assert!(m.is_empty(), "underscore is a word char");
}
}
#[test]
fn string_cw_combines_case_fold_and_whitespace_flexibility() {
let r = rule(
0,
"foo bar",
StringFlags::default()
.with_ignore_lowercase(true)
.with_compact_optional_whitespace(true),
"combo",
);
let mut ctx = EvaluationContext::new(cfg());
let m = evaluate_rules(&[r], b"FOOBAR rest", &mut ctx).unwrap();
assert_eq!(
m.len(),
1,
"string/cw should match FOOBAR (case-folded, no whitespace)"
);
}
#[test]
fn string_without_flags_remains_case_sensitive() {
let r = rule(0, "foo", StringFlags::default(), "plain");
let mut ctx = EvaluationContext::new(cfg());
let m = evaluate_rules(&[r], b"FOObar", &mut ctx).unwrap();
assert!(
m.is_empty(),
"default-flag string must still be case-sensitive (no regression)"
);
}
#[test]
fn parse_text_magic_string_b_flag_is_rejected() {
use libmagic_rs::parser::parse_text_magic_file;
let result = parse_text_magic_file("0 string/B FOO bar\n");
assert!(
result.is_err(),
"string/B should be a parse error -- /B is a pstring suffix, not a string flag"
);
}
#[test]
fn string_capital_t_with_all_whitespace_pattern_does_not_match_everything() {
let r = rule(
0,
" ", StringFlags::default().with_trim(true),
"should not match",
);
let mut ctx = EvaluationContext::new(cfg());
let matches = evaluate_rules(std::slice::from_ref(&r), b"any file content", &mut ctx).unwrap();
assert!(
matches.is_empty(),
"string/T with all-whitespace pattern must not match every file"
);
}
#[test]
fn string_capital_t_combined_with_f_enforces_boundary_on_trimmed_core() {
let r = rule(
0,
" int ",
StringFlags::default().with_trim(true).with_full_word(true),
"int keyword",
);
{
let mut ctx = EvaluationContext::new(cfg());
let m = evaluate_rules(std::slice::from_ref(&r), b"int x = 0", &mut ctx).unwrap();
assert_eq!(m.len(), 1, "trimmed 'int' should match with space boundary");
}
{
let mut ctx = EvaluationContext::new(cfg());
let m = evaluate_rules(std::slice::from_ref(&r), b"integer x", &mut ctx).unwrap();
assert!(
m.is_empty(),
"trimmed 'int' must not match inside 'integer' (/f boundary check)"
);
}
}