1#[cfg(feature = "async")]
5use bytes::Bytes;
6#[cfg(feature = "async")]
7use http_body_util::BodyExt;
8#[cfg(feature = "async")]
9use hyper::body::Incoming;
10use percent_encoding::{AsciiSet, NON_ALPHANUMERIC, utf8_percent_encode};
11use serde_derive::{Deserialize, Serialize};
12use std::fmt;
13use std::sync::OnceLock;
14use time::{OffsetDateTime, UtcOffset, format_description};
15
16const URL_ENCODE: &AsciiSet = &NON_ALPHANUMERIC.remove(b'-').remove(b'/');
19#[inline]
20pub(crate) fn url_encode(input: &str) -> String {
21 utf8_percent_encode(input, URL_ENCODE).to_string()
22}
23
24#[inline]
26pub(crate) fn invalid_metadata_key(input: &str) -> bool {
27 !input
28 .bytes()
29 .all(|b| b.is_ascii_alphanumeric() || b == b'-')
30}
31
32#[cfg(feature = "async")]
33#[inline]
34pub(crate) async fn body_to_bytes(body: Incoming) -> Result<Bytes, hyper::Error> {
35 Ok(body.collect().await?.to_bytes())
36}
37
38static GMT_FORMAT: OnceLock<Vec<format_description::FormatItem<'static>>> = OnceLock::new();
39
40#[inline]
41pub(crate) fn format_gmt(datetime: OffsetDateTime) -> String {
42 let format = GMT_FORMAT.get_or_init(|| {
43 format_description::parse(
44 "[weekday repr:short], [day padding:space] [month repr:short] [year] [hour]:[minute]:[second] GMT",
45 )
46 .expect("valid format")
47 });
48 datetime
49 .to_offset(UtcOffset::UTC)
50 .format(format)
51 .expect("formatting")
52}
53
54pub(crate) const EXCLUDED_VALUES: [&str; 85] = [
61 "accessPoint",
62 "accessPointPolicy",
63 "acl",
64 "append",
65 "asyncFetch",
66 "bucketArchiveDirectRead",
67 "bucketInfo",
68 "callback",
69 "callback-var",
70 "cname",
71 "comp",
72 "continuation-token",
73 "cors",
74 "delete",
75 "encryption",
76 "endTime",
77 "group",
78 "httpsConfig",
79 "img",
80 "inventory",
81 "inventoryId",
82 "lifecycle",
83 "link",
84 "live",
85 "location",
86 "logging",
87 "metaQuery",
88 "objectInfo",
89 "objectMeta",
90 "partNumber",
91 "policy",
92 "policyStatus",
93 "position",
94 "processConfiguration",
95 "publicAccessBlock",
96 "qos",
97 "qosInfo",
98 "qosRequester",
99 "redundancyTransition",
100 "referer",
101 "regionList",
102 "replication",
103 "replicationLocation",
104 "replicationProgress",
105 "requestPayment",
106 "requesterQosInfo",
107 "resourceGroup",
108 "resourcePool",
109 "resourcePoolBuckets",
110 "resourcePoolInfo",
111 "response-cache-control",
112 "response-content-disposition",
113 "response-content-encoding",
114 "response-content-language",
115 "response-content-type",
116 "response-expires",
117 "restore",
118 "security-token",
119 "sequential",
120 "startTime",
121 "stat",
122 "status",
123 "style",
124 "styleName",
125 "symlink",
126 "tagging",
127 "transferAcceleration",
128 "udf",
129 "udfApplication",
130 "udfApplicationLog",
131 "udfImage",
132 "udfImageDesc",
133 "udfName",
134 "uploadId",
135 "uploads",
136 "versionId",
137 "versioning",
138 "versions",
139 "vip",
140 "vod",
141 "vpcip",
142 "website",
143 "worm",
144 "wormExtend",
145 "wormId",
146];
147
148#[derive(Debug, Deserialize, Clone)]
152pub enum Acl {
153 #[serde(rename = "default")]
155 Default,
156 #[serde(rename = "private")]
158 Private,
159 #[serde(rename = "public-read")]
161 PublicRead,
162 #[serde(rename = "public-read-write")]
164 PublicReadWrite,
165}
166impl fmt::Display for Acl {
167 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> fmt::Result {
168 let value = match self {
169 Acl::Default => "default",
170 Acl::Private => "private",
171 Acl::PublicRead => "public-read",
172 Acl::PublicReadWrite => "public-read-write",
173 };
174 write!(f, "{}", value)
175 }
176}
177
178#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
180pub enum StorageClass {
181 Standard,
183 IA,
185 Archive,
187 ColdArchive,
189 DeepColdArchive,
191}
192impl fmt::Display for StorageClass {
193 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
194 match self {
195 StorageClass::Standard => f.write_str("Standard"),
196 StorageClass::IA => f.write_str("IA"),
197 StorageClass::Archive => f.write_str("Archive"),
198 StorageClass::ColdArchive => f.write_str("ColdArchive"),
199 StorageClass::DeepColdArchive => f.write_str("DeepColdArchive"),
200 }
201 }
202}
203
204#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
206pub enum DataRedundancyType {
207 LRS,
209 ZRS,
211}
212impl fmt::Display for DataRedundancyType {
213 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
214 match self {
215 DataRedundancyType::LRS => f.write_str("LRS"),
216 DataRedundancyType::ZRS => f.write_str("ZRS"),
217 }
218 }
219}
220
221#[derive(Debug, Clone, Serialize, Deserialize, Copy)]
223pub enum RestoreTier {
224 Expedited,
226 Standard,
228 Bulk,
230}
231impl fmt::Display for RestoreTier {
232 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
233 match self {
234 RestoreTier::Standard => f.write_str("Standard"),
235 RestoreTier::Expedited => f.write_str("Expedited"),
236 RestoreTier::Bulk => f.write_str("Bulk"),
237 }
238 }
239}
240
241#[derive(Debug, Clone)]
243pub enum CacheControl {
244 NoCache,
245 NoStore,
246 Public,
247 Private,
248 MaxAge(u32),
249}
250impl fmt::Display for CacheControl {
251 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
252 match self {
253 CacheControl::NoCache => f.write_str("no-cache"),
254 CacheControl::NoStore => f.write_str("no-store"),
255 CacheControl::Public => f.write_str("public"),
256 CacheControl::Private => f.write_str("private"),
257 CacheControl::MaxAge(val) => write!(f, "max-age={}", val),
258 }
259 }
260}
261
262#[derive(Debug, Clone)]
264pub enum ContentDisposition {
265 Inline,
266 Attachment,
267 AttachmentWithNewName(String),
268}
269impl fmt::Display for ContentDisposition {
270 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
271 match self {
272 ContentDisposition::Inline => f.write_str("inline"),
273 ContentDisposition::AttachmentWithNewName(file_name) => write!(
274 f,
275 "attachment;filename=\"{0}\";filename*=UTF-8''{0}",
276 url_encode(file_name)
277 ),
278 ContentDisposition::Attachment => f.write_str("attachment"),
279 }
280 }
281}
282
283#[derive(Debug, Deserialize)]
285pub struct Owner {
286 #[serde(rename = "ID")]
288 pub id: u64,
289 #[serde(rename = "DisplayName")]
291 pub display_name: String,
292}
293
294#[derive(Debug)]
296pub struct BucketAcl {
297 pub owner: Owner,
298 pub acl: Acl,
299}
300
301#[cfg(test)]
302mod tests {
303 use super::*;
304
305 #[test]
306 fn test_url_encode_and_invalid_metadata_key() {
307 assert_eq!(url_encode("a b"), "a%20b");
308 assert!(!invalid_metadata_key("abc-123"));
309 assert!(invalid_metadata_key("abc_123"));
310 }
311
312 #[test]
313 fn test_cache_control_and_content_disposition() {
314 assert_eq!(CacheControl::MaxAge(60).to_string(), "max-age=60");
315 let name = "file name.txt";
316 let expected = format!(
317 "attachment;filename=\"{0}\";filename*=UTF-8''{0}",
318 url_encode(name)
319 );
320 assert_eq!(
321 ContentDisposition::AttachmentWithNewName(name.into()).to_string(),
322 expected
323 );
324 }
325}