1use base64::engine::general_purpose;
2use base64::Engine;
3use hmac::Mac;
4use http_body_util::BodyStream;
5use hyper::body::Incoming;
6use std::collections::HashMap;
7use time::format_description::well_known::Rfc2822;
8use time::OffsetDateTime;
9use url::Url;
10
11use crate::bucket::Bucket;
12use crate::command::Command;
13use crate::error::S3Error;
14use crate::signing;
15use crate::LONG_DATETIME;
16use bytes::Bytes;
17use http::header::{
18 HeaderName, ACCEPT, AUTHORIZATION, CONTENT_LENGTH, CONTENT_TYPE, DATE, HOST, RANGE,
19};
20use http::HeaderMap;
21use std::fmt::Write as _;
22
23#[derive(Debug)]
24
25pub struct ResponseData {
26 bytes: Bytes,
27 status_code: u16,
28 headers: HashMap<String, String>,
29}
30
31impl From<ResponseData> for Vec<u8> {
32 fn from(data: ResponseData) -> Vec<u8> {
33 data.to_vec()
34 }
35}
36
37impl ResponseData {
38 pub fn new(bytes: Bytes, status_code: u16, headers: HashMap<String, String>) -> ResponseData {
39 ResponseData {
40 bytes,
41 status_code,
42 headers,
43 }
44 }
45
46 pub fn as_slice(&self) -> &[u8] {
47 &self.bytes
48 }
49
50 pub fn to_vec(self) -> Vec<u8> {
51 self.bytes.to_vec()
52 }
53
54 pub fn bytes(&self) -> &Bytes {
55 &self.bytes
56 }
57
58 pub fn status_code(&self) -> u16 {
59 self.status_code
60 }
61
62 pub fn as_str(&self) -> Result<&str, std::str::Utf8Error> {
63 std::str::from_utf8(self.as_slice())
64 }
65
66 pub fn to_string(&self) -> Result<String, std::str::Utf8Error> {
67 std::str::from_utf8(self.as_slice()).map(|s| s.to_string())
68 }
69
70 pub fn headers(&self) -> HashMap<String, String> {
71 self.headers.clone()
72 }
73}
74
75use std::fmt;
76
77impl fmt::Display for ResponseData {
78 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
79 write!(
80 f,
81 "Status code: {}\n Data: {}",
82 self.status_code(),
83 self.to_string()
84 .unwrap_or_else(|_| "Data could not be cast to UTF string".to_string())
85 )
86 }
87}
88
89pub type DataStream = BodyStream<Incoming>;
90
91pub struct ResponseDataStream {
92 pub body_stream: DataStream,
93 pub status_code: u16,
94}
95
96impl ResponseDataStream {
97 pub fn body_stream(&mut self) -> &mut DataStream {
98 &mut self.body_stream
99 }
100}
101
102pub trait Request: Send + Sync {
103 type Response;
104 type HeaderMap;
105
106 fn response(&self)
107 -> impl std::future::Future<Output = Result<Self::Response, S3Error>> + Send;
108 fn response_data(
109 &self,
110 etag: bool,
111 ) -> impl std::future::Future<Output = Result<ResponseData, S3Error>> + Send;
112 fn response_data_to_writer<T: tokio::io::AsyncWrite + Send + Unpin>(
113 &self,
114 writer: &mut T,
115 ) -> impl std::future::Future<Output = Result<u16, S3Error>> + Send;
116 fn response_data_to_stream(
117 &self,
118 ) -> impl std::future::Future<Output = Result<ResponseDataStream, S3Error>> + Send;
119 fn response_header(
120 &self,
121 ) -> impl std::future::Future<Output = Result<(Self::HeaderMap, u16), S3Error>> + Send;
122 fn datetime(&self) -> OffsetDateTime;
123 fn bucket(&self) -> Bucket;
124 fn command(&self) -> Command;
125 fn path(&self) -> String;
126
127 fn signing_key(&self) -> Result<Vec<u8>, S3Error> {
128 signing::signing_key(
129 &self.datetime(),
130 &self
131 .bucket()
132 .secret_key()?
133 .expect("Secret key must be provided to sign headers, found None"),
134 &self.bucket().region(),
135 "s3",
136 )
137 }
138
139 fn request_body(&self) -> Vec<u8> {
140 match self.command() {
141 Command::PutObject { content, .. } => Vec::from(content),
142 Command::PutObjectTagging { tags } => Vec::from(tags),
143 Command::UploadPart { content, .. } => Vec::from(content),
144 Command::CompleteMultipartUpload { data, .. } => data.to_string().as_bytes().to_vec(),
145 Command::CreateBucket { config } => config
146 .location_constraint_payload()
147 .map(Vec::from)
148 .unwrap_or_default(),
149 _ => vec![],
150 }
151 }
152
153 fn long_date(&self) -> Result<String, S3Error> {
154 Ok(self.datetime().format(LONG_DATETIME)?)
155 }
156
157 fn string_to_sign(&self, request: &str) -> Result<String, S3Error> {
158 match self.command() {
159 Command::PresignPost { post_policy, .. } => Ok(post_policy),
160 _ => Ok(signing::string_to_sign(
161 &self.datetime(),
162 &self.bucket().region(),
163 request,
164 )?),
165 }
166 }
167
168 fn host_header(&self) -> String {
169 self.bucket().host()
170 }
171
172 fn presigned(&self) -> Result<String, S3Error> {
173 let (expiry, custom_headers, custom_queries) = match self.command() {
174 Command::PresignGet {
175 expiry_secs,
176 custom_queries,
177 } => (expiry_secs, None, custom_queries),
178 Command::PresignPut {
179 expiry_secs,
180 custom_headers,
181 } => (expiry_secs, custom_headers, None),
182 Command::PresignDelete { expiry_secs } => (expiry_secs, None, None),
183 _ => unreachable!(),
184 };
185
186 Ok(format!(
187 "{}&X-Amz-Signature={}",
188 self.presigned_url_no_sig(expiry, custom_headers.as_ref(), custom_queries.as_ref())?,
189 self.presigned_authorization(custom_headers.as_ref())?
190 ))
191 }
192
193 fn presigned_authorization(
194 &self,
195 custom_headers: Option<&HeaderMap>,
196 ) -> Result<String, S3Error> {
197 let mut headers = HeaderMap::new();
198 let host_header = self.host_header();
199 headers.insert(HOST, host_header.parse()?);
200 if let Some(custom_headers) = custom_headers {
201 for (k, v) in custom_headers.iter() {
202 headers.insert(k.clone(), v.clone());
203 }
204 }
205 let canonical_request = self.presigned_canonical_request(&headers)?;
206 let string_to_sign = self.string_to_sign(&canonical_request)?;
207 let mut hmac = signing::HmacSha256::new_from_slice(&self.signing_key()?)?;
208 hmac.update(string_to_sign.as_bytes());
209 let signature = hex::encode(hmac.finalize().into_bytes());
210 Ok(signature)
212 }
213
214 fn presigned_canonical_request(&self, headers: &HeaderMap) -> Result<String, S3Error> {
215 let (expiry, custom_headers, custom_queries) = match self.command() {
216 Command::PresignGet {
217 expiry_secs,
218 custom_queries,
219 } => (expiry_secs, None, custom_queries),
220 Command::PresignPut {
221 expiry_secs,
222 custom_headers,
223 } => (expiry_secs, custom_headers, None),
224 Command::PresignDelete { expiry_secs } => (expiry_secs, None, None),
225 _ => unreachable!(),
226 };
227
228 signing::canonical_request(
229 &self.command().http_verb().to_string(),
230 &self.presigned_url_no_sig(expiry, custom_headers.as_ref(), custom_queries.as_ref())?,
231 headers,
232 "UNSIGNED-PAYLOAD",
233 )
234 }
235
236 fn presigned_url_no_sig(
237 &self,
238 expiry: u32,
239 custom_headers: Option<&HeaderMap>,
240 custom_queries: Option<&HashMap<String, String>>,
241 ) -> Result<Url, S3Error> {
242 let bucket = self.bucket();
243 let token = if let Some(security_token) = bucket.security_token()? {
244 Some(security_token)
245 } else {
246 bucket.session_token()?
247 };
248 let url = Url::parse(&format!(
249 "{}{}{}",
250 self.url()?,
251 &signing::authorization_query_params_no_sig(
252 &self.bucket().access_key()?.unwrap_or_default(),
253 &self.datetime(),
254 &self.bucket().region(),
255 expiry,
256 custom_headers,
257 token.as_ref()
258 )?,
259 &signing::flatten_queries(custom_queries)?,
260 ))?;
261
262 Ok(url)
263 }
264
265 fn url(&self) -> Result<Url, S3Error> {
266 let mut url_str = self.bucket().url();
267
268 if let Command::ListBuckets { .. } = self.command() {
269 return Ok(Url::parse(&url_str)?);
270 }
271
272 if let Command::CreateBucket { .. } = self.command() {
273 return Ok(Url::parse(&url_str)?);
274 }
275
276 let path = if self.path().starts_with('/') {
277 self.path()[1..].to_string()
278 } else {
279 self.path()[..].to_string()
280 };
281
282 url_str.push('/');
283 url_str.push_str(&signing::uri_encode(&path, false));
284
285 #[allow(clippy::collapsible_match)]
287 match self.command() {
288 Command::InitiateMultipartUpload { .. } | Command::ListMultipartUploads { .. } => {
289 url_str.push_str("?uploads")
290 }
291 Command::AbortMultipartUpload { upload_id } => {
292 write!(url_str, "?uploadId={}", upload_id).expect("Could not write to url_str");
293 }
294 Command::CompleteMultipartUpload { upload_id, .. } => {
295 write!(url_str, "?uploadId={}", upload_id).expect("Could not write to url_str");
296 }
297 Command::GetObjectTorrent => url_str.push_str("?torrent"),
298 Command::PutObject { multipart, .. } => {
299 if let Some(multipart) = multipart {
300 url_str.push_str(&multipart.query_string())
301 }
302 }
303 _ => {}
304 }
305
306 let mut url = Url::parse(&url_str)?;
307
308 for (key, value) in &self.bucket().extra_query {
309 url.query_pairs_mut().append_pair(key, value);
310 }
311
312 if let Command::ListObjectsV2 {
313 prefix,
314 delimiter,
315 continuation_token,
316 start_after,
317 max_keys,
318 } = self.command().clone()
319 {
320 let mut query_pairs = url.query_pairs_mut();
321 delimiter.map(|d| query_pairs.append_pair("delimiter", &d));
322
323 query_pairs.append_pair("prefix", &prefix);
324 query_pairs.append_pair("list-type", "2");
325 if let Some(token) = continuation_token {
326 query_pairs.append_pair("continuation-token", &token);
327 }
328 if let Some(start_after) = start_after {
329 query_pairs.append_pair("start-after", &start_after);
330 }
331 if let Some(max_keys) = max_keys {
332 query_pairs.append_pair("max-keys", &max_keys.to_string());
333 }
334 }
335
336 if let Command::ListObjects {
337 prefix,
338 delimiter,
339 marker,
340 max_keys,
341 } = self.command().clone()
342 {
343 let mut query_pairs = url.query_pairs_mut();
344 delimiter.map(|d| query_pairs.append_pair("delimiter", &d));
345
346 query_pairs.append_pair("prefix", &prefix);
347 if let Some(marker) = marker {
348 query_pairs.append_pair("marker", &marker);
349 }
350 if let Some(max_keys) = max_keys {
351 query_pairs.append_pair("max-keys", &max_keys.to_string());
352 }
353 }
354
355 match self.command() {
356 Command::ListMultipartUploads {
357 prefix,
358 delimiter,
359 key_marker,
360 max_uploads,
361 } => {
362 let mut query_pairs = url.query_pairs_mut();
363 delimiter.map(|d| query_pairs.append_pair("delimiter", d));
364 if let Some(prefix) = prefix {
365 query_pairs.append_pair("prefix", prefix);
366 }
367 if let Some(key_marker) = key_marker {
368 query_pairs.append_pair("key-marker", &key_marker);
369 }
370 if let Some(max_uploads) = max_uploads {
371 query_pairs.append_pair("max-uploads", max_uploads.to_string().as_str());
372 }
373 }
374 Command::PutObjectTagging { .. }
375 | Command::GetObjectTagging
376 | Command::DeleteObjectTagging => {
377 url.query_pairs_mut().append_pair("tagging", "");
378 }
379 _ => {}
380 }
381
382 Ok(url)
383 }
384
385 fn canonical_request(&self, headers: &HeaderMap) -> Result<String, S3Error> {
386 signing::canonical_request(
387 &self.command().http_verb().to_string(),
388 &self.url()?,
389 headers,
390 &self.command().sha256(),
391 )
392 }
393
394 fn authorization(&self, headers: &HeaderMap) -> Result<String, S3Error> {
395 let canonical_request = self.canonical_request(headers)?;
396 let string_to_sign = self.string_to_sign(&canonical_request)?;
397 let mut hmac = signing::HmacSha256::new_from_slice(&self.signing_key()?)?;
398 hmac.update(string_to_sign.as_bytes());
399 let signature = hex::encode(hmac.finalize().into_bytes());
400 let signed_header = signing::signed_header_string(headers);
401 signing::authorization_header(
402 &self.bucket().access_key()?.expect("No access_key provided"),
403 &self.datetime(),
404 &self.bucket().region(),
405 &signed_header,
406 &signature,
407 )
408 }
409
410 fn headers(&self) -> Result<HeaderMap, S3Error> {
411 let sha256 = self.command().sha256();
413
414 let mut headers = HeaderMap::new();
418
419 for (k, v) in self.bucket().extra_headers.iter() {
420 headers.insert(k.clone(), v.clone());
421 }
422
423 let host_header = self.host_header();
424
425 headers.insert(HOST, host_header.parse()?);
426
427 match self.command() {
428 Command::CopyObject { from } => {
429 headers.insert(HeaderName::from_static("x-amz-copy-source"), from.parse()?);
430 }
431 Command::ListObjects { .. } => {}
432 Command::ListObjectsV2 { .. } => {}
433 Command::GetObject => {}
434 Command::GetObjectTagging => {}
435 Command::GetBucketLocation => {}
436 _ => {
437 headers.insert(
438 CONTENT_LENGTH,
439 self.command().content_length().to_string().parse()?,
440 );
441 headers.insert(CONTENT_TYPE, self.command().content_type().parse()?);
442 }
443 }
444 headers.insert(
445 HeaderName::from_static("x-amz-content-sha256"),
446 sha256.parse()?,
447 );
448 headers.insert(
449 HeaderName::from_static("x-amz-date"),
450 self.long_date()?.parse()?,
451 );
452
453 if let Some(session_token) = self.bucket().session_token()? {
454 headers.insert(
455 HeaderName::from_static("x-amz-security-token"),
456 session_token.parse()?,
457 );
458 } else if let Some(security_token) = self.bucket().security_token()? {
459 headers.insert(
460 HeaderName::from_static("x-amz-security-token"),
461 security_token.parse()?,
462 );
463 }
464
465 if let Command::PutObjectTagging { tags } = self.command() {
466 let digest = md5::compute(tags);
467 let hash = general_purpose::STANDARD.encode(digest.as_ref());
468 headers.insert(HeaderName::from_static("content-md5"), hash.parse()?);
469 } else if let Command::PutObject { content, .. } = self.command() {
470 let digest = md5::compute(content);
471 let hash = general_purpose::STANDARD.encode(digest.as_ref());
472 headers.insert(HeaderName::from_static("content-md5"), hash.parse()?);
473 } else if let Command::UploadPart { content, .. } = self.command() {
474 let digest = md5::compute(content);
475 let hash = general_purpose::STANDARD.encode(digest.as_ref());
476 headers.insert(HeaderName::from_static("content-md5"), hash.parse()?);
477 } else if let Command::GetObject {} = self.command() {
478 headers.insert(ACCEPT, "application/octet-stream".to_string().parse()?);
479 } else if let Command::GetObjectRange { start, end } = self.command() {
481 headers.insert(ACCEPT, "application/octet-stream".to_string().parse()?);
482
483 let mut range = format!("bytes={}-", start);
484
485 if let Some(end) = end {
486 range.push_str(&end.to_string());
487 }
488
489 headers.insert(RANGE, range.parse()?);
490 } else if let Command::CreateBucket { ref config } = self.command() {
491 config.add_headers(&mut headers)?;
492 }
493
494 if self.bucket().secret_key()?.is_some() {
496 let authorization = self.authorization(&headers)?;
497 headers.insert(AUTHORIZATION, authorization.parse()?);
498 }
499
500 headers.insert(DATE, self.datetime().format(&Rfc2822)?.parse()?);
507
508 Ok(headers)
509 }
510}