use http::HeaderValue;
use itertools::{Either, Itertools};
use thiserror::Error;
use crate::header::{Header, PseudoHeader};
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum CanonicalizeError {
#[error("Missing headers required for signature: {0:?}")]
MissingHeaders(Vec<Header>),
}
pub trait RequestLike {
fn header(&self, header: &Header) -> Option<HeaderValue>;
fn has_header(&self, header: &Header) -> bool {
self.header(header).is_some()
}
}
impl<T: RequestLike> RequestLike for &T {
fn header(&self, header: &Header) -> Option<HeaderValue> {
(**self).header(header)
}
}
#[derive(Default)]
pub struct CanonicalizeConfig {
headers: Option<Vec<Header>>,
signature_created: Option<HeaderValue>,
signature_expires: Option<HeaderValue>,
}
impl CanonicalizeConfig {
pub fn new() -> Self {
Self::default()
}
pub fn with_headers(mut self, headers: Vec<Header>) -> Self {
self.headers = Some(headers);
self
}
pub fn set_headers(&mut self, headers: Vec<Header>) -> &mut Self {
self.headers = Some(headers);
self
}
pub fn headers(&self) -> Option<impl IntoIterator<Item = &Header>> {
self.headers.as_ref()
}
pub fn with_signature_created(mut self, signature_created: HeaderValue) -> Self {
self.signature_created = Some(signature_created);
self
}
pub fn set_signature_created(&mut self, signature_created: HeaderValue) -> &mut Self {
self.signature_created = Some(signature_created);
self
}
pub fn signature_created(&self) -> Option<&HeaderValue> {
self.signature_created.as_ref()
}
pub fn with_signature_expires(mut self, signature_expires: HeaderValue) -> Self {
self.signature_expires = Some(signature_expires);
self
}
pub fn set_signature_expires(&mut self, signature_expires: HeaderValue) -> &mut Self {
self.signature_expires = Some(signature_expires);
self
}
pub fn signature_expires(&self) -> Option<&HeaderValue> {
self.signature_expires.as_ref()
}
}
pub trait CanonicalizeExt {
fn canonicalize(
&self,
config: &CanonicalizeConfig,
) -> Result<SignatureString, CanonicalizeError>;
}
const DEFAULT_HEADERS: &[Header] = &[Header::Pseudo(PseudoHeader::Created)];
pub struct SignatureString {
content: Vec<u8>,
pub(crate) headers: Vec<(Header, HeaderValue)>,
}
impl SignatureString {
pub fn as_bytes(&self) -> &[u8] {
&self.content
}
}
impl From<SignatureString> for Vec<u8> {
fn from(other: SignatureString) -> Self {
other.content
}
}
impl<T: RequestLike> CanonicalizeExt for T {
fn canonicalize(
&self,
config: &CanonicalizeConfig,
) -> Result<SignatureString, CanonicalizeError> {
let (headers, missing_headers): (Vec<_>, Vec<_>) = config
.headers
.as_deref()
.unwrap_or(DEFAULT_HEADERS)
.iter()
.cloned()
.partition_map(|header| {
if let Some(header_value) = match header {
Header::Pseudo(PseudoHeader::Created) => config.signature_created.clone(),
Header::Pseudo(PseudoHeader::Expires) => config.signature_expires.clone(),
_ => self.header(&header),
} {
Either::Left((header, header_value))
} else {
Either::Right(header)
}
});
if !missing_headers.is_empty() {
return Err(CanonicalizeError::MissingHeaders(missing_headers));
}
let mut content = Vec::new();
for (name, value) in &headers {
if !content.is_empty() {
content.push(b'\n');
}
content.extend(name.as_str().as_bytes());
content.extend(b": ");
content.extend(value.as_bytes());
}
Ok(SignatureString { content, headers })
}
}