rust_aliyun/oss/
callback.rs

1use crate::error::Error::CommonError;
2use crate::Result;
3use base64ct::Encoding;
4use md5::Md5;
5use rsa::pkcs1::DecodeRsaPublicKey;
6use rsa::pkcs1v15::{Signature, VerifyingKey};
7use rsa::pkcs8::DecodePublicKey;
8use rsa::signature::Verifier;
9use rsa::{pss, Pkcs1v15Sign};
10use serde::{Deserialize, Serialize, Serializer};
11use serde_json::Value;
12use sha2::Sha256;
13use std::collections::HashMap;
14use crate::util::date::acs_short_date;
15
16#[derive(Debug, Serialize, Deserialize)]
17pub struct CallbackRequest {
18    #[serde(rename = "callbackUrl")]
19    pub url: String,
20    #[serde(rename = "callbackHost", skip_serializing_if = "Option::is_none")]
21    pub host: Option<String>,
22    #[serde(rename = "callbackBody")]
23    pub body: HashMap<String, BodyField>,
24    #[serde(rename = "callbackSNI")]
25    pub sni: bool,
26    #[serde(rename = "callbackBodyType")]
27    pub body_type: BodyType,
28}
29
30#[derive(Debug, Serialize, Deserialize)]
31pub struct Callback {
32    pub callback: String,
33    pub callback_var: String,
34    pub date: String,
35    pub content_type: String,
36}
37
38impl CallbackRequest {
39    pub fn new(url: &str, body: HashMap<String, BodyField>, body_type: BodyType) -> Self {
40        CallbackRequest {
41            url: url.to_string(),
42            body,
43            body_type,
44            sni: false,
45            host: None,
46        }
47    }
48}
49
50pub fn build_callback(
51    url: &str,
52    fields: Option<HashMap<String, BodyField>>,
53    custom_fields: Option<HashMap<String, Value>>,
54    body_type: BodyType,
55) -> Result<Callback> {
56    let mut body = if let Some(fields) = fields {
57        fields
58    } else {
59        HashMap::new()
60    };
61
62    let mut vars = HashMap::new();
63
64    if let Some(custom_fields) = custom_fields {
65        custom_fields.into_iter().for_each(|(k, v)| {
66            body.insert(k.clone(), BodyField::Var(k.clone()));
67            vars.insert(BodyField::Var(k), v);
68        });
69    }
70
71    let request = CallbackRequest::new(url, body, body_type);
72    let callback = serde_json::to_string(&request)?;
73    log::debug!("callback: {}", callback);
74    let callback = base64ct::Base64::encode_string(callback.as_bytes());
75
76    let callback_var = serde_json::to_string(&vars)?;
77    let callback_var = base64ct::Base64::encode_string(callback_var.as_bytes());
78    log::debug!("callback: {}", callback_var);
79    Ok(Callback {
80        callback,
81        callback_var,
82        date: acs_short_date(),
83        content_type: "application/octet-stream; charset=utf-8".to_string(),
84    })
85}
86
87const ALIYUN_PUBLIC_PEM: &str = r#"-----BEGIN PUBLIC KEY-----
88MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKs/JBGzwUB2aVht4crBx3oIPBLNsjGs
89C0fTXv+nvlmklvkcolvpvXLTjaxUHR3W9LXxQ2EHXAJfCB+6H2YF1k8CAwEAAQ==
90-----END PUBLIC KEY-----"#;
91
92pub fn verify(
93    authorization: &str,
94    path: &str,
95    queries: Option<String>,
96    body: Option<String>,
97) -> Result<()> {
98    let signed_str = format!(
99        "{}{}\n{}",
100        urlencoding::decode(path)?,
101        queries.unwrap_or_default(),
102        body.unwrap_or_default()
103    );
104    let sig = base64ct::Base64::decode_vec(authorization)?;
105    let key = rsa::RsaPublicKey::from_public_key_pem(ALIYUN_PUBLIC_PEM)
106        .map_err(|err| CommonError(err.to_string()))?;
107    let verifying_key: VerifyingKey<Md5> = VerifyingKey::<Md5>::new_unprefixed(key);
108    let signature = Signature::try_from(&sig[..]).map_err(|err| CommonError(err.to_string()))?;
109    Ok(verifying_key
110        .verify(signed_str.as_bytes(), &signature)
111        .map_err(|err| CommonError(err.to_string()))?)
112}
113
114#[derive(Debug, Serialize, Deserialize, Default)]
115pub enum BodyType {
116    #[default]
117    #[serde(rename = "application/x-www-form-urlencoded")]
118    Form,
119    #[serde(rename = "application/json")]
120    Body,
121}
122#[derive(Debug, Serialize, Deserialize, Eq, PartialEq, Hash)]
123pub enum BodyField {
124    #[serde(rename = "${bucket}")]
125    Bucket,
126    #[serde(rename = "${object}")]
127    Object,
128    #[serde(rename = "${etag}")]
129    Etag,
130    #[serde(rename = "${size}")]
131    Size,
132    #[serde(rename = "${mimeType}")]
133    MimeType,
134    #[serde(rename = "${imageInfo.height}")]
135    ImageHeight,
136    #[serde(rename = "${imageInfo.width}")]
137    ImageWidth,
138    #[serde(rename = "${imageInfo.format}")]
139    ImageFormat,
140    #[serde(rename = "${crc64}")]
141    Crc64,
142    #[serde(rename = "${contentMd5}")]
143    Md5,
144    #[serde(rename = "${vpcId}")]
145    VpcId,
146    #[serde(rename = "${clientIp}")]
147    ClientIp,
148    #[serde(rename = "${reqId}")]
149    ReqId,
150    #[serde(rename = "${operation}")]
151    Operation,
152    #[serde(untagged, with = "body_value_ser")]
153    Var(String),
154}
155
156mod body_value_ser {
157    use serde::{de, Deserialize, Deserializer, Serializer};
158
159    pub fn serialize<S>(var: &String, serializer: S) -> Result<S::Ok, S::Error>
160    where
161        S: Serializer,
162    {
163        let s = format!("${{x:{}}}", var);
164        serializer.serialize_str(&s)
165    }
166    
167    pub fn deserialize<'de, D>(deserializer: D) -> Result<String, D::Error>
168    where
169        D: Deserializer<'de>,
170    {
171        let s = String::deserialize(deserializer)?;
172        if s.starts_with("${x:") && s.ends_with('}') {
173            Ok(s[4..s.len() - 1].to_string())
174        } else {
175            Err(de::Error::custom(format!(
176                "Invalid format for Var, expected '${{x:...}}', got '{}'",
177                s
178            )))
179        }
180    }
181}
182
183mod test {
184    use crate::oss::callback::BodyField;
185    use serde::Serialize;
186    use serde_json::json;
187    use std::collections::HashMap;
188
189    #[derive(Serialize)]
190    struct V {
191        v: BodyField,
192    }
193
194    #[test]
195    fn test_callback() {
196        let ret = serde_json::to_string(&BodyField::Var("vpcId".to_string()));
197        assert_eq!(ret.unwrap(), "\"${x:vpcId}\"");
198
199        let ret = serde_json::to_string(&BodyField::VpcId);
200        assert_eq!(ret.unwrap(), "\"${vpcId}\"")
201    }
202
203    #[test]
204    fn test_build_callback() {
205        let ret = super::build_callback(
206            "http://127.0.0.1:8080/callback",
207            Some(HashMap::from([
208                ("bucket".to_string(), BodyField::Bucket),
209                ("object".to_string(), BodyField::Object),
210                ("etag".to_string(), BodyField::Etag),
211                ("size".to_string(), BodyField::Size),
212                ("mimeType".to_string(), BodyField::MimeType),
213                ("imageInfo.height".to_string(), BodyField::ImageHeight),
214            ])),
215            Some(HashMap::from([
216                ("abc".to_string(), json!("vpcId".to_string())),
217                ("ffd".to_string(), json!("clientIp")),
218                ("efc".to_string(), json!("reqId")),
219                ("asf".to_string(), json!("operation")),
220            ])),
221            super::BodyType::Form,
222        )
223        .unwrap();
224
225        println!("{:?}", ret)
226    }
227}