xt_oss/
util.rs

1use std::{
2    fmt,
3    fs::File,
4    io::{self, Read},
5};
6
7use base64::{engine::general_purpose, Engine as _};
8use chrono::{DateTime, Local, Utc};
9use crypto::{digest::Digest, md5::Md5};
10use oss::http;
11
12use crate::oss;
13
14fn get_env(key: &str, default: &str) -> String {
15    std::env::var(key).unwrap_or(default.to_string())
16}
17
18fn get_env_bool(key: &str, default: bool) -> bool {
19    std::env::var(key)
20        .unwrap_or(default.to_string())
21        .parse()
22        .unwrap_or(default)
23}
24
25/// UTC to GMT format
26#[inline]
27pub fn utc_to_gmt(datetime: DateTime<Utc>) -> String {
28    datetime.format(super::oss::GMT_DATE_FMT).to_string()
29}
30
31/// LOCAL to GMT format
32#[inline]
33pub fn local_to_gmt(local_datetime: DateTime<Local>) -> String {
34    let utc_datetime: DateTime<Utc> = local_datetime.with_timezone(&Utc);
35    utc_datetime.format(super::oss::GMT_DATE_FMT).to_string()
36}
37
38pub enum AllowedOriginItem<'a> {
39    Any,
40    Urls(Vec<&'a str>),
41}
42
43impl<'a> fmt::Display for AllowedOriginItem<'a> {
44    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
45        write!(
46            f,
47            "{}",
48            match self {
49                AllowedOriginItem::Any => "*".to_string(),
50                AllowedOriginItem::Urls(urls) => urls.join(","),
51            }
52        )
53    }
54}
55
56pub enum AllowedMethodItem {
57    Any,
58    Methods(Vec<http::Method>),
59}
60
61impl fmt::Display for AllowedMethodItem {
62    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
63        write!(
64            f,
65            "{}",
66            match self {
67                AllowedMethodItem::Any => "*".to_string(),
68                AllowedMethodItem::Methods(methods) => {
69                    methods
70                        .into_iter()
71                        .map(|entry| entry.to_string())
72                        .collect::<Vec<String>>()
73                        .join(",")
74                }
75            }
76        )
77    }
78}
79
80pub enum AllowedHeaderItem {
81    Any,
82    Headers(Vec<http::header::HeaderName>),
83}
84
85impl fmt::Display for AllowedHeaderItem {
86    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
87        write!(
88            f,
89            "{}",
90            match self {
91                AllowedHeaderItem::Any => "*".to_string(),
92                AllowedHeaderItem::Headers(headers) => {
93                    headers
94                        .into_iter()
95                        .map(|entry| entry.to_string())
96                        .collect::<Vec<String>>()
97                        .join(",")
98                }
99            }
100        )
101    }
102}
103
104pub fn options_from_env() -> oss::Options<'static> {
105    oss::Options::new()
106        .with_access_key_id(get_env("OSS_ACCESS_KEY_ID", "").leak())
107        .with_access_key_secret(get_env("OSS_ACCESS_KEY_SECRET", "").leak())
108        .with_region(get_env("OSS_REGION", oss::DEFAULT_REGION).leak())
109        .with_endpoint(get_env("OSS_ENDPOINT", "").leak())
110        .with_bucket(get_env("OSS_BUCKET", "").leak())
111        .with_sts_token(get_env("OSS_STS_TOKEN", "").leak())
112        .with_internal(get_env_bool("OSS_INTERNAL", false))
113        .with_cname(get_env_bool("OSS_CNAME", false))
114        // .with_is_request_pay(get_env_bool("OSS_IS_REQUEST_PAY", false))
115        .with_secret(get_env_bool("OSS_SECURE", false))
116        .with_timeout(
117            get_env("OSS_TIMEOUT", "60")
118                .parse::<u64>()
119                .unwrap_or(oss::DEFAULT_TIMEOUT),
120        )
121}
122
123/// 获取文件md5值
124///
125/// [doc](https://help.aliyun.com/zh/oss/developer-reference/include-signatures-in-the-authorization-header#section-i74-k35-5w4)
126pub fn oss_file_md5<'a>(file: &'a str) -> Result<String, io::Error> {
127    let mut file = File::open(file)?;
128    let mut hasher = Md5::new();
129    let mut buffer = [0; 1024];
130    loop {
131        let bytes_read = file.read(&mut buffer)?;
132        if bytes_read == 0 {
133            break;
134        }
135        hasher.input(&buffer[..bytes_read]);
136    }
137    let bytes = hex::decode(&hasher.result_str()).unwrap();
138    Ok(general_purpose::STANDARD.encode(&bytes))
139}
140
141pub fn oss_md5<'a>(content: &'a [u8]) -> Result<String, io::Error> {
142    let mut hasher = Md5::new();
143    hasher.input(content);
144    let bytes = hex::decode(&hasher.result_str()).unwrap();
145    Ok(general_purpose::STANDARD.encode(&bytes))
146}
147
148/// 获取字节范围描述
149///
150/// - `start`  开始位置
151/// - `amount` 获取字节数量,支持负数
152///
153/// # example
154///
155/// ```rust no_run
156/// use xt_oss::util::ByteRange;
157/// assert_eq!(ByteRange::new().to_string(), "bytes=0-");
158/// assert_eq!(ByteRange::new().with_amount(500).to_string(), "bytes=0-499");
159/// assert_eq!(ByteRange::new().with_amount(-500).to_string(), "bytes=-500");
160/// assert_eq!(ByteRange::new().with_start(100).to_string(), "bytes=100-");
161/// assert_eq!(ByteRange::from((100, 500)).to_string(), "bytes=100-599");
162/// assert_eq!(ByteRange::from((100, -500)).to_string(), "bytes=0-99");
163/// assert_eq!(ByteRange::from((100, -50)).to_string(), "bytes=50-99");
164/// ```
165///
166#[derive(Debug, Default, Clone, Copy)]
167pub struct ByteRange {
168    start: Option<u64>,
169    amount: Option<i64>,
170}
171
172impl ByteRange {
173    pub fn new() -> Self {
174        Self::default()
175    }
176
177    pub fn chunk(total: u64, chunk_size: u64) -> Vec<Self> {
178        let mut reuslt: Vec<ByteRange> = vec![];
179        let mut max_count = 0;
180        for i in 0..total / chunk_size {
181            reuslt.push((i * chunk_size, chunk_size as i64).into());
182            max_count = i;
183        }
184
185        let rest = total - ((max_count + 1) * chunk_size);
186        if rest != 0 {
187            let start = total - rest;
188            reuslt.push((start, rest as i64).into());
189        }
190        reuslt
191    }
192
193    pub fn with_start(mut self, value: u64) -> Self {
194        self.start = Some(value);
195        self
196    }
197
198    pub fn with_amount(mut self, value: i64) -> Self {
199        self.amount = Some(value);
200        self
201    }
202
203    pub fn start(&self) -> u64 {
204        self.start.unwrap_or_default()
205    }
206
207    pub fn amount(&self) -> i64 {
208        self.amount.unwrap_or_default()
209    }
210}
211
212impl From<(u64, i64)> for ByteRange {
213    fn from(item: (u64, i64)) -> Self {
214        Self {
215            start: Some(item.0),
216            amount: Some(item.1),
217        }
218    }
219}
220
221impl fmt::Display for ByteRange {
222    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
223        match (self.start, self.amount) {
224            (None, None) => write!(f, "bytes=0-"),
225            (None, Some(amount)) => {
226                if amount >= 0 {
227                    write!(f, "bytes=0-{}", amount - 1)
228                } else {
229                    write!(f, "bytes=-{}", amount.abs())
230                }
231            }
232            (Some(start), None) => write!(f, "bytes={}-", start),
233            (Some(start), Some(amount)) if amount > 0 => {
234                write!(f, "bytes={}-{}", start, start + amount as u64 - 1)
235            }
236            (Some(start), Some(amount)) => {
237                let start_pos = if start as i64 + amount > 0 {
238                    start as i64 + amount
239                } else {
240                    0
241                };
242                write!(f, "bytes={}-{}", start_pos.max(0), start - 1)
243            }
244        }
245    }
246}
247
248#[cfg(test)]
249pub mod tests {
250    use super::ByteRange;
251
252    #[test]
253    fn range_1() {
254        assert_eq!(ByteRange::new().to_string(), "bytes=0-");
255        assert_eq!(ByteRange::new().with_amount(500).to_string(), "bytes=0-499");
256        assert_eq!(ByteRange::new().with_amount(-500).to_string(), "bytes=-500");
257        assert_eq!(ByteRange::new().with_start(100).to_string(), "bytes=100-");
258        // 通过元组生成
259        assert_eq!(ByteRange::from((100, 500)).to_string(), "bytes=100-599");
260        assert_eq!(ByteRange::from((100, -500)).to_string(), "bytes=0-99");
261        assert_eq!(ByteRange::from((100, -50)).to_string(), "bytes=50-99");
262    }
263
264    #[test]
265    fn range_2() {
266        let range_list = ByteRange::chunk(87475, 1024);
267        assert_eq!("bytes=87040-87474", range_list.last().unwrap().to_string())
268    }
269}