1use std::{
2 collections::HashMap,
3 fmt::{Display, Formatter},
4};
5
6use anyhow::Error;
7use derive_builder::Builder;
8use reqwest::Method;
9use serde::{Deserialize, Serialize};
10
11use crate::auth::{HmacSha1Signer, Signer};
12
13#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize, Default)]
15pub enum UfileProtocol {
16 Http,
17 #[default]
18 Https,
19}
20
21impl Display for UfileProtocol {
22 fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
23 match self {
24 UfileProtocol::Http => write!(f, "http"),
25 UfileProtocol::Https => write!(f, "https"),
26 }
27 }
28}
29
30#[derive(Debug, Builder)]
31pub struct ObjectOptAuthParam {
32 pub method: Method,
35 #[builder(setter(into))]
38 pub bucket: String,
39 #[builder(setter(into))]
42 pub key_name: String,
43 #[builder(setter(into, strip_option), default)]
46 pub content_type: Option<String>,
47 #[builder(setter(into, strip_option), default)]
50 pub content_md5: Option<String>,
51 #[builder(setter(into, strip_option), default)]
54 pub date: Option<String>,
55 #[builder(setter(into, strip_option), default)]
62 pub x_ufile_copy_source: Option<String>,
63 #[builder(setter(into, strip_option), default)]
66 pub x_ufile_copy_source_range: Option<String>,
67}
68
69#[derive(Debug, Clone, Builder, Serialize, Deserialize)]
73pub struct ObjectConfig {
74 #[builder(default = "https://api.ucloud.cn".to_string())]
76 #[builder(setter(into))]
77 pub endpoint: String,
78 #[builder(setter(into))]
80 pub private_key: String,
81 #[builder(setter(into))]
83 pub public_key: String,
84 #[serde(rename = "Region")]
86 #[builder(setter(into))]
87 pub region: String,
88 #[serde(rename = "ProxySuffix")]
90 #[builder(setter(into, strip_option), default = Some("ufileos.com".to_string()))]
91 pub proxy_suffix: Option<String>,
92
93 #[serde(rename = "CustomHost")]
95 #[builder(setter(into, strip_option), default)]
96 pub custom_host: Option<String>,
97
98 #[serde(skip)]
100 #[builder(setter(into, strip_option), default)]
101 pub protocol: UfileProtocol,
102}
103
104impl Default for ObjectConfig {
105 fn default() -> Self {
106 Self {
107 endpoint: "https://api.ucloud.cn".to_string(),
108 private_key: "".to_string(),
109 public_key: "".to_string(),
110 region: "cn-sh2".to_string(),
111 proxy_suffix: None,
112 custom_host: None,
113 protocol: UfileProtocol::Https,
114 }
115 }
116}
117
118impl ObjectConfig {
119 pub fn generate_final_host(&self, bucket_name: &str, key_name: &str) -> String {
121 let key_name = urlencoding::encode(key_name);
122 if let Some(ref custom_hosts) = self.custom_host {
123 format!("{}/{}", custom_hosts, key_name)
124 } else {
125 let bucket_name = urlencoding::encode(bucket_name);
126 let region = urlencoding::encode(&self.region);
127 let proxy_suffix = if let Some(ref suffix) = self.proxy_suffix {
128 suffix
129 } else {
130 ""
131 };
132 let proxy_suffix = urlencoding::encode(proxy_suffix);
133 format!(
134 "{}://{}.{}.{}/{}",
135 self.protocol,
136 bucket_name.as_ref(),
137 region.as_ref(),
138 proxy_suffix.as_ref(),
139 key_name.as_ref()
140 )
141 }
142 }
143
144 pub fn authorization_private_url(
153 &self,
154 method: Method,
155 bucket_name: &str,
156 key_name: &str,
157 expires: &str,
158 ) -> Result<String, Error> {
159 if bucket_name.is_empty() {
160 return Err(Error::msg("bucket must not be empty."));
161 }
162
163 if key_name.is_empty() {
164 return Err(Error::msg("key_name must not be empty."));
165 }
166
167 if expires.parse::<u64>()? == 0 {
168 return Err(Error::msg("expires must not be zero."));
169 }
170 let sign_data = format!(
171 "{}\n{}\n{}\n{}\n/{}/{}",
172 method.as_str(),
173 "",
174 "",
175 expires,
176 bucket_name,
177 key_name
178 );
179 tracing::debug!("sign_data: \n{}", sign_data);
180 HmacSha1Signer.signature(&self.private_key, &sign_data)
182 }
183}
184
185#[derive(Debug, Serialize, Deserialize)]
186pub struct BaseResponse {
187 #[serde(skip)]
188 pub headers: HashMap<String, String>,
189 #[serde(rename = "RetCode")]
190 pub ret_code: i32,
191 #[serde(rename = "Message", alias = "ErrMsg")]
192 pub message: Option<String>,
193}
194
195#[derive(Debug, Serialize, Deserialize)]
196pub struct PutObjectResultResponse {
197 #[serde(flatten)]
198 pub resp: BaseResponse,
199 #[serde(rename = "ETag")]
200 pub etag: String,
201}
202
203impl From<BaseResponse> for PutObjectResultResponse {
204 fn from(resp: BaseResponse) -> Self {
205 Self {
206 resp,
207 etag: String::new(),
208 }
209 }
210}
211
212#[derive(Debug, Clone, Serialize, Deserialize)]
214#[serde(rename_all = "PascalCase")]
215pub struct InitMultipartState {
216 pub upload_id: String,
218 pub blk_size: u64,
220 pub bucket: String,
222 #[serde(rename = "Key", alias = "Key")]
224 pub key_name: String,
225 pub mime_type: Option<String>,
227}
228
229#[derive(Debug, Clone, Serialize, Deserialize)]
230#[serde(rename_all = "PascalCase")]
231pub struct MultipartUploadState {
232 #[serde(skip_deserializing)]
233 pub headers: HashMap<String, String>,
234 pub part_number: usize,
235 #[serde(skip_deserializing)]
236 pub etag: String,
237}
238
239#[derive(Debug, Serialize, Deserialize)]
241#[serde(rename_all = "PascalCase")]
242pub struct FinishUploadResponse {
243 #[serde(skip_deserializing)]
244 pub headers: HashMap<String, String>,
245 pub bucket: String,
246 pub key: String,
247 pub file_size: isize,
248 #[serde(skip_deserializing)]
249 pub etag: String,
250}
251
252#[derive(Debug, Serialize, Deserialize)]
254pub struct HeadFileResponse {
255 #[serde(skip_deserializing)]
256 pub headers: Option<HashMap<String, String>>,
257 pub etag: Option<String>,
259 pub content_type: String,
261 pub content_length: u64,
263 pub last_modified: Option<String>,
265}