mur_common/muragent/
reader.rs1use crate::muragent::MuragentError;
4use flate2::read::GzDecoder;
5use std::collections::BTreeMap;
6use std::io::Read;
7use std::path::Path;
8use tar::Archive;
9
10pub struct MuragentArchive {
11 pub files: BTreeMap<String, Vec<u8>>,
13}
14
15impl MuragentArchive {
16 pub fn read(path: &Path) -> Result<Self, MuragentError> {
18 let file = std::fs::File::open(path).map_err(MuragentError::Io)?;
19 let gz = GzDecoder::new(file);
20 let mut archive = Archive::new(gz);
21 let mut files = BTreeMap::new();
22
23 for entry in archive
24 .entries()
25 .map_err(|e| MuragentError::Other(format!("tar entries: {e}")))?
26 {
27 let mut entry = entry.map_err(|e| MuragentError::Other(format!("tar entry: {e}")))?;
28
29 let entry_path = entry
30 .path()
31 .map_err(|e| MuragentError::Other(format!("entry path: {e}")))?
32 .to_str()
33 .ok_or_else(|| MuragentError::Other("non-UTF-8 path in tarball".into()))?
34 .to_string();
35
36 let entry_type = entry.header().entry_type();
37 if entry_type == tar::EntryType::Symlink || entry_type == tar::EntryType::Link {
38 return Err(MuragentError::ExecutableContent(format!(
39 "symlinks not allowed in .muragent: {entry_path}"
40 )));
41 }
42
43 if entry_type != tar::EntryType::Regular
44 && entry_type != tar::EntryType::Directory
45 && entry_type != tar::EntryType::GNULongName
46 && entry_type != tar::EntryType::GNULongLink
47 {
48 return Err(MuragentError::ExecutableContent(format!(
49 "tar entry type {:?} not allowed: {entry_path}",
50 entry_type
51 )));
52 }
53
54 if entry_type == tar::EntryType::Directory {
56 continue;
57 }
58
59 crate::muragent::jcs_canonical::validate_tarball_path(&entry_path)
60 .map_err(|e| MuragentError::Other(e.to_string()))?;
61
62 let mode = entry.header().mode().unwrap_or(0o644);
64 crate::muragent::executable_ban::check_mode_bits(mode, false)
65 .map_err(MuragentError::ExecutableContent)?;
66
67 let mut data = Vec::new();
68 entry.read_to_end(&mut data).map_err(MuragentError::Io)?;
69
70 files.insert(entry_path, data);
71 }
72
73 Ok(Self { files })
74 }
75
76 pub fn get(&self, path: &str) -> Option<&[u8]> {
77 self.files.get(path).map(|v| v.as_slice())
78 }
79
80 pub fn get_str(&self, path: &str) -> Result<&str, MuragentError> {
81 let bytes = self
82 .get(path)
83 .ok_or_else(|| MuragentError::Other(format!("file not found: {path}")))?;
84 std::str::from_utf8(bytes)
85 .map_err(|e| MuragentError::Other(format!("{path} is not valid UTF-8: {e}")))
86 }
87
88 pub fn files_as_vec(&self) -> Vec<(String, Vec<u8>)> {
89 self.files
90 .iter()
91 .map(|(k, v)| (k.clone(), v.clone()))
92 .collect()
93 }
94}