1use 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
11mod listbuckets;
13pub use self::listbuckets::ListBuckets;
14
15mod listobjectsv2;
17pub use self::listobjectsv2::ListObjectsV2;
18
19mod listobjectversions;
21pub use self::listobjectversions::ListObjectVersions;
22
23mod headobject;
25pub use self::headobject::HeadObject;
26
27mod getobject;
29pub use self::getobject::GetObject;
30
31mod getobjectacl;
33pub use self::getobjectacl::GetObjectAcl;
34
35mod getobjectattributes;
37pub use self::getobjectattributes::GetObjectAttributes;
38
39mod putobject;
41pub use self::putobject::PutObject;
42
43mod putobjectacl;
45pub use self::putobjectacl::PutObjectAcl;
46
47mod createmultipartupload;
49pub use self::createmultipartupload::CreateMultipartUpload;
50
51mod uploadpart;
53pub use self::uploadpart::UploadPart;
54
55mod streampart;
56pub use self::streampart::StreamPart;
57
58mod completemultipartupload;
60pub use self::completemultipartupload::{CompleteMultipartUpload, Part};
61
62mod listmultipartuploads;
64pub use self::listmultipartuploads::ListMultipartUploads;
65
66mod abortmultipartupload;
68pub use self::abortmultipartupload::AbortMultipartUpload;
69
70mod deleteobject;
72pub use self::deleteobject::DeleteObject;
73
74mod createbucket;
76pub use self::createbucket::CreateBucket;
77
78mod deletebucket;
80pub use self::deletebucket::DeleteBucket;
81
82pub trait Action {
83 fn headers(&self) -> Option<BTreeMap<&str, &str>>;
85
86 fn http_method(&self) -> Result<Method>;
90
91 fn query_pairs(&self) -> Option<BTreeMap<&str, &str>>;
93
94 fn path(&self) -> Option<Vec<&str>>;
96
97 fn sign(
101 &self,
102 s3: &S3,
103 hash_payload: &[u8],
104 md5: Option<&[u8]>,
105 content_length: Option<usize>,
106 ) -> Result<(Url, BTreeMap<String, String>)> {
107 let mut url = s3.endpoint()?;
108
109 if let Some(path) = self.path() {
111 for p in path {
112 url.path_segments_mut()
113 .map_err(|e| anyhow!("cannot be base: {:#?}", e))?
114 .push(p);
115 }
116 }
117
118 if let Some(pairs) = &self.query_pairs() {
120 for (k, v) in pairs {
121 url.query_pairs_mut().append_pair(k, v);
122 }
123 }
124
125 if s3.no_sign_request {
127 return Ok((url, BTreeMap::new()));
128 }
129
130 log::info!("URL to sign: {url}");
131
132 let mut signature = Signature::new(s3, "s3", self.http_method()?)?;
133
134 let headers = signature.sign(&url, hash_payload, md5, content_length, self.headers());
135
136 Ok((url, headers?))
137 }
138}
139
140pub async fn response_error(response: Response) -> Result<String> {
143 let mut error: BTreeMap<&str, String> = BTreeMap::new();
144
145 error.insert("HTTP Status Code", response.status().to_string());
146
147 if let Some(x_amz_id_2) = response.headers().get("x-amz-id-2") {
148 error.insert("x-amz-id-2", x_amz_id_2.to_str()?.to_string());
149 }
150
151 if let Some(rid) = response.headers().get("x-amz-request-id") {
152 error.insert("Request ID", rid.to_str()?.to_string());
153 }
154
155 match response.text().await {
157 Ok(body) => {
158 if let Ok(e) = from_str::<ErrorResponse>(&body) {
159 error.insert("Code", e.code);
160 error.insert("Message", e.message);
161 } else if !body.is_empty() {
162 error.insert("Response", body);
163 }
164 }
165 Err(e) => {
166 log::warn!("Failed to read error response body: {}", e);
168 error.insert(
169 "Note",
170 "Connection closed before response body could be read".to_string(),
171 );
172 }
173 }
174
175 Ok(error.iter().fold(String::new(), |mut output, (k, v)| {
176 let _ = writeln!(output, "{k}: {v}");
177 output
178 }))
179}