1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
use http::HeaderValue;
use itertools::{Either, Itertools};
use thiserror::Error;

use crate::header::{Header, PseudoHeader};

/// The types of error which may occur whilst computing the canonical "signature string"
/// for a request.
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum CanonicalizeError {
    /// One or more headers required to be part of the signature was not present
    /// on the request, and the `skip_missing` configuration option
    /// was disabled.
    #[error("Missing headers required for signature: {0:?}")]
    MissingHeaders(Vec<Header>),
}

/// Base trait for all request types
pub trait RequestLike {
    /// Returns an existing header on the request. This method *must* reflect changes made
    /// be the `ClientRequestLike::set_header` method, with the possible exception of the
    /// `Authorization` header itself.
    fn header(&self, header: &Header) -> Option<HeaderValue>;

    /// Returns true if this request contains a value for the specified header. If this
    /// returns true, following requests to `header()` for the same name must return a
    /// value.
    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)
    }
}

/// Configuration for computing the canonical "signature string" of a request.
#[derive(Default)]
pub struct CanonicalizeConfig {
    headers: Option<Vec<Header>>,
    signature_created: Option<HeaderValue>,
    signature_expires: Option<HeaderValue>,
}

impl CanonicalizeConfig {
    /// Creates a new canonicalization configuration using the default values.
    pub fn new() -> Self {
        Self::default()
    }
    /// Set the headers to include in the signature
    pub fn with_headers(mut self, headers: Vec<Header>) -> Self {
        self.headers = Some(headers);
        self
    }
    /// Set the headers to include in the signature
    pub fn set_headers(&mut self, headers: Vec<Header>) -> &mut Self {
        self.headers = Some(headers);
        self
    }
    /// Get the headers to include in the signature
    pub fn headers(&self) -> Option<impl IntoIterator<Item = &Header>> {
        self.headers.as_ref()
    }
    /// Set the "signature created" pseudo-header
    pub fn with_signature_created(mut self, signature_created: HeaderValue) -> Self {
        self.signature_created = Some(signature_created);
        self
    }
    /// Set the "signature created" pseudo-header
    pub fn set_signature_created(&mut self, signature_created: HeaderValue) -> &mut Self {
        self.signature_created = Some(signature_created);
        self
    }
    /// Get the "signature created" pseudo-header
    pub fn signature_created(&self) -> Option<&HeaderValue> {
        self.signature_created.as_ref()
    }
    /// Set the "signature expires" pseudo-header
    pub fn with_signature_expires(mut self, signature_expires: HeaderValue) -> Self {
        self.signature_expires = Some(signature_expires);
        self
    }
    /// Set the "signature expires" pseudo-header
    pub fn set_signature_expires(&mut self, signature_expires: HeaderValue) -> &mut Self {
        self.signature_expires = Some(signature_expires);
        self
    }
    /// Get the "signature expires" pseudo-header
    pub fn signature_expires(&self) -> Option<&HeaderValue> {
        self.signature_expires.as_ref()
    }
}

/// Extension method for computing the canonical "signature string" of a request.
pub trait CanonicalizeExt {
    /// Compute the canonical representation of this request
    fn canonicalize(
        &self,
        config: &CanonicalizeConfig,
    ) -> Result<SignatureString, CanonicalizeError>;
}

const DEFAULT_HEADERS: &[Header] = &[Header::Pseudo(PseudoHeader::Created)];

/// Opaque struct storing a computed signature string.
pub struct SignatureString {
    content: Vec<u8>,
    pub(crate) headers: Vec<(Header, HeaderValue)>,
}

impl SignatureString {
    /// Obtain a view of this signature string as a byte slice
    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> {
        // Find value of each header
        let (headers, missing_headers): (Vec<_>, Vec<_>) = config
            .headers
            .as_deref()
            .unwrap_or_else(|| 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)
                }
            });

        // Check for missing headers
        if !missing_headers.is_empty() {
            return Err(CanonicalizeError::MissingHeaders(missing_headers));
        }

        // Build signature string block
        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 })
    }
}