1use std::collections::HashMap;
4
5use crate::{
6 presign_common::{build_presign_get_request, PresignGetOptions},
7 request::OssRequest,
8 util::{self, get_iso8601_date_time_string},
9 Client,
10};
11
12#[derive(Debug, Clone)]
14#[cfg_attr(feature = "serde-support", derive(serde::Serialize, serde::Deserialize))]
15#[cfg_attr(feature = "serde-camelcase", serde(rename_all = "camelCase"))]
16pub struct SignedOssRequest {
17 pub url: String,
19
20 pub headers: HashMap<String, String>,
22}
23
24impl Client {
25 pub fn presign_url<S1, S2>(&self, bucket_name: S1, object_key: S2, options: PresignGetOptions) -> String
27 where
28 S1: AsRef<str>,
29 S2: AsRef<str>,
30 {
31 let mut request = build_presign_get_request(bucket_name.as_ref(), object_key.as_ref(), &options);
32
33 let date_time_string = request.query.get("x-oss-date").unwrap().clone();
34 let date_string = &date_time_string[..8];
35
36 let credential = format!("{}/{}/{}/oss/aliyun_v4_request", self.access_key_id, date_string, self.region);
37
38 request = request.add_query("x-oss-credential", &credential);
39
40 if let Some(s) = &self.sts_token {
41 request = request.add_query("x-oss-security-token", s);
42 }
43
44 let canonical_request = request.build_canonical_request();
45
46 let canonical_request_hash = util::sha256(canonical_request.as_bytes());
47
48 let string_to_sign = format!(
49 "OSS4-HMAC-SHA256\n{}\n{}/{}/oss/aliyun_v4_request\n{}",
50 date_time_string,
51 date_string,
52 self.region,
53 hex::encode(&canonical_request_hash)
54 );
55
56 let sig = self.calculate_signature(&string_to_sign, date_string);
57
58 request = request.add_query("x-oss-signature", &sig);
59
60 let uri = request.build_request_uri();
61 let query_string = request.build_canonical_query_string();
62
63 let domain_name = if request.bucket_name.is_empty() {
64 format!("{}://{}{}", self.scheme, self.endpoint, uri)
65 } else {
66 format!("{}://{}.{}{}", self.scheme, request.bucket_name, self.endpoint, uri)
67 };
68
69 if query_string.is_empty() {
70 domain_name
71 } else {
72 format!("{}?{}", domain_name, query_string)
73 }
74 }
75
76 pub fn presign_raw_request(&self, mut oss_request: OssRequest) -> SignedOssRequest {
130 let date_header = "x-oss-date".to_string();
131 {
132 oss_request.headers_mut().entry(date_header.clone()).or_insert(get_iso8601_date_time_string());
133 }
134
135 let date_time_string = oss_request.headers.get(&date_header).unwrap().to_string();
136 let date_string = &date_time_string[..8];
137
138 if let Some(s) = &self.sts_token {
139 if !oss_request.headers.contains_key("x-oss-security-token") {
140 oss_request = oss_request.add_header("x-oss-security-token", s);
141 }
142 }
143 let additional_headers = oss_request.build_additional_headers();
144 let string_to_sign = oss_request.build_string_to_sign(&self.region);
145
146 log::debug!("string to sign: \n--------\n{}\n--------", string_to_sign);
147
148 let sig = self.calculate_signature(&string_to_sign, date_string);
149
150 log::debug!("signature: {}", sig);
151
152 let auth_string = format!(
153 "OSS4-HMAC-SHA256 Credential={}/{}/{}/oss/aliyun_v4_request,{}Signature={}",
154 self.access_key_id,
155 date_string,
156 self.region,
157 if additional_headers.is_empty() {
158 "".to_string()
159 } else {
160 format!("{},", additional_headers)
161 },
162 sig
163 );
164
165 oss_request = oss_request.add_header("authorization", &auth_string);
166
167 let uri = oss_request.build_request_uri();
168 let query_string = oss_request.build_canonical_query_string();
169
170 let url = if oss_request.bucket_name.is_empty() {
171 format!("{}://{}{}", self.scheme, self.endpoint, uri)
172 } else {
173 format!("{}://{}.{}{}", self.scheme, oss_request.bucket_name, self.endpoint, uri)
174 };
175
176 let url = if query_string.is_empty() { url } else { format!("{}?{}", url, query_string) };
177
178 SignedOssRequest {
179 url,
180 headers: oss_request.headers,
181 }
182 }
183}
184
185#[cfg(all(test, feature = "blocking"))]
186mod test_presign {
187 use std::{str::FromStr, sync::Once};
188
189 use reqwest::header::{HeaderMap, HeaderName, HeaderValue};
190 use uuid::Uuid;
191
192 use crate::{
193 presign::SignedOssRequest,
194 presign_common::PresignGetOptionsBuilder,
195 request::{OssRequest, RequestMethod},
196 util::debug_blocking_request,
197 Client,
198 };
199
200 static INIT: Once = Once::new();
201
202 fn setup() {
203 INIT.call_once(|| {
204 simple_logger::init_with_level(log::Level::Debug).unwrap();
205 dotenvy::dotenv().unwrap();
206 });
207 }
208
209 #[test]
210 fn test_presign_get_with_options() {
211 setup();
212 let client = Client::from_env();
213
214 let bucket = "yuanyq";
215 let object = "rust-sdk-test/test-1.webp";
216
217 let options = PresignGetOptionsBuilder::new(3600).process("style/test-img-process").build();
218
219 let url = client.presign_url(bucket, object, options);
220
221 log::debug!("{}", url);
222
223 let response = reqwest::blocking::get(url);
224 assert!(response.is_ok());
225 assert_eq!(reqwest::StatusCode::OK, response.unwrap().status());
226 }
227
228 #[test]
229 fn test_presign_raw_request() {
230 setup();
231 let client = Client::from_env();
232
233 let request = OssRequest::new()
234 .method(RequestMethod::Get)
235 .bucket("yuanyq")
236 .object("rust-sdk-test/20dcd5da-804f-406d-a921-3c3f08de04e3.jpg")
237 .add_header("x-oss-expires", "3600")
238 .add_query("x-oss-process", "style/test-img-process");
239
240 let SignedOssRequest { url, headers } = client.presign_raw_request(request);
241 log::debug!("{} {:#?}", url, headers);
242
243 let mut req_headers = HeaderMap::new();
244 headers.into_iter().for_each(|(k, v)| {
245 req_headers.append(HeaderName::from_str(k.as_str()).unwrap(), HeaderValue::from_str(v.as_str()).unwrap());
246 });
247
248 let http_client = reqwest::blocking::Client::new();
249 let req = http_client
250 .request(reqwest::Method::GET, reqwest::Url::parse(url.as_str()).unwrap())
251 .headers(req_headers)
252 .build()
253 .unwrap();
254
255 debug_blocking_request(&req);
256
257 let response = http_client.execute(req);
258 assert!(response.is_ok());
259 let response = response.unwrap();
260 assert_eq!(reqwest::StatusCode::OK, response.status());
261 }
262
263 #[test]
264 fn test_presign_raw_request_2() {
265 setup();
266 let client = Client::from_env();
267
268 let object = format!("rust-sdk-test/{}.webp", Uuid::new_v4());
269
270 let request = OssRequest::new()
271 .method(RequestMethod::Put)
272 .bucket("yuanyq")
273 .object(&object)
274 .add_header("content-type", "image/webp")
275 .add_header("content-length", "36958");
276
277 let SignedOssRequest { url, headers } = client.presign_raw_request(request);
278 log::debug!("{} {:#?}", url, headers);
279 }
280}