• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

tu6ge / oss-rs / 5863951548

pending completion
5863951548

Pull #24

github

tu6ge
feat(client): Reduce constraints on generic param
Pull Request #24: Branch0.12

39 of 39 new or added lines in 4 files covered. (100.0%)

6180 of 6422 relevant lines covered (96.23%)

9.24 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

98.47
/src/auth.rs
1
//! # Auth 模块
3✔
2
//! 计算 OSS API 的签名,并将数据收集到 `http::header::HeaderMap` 中
3
//!
4
//! ## Examples
5
//! ```rust
6
//! # use aliyun_oss_client::auth::RequestWithOSS;
7
//! use http::Method;
8
//!
9
//! async fn run() {
10
//!     let client = reqwest::Client::default();
11
//!     let mut request = client
12
//!         .request(
13
//!             Method::GET,
14
//!             "https://foo.oss-cn-shanghai.aliyuncs.com/?bucketInfo",
15
//!         )
16
//!         .build()
17
//!         .unwrap();
18
//!     request.with_oss("key1".into(), "secret2".into()).unwrap();
19
//!     let response = client.execute(request).await;
20
//!     println!("{:?}", response);
21
//! }
22
//! ```
23

24
use crate::{
25
    types::{
26
        CanonicalizedResource, ContentMd5, ContentType, Date, InnerCanonicalizedResource,
27
        InnerContentMd5, InnerDate, InnerKeyId, InnerKeySecret, KeyId, KeySecret,
28
        CONTINUATION_TOKEN,
29
    },
30
    BucketName, EndPoint,
31
};
32
use chrono::Utc;
33
#[cfg(test)]
34
use http::header::AsHeaderName;
35
use http::{
36
    header::{HeaderMap, HeaderValue, IntoHeaderName, InvalidHeaderValue, CONTENT_TYPE},
37
    Method,
38
};
39
#[cfg(test)]
40
use mockall::automock;
41
use std::fmt::{Debug, Display};
42
use std::{borrow::Cow, convert::TryInto};
43

44
pub mod query;
45

46
pub use query::QueryAuth;
47

48
#[cfg(test)]
49
mod test;
50

51
/// 计算 OSS 签名的数据
52
#[derive(Default, Clone)]
314✔
53
pub struct InnerAuth<'a> {
54
    access_key_id: InnerKeyId<'a>,
157✔
55
    access_key_secret: InnerKeySecret<'a>,
157✔
56
    method: Method,
157✔
57
    content_md5: Option<InnerContentMd5<'a>>,
157✔
58
    date: InnerDate<'a>,
157✔
59
    canonicalized_resource: InnerCanonicalizedResource<'a>,
157✔
60
    headers: HeaderMap,
157✔
61
}
62
/// 静态作用域的 InnerAuth
63
pub type Auth = InnerAuth<'static>;
64

65
impl<'a> InnerAuth<'a> {
66
    fn set_key(&mut self, access_key_id: InnerKeyId<'a>) {
84✔
67
        self.access_key_id = access_key_id;
84✔
68
    }
84✔
69

70
    #[cfg(test)]
71
    fn get_key(self) -> InnerKeyId<'a> {
2✔
72
        self.access_key_id
2✔
73
    }
2✔
74

75
    fn set_secret(&mut self, secret: InnerKeySecret<'a>) {
81✔
76
        self.access_key_secret = secret;
81✔
77
    }
81✔
78
    fn set_method(&mut self, method: Method) {
57✔
79
        self.method = method;
57✔
80
    }
57✔
81
    fn set_content_md5(&mut self, content_md5: ContentMd5) {
6✔
82
        self.content_md5 = Some(content_md5)
6✔
83
    }
6✔
84
    fn set_date(&mut self, date: Date) {
58✔
85
        self.date = date;
58✔
86
    }
58✔
87
    fn set_canonicalized_resource(&mut self, canonicalized_resource: CanonicalizedResource) {
58✔
88
        self.canonicalized_resource = canonicalized_resource;
58✔
89
    }
58✔
90
    fn set_headers(&mut self, headers: HeaderMap) {
1✔
91
        self.headers = headers;
1✔
92
    }
1✔
93
    fn extend_headers(&mut self, headers: HeaderMap) {
50✔
94
        self.headers.extend(headers);
50✔
95
    }
50✔
96
    fn header_insert<K: IntoHeaderName + 'static>(&mut self, key: K, val: HeaderValue) {
14✔
97
        self.headers.insert(key, val);
14✔
98
    }
