#[cfg(test)]
mod regressions;
mod secure;
#[cfg(test)]
mod tests;
use crate::model::artifacts::ArtifactChecksumRecord;
use sha2::{Digest, Sha256};
#[cfg(unix)]
use std::io::Write;
use std::{
io::{self, Read},
path::{Path, PathBuf},
};
use thiserror::Error;
pub fn checksum_file(path: &Path) -> Result<ArtifactChecksumRecord, ArtifactError> {
secure::checksum_path(path, secure::ExpectedArtifactType::File)
}
pub fn checksum_path(path: &Path) -> Result<ArtifactChecksumRecord, ArtifactError> {
secure::checksum_path(path, secure::ExpectedArtifactType::Any)
}
pub fn checksum_directory(path: &Path) -> Result<ArtifactChecksumRecord, ArtifactError> {
secure::checksum_path(path, secure::ExpectedArtifactType::Directory)
}
pub fn checksum_reader(reader: &mut impl Read) -> Result<ArtifactChecksumRecord, ArtifactError> {
let mut hasher = Sha256::new();
let mut buffer = vec![0; 64 * 1024];
loop {
let read = reader.read(&mut buffer)?;
if read == 0 {
break;
}
hasher.update(&buffer[..read]);
}
Ok(ArtifactChecksumRecord::from_digest(
hasher.finalize().into(),
))
}
#[cfg(unix)]
pub(crate) fn copy_from_reader(
reader: &mut impl Read,
writer: &mut impl Write,
) -> Result<ArtifactChecksumRecord, ArtifactError> {
use ic_host_tools::artifact::{ArtifactError as InputError, CopyError};
let identity = ic_host_tools::artifact::copy_reader(reader, writer, u64::MAX).map_err(
|error| match error {
CopyError::Input(InputError::Io(error)) | CopyError::Output(error) => {
ArtifactError::Io(error)
}
CopyError::Input(error) => ArtifactError::Io(io::Error::other(error)),
},
)?;
Ok(ArtifactChecksumRecord::from_digest(
*identity.sha256.as_bytes(),
))
}
pub(crate) fn checksum_relative_files(
mut files: Vec<(PathBuf, ArtifactChecksumRecord)>,
) -> ArtifactChecksumRecord {
files.sort_by(|left, right| left.0.cmp(&right.0));
let mut hasher = Sha256::new();
for (relative, checksum) in files {
hasher.update(relative.to_string_lossy().as_bytes());
hasher.update([0]);
hasher.update(checksum.hash().as_bytes());
hasher.update(*b"\n");
}
ArtifactChecksumRecord::from_digest(hasher.finalize().into())
}
#[cfg(unix)]
fn require_utf8_tree_name(
name: &std::ffi::OsStr,
display_root: &Path,
) -> Result<(), ArtifactError> {
if name.to_str().is_none() {
return Err(ArtifactError::NonUtf8Path {
path: display_root.join(name),
});
}
Ok(())
}
pub fn checksum_relative_path(
root: &Path,
relative: &Path,
) -> Result<ArtifactChecksumRecord, ArtifactError> {
secure::checksum_relative_path(root, relative)
}
pub fn stage_relative_path(
root: &Path,
relative: &Path,
destination: &Path,
) -> Result<ArtifactChecksumRecord, ArtifactError> {
secure::stage_relative_path(root, relative, destination)
}
#[derive(Debug, Error)]
pub enum ArtifactError {
#[error("artifact path is not UTF-8: {path:?}")]
NonUtf8Path {
path: PathBuf,
},
#[error(transparent)]
Io(#[from] io::Error),
#[error("unsupported artifact entry at {path}: {kind}")]
UnsupportedEntry {
path: String,
kind: String,
},
#[error("secure artifact traversal is unsupported on platform {0}")]
UnsupportedPlatform(&'static str),
}