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