14✔
99
    fn headers_clear(&mut self) {
1✔
100
        self.headers.clear();
1✔
101
    }
1✔
102

103
    #[cfg(test)]
104
    fn get_header<K>(self, key: K) -> Option<HeaderValue>
1✔
105
    where
106
        K: AsHeaderName,
107
    {
108
        self.headers.get(key).cloned()
1✔
109
    }
1✔
110

111
    #[cfg(test)]
112
    fn header_len(&self) -> usize {
2✔
113
        self.headers.len()
2✔
114
    }
2✔
115

116
    #[cfg(test)]
117
    fn header_contains_key<K>(&self, key: K) -> bool
1✔
118
    where
119
        K: AsHeaderName,
120
    {
121
        self.headers.contains_key(key)
1✔
122
    }
1✔
123
}
124

125
#[cfg_attr(test, automock)]
158✔
126
trait AuthToHeaderMap {
127
    fn get_original_header(&self) -> HeaderMap;
128
    fn get_header_key(&self) -> Result<HeaderValue, InvalidHeaderValue>;
129
    fn get_header_method(&self) -> Result<HeaderValue, InvalidHeaderValue>;
130
    fn get_header_md5(&self) -> Option<HeaderValue>;
131
    fn get_header_date(&self) -> Result<HeaderValue, InvalidHeaderValue>;
132
    fn get_header_resource(&self) -> Result<HeaderValue, InvalidHeaderValue>;
133
}
134

135
impl AuthToHeaderMap for InnerAuth<'_> {
136
    fn get_original_header(&self) -> HeaderMap {
58✔
137
        // 7 = 6 + 1
138
        let mut header = HeaderMap::with_capacity(7 + self.headers.len());
58✔
139
        header.extend(self.headers.clone());
58✔
140
        header
141
    }
58✔
142
    fn get_header_key(&self) -> Result<HeaderValue, InvalidHeaderValue> {
58✔
143
        self.access_key_id.as_ref().try_into()
58✔
144
    }
58✔
145
    fn get_header_method(&self) -> Result<HeaderValue, InvalidHeaderValue> {
58✔
146
        self.method.as_str().try_into()
58✔
147
    }
58✔
148
    fn get_header_md5(&self) -> Option<HeaderValue> {
59✔
149
        self.content_md5
59✔
150
            .as_ref()
151
            .and_then(|val| TryInto::<HeaderValue>::try_into(val).ok())
4✔
152
    }
59✔
153
    fn get_header_date(&self) -> Result<HeaderValue, InvalidHeaderValue> {
58✔
154
        self.date.as_ref().try_into()
58✔
155
    }
58✔
156
    fn get_header_resource(&self) -> Result<HeaderValue, InvalidHeaderValue> {
58✔
157
        self.canonicalized_resource.as_ref().try_into()
58✔
158
    }
58✔
159
}
160

161
trait AuthToOssHeader {
162
    fn to_oss_header(&self) -> OssHeader;
163
}
164

165
impl AuthToOssHeader for InnerAuth<'_> {
166
    /// 转化成 OssHeader
167
    fn to_oss_header(&self) -> OssHeader {
59✔
168
        const X_OSS_PRE: &str = "x-oss-";
169
        //return Some("x-oss-copy-source:/honglei123/file1.txt");
170
        let mut header: Vec<_> = self
59✔
171
            .headers
172
            .iter()
173
            .filter(|(k, _v)| k.as_str().starts_with(X_OSS_PRE))
40✔
174
            .collect();
175
        if header.is_empty() {
59✔
176
            return OssHeader(None);
57✔
177
        }
178

179
        header.sort_by(|(k1, _), (k2, _)| k1.as_str().cmp(k2.as_str()));
3✔
180

181
        let header_vec: Vec<_> = header
2✔
182
            .iter()
183
            .filter_map(|(k, v)| {
3✔
184
                v.to_str()
6✔
185
                    .ok()
186
                    .map(|value| k.as_str().to_owned() + ":" + value)
6✔
187
            })
3✔
188
            .collect();
189

190
        OssHeader(Some(header_vec.join(LINE_BREAK)))
2✔
191
    }
59✔
192
}
193

194
/// 从 auth 中提取各个字段,用于计算签名的原始字符串
195
trait AuthSignString {
196
    fn get_sign_info(
197
        &self,
198
    ) -> (
199
        &InnerKeyId,
200
        &InnerKeySecret,
201
        &Method,
202
        InnerContentMd5,
203
        ContentType,
204
        &InnerDate,
205
        &InnerCanonicalizedResource,
206
    );
207
}
208

