ali_oss/types/
oss_config.rs1use crate::SignatureAble;
2use base64::prelude::*;
3use percent_encoding::percent_decode_str;
4use reqwest::{header, Method, Url};
5use std::borrow::Cow;
6
7#[derive(Debug, Clone)]
8pub struct OssConfig {
9 pub access_key_id: String,
10 pub access_key_secret: String,
11 pub bucket_name: String,
12 pub bucket_location: crate::types::BucketLocation,
13 pub path: String,
14 pub is_internal: bool,
15}
16
17impl OssConfig {
18 pub fn from_env() -> anyhow::Result<Self> {
19 let access_key_id = std::env::var("ALI_OSS_ACCESS_KEY_ID")?;
20 let access_key_secret = std::env::var("ALI_OSS_ACCESS_KEY_SECRET")?;
21 let bucket_name = std::env::var("ALI_OSS_BUCKET")?;
22 let bucket_location = std::env::var("ALI_OSS_LOCATION")?;
23 let path = std::env::var("ALI_OSS_PATH").unwrap_or("".to_owned());
24 let internal = std::env::var("ALI_OSS_INTERNAL")?;
25
26 let client = Self::new(access_key_id, access_key_secret, bucket_name, bucket_location, path, internal == "true");
28 Ok(client)
29 }
30 pub fn new(access_key_id: String, access_key_secret: String, bucket_name: String, bucket_location: String, path: String, is_internal: bool) -> Self {
31 let path = if path.starts_with("/") { path[1..].to_string() } else { path };
32 let path = if path.ends_with("/") { path[..path.len() - 1].to_string() } else { path };
33 Self {
34 access_key_id: access_key_id.to_string(),
35 access_key_secret: access_key_secret.to_string(),
36 bucket_name: bucket_name.to_string(),
37 bucket_location: crate::types::BucketLocation::new(bucket_location),
38 path,
39 is_internal,
40 }
41 }
42 pub fn generate_endpoint_url(bucket_location: &str, is_internal: bool) -> anyhow::Result<Url> {
51 let src = format!("https://{}{}.aliyuncs.com", bucket_location, if is_internal { "-internal" } else { "" });
52 Ok(Url::parse(&src)?)
53 }
54 pub fn generate_bucket_url(backet_name: &str, bucket_location: &str, is_internal: bool) -> anyhow::Result<Url> {
63 let src = format!("https://{}.{}{}.aliyuncs.com", backet_name, bucket_location, if is_internal { "-internal" } else { "" });
64 Ok(Url::parse(&src)?)
65 }
66}
67
68impl OssConfig {
69 pub fn get_object_name<'a>(&self, object_name: &'a str) -> Cow<'a, str> {
70 if self.path.is_empty() {
71 if object_name.starts_with("/") {
72 object_name[1..].into()
73 } else {
74 object_name.into()
75 }
76 } else {
77 format!("{}/{}", self.path, if object_name.starts_with("/") { &object_name[1..] } else { object_name }).into()
78 }
79 }
80 pub fn get_encoded_object_name<'a>(&self, object_name: &'a str) -> Cow<'a, str> {
81 let object_name = self.get_object_name(object_name);
82 let url = Url::parse(format!("https://localhost/{}", object_name).as_str()).unwrap();
83 url.path().trim_start_matches("/").to_owned().into()
84 }
85 pub fn get_decoded_object_name<'a>(&self, object_name: &'a str) -> Cow<'a, str> {
86 let object_name = self.get_object_name(object_name);
87 decode_if_encoded(object_name.as_ref()).into()
88 }
89
90 pub fn get_endpoint_url(&self) -> anyhow::Result<Url> {
91 Self::generate_endpoint_url(&self.bucket_location.as_str(), self.is_internal)
92 }
93
94 pub fn get_endpoint_request(&self, method: Method) -> anyhow::Result<reqwest::Request> {
95 let url = self.get_endpoint_url()?;
96 let request = reqwest::Request::new(method, url);
97 Ok(request)
99 }
100
101 pub fn get_bucket_url(&self) -> anyhow::Result<Url> {
102 Self::generate_bucket_url(&self.bucket_name, &self.bucket_location.as_str(), self.is_internal)
103 }
104
105 pub fn get_bucket_request(&self, method: Method, body: Option<bytes::Bytes>) -> anyhow::Result<reqwest::Request> {
106 let url = self.get_bucket_url()?;
107 let mut request = reqwest::Request::new(method, url);
108 if let Some(body) = body {
109 match infer::get(&body) {
110 Some(mime) => {
111 request.headers_mut().insert(header::CONTENT_TYPE, mime.mime_type().try_into()?);
112 }
113 None => {
114 request.headers_mut().insert(header::CONTENT_TYPE, "text/plain".try_into()?);
115 }
116 }
117 request.headers_mut().insert("Content-MD5", {
119 let md5_hash = md5::compute(&body);
120 base64::engine::general_purpose::STANDARD.encode(md5_hash.as_slice()).try_into()?
121 });
122 request.headers_mut().insert(header::CONTENT_LENGTH, body.len().try_into()?);
123 *request.body_mut() = Some(reqwest::Body::from(body));
124 }
125 Ok(request)
127 }
128
129 pub(crate) fn sign_header_request(&self, request: &mut reqwest::Request) -> anyhow::Result<()> {
130 let content_md5 = {
131 let content_md5 = request.headers().get("Content-MD5");
132 if let Some(content_md5) = content_md5 {
133 Some(content_md5.to_str()?.to_owned())
134 } else {
135 None
136 }
137 };
138 let content_type = {
139 let content_type = request.headers().get("Content-Type");
140 if let Some(content_type) = content_type {
141 Some(content_type.to_str()?.to_owned())
142 } else {
143 None
144 }
145 };
146 let canonicalized_oss_headers: crate::types::CanonicalizedHeaders = (&*request).into();
147 let canonicalized_resource = {
148 let host = request.url().host_str().ok_or(anyhow::anyhow!("host not found"))?;
149 if host.starts_with(self.bucket_location.as_str()) {
150 crate::types::CanonicalizedResource::default()
151 } else {
152 let path = decode_if_encoded(request.url().path()); let query = request.url().query();
154 let mut resource = format!("/{}{}", self.bucket_name, path);
155 if let Some(query) = query {
156 if let Some(first_query) = query.split('&').next() {
157 if !first_query.contains("=") {
158 resource.push_str(&format!("?{}", query));
159 }
160 }
161 }
162 crate::types::CanonicalizedResource::new(resource)
163 }
164 };
165 let header_signature = crate::types::HeaderSignature::new(request.method().clone(), content_md5, content_type, chrono::Utc::now(), canonicalized_oss_headers, canonicalized_resource);
166 let signatured_string = header_signature.get_signature_string(self);
167 let authorization = format!("OSS {}:{}", self.access_key_id, signatured_string);
168 request.headers_mut().insert("Authorization", authorization.try_into()?);
169 request.headers_mut().insert("Date", header_signature.get_date_string().try_into()?);
170 Ok(())
171 }
172 pub(crate) fn get_request_builder(&self, request: reqwest::Request) -> anyhow::Result<reqwest::RequestBuilder> {
173 let req_client = reqwest::Client::new();
174 Ok(reqwest::RequestBuilder::from_parts(req_client, request))
175 }
176}
177
178fn decode_if_encoded(input: &str) -> String {
179 let decoded = percent_decode_str(input).decode_utf8();
181 match decoded {
182 Ok(decoded_str) => decoded_str.to_string(), Err(_) => input.to_string(), }
185}