1use crate::error::Result;
40use crate::middleware::{Middleware, Next};
41use crate::request::Request;
42use crate::response::Response;
43use async_trait::async_trait;
44use http::header::{HeaderValue, VARY};
45use http::{Method, StatusCode};
46use std::sync::Arc;
47use std::time::Duration;
48
49type OriginFn = dyn Fn(&str) -> bool + Send + Sync;
50
51enum Origins {
52 List(Vec<String>),
53 Any,
54 Predicate(Arc<OriginFn>),
55}
56
57pub struct Cors {
59 origins: Origins,
60 methods: Vec<Method>,
61 headers: Option<Vec<String>>,
63 expose: Vec<String>,
64 credentials: bool,
65 max_age: Duration,
66 private_network: bool,
67}
68
69impl Default for Cors {
70 fn default() -> Self {
71 Self::new()
72 }
73}
74
75impl Cors {
76 pub fn new() -> Self {
80 Self {
81 origins: Origins::List(Vec::new()),
82 methods: vec![
83 Method::GET,
84 Method::HEAD,
85 Method::POST,
86 Method::PUT,
87 Method::PATCH,
88 Method::DELETE,
89 ],
90 headers: None,
91 expose: Vec::new(),
92 credentials: false,
93 max_age: Duration::from_secs(600),
94 private_network: false,
95 }
96 }
97
98 pub fn allow_origin(mut self, origin: impl Into<String>) -> Self {
101 let origin = origin.into();
102 match &mut self.origins {
103 Origins::List(list) => list.push(origin),
104 _ => self.origins = Origins::List(vec![origin]),
105 }
106 self
107 }
108
109 pub fn allow_origins<I, S>(mut self, origins: I) -> Self
110 where
111 I: IntoIterator<Item = S>,
112 S: Into<String>,
113 {
114 for origin in origins {
115 self = self.allow_origin(origin);
116 }
117 self
118 }
119
120 pub fn allow_any_origin(mut self) -> Self {
123 self.origins = Origins::Any;
124 self
125 }
126
127 pub fn allow_origin_fn<F>(mut self, f: F) -> Self
129 where
130 F: Fn(&str) -> bool + Send + Sync + 'static,
131 {
132 self.origins = Origins::Predicate(Arc::new(f));
133 self
134 }
135
136 pub fn allow_methods<I>(mut self, methods: I) -> Self
138 where
139 I: IntoIterator<Item = Method>,
140 {
141 self.methods = methods.into_iter().collect();
142 self
143 }
144
145 pub fn allow_headers<I, S>(mut self, headers: I) -> Self
148 where
149 I: IntoIterator<Item = S>,
150 S: Into<String>,
151 {
152 self.headers = Some(headers.into_iter().map(Into::into).collect());
153 self
154 }
155
156 pub fn expose_headers<I, S>(mut self, headers: I) -> Self
158 where
159 I: IntoIterator<Item = S>,
160 S: Into<String>,
161 {
162 self.expose = headers.into_iter().map(Into::into).collect();
163 self
164 }
165
166 pub fn allow_credentials(mut self) -> Self {
169 self.credentials = true;
170 self
171 }
172
173 pub fn max_age(mut self, max_age: Duration) -> Self {
175 self.max_age = max_age;
176 self
177 }
178
179 pub fn allow_private_network(mut self) -> Self {
182 self.private_network = true;
183 self
184 }
185
186 fn origin_allowed(&self, origin: &str) -> bool {
187 match &self.origins {
188 Origins::Any => true,
189 Origins::List(list) => list.iter().any(|o| o == origin),
190 Origins::Predicate(f) => f(origin),
191 }
192 }
193
194 fn any(&self) -> bool {
195 matches!(self.origins, Origins::Any)
196 }
197
198 fn decorate(&self, mut res: Response, origin: &str) -> Response {
200 if self.any() {
201 res = res.with_header("access-control-allow-origin", "*");
202 } else {
203 res = res.with_header("access-control-allow-origin", origin);
204 add_vary(&mut res, "origin");
205 if self.credentials {
206 res = res.with_header("access-control-allow-credentials", "true");
207 }
208 }
209 if !self.expose.is_empty() {
210 res = res.with_header("access-control-expose-headers", self.expose.join(", "));
211 }
212 res
213 }
214
215 fn preflight(&self, req: &Request, origin: &str) -> Response {
216 let requested = req
217 .headers
218 .get("access-control-request-method")
219 .and_then(|v| v.to_str().ok())
220 .unwrap_or("");
221 let method_ok = self.methods.iter().any(|m| m.as_str() == requested);
222 if !self.origin_allowed(origin) || !method_ok {
223 let mut res = Response::new(StatusCode::FORBIDDEN);
224 add_vary(&mut res, "origin");
225 return res;
226 }
227
228 let mut res = self.decorate(Response::new(StatusCode::NO_CONTENT), origin);
229 let methods: Vec<&str> = self.methods.iter().map(Method::as_str).collect();
230 res = res
231 .with_header("access-control-allow-methods", methods.join(", "))
232 .with_header("access-control-max-age", self.max_age.as_secs().to_string());
233 match &self.headers {
234 Some(list) if !list.is_empty() => {
235 res = res.with_header("access-control-allow-headers", list.join(", "));
236 }
237 Some(_) => {}
238 None => {
239 if let Some(asked) = req
240 .headers
241 .get("access-control-request-headers")
242 .and_then(|v| v.to_str().ok())
243 .filter(|v| !v.trim().is_empty())
244 {
245 res = res.with_header("access-control-allow-headers", asked);
246 add_vary(&mut res, "access-control-request-headers");
247 }
248 }
249 }
250 if self.private_network
251 && req
252 .headers
253 .get("access-control-request-private-network")
254 .is_some_and(|v| v == "true")
255 {
256 res = res.with_header("access-control-allow-private-network", "true");
257 }
258 res
259 }
260}
261
262pub(crate) fn add_vary(res: &mut Response, token: &str) {
264 let existing: Vec<String> = res
265 .headers
266 .get_all(VARY)
267 .iter()
268 .filter_map(|v| v.to_str().ok())
269 .flat_map(|v| v.split(','))
270 .map(|t| t.trim().to_string())
271 .filter(|t| !t.is_empty())
272 .collect();
273 if existing
274 .iter()
275 .any(|t| t == "*" || t.eq_ignore_ascii_case(token))
276 {
277 return;
278 }
279 let mut all = existing;
280 all.push(token.to_string());
281 if let Ok(value) = HeaderValue::from_str(&all.join(", ")) {
282 res.headers.insert(VARY, value);
283 }
284}
285
286#[async_trait]
287impl Middleware for Cors {
288 async fn handle(&self, req: Request, next: Next) -> Result<Response> {
289 let origin = req
290 .headers
291 .get("origin")
292 .and_then(|v| v.to_str().ok())
293 .map(str::to_string);
294 let Some(origin) = origin else {
295 let mut res = into_response(next.run(req).await);
298 if !self.any() {
299 add_vary(&mut res, "origin");
300 }
301 return Ok(res);
302 };
303
304 let is_preflight = req.method == Method::OPTIONS
305 && req.headers.contains_key("access-control-request-method");
306 if is_preflight {
307 return Ok(self.preflight(&req, &origin));
308 }
309
310 let mut res = into_response(next.run(req).await);
311 if self.origin_allowed(&origin) {
312 res = self.decorate(res, &origin);
313 } else if !self.any() {
314 add_vary(&mut res, "origin");
315 }
316 Ok(res)
317 }
318}
319
320fn into_response(result: Result<Response>) -> Response {
321 result.unwrap_or_else(Response::from_error)
322}
323
324#[cfg(test)]
325mod tests {
326 use super::*;
327
328 #[test]
329 fn vary_is_merged_not_replaced() {
330 let mut res = Response::ok().with_header("vary", "accept-encoding");
331 add_vary(&mut res, "origin");
332 assert_eq!(res.headers[VARY], "accept-encoding, origin");
333 add_vary(&mut res, "Origin");
334 assert_eq!(res.headers[VARY], "accept-encoding, origin");
335 }
336
337 #[test]
338 fn vary_star_is_left_alone() {
339 let mut res = Response::ok().with_header("vary", "*");
340 add_vary(&mut res, "origin");
341 assert_eq!(res.headers[VARY], "*");
342 }
343
344 #[test]
345 fn origin_matching_is_exact() {
346 let cors = Cors::new().allow_origin("https://a.example");
347 assert!(cors.origin_allowed("https://a.example"));
348 assert!(!cors.origin_allowed("https://a.example.evil.com"));
349 assert!(!cors.origin_allowed("http://a.example"));
350 assert!(!Cors::new().origin_allowed("null"));
351 }
352}