Skip to main content

s3m/s3/actions/
mod.rs

1//! Actions
2//! <https://docs.aws.amazon.com/AmazonS3/latest/API/API_Operations.html>
3
4use crate::s3::{S3, responses::ErrorResponse, signature::Signature};
5use anyhow::{Result, anyhow};
6use quick_xml::de::from_str;
7use reqwest::{Method, Response};
8use std::{collections::BTreeMap, fmt::Write};
9use url::Url;
10
11// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListBuckets.html>
12mod listbuckets;
13pub use self::listbuckets::ListBuckets;
14
15// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectsV2.html>
16mod listobjectsv2;
17pub use self::listobjectsv2::ListObjectsV2;
18
19// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListObjectVersions.html>
20mod listobjectversions;
21pub use self::listobjectversions::ListObjectVersions;
22
23// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_HeadObject.html>
24mod headobject;
25pub use self::headobject::HeadObject;
26
27// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObject.html>
28mod getobject;
29pub use self::getobject::GetObject;
30
31// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAcl.html>
32mod getobjectacl;
33pub use self::getobjectacl::GetObjectAcl;
34
35// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_GetObjectAttributes.html>
36mod getobjectattributes;
37pub use self::getobjectattributes::GetObjectAttributes;
38
39// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObject.html>
40mod putobject;
41pub use self::putobject::PutObject;
42
43// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_PutObjectAcl.html>
44mod putobjectacl;
45pub use self::putobjectacl::PutObjectAcl;
46
47// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateMultipartUpload.html>
48mod createmultipartupload;
49pub use self::createmultipartupload::CreateMultipartUpload;
50
51// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_UploadPart.html>
52mod uploadpart;
53pub use self::uploadpart::UploadPart;
54
55mod streampart;
56pub use self::streampart::StreamPart;
57
58// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_CompleteMultipartUpload.html>
59mod completemultipartupload;
60pub use self::completemultipartupload::{CompleteMultipartUpload, Part};
61
62// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_ListMultipartUploads.html>
63mod listmultipartuploads;
64pub use self::listmultipartuploads::ListMultipartUploads;
65
66// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_AbortMultipartUpload.html>
67mod abortmultipartupload;
68pub use self::abortmultipartupload::AbortMultipartUpload;
69
70// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObject.html>
71mod deleteobject;
72pub use self::deleteobject::DeleteObject;
73
74// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteObjects.html>
75mod deleteobjects;
76pub use self::deleteobjects::{DeleteObjects, ObjectIdentifier};
77
78// <https://docs.aws.amazon.com/AmazonS3/latest/API/API_CreateBucket.html>
79mod createbucket;
80pub use self::createbucket::CreateBucket;
81
82// https://docs.aws.amazon.com/AmazonS3/latest/API/API_DeleteBucket.html
83mod deletebucket;
84pub use self::deletebucket::DeleteBucket;
85
86pub trait Action {
87    // headers to send in the request
88    fn headers(&self) -> Option<BTreeMap<&str, &str>>;
89
90    // HTTP method to use
91    /// # Errors
92    /// Will return an error if the HTTP method is not supported
93    fn http_method(&self) -> Result<Method>;
94
95    // URL query pairs
96    fn query_pairs(&self) -> Option<BTreeMap<&str, &str>>;
97
98    // URL path
99    fn path(&self) -> Option<Vec<&str>>;
100
101    /// # Errors
102    ///
103    /// Will return `Err` if the signature can not be created
104    fn sign(
105        &self,
106        s3: &S3,
107        hash_payload: &[u8],
108        md5: Option<&[u8]>,
109        content_length: Option<usize>,
110    ) -> Result<(Url, BTreeMap<String, String>)> {
111        let mut url = s3.endpoint()?;
112
113        // mainly for PUT when uploading an object
114        if let Some(path) = self.path() {
115            for p in path {
116                url.path_segments_mut()
117                    .map_err(|e| anyhow!("cannot be base: {e:#?}"))?
118                    .push(p);
119            }
120        }
121
122        // GET - query pairs
123        if let Some(pairs) = &self.query_pairs() {
124            for (k, v) in pairs {
125                url.query_pairs_mut().append_pair(k, v);
126            }
127        }
128
129        // --no-sign-request
130        if s3.no_sign_request {
131            return Ok((url, BTreeMap::new()));
132        }
133
134        log::info!("URL to sign: {url}");
135
136        let mut signature = Signature::new(s3, "s3", self.http_method()?)?;
137
138        let headers = signature.sign(&url, hash_payload, md5, content_length, self.headers());
139
140        Ok((url, headers?))
141    }
142}
143
144/// # Errors
145/// Will return an error if can't add the headers to the request
146pub async fn response_error(response: Response) -> Result<String> {
147    let mut error: BTreeMap<&str, String> = BTreeMap::new();
148
149    error.insert("HTTP Status Code", response.status().to_string());
150
151    if let Some(x_amz_id_2) = response.headers().get("x-amz-id-2") {
152        error.insert("x-amz-id-2", x_amz_id_2.to_str()?.to_string());
153    }
154
155    if let Some(rid) = response.headers().get("x-amz-request-id") {
156        error.insert("Request ID", rid.to_str()?.to_string());
157    }
158
159    // Try to read the response body, but don't fail if we can't
160    match response.text().await {
161        Ok(body) => {
162            if let Ok(e) = from_str::<ErrorResponse>(&body) {
163                error.insert("Code", e.code);
164                error.insert("Message", e.message);
165            } else if !body.is_empty() {
166                error.insert("Response", body);
167            }
168        }
169        Err(e) => {
170            // If we can't read the body, still report what we know
171            log::warn!("Failed to read error response body: {e}");
172            error.insert(
173                "Note",
174                "Connection closed before response body could be read".to_string(),
175            );
176        }
177    }
178
179    Ok(error.iter().fold(String::new(), |mut output, (k, v)| {
180        let _ = writeln!(output, "{k}: {v}");
181        output
182    }))
183}