Skip to main content

rusty_cat/aliyun-oss-direct/
download.rs

1use reqwest::header::{HeaderMap, ACCEPT, RANGE};
2use reqwest::Url;
3
4use super::constants::DEFAULT_RANGE_ACCEPT;
5use super::signing::{header_value, param_error, signed_headers};
6use crate::{BreakpointDownload, DownloadHeadCtx, DownloadRangeGetCtx, MeowError, TransferTask};
7
8/// Aliyun OSS direct range download protocol using AccessKey signing.
9#[derive(Clone)]
10pub struct AliOssDirectDownload {
11    bucket: String,
12    access_key_id: String,
13    access_key_secret: String,
14    region: String,
15}
16
17impl AliOssDirectDownload {
18    pub fn new(
19        bucket: impl Into<String>,
20        access_key_id: impl Into<String>,
21        access_key_secret: impl Into<String>,
22        region: impl Into<String>,
23    ) -> Self {
24        Self {
25            bucket: bucket.into(),
26            access_key_id: access_key_id.into(),
27            access_key_secret: access_key_secret.into(),
28            region: region.into(),
29        }
30    }
31
32    fn object_canonical_uri_from_task_url(&self, task: &TransferTask) -> Result<String, MeowError> {
33        let url = Url::parse(task.url()).map_err(param_error)?;
34        Ok(format!("/{}{}", self.bucket, url.path()))
35    }
36
37    fn apply_signed_headers(
38        &self,
39        task: &TransferTask,
40        method: &str,
41        base: &mut HeaderMap,
42    ) -> Result<(), MeowError> {
43        let headers = signed_headers(
44            method,
45            self.object_canonical_uri_from_task_url(task)?.as_str(),
46            None,
47            &[],
48            None,
49            self.access_key_id.as_str(),
50            self.access_key_secret.as_str(),
51            self.region.as_str(),
52        )?;
53        for (k, v) in headers {
54            if let Some(k) = k {
55                base.insert(k, v);
56            }
57        }
58        Ok(())
59    }
60}
61
62impl BreakpointDownload for AliOssDirectDownload {
63    fn resume_identity(&self, task: &TransferTask) -> Result<Option<Vec<u8>>, MeowError> {
64        let mut headers = task.headers().clone();
65        if !headers.contains_key(ACCEPT) {
66            headers.insert(ACCEPT, header_value(DEFAULT_RANGE_ACCEPT)?);
67        }
68        let mut context = crate::http_breakpoint::canonical_resume_headers(headers);
69        context.extend_from_slice(b"rusty-cat/aliyun-oss-direct/v1\0");
70        crate::http_breakpoint::append_resume_identity_field(&mut context, self.bucket.as_bytes());
71        crate::http_breakpoint::append_resume_identity_field(
72            &mut context,
73            self.access_key_id.as_bytes(),
74        );
75        crate::http_breakpoint::append_resume_identity_field(&mut context, self.region.as_bytes());
76        Ok(Some(context))
77    }
78
79    fn merge_head_headers(&self, ctx: DownloadHeadCtx<'_>) -> Result<(), MeowError> {
80        self.apply_signed_headers(ctx.task, "HEAD", ctx.base)
81    }
82
83    fn merge_range_get_headers(&self, ctx: DownloadRangeGetCtx<'_>) -> Result<(), MeowError> {
84        ctx.base.insert(RANGE, header_value(ctx.range_value)?);
85        if !ctx.base.contains_key(ACCEPT) {
86            ctx.base.insert(ACCEPT, header_value(DEFAULT_RANGE_ACCEPT)?);
87        }
88        self.apply_signed_headers(ctx.task, "GET", ctx.base)
89    }
90}