Skip to main content

wae_storage/providers/
azure_blob.rs

1//! Azure Blob Storage 存储提供商实现
2
3use crate::{StorageConfig, StorageProvider, StorageResult};
4
5use base64::{Engine as _, engine::general_purpose::STANDARD};
6use chrono::{Duration, Utc};
7use hmac::{Hmac, Mac};
8use sha2::Sha256;
9use url::Url;
10use wae_types::WaeError;
11
12/// Azure Blob Storage 存储提供商
13///
14/// 实现 Azure Blob Storage 对象存储服务的签名和预签名 URL 生成
15pub struct AzureBlobProvider;
16
17impl AzureBlobProvider {
18    /// 使用 HMAC-SHA256 算法计算消息认证码
19    fn hmac_sha256(key: &[u8], data: &[u8]) -> Vec<u8> {
20        let mut mac = Hmac::<Sha256>::new_from_slice(key).expect("HMAC can take key of any size");
21        mac.update(data);
22        mac.finalize().into_bytes().to_vec()
23    }
24}
25
26impl StorageProvider for AzureBlobProvider {
27    fn sign_url(&self, path: &str, config: &StorageConfig) -> StorageResult<Url> {
28        if path.is_empty() {
29            return Err(WaeError::invalid_params("path", "Empty path"));
30        }
31
32        if path.starts_with("http://") || path.starts_with("https://") {
33            return Url::parse(path).map_err(|e| WaeError::invalid_params("path", e.to_string()));
34        }
35
36        let clean_path = path.trim_start_matches('/');
37        let now = Utc::now();
38        let _expiration = (now + Duration::seconds(3600)).timestamp();
39        let x_ms_version = "2023-11-03";
40        let _x_ms_date = now.format("%a, %d %b %Y %H:%M:%S GMT").to_string();
41
42        let host_str = config.cdn_url.clone().unwrap_or_else(|| {
43            if let Some(endpoint) = &config.endpoint {
44                endpoint.clone()
45            }
46            else {
47                format!("https://{}.blob.core.windows.net/{}", config.secret_id, config.bucket)
48            }
49        });
50
51        let host_url =
52            if host_str.starts_with("http") { Url::parse(&host_str) } else { Url::parse(&format!("https://{}", host_str)) }
53                .map_err(|e| WaeError::invalid_params("host", e.to_string()))?;
54
55        let mut url = host_url.join(clean_path).map_err(|e| WaeError::invalid_params("path", e.to_string()))?;
56
57        let _method = "r";
58        let start = "";
59        let now = Utc::now();
60        let expiry = (now + Duration::seconds(3600)).format("%Y-%m-%dT%H:%M:%SZ").to_string();
61        let resource = "b";
62        let permissions = "r";
63        let version = x_ms_version;
64
65        let account_key = STANDARD
66            .decode(&config.secret_key)
67            .map_err(|e| WaeError::invalid_params("secret_key", format!("Invalid base64: {}", e)))?;
68
69        let string_to_sign = format!(
70            "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
71            permissions,
72            start,
73            expiry,
74            format!("/{}/{}/{}", config.secret_id, config.bucket, clean_path),
75            "",
76            "",
77            "",
78            "",
79            "",
80            "",
81            "",
82            version,
83            "",
84            ""
85        );
86
87        let signature = Self::hmac_sha256(&account_key, string_to_sign.as_bytes());
88        let signature_base64 = STANDARD.encode(signature);
89
90        url.query_pairs_mut()
91            .append_pair("sv", version)
92            .append_pair("sr", resource)
93            .append_pair("sp", permissions)
94            .append_pair("se", &expiry)
95            .append_pair("sig", &signature_base64);
96
97        Ok(url)
98    }
99
100    fn get_presigned_put_url(&self, key: &str, config: &StorageConfig) -> StorageResult<Url> {
101        let clean_key = key.trim_start_matches('/');
102        let now = Utc::now();
103        let _expiration = (now + Duration::seconds(900)).timestamp();
104        let x_ms_version = "2023-11-03";
105
106        let host_str = config
107            .endpoint
108            .clone()
109            .unwrap_or_else(|| format!("https://{}.blob.core.windows.net/{}", config.secret_id, config.bucket));
110
111        let host_url =
112            if host_str.starts_with("http") { Url::parse(&host_str) } else { Url::parse(&format!("https://{}", host_str)) }
113                .map_err(|e| WaeError::invalid_params("host", e.to_string()))?;
114
115        let mut url = host_url.join(clean_key).map_err(|e| WaeError::invalid_params("key", e.to_string()))?;
116
117        let permissions = "w";
118        let resource = "b";
119        let version = x_ms_version;
120
121        let account_key = STANDARD
122            .decode(&config.secret_key)
123            .map_err(|e| WaeError::invalid_params("secret_key", format!("Invalid base64: {}", e)))?;
124
125        let string_to_sign = format!(
126            "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
127            permissions,
128            "",
129            &format!("{}", (Utc::now() + Duration::seconds(900)).format("%Y-%m-%dT%H:%M:%SZ")),
130            format!("/{}/{}/{}", config.secret_id, config.bucket, clean_key),
131            "",
132            "",
133            "",
134            "",
135            "",
136            "",
137            "",
138            version,
139            "",
140            ""
141        );
142
143        let signature = Self::hmac_sha256(&account_key, string_to_sign.as_bytes());
144        let signature_base64 = STANDARD.encode(signature);
145
146        url.query_pairs_mut()
147            .append_pair("sv", version)
148            .append_pair("sr", resource)
149            .append_pair("sp", permissions)
150            .append_pair("se", &format!("{}", (Utc::now() + Duration::seconds(900)).format("%Y-%m-%dT%H:%M:%SZ")))
151            .append_pair("sig", &signature_base64);
152
153        Ok(url)
154    }
155}