use assert_cmd::Command;
use libmagic_rs::EvaluationConfig;
use std::fs;
use tempfile::TempDir;
#[test]
fn test_cli_rejects_planted_missing_magic_in_cwd() {
let dir = TempDir::new().unwrap();
fs::write(
dir.path().join("missing.magic"),
"0 string TEST planted-magic-pwn\n",
)
.unwrap();
let target = dir.path().join("target.bin");
fs::write(&target, b"TEST").unwrap();
let out = Command::cargo_bin("rmagic")
.unwrap()
.current_dir(dir.path())
.arg(target.file_name().unwrap())
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stdout.contains("planted-magic-pwn"),
"CLI resolved planted ./missing.magic from cwd (S-H1 regression)\n\
stdout: {stdout}\nstderr: {stderr}"
);
}
#[test]
fn test_cli_rejects_planted_third_party_magic_in_ci_env() {
let dir = TempDir::new().unwrap();
let tp = dir.path().join("third_party");
fs::create_dir_all(&tp).unwrap();
fs::write(tp.join("magic.mgc"), "0 string EVIL planted-ci-magic-pwn\n").unwrap();
let target = dir.path().join("target.bin");
fs::write(&target, b"EVIL").unwrap();
let out = Command::cargo_bin("rmagic")
.unwrap()
.current_dir(dir.path())
.env("CI", "true")
.env("GITHUB_ACTIONS", "true")
.arg(target.file_name().unwrap())
.output()
.unwrap();
let stdout = String::from_utf8_lossy(&out.stdout);
let stderr = String::from_utf8_lossy(&out.stderr);
assert!(
!stdout.contains("planted-ci-magic-pwn"),
"CLI resolved planted third_party/magic.mgc under CI env (S-H1 regression)\n\
stdout: {stdout}\nstderr: {stderr}"
);
}
#[test]
fn test_file_buffer_error_uses_caller_path_not_canonical() {
use libmagic_rs::io::{FileBuffer, IoError};
use std::path::PathBuf;
let dir = TempDir::new().unwrap();
let path = dir.path().join("empty.bin");
fs::write(&path, b"").unwrap();
let err = FileBuffer::new(&path).unwrap_err();
match err {
IoError::EmptyFile { path: reported } => {
assert_eq!(
reported,
PathBuf::from(&path),
"EmptyFile error should report caller-supplied path, not canonicalized"
);
}
other => panic!("Expected EmptyFile, got {other:?}"),
}
}
#[test]
fn test_regex_compile_bounded_for_pathological_patterns() {
use libmagic_rs::evaluator::{EvaluationContext, evaluate_rules};
use libmagic_rs::parser::ast::{RegexCount, RegexFlags};
use libmagic_rs::{MagicRule, OffsetSpec, Operator, TypeKind, Value};
use std::time::Instant;
let cases: &[(&str, &str)] = &[
("[a-z]{1000000}", "huge character-class repetition"),
("a{1000000}", "huge literal repetition"),
(".{1000000}", "huge any-char repetition"),
];
let buf = vec![b'a'; 128];
let config = EvaluationConfig::default().with_timeout_ms(Some(1000));
for (pat, label) in cases {
let rule = MagicRule {
offset: OffsetSpec::Absolute(0),
typ: TypeKind::Regex {
flags: RegexFlags::default(),
count: RegexCount::Default,
},
op: Operator::Equal,
value: Value::String((*pat).to_string()),
message: "never-matches".to_string(),
children: vec![],
level: 0,
strength_modifier: None,
value_transform: None,
};
let mut ctx = EvaluationContext::new(config.clone());
let start = Instant::now();
let _ = evaluate_rules(&[rule], &buf, &mut ctx);
let elapsed = start.elapsed();
assert!(
elapsed.as_millis() < 500,
"{label}: pathological regex ran for {elapsed:?} (S-M2 regression)"
);
}
}
#[test]
fn test_evaluation_config_default_is_unbounded() {
let cfg = EvaluationConfig::default();
assert_eq!(
cfg.timeout_ms, None,
"EvaluationConfig::default() is expected to leave timeout_ms unset. \
If you are intentionally changing this behavior, update GOTCHAS S13.1 \
and the rustdoc `# Security` section on the MagicDatabase constructors."
);
}