Skip to content

Commit

Permalink
fix: extraction bars proper length and add checksum verification to p…
Browse files Browse the repository at this point in the history
…ath source (#666)
  • Loading branch information
wolfv authored Feb 26, 2024
1 parent 0827152 commit ea9dee7
Show file tree
Hide file tree
Showing 9 changed files with 371 additions and 312 deletions.
2 changes: 1 addition & 1 deletion src/recipe/parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ pub use self::{
Compiler, Dependency, IgnoreRunExports, PinSubpackage, Requirements, RunExports,
},
script::{Script, ScriptContent},
source::{Checksum, GitRev, GitSource, GitUrl, PathSource, Source, UrlSource},
source::{GitRev, GitSource, GitUrl, PathSource, Source, UrlSource},
test::{
CommandsTest, CommandsTestFiles, CommandsTestRequirements, DownstreamTest,
PackageContentsTest, PythonTest, TestType,
Expand Down
93 changes: 44 additions & 49 deletions src/recipe/parser/source.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ use crate::{
},
error::{ErrorKind, PartialParsingError},
},
source::SourceError,
};

use super::FlattenErrors;
Expand Down Expand Up @@ -442,22 +441,21 @@ impl TryConvertNode<UrlSource> for RenderedMappingNode {
let mut file_name = None;

self.iter().map(|(key, value)| {
let key_str = key.as_str();
match key_str {
"url" => url = value.try_convert(key_str)?,
match key.as_str() {
"url" => url = value.try_convert(key)?,
"sha256" => {
let sha256_str: RenderedScalarNode = value.try_convert(key_str)?;
let sha256_str: RenderedScalarNode = value.try_convert(key)?;
let sha256_out = rattler_digest::parse_digest_from_hex::<Sha256>(sha256_str.as_str()).ok_or_else(|| vec![_partialerror!(*sha256_str.span(), ErrorKind::InvalidSha256)])?;
sha256 = Some(sha256_out);
}
"md5" => {
let md5_str: RenderedScalarNode = value.try_convert(key_str)?;
let md5_str: RenderedScalarNode = value.try_convert(key)?;
let md5_out = rattler_digest::parse_digest_from_hex::<Md5>(md5_str.as_str()).ok_or_else(|| vec![_partialerror!(*md5_str.span(), ErrorKind::InvalidMd5)])?;
md5 = Some(md5_out);
}
"file_name" => file_name = value.try_convert(key_str)?,
"patches" => patches = value.try_convert(key_str)?,
"target_directory" => target_directory = value.try_convert(key_str)?,
"file_name" => file_name = value.try_convert(key)?,
"patches" => patches = value.try_convert(key)?,
"target_directory" => target_directory = value.try_convert(key)?,
invalid_key => {
return Err(vec![_partialerror!(
*key.span(),
Expand Down Expand Up @@ -496,58 +494,33 @@ impl TryConvertNode<UrlSource> for RenderedMappingNode {
}
}

/// Checksum information.
#[serde_as]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum Checksum {
/// A SHA256 checksum
Sha256(#[serde_as(as = "SerializableHash::<rattler_digest::Sha256>")] Sha256Hash),
/// A MD5 checksum
Md5(#[serde_as(as = "SerializableHash::<rattler_digest::Md5>")] Md5Hash),
}

impl TryFrom<&UrlSource> for Checksum {
type Error = SourceError;

fn try_from(source: &UrlSource) -> Result<Self, Self::Error> {
if let Some(sha256) = source.sha256() {
Ok(Checksum::Sha256(*sha256))
} else if let Some(md5) = source.md5() {
Ok(Checksum::Md5(*md5))
} else {
return Err(SourceError::NoChecksum(source.url().clone()));
}
}
}

impl Checksum {
/// Get the checksum as a hex string.
pub fn to_hex(&self) -> String {
match self {
Checksum::Sha256(sha256) => hex::encode(sha256),
Checksum::Md5(md5) => hex::encode(md5),
}
}
}

/// A local path source. The source code will be copied to the `work`
/// (or `work/<folder>` directory).
#[serde_as]
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub struct PathSource {
/// Path to the local source code
path: PathBuf,
pub path: PathBuf,
/// Optionally a sha256 checksum to verify the source code
#[serde(skip_serializing_if = "Option::is_none")]
#[serde_as(as = "Option<SerializableHash::<rattler_digest::Sha256>>")]
pub sha256: Option<Sha256Hash>,
/// Optionally a md5 checksum to verify the source code
#[serde(skip_serializing_if = "Option::is_none")]
#[serde_as(as = "Option<SerializableHash::<rattler_digest::Md5>>")]
pub md5: Option<Md5Hash>,
/// Patches to apply to the source code
#[serde(default, skip_serializing_if = "Vec::is_empty")]
patches: Vec<PathBuf>,
pub patches: Vec<PathBuf>,
/// Optionally a folder name under the `work` directory to place the source code
#[serde(skip_serializing_if = "Option::is_none")]
target_directory: Option<PathBuf>,
pub target_directory: Option<PathBuf>,
/// Optionally a file name to rename the file to
#[serde(skip_serializing_if = "Option::is_none")]
file_name: Option<PathBuf>,
pub file_name: Option<PathBuf>,
/// Whether to use the `.gitignore` file in the source directory. Defaults to `true`.
#[serde(skip_serializing_if = "should_not_serialize_use_gitignore")]
use_gitignore: bool,
pub use_gitignore: bool,
}

/// Helper method to skip serializing the use_gitignore flag if it is true.
Expand Down Expand Up @@ -589,10 +562,22 @@ impl TryConvertNode<PathSource> for RenderedMappingNode {
let mut target_directory = None;
let mut use_gitignore = true;
let mut file_name = None;
let mut sha256 = None;
let mut md5 = None;

self.iter().map(|(key, value)| {
match key.as_str() {
"path" => path = value.try_convert("path")?,
"sha256" => {
let sha256_str: RenderedScalarNode = value.try_convert(key)?;
let sha256_out = rattler_digest::parse_digest_from_hex::<Sha256>(sha256_str.as_str()).ok_or_else(|| vec![_partialerror!(*sha256_str.span(), ErrorKind::InvalidSha256)])?;
sha256 = Some(sha256_out);
}
"md5" => {
let md5_str: RenderedScalarNode = value.try_convert(key)?;
let md5_out = rattler_digest::parse_digest_from_hex::<Md5>(md5_str.as_str()).ok_or_else(|| vec![_partialerror!(*md5_str.span(), ErrorKind::InvalidMd5)])?;
md5 = Some(md5_out);
}
"patches" => patches = value.try_convert("patches")?,
"target_directory" => target_directory = value.try_convert("target_directory")?,
"file_name" => file_name = value.try_convert("file_name")?,
Expand All @@ -608,16 +593,26 @@ impl TryConvertNode<PathSource> for RenderedMappingNode {
Ok(())
}).flatten_errors()?;

let path = path.ok_or_else(|| {
let path: PathBuf = path.ok_or_else(|| {
vec![_partialerror!(
*self.span(),
ErrorKind::MissingField("path".into()),
help = "path `source` must have a `path` field"
)]
})?;

if path.is_dir() && (sha256.is_some() || md5.is_some()) {
return Err(vec![_partialerror!(
*self.span(),
ErrorKind::Other,
help = "path `source` with a directory cannot have a `sha256` or `md5` checksum"
)]);
}

Ok(PathSource {
path,
sha256,
md5,
patches,
target_directory,
file_name,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ source:
- file.patch
target_directory: test-package
- path: ./some-file.tar.gz
sha256: 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef
md5: 1234567890abcdef1234567890abcdef
patches:
- file.patch
target_directory: test-package
Expand Down
86 changes: 86 additions & 0 deletions src/source/checksum.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
//! Module to handle checksums and validate checksums of downloaded files.
use std::path::Path;

use rattler_digest::{compute_file_digest, serde::SerializableHash, Md5, Md5Hash, Sha256Hash};
use serde::{Deserialize, Serialize};
use serde_with::serde_as;

use crate::recipe::parser::{PathSource, UrlSource};

/// Checksum information.
#[serde_as]
#[derive(Debug, Clone, PartialEq, Deserialize, Serialize)]
pub enum Checksum {
/// A SHA256 checksum
Sha256(#[serde_as(as = "SerializableHash::<rattler_digest::Sha256>")] Sha256Hash),
/// A MD5 checksum
Md5(#[serde_as(as = "SerializableHash::<rattler_digest::Md5>")] Md5Hash),
}

impl Checksum {
/// Create a checksum from a URL source.
pub fn from_url_source(source: &UrlSource) -> Option<Self> {
if let Some(sha256) = source.sha256() {
Some(Checksum::Sha256(*sha256))
} else {
source.md5().map(|md5| Checksum::Md5(*md5))
}
}

/// Create a checksum from a path source.
pub fn from_path_source(source: &PathSource) -> Option<Self> {
if let Some(sha256) = source.sha256 {
Some(Checksum::Sha256(sha256))
} else {
source.md5.map(Checksum::Md5)
}
}

/// Get the checksum as a hex string.
pub fn to_hex(&self) -> String {
match self {
Checksum::Sha256(sha256) => hex::encode(sha256),
Checksum::Md5(md5) => hex::encode(md5),
}
}

/// Validate the checksum of a file.
pub fn validate(&self, path: &Path) -> bool {
match self {
Checksum::Sha256(value) => {
let digest =
compute_file_digest::<sha2::Sha256>(path).expect("Could not compute SHA256");
let computed_sha = hex::encode(digest);
let checksum_sha = hex::encode(value);
if !computed_sha.eq(&checksum_sha) {
tracing::error!(
"SHA256 values of downloaded file not matching!\nDownloaded = {}, should be {}",
computed_sha,
checksum_sha
);
false
} else {
tracing::info!("Validated SHA256 values of the downloaded file!");
true
}
}
Checksum::Md5(value) => {
let digest = compute_file_digest::<Md5>(path).expect("Could not compute SHA256");
let computed_md5 = hex::encode(digest);
let checksum_md5 = hex::encode(value);
if !computed_md5.eq(&checksum_md5) {
tracing::error!(
"MD5 values of downloaded file not matching!\nDownloaded = {}, should be {}",
computed_md5,
checksum_md5
);
false
} else {
tracing::info!("Validated MD5 values of the downloaded file!");
true
}
}
}
}
}
Loading

0 comments on commit ea9dee7

Please sign in to comment.