ic_host_artifacts/artifact/tree/
mod.rs1use super::{Digest as _, Sha256, Sha256Digest};
7use std::{
8 fmt,
9 path::{Component, PathBuf},
10};
11
12pub fn checksum_relative_files(
24 mut files: Vec<(PathBuf, Sha256Digest)>,
25) -> Result<Sha256Digest, DirectoryChecksumError> {
26 files.sort_by(|left, right| left.0.cmp(&right.0));
27 let mut hasher = Sha256::new();
28 let mut previous = None;
29 for (relative, checksum) in &files {
30 let name = relative
31 .to_str()
32 .ok_or_else(|| DirectoryChecksumError::NonUtf8Path {
33 path: relative.clone(),
34 })?;
35 if name.contains('\0')
36 || name.split('/').any(|part| matches!(part, "" | "." | ".."))
37 || !relative
38 .components()
39 .all(|part| matches!(part, Component::Normal(_)))
40 {
41 return Err(DirectoryChecksumError::InvalidRelativePath {
42 path: relative.clone(),
43 });
44 }
45 if previous == Some(relative) {
46 return Err(DirectoryChecksumError::DuplicatePath {
47 path: relative.clone(),
48 });
49 }
50 previous = Some(relative);
51 hasher.update(name.as_bytes());
52 hasher.update([0]);
53 hasher.update(checksum.hex_bytes());
54 hasher.update(b"\n");
55 }
56 Ok(Sha256Digest::from_bytes(hasher.finalize().into()))
57}
58
59#[derive(Clone, Debug, Eq, PartialEq)]
61pub enum DirectoryChecksumError {
62 NonUtf8Path {
64 path: PathBuf,
66 },
67 InvalidRelativePath {
69 path: PathBuf,
71 },
72 DuplicatePath {
74 path: PathBuf,
76 },
77}
78
79impl fmt::Display for DirectoryChecksumError {
80 #[expect(
81 clippy::unnecessary_debug_formatting,
82 reason = "retain escaped exact path identities, including control characters and non-UTF-8 bytes"
83 )]
84 fn fmt(&self, formatter: &mut fmt::Formatter<'_>) -> fmt::Result {
85 match self {
86 Self::NonUtf8Path { path } => {
87 write!(formatter, "directory checksum path is not UTF-8: {path:?}")
88 }
89 Self::InvalidRelativePath { path } => write!(
90 formatter,
91 "directory checksum path is not canonical and relative: {path:?}"
92 ),
93 Self::DuplicatePath { path } => {
94 write!(formatter, "duplicate directory checksum path: {path:?}")
95 }
96 }
97 }
98}
99impl std::error::Error for DirectoryChecksumError {}
100
101#[cfg(test)]
102mod tests;