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