209
impl AuthSignString for InnerAuth<'_> {
210
    #[inline]
211
    fn get_sign_info(
65✔
212
        &self,
213
    ) -> (
214
        &InnerKeyId,
215
        &InnerKeySecret,
216
        &Method,
217
        InnerContentMd5,
218
        ContentType,
219
        &InnerDate,
220
        &InnerCanonicalizedResource,
221
    ) {
222
        (
65✔
223
            &self.access_key_id,
65✔
224
            &self.access_key_secret,
65✔
225
            &self.method,
65✔
226
            self.content_md5.clone().unwrap_or_default(),
65✔
227
            self.headers
195✔
228
                .get(CONTENT_TYPE)
65✔
229
                .map_or(ContentType::default(), |ct| {
80✔
230
                    ct.to_owned().try_into().unwrap_or_else(|_| {
15✔
231
                        unreachable!("HeaderValue always is a rightful ContentType")
×
232
                    })
233
                }),
15✔
234
            &self.date,
65✔
235
            &self.canonicalized_resource,
65✔
236
        )
237
    }
65✔
238
}
239

240
impl InnerAuth<'_> {
241
    /// 返回携带了签名信息的 headers
242
    pub fn get_headers(&self) -> AuthResult<HeaderMap> {
54✔
243
        let mut map = HeaderMap::from_auth(self)?;
54✔
244

245
        let oss_header = self.to_oss_header();
54✔
246
        let sign_string = SignString::from_auth(self, oss_header);
54✔
247
        map.append_sign(sign_string.to_sign().map_err(AuthError::from)?)?;
54✔
248

249
        Ok(map)
54✔
250
    }
54✔
251
    /// 将 Auth 信息计算后附加到 HeaderMap 上
252
    fn append_headers(&self, headers: &mut HeaderMap) -> AuthResult<()> {
2✔
253
        headers.append_auth(self)?;
2✔
254
        let oss_header = self.to_oss_header();
2✔
255
        let sign_string = SignString::from_auth(self, oss_header);
2✔
256
        headers.append_sign(sign_string.to_sign().map_err(AuthError::from)?)?;
2✔
257

258
        Ok(())
2✔
259
    }
2✔
260
}
261

262
trait AuthHeader {
263
    fn from_auth(auth: &impl AuthToHeaderMap) -> Result<Self, InvalidHeaderValue>
264
    where
265
        Self: Sized;
266
    fn append_sign<S: TryInto<HeaderValue, Error = InvalidHeaderValue>>(
267
        &mut self,
268
        sign: S,
269
    ) -> Result<Option<HeaderValue>, InvalidHeaderValue>;
270
}
271

272
const ACCESS_KEY_ID: &str = "AccessKeyId";
273
const VERB_IDENT: &str = "VERB";
274
const CONTENT_MD5: &str = "Content-MD5";
275
const DATE: &str = "Date";
276
const CANONICALIZED_RESOURCE: &str = "CanonicalizedResource";
277
const AUTHORIZATION: &str = "Authorization";
278

279
impl AuthHeader for HeaderMap {
280
    fn from_auth(auth: &impl AuthToHeaderMap) -> Result<Self, InvalidHeaderValue> {
55✔
281
        let mut map = auth.get_original_header();
55✔
282

283
        map.insert(ACCESS_KEY_ID, auth.get_header_key()?);
55✔
284
        map.insert(VERB_IDENT, auth.get_header_method()?);
55✔
285

286
        if let Some(a) = auth.get_header_md5() {
55✔
287
            map.insert(CONTENT_MD5, a);
2✔
288
        }
55✔
289

290
        map.insert(DATE, auth.get_header_date()?);
55✔
291
        map.insert(CANONICALIZED_RESOURCE, auth.get_header_resource()?);
55✔
292

293
        //println!("header list: {:?}",map);
294
        Ok(map)
55✔
295
    }
55✔
296
    fn append_sign<S: TryInto<HeaderValue, Error = InvalidHeaderValue>>(
58✔
297
        &mut self,
298
        sign: S,
299
    ) -> Result<Option<HeaderValue>, InvalidHeaderValue> {
300
        let mut value: HeaderValue = sign.try_into()?;
58✔
301
        value.set_sensitive(true);
58✔
302
        let res = self.insert(AUTHORIZATION, value);
58✔
303
        Ok(res)
58✔
304
    }
58✔
305
}
306

307
trait AppendAuthHeader {
308
    fn append_auth<'a>(&'a mut self, auth: &InnerAuth<'a>) -> Result<(), InvalidHeaderValue>;
309
}
310

