1use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
4use reqwest::header::{CONTENT_TYPE, DATE};
5
6use base64::{ engine::general_purpose, Engine};
7use hmac::{Hmac, Mac};
8
9type HmacSha1 = Hmac<sha1::Sha1>;
10
11use crate::errors::OSSError;
12
13use super::oss::OSS;
14
15pub trait Auth {
16 fn oss_sign(
17 &self,
18 verb: &str,
19 bucket: &str,
20 object: &str,
21 oss_resources: &str,
22 headers: &HeaderMap,
23 ) -> Result<String, OSSError>;
24
25 fn sign_content(&self, content: &str) -> Result<String, OSSError>;
26}
27
28impl<'a> Auth for OSS<'a> {
29 fn oss_sign(
30 &self,
31 verb: &str,
32 bucket: &str,
33 object: &str,
34 oss_resources: &str,
35 headers: &HeaderMap,
36 ) -> Result<String, OSSError> {
37 let date = headers
38 .get(DATE)
39 .map(|d| d.to_str().unwrap_or_default())
40 .unwrap_or_default();
41 let content_type = headers
42 .get(CONTENT_TYPE)
43 .map(|c| c.to_str().unwrap_or_default())
44 .unwrap_or_default();
45 let content_md5 = headers
46 .get("Content-MD5")
47 .map(|md5| general_purpose::STANDARD.encode(md5.to_str().unwrap_or_default()))
48 .unwrap_or_default();
49
50 let mut oss_headers: Vec<(&HeaderName, &HeaderValue)> = headers
51 .iter()
52 .filter(|(k, _)| k.as_str().contains("x-oss-"))
53 .collect();
54 oss_headers.sort_by(|a, b| a.0.to_string().cmp(&b.0.to_string()));
55 let mut oss_headers_str = String::new();
56 for (k, v) in oss_headers {
57 oss_headers_str += &format!(
58 "{}:{}\n",
59 k.to_owned().as_str(),
60 v.to_owned().to_str().unwrap_or("")
61 );
62 }
63
64 let oss_resource_str = get_oss_resource_str(bucket, object, oss_resources);
65 let sign_str = format!(
66 "{}\n{}\n{}\n{}\n{}{}",
67 verb, content_md5, content_type, date, oss_headers_str, oss_resource_str
68 );
69
70 self.sign_content(sign_str.as_str())
71 }
72
73 fn sign_content(&self, content: &str) -> Result<String, OSSError> {
74 let mut hasher =
75 HmacSha1::new_from_slice(self.key_secret().as_bytes()).map_err(OSSError::Sign)?;
76 hasher.update(content.as_bytes());
77
78 let sign_str_base64 = general_purpose::STANDARD.encode(hasher.finalize().into_bytes());
79
80 let authorization = format!("OSS {}:{}", self.key_id(), sign_str_base64);
81 debug!("authorization: {}", authorization);
82 Ok(authorization)
83 }
84}
85
86#[inline]
87fn get_oss_resource_str(bucket: &str, object: &str, oss_resources: &str) -> String {
88 let oss_resources = if !oss_resources.is_empty() {
89 String::from("?") + oss_resources
90 } else {
91 String::new()
92 };
93 if bucket.is_empty() {
94 format!("/{}{}", bucket, oss_resources)
95 } else {
96 format!("/{}/{}{}", bucket, object, oss_resources)
97 }
98}