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 resource_path = format!("/{}/{}/{}", config.secret_id, config.bucket, clean_path);
70        let string_to_sign = format!(
71            "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
72            permissions, start, expiry, resource_path, "", "", "", "", "", "", "", version, "", ""
73        );
74
75        let signature = Self::hmac_sha256(&account_key, string_to_sign.as_bytes());
76        let signature_base64 = STANDARD.encode(signature);
77
78        url.query_pairs_mut()
79            .append_pair("sv", version)
80            .append_pair("sr", resource)
81            .append_pair("sp", permissions)
82            .append_pair("se", &expiry)
83            .append_pair("sig", &signature_base64);
84
85        Ok(url)
86    }
87
88    fn get_presigned_put_url(&self, key: &str, config: &StorageConfig) -> StorageResult<Url> {
89        let clean_key = key.trim_start_matches('/');
90        let now = Utc::now();
91        let _expiration = (now + Duration::seconds(900)).timestamp();
92        let x_ms_version = "2023-11-03";
93
94        let host_str = config
95            .endpoint
96            .clone()
97            .unwrap_or_else(|| format!("https://{}.blob.core.windows.net/{}", config.secret_id, config.bucket));
98
99        let host_url =
100            if host_str.starts_with("http") { Url::parse(&host_str) } else { Url::parse(&format!("https://{}", host_str)) }
101                .map_err(|e| WaeError::invalid_params("host", e.to_string()))?;
102
103        let mut url = host_url.join(clean_key).map_err(|e| WaeError::invalid_params("key", e.to_string()))?;
104
105        let permissions = "w";
106        let resource = "b";
107        let version = x_ms_version;
108        let expiry = (Utc::now() + Duration::seconds(900)).format("%Y-%m-%dT%H:%M:%SZ").to_string();
109        let resource_path = format!("/{}/{}/{}", config.secret_id, config.bucket, clean_key);
110
111        let account_key = STANDARD
112            .decode(&config.secret_key)
113            .map_err(|e| WaeError::invalid_params("secret_key", format!("Invalid base64: {}", e)))?;
114
115        let string_to_sign = format!(
116            "{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n{}\n",
117            permissions, "", expiry, resource_path, "", "", "", "", "", "", "", version, "", ""
118        );
119
120        let signature = Self::hmac_sha256(&account_key, string_to_sign.as_bytes());
121        let signature_base64 = STANDARD.encode(signature);
122
123        url.query_pairs_mut()
124            .append_pair("sv", version)
125            .append_pair("sr", resource)
126            .append_pair("sp", permissions)
127            .append_pair("se", &expiry)
128            .append_pair("sig", &signature_base64);
129
130        Ok(url)
131    }
132}