use std::ffi::OsString;
use std::path::{Path, PathBuf};
use qq_cli::napcat::runtime::launch;
fn ordinary(path: &Path) -> OsString {
let path = path.canonicalize().unwrap();
let text = path.to_str().unwrap();
text.strip_prefix("\\\\?\\").unwrap_or(text).into()
}
fn entries(pairs: &[(&str, &str)]) -> Vec<(OsString, OsString)> {
pairs
.iter()
.map(|(key, value)| ((*key).into(), (*value).into()))
.collect()
}
#[test]
fn native_bootstrap_preserves_three_literal_arguments_without_starting_any_file() {
let temp = tempfile::tempdir().unwrap();
let installation = temp.path().join("framework with spaces");
let qq = temp.path().join("qq with spaces");
let work = temp.path().join("run with spaces");
for dir in [&installation, &qq, &work] {
std::fs::create_dir(dir).unwrap();
}
for name in ["napimain.exe", "napiloader.dll", "nativeLoader.cjs"] {
std::fs::write(installation.join(name), b"original inert synthetic fixture").unwrap();
}
let executable = qq.join("QQ.exe");
std::fs::write(&executable, b"original inert synthetic fixture").unwrap();
let plan = launch::bootstrap(&executable, &installation, &work).unwrap();
assert_eq!(
plan.program,
installation.canonicalize().unwrap().join("napimain.exe")
);
assert_eq!(plan.arguments[0], ordinary(&executable));
assert_eq!(
plan.arguments[1],
ordinary(&installation.join("napiloader.dll"))
);
let expected = installation
.canonicalize()
.unwrap()
.join("nativeLoader.cjs");
let expected = expected.to_str().unwrap();
assert_eq!(
plan.arguments[2],
OsString::from(
expected
.strip_prefix("\\\\?\\")
.unwrap_or(expected)
.replace('\\', "/")
)
);
assert_eq!(plan.current_directory, work.canonicalize().unwrap());
for forbidden in [&installation, &qq, temp.path()] {
assert!(launch::bootstrap(&executable, &installation, forbidden).is_err());
}
std::fs::remove_file(installation.join("napiloader.dll")).unwrap();
assert_eq!(
launch::bootstrap(&executable, &installation, &work)
.err()
.unwrap()
.code,
"LAUNCH_ENV_INVALID"
);
}
#[test]
fn child_environment_drops_injection_overrides_and_replaces_search_and_temporary_paths() {
let temp = tempfile::tempdir().unwrap();
let qq = temp.path().join("qq");
let work = temp.path().join("run");
let system = temp.path().join("system");
for path in [&qq, &work.join("tmp"), &system.join("System32")] {
std::fs::create_dir_all(path).unwrap();
}
let system_text = system.to_str().unwrap();
let input = entries(&[
("SystemRoot", system_text),
("USERPROFILE", "synthetic-user-home"),
("node_options", "--require=untrusted"),
("NAPCAT_QQ_VERSION_CONFIG_PATH", "untrusted"),
("NAPCAT_WORKDIR", "untrusted"),
("PATH", "untrusted"),
("TEMP", "untrusted"),
("HTTP_PROXY", "untrusted"),
("UNRELATED_SECRET", "synthetic secret"),
]);
let environment = launch::environment(input.clone(), &qq, &work).unwrap();
assert_eq!(
environment[&OsString::from("USERPROFILE")],
"synthetic-user-home"
);
for name in [
"NODE_OPTIONS",
"NAPCAT_QQ_VERSION_CONFIG_PATH",
"HTTP_PROXY",
"UNRELATED_SECRET",
] {
assert!(!environment.contains_key(&OsString::from(name)));
}
assert_eq!(
environment[&OsString::from("NAPCAT_WORKDIR")],
ordinary(&work)
);
assert_eq!(
environment[&OsString::from("TEMP")],
ordinary(&work.join("tmp"))
);
assert_eq!(
std::env::split_paths(&environment[&OsString::from("PATH")]).collect::<Vec<_>>(),
[
PathBuf::from(ordinary(&qq)),
PathBuf::from(ordinary(&system.join("System32"))),
PathBuf::from(ordinary(&system))
]
);
assert_eq!(
launch::environment(input.into_iter().rev(), &qq, &work).unwrap(),
environment
);
assert_eq!(std::fs::read_dir(&work).unwrap().count(), 1);
}
#[test]
fn child_environment_rejects_ambiguous_system_roots_and_unisolated_directories() {
let temp = tempfile::tempdir().unwrap();
let qq = temp.path().join("qq");
let work = temp.path().join("run");
let system = temp.path().join("system");
for path in [&qq, &work.join("tmp"), &system.join("System32")] {
std::fs::create_dir_all(path).unwrap();
}
let root = system.to_str().unwrap();
for input in [
entries(&[]),
entries(&[("SYSTEMROOT", root), ("systemroot", "different")]),
entries(&[("SYSTEMROOT", root), ("WINDIR", qq.to_str().unwrap())]),
entries(&[("SYSTEMROOT", root), ("APPDATA", "invalid\0value")]),
] {
assert_eq!(
launch::environment(input, &qq, &work).unwrap_err().code,
"LAUNCH_ENV_INVALID"
);
}
let input = entries(&[("SYSTEMROOT", root)]);
assert!(launch::environment(input.clone(), &qq, &qq).is_err());
assert!(launch::environment(input.clone(), Path::new("relative"), &work).is_err());
std::fs::remove_dir(work.join("tmp")).unwrap();
assert!(launch::environment(input, &qq, &work).is_err());
}