#![cfg(unix)]
use super::{
DEFAULT_RULES_FILE, RULES_VARIABLE, SCANNER_SELF_MATCH_PATHS, is_scannable,
rules_candidate_path, rules_source,
};
use crate::candidate_object::{CandidateMode, parse_object_id};
use crate::candidate_record::CandidateChange;
use crate::candidate_version::{Candidate, CandidateIdentity};
use crate::scanner_adapter::RulesSource;
use std::ffi::{OsStr, OsString};
use std::os::unix::ffi::{OsStrExt, OsStringExt};
use std::path::{Path, PathBuf};
fn candidate(path: &[u8], change: CandidateChange, mode: CandidateMode) -> Candidate {
return Candidate {
identity: CandidateIdentity {
generation: 0,
index: 0,
},
path: path.to_vec(),
mode,
change,
object: parse_object_id(&[b'a'; 40]),
};
}
#[test]
fn names_match_the_standalone_scanner() {
assert_eq!(RULES_VARIABLE, "FORBIDDEN_STRINGS_RULES");
assert_eq!(DEFAULT_RULES_FILE, "forbidden-strings.local.txt");
assert_eq!(
SCANNER_SELF_MATCH_PATHS,
[
b"package/cli/forbidden-strings/data/betterleaks-default-config.toml".as_slice(),
b"package/cli/forbidden-strings/data/builtin-rules.txt",
b"package/cli/forbidden-strings/src/port-betterleaks-relaxations.ts",
]
);
}
#[test]
fn rules_file_precedence_matches_the_standalone_scanner() {
let root: &Path = Path::new("/repo/root");
assert_eq!(
rules_source(None, None, root),
RulesSource {
path: PathBuf::from("/repo/root/forbidden-strings.local.txt"),
explicit: false,
}
);
assert_eq!(
rules_source(None, Some(OsStr::new("rules/own.txt")), root),
RulesSource {
path: PathBuf::from("/repo/root/rules/own.txt"),
explicit: true,
}
);
assert_eq!(
rules_source(None, Some(OsStr::new("/elsewhere/rules.txt")), root),
RulesSource {
path: PathBuf::from("/elsewhere/rules.txt"),
explicit: true,
}
);
let bytes: OsString = OsString::from_vec(b"r\xffules.txt".to_vec());
assert_eq!(
rules_source(None, Some(bytes.as_os_str()), root)
.path
.as_os_str()
.as_bytes(),
b"/repo/root/r\xffules.txt"
);
assert!(rules_source(None, Some(OsStr::new("")), root).explicit);
assert_eq!(
rules_source(
Some(".cache/rules.txt"),
Some(OsStr::new("/elsewhere/rules.txt")),
root
),
RulesSource {
path: PathBuf::from("/repo/root/.cache/rules.txt"),
explicit: true,
}
);
assert_eq!(
rules_source(Some("configured.txt"), None, root),
RulesSource {
path: PathBuf::from("/repo/root/configured.txt"),
explicit: true,
}
);
}
#[test]
fn rules_candidate_path_is_the_repository_relative_name() {
let root: &Path = Path::new("/repo/root");
for (rules, expected) in [
(
"/repo/root/forbidden-strings.local.txt",
Some(b"forbidden-strings.local.txt".as_slice()),
),
(
"/repo/root/config/rules.txt",
Some(b"config/rules.txt".as_slice()),
),
(
"/repo/root/./config/../rules.txt",
Some(b"rules.txt".as_slice()),
),
("/repo/root/a/b/../../c.txt", Some(b"c.txt".as_slice())),
(
"/repo/root/../root/again.txt",
Some(b"again.txt".as_slice()),
),
("/repo/root", None),
("/repo/root/.", None),
("/repo/root/sub/..", None),
("/repo/root/../outside.txt", None),
("/repo/rootless/rules.txt", None),
("/repo", None),
("/elsewhere/rules.txt", None),
("/../../repo/root/rules.txt", Some(b"rules.txt".as_slice())),
] {
assert_eq!(
rules_candidate_path(Path::new(rules), root).as_deref(),
expected,
"{rules}"
);
}
let rules: PathBuf = PathBuf::from(OsString::from_vec(b"/repo/root/d\xff/r\xfe.txt".to_vec()));
assert_eq!(
rules_candidate_path(rules.as_path(), root).as_deref(),
Some(b"d\xff/r\xfe.txt".as_slice())
);
}
#[test]
fn eligibility_excludes_only_deletions_and_rule_sources() {
let rules: &[u8] = b"config/rules.txt";
for mode in [
CandidateMode::Regular,
CandidateMode::Executable,
CandidateMode::Symlink,
CandidateMode::Gitlink,
] {
for change in [CandidateChange::Added, CandidateChange::Modified] {
assert!(is_scannable(
&candidate(b"src/file.rs", change, mode),
Some(rules)
));
assert!(is_scannable(&candidate(b"src/file.rs", change, mode), None));
}
assert!(!is_scannable(
&candidate(b"src/file.rs", CandidateChange::Deleted, mode),
None
));
}
for path in SCANNER_SELF_MATCH_PATHS {
assert!(!is_scannable(
&candidate(path, CandidateChange::Modified, CandidateMode::Regular),
None
));
}
let own: Candidate = candidate(rules, CandidateChange::Modified, CandidateMode::Regular);
assert!(!is_scannable(&own, Some(rules)));
assert!(is_scannable(&own, None));
assert!(is_scannable(&own, Some(b"config/rules.txt.bak")));
for path in [
b"config/rules.tx".as_slice(),
b"other/config/rules.txt",
b"package/cli/forbidden-strings/data/builtin-rules.txt.orig",
b"vendor/package/cli/forbidden-strings/data/builtin-rules.txt",
b"package/cli/forbidden-strings/data",
] {
assert!(
is_scannable(
&candidate(path, CandidateChange::Added, CandidateMode::Regular),
Some(rules)
),
"{:?}",
String::from_utf8_lossy(path)
);
}
}