mod archive;
#[cfg(target_os = "macos")]
mod codesign;
mod deflate;
mod extract;
mod macho;
#[cfg(target_os = "macos")]
mod publish;
use std::io::{Read, Seek};
use crate::distribution::install_state::ArtifactStageError;
use crate::distribution::install_state::VerifiedArchiveFile;
use crate::distribution::update_transport::VerifiedReleaseBundle;
#[derive(Debug, thiserror::Error)]
pub(super) enum PreparedReleaseError {
#[error("the authenticated release archive is outside the supported ZIP profile")]
ArchiveProfile,
#[error("the authenticated release archive could not be read completely")]
ArchiveRead,
#[error("the authenticated release archive changed after download")]
ArchiveIntegrity(#[from] ArtifactStageError),
#[error(transparent)]
Authentication(#[from] crate::distribution::update_auth::ArtifactFetchAuthorizationError),
#[error(transparent)]
Extraction(#[from] crate::distribution::install_state::ExtractionError),
#[error(transparent)]
Publication(#[from] crate::distribution::install_state::PreparedVersionError),
#[error(transparent)]
PreparedCommit(#[from] crate::distribution::update_auth::PreparedVersionCommitError),
#[error("prepared version {version} was committed, but final durability is unknown")]
PreparedVersionDurabilityUnknown {
version: String,
#[source]
source: Box<PreparedReleaseError>,
},
#[cfg(target_os = "macos")]
#[error("the staged executable is outside the supported Mach-O profile")]
MachO,
#[cfg(target_os = "macos")]
#[error("the staged executable does not satisfy the native code-signing policy")]
CodeSigning,
}
impl PreparedReleaseError {
fn after_prepared_commit(self, version: &str) -> Self {
match self {
Self::PreparedVersionDurabilityUnknown { .. } => self,
Self::Publication(
crate::distribution::install_state::PreparedVersionError::PublishedDurabilityUnknown {
..
},
) => self,
_ => Self::PreparedVersionDurabilityUnknown {
version: version.to_owned(),
source: Box::new(self),
},
}
}
}
#[cfg(target_os = "macos")]
impl From<macho::MachOError> for PreparedReleaseError {
fn from(_: macho::MachOError) -> Self {
Self::MachO
}
}
#[cfg(target_os = "macos")]
impl From<codesign::CodeSigningError> for PreparedReleaseError {
fn from(_: codesign::CodeSigningError) -> Self {
Self::CodeSigning
}
}
#[cfg(target_os = "macos")]
pub(in crate::distribution) use codesign::DeveloperIdVerification;
#[cfg(all(test, target_os = "macos"))]
pub(in crate::distribution) use extract::{
verify_and_normalize_release_for_test, verify_and_normalize_release_with_hook_for_test,
};
#[cfg(target_os = "macos")]
#[allow(unused_imports)]
pub(in crate::distribution) use publish::PreparedReleaseOutcome;
#[cfg(all(test, target_os = "macos"))]
pub(in crate::distribution) use publish::{
prepare_release_for_test, prepare_release_for_test_with_clocks,
};
pub(super) struct ArchiveBoundRelease<'a> {
bundle: VerifiedReleaseBundle<'a>,
profile: archive::VerifiedArchiveProfile,
}
impl std::fmt::Debug for ArchiveBoundRelease<'_> {
fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
formatter
.debug_struct("ArchiveBoundRelease")
.finish_non_exhaustive()
}
}
#[cfg(test)]
impl ArchiveBoundRelease<'_> {
pub(in crate::distribution) fn fail_archive_revalidation_after_for_test(
&mut self,
calls: usize,
) {
let (_, _, archive) = self.bundle.preparation_parts_mut();
archive.fail_revalidation_after_for_test(calls);
}
}
pub(super) fn bind_archive<'a>(
mut bundle: VerifiedReleaseBundle<'a>,
) -> Result<ArchiveBoundRelease<'a>, PreparedReleaseError> {
let (manifest_bytes, manifest, archive_file) = bundle.preparation_parts_mut();
let profile = validate_archive_reader(archive_file, manifest_bytes, manifest)?;
Ok(ArchiveBoundRelease { bundle, profile })
}
pub(super) fn extract_release(
release: ArchiveBoundRelease<'_>,
) -> Result<extract::ExtractedRelease<'_>, PreparedReleaseError> {
extract::extract_release(release)
}
trait ArchiveIntegrity {
fn revalidate_for_preparation(&mut self) -> Result<(), PreparedReleaseError>;
}
impl ArchiveIntegrity for VerifiedArchiveFile {
fn revalidate_for_preparation(&mut self) -> Result<(), PreparedReleaseError> {
self.revalidate().map_err(PreparedReleaseError::from)
}
}
fn validate_archive_reader<A: Read + Seek + ArchiveIntegrity>(
archive_file: &mut A,
manifest_bytes: &[u8],
manifest: &crate::distribution::schema::ReleaseManifestV1,
) -> Result<archive::VerifiedArchiveProfile, PreparedReleaseError> {
archive_file.revalidate_for_preparation()?;
let profile = archive::verify_archive(archive_file, manifest_bytes, manifest)?;
archive_file.revalidate_for_preparation()?;
Ok(profile)
}
#[cfg(test)]
#[path = "codesign_tests.rs"]
mod codesign_tests;
#[cfg(test)]
mod macho_tests;
#[cfg(test)]
mod tests;