rusty_s3/actions/
head_bucket.rs

1use std::time::Duration;
2
3use jiff::Timestamp;
4use url::Url;
5
6use super::S3Action;
7use crate::actions::Method;
8use crate::signing::sign;
9use crate::{Bucket, Credentials, Map};
10
11/// Retrieve an bucket's metadata from S3, using a `HEAD` request.
12///
13/// Find out more about `HeadBucket` from the [AWS API Reference][api]
14///
15/// [api]: https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadBucket.html
16#[derive(Debug, Clone)]
17pub struct HeadBucket<'a> {
18    bucket: &'a Bucket,
19    credentials: Option<&'a Credentials>,
20
21    query: Map<'a>,
22    headers: Map<'a>,
23}
24
25impl<'a> HeadBucket<'a> {
26    #[inline]
27    #[must_use]
28    pub const fn new(bucket: &'a Bucket, credentials: Option<&'a Credentials>) -> Self {
29        Self {
30            bucket,
31            credentials,
32
33            query: Map::new(),
34            headers: Map::new(),
35        }
36    }
37}
38
39impl<'a> S3Action<'a> for HeadBucket<'a> {
40    const METHOD: Method = Method::Head;
41
42    fn query_mut(&mut self) -> &mut Map<'a> {
43        &mut self.query
44    }
45
46    fn headers_mut(&mut self) -> &mut Map<'a> {
47        &mut self.headers
48    }
49
50    fn sign_with_time(&self, expires_in: Duration, time: &Timestamp) -> Url {
51        let url = self.bucket.base_url().clone();
52
53        match self.credentials {
54            Some(credentials) => sign(
55                time,
56                Self::METHOD,
57                url,
58                credentials.key(),
59                credentials.secret(),
60                credentials.token(),
61                self.bucket.region(),
62                expires_in.as_secs(),
63                self.query.iter(),
64                self.headers.iter(),
65            ),
66            None => crate::signing::util::add_query_params(url, self.query.iter()),
67        }
68    }
69}
70
71#[cfg(test)]
72mod tests {
73    use pretty_assertions::assert_eq;
74
75    use super::*;
76    use crate::{Bucket, Credentials, UrlStyle};
77
78    #[test]
79    fn aws_example() {
80        // Fri, 24 May 2013 00:00:00 GMT
81        let date = Timestamp::from_second(1369353600).unwrap();
82        let expires_in = Duration::from_secs(86400);
83
84        let endpoint = "https://s3.amazonaws.com".parse().unwrap();
85        let bucket = Bucket::new(
86            endpoint,
87            UrlStyle::VirtualHost,
88            "examplebucket",
89            "us-east-1",
90        )
91        .unwrap();
92        let credentials = Credentials::new(
93            "AKIAIOSFODNN7EXAMPLE",
94            "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
95        );
96
97        let action = HeadBucket::new(&bucket, Some(&credentials));
98
99        let url = action.sign_with_time(expires_in, &date);
100
101        let expected = "https://examplebucket.s3.amazonaws.com/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20130524T000000Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&X-Amz-Signature=97f0c782bfd320e7b75026ed746d7e0c759da7b6bf12ed485bbfef4530c16191";
102
103        assert_eq!(expected, url.as_str());
104    }
105
106    #[test]
107    fn aws_example_custom_query() {
108        // Fri, 24 May 2013 00:00:00 GMT
109        let date = Timestamp::from_second(1369353600).unwrap();
110        let expires_in = Duration::from_secs(86400);
111
112        let endpoint = "https://s3.amazonaws.com".parse().unwrap();
113        let bucket = Bucket::new(
114            endpoint,
115            UrlStyle::VirtualHost,
116            "examplebucket",
117            "us-east-1",
118        )
119        .unwrap();
120        let credentials = Credentials::new(
121            "AKIAIOSFODNN7EXAMPLE",
122            "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY",
123        );
124
125        let mut action = HeadBucket::new(&bucket, Some(&credentials));
126        action
127            .query_mut()
128            .insert("response-content-type", "text/plain");
129
130        let url = action.sign_with_time(expires_in, &date);
131        let expected = "https://examplebucket.s3.amazonaws.com/?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Credential=AKIAIOSFODNN7EXAMPLE%2F20130524%2Fus-east-1%2Fs3%2Faws4_request&X-Amz-Date=20130524T000000Z&X-Amz-Expires=86400&X-Amz-SignedHeaders=host&response-content-type=text%2Fplain&X-Amz-Signature=1f567b0987313c6ed9c0e92e4e3b70590f96e836b91033f659e6457bfa82dcd0";
132
133        assert_eq!(expected, url.as_str());
134    }
135
136    #[test]
137    fn anonymous_custom_query() {
138        let expires_in = Duration::from_secs(86400);
139
140        let endpoint = "https://s3.amazonaws.com".parse().unwrap();
141        let bucket = Bucket::new(
142            endpoint,
143            UrlStyle::VirtualHost,
144            "examplebucket",
145            "us-east-1",
146        )
147        .unwrap();
148
149        let mut action = HeadBucket::new(&bucket, None);
150        action
151            .query_mut()
152            .insert("response-content-type", "text/plain");
153
154        let url = action.sign(expires_in);
155        let expected = "https://examplebucket.s3.amazonaws.com/?response-content-type=text%2Fplain";
156
157        assert_eq!(expected, url.as_str());
158    }
159}