//! 自有签名、归档和 Lib 输入验证;不连接生产 deverd 或修改机器缓存。 mod central; mod pep517; mod pep517_fixture; mod python; mod python_fixture; use super::{Fixture, fixture::digest, write}; use dever::compiler::project::dependency::{ library::{Spec, archive}, worker::signed::SignedResources, }; use ring::signature::{Ed25519KeyPair, KeyPair}; use std::io::Write; #[test] fn lib_identities_normalize_extras_and_reject_ranges_and_path_injection() { assert_eq!( "pip:Some_Name[TEST,docs]@1.2.3" .parse::() .unwrap() .key(), "pip:some-name[docs,test]@1.2.3" ); assert_eq!( "go:example.org/widget@v1.2.3" .parse::() .unwrap() .version, "1.2.3" ); assert_eq!( "npm:@scope/name@1.2.3".parse::().unwrap().name, "@scope/name" ); for value in [ "npm:thing@^1", "npm:../thing@1.2.3", "pip:thing[]@1", "pip:thing@>=1", "go:../thing@1.2.3", "npm:@scope/../thing@1.2.3", ] { assert!(value.parse::().is_err(), "accepted {value}"); } } #[test] fn installed_resources_require_independent_signature_and_exact_catalog_bytes() { let fixture = Fixture::new(&[]); let root = fixture.directory.0.join("installation"); let key = Ed25519KeyPair::from_seed_unchecked(&[91; 32]).unwrap(); let hex = |bytes: &[u8]| { bytes .iter() .map(|byte| format!("{byte:02x}")) .collect::() }; write( &root, "state/trusted-release-key", hex(key.public_key().as_ref()), ); let bytes = b"signed fixture contents"; let path = "runtime/npm/linux-x86_64/runtime.pack"; let target = dever::compiler::toolchain::BuildTarget::host().unwrap(); let catalog = serde_json::to_vec(&serde_json::json!({"format":"dever-release-v2","version":"0.0.1","platform":target.name(),"artifacts":[{"path":"base.txt","bytes":bytes.len(),"sha256":digest(bytes)}],"extensions":[{"kind":{"type":"runtime","ecosystem":"npm"},"target":"linux-x86_64","artifacts":[{"path":path,"bytes":bytes.len(),"sha256":digest(bytes)}]}]})).unwrap(); write(&root, "versions/0.0.1/base.txt", bytes); write(&root, "versions/0.0.1/manifest.json", &catalog); write( &root, "versions/0.0.1/manifest.sig", hex(key.sign(&catalog).as_ref()), ); let signed = SignedResources::load(&root, "0.0.1").unwrap(); assert_eq!(signed.read("base.txt").unwrap(), bytes); let actual = signed.read_with(path, |version, catalog_hash, extension, artifact| { assert_eq!(version, "0.0.1"); assert_eq!(catalog_hash, digest(&catalog)); assert_eq!(extension, &serde_json::json!({"kind":{"type":"runtime","ecosystem":"npm"},"target":"linux-x86_64"})); assert_eq!(artifact.path, path); Ok(bytes.to_vec()) }).unwrap(); assert_eq!(actual, bytes); assert!( signed .read_with(path, |_, _, _, _| Ok(b"changed".to_vec())) .is_err() ); write(&root, "versions/0.0.1/base.txt", b"different signed file"); assert!(signed.read("base.txt").is_err()); write(&root, "versions/0.0.1/manifest.json", b"{}"); assert!(SignedResources::load(&root, "0.0.1").is_err()); } #[test] fn archive_consumption_rejects_links_and_file_directory_collisions() { let make = |entries: &[(&str, &[u8], tar::EntryType)]| { let gzip = flate2::write::GzEncoder::new(Vec::new(), flate2::Compression::fast()); let mut archive = tar::Builder::new(gzip); for (path, bytes, kind) in entries { let mut header = tar::Header::new_gnu(); header.set_entry_type(*kind); header.set_size(bytes.len() as u64); header.set_mode(0o755); header.set_cksum(); archive.append_data(&mut header, path, *bytes).unwrap(); } archive.into_inner().unwrap().finish().unwrap() }; let bytes = make(&[( "package/main.js", b"export const value = 42;", tar::EntryType::Regular, )]); assert_eq!(archive::files(&bytes, false).unwrap().len(), 1); assert!(archive::entries(&bytes, false).unwrap()["package/main.js"].1); let bytes = make(&[("package/main.js", b"", tar::EntryType::Symlink)]); assert!(archive::files(&bytes, false).unwrap_err().contains("links")); let bytes = make(&[ ("package", b"not a directory", tar::EntryType::Regular), ("package/main.js", b"", tar::EntryType::Regular), ]); assert!( archive::files(&bytes, false) .unwrap_err() .contains("collision") ); let mut zip = zip::ZipWriter::new(std::io::Cursor::new(Vec::new())); zip.start_file("../escape", zip::write::SimpleFileOptions::default()) .unwrap(); zip.write_all(b"bad").unwrap(); assert!(archive::files(&zip.finish().unwrap().into_inner(), true).is_err()); } #[test] fn lib_zip_rejects_duplicate_central_paths() { let mut zip = zip::ZipWriter::new(std::io::Cursor::new(Vec::new())); for name in ["package/first", "package/other"] { zip.start_file( name, zip::write::SimpleFileOptions::default().unix_permissions(0o755), ) .unwrap(); zip.write_all(name.as_bytes()).unwrap(); } let mut bytes = zip.finish().unwrap().into_inner(); let files = archive::entries(&bytes, true).unwrap(); assert_eq!(files.len(), 2); assert!(files["package/first"].1); let mut cursor = 0; while cursor + 5 <= bytes.len() { if &bytes[cursor..cursor + 5] == b"other" { bytes[cursor..cursor + 5].copy_from_slice(b"first"); } cursor += 1; } assert!( archive::entries(&bytes, true) .unwrap_err() .contains("duplicate or inconsistent") ); } #[test] fn python_wheel_identity_abi_and_dependency_edges_are_checked_before_installation() { let bytes = python_fixture::wheel("tiny", "1.0.0", ""); let lib = serde_json::from_value(serde_json::json!({"spec":{"ecosystem":"pip","name":"tiny","version":"1.0.0"},"dependencies":[],"runtime":{"name":"python","version":"3.12.1","sha256":"0".repeat(64)},"artifacts":[],"schema":"pypi-wheel-v1"})).unwrap(); let markers = serde_json::json!({"implementation_name":"cpython","implementation_version":"3.12.1","os_name":"posix","platform_machine":"x86_64","platform_python_implementation":"CPython","platform_release":"","platform_system":"Linux","platform_version":"","python_full_version":"3.12.1","python_version":"3.12","sys_platform":"linux"}); let mut runtime = serde_json::from_value::(serde_json::json!({"format":"dever-worker-runtime-v1","ecosystem":"pip","target":"linux-x86_64","executable":"bin/python3","arguments":["-I","-S","-B"],"python_wheel_tags":["py3-none-any"],"python_extension_suffixes":[".so"],"python_markers":markers})).unwrap(); let files = dever::compiler::project::dependency::library::python::install(&lib, &bytes, &runtime, &[]) .unwrap(); assert_eq!(files["tiny/__init__.py"], b"VALUE = 42\n"); runtime.python_wheel_tags.clear(); assert!( dever::compiler::project::dependency::library::python::install(&lib, &bytes, &runtime, &[]) .unwrap_err() .contains("tags") ); }