311
impl AppendAuthHeader for HeaderMap {
312
    fn append_auth<'a>(&'a mut self, auth: &InnerAuth<'a>) -> Result<(), InvalidHeaderValue> {
3✔
313
        self.extend(auth.get_original_header());
3✔
314

315
        self.insert(ACCESS_KEY_ID, auth.get_header_key()?);
3✔
316
        self.insert(VERB_IDENT, auth.get_header_method()?);
3✔
317

318
        if let Some(a) = auth.get_header_md5() {
3✔
319
            self.insert(CONTENT_MD5, a);
2✔
320
        }
3✔
321

322
        self.insert(DATE, auth.get_header_date()?);
3✔
323
        self.insert(CANONICALIZED_RESOURCE, auth.get_header_resource()?);
3✔
324

325
        Ok(())
3✔
326
    }
3✔
327
}
328

329
/// # 前缀是 x-oss- 的 header 记录
330
///
331
/// 将他们按顺序组合成一个字符串,用于计算签名
332
struct OssHeader(Option<String>);
333

334
impl OssHeader {
335
    #[allow(dead_code)]
336
    fn new(string: Option<String>) -> Self {
9✔
337
        Self(string)
9✔
338
    }
9✔
339

340
    #[allow(dead_code)]
341
    fn is_none(&self) -> bool {
2✔
342
        self.0.is_none()
2✔
343
    }
2✔
344

345
    #[inline]
346
    fn len(&self) -> usize {
66✔
347
        self.0.as_ref().map_or(0_usize, |str| str.len())
74✔
348
    }
66✔
349
}
350

351
impl Display for OssHeader {
352
    /// 转化成 SignString 需要的格式
353
    fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
66✔
354
        let mut content = String::with_capacity({
66✔
355
            let len = self.len();
66✔
356
            if len > 0 {
66✔
357
                len + 2
6✔
358
            } else {
359
                0
60✔
360
            }
361
        });
362
        if let Some(str) = &self.0 {
66✔
363
            content.push_str(str);
8✔
364
            content.push_str(LINE_BREAK);
8✔
365
        }
366
        write!(f, "{}", content)
66✔
367
    }
66✔
368
}
369

370
/// 待签名的数据
371
#[derive(Debug)]
2✔
372
struct SignString<'a> {
373
    data: String,
1✔
374
    key: InnerKeyId<'a>,
375
    secret: InnerKeySecret<'a>,
1✔
376
}
377

378
const LINE_BREAK: &str = "\n";
379

380
impl<'a, 'b> SignString<'_> {
381
    #[allow(dead_code)]
382
    #[inline]
383
    fn new(data: &'b str, key: InnerKeyId<'a>, secret: InnerKeySecret<'a>) -> SignString<'a> {
2✔
384
        SignString {
2✔
385
            data: data.to_owned(),
2✔
386
            key,
387
            secret,
388
        }
389
    }
2✔
390
}
391

392
impl<'a> SignString<'a> {
393
    fn from_auth(auth: &'a impl AuthSignString, header: OssHeader) -> SignString {
57✔
394
        let (key, secret, verb, content_md5, content_type, date, canonicalized_resource) =
57✔
395
            auth.get_sign_info();
57✔
396
        let method = verb.to_string();
57✔
397

398
        let data = method
342✔
399
            + LINE_BREAK
400
            + content_md5.as_ref()
57✔
401
            + LINE_BREAK
402
            + content_type.as_ref()
57✔
403
            + LINE_BREAK
404
            + date.as_ref()
57✔
405
            + LINE_BREAK
406
            + &header.to_string()
57✔
407
            + canonicalized_resource.as_ref();
114✔
408

409
        SignString {
57✔
410
            data,
57✔
411
            key: key.clone(),
57✔
412
            secret: secret.clone(),
57✔
413
        }
414
    }
57✔
415

416
    #[cfg(test)]
417
    pub fn data(&self) -> String {
1✔
418
        self.data.clone()
1✔
419
    }
1✔
420

421
    #[cfg(test)]
422
    fn key_string(&self) -> String {
1✔
423
        self.key.as_ref().to_string()
1✔
424
    }
1✔
425

426
    #[cfg(test)]
427
    fn secret_string(&self) -> String {
1✔
428
        self.secret.as_str().to_string()
1✔
429
    }
1✔
430

431
    // 转化成签名
432
    fn to_sign(&self) -> Result<Sign, hmac::digest::crypto_common::InvalidLength> {
57✔
433
        Ok(Sign {
57✔
434
            data: self.secret.encryption(self.data.as_bytes())?,
57✔
435
            key: self.key.clone(),
57✔
436
        })
437
    }
57✔
438
}
439

