Record download checksums

This commit is contained in:
2026-05-07 22:07:03 -04:00
parent 7c9c9ab1e2
commit 5682434ea3
5 changed files with 142 additions and 4 deletions
+62
View File
@@ -31,6 +31,17 @@ pub enum DownloadSecurity {
DangerousExtension,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub enum DownloadChecksumAlgorithm {
Sha256,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DownloadChecksum {
algorithm: DownloadChecksumAlgorithm,
value: String,
}
#[derive(Clone, Debug, Eq, PartialEq)]
pub struct DownloadEntry {
id: DownloadId,
@@ -43,6 +54,7 @@ pub struct DownloadEntry {
state: DownloadState,
received_bytes: u64,
total_bytes: Option<u64>,
checksum: Option<DownloadChecksum>,
started_at: SystemTime,
}
@@ -137,6 +149,40 @@ impl DownloadSecurity {
}
}
impl DownloadChecksumAlgorithm {
#[must_use]
pub fn as_str(&self) -> &'static str {
match self {
Self::Sha256 => "sha256",
}
}
}
impl DownloadChecksum {
pub fn sha256_hex(value: impl Into<String>) -> Result<Self, DomainError> {
let value = value.into();
let value = value.trim();
if !is_sha256_hex(value) {
return Err(DomainError::InvalidDownloadChecksum {
algorithm: DownloadChecksumAlgorithm::Sha256.as_str(),
value: value.to_string(),
});
}
Ok(Self { algorithm: DownloadChecksumAlgorithm::Sha256, value: value.to_ascii_lowercase() })
}
#[must_use]
pub fn algorithm(&self) -> &DownloadChecksumAlgorithm {
&self.algorithm
}
#[must_use]
pub fn value(&self) -> &str {
&self.value
}
}
impl DownloadEntry {
pub fn started(
profile_id: ProfileId,
@@ -165,6 +211,7 @@ impl DownloadEntry {
state: DownloadState::InProgress,
received_bytes: 0,
total_bytes,
checksum: None,
started_at,
})
}
@@ -215,6 +262,12 @@ impl DownloadEntry {
Ok(())
}
pub fn record_checksum(&mut self, checksum: DownloadChecksum) -> Result<(), DomainError> {
self.require_state("record checksum", &[DownloadState::Completed])?;
self.checksum = Some(checksum);
Ok(())
}
#[must_use]
pub fn id(&self) -> &DownloadId {
&self.id
@@ -265,6 +318,11 @@ impl DownloadEntry {
self.total_bytes
}
#[must_use]
pub fn checksum(&self) -> Option<&DownloadChecksum> {
self.checksum.as_ref()
}
#[must_use]
pub fn started_at(&self) -> SystemTime {
self.started_at
@@ -330,3 +388,7 @@ fn download_extension(file_name: &str) -> Option<&str> {
fn is_dangerous_extension(extension: &str) -> bool {
DANGEROUS_DOWNLOAD_EXTENSIONS.iter().any(|candidate| extension.eq_ignore_ascii_case(candidate))
}
fn is_sha256_hex(value: &str) -> bool {
value.len() == 64 && value.as_bytes().iter().all(u8::is_ascii_hexdigit)
}
+3
View File
@@ -11,6 +11,9 @@ pub enum DomainError {
#[error("invalid file name: {value}")]
InvalidFileName { value: String },
#[error("invalid {algorithm} download checksum: {value}")]
InvalidDownloadChecksum { algorithm: &'static str, value: String },
#[error("invalid download directory: {path}")]
InvalidDownloadDirectory { path: String },
+2 -1
View File
@@ -13,7 +13,8 @@ mod url_text;
pub use archive::{ArchiveSource, ArchivedTab};
pub use command::{CommandIntent, CommandScope};
pub use download::{
DownloadDestination, DownloadEntry, DownloadPolicy, DownloadSecurity, DownloadState,
DownloadChecksum, DownloadChecksumAlgorithm, DownloadDestination, DownloadEntry,
DownloadPolicy, DownloadSecurity, DownloadState,
};
pub use error::DomainError;
pub use history::HistoryEntry;