qiniu_credential/
lib.rs

1#![cfg_attr(feature = "docs", feature(doc_cfg))]
2#![deny(
3    single_use_lifetimes,
4    missing_debug_implementations,
5    large_assignments,
6    exported_private_dependencies,
7    absolute_paths_not_starting_with_crate,
8    anonymous_parameters,
9    explicit_outlives_requirements,
10    keyword_idents,
11    macro_use_extern_crate,
12    meta_variable_misuse,
13    missing_docs,
14    non_ascii_idents,
15    indirect_structural_match,
16    trivial_numeric_casts,
17    unreachable_pub,
18    unsafe_code,
19    unused_crate_dependencies,
20    unused_extern_crates,
21    unused_import_braces,
22    unused_lifetimes,
23    unused_qualifications
24)]
25
26//! # qiniu-credential
27//!
28//! ## 七牛认证信息
29//!
30//! 负责存储调用七牛 API 所必要的认证信息,提供 [`CredentialProvider`] 方便扩展获取认证信息的方式。
31//! 同时提供阻塞接口和异步接口(异步接口需要启用 `async` 功能)。
32//! 提供 [`CredentialProvider`] 的多个实现方式,例如:
33//!
34//! - [`GlobalCredentialProvider`] : 使用全局变量配置的认证信息
35//! - [`EnvCredentialProvider`] : 使用环境变量配置的认证信息
36//! - [`ChainCredentialsProvider`] : 配置多个 [`CredentialProvider`] 形成认证信息串,遍历找寻第一个可用的认证信息
37//!
38//! ### 代码示例
39//!
40//! #### 计算七牛鉴权签名 V1
41//!
42//! ```
43//! use qiniu_credential::{Credential, HeaderValue, prelude::*};
44//! use mime::APPLICATION_WWW_FORM_URLENCODED;
45//! use std::io::Cursor;
46//!
47//! # fn main() -> anyhow::Result<()> {
48//! let credential = Credential::new("abcdefghklmnopq", "1234567890");
49//! let authorization = credential
50//!     .get(Default::default())?
51//!     .authorization_v1_for_request(
52//!         &"http://upload.qiniup.com/".parse()?,
53//!         Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
54//!         b"name=test&language=go"
55//!     );
56//! # Ok(())
57//! # }
58//! ```
59//!
60//! #### 计算七牛鉴权签名 V2
61//!
62//! ```
63//! use qiniu_credential::{Credential, Method, HeaderMap, HeaderValue, prelude::*};
64//! use http::header::CONTENT_TYPE;
65//! use mime::APPLICATION_JSON;
66//! # fn main() -> anyhow::Result<()> {
67//! let credential = Credential::new("abcdefghklmnopq", "1234567890");
68//! let mut headers = HeaderMap::new();
69//! headers.insert(CONTENT_TYPE, HeaderValue::from_str(APPLICATION_JSON.as_ref())?);
70//! let authorization = credential
71//!     .get(Default::default())?
72//!     .authorization_v2_for_request(
73//!         &Method::GET,
74//!         &"http://upload.qiniup.com/".parse()?,
75//!         &headers,
76//!         b"{\"name\":\"test\"}".as_slice(),
77//!     );
78//! # Ok(())
79//! # }
80//! ```
81//!
82//! #### 计算下载地址签名
83//!
84//! ```
85//! use qiniu_credential::{Credential, prelude::*};
86//! use std::time::Duration;
87//! # fn main() -> anyhow::Result<()> {
88//! let credential = Credential::new("abcdefghklmnopq", "1234567890");
89//! let url = "http://www.qiniu.com/?go=1".parse()?;
90//! let url = credential
91//!     .get(Default::default())?
92//!     .sign_download_url(url, Duration::from_secs(3600));
93//! println!("{}", url);
94//! Ok(())
95//! }
96//! ```
97
98use assert_impl::assert_impl;
99use auto_impl::auto_impl;
100use dyn_clonable::clonable;
101use hmac::{Hmac, Mac, NewMac};
102use http::header::CONTENT_TYPE;
103pub use http::{
104    header::{HeaderMap, HeaderName, HeaderValue},
105    method::Method,
106    uri::Uri,
107    Extensions,
108};
109use mime::{APPLICATION_OCTET_STREAM, APPLICATION_WWW_FORM_URLENCODED};
110use once_cell::sync::Lazy;
111use qiniu_utils::base64;
112use sha1::Sha1;
113use std::{
114    collections::VecDeque,
115    env,
116    fmt::{self, Debug},
117    io::{copy, Error as IoError, ErrorKind as IoErrorKind, Read, Result as IoResult},
118    mem::take,
119    ops::{Deref, DerefMut},
120    sync::{Arc, RwLock},
121    time::{Duration, SystemTime, UNIX_EPOCH},
122};
123
124mod header_name;
125use header_name::make_header_name;
126
127mod key;
128pub use key::{AccessKey, SecretKey};
129
130/// 将所有 Trait 全部重新导出,方便统一导入
131pub mod prelude {
132    pub use super::CredentialProvider;
133}
134
135/// 认证信息
136///
137/// 返回认证信息的 AccessKey 和 SecretKey
138#[derive(Clone, Debug, PartialEq, Eq)]
139pub struct Credential {
140    access_key: AccessKey,
141    secret_key: SecretKey,
142}
143
144impl Credential {
145    /// 创建认证信息
146    #[inline]
147    pub fn new(access_key: impl Into<AccessKey>, secret_key: impl Into<SecretKey>) -> Self {
148        Self {
149            access_key: access_key.into(),
150            secret_key: secret_key.into(),
151        }
152    }
153
154    /// 获取认证信息的 AccessKey
155    #[inline]
156    pub fn access_key(&self) -> &AccessKey {
157        &self.access_key
158    }
159
160    /// 获取认证信息的 SecretKey
161    #[inline]
162    pub fn secret_key(&self) -> &SecretKey {
163        &self.secret_key
164    }
165
166    /// 同时返回认证信息的 AccessKey 和 SecretKey
167    #[inline]
168    pub fn split(self) -> (AccessKey, SecretKey) {
169        (self.access_key, self.secret_key)
170    }
171
172    /// 使用七牛签名算法对数据进行签名
173    ///
174    /// 参考[管理凭证的签名算法文档](https://developer.qiniu.com/kodo/manual/1201/access-token)
175    ///
176    /// ```
177    /// use qiniu_credential::{Credential, prelude::*};
178    /// # fn main() -> anyhow::Result<()> {
179    /// let credential = Credential::new("abcdefghklmnopq", "1234567890");
180    /// assert_eq!(
181    ///     credential.get(Default::default())?.sign(b"hello"),
182    ///     "abcdefghklmnopq:b84KVc-LroDiz0ebUANfdzSRxa0="
183    /// );
184    /// # Ok(())
185    /// # }
186    /// ```
187    pub fn sign(&self, data: &[u8]) -> String {
188        self.sign_within::<IoError, _>(|hmac| {
189            hmac.update(data);
190            Ok(())
191        })
192        .unwrap()
193    }
194
195    /// 使用七牛签名算法对输入流数据进行签名
196    ///
197    /// 该方法的异步版本为 [`Credential::sign_async_reader`]。
198    ///
199    /// 参考[管理凭证的签名算法文档](https://developer.qiniu.com/kodo/manual/1201/access-token)
200    ///
201    /// ```
202    /// use qiniu_credential::{Credential, prelude::*};
203    /// use std::io::Cursor;
204    /// # fn main() -> anyhow::Result<()> {
205    /// let credential = Credential::new("abcdefghklmnopq", "1234567890");
206    /// assert_eq!(
207    ///     credential
208    ///         .get(Default::default())?
209    ///         .sign_reader(&mut Cursor::new(b"world"))?,
210    ///     "abcdefghklmnopq:VjgXt0P_nCxHuaTfiFz-UjDJ1AQ="
211    /// );
212    /// # Ok(())
213    /// # }
214    /// ```
215    pub fn sign_reader(&self, reader: &mut dyn Read) -> IoResult<String> {
216        self.sign_within(|hmac| copy(reader, hmac).map(|_| ()))
217    }
218
219    fn sign_within<E, F: FnOnce(&mut Hmac<Sha1>) -> Result<(), E>>(&self, f: F) -> Result<String, E> {
220        let signature = generate_base64ed_hmac_sha1_digest_within(self.secret_key(), f)?;
221        Ok(self.access_key().to_string() + ":" + &signature)
222    }
223
224    /// 使用七牛签名算法对数据进行签名,并同时给出签名和原数据
225    ///
226    /// 参考[上传凭证的签名算法文档](https://developer.qiniu.com/kodo/manual/1208/upload-token)
227    ///
228    /// ```
229    /// use qiniu_credential::{Credential, prelude::*};
230    /// use std::io::Cursor;
231    /// # fn main() -> anyhow::Result<()> {
232    /// let credential = Credential::new("abcdefghklmnopq", "1234567890");
233    /// assert_eq!(
234    ///     credential.get(Default::default())?.sign_with_data(b"hello"),
235    ///     "abcdefghklmnopq:BZYt5uVRy1RVt5ZTXbaIt2ROVMA=:aGVsbG8="
236    /// );
237    /// # Ok(())
238    /// # }
239    /// ```
240    pub fn sign_with_data(&self, data: &[u8]) -> String {
241        let encoded_data = base64::urlsafe(data);
242        self.sign(encoded_data.as_bytes()) + ":" + &encoded_data
243    }
244
245    /// 使用七牛签名算法 V1 对 HTTP 请求(请求体为内存数据)进行签名,返回 Authorization 的值
246    ///
247    /// ```
248    /// use qiniu_credential::{Credential, HeaderValue, prelude::*};
249    /// use mime::APPLICATION_WWW_FORM_URLENCODED;
250    /// use std::io::Cursor;
251    /// # fn main() -> anyhow::Result<()> {
252    /// let credential = Credential::new("abcdefghklmnopq", "1234567890");
253    /// let authorization = credential
254    ///     .get(Default::default())?
255    ///     .authorization_v1_for_request(
256    ///         &"http://upload.qiniup.com/".parse()?,
257    ///         Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
258    ///         b"name=test&language=go"
259    ///     );
260    /// # Ok(())
261    /// # }
262    /// ```
263    pub fn authorization_v1_for_request(&self, url: &Uri, content_type: Option<&HeaderValue>, body: &[u8]) -> String {
264        let authorization_token = sign_request_v1(self, url, content_type, body);
265        "QBox ".to_owned() + &authorization_token
266    }
267
268    /// 使用七牛签名算法 V1 对 HTTP 请求(请求体为输入流)进行签名,返回 Authorization 的值
269    ///
270    /// 该方法的异步版本为 [`Credential::authorization_v1_for_request_with_async_body_reader`]。
271    ///
272    /// ```
273    /// use qiniu_credential::{Credential, HeaderValue, prelude::*};
274    /// use std::io::Cursor;
275    /// use mime::APPLICATION_WWW_FORM_URLENCODED;
276    /// # fn main() -> anyhow::Result<()> {
277    /// let credential = Credential::new("abcdefghklmnopq", "1234567890");
278    /// let authorization = credential
279    ///     .get(Default::default())?
280    ///     .authorization_v1_for_request_with_body_reader(
281    ///         &"http://upload.qiniup.com/".parse()?,
282    ///         Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
283    ///         &mut Cursor::new(b"name=test&language=go")
284    ///     )?;
285    /// # Ok(())
286    /// # }
287    /// ```
288    pub fn authorization_v1_for_request_with_body_reader(
289        &self,
290        url: &Uri,
291        content_type: Option<&HeaderValue>,
292        body: &mut dyn Read,
293    ) -> IoResult<String> {
294        let authorization_token = sign_request_v1_with_body_reader(self, url, content_type, body)?;
295        Ok("QBox ".to_owned() + &authorization_token)
296    }
297
298    /// 使用七牛签名算法 V2 对 HTTP 请求(请求体为内存数据)进行签名,返回 Authorization 的值
299    ///
300    /// ```
301    /// use qiniu_credential::{Credential, Method, HeaderMap, HeaderValue, prelude::*};
302    /// use http::header::CONTENT_TYPE;
303    /// use mime::APPLICATION_JSON;
304    /// # fn main() -> anyhow::Result<()> {
305    /// let credential = Credential::new("abcdefghklmnopq", "1234567890");
306    /// let mut headers = HeaderMap::new();
307    /// headers.insert(CONTENT_TYPE, HeaderValue::from_str(APPLICATION_JSON.as_ref())?);
308    /// let authorization = credential
309    ///     .get(Default::default())?
310    ///     .authorization_v2_for_request(
311    ///         &Method::GET,
312    ///         &"http://upload.qiniup.com/".parse()?,
313    ///         &headers,
314    ///         b"{\"name\":\"test\"}".as_slice(),
315    ///     );
316    /// # Ok(())
317    /// # }
318    /// ```
319    pub fn authorization_v2_for_request(&self, method: &Method, url: &Uri, headers: &HeaderMap, body: &[u8]) -> String {
320        let authorization_token = sign_request_v2(self, method, url, headers, body);
321        "Qiniu ".to_owned() + &authorization_token
322    }
323
324    /// 使用七牛签名算法 V2 对 HTTP 请求(请求体为输入流)进行签名,返回 Authorization 的值
325    ///
326    /// 该方法的异步版本为 [`Credential::authorization_v2_for_request_with_async_body_reader`]。
327    ///
328    /// ```
329    /// use qiniu_credential::{Credential, Method, HeaderMap, HeaderValue, prelude::*};
330    /// use http::header::CONTENT_TYPE;
331    /// use mime::APPLICATION_JSON;
332    /// use std::io::Cursor;
333    /// # fn main() -> anyhow::Result<()> {
334    /// let credential = Credential::new("abcdefghklmnopq", "1234567890");
335    /// let mut headers = HeaderMap::new();
336    /// headers.insert(CONTENT_TYPE, HeaderValue::from_str(APPLICATION_JSON.as_ref())?);
337    /// let authorization = credential
338    ///     .get(Default::default())?
339    ///     .authorization_v2_for_request_with_body_reader(
340    ///         &Method::GET,
341    ///         &"http://upload.qiniup.com/".parse()?,
342    ///         &headers,
343    ///         &mut Cursor::new(b"{\"name\":\"test\"}")
344    ///     )?;
345    /// # Ok(())
346    /// # }
347    /// ```
348    pub fn authorization_v2_for_request_with_body_reader(
349        &self,
350        method: &Method,
351        url: &Uri,
352        headers: &HeaderMap,
353        body: &mut dyn Read,
354    ) -> IoResult<String> {
355        let authorization_token = sign_request_v2_with_body_reader(self, method, url, headers, body)?;
356        Ok("Qiniu ".to_owned() + &authorization_token)
357    }
358
359    /// 对对象的下载 URL 签名,可以生成私有存储空间的下载地址
360    ///
361    /// ```
362    /// use qiniu_credential::{Credential, prelude::*};
363    /// use std::time::Duration;
364    /// # fn main() -> anyhow::Result<()> {
365    /// let credential = Credential::new("abcdefghklmnopq", "1234567890");
366    /// let url = "http://www.qiniu.com/?go=1".parse()?;
367    /// let url = credential
368    ///     .get(Default::default())?
369    ///     .sign_download_url(url, Duration::from_secs(3600));
370    /// println!("{}", url);
371    /// Ok(())
372    /// }
373    /// ```
374    pub fn sign_download_url(&self, url: Uri, lifetime: Duration) -> Uri {
375        let deadline = SystemTime::now() + lifetime;
376        let deadline = deadline
377            .duration_since(UNIX_EPOCH)
378            .expect("Invalid UNIX Timestamp")
379            .as_secs();
380        let to_sign = append_query_pairs_to_url(url, &[("e", &deadline.to_string())]);
381        let signature = self.sign(to_sign.to_string().as_bytes());
382        return append_query_pairs_to_url(to_sign, &[("token", &signature)]);
383
384        fn append_query_pairs_to_url(url: Uri, pairs: &[(&str, &str)]) -> Uri {
385            let path_string = url.path().to_owned();
386            let query_string = url.query().unwrap_or_default().to_owned();
387            let mut serializer = form_urlencoded::Serializer::new(query_string);
388            for (key, value) in pairs.iter() {
389                serializer.append_pair(key, value);
390            }
391            let query_string = serializer.finish();
392            let mut path_and_query = path_string;
393            if !query_string.is_empty() {
394                path_and_query.push('?');
395                path_and_query.push_str(&query_string);
396            }
397            let parts = url.into_parts();
398            let mut builder = Uri::builder();
399            if let Some(scheme) = parts.scheme {
400                builder = builder.scheme(scheme);
401            }
402            if let Some(authority) = parts.authority {
403                builder = builder.authority(authority);
404            }
405            builder.path_and_query(&path_and_query).build().unwrap()
406        }
407    }
408
409    #[allow(dead_code)]
410    fn assert() {
411        assert_impl!(Send: Self);
412        assert_impl!(Sync: Self);
413    }
414}
415
416#[cfg(feature = "async")]
417impl Credential {
418    /// 使用七牛签名算法对异步输入流数据进行签名
419    ///
420    /// 参考[管理凭证的签名算法文档](https://developer.qiniu.com/kodo/manual/1201/access-token)
421    ///
422    /// ```
423    /// use qiniu_credential::{Credential, prelude::*};
424    /// use futures_lite::io::Cursor;
425    /// # async fn f() -> anyhow::Result<()> {
426    /// let credential = Credential::new("abcdefghklmnopq", "1234567890");
427    /// assert_eq!(
428    ///     credential
429    ///         .async_get(Default::default()).await?
430    ///         .sign_async_reader(&mut Cursor::new(b"world")).await?,
431    ///     "abcdefghklmnopq:VjgXt0P_nCxHuaTfiFz-UjDJ1AQ="
432    /// );
433    /// # Ok(())
434    /// # }
435    /// ```
436    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
437    pub async fn sign_async_reader(&self, reader: &mut (dyn AsyncRead + Send + Unpin)) -> IoResult<String> {
438        let mut hmac = new_hmac_sha1(self.secret_key());
439        copy_async_reader_to_hmac_sha1(&mut hmac, reader).await?;
440        Ok(base64ed_hmac_sha1_with_access_key(self.access_key().to_string(), hmac))
441    }
442
443    /// 使用七牛签名算法 V1 对 HTTP 请求(请求体为异步输入流)进行签名,返回 Authorization 的值
444    ///
445    /// ```
446    /// use qiniu_credential::{Credential, HeaderValue, prelude::*};
447    /// use mime::APPLICATION_WWW_FORM_URLENCODED;
448    /// use futures_lite::io::Cursor;
449    /// # async fn f() -> anyhow::Result<()> {
450    /// let credential = Credential::new("abcdefghklmnopq", "1234567890");
451    /// let authorization = credential
452    ///     .async_get(Default::default()).await?
453    ///     .authorization_v1_for_request_with_async_body_reader(
454    ///         &"http://upload.qiniup.com/".parse()?,
455    ///         Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
456    ///         &mut Cursor::new(b"name=test&language=go")
457    ///     ).await?;
458    /// # Ok(())
459    /// # }
460    /// ```
461    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
462    pub async fn authorization_v1_for_request_with_async_body_reader(
463        &self,
464        url: &Uri,
465        content_type: Option<&HeaderValue>,
466        body: &mut (dyn AsyncRead + Send + Unpin),
467    ) -> IoResult<String> {
468        let authorization_token = sign_request_v1_with_async_body_reader(self, url, content_type, body).await?;
469        Ok("QBox ".to_owned() + &authorization_token)
470    }
471
472    /// 使用七牛签名算法 V2 对 HTTP 请求(请求体为异步输入流)进行签名,返回 Authorization 的值
473    ///
474    /// ```
475    /// use qiniu_credential::{Credential, Method, HeaderMap, HeaderValue, prelude::*};
476    /// use http::header::CONTENT_TYPE;
477    /// use mime::APPLICATION_JSON;
478    /// use futures_lite::io::Cursor;
479    /// #[async_std::main]
480    /// # async fn main() -> anyhow::Result<()> {
481    /// let credential = Credential::new("abcdefghklmnopq", "1234567890");
482    /// let mut headers = HeaderMap::new();
483    /// headers.insert(CONTENT_TYPE, HeaderValue::from_str(APPLICATION_JSON.as_ref())?);
484    /// let authorization = credential
485    ///     .async_get(Default::default()).await?
486    ///     .authorization_v2_for_request_with_async_body_reader(
487    ///         &Method::GET,
488    ///         &"http://upload.qiniup.com/".parse()?,
489    ///         &headers,
490    ///         &mut Cursor::new(b"{\"name\":\"test\"}")
491    ///     ).await?;
492    /// # Ok(())
493    /// # }
494    /// ```
495    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
496    pub async fn authorization_v2_for_request_with_async_body_reader(
497        &self,
498        method: &Method,
499        url: &Uri,
500        headers: &HeaderMap,
501        body: &mut (dyn AsyncRead + Send + Unpin),
502    ) -> IoResult<String> {
503        let authorization_token = sign_request_v2_with_async_body_reader(self, method, url, headers, body).await?;
504        Ok("Qiniu ".to_owned() + &authorization_token)
505    }
506}
507
508fn sign_request_v1(cred: &Credential, url: &Uri, content_type: Option<&HeaderValue>, body: &[u8]) -> String {
509    cred.sign_within::<IoError, _>(|hmac| {
510        _sign_request_v1_without_body(hmac, url);
511        if let Some(content_type) = content_type {
512            if !body.is_empty() && will_push_body_v1(content_type) {
513                hmac.update(body);
514            }
515        }
516        Ok(())
517    })
518    .unwrap()
519}
520
521fn sign_request_v1_with_body_reader(
522    cred: &Credential,
523    url: &Uri,
524    content_type: Option<&HeaderValue>,
525    body: &mut dyn Read,
526) -> IoResult<String> {
527    cred.sign_within(|hmac| {
528        _sign_request_v1_without_body(hmac, url);
529        if let Some(content_type) = content_type {
530            if will_push_body_v1(content_type) {
531                copy(body, hmac)?;
532            }
533        }
534        Ok(())
535    })
536}
537
538fn _sign_request_v1_without_body(digest: &mut Hmac<Sha1>, url: &Uri) {
539    digest.update(url.path().as_bytes());
540    if let Some(query) = url.query() {
541        if !query.is_empty() {
542            digest.update(b"?");
543            digest.update(query.as_bytes());
544        }
545    }
546    digest.update(b"\n");
547}
548
549fn sign_request_v2(cred: &Credential, method: &Method, url: &Uri, headers: &HeaderMap, body: &[u8]) -> String {
550    cred.sign_within::<IoError, _>(|hmac| {
551        _sign_request_v2_without_body(hmac, method, url, headers);
552        if let Some(content_type) = headers.get(CONTENT_TYPE) {
553            if will_push_body_v2(content_type) {
554                hmac.update(body);
555            }
556        }
557        Ok(())
558    })
559    .unwrap()
560}
561
562fn sign_request_v2_with_body_reader(
563    cred: &Credential,
564    method: &Method,
565    url: &Uri,
566    headers: &HeaderMap,
567    body: &mut dyn Read,
568) -> IoResult<String> {
569    cred.sign_within(|hmac| {
570        _sign_request_v2_without_body(hmac, method, url, headers);
571        if let Some(content_type) = headers.get(CONTENT_TYPE) {
572            if will_push_body_v2(content_type) {
573                copy(body, hmac)?;
574            }
575        }
576        Ok(())
577    })
578}
579
580fn _sign_request_v2_without_body(digest: &mut Hmac<Sha1>, method: &Method, url: &Uri, headers: &HeaderMap) {
581    digest.update(method.as_str().as_bytes());
582    digest.update(b" ");
583    digest.update(url.path().as_bytes());
584    if let Some(query) = url.query() {
585        if !query.is_empty() {
586            digest.update(b"?");
587            digest.update(query.as_bytes());
588        }
589    }
590    if let Some(host) = url.host() {
591        digest.update(b"\nHost: ");
592        digest.update(host.as_bytes());
593    }
594    if let Some(port) = url.port() {
595        digest.update(b":");
596        digest.update(port.to_string().as_bytes());
597    }
598    digest.update(b"\n");
599
600    if let Some(content_type) = headers.get(CONTENT_TYPE) {
601        digest.update(b"Content-Type: ");
602        digest.update(content_type.as_bytes());
603        digest.update(b"\n");
604    }
605    _sign_data_for_x_qiniu_headers(digest, headers);
606    digest.update(b"\n");
607    return;
608
609    fn _sign_data_for_x_qiniu_headers(digest: &mut Hmac<Sha1>, headers: &HeaderMap) {
610        let mut x_qiniu_headers = headers
611            .iter()
612            .map(|(key, value)| (make_header_name(key.as_str().into()), value.as_bytes()))
613            .filter(|(key, _)| key.len() > "X-Qiniu-".len())
614            .filter(|(key, _)| key.starts_with("X-Qiniu-"))
615            .collect::<Vec<_>>();
616        if x_qiniu_headers.is_empty() {
617            return;
618        }
619        x_qiniu_headers.sort_unstable();
620        for (header_key, header_value) in x_qiniu_headers {
621            digest.update(header_key.as_bytes());
622            digest.update(b": ");
623            digest.update(header_value);
624            digest.update(b"\n");
625        }
626    }
627}
628
629fn generate_base64ed_hmac_sha1_digest_within<E, F: FnOnce(&mut Hmac<Sha1>) -> Result<(), E>>(
630    secret_key: &str,
631    f: F,
632) -> Result<String, E> {
633    let mut hmac = new_hmac_sha1(secret_key);
634    f(&mut hmac)?;
635    Ok(base64ed_hmac_sha1(hmac))
636}
637
638fn new_hmac_sha1(secret_key: &str) -> Hmac<Sha1> {
639    Hmac::<Sha1>::new_from_slice(secret_key.as_bytes()).unwrap()
640}
641
642fn base64ed_hmac_sha1(hmac: Hmac<Sha1>) -> String {
643    base64::urlsafe(&hmac.finalize().into_bytes())
644}
645
646#[cfg(feature = "async")]
647fn base64ed_hmac_sha1_with_access_key(access_key: String, hmac: Hmac<Sha1>) -> String {
648    access_key + ":" + &base64ed_hmac_sha1(hmac)
649}
650
651fn will_push_body_v1(content_type: &HeaderValue) -> bool {
652    APPLICATION_WWW_FORM_URLENCODED.as_ref() == content_type
653}
654
655fn will_push_body_v2(content_type: &HeaderValue) -> bool {
656    APPLICATION_OCTET_STREAM.as_ref() != content_type
657}
658
659#[cfg(feature = "async")]
660mod async_sign {
661    use super::*;
662    use futures_lite::io::AsyncRead;
663    use std::task::{Context, Poll};
664
665    pub(super) async fn sign_request_v1_with_async_body_reader(
666        cred: &Credential,
667        url: &Uri,
668        content_type: Option<&HeaderValue>,
669        body: &mut (dyn AsyncRead + Send + Unpin),
670    ) -> IoResult<String> {
671        let mut hmac = new_hmac_sha1(cred.secret_key());
672        _sign_request_v1_without_body(&mut hmac, url);
673        if let Some(content_type) = content_type {
674            if will_push_body_v1(content_type) {
675                copy_async_reader_to_hmac_sha1(&mut hmac, body).await?;
676            }
677        }
678        Ok(base64ed_hmac_sha1_with_access_key(cred.access_key().to_string(), hmac))
679    }
680
681    pub(super) async fn sign_request_v2_with_async_body_reader(
682        cred: &Credential,
683        method: &Method,
684        url: &Uri,
685        headers: &HeaderMap,
686        body: &mut (dyn AsyncRead + Send + Unpin),
687    ) -> IoResult<String> {
688        let mut hmac = new_hmac_sha1(cred.secret_key());
689        _sign_request_v2_without_body(&mut hmac, method, url, headers);
690        if let Some(content_type) = headers.get(CONTENT_TYPE) {
691            if will_push_body_v2(content_type) {
692                copy_async_reader_to_hmac_sha1(&mut hmac, body).await?;
693            }
694        }
695        Ok(base64ed_hmac_sha1_with_access_key(cred.access_key().to_string(), hmac))
696    }
697
698    pub(super) async fn copy_async_reader_to_hmac_sha1(
699        hmac: &mut Hmac<Sha1>,
700        reader: &mut (dyn AsyncRead + Send + Unpin),
701    ) -> IoResult<u64> {
702        use futures_lite::io::{copy as async_io_copy, AsyncWrite};
703
704        struct AsyncHmacWriter<'a>(&'a mut Hmac<Sha1>);
705
706        impl AsyncWrite for AsyncHmacWriter<'_> {
707            #[inline]
708            fn poll_write(self: Pin<&mut Self>, _cx: &mut Context<'_>, buf: &[u8]) -> Poll<IoResult<usize>> {
709                #[allow(unsafe_code)]
710                unsafe { self.get_unchecked_mut() }.0.update(buf);
711                Poll::Ready(Ok(buf.len()))
712            }
713
714            #[inline]
715            fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<IoResult<()>> {
716                Poll::Ready(Ok(()))
717            }
718
719            #[inline]
720            fn poll_close(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<IoResult<()>> {
721                Poll::Ready(Ok(()))
722            }
723        }
724
725        async_io_copy(reader, &mut AsyncHmacWriter(hmac)).await
726    }
727}
728
729#[cfg(feature = "async")]
730#[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
731pub use futures_lite::AsyncRead;
732
733#[cfg(feature = "async")]
734use {
735    async_sign::*,
736    std::{future::Future, pin::Pin},
737};
738
739#[cfg(feature = "async")]
740type AsyncIoResult<'a, T> = Pin<Box<dyn Future<Output = IoResult<T>> + 'a + Send>>;
741
742/// 认证信息获取接口
743#[clonable]
744#[auto_impl(&, &mut, Box, Rc, Arc)]
745pub trait CredentialProvider: Clone + Debug + Sync + Send {
746    /// 返回七牛认证信息
747    ///
748    /// 该方法的异步版本为 [`Self::async_get`]。
749    fn get(&self, opts: GetOptions) -> IoResult<GotCredential>;
750
751    /// 异步返回七牛认证信息
752    #[inline]
753    #[cfg(feature = "async")]
754    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
755    fn async_get(&self, opts: GetOptions) -> AsyncIoResult<'_, GotCredential> {
756        Box::pin(async move { self.get(opts) })
757    }
758}
759
760impl CredentialProvider for Credential {
761    #[inline]
762    fn get(&self, _opts: GetOptions) -> IoResult<GotCredential> {
763        Ok(self.to_owned().into())
764    }
765}
766
767/// 获取认证信息的选项
768#[derive(Copy, Clone, Debug, Default)]
769pub struct GetOptions {}
770
771/// 获取的认证信息
772///
773/// 该数据结构目前和认证信息相同,可以和认证信息相互转换,但之后可能会添加更多字段
774#[derive(Clone, Debug, PartialEq, Eq)]
775pub struct GotCredential(Credential);
776
777impl From<GotCredential> for Credential {
778    #[inline]
779    fn from(result: GotCredential) -> Self {
780        result.into_credential()
781    }
782}
783
784impl From<Credential> for GotCredential {
785    #[inline]
786    fn from(credential: Credential) -> Self {
787        Self(credential)
788    }
789}
790
791impl GotCredential {
792    /// 获取认证信息
793    #[inline]
794    pub fn credential(&self) -> &Credential {
795        &self.0
796    }
797
798    /// 获取认证信息的可变引用
799    #[inline]
800    pub fn credential_mut(&mut self) -> &mut Credential {
801        &mut self.0
802    }
803
804    /// 转换为认证信息
805    #[inline]
806    pub fn into_credential(self) -> Credential {
807        self.0
808    }
809}
810
811impl Deref for GotCredential {
812    type Target = Credential;
813
814    #[inline]
815    fn deref(&self) -> &Self::Target {
816        &self.0
817    }
818}
819
820impl DerefMut for GotCredential {
821    #[inline]
822    fn deref_mut(&mut self) -> &mut Self::Target {
823        &mut self.0
824    }
825}
826
827impl CredentialProvider for GotCredential {
828    #[inline]
829    fn get(&self, _opts: GetOptions) -> IoResult<GotCredential> {
830        Ok(self.to_owned())
831    }
832}
833
834/// 全局认证信息提供者,可以将认证信息配置在全局变量中。任何全局认证信息提供者实例都可以设置和访问全局认证信息。
835#[derive(Copy, Clone, Eq, PartialEq)]
836pub struct GlobalCredentialProvider;
837
838static GLOBAL_CREDENTIAL: Lazy<RwLock<Option<Credential>>> = Lazy::new(|| RwLock::new(None));
839
840impl GlobalCredentialProvider {
841    /// 配置全局认证信息
842    #[inline]
843    pub fn setup(credential: Credential) {
844        let mut global_credential = GLOBAL_CREDENTIAL.write().unwrap();
845        *global_credential = Some(credential);
846    }
847
848    /// 清空全局认证信息
849    #[inline]
850    pub fn clear() {
851        let mut global_credential = GLOBAL_CREDENTIAL.write().unwrap();
852        *global_credential = None;
853    }
854}
855
856impl CredentialProvider for GlobalCredentialProvider {
857    #[inline]
858    fn get(&self, _opts: GetOptions) -> IoResult<GotCredential> {
859        if let Some(credential) = GLOBAL_CREDENTIAL.read().unwrap().as_ref() {
860            Ok(credential.to_owned().into())
861        } else {
862            Err(IoError::new(
863                IoErrorKind::Other,
864                "GlobalCredentialProvider is not setuped, please call GlobalCredentialProvider::setup() to do it",
865            ))
866        }
867    }
868}
869
870impl Debug for GlobalCredentialProvider {
871    #[inline]
872    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
873        let mut d = f.debug_struct("GlobalCredentialProvider");
874        d.field("credential", &GLOBAL_CREDENTIAL.read().unwrap());
875        d.finish()
876    }
877}
878
879/// 环境变量认证信息提供者,可以将认证信息配置在环境变量中。
880#[derive(Copy, Clone, Eq, PartialEq)]
881pub struct EnvCredentialProvider;
882
883/// 设置七牛 AccessKey 的环境变量
884pub const QINIU_ACCESS_KEY_ENV_KEY: &str = "QINIU_ACCESS_KEY";
885/// 设置七牛 SecretKey 的环境变量
886pub const QINIU_SECRET_KEY_ENV_KEY: &str = "QINIU_SECRET_KEY";
887
888impl EnvCredentialProvider {
889    /// 配置环境变量认证信息提供者
890    #[inline]
891    pub fn setup(credential: &Credential) {
892        env::set_var(QINIU_ACCESS_KEY_ENV_KEY, credential.access_key().as_str());
893        env::set_var(QINIU_SECRET_KEY_ENV_KEY, credential.secret_key().as_str());
894    }
895
896    /// 清空环境变量认证信息
897    #[inline]
898    pub fn clear() {
899        env::remove_var(QINIU_ACCESS_KEY_ENV_KEY);
900        env::remove_var(QINIU_SECRET_KEY_ENV_KEY);
901    }
902}
903
904impl CredentialProvider for EnvCredentialProvider {
905    fn get(&self, _opts: GetOptions) -> IoResult<GotCredential> {
906        match (env::var(QINIU_ACCESS_KEY_ENV_KEY), env::var(QINIU_SECRET_KEY_ENV_KEY)) {
907            (Ok(access_key), Ok(secret_key)) if !access_key.is_empty() && !secret_key.is_empty() => {
908                Ok(Credential::new(access_key, secret_key).into())
909            }
910            _ => {
911                static ERROR_MESSAGE: Lazy<String> = Lazy::new(|| {
912                    format!("EnvCredentialProvider is not setuped, please call EnvCredentialProvider::setup() to do it, or set environment variable `{QINIU_ACCESS_KEY_ENV_KEY}` and `{QINIU_SECRET_KEY_ENV_KEY}`")
913                });
914                Err(IoError::new(IoErrorKind::Other, ERROR_MESSAGE.as_str()))
915            }
916        }
917    }
918}
919
920impl Debug for EnvCredentialProvider {
921    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
922        let mut d = f.debug_struct("EnvCredentialProvider");
923        if let (Some(access_key), Some(secret_key)) = (
924            env::var_os(QINIU_ACCESS_KEY_ENV_KEY),
925            env::var_os(QINIU_SECRET_KEY_ENV_KEY),
926        ) {
927            d.field("access_key", &access_key).field("secret_key", &secret_key);
928        }
929        d.finish()
930    }
931}
932
933/// 认证信息串提供者
934///
935/// 将多个认证信息提供者串联,遍历并找寻第一个可用认证信息
936#[derive(Clone, Debug)]
937pub struct ChainCredentialsProvider {
938    credentials: Arc<[Box<dyn CredentialProvider>]>,
939}
940
941impl ChainCredentialsProvider {
942    /// 创建认证信息串提供者构建器
943    #[inline]
944    pub fn builder(credential: impl CredentialProvider + 'static) -> ChainCredentialsProviderBuilder {
945        ChainCredentialsProviderBuilder::new(credential)
946    }
947}
948
949impl CredentialProvider for ChainCredentialsProvider {
950    fn get(&self, opts: GetOptions) -> IoResult<GotCredential> {
951        let mut last_err = None;
952        if let Some(credential) = self.credentials.iter().find_map(|c| match c.get(opts) {
953            Ok(cred) => Some(cred),
954            Err(err) => {
955                last_err = Some(err);
956                None
957            }
958        }) {
959            Ok(credential)
960        } else {
961            Err(last_err.expect("No credential in ChainCredentialsProvider, which is unexpected"))
962        }
963    }
964
965    #[cfg(feature = "async")]
966    #[cfg_attr(feature = "docs", doc(cfg(feature = "async")))]
967    fn async_get(&self, opts: GetOptions) -> AsyncIoResult<'_, GotCredential> {
968        Box::pin(async move {
969            let mut last_err = None;
970            for provider in self.credentials.iter() {
971                match provider.async_get(opts).await {
972                    Ok(cred) => {
973                        return Ok(cred);
974                    }
975                    Err(err) => {
976                        last_err = Some(err);
977                    }
978                }
979            }
980            Err(last_err.expect("No credential in ChainCredentialsProvider, which is unexpected"))
981        })
982    }
983}
984
985impl Default for ChainCredentialsProvider {
986    #[inline]
987    fn default() -> Self {
988        ChainCredentialsProviderBuilder::new(Box::new(GlobalCredentialProvider))
989            .append_credential(Box::new(EnvCredentialProvider))
990            .build()
991    }
992}
993
994impl FromIterator<Box<dyn CredentialProvider>> for ChainCredentialsProvider {
995    #[inline]
996    fn from_iter<T: IntoIterator<Item = Box<dyn CredentialProvider>>>(iter: T) -> Self {
997        ChainCredentialsProviderBuilder::from_iter(iter).build()
998    }
999}
1000
1001impl<'a> IntoIterator for &'a ChainCredentialsProvider {
1002    type Item = &'a Box<dyn CredentialProvider + 'static>;
1003    type IntoIter = std::slice::Iter<'a, Box<dyn CredentialProvider + 'static>>;
1004
1005    #[inline]
1006    fn into_iter(self) -> Self::IntoIter {
1007        self.credentials.iter()
1008    }
1009}
1010
1011/// 串联认证信息构建器
1012///
1013/// 接受多个认证信息获取接口的实例并将他们串联成串联认证信息
1014#[derive(Debug, Clone, Default)]
1015pub struct ChainCredentialsProviderBuilder {
1016    credentials: VecDeque<Box<dyn CredentialProvider + 'static>>,
1017}
1018
1019impl ChainCredentialsProviderBuilder {
1020    /// 构建新的串联认证信息构建器
1021    #[inline]
1022    pub fn new(credential: impl CredentialProvider + 'static) -> Self {
1023        let mut builder = Self::default();
1024        builder.append_credential(credential);
1025        builder
1026    }
1027
1028    /// 将认证信息获取接口的实例推送到认证串末端
1029    #[inline]
1030    pub fn append_credential(&mut self, credential: impl CredentialProvider + 'static) -> &mut Self {
1031        self.credentials.push_back(Box::new(credential));
1032        self
1033    }
1034
1035    /// 将认证信息获取接口的实例推送到认证串顶端
1036    #[inline]
1037    pub fn prepend_credential(&mut self, credential: impl CredentialProvider + 'static) -> &mut Self {
1038        self.credentials.push_front(Box::new(credential));
1039        self
1040    }
1041
1042    /// 串联认证信息
1043    #[inline]
1044    pub fn build(&mut self) -> ChainCredentialsProvider {
1045        assert!(
1046            !self.credentials.is_empty(),
1047            "ChainCredentialsProvider must owns at least one CredentialProvider"
1048        );
1049        ChainCredentialsProvider {
1050            credentials: Vec::from(take(&mut self.credentials)).into_boxed_slice().into(),
1051        }
1052    }
1053}
1054
1055impl FromIterator<Box<dyn CredentialProvider>> for ChainCredentialsProviderBuilder {
1056    #[inline]
1057    fn from_iter<T: IntoIterator<Item = Box<dyn CredentialProvider>>>(iter: T) -> Self {
1058        ChainCredentialsProviderBuilder {
1059            credentials: VecDeque::from_iter(iter),
1060        }
1061    }
1062}
1063
1064impl Extend<Box<dyn CredentialProvider>> for ChainCredentialsProviderBuilder {
1065    #[inline]
1066    fn extend<T: IntoIterator<Item = Box<dyn CredentialProvider>>>(&mut self, iter: T) {
1067        self.credentials.extend(iter)
1068    }
1069}
1070
1071#[cfg(test)]
1072mod tests {
1073    use super::*;
1074    use anyhow::Result;
1075    use async_std as _;
1076    use mime::APPLICATION_JSON;
1077    use std::{io::Cursor, thread, time::Duration};
1078
1079    #[test]
1080    fn test_sign() -> Result<()> {
1081        let credential = get_credential();
1082        let mut threads = Vec::new();
1083        {
1084            let credential = credential.to_owned();
1085            threads.push(thread::spawn(move || {
1086                assert_eq!(
1087                    credential.get(Default::default()).unwrap().sign(b"hello"),
1088                    "abcdefghklmnopq:b84KVc-LroDiz0ebUANfdzSRxa0="
1089                );
1090                assert_eq!(
1091                    credential
1092                        .get(Default::default())
1093                        .unwrap()
1094                        .sign_reader(&mut Cursor::new(b"world"))
1095                        .unwrap(),
1096                    "abcdefghklmnopq:VjgXt0P_nCxHuaTfiFz-UjDJ1AQ="
1097                );
1098            }));
1099        }
1100        {
1101            threads.push(thread::spawn(move || {
1102                assert_eq!(
1103                    credential.get(Default::default()).unwrap().sign(b"-test"),
1104                    "abcdefghklmnopq:vYKRLUoXRlNHfpMEQeewG0zylaw="
1105                );
1106                assert_eq!(
1107                    credential
1108                        .get(Default::default())
1109                        .unwrap()
1110                        .sign_reader(&mut Cursor::new(b"ba#a-"))
1111                        .unwrap(),
1112                    "abcdefghklmnopq:2d_Yr6H1GdTKg3RvMtpHOhi047M="
1113                );
1114            }));
1115        }
1116        threads.into_iter().for_each(|thread| thread.join().unwrap());
1117        Ok(())
1118    }
1119
1120    #[test]
1121    fn test_sign_with_data() -> Result<()> {
1122        let credential = get_credential();
1123        let mut threads = Vec::new();
1124        {
1125            let credential = credential.to_owned();
1126            threads.push(thread::spawn(move || {
1127                assert_eq!(
1128                    credential.get(Default::default()).unwrap().sign_with_data(b"hello"),
1129                    "abcdefghklmnopq:BZYt5uVRy1RVt5ZTXbaIt2ROVMA=:aGVsbG8="
1130                );
1131                assert_eq!(
1132                    credential.get(Default::default()).unwrap().sign_with_data(b"world"),
1133                    "abcdefghklmnopq:Wpe04qzPphiSZb1u6I0nFn6KpZg=:d29ybGQ="
1134                );
1135            }));
1136        }
1137        {
1138            threads.push(thread::spawn(move || {
1139                assert_eq!(
1140                    credential.get(Default::default()).unwrap().sign_with_data(b"-test"),
1141                    "abcdefghklmnopq:HlxenSSP_6BbaYNzx1fyeyw8v1Y=:LXRlc3Q="
1142                );
1143                assert_eq!(
1144                    credential.get(Default::default()).unwrap().sign_with_data(b"ba#a-"),
1145                    "abcdefghklmnopq:kwzeJrFziPDMO4jv3DKVLDyqud0=:YmEjYS0="
1146                );
1147            }));
1148        }
1149        threads.into_iter().for_each(|thread| thread.join().unwrap());
1150        Ok(())
1151    }
1152
1153    #[test]
1154    fn test_authorization_v1_with_body_reader() -> Result<()> {
1155        let credential = get_credential();
1156        assert_eq!(
1157            credential
1158                .get(Default::default())?
1159                .authorization_v1_for_request_with_body_reader(
1160                    &"http://upload.qiniup.com/".parse()?,
1161                    None,
1162                    &mut Cursor::new(b"{\"name\":\"test\"}")
1163                )?,
1164            "QBox ".to_owned() + &credential.get(Default::default())?.sign(b"/\n")
1165        );
1166        assert_eq!(
1167            credential
1168                .get(Default::default())?
1169                .authorization_v1_for_request_with_body_reader(
1170                    &"http://upload.qiniup.com/".parse()?,
1171                    Some(&HeaderValue::from_str(APPLICATION_JSON.as_ref())?),
1172                    &mut Cursor::new(b"{\"name\":\"test\"}")
1173                )?,
1174            "QBox ".to_owned() + &credential.get(Default::default())?.sign(b"/\n")
1175        );
1176        assert_eq!(
1177            credential
1178                .get(Default::default())?
1179                .authorization_v1_for_request_with_body_reader(
1180                    &"http://upload.qiniup.com/".parse()?,
1181                    Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
1182                    &mut Cursor::new(b"name=test&language=go")
1183                )?,
1184            "QBox ".to_owned() + &credential.get(Default::default())?.sign(b"/\nname=test&language=go")
1185        );
1186        assert_eq!(
1187            credential
1188                .get(Default::default())?
1189                .authorization_v1_for_request_with_body_reader(
1190                    &"http://upload.qiniup.com/?v=2".parse()?,
1191                    Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
1192                    &mut Cursor::new(b"name=test&language=go")
1193                )?,
1194            "QBox ".to_owned()
1195                + &credential
1196                    .get(Default::default())?
1197                    .sign(b"/?v=2\nname=test&language=go")
1198        );
1199        assert_eq!(
1200            credential
1201                .get(Default::default())?
1202                .authorization_v1_for_request_with_body_reader(
1203                    &"http://upload.qiniup.com/find/sdk?v=2".parse()?,
1204                    Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
1205                    &mut Cursor::new(b"name=test&language=go")
1206                )?,
1207            "QBox ".to_owned()
1208                + &credential
1209                    .get(Default::default())?
1210                    .sign(b"/find/sdk?v=2\nname=test&language=go")
1211        );
1212        Ok(())
1213    }
1214
1215    #[test]
1216    fn test_authorization_v2_with_body_reader() -> Result<()> {
1217        let credential = get_global_credential();
1218        let empty_headers = {
1219            let mut headers = HeaderMap::new();
1220            headers.insert("x-qbox-meta", HeaderValue::from_str("value")?);
1221            headers
1222        };
1223        let json_headers = {
1224            let mut headers = HeaderMap::new();
1225            headers.insert(CONTENT_TYPE, HeaderValue::from_str(APPLICATION_JSON.as_ref())?);
1226            headers.insert("x-qbox-meta", HeaderValue::from_str("value")?);
1227            headers.insert("x-qiniu-cxxxx", HeaderValue::from_str("valuec")?);
1228            headers.insert("x-qiniu-bxxxx", HeaderValue::from_str("valueb")?);
1229            headers.insert("x-qiniu-axxxx", HeaderValue::from_str("valuea")?);
1230            headers.insert("x-qiniu-e", HeaderValue::from_str("value")?);
1231            headers.insert("x-qiniu-", HeaderValue::from_str("value")?);
1232            headers.insert("x-qiniu", HeaderValue::from_str("value")?);
1233            headers
1234        };
1235        let form_headers = {
1236            let mut headers = HeaderMap::new();
1237            headers.insert(
1238                CONTENT_TYPE,
1239                HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?,
1240            );
1241            headers.insert("x-qbox-meta", HeaderValue::from_str("value")?);
1242            headers.insert("x-qiniu-cxxxx", HeaderValue::from_str("valuec")?);
1243            headers.insert("x-qiniu-bxxxx", HeaderValue::from_str("valueb")?);
1244            headers.insert("x-qiniu-axxxx", HeaderValue::from_str("valuea")?);
1245            headers.insert("x-qiniu-e", HeaderValue::from_str("value")?);
1246            headers.insert("x-qiniu-", HeaderValue::from_str("value")?);
1247            headers.insert("x-qiniu", HeaderValue::from_str("value")?);
1248            headers
1249        };
1250        assert_eq!(
1251            credential
1252                .get(Default::default())?
1253                .authorization_v2_for_request_with_body_reader(
1254                    &Method::GET,
1255                    &"http://upload.qiniup.com/".parse()?,
1256                    &json_headers,
1257                    &mut Cursor::new(b"{\"name\":\"test\"}")
1258                )?,
1259            "Qiniu ".to_owned()
1260                + &credential.get(Default::default())?.sign(
1261                    concat!(
1262                        "GET /\n",
1263                        "Host: upload.qiniup.com\n",
1264                        "Content-Type: application/json\n",
1265                        "X-Qiniu-Axxxx: valuea\n",
1266                        "X-Qiniu-Bxxxx: valueb\n",
1267                        "X-Qiniu-Cxxxx: valuec\n",
1268                        "X-Qiniu-E: value\n\n",
1269                        "{\"name\":\"test\"}"
1270                    )
1271                    .as_bytes()
1272                )
1273        );
1274        assert_eq!(
1275            credential
1276                .get(Default::default())?
1277                .authorization_v2_for_request_with_body_reader(
1278                    &Method::GET,
1279                    &"http://upload.qiniup.com/".parse()?,
1280                    &empty_headers,
1281                    &mut Cursor::new(b"{\"name\":\"test\"}")
1282                )?,
1283            "Qiniu ".to_owned()
1284                + &credential
1285                    .get(Default::default())?
1286                    .sign(concat!("GET /\n", "Host: upload.qiniup.com\n\n").as_bytes())
1287        );
1288        assert_eq!(
1289            credential
1290                .get(Default::default())?
1291                .authorization_v2_for_request_with_body_reader(
1292                    &Method::POST,
1293                    &"http://upload.qiniup.com/".parse()?,
1294                    &json_headers,
1295                    &mut Cursor::new(b"{\"name\":\"test\"}")
1296                )?,
1297            "Qiniu ".to_owned()
1298                + &credential.get(Default::default())?.sign(
1299                    concat!(
1300                        "POST /\n",
1301                        "Host: upload.qiniup.com\n",
1302                        "Content-Type: application/json\n",
1303                        "X-Qiniu-Axxxx: valuea\n",
1304                        "X-Qiniu-Bxxxx: valueb\n",
1305                        "X-Qiniu-Cxxxx: valuec\n",
1306                        "X-Qiniu-E: value\n\n",
1307                        "{\"name\":\"test\"}"
1308                    )
1309                    .as_bytes()
1310                )
1311        );
1312        assert_eq!(
1313            credential
1314                .get(Default::default())?
1315                .authorization_v2_for_request_with_body_reader(
1316                    &Method::GET,
1317                    &"http://upload.qiniup.com/".parse()?,
1318                    &form_headers,
1319                    &mut Cursor::new(b"name=test&language=go")
1320                )?,
1321            "Qiniu ".to_owned()
1322                + &credential.get(Default::default())?.sign(
1323                    concat!(
1324                        "GET /\n",
1325                        "Host: upload.qiniup.com\n",
1326                        "Content-Type: application/x-www-form-urlencoded\n",
1327                        "X-Qiniu-Axxxx: valuea\n",
1328                        "X-Qiniu-Bxxxx: valueb\n",
1329                        "X-Qiniu-Cxxxx: valuec\n",
1330                        "X-Qiniu-E: value\n\n",
1331                        "name=test&language=go"
1332                    )
1333                    .as_bytes()
1334                )
1335        );
1336        assert_eq!(
1337            credential
1338                .get(Default::default())?
1339                .authorization_v2_for_request_with_body_reader(
1340                    &Method::GET,
1341                    &"http://upload.qiniup.com/?v=2".parse()?,
1342                    &form_headers,
1343                    &mut Cursor::new(b"name=test&language=go")
1344                )?,
1345            "Qiniu ".to_owned()
1346                + &credential.get(Default::default())?.sign(
1347                    concat!(
1348                        "GET /?v=2\n",
1349                        "Host: upload.qiniup.com\n",
1350                        "Content-Type: application/x-www-form-urlencoded\n",
1351                        "X-Qiniu-Axxxx: valuea\n",
1352                        "X-Qiniu-Bxxxx: valueb\n",
1353                        "X-Qiniu-Cxxxx: valuec\n",
1354                        "X-Qiniu-E: value\n\n",
1355                        "name=test&language=go"
1356                    )
1357                    .as_bytes()
1358                )
1359        );
1360        assert_eq!(
1361            credential
1362                .get(Default::default())?
1363                .authorization_v2_for_request_with_body_reader(
1364                    &Method::GET,
1365                    &"http://upload.qiniup.com/find/sdk?v=2".parse()?,
1366                    &form_headers,
1367                    &mut Cursor::new(b"name=test&language=go")
1368                )?,
1369            "Qiniu ".to_owned()
1370                + &credential.get(Default::default())?.sign(
1371                    concat!(
1372                        "GET /find/sdk?v=2\n",
1373                        "Host: upload.qiniup.com\n",
1374                        "Content-Type: application/x-www-form-urlencoded\n",
1375                        "X-Qiniu-Axxxx: valuea\n",
1376                        "X-Qiniu-Bxxxx: valueb\n",
1377                        "X-Qiniu-Cxxxx: valuec\n",
1378                        "X-Qiniu-E: value\n\n",
1379                        "name=test&language=go"
1380                    )
1381                    .as_bytes()
1382                )
1383        );
1384        Ok(())
1385    }
1386
1387    #[test]
1388    fn test_sign_download_url() -> Result<()> {
1389        let credential = get_env_credential();
1390        let url = "http://www.qiniu.com/?go=1".parse()?;
1391        let url = credential
1392            .get(Default::default())?
1393            .sign_download_url(url, Duration::from_secs(3600));
1394        assert!(url.to_string().starts_with("http://www.qiniu.com/?go=1&e="));
1395        assert!(url.to_string().contains("&token=abcdefghklmnopq"));
1396        Ok(())
1397    }
1398
1399    #[test]
1400    fn test_chain_credentials() -> Result<()> {
1401        GlobalCredentialProvider::clear();
1402        let chain_credentials = ChainCredentialsProvider::default();
1403        env::set_var(QINIU_ACCESS_KEY_ENV_KEY, "TEST2");
1404        env::set_var(QINIU_SECRET_KEY_ENV_KEY, "test2");
1405        {
1406            let cred = chain_credentials.get(Default::default())?;
1407            assert_eq!(cred.access_key().as_str(), "TEST2");
1408        }
1409        GlobalCredentialProvider::setup(Credential::new("TEST1", "test1"));
1410        {
1411            let cred = chain_credentials.get(Default::default())?;
1412            assert_eq!(cred.access_key().as_str(), "TEST1");
1413        }
1414        Ok(())
1415    }
1416
1417    #[test]
1418    #[should_panic]
1419    fn test_build_empty_chain_credentials() {
1420        ChainCredentialsProviderBuilder::default().build();
1421    }
1422
1423    fn get_credential() -> Credential {
1424        Credential::new("abcdefghklmnopq", "1234567890")
1425    }
1426
1427    fn get_global_credential() -> GlobalCredentialProvider {
1428        GlobalCredentialProvider::setup(Credential::new("abcdefghklmnopq", "1234567890"));
1429        GlobalCredentialProvider
1430    }
1431
1432    fn get_env_credential() -> EnvCredentialProvider {
1433        env::set_var(QINIU_ACCESS_KEY_ENV_KEY, "abcdefghklmnopq");
1434        env::set_var(QINIU_SECRET_KEY_ENV_KEY, "1234567890");
1435        EnvCredentialProvider
1436    }
1437
1438    #[cfg(feature = "async")]
1439    mod async_test {
1440        use super::*;
1441        use futures_lite::io::Cursor;
1442
1443        #[async_std::test]
1444        async fn test_sign_async_reader() -> Result<()> {
1445            let credential = get_credential();
1446            assert_eq!(
1447                credential
1448                    .get(Default::default())?
1449                    .sign_async_reader(&mut Cursor::new(b"hello"))
1450                    .await?,
1451                "abcdefghklmnopq:b84KVc-LroDiz0ebUANfdzSRxa0="
1452            );
1453            assert_eq!(
1454                credential
1455                    .get(Default::default())?
1456                    .sign_async_reader(&mut Cursor::new(b"world"))
1457                    .await?,
1458                "abcdefghklmnopq:VjgXt0P_nCxHuaTfiFz-UjDJ1AQ="
1459            );
1460            assert_eq!(
1461                credential
1462                    .get(Default::default())?
1463                    .sign_async_reader(&mut Cursor::new(b"-test"))
1464                    .await?,
1465                "abcdefghklmnopq:vYKRLUoXRlNHfpMEQeewG0zylaw="
1466            );
1467            assert_eq!(
1468                credential
1469                    .get(Default::default())?
1470                    .sign_async_reader(&mut Cursor::new(b"ba#a-"))
1471                    .await?,
1472                "abcdefghklmnopq:2d_Yr6H1GdTKg3RvMtpHOhi047M="
1473            );
1474            Ok(())
1475        }
1476
1477        #[async_std::test]
1478        async fn test_async_authorization_v1() -> Result<()> {
1479            let credential = get_credential();
1480            assert_eq!(
1481                credential
1482                    .get(Default::default())?
1483                    .authorization_v1_for_request_with_async_body_reader(
1484                        &"http://upload.qiniup.com/".parse()?,
1485                        None,
1486                        &mut Cursor::new(b"{\"name\":\"test\"}")
1487                    )
1488                    .await?,
1489                "QBox ".to_owned() + &credential.get(Default::default())?.sign(b"/\n")
1490            );
1491            assert_eq!(
1492                credential
1493                    .get(Default::default())?
1494                    .authorization_v1_for_request_with_async_body_reader(
1495                        &"http://upload.qiniup.com/".parse()?,
1496                        Some(&HeaderValue::from_str(APPLICATION_JSON.as_ref())?),
1497                        &mut Cursor::new(b"{\"name\":\"test\"}")
1498                    )
1499                    .await?,
1500                "QBox ".to_owned() + &credential.get(Default::default())?.sign(b"/\n")
1501            );
1502            assert_eq!(
1503                credential
1504                    .get(Default::default())?
1505                    .authorization_v1_for_request_with_async_body_reader(
1506                        &"http://upload.qiniup.com/".parse()?,
1507                        Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
1508                        &mut Cursor::new(b"name=test&language=go")
1509                    )
1510                    .await?,
1511                "QBox ".to_owned() + &credential.get(Default::default())?.sign(b"/\nname=test&language=go")
1512            );
1513            assert_eq!(
1514                credential
1515                    .get(Default::default())?
1516                    .authorization_v1_for_request_with_async_body_reader(
1517                        &"http://upload.qiniup.com/?v=2".parse()?,
1518                        Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
1519                        &mut Cursor::new(b"name=test&language=go")
1520                    )
1521                    .await?,
1522                "QBox ".to_owned()
1523                    + &credential
1524                        .get(Default::default())?
1525                        .sign(b"/?v=2\nname=test&language=go")
1526            );
1527            assert_eq!(
1528                credential
1529                    .get(Default::default())?
1530                    .authorization_v1_for_request_with_async_body_reader(
1531                        &"http://upload.qiniup.com/find/sdk?v=2".parse()?,
1532                        Some(&HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?),
1533                        &mut Cursor::new(b"name=test&language=go")
1534                    )
1535                    .await?,
1536                "QBox ".to_owned()
1537                    + &credential
1538                        .get(Default::default())?
1539                        .sign(b"/find/sdk?v=2\nname=test&language=go")
1540            );
1541            Ok(())
1542        }
1543
1544        #[async_std::test]
1545        async fn test_async_authorization_v2() -> Result<()> {
1546            let credential = get_global_credential();
1547            let empty_headers = {
1548                let mut headers = HeaderMap::new();
1549                headers.insert("x-qbox-meta", HeaderValue::from_str("value")?);
1550                headers
1551            };
1552            let json_headers = {
1553                let mut headers = HeaderMap::new();
1554                headers.insert(CONTENT_TYPE, HeaderValue::from_str(APPLICATION_JSON.as_ref())?);
1555                headers.insert("x-qbox-meta", HeaderValue::from_str("value")?);
1556                headers.insert("x-qiniu-cxxxx", HeaderValue::from_str("valuec")?);
1557                headers.insert("x-qiniu-bxxxx", HeaderValue::from_str("valueb")?);
1558                headers.insert("x-qiniu-axxxx", HeaderValue::from_str("valuea")?);
1559                headers.insert("x-qiniu-e", HeaderValue::from_str("value")?);
1560                headers.insert("x-qiniu-", HeaderValue::from_str("value")?);
1561                headers.insert("x-qiniu", HeaderValue::from_str("value")?);
1562                headers
1563            };
1564            let form_headers = {
1565                let mut headers = HeaderMap::new();
1566                headers.insert(
1567                    CONTENT_TYPE,
1568                    HeaderValue::from_str(APPLICATION_WWW_FORM_URLENCODED.as_ref())?,
1569                );
1570                headers.insert("x-qbox-meta", HeaderValue::from_str("value")?);
1571                headers.insert("x-qiniu-cxxxx", HeaderValue::from_str("valuec")?);
1572                headers.insert("x-qiniu-bxxxx", HeaderValue::from_str("valueb")?);
1573                headers.insert("x-qiniu-axxxx", HeaderValue::from_str("valuea")?);
1574                headers.insert("x-qiniu-e", HeaderValue::from_str("value")?);
1575                headers.insert("x-qiniu-", HeaderValue::from_str("value")?);
1576                headers.insert("x-qiniu", HeaderValue::from_str("value")?);
1577                headers
1578            };
1579            assert_eq!(
1580                credential
1581                    .get(Default::default())?
1582                    .authorization_v2_for_request_with_async_body_reader(
1583                        &Method::GET,
1584                        &"http://upload.qiniup.com/".parse()?,
1585                        &json_headers,
1586                        &mut Cursor::new(b"{\"name\":\"test\"}")
1587                    )
1588                    .await?,
1589                "Qiniu ".to_owned()
1590                    + &credential.get(Default::default())?.sign(
1591                        concat!(
1592                            "GET /\n",
1593                            "Host: upload.qiniup.com\n",
1594                            "Content-Type: application/json\n",
1595                            "X-Qiniu-Axxxx: valuea\n",
1596                            "X-Qiniu-Bxxxx: valueb\n",
1597                            "X-Qiniu-Cxxxx: valuec\n",
1598                            "X-Qiniu-E: value\n\n",
1599                            "{\"name\":\"test\"}"
1600                        )
1601                        .as_bytes()
1602                    )
1603            );
1604            assert_eq!(
1605                credential
1606                    .get(Default::default())?
1607                    .authorization_v2_for_request_with_async_body_reader(
1608                        &Method::GET,
1609                        &"http://upload.qiniup.com/".parse()?,
1610                        &empty_headers,
1611                        &mut Cursor::new(b"{\"name\":\"test\"}")
1612                    )
1613                    .await?,
1614                "Qiniu ".to_owned()
1615                    + &credential
1616                        .get(Default::default())?
1617                        .sign(concat!("GET /\n", "Host: upload.qiniup.com\n\n").as_bytes())
1618            );
1619            assert_eq!(
1620                credential
1621                    .get(Default::default())?
1622                    .authorization_v2_for_request_with_async_body_reader(
1623                        &Method::POST,
1624                        &"http://upload.qiniup.com/".parse()?,
1625                        &json_headers,
1626                        &mut Cursor::new(b"{\"name\":\"test\"}")
1627                    )
1628                    .await?,
1629                "Qiniu ".to_owned()
1630                    + &credential.get(Default::default())?.sign(
1631                        concat!(
1632                            "POST /\n",
1633                            "Host: upload.qiniup.com\n",
1634                            "Content-Type: application/json\n",
1635                            "X-Qiniu-Axxxx: valuea\n",
1636                            "X-Qiniu-Bxxxx: valueb\n",
1637                            "X-Qiniu-Cxxxx: valuec\n",
1638                            "X-Qiniu-E: value\n\n",
1639                            "{\"name\":\"test\"}"
1640                        )
1641                        .as_bytes()
1642                    )
1643            );
1644            assert_eq!(
1645                credential
1646                    .get(Default::default())?
1647                    .authorization_v2_for_request_with_async_body_reader(
1648                        &Method::GET,
1649                        &"http://upload.qiniup.com/".parse()?,
1650                        &form_headers,
1651                        &mut Cursor::new(b"name=test&language=go")
1652                    )
1653                    .await?,
1654                "Qiniu ".to_owned()
1655                    + &credential.get(Default::default())?.sign(
1656                        concat!(
1657                            "GET /\n",
1658                            "Host: upload.qiniup.com\n",
1659                            "Content-Type: application/x-www-form-urlencoded\n",
1660                            "X-Qiniu-Axxxx: valuea\n",
1661                            "X-Qiniu-Bxxxx: valueb\n",
1662                            "X-Qiniu-Cxxxx: valuec\n",
1663                            "X-Qiniu-E: value\n\n",
1664                            "name=test&language=go"
1665                        )
1666                        .as_bytes()
1667                    )
1668            );
1669            assert_eq!(
1670                credential
1671                    .get(Default::default())?
1672                    .authorization_v2_for_request_with_async_body_reader(
1673                        &Method::GET,
1674                        &"http://upload.qiniup.com/?v=2".parse()?,
1675                        &form_headers,
1676                        &mut Cursor::new(b"name=test&language=go")
1677                    )
1678                    .await?,
1679                "Qiniu ".to_owned()
1680                    + &credential.get(Default::default())?.sign(
1681                        concat!(
1682                            "GET /?v=2\n",
1683                            "Host: upload.qiniup.com\n",
1684                            "Content-Type: application/x-www-form-urlencoded\n",
1685                            "X-Qiniu-Axxxx: valuea\n",
1686                            "X-Qiniu-Bxxxx: valueb\n",
1687                            "X-Qiniu-Cxxxx: valuec\n",
1688                            "X-Qiniu-E: value\n\n",
1689                            "name=test&language=go"
1690                        )
1691                        .as_bytes()
1692                    )
1693            );
1694            assert_eq!(
1695                credential
1696                    .get(Default::default())?
1697                    .authorization_v2_for_request_with_async_body_reader(
1698                        &Method::GET,
1699                        &"http://upload.qiniup.com/find/sdk?v=2".parse()?,
1700                        &form_headers,
1701                        &mut Cursor::new(b"name=test&language=go")
1702                    )
1703                    .await?,
1704                "Qiniu ".to_owned()
1705                    + &credential.get(Default::default())?.sign(
1706                        concat!(
1707                            "GET /find/sdk?v=2\n",
1708                            "Host: upload.qiniup.com\n",
1709                            "Content-Type: application/x-www-form-urlencoded\n",
1710                            "X-Qiniu-Axxxx: valuea\n",
1711                            "X-Qiniu-Bxxxx: valueb\n",
1712                            "X-Qiniu-Cxxxx: valuec\n",
1713                            "X-Qiniu-E: value\n\n",
1714                            "name=test&language=go"
1715                        )
1716                        .as_bytes()
1717                    )
1718            );
1719            Ok(())
1720        }
1721
1722        #[async_std::test]
1723        async fn test_async_sign_download_url() -> Result<()> {
1724            let credential = get_env_credential();
1725            let url = "http://www.qiniu.com/?go=1".parse()?;
1726            let url = credential
1727                .async_get(Default::default())
1728                .await?
1729                .sign_download_url(url, Duration::from_secs(3600));
1730            assert!(url.to_string().starts_with("http://www.qiniu.com/?go=1&e="));
1731            assert!(url.to_string().contains("&token=abcdefghklmnopq"));
1732            Ok(())
1733        }
1734
1735        #[async_std::test]
1736        async fn test_async_chain_credentials() -> Result<()> {
1737            GlobalCredentialProvider::clear();
1738            let chain_credentials = ChainCredentialsProvider::default();
1739            env::set_var(QINIU_ACCESS_KEY_ENV_KEY, "TEST2");
1740            env::set_var(QINIU_SECRET_KEY_ENV_KEY, "test2");
1741            {
1742                let cred = chain_credentials.async_get(Default::default()).await?;
1743                assert_eq!(cred.access_key().as_str(), "TEST2");
1744            }
1745            GlobalCredentialProvider::setup(Credential::new("TEST1", "test1"));
1746            {
1747                let cred = chain_credentials.async_get(Default::default()).await?;
1748                assert_eq!(cred.access_key().as_str(), "TEST1");
1749            }
1750            Ok(())
1751        }
1752    }
1753}