use crate::{
config::rt::RtcBuild,
pipelines::{Attr, Attrs},
};
use base64::{display::Base64Display, engine::general_purpose::STANDARD, Engine};
use sha2::{Digest, Sha256, Sha384, Sha512};
use std::{
convert::Infallible,
fmt::{Debug, Display, Formatter},
future::Future,
str::FromStr,
};
const ATTR_INTEGRITY: &str = "data-integrity";
#[derive(Copy, Clone, Eq, PartialEq, Hash, Debug)]
pub enum IntegrityType {
None,
Sha256,
Sha384,
Sha512,
}
impl IntegrityType {
pub fn default_unless(disabled: bool) -> IntegrityType {
if disabled {
Self::None
} else {
Self::Sha384
}
}
pub fn from_attrs(attrs: &Attrs, cfg: &RtcBuild) -> anyhow::Result<IntegrityType> {
Ok(attrs
.get(ATTR_INTEGRITY)
.map(|attr| IntegrityType::from_str(attr))
.transpose()?
.unwrap_or_else(|| IntegrityType::default_unless(cfg.no_sri)))
}
}
impl FromStr for IntegrityType {
type Err = IntegrityTypeParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
Ok(match s {
"none" => Self::None,
"sha256" => Self::Sha256,
"sha384" | "" => Self::Sha384,
"sha512" => Self::Sha512,
_ => return Err(IntegrityTypeParseError::InvalidValue),
})
}
}
impl Display for IntegrityType {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
match self {
Self::None => write!(f, "none"),
Self::Sha256 => write!(f, "sha256"),
Self::Sha384 => write!(f, "sha384"),
Self::Sha512 => write!(f, "sha512"),
}
}
}
#[derive(Debug, thiserror::Error)]
pub enum IntegrityTypeParseError {
#[error("invalid value")]
InvalidValue,
}
#[derive(Clone)]
pub struct OutputDigest {
pub integrity: IntegrityType,
pub hash: Vec<u8>,
}
impl Debug for OutputDigest {
fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
f.debug_struct("OutputDigest")
.field("integrity", &self.integrity)
.field("hash", &STANDARD.encode(&self.hash))
.finish()
}
}
impl Default for OutputDigest {
fn default() -> Self {
Self {
integrity: IntegrityType::None,
hash: vec![],
}
}
}
impl OutputDigest {
pub fn to_integrity_value(&self) -> Option<impl Display + '_> {
match self.integrity {
IntegrityType::None => None,
integrity => Some(format!(
"{integrity}-{hash}",
hash = Base64Display::new(&self.hash, &STANDARD)
)),
}
}
pub fn insert_into(&self, attrs: &mut Attrs) {
if let Some(value) = self.to_integrity_value() {
attrs.insert(
"integrity".to_string(),
Attr {
value: value.to_string(),
need_escape: false,
},
);
}
}
pub fn generate<F, T, E>(integrity: IntegrityType, f: F) -> Result<Self, E>
where
F: FnOnce() -> Result<T, E>,
T: AsRef<[u8]>,
{
let hash = match integrity {
IntegrityType::None => vec![],
IntegrityType::Sha256 => Vec::from_iter(Sha256::digest(f()?)),
IntegrityType::Sha384 => Vec::from_iter(Sha384::digest(f()?)),
IntegrityType::Sha512 => Vec::from_iter(Sha512::digest(f()?)),
};
Ok(Self { integrity, hash })
}
pub async fn generate_async<F, T, E, Fut>(integrity: IntegrityType, f: F) -> Result<Self, E>
where
F: FnOnce() -> Fut,
T: AsRef<[u8]>,
Fut: Future<Output = Result<T, E>>,
{
let hash = match integrity {
IntegrityType::None => vec![],
IntegrityType::Sha256 => Vec::from_iter(Sha256::digest(f().await?)),
IntegrityType::Sha384 => Vec::from_iter(Sha384::digest(f().await?)),
IntegrityType::Sha512 => Vec::from_iter(Sha512::digest(f().await?)),
};
Ok(Self { integrity, hash })
}
pub fn generate_from(integrity: IntegrityType, data: impl AsRef<[u8]>) -> Self {
Self::generate::<_, _, Infallible>(integrity, || Ok(data))
.unwrap()
}
}