440
/// header 中的签名
441
#[derive(Debug)]
2✔
442
struct Sign<'a> {
443
    data: String,
1✔
444
    key: InnerKeyId<'a>,
1✔
445
}
446

447
impl Sign<'_> {
448
    #[cfg(test)]
449
    fn new<'a, 'b>(data: &'b str, key: InnerKeyId<'a>) -> Sign<'a> {
2✔
450
        Sign {
2✔
451
            data: data.to_owned(),
2✔
452
            key,
453
        }
454
    }
2✔
455

456
    #[cfg(test)]
457
    pub fn data(&self) -> &str {
1✔
458
        &self.data
1✔
459
    }
1✔
460

461
    #[cfg(test)]
462
    pub fn key_string(&self) -> String {
1✔
463
        self.key.as_ref().to_string()
1✔
464
    }
1✔
465
}
466

467
impl TryInto<HeaderValue> for Sign<'_> {
468
    type Error = InvalidHeaderValue;
469

470
    /// 转化成 header 中需要的格式
471
    fn try_into(self) -> Result<HeaderValue, Self::Error> {
57✔
472
        let sign = format!("OSS {}:{}", self.key.as_ref(), self.data);
57✔
473
        sign.parse()
57✔
474
    }
57✔
475
}
476

477
/// Auth 结构体的构建器
478
#[derive(Default, Clone)]
310✔
479
pub struct AuthBuilder {
480
    auth: Auth,
155✔
481
}
482

483
impl AuthBuilder {
484
    /// 给 key 赋值
485
    ///
486
    /// ```
487
    /// # use aliyun_oss_client::auth::AuthBuilder;
488
    /// let mut headers = AuthBuilder::default();
489
    /// headers.key("bar");
490
    /// headers.get_headers();
491
    /// ```
492
    #[inline]
493
    pub fn key<K: Into<KeyId>>(&mut self, key: K) {
67✔
494
        self.auth.set_key(key.into());
67✔
495
    }
67✔
496

497
    /// 给 secret 赋值
498
    #[inline]
499
    pub fn secret<S: Into<KeySecret>>(&mut self, secret: S) {
65✔
500
        self.auth.set_secret(secret.into());
65✔
501
    }
65✔
502

503
    pub(crate) fn get_key(&self) -> &KeyId {
1✔
504
        &self.auth.access_key_id
1✔
505
    }
1✔
506
    pub(crate) fn get_secret(&self) -> &KeySecret {
1✔
507
        &self.auth.access_key_secret
1✔
508
    }
1✔
509

510
    /// 给 verb 赋值
511
    #[inline]
512
    pub fn method(&mut self, method: &Method) {
46✔
513
        self.auth.set_method(method.to_owned());
46✔
514
    }
46✔
515

516
    /// 给 content_md5 赋值
517
    #[inline]
518
    pub fn content_md5<Md5: Into<ContentMd5>>(&mut self, content_md5: Md5) {
6✔
519
        self.auth.set_content_md5(content_md5.into());
6✔
520
    }
6✔
521

522
    /// # 给 date 赋值
523
    ///
524
    /// ## Example
525
    /// ```
526
    /// use chrono::Utc;
527
    /// let builder = aliyun_oss_client::auth::AuthBuilder::default().date(Utc::now());
528
    /// ```
529
    #[inline]
530
    pub fn date<D: Into<Date>>(&mut self, date: D) {
58✔
531
        self.auth.set_date(date.into());
58✔
532
    }
58✔
533

534
    /// 给 content_md5 赋值
535
    #[inline]
536
    pub fn canonicalized_resource<Res: Into<CanonicalizedResource>>(&mut self, data: Res) {
57✔
537
        self.auth.set_canonicalized_resource(data.into());
57✔
538
    }
57✔
539

540
    /// 给 Auth 附加新的 headers 信息
541
    #[inline]
542
    pub fn with_headers(&mut self, headers: Option<HeaderMap>) {
2✔
543
        if let Some(headers) = headers {
2✔
544
            self.extend_headers(headers);
1✔
545
        }
546
    }
2✔
547

548
    /// 给 Auth 设置全新的 headers 信息
549
    #[inline]
550
    pub fn headers(&mut self, headers: HeaderMap) {
1✔
551
        self.auth.set_headers(headers);
1✔
552
    }
1✔
553

554
    /// 给 Auth 附加新的 headers 信息
555
    #[inline]
556
    pub fn extend_headers(&mut self, headers: HeaderMap) {
39✔
557
        self.auth.extend_headers(headers);
39✔
558
    }
39✔
559

560
    /// 给 header 序列添加新值
561
    #[inline]
562
    pub fn header_insert<K: IntoHeaderName + 'static>(&mut self, key: K, val: HeaderValue) {
14✔
563
        self.auth.header_insert(key, val);
14✔
564
    }
