1use http::HeaderValue;
2use itertools::{Either, Itertools};
3use thiserror::Error;
4
5use crate::header::{Header, PseudoHeader};
6
7#[derive(Debug, Error)]
10#[non_exhaustive]
11pub enum CanonicalizeError {
12 #[error("Missing headers required for signature: {0:?}")]
16 MissingHeaders(Vec<Header>),
17}
18
19pub trait RequestLike {
21 fn header(&self, header: &Header) -> Option<HeaderValue>;
25
26 fn has_header(&self, header: &Header) -> bool {
30 self.header(header).is_some()
31 }
32}
33
34impl<T: RequestLike> RequestLike for &T {
35 fn header(&self, header: &Header) -> Option<HeaderValue> {
36 (**self).header(header)
37 }
38}
39
40#[derive(Default)]
42pub struct CanonicalizeConfig {
43 headers: Option<Vec<Header>>,
44 signature_created: Option<HeaderValue>,
45 signature_expires: Option<HeaderValue>,
46}
47
48impl CanonicalizeConfig {
49 pub fn new() -> Self {
51 Self::default()
52 }
53 pub fn with_headers(mut self, headers: Vec<Header>) -> Self {
55 self.headers = Some(headers);
56 self
57 }
58 pub fn set_headers(&mut self, headers: Vec<Header>) -> &mut Self {
60 self.headers = Some(headers);
61 self
62 }
63 pub fn headers(&self) -> Option<impl IntoIterator<Item = &Header>> {
65 self.headers.as_ref()
66 }
67 pub fn with_signature_created(mut self, signature_created: HeaderValue) -> Self {
69 self.signature_created = Some(signature_created);
70 self
71 }
72 pub fn set_signature_created(&mut self, signature_created: HeaderValue) -> &mut Self {
74 self.signature_created = Some(signature_created);
75 self
76 }
77 pub fn signature_created(&self) -> Option<&HeaderValue> {
79 self.signature_created.as_ref()
80 }
81 pub fn with_signature_expires(mut self, signature_expires: HeaderValue) -> Self {
83 self.signature_expires = Some(signature_expires);
84 self
85 }
86 pub fn set_signature_expires(&mut self, signature_expires: HeaderValue) -> &mut Self {
88 self.signature_expires = Some(signature_expires);
89 self
90 }
91 pub fn signature_expires(&self) -> Option<&HeaderValue> {
93 self.signature_expires.as_ref()
94 }
95}
96
97pub trait CanonicalizeExt {
99 fn canonicalize(
101 &self,
102 config: &CanonicalizeConfig,
103 ) -> Result<SignatureString, CanonicalizeError>;
104}
105
106const DEFAULT_HEADERS: &[Header] = &[Header::Pseudo(PseudoHeader::Created)];
107
108pub struct SignatureString {
110 content: Vec<u8>,
111 pub(crate) headers: Vec<(Header, HeaderValue)>,
112}
113
114impl SignatureString {
115 pub fn as_bytes(&self) -> &[u8] {
117 &self.content
118 }
119}
120
121impl From<SignatureString> for Vec<u8> {
122 fn from(other: SignatureString) -> Self {
123 other.content
124 }
125}
126
127impl<T: RequestLike> CanonicalizeExt for T {
128 fn canonicalize(
129 &self,
130 config: &CanonicalizeConfig,
131 ) -> Result<SignatureString, CanonicalizeError> {
132 let (headers, missing_headers): (Vec<_>, Vec<_>) = config
134 .headers
135 .as_deref()
136 .unwrap_or(DEFAULT_HEADERS)
137 .iter()
138 .cloned()
139 .partition_map(|header| {
140 if let Some(header_value) = match header {
141 Header::Pseudo(PseudoHeader::Created) => config.signature_created.clone(),
142 Header::Pseudo(PseudoHeader::Expires) => config.signature_expires.clone(),
143 _ => self.header(&header),
144 } {
145 Either::Left((header, header_value))
146 } else {
147 Either::Right(header)
148 }
149 });
150
151 if !missing_headers.is_empty() {
153 return Err(CanonicalizeError::MissingHeaders(missing_headers));
154 }
155
156 let mut content = Vec::new();
158 for (name, value) in &headers {
159 if !content.is_empty() {
160 content.push(b'\n');
161 }
162 content.extend(name.as_str().as_bytes());
163 content.extend(b": ");
164 content.extend(value.as_bytes());
165 }
166
167 Ok(SignatureString { content, headers })
168 }
169}