lean_ctx/core/context_bundle/
export.rs1use std::path::{Path, PathBuf};
11
12pub fn destination(tool: &str, root: &Path, candidate: &Path) -> Result<PathBuf, String> {
15 let (output, _) = crate::core::io_boundary::jail_and_check_export_path(tool, candidate, root)?;
16 let canonical_root = crate::core::pathutil::canonicalize_secure(root)
20 .map_err(|_| "project root is unavailable".to_string())?;
21 let inside = output
22 .parent()
23 .and_then(|parent| crate::core::pathutil::canonicalize_secure(parent).ok())
24 .is_some_and(|parent| parent.starts_with(&canonical_root));
25 if !inside {
26 return Err("a bundle file must stay inside the project root".to_string());
27 }
28 crate::core::pathjail::enforce_writable(&output)?;
29 Ok(output)
30}
31
32pub fn write(tool: &str, root: &Path, candidate: &Path, bytes: &[u8]) -> Result<PathBuf, String> {
38 let output = destination(tool, root, candidate)?;
39 crate::core::pathjail_write::write_requested_below(root, candidate, &output, bytes)
40}
41
42#[cfg(all(test, unix))]
43mod tests {
44 use super::*;
45 use std::os::unix::fs::symlink;
46
47 #[test]
50 fn write_refuses_a_symlinked_bundle_file() {
51 let _iso = crate::core::data_dir::isolated_data_dir();
52 let tmp = tempfile::tempdir().unwrap();
53 let root = tmp.path().join("proj");
54 std::fs::create_dir_all(&root).unwrap();
55 std::fs::write(root.join("Cargo.toml"), "[package]\nname=\"p\"\n").unwrap();
56 let outside = tmp.path().join("outside.xml");
57
58 symlink(&outside, root.join("bundle.xml")).unwrap();
60 let err = write("ctx_pack", &root, &root.join("bundle.xml"), b"<x/>").unwrap_err();
61 assert!(err.contains("symlink"), "{err}");
62 assert!(!outside.exists());
63
64 let victim = tmp.path().join("victim.xml");
66 std::fs::write(&victim, "keep").unwrap();
67 symlink(&victim, root.join("bundle2.xml")).unwrap();
68 assert!(write("ctx_pack", &root, &root.join("bundle2.xml"), b"<x/>").is_err());
69 assert_eq!(std::fs::read_to_string(&victim).unwrap(), "keep");
70
71 let path = write("ctx_pack", &root, &root.join("ok.xml"), b"<x/>").unwrap();
73 assert_eq!(std::fs::read(&path).unwrap(), b"<x/>");
74 }
75}