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#[inline]
27pub fn utc_to_gmt(datetime: DateTime<Utc>) -> String {
28 datetime.format(super::oss::GMT_DATE_FMT).to_string()
29}
30
31#[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_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
123pub 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#[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 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}