rust_aliyun/oss/
presign.rs

1use crate::client::{Calculator, Client};
2use crate::error::Error;
3use crate::util::date::{acs_short_date, date};
4use crate::util::request::{canonical, hash, hex, sign, sign_string};
5use crate::{Result, __setter};
6use reqwest::header::HeaderMap;
7use reqwest::Method;
8use serde::Serialize;
9use std::collections::HashMap;
10const SIGN_ALGORITHM: &'static str = "OSS4-HMAC-SHA256";
11
12#[derive(Debug, Default)]
13pub struct PresignUrlReqeust {
14    pub expire: i64,
15    pub method: Method,
16    pub bucket: String,
17    pub host: Option<String>,
18    pub ssl: bool,
19    pub uri: String,
20    pub queries: Option<HashMap<String, String>>,
21    pub headers: Option<HashMap<String, String>>,
22}
23
24impl PresignUrlReqeust {
25    __setter!(expire: i64);
26    __setter!(method: Method);
27    __setter!(bucket: String);
28    __setter!(host: Option<String>);
29    __setter!(ssl: bool);
30    __setter!(uri: String);
31    __setter!(queries: Option<HashMap<String, String>>);
32    __setter!(headers: Option<HashMap<String, String>>);
33
34    pub fn get() -> Self {
35        Self {
36            method: Method::GET,
37            ..Default::default()
38        }
39    }
40    pub fn post() -> Self {
41        Self {
42            method: Method::POST,
43            ..Default::default()
44        }
45    }
46
47
48    pub fn put() -> Self {
49        Self {
50            method: Method::PUT,
51            ..Default::default()
52        }
53    }
54
55    fn credential(&self, access_key_id: &str, region: &str) -> String {
56        let credential = format!(
57            "{}/{}/{}/oss/aliyun_v4_request",
58            access_key_id,
59            date(),
60            region
61        );
62        credential
63    }
64
65    pub fn signature(&self, key: &str, data: &str, date: &str, region: &str) -> Result<String> {
66        let key1 = sign(format!("aliyun_v4{}", key).as_bytes(), date)?;
67        let key2 = sign(&key1, region)?;
68        let key3 = sign(&key2, "oss")?;
69        let key = sign(&key3, "aliyun_v4_request")?;
70        Ok(sign_string(&key, data)?)
71    }
72    fn canonical_uri(&self) -> String {
73        format!(
74            "/{}/{}",
75            self.bucket,
76            self.uri
77                .split("/")
78                .map(|s| canonical(s))
79                .collect::<Vec<String>>()
80                .join("/")
81        )
82    }
83
84    fn canonical_headers(&self, headers: &HashMap<String, String>) -> Result<(String, String)> {
85        let mut canonical_headers = headers
86            .iter()
87            .map(|item| (item.0.to_string().to_lowercase(), item.1))
88            .filter(|item| {
89                item.0 == "host"
90                    || item.0 == "content-type"
91                    || item.0 == "content-md5"
92                    || item.0.starts_with("x-acs")
93            })
94            .collect::<Vec<(_, _)>>();
95
96        canonical_headers.sort_by(|a, b| a.0.cmp(&b.0));
97
98        let mut header_string = canonical_headers
99            .iter()
100            .map(|item| format!("{}:{}\n", item.0, item.1.trim()))
101            .fold(String::new(), |acc, item| acc + &item);
102
103        let canonical_header_name_string = canonical_headers
104            .iter()
105            .map(|item| item.0.clone())
106            .collect::<Vec<_>>()
107            .join(";");
108        Ok((header_string, canonical_header_name_string))
109    }
110
111    fn canonical_queries(&self, queries: &HashMap<String, String>) -> Result<String> {
112        let mut canonical_queries = queries
113            .iter()
114            .map(|item| (item.0.to_string().to_lowercase(), item.1))
115            .collect::<Vec<(_, _)>>();
116        canonical_queries.sort_by(|a, b| a.0.cmp(&b.0));
117        Ok(serde_urlencoded::to_string(canonical_queries)?)
118    }
119
120    fn build_queries(&self, client: &Client) -> Result<HashMap<String, String>> {
121        let Client {
122            access_key_id,
123            region,
124            ..
125        } = client;
126
127        let region = if let Some(region) = region {
128            region
129        } else {
130            return Err(Error::CommonError("region should be set".to_string()));
131        };
132
133        let mut queries = if let Some(queries) = &self.queries {
134            queries.clone()
135        } else {
136            HashMap::new()
137        };
138
139        queries.insert(
140            "x-oss-signature-version".to_string(),
141            SIGN_ALGORITHM.to_string(),
142        );
143        queries.insert(
144            "x-oss-credential".to_string(),
145            self.credential(access_key_id, region),
146        );
147        queries.insert("x-oss-date".to_string(), acs_short_date());
148        queries.insert("x-oss-expires".to_string(), format!("{}", self.expire));
149
150        let (header_string, header_name_string) = if let Some(headers) = &self.headers {
151            self.canonical_headers(&headers)?
152        } else {
153            (String::new(), String::new())
154        };
155
156        if !header_name_string.is_empty() {
157            queries.insert(
158                "x-oss-additional-headers".to_string(),
159                header_name_string.clone(),
160            );
161        }
162
163        let canonical_request = format!(
164            "{}\n{}\n{}\n{}\n{}\nUNSIGNED-PAYLOAD",
165            self.method,
166            self.canonical_uri(),
167            self.canonical_queries(&queries)?,
168            header_string,
169            header_name_string
170        );
171
172        log::debug!("canonical request: {}", canonical_request);
173        let date_str = date();
174        let pre_sign = format!(
175            "{}\n{}\n{}/{}/oss/aliyun_v4_request\n{}",
176            SIGN_ALGORITHM,
177            acs_short_date(),
178            date_str,
179            region,
180            hex(&canonical_request)
181        );
182        log::debug!("pre-sign: {}", pre_sign);
183        let sign = self.signature(&client.access_key_secret, &pre_sign, &date_str, region)?;
184        queries.insert("x-oss-signature".to_string(), sign);
185        Ok(queries)
186    }
187}
188
189impl Calculator<String> for PresignUrlReqeust {
190    fn calculate(&self, client: &Client) -> Result<String> {
191        let queries = self.build_queries(client)?;
192        let host = if let Some(host) = &self.host {
193            host
194        } else {
195            &format!("{}.{}", self.bucket, client.endpoint)
196        };
197        Ok(format!(
198            "{}{}/{}?{}",
199            if self.ssl { "https://" } else { "http://" },
200            host,
201            self.uri,
202            serde_urlencoded::to_string(&queries)?
203        ))
204    }
205}