1use chrono::prelude::*;
2use reqwest::header::{HeaderMap, DATE};
3use reqwest::Client;
4use std::borrow::Cow;
5use std::collections::HashMap;
6use std::str;
7use std::time::{Duration, SystemTime};
8
9use crate::errors::ObjectError;
10
11use super::auth::*;
12use super::errors::Error;
13use super::utils::*;
14
15const RESOURCES: [&str; 50] = [
16 "acl",
17 "uploads",
18 "location",
19 "cors",
20 "logging",
21 "website",
22 "referer",
23 "lifecycle",
24 "delete",
25 "append",
26 "tagging",
27 "objectMeta",
28 "uploadId",
29 "partNumber",
30 "security-token",
31 "position",
32 "img",
33 "style",
34 "styleName",
35 "replication",
36 "replicationProgress",
37 "replicationLocation",
38 "cname",
39 "bucketInfo",
40 "comp",
41 "qos",
42 "live",
43 "status",
44 "vod",
45 "startTime",
46 "endTime",
47 "symlink",
48 "x-oss-process",
49 "response-content-type",
50 "response-content-language",
51 "response-expires",
52 "response-cache-control",
53 "response-content-disposition",
54 "response-content-encoding",
55 "udf",
56 "udfName",
57 "udfImage",
58 "udfId",
59 "udfImageDesc",
60 "udfApplication",
61 "comp",
62 "udfApplicationLog",
63 "restore",
64 "callback",
65 "callback-var",
66];
67
68#[derive(Clone, Debug)]
69pub struct OSS<'a> {
70 key_id: Cow<'a, str>,
71 key_secret: Cow<'a, str>,
72 endpoint: Cow<'a, str>,
73 bucket: Cow<'a, str>,
74
75 pub(crate) http_client: Client,
76}
77
78#[derive(Default)]
79pub struct Options {
80 pub pool_max_idle_per_host: Option<usize>,
81 pub timeout: Option<Duration>,
82}
83
84impl<'a> OSS<'a> {
85 pub fn new<S>(key_id: S, key_secret: S, endpoint: S, bucket: S) -> Self
86 where
87 S: Into<Cow<'a, str>>,
88 {
89 Self::new_with_opts(key_id, key_secret, endpoint, bucket, Default::default())
90 }
91
92 pub fn new_with_opts<S>(key_id: S, key_secret: S, endpoint: S, bucket: S, opts: Options) -> Self
93 where
94 S: Into<Cow<'a, str>>,
95 {
96 let mut builder = Client::builder();
97 if let Some(timeout) = opts.timeout {
98 builder = builder.timeout(timeout);
99 }
100 if let Some(max_per_host) = opts.pool_max_idle_per_host {
101 builder = builder.pool_max_idle_per_host(max_per_host);
102 }
103
104 let http_client = builder.build().expect("Build http client failed");
105 OSS {
106 key_id: key_id.into(),
107 key_secret: key_secret.into(),
108 endpoint: endpoint.into(),
109 bucket: bucket.into(),
110 http_client,
111 }
112 }
113
114 pub fn bucket(&self) -> &str {
115 &self.bucket
116 }
117
118 pub fn endpoint(&self) -> &str {
119 &self.endpoint
120 }
121
122 pub fn key_id(&self) -> &str {
123 &self.key_id
124 }
125
126 pub fn key_secret(&self) -> &str {
127 &self.key_secret
128 }
129
130 pub fn set_bucket(&mut self, bucket: &'a str) {
131 self.bucket = bucket.into()
132 }
133
134 pub fn host(&self, bucket: &str, object: &str, resources_str: &str) -> String {
135 if self.endpoint.starts_with("https") {
136 format!(
137 "https://{}.{}/{}?{}",
138 bucket,
139 self.endpoint.replacen("https://", "", 1),
140 object,
141 resources_str
142 )
143 } else {
144 format!(
145 "http://{}.{}/{}?{}",
146 bucket,
147 self.endpoint.replacen("http://", "", 1),
148 object,
149 resources_str
150 )
151 }
152 }
153
154 pub fn date(&self) -> String {
155 let now: DateTime<Utc> = Utc::now();
156 now.format("%a, %d %b %Y %T GMT").to_string()
157 }
158
159 pub fn get_resources_str<S>(&self, params: &HashMap<S, Option<S>>) -> String
160 where
161 S: AsRef<str>,
162 {
163 let mut resources: Vec<(&S, &Option<S>)> = params
164 .iter()
165 .filter(|(k, _)| RESOURCES.contains(&k.as_ref()))
166 .collect();
167 resources.sort_by(|a, b| a.0.as_ref().to_string().cmp(&b.0.as_ref().to_string()));
168 let mut result = String::new();
169 for (k, v) in resources {
170 if !result.is_empty() {
171 result += "&";
172 }
173 if let Some(vv) = v {
174 result += &format!("{}={}", k.as_ref().to_owned(), vv.as_ref());
175 } else {
176 result += k.as_ref();
177 }
178 }
179 result
180 }
181
182 pub fn get_params_str<S>(&self, params: &HashMap<S, Option<S>>) -> String
183 where
184 S: AsRef<str>,
185 {
186 let mut resources: Vec<(&S, &Option<S>)> = params.iter().collect();
187 resources.sort_by(|a, b| a.0.as_ref().to_string().cmp(&b.0.as_ref().to_string()));
188 let mut result = String::new();
189 for (k, v) in resources {
190 if !result.is_empty() {
191 result += "&";
192 }
193 if let Some(vv) = v {
194 result += &format!("{}={}", k.as_ref().to_owned(), vv.as_ref());
195 } else {
196 result += k.as_ref();
197 }
198 }
199 result
200 }
201
202 pub fn build_request<S1, S2, H, R>(
204 &self,
205 req_type: RequestType,
206 object_name: S1,
207 headers: H,
208 resources: R,
209 ) -> Result<(String, HeaderMap), Error>
210 where
211 S1: AsRef<str>,
212 S2: AsRef<str>,
213 H: Into<Option<HashMap<S2, S2>>>,
214 R: Into<Option<HashMap<S2, Option<S2>>>>,
215 {
216 let object_name = object_name.as_ref();
217 let (resources_str, params_str) = if let Some(r) = resources.into() {
218 (self.get_resources_str(&r), self.get_params_str(&r))
219 } else {
220 (String::new(), String::new())
221 };
222
223 let host = self.host(self.bucket(), object_name, ¶ms_str);
224 let date = self.date();
225 let mut headers = if let Some(h) = headers.into() {
226 to_headers(h)?
227 } else {
228 HeaderMap::new()
229 };
230 headers.insert(DATE, date.parse()?);
231 let authorization = self.oss_sign(
232 req_type.as_str(),
233 self.key_id(),
234 self.key_secret(),
235 self.bucket(),
236 object_name,
237 &resources_str,
238 &headers,
239 );
240 headers.insert("Authorization", authorization.parse()?);
241
242 Ok((host, headers))
243 }
244}
245
246pub enum RequestType {
247 Get,
248 Put,
249 Post,
250 Delete,
251 Head,
252}
253
254impl RequestType {
255 pub(crate) fn as_str(&self) -> &str {
256 match self {
257 RequestType::Get => "GET",
258 RequestType::Put => "PUT",
259 RequestType::Post => "POST",
260 RequestType::Delete => "DELETE",
261 RequestType::Head => "HEAD",
262 }
263 }
264}
265
266#[derive(Debug)]
267pub struct ObjectMeta {
268 pub last_modified: SystemTime,
270 pub size: usize,
272 pub md5: String,
274}
275
276impl ObjectMeta {
277 pub fn from_header_map(header: &HeaderMap) -> Result<Self, Error> {
278 let getter = |key: &str| -> Result<&str, Error> {
279 let value = header
280 .get(key)
281 .ok_or_else(|| {
282 Error::Object(ObjectError::HeadError {
283 msg: format!(
284 "can not find {} in head response, response header: {:?}",
285 key, header
286 )
287 .into(),
288 })
289 })?
290 .to_str()
291 .map_err(|_| {
292 Error::Object(ObjectError::HeadError {
293 msg: format!("header entry {} contains invalid ASCII code", key).into(),
294 })
295 })?;
296 Ok(value)
297 };
298
299 let last_modified = httpdate::parse_http_date(getter("Last-Modified")?).map_err(|e| {
300 Error::Object(ObjectError::HeadError {
301 msg: format!("cannot parse to system time: {}", e).into(),
302 })
303 })?;
304 let size = getter("Content-Length")?.parse().map_err(|e| {
305 Error::Object(ObjectError::HeadError {
306 msg: format!("cannot parse to number: {}", e).into(),
307 })
308 })?;
309 let md5 = getter("Content-Md5")?.to_string();
310
311 Ok(Self {
312 last_modified,
313 size,
314 md5,
315 })
316 }
317}