1use std::str::FromStr;
2
3use crate::error::S3Error;
4use crate::request::ResponseData;
5use crate::{bucket::CHUNK_SIZE, serde_types::HeadObjectResult};
6
7use crate::request::{AsyncRead, AsyncReadExt};
8use std::fs::File;
9use std::io::Read;
10use std::path::Path;
11
12pub struct PutStreamResponse {
13 status_code: u16,
14 uploaded_bytes: usize,
15}
16
17impl PutStreamResponse {
18 pub fn new(status_code: u16, uploaded_bytes: usize) -> Self {
19 Self {
20 status_code,
21 uploaded_bytes,
22 }
23 }
24 pub fn status_code(&self) -> u16 {
25 self.status_code
26 }
27
28 pub fn uploaded_bytes(&self) -> usize {
29 self.uploaded_bytes
30 }
31}
32
33pub fn etag_for_path(path: impl AsRef<Path>) -> Result<String, S3Error> {
42 let mut file = File::open(path)?;
43 let mut last_digest: [u8; 16];
44 let mut digests = Vec::new();
45 let mut chunks = 0;
46 loop {
47 let chunk = read_chunk(&mut file)?;
48 last_digest = md5::compute(&chunk).into();
49 digests.extend_from_slice(&last_digest);
50 chunks += 1;
51 if chunk.len() < CHUNK_SIZE {
52 break;
53 }
54 }
55 let etag = if chunks <= 1 {
56 format!("{:x}", md5::Digest(last_digest))
57 } else {
58 let digest = format!("{:x}", md5::compute(digests));
59 format!("{}-{}", digest, chunks)
60 };
61 Ok(etag)
62}
63
64pub fn read_chunk<R: Read>(reader: &mut R) -> Result<Vec<u8>, S3Error> {
65 let mut chunk = Vec::with_capacity(CHUNK_SIZE);
66 let mut take = reader.take(CHUNK_SIZE as u64);
67 take.read_to_end(&mut chunk)?;
68
69 Ok(chunk)
70}
71
72pub async fn read_chunk_async<R: AsyncRead + Unpin>(reader: &mut R) -> Result<Vec<u8>, S3Error> {
73 let mut chunk = Vec::with_capacity(CHUNK_SIZE);
74 let mut take = reader.take(CHUNK_SIZE as u64);
75 take.read_to_end(&mut chunk).await?;
76
77 Ok(chunk)
78}
79
80pub trait GetAndConvertHeaders {
81 fn get_and_convert<T: FromStr>(&self, header: &str) -> Option<T>;
82 fn get_string(&self, header: &str) -> Option<String>;
83}
84
85impl GetAndConvertHeaders for http::header::HeaderMap {
86 fn get_and_convert<T: FromStr>(&self, header: &str) -> Option<T> {
87 self.get(header)?.to_str().ok()?.parse::<T>().ok()
88 }
89 fn get_string(&self, header: &str) -> Option<String> {
90 Some(self.get(header)?.to_str().ok()?.to_owned())
91 }
92}
93
94impl From<&http::HeaderMap> for HeadObjectResult {
95 fn from(headers: &http::HeaderMap) -> Self {
96 let mut result = HeadObjectResult {
97 accept_ranges: headers.get_string("accept-ranges"),
98 cache_control: headers.get_string("Cache-Control"),
99 content_disposition: headers.get_string("Content-Disposition"),
100 content_encoding: headers.get_string("Content-Encoding"),
101 content_language: headers.get_string("Content-Language"),
102 content_length: headers.get_and_convert("Content-Length"),
103 content_type: headers.get_string("Content-Type"),
104 delete_marker: headers.get_and_convert("x-amz-delete-marker"),
105 e_tag: headers.get_string("ETag"),
106 expiration: headers.get_string("x-amz-expiration"),
107 expires: headers.get_and_convert("Expires"),
108 last_modified: headers.get_and_convert("Last-Modified"),
109 ..Default::default()
110 };
111 let mut values = ::std::collections::HashMap::new();
112 for (key, value) in headers.iter() {
113 if key.as_str().starts_with("x-amz-meta-") {
114 if let Ok(value) = value.to_str() {
115 values.insert(
116 key.as_str()["x-amz-meta-".len()..].to_owned(),
117 value.to_owned(),
118 );
119 }
120 }
121 }
122 result.metadata = Some(values);
123 result.missing_meta = headers.get_and_convert("x-amz-missing-meta");
124 result.object_lock_legal_hold_status = headers.get_string("x-amz-object-lock-legal-hold");
125 result.object_lock_mode = headers.get_string("x-amz-object-lock-mode");
126 result.object_lock_retain_until_date =
127 headers.get_string("x-amz-object-lock-retain-until-date");
128 result.parts_count = headers.get_and_convert("x-amz-mp-parts-count");
129 result.replication_status = headers.get_string("x-amz-replication-status");
130 result.request_charged = headers.get_string("x-amz-request-charged");
131 result.restore = headers.get_string("x-amz-restore");
132 result.sse_customer_algorithm =
133 headers.get_string("x-amz-server-side-encryption-customer-algorithm");
134 result.sse_customer_key_md5 =
135 headers.get_string("x-amz-server-side-encryption-customer-key-MD5");
136 result.ssekms_key_id = headers.get_string("x-amz-server-side-encryption-aws-kms-key-id");
137 result.server_side_encryption = headers.get_string("x-amz-server-side-encryption");
138 result.storage_class = headers.get_string("x-amz-storage-class");
139 result.version_id = headers.get_string("x-amz-version-id");
140 result.website_redirect_location = headers.get_string("x-amz-website-redirect-location");
141 result
142 }
143}
144
145pub(crate) fn error_from_response_data(response_data: ResponseData) -> Result<S3Error, S3Error> {
146 let utf8_content = String::from_utf8(response_data.as_slice().to_vec())?;
147 Err(S3Error::HttpFailWithBody(
148 response_data.status_code(),
149 utf8_content,
150 ))
151}
152
153#[cfg(test)]
154mod test {
155 use crate::utils::etag_for_path;
156 use std::fs::File;
157 use std::io::prelude::*;
158 use std::io::Cursor;
159
160 fn object(size: u32) -> Vec<u8> {
161 (0..size).map(|_| 33).collect()
162 }
163
164 #[test]
165 fn test_etag_large_file() {
166 let path = "test_etag";
167 std::fs::remove_file(path).unwrap_or(());
168 let test: Vec<u8> = object(10_000_000);
169
170 let mut file = File::create(path).unwrap();
171 file.write_all(&test).unwrap();
172
173 let etag = etag_for_path(path).unwrap();
174
175 std::fs::remove_file(path).unwrap_or(());
176
177 assert_eq!(etag, "e438487f09f09c042b2de097765e5ac2-2");
178 }
179
180 #[test]
181 fn test_etag_small_file() {
182 let path = "test_etag";
183 std::fs::remove_file(path).unwrap_or(());
184 let test: Vec<u8> = object(1000);
185
186 let mut file = File::create(path).unwrap();
187 file.write_all(&test).unwrap();
188
189 let etag = etag_for_path(path).unwrap();
190
191 std::fs::remove_file(path).unwrap_or(());
192
193 assert_eq!(etag, "8122ef1c2b2331f7986349560248cf56");
194 }
195
196 #[test]
197 fn test_read_chunk_all_zero() {
198 let blob = vec![0u8; 10_000_000];
199 let mut blob = Cursor::new(blob);
200
201 let result = super::read_chunk(&mut blob).unwrap();
202
203 assert_eq!(result.len(), crate::bucket::CHUNK_SIZE);
204 }
205
206 #[test]
207 fn test_read_chunk_multi_chunk() {
208 let blob = vec![1u8; 10_000_000];
209 let mut blob = Cursor::new(blob);
210
211 let result = super::read_chunk(&mut blob).unwrap();
212 assert_eq!(result.len(), crate::bucket::CHUNK_SIZE);
213
214 let result = super::read_chunk(&mut blob).unwrap();
215 assert_eq!(result.len(), 1_611_392);
216 }
217}