1use std::sync::Arc;
4
5use serde::{Deserialize, Serialize};
6
7use crate::client::{BucketOperations, OSSClientInner};
8use crate::error::{ErrorContext, OssError, OssErrorKind, Result};
9use crate::http::client::HttpRequest;
10use crate::types::bucket::BucketName;
11
12#[derive(Debug, Clone, Serialize)]
13#[serde(rename = "RefererConfiguration")]
14struct RefererConfiguration {
15 #[serde(rename = "AllowEmptyReferer")]
16 allow_empty_referer: bool,
17 #[serde(rename = "RefererList", skip_serializing_if = "Option::is_none")]
18 referer_list: Option<RefererList>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize)]
22struct RefererList {
23 #[serde(rename = "Referer")]
24 referer: Vec<String>,
25}
26
27#[derive(Debug, Clone, Deserialize)]
28#[serde(rename = "RefererConfiguration")]
29struct RefererConfigurationResponse {
30 #[serde(rename = "AllowEmptyReferer")]
31 allow_empty_referer: bool,
32 #[serde(rename = "RefererList", default)]
33 referer_list: Option<RefererList>,
34}
35
36pub struct PutBucketRefererBuilder {
37 client: Arc<OSSClientInner>,
38 bucket: BucketName,
39 allow_empty_referer: bool,
40 referers: Vec<String>,
41}
42
43impl PutBucketRefererBuilder {
44 pub(crate) fn new(client: Arc<OSSClientInner>, bucket: BucketName) -> Self {
45 Self {
46 client,
47 bucket,
48 allow_empty_referer: true,
49 referers: Vec::new(),
50 }
51 }
52
53 pub fn allow_empty_referer(mut self, allow: bool) -> Self {
54 self.allow_empty_referer = allow;
55 self
56 }
57
58 pub fn add_referer(mut self, referer: impl Into<String>) -> Self {
59 self.referers.push(referer.into());
60 self
61 }
62
63 pub async fn send(self) -> Result<PutBucketRefererOutput> {
64 let endpoint = self.client.endpoint.clone();
65 let uri = format!("https://{}.{}?referer", self.bucket.as_str(), endpoint);
66 let query_params: Vec<(String, String)> = vec![("referer".into(), String::new())];
67
68 let config = RefererConfiguration {
69 allow_empty_referer: self.allow_empty_referer,
70 referer_list: if self.referers.is_empty() {
71 None
72 } else {
73 Some(RefererList {
74 referer: self.referers,
75 })
76 },
77 };
78 let body_xml = crate::util::xml::to_xml(&config)?;
79
80 let request = HttpRequest::builder()
81 .method(http::Method::PUT)
82 .uri(&uri)
83 .body(bytes::Bytes::from(body_xml))
84 .build();
85
86 let response = self
87 .client
88 .send_signed(request, Some(&self.bucket), query_params)
89 .await
90 .map_err(|e| OssError {
91 kind: OssErrorKind::TransportError,
92 context: Box::new(ErrorContext {
93 operation: Some("PutBucketReferer".into()),
94 bucket: Some(self.bucket.to_string()),
95 endpoint: Some(endpoint),
96 ..Default::default()
97 }),
98 source: Some(Box::new(e)),
99 })?;
100
101 if response.status().is_success() {
102 Ok(PutBucketRefererOutput {
103 request_id: response
104 .headers
105 .get("x-oss-request-id")
106 .and_then(|v| v.to_str().ok())
107 .unwrap_or("")
108 .to_string(),
109 })
110 } else {
111 Err(OssError {
112 kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
113 status_code: response.status().as_u16(),
114 code: String::new(),
115 message: String::new(),
116 request_id: String::new(),
117 host_id: String::new(),
118 resource: Some(self.bucket.to_string()),
119 string_to_sign: None,
120 })),
121 context: Box::new(ErrorContext {
122 operation: Some("PutBucketReferer".into()),
123 bucket: Some(self.bucket.to_string()),
124 ..Default::default()
125 }),
126 source: None,
127 })
128 }
129 }
130}
131
132#[derive(Debug, Clone)]
133pub struct PutBucketRefererOutput {
134 pub request_id: String,
135}
136
137pub struct GetBucketRefererBuilder {
138 client: Arc<OSSClientInner>,
139 bucket: BucketName,
140}
141
142impl GetBucketRefererBuilder {
143 pub(crate) fn new(client: Arc<OSSClientInner>, bucket: BucketName) -> Self {
144 Self { client, bucket }
145 }
146
147 pub async fn send(self) -> Result<GetBucketRefererOutput> {
148 let endpoint = self.client.endpoint.clone();
149 let uri = format!("https://{}.{}?referer", self.bucket.as_str(), endpoint);
150 let query_params: Vec<(String, String)> = vec![("referer".into(), String::new())];
151
152 let request = HttpRequest::builder()
153 .method(http::Method::GET)
154 .uri(&uri)
155 .build();
156
157 let response = self
158 .client
159 .send_signed(request, Some(&self.bucket), query_params)
160 .await
161 .map_err(|e| OssError {
162 kind: OssErrorKind::TransportError,
163 context: Box::new(ErrorContext {
164 operation: Some("GetBucketReferer".into()),
165 bucket: Some(self.bucket.to_string()),
166 endpoint: Some(endpoint),
167 ..Default::default()
168 }),
169 source: Some(Box::new(e)),
170 })?;
171
172 if response.is_success() {
173 let body_str = response.body_as_str().unwrap_or("");
174 let config: RefererConfigurationResponse = crate::util::xml::from_xml(body_str)
175 .map_err(|e| OssError {
176 kind: OssErrorKind::DeserializationError,
177 context: Box::new(ErrorContext {
178 operation: Some("GetBucketReferer: parse XML".into()),
179 bucket: Some(self.bucket.to_string()),
180 ..Default::default()
181 }),
182 source: Some(Box::new(e)),
183 })?;
184
185 Ok(GetBucketRefererOutput {
186 allow_empty_referer: config.allow_empty_referer,
187 referers: config.referer_list.map(|rl| rl.referer).unwrap_or_default(),
188 })
189 } else {
190 Err(OssError {
191 kind: OssErrorKind::ServiceError(Box::new(crate::error::OssServiceError {
192 status_code: response.status().as_u16(),
193 code: String::new(),
194 message: String::new(),
195 request_id: String::new(),
196 host_id: String::new(),
197 resource: Some(self.bucket.to_string()),
198 string_to_sign: None,
199 })),
200 context: Box::new(ErrorContext {
201 operation: Some("GetBucketReferer".into()),
202 bucket: Some(self.bucket.to_string()),
203 ..Default::default()
204 }),
205 source: None,
206 })
207 }
208 }
209}
210
211#[derive(Debug, Clone)]
212pub struct GetBucketRefererOutput {
213 pub allow_empty_referer: bool,
214 pub referers: Vec<String>,
215}
216
217impl BucketOperations {
218 pub fn put_referer(&self) -> PutBucketRefererBuilder {
219 PutBucketRefererBuilder::new(self.client_inner().clone(), self.bucket_name().clone())
220 }
221
222 pub fn get_referer(&self) -> GetBucketRefererBuilder {
223 GetBucketRefererBuilder::new(self.client_inner().clone(), self.bucket_name().clone())
224 }
225}
226
227#[cfg(test)]
228mod tests {
229 use std::sync::Mutex;
230
231 use crate::client::OSSClientInner;
232 use crate::config::credentials::Credentials;
233 use crate::http::client::{HttpClient, HttpRequest, HttpResponse};
234 use crate::types::region::Region;
235
236 use super::*;
237
238 struct RecordingHttpClient {
239 requests: Arc<Mutex<Vec<HttpRequest>>>,
240 status_code: http::StatusCode,
241 response_body: bytes::Bytes,
242 }
243
244 #[async_trait::async_trait]
245 impl HttpClient for RecordingHttpClient {
246 async fn send(&self, request: HttpRequest) -> crate::error::Result<HttpResponse> {
247 self.requests.lock().unwrap().push(request);
248 let mut headers = http::HeaderMap::new();
249 headers.insert(
250 "x-oss-request-id",
251 http::HeaderValue::from_static("rid-referer"),
252 );
253 Ok(HttpResponse {
254 status: self.status_code,
255 headers,
256 body: self.response_body.clone(),
257 })
258 }
259 }
260
261 fn create_test_inner_with_body(
262 status: http::StatusCode,
263 body: bytes::Bytes,
264 ) -> (Arc<OSSClientInner>, Arc<Mutex<Vec<HttpRequest>>>) {
265 let requests = Arc::new(Mutex::new(Vec::new()));
266 let http = Arc::new(RecordingHttpClient {
267 requests: requests.clone(),
268 status_code: status,
269 response_body: body,
270 });
271 let credentials = Arc::new(crate::config::credentials::StaticCredentialsProvider::new(
272 Credentials::builder()
273 .access_key_id("test-ak")
274 .access_key_secret("test-sk")
275 .build()
276 .unwrap(),
277 ));
278 let inner = Arc::new(OSSClientInner {
279 http,
280 credentials,
281 signer: Arc::from(crate::signer::create_signer(crate::signer::SignVersion::V4)),
282 region: Region::CnHangzhou,
283 endpoint: "oss-cn-hangzhou.aliyuncs.com".into(),
284 });
285 (inner, requests)
286 }
287
288 #[test]
289 fn referer_xml_generation() {
290 let config = RefererConfiguration {
291 allow_empty_referer: true,
292 referer_list: Some(RefererList {
293 referer: vec!["https://example.com".into(), "https://*.example.com".into()],
294 }),
295 };
296 let xml = crate::util::xml::to_xml(&config).unwrap();
297 assert!(xml.contains("<AllowEmptyReferer>true</AllowEmptyReferer>"));
298 assert!(xml.contains("<Referer>https://example.com</Referer>"));
299 }
300
301 #[tokio::test]
302 async fn put_referer_sends_request() {
303 let (inner, requests) =
304 create_test_inner_with_body(http::StatusCode::OK, bytes::Bytes::new());
305 let builder = PutBucketRefererBuilder::new(inner, BucketName::new("test-bucket").unwrap())
306 .add_referer("https://example.com");
307 builder.send().await.unwrap();
308 let captured = requests.lock().unwrap();
309 assert_eq!(captured[0].method, http::Method::PUT);
310 assert!(captured[0].uri.contains("?referer"));
311 }
312}