14✔
565

566
    /// 清理 headers
567
    #[inline]
568
    pub fn header_clear(&mut self) {
1✔
569
        self.auth.headers_clear();
1✔
570
    }
1✔
571

572
    #[allow(dead_code)]
573
    #[inline]
574
    fn build(self) -> Auth {
25✔
575
        self.auth
25✔
576
    }
25✔
577
}
578

579
impl AuthBuilder {
580
    /// 返回携带了签名信息的 headers
581
    pub fn get_headers(&self) -> AuthResult<HeaderMap> {
50✔
582
        self.auth.get_headers()
50✔
583
    }
50✔
584
}
585

586
/// 将 OSS 签名信息附加到 Request 中
587
pub trait RequestWithOSS {
588
    /// 输入 key,secret,以及 Request 中的 method,header,url,query
589
    /// 等信息,计算 OSS 签名
590
    /// 并把签名后的 header 信息,传递给 self
591
    fn with_oss(&mut self, key: InnerKeyId, secret: InnerKeySecret) -> AuthResult<()>;
592
}
593

594
use reqwest::{Request, Url};
595

596
impl RequestWithOSS for Request {
597
    fn with_oss(&mut self, key: InnerKeyId, secret: InnerKeySecret) -> AuthResult<()> {
2✔
598
        let mut auth = InnerAuth {
2✔
599
            access_key_id: key,
600
            access_key_secret: secret,
601
            method: self.method().clone(),
2✔
602
            date: Utc::now().into(),
2✔
603
            ..Default::default()
2✔
604
        };
2✔
605

606
        auth.set_canonicalized_resource(self.url().canonicalized_resource().ok_or(AuthError {
2✔
607
            kind: AuthErrorKind::InvalidCanonicalizedResource,
608
        })?);
1✔
609

610
        auth.append_headers(self.headers_mut())?;
1✔
611

612
        Ok(())
1✔
613
    }
2✔
614
}
615

616
/// 根据 Url 计算 [`CanonicalizedResource`]
617
///
618
/// [`CanonicalizedResource`]: crate::types::CanonicalizedResource
619
trait GenCanonicalizedResource {
620
    /// 计算并返回 [`CanonicalizedResource`], 无法计算则返回 `None`
621
    ///
622
    /// [`CanonicalizedResource`]: crate::types::CanonicalizedResource
623
    fn canonicalized_resource(&self) -> Option<CanonicalizedResource>;
624

625
    /// 根据 Url 计算 bucket 名称和 Endpoint
626
    fn oss_host(&self) -> OssHost;
627

628
    /// 根据 Url 的 query 计算 [`CanonicalizedResource`]
629
    ///
630
    /// [`CanonicalizedResource`]: crate::types::CanonicalizedResource
631
    fn object_list_resource(&self, bucket: &BucketName) -> CanonicalizedResource;
632

633
    /// 根据 Url 的 path 计算当前使用的 Object 文件路径
634
    fn object_path(&self) -> Option<Cow<'_, str>>;
635
}
636

637
/// Oss 域名的几种状态
638
#[derive(PartialEq, Debug, Eq)]
11✔
639
enum OssHost {
640
    /// 有 bucket 的,包含 bucket 名字
641
    Bucket(BucketName),
2✔
642
    /// 只有 endpoint
643
    EndPoint,
644
    /// 其他
645
    None,
646
}
647

648
const LIST_TYPE2: &str = "list-type=2";
649
const LIST_TYPE2_AND: &str = "list-type=2&";
650
const COM: &str = "com";
651
const ALIYUNCS: &str = "aliyuncs";
652

653
impl GenCanonicalizedResource for Url {
654
    fn canonicalized_resource(&self) -> Option<CanonicalizedResource> {
10✔
655
        use crate::types::BUCKET_INFO;
656

657
        let bucket = match self.oss_host() {
10✔
658
            OssHost::None => return None,
2✔
659
            OssHost::EndPoint => return Some(CanonicalizedResource::from_endpoint()),
1✔
660
            OssHost::Bucket(bucket) => bucket,
7✔
661
        };
1✔
662

663
        if self.path().is_empty() {
7✔
664
            return None;
×
665
        }
666

667
        // 没有 object 的情况
668
        if self.path() == "/" {
7✔
669
            return match self.query() {
6✔
670
                // 查询单个bucket 信息
671
                Some(BUCKET_INFO) => Some(CanonicalizedResource::from_bucket_name(
6✔
672
                    &bucket,
673
                    Some(BUCKET_INFO),
4✔
674
                )),
4✔
675
                // 查 object_list
676
                Some(q) if q.ends_with(LIST_TYPE2) || q.contains(LIST_TYPE2_AND) => {
2✔
677
                    Some(self.object_list_resource(&bucket))
2✔
678
                }
2✔
679
                // 其他情况待定
680
                _ => todo!("Unable to obtain can information based on existing query information"),
×
681
            };
682
        }
683

684
        // 获取 ObjectPath 失败,返回 None,否则根据 ObjectPath 计算 CanonicalizedResource
685
        self.object_path()
2✔
686
            .map(|path| CanonicalizedResource::from_object_without_query(bucket.as_ref(), path))
2✔
687
    }
10✔
688

689
    fn oss_host(&self) -> OssHost {
20✔
690
        use url::Host;
691
        let domain = match self.host() {
20✔
692
            Some(Host::Domain(domain)) => domain,
19✔
693
            _ => return OssHost::None,
1✔
694
        };
695

696
        let mut url_pieces = domain.rsplit('.');
19✔
697

698
        match (url_pieces.next(), url_pieces.next()) {
19✔
699
            (Some(COM), Some(ALIYUNCS)) => (),
19✔
700
            _ => return OssHost::None,
2✔
701
        }
702

703
        match url_pieces.next() {
17✔
704
            Some(endpoint) => match EndPoint::from_host_piece(endpoint) {
16✔
705
                Ok(_) => (),
706
                _ => return OssHost::None,
2✔
707
            },
16✔
708
            _ => return OssHost::None,
1✔
709
        };
710

711
        match url_pieces.next() {
14✔
712
            Some(bucket) => {
11✔
713
                if let Ok(b) = BucketName::from_static(bucket) {
11✔
714
                    OssHost::Bucket(b)
10✔
715
                } else {
716
                    OssHost::None
1✔
717
                }
718
            }
11✔
719
            None => OssHost::EndPoint,
3✔
720
        }
721
    }
20✔
722

723
    fn object_list_resource(&self, bucket: &BucketName) -> CanonicalizedResource {
6✔
724
        let mut query = self.query_pairs().filter(|(_, val)| !val.is_empty());
13✔
725
        match query.find(|(key, _)| key == CONTINUATION_TOKEN) {
11✔
726
            Some((_, token)) => CanonicalizedResource::new(format!(
6✔
727
                "/{}/?continuation-token={}",
728
                bucket.as_ref(),
3✔
729
                token
730
            )),
3✔
731
            None => CanonicalizedResource::new(format!("/{}/", bucket.as_ref())),
3✔
732
        }
733
    }
6✔
734

735
    fn object_path(&self) -> Option<Cow<'_, str>> {
8✔
736
        use percent_encoding::percent_decode;
737

738
        if self.path().ends_with('/') {
8✔
739
            return None;
3✔
740
        }
741

742
        let input = if self.path().starts_with('/') {
5✔
743
            &self.path()[1..]
5✔
744
        } else {
745
            self.path()
×
746
        }
747
        .as_bytes();
748
        percent_decode(input).decode_utf8().ok()
5✔
749
    }
8✔
750
}
751

752
impl GenCanonicalizedResource for Request {
753
    fn canonicalized_resource(&self) -> Option<CanonicalizedResource> {
1✔
754
        self.url().canonicalized_resource()
1✔
755
    }
1✔
756

757
    fn oss_host(&self) -> OssHost {
1✔
758
        self.url().oss_host()
1✔
759
    }
1✔
760

761
    fn object_list_resource(&self, bucket: &BucketName) -> CanonicalizedResource {
1✔
762
        self.url().object_list_resource(bucket)
1✔
763
    }
1✔
764

765
    fn object_path(&self) -> Option<Cow<'_, str>> {
1✔
766
        self.url().object_path()
1✔
767
    }
1✔
768
}
769

770
/// Auth 模块的错误
771
#[derive(Debug)]
4✔
772
#[non_exhaustive]
773
pub struct AuthError {
774
    pub(crate) kind: AuthErrorKind,
2✔
775
}
776

777
impl AuthError {
778
    #[cfg(test)]
779
    pub(crate) fn test_new() -> Self {
1✔
780
        Self {
781
            kind: AuthErrorKind::InvalidCanonicalizedResource,
782
        }
783
    }
1✔
784
}
785

786
/// Auth 模块错误的枚举
787
#[derive(Debug)]
2✔
788
#[non_exhaustive]
789
pub(crate) enum AuthErrorKind {
790
    #[doc(hidden)]
791
    HeaderValue(http::header::InvalidHeaderValue),
×
792
    #[doc(hidden)]
793
    Hmac(hmac::digest::crypto_common::InvalidLength),
×
794
    #[doc(hidden)]
795
    InvalidCanonicalizedResource,
796
}
797

798
impl std::error::Error for AuthError {
799
    fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
3✔
800
        use AuthErrorKind::*;
801
        match &self.kind {
3✔
802
            HeaderValue(e) => Some(e),
1✔
803
            Hmac(e) => Some(e),
1✔
804
            InvalidCanonicalizedResource => None,
1✔
805
        }
806
    }
3✔
807
}
808

809
impl Display for AuthError {
810
    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
7✔
811
        use AuthErrorKind::*;
812
        match self.kind {
7✔
813
            HeaderValue(_) => f.write_str("failed to parse header value"),
2✔
814
            Hmac(_) => f.write_str("invalid aliyun secret length"),
2✔
815
            InvalidCanonicalizedResource => f.write_str("invalid canonicalized-resource"),
3✔
816
        }
817
    }
7✔
818
}
819

820
impl From<http::header::InvalidHeaderValue> for AuthError {
821
    /// ```
822
    /// # use aliyun_oss_client::auth::AuthError;
823
    /// # use http::header::HeaderValue;
824
    /// let val = HeaderValue::from_str("\n");
825
    /// let header_error = val.unwrap_err();
826
    /// let auth_error: AuthError = header_error.into();
827
    /// assert_eq!(format!("{}", auth_error), "failed to parse header value");
828
    /// ```
829
    fn from(value: http::header::InvalidHeaderValue) -> Self {
1✔
830
        Self {
831
            kind: AuthErrorKind::HeaderValue(value),
832
        }
833
    }
1✔
834
}
835
impl From<hmac::digest::crypto_common::InvalidLength> for AuthError {
836
    /// ```
837
    /// # use aliyun_oss_client::auth::AuthError;
838
    /// let hmac_error = hmac::digest::crypto_common::InvalidLength {};
839
    /// let auth_error: AuthError = hmac_error.into();
840
    /// assert_eq!(format!("{}", auth_error), "invalid aliyun secret length");
841
    /// ```
842
    fn from(value: hmac::digest::crypto_common::InvalidLength) -> Self {
1✔
843
        Self {
844
            kind: AuthErrorKind::Hmac(value),
845
        }
846
    }
1✔
847
}
848

849
type AuthResult<T> = Result<T, AuthError>;
850

851
#[cfg(test)]
852
mod builder_tests {
853
    use http::{header::CONTENT_LANGUAGE, HeaderMap};
854

855
    use super::AuthBuilder;
856

857
    #[test]
858
    fn key() {
2✔
859
        let builder = AuthBuilder::default();
1✔
860
        assert_eq!(builder.build().get_key().as_ref(), "");
1✔
861

862
        let mut builder = AuthBuilder::default();
1✔
863
        builder.key("bar");
1✔
864
        assert_eq!(builder.build().get_key().as_ref(), "bar");
1✔
865
    }
2✔
866

867
    #[test]
868
    fn with_headers() {
2✔
869
        let builder = AuthBuilder::default();
1✔
870
        let before_len = builder.build().get_headers().unwrap().len();
1✔
871
        assert_eq!(before_len, 5);
1✔
872

873
        let mut builder = AuthBuilder::default();
1✔
874
        builder.with_headers(Some({
1✔
875
            let mut headers = HeaderMap::new();
1✔
876
            headers.insert(CONTENT_LANGUAGE, "abc".parse().unwrap());
1✔
877
            headers
1✔
878
        }));
879
        let len = builder.build().get_headers().unwrap().len();
1✔
880
        assert_eq!(len, 6);
1✔
881

882
        let mut builder = AuthBuilder::default();
1✔
883
        builder.with_headers(None);
1✔
884
        let len = builder.build().get_headers().unwrap().len();
1✔
885
        assert_eq!(len, 5);
1✔
886
    }
2✔
887
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc