rama_http/layer/cors/mod.rs
1//! Middleware which adds headers for [CORS][mdn].
2//!
3//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
4
5use crate::{
6 HeaderMap, HeaderValue, Method, Request, Response,
7 header::{self},
8};
9use rama_core::error::BoxError;
10use rama_core::error::BoxErrorExt as _;
11use rama_core::{Layer, Service};
12use rama_http_headers::{
13 AccessControlAllowHeaders, AccessControlAllowMethods, AccessControlExposeHeaders,
14 AccessControlMaxAge, HeaderMapExt, Vary, util::Seconds,
15};
16use rama_http_types::{body::OptionalBody, request::Parts as RequestParts};
17use rama_utils::collections::NonEmptyVec;
18use rama_utils::macros::{define_inner_service_accessors, generate_set_and_with};
19use std::{mem, sync::Arc};
20
21mod allow_credentials;
22mod allow_headers;
23mod allow_methods;
24mod allow_origin;
25mod allow_private_network;
26mod max_age;
27
28#[cfg(test)]
29mod tests;
30
31use self::{
32 allow_credentials::AllowCredentials, allow_headers::AllowHeaders, allow_methods::AllowMethods,
33 allow_origin::AllowOrigin, allow_private_network::AllowPrivateNetwork, max_age::MaxAge,
34};
35
36/// Layer that applies the [`Cors`] middleware which adds headers for [CORS][mdn].
37///
38/// See the [module docs](crate::layer::cors) for an example.
39///
40/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
41#[derive(Debug, Clone)]
42#[must_use]
43pub struct CorsLayer {
44 allow_credentials: Option<AllowCredentials>,
45 allow_headers: Option<AllowHeaders>,
46 allow_methods: Option<AllowMethods>,
47 allow_origin: Option<AllowOrigin>,
48 allow_private_network: Option<AllowPrivateNetwork>,
49 expose_headers: Option<AccessControlExposeHeaders>,
50 max_age: Option<MaxAge>,
51 vary: Option<Vary>,
52 handle_options_request: bool,
53}
54
55impl CorsLayer {
56 /// Create a new `CorsLayer`.
57 ///
58 /// No headers are sent by default. Use the builder methods to customize
59 /// the behavior.
60 ///
61 /// You need to set at least an allowed origin for browsers to make
62 /// successful cross-origin requests to your service.
63 pub fn new() -> Self {
64 Self {
65 allow_credentials: None,
66 allow_headers: None,
67 allow_methods: None,
68 allow_origin: None,
69 allow_private_network: None,
70 expose_headers: None,
71 max_age: None,
72 vary: None,
73 handle_options_request: false,
74 }
75 }
76
77 /// A permissive configuration:
78 ///
79 /// - All request headers allowed.
80 /// - All methods allowed.
81 /// - All origins allowed.
82 /// - All headers exposed.
83 pub fn permissive() -> Self {
84 Self {
85 allow_headers: Some(AllowHeaders::Const(AccessControlAllowHeaders::new_any())),
86 allow_methods: Some(AllowMethods::Const(AccessControlAllowMethods::new_any())),
87 allow_origin: Some(AllowOrigin::Any),
88 expose_headers: Some(AccessControlExposeHeaders::new_any()),
89 ..Self::new()
90 }
91 }
92
93 /// A very permissive configuration:
94 ///
95 /// - **Credentials allowed.**
96 /// - The method received in `Access-Control-Request-Method` is sent back
97 /// as an allowed method.
98 /// - The origin of the preflight request is sent back as an allowed origin.
99 /// - The header names received in `Access-Control-Request-Headers` are sent
100 /// back as allowed headers.
101 /// - No headers are currently exposed, but this may change in the future.
102 pub fn very_permissive() -> Self {
103 Self {
104 allow_credentials: Some(AllowCredentials::Const),
105 allow_headers: Some(AllowHeaders::MirrorRequest),
106 allow_methods: Some(AllowMethods::MirrorRequest),
107 allow_origin: Some(AllowOrigin::MirrorRequest),
108 ..Self::new()
109 }
110 }
111
112 fn is_allow_credentials_any(&self) -> bool {
113 matches!(self.allow_credentials, Some(AllowCredentials::Const))
114 }
115
116 generate_set_and_with! {
117 /// Always set the [`Access-Control-Allow-Credentials`][mdn] header.
118 ///
119 /// # Errors
120 ///
121 /// Errors in case any of the other CORS headers which
122 /// support the wildcard value have been set to use it.
123 ///
124 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
125 pub fn allow_credentials(mut self) -> Result<Self, BoxError> {
126 if self.allow_headers.as_ref().map(|v| v.is_any()).unwrap_or_default()
127 || self.allow_methods.as_ref().map(|v| v.is_any()).unwrap_or_default()
128 || self.allow_origin.as_ref().map(|v| v.is_any()).unwrap_or_default()
129 || self.expose_headers.as_ref().map(|v| v.is_any()).unwrap_or_default() {
130 return Err(BoxError::from_static_str("CORS combo error: allow credentials is not allowed if some of the wildcard-abled headers are set to use the wildcard value"));
131 }
132 self.allow_credentials = Some(AllowCredentials::Const);
133 Ok(self)
134 }
135 }
136
137 generate_set_and_with! {
138 /// Set the [`Access-Control-Allow-Credentials`][mdn] header if predicate is satisfied.
139 ///
140 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
141 pub fn allow_credentials_if(
142 mut self,
143 predicate: impl Fn(&HeaderValue, &RequestParts) -> bool + Send + Sync + 'static
144 ) -> Self {
145 self.allow_credentials = Some(AllowCredentials::Predicate(Arc::new(predicate)));
146 self
147 }
148 }
149
150 generate_set_and_with! {
151 /// Set the value of the [`Access-Control-Allow-Headers`][mdn] header.
152 ///
153 /// # Errors
154 ///
155 /// Errors in case credentials are allowed and the given header
156 /// contains the wildcard value (`*`).
157 ///
158 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
159 pub fn allow_headers(mut self, headers: AccessControlAllowHeaders) -> Result<Self, BoxError> {
160 if headers.is_any() && self.is_allow_credentials_any() {
161 return Err(BoxError::from_static_str("Invalid CORS configuration: Cannot combine `Access-Control-Allow-Credentials: true` with `Access-Control-Allow-Headers: *`"))
162 }
163 self.allow_headers = Some(AllowHeaders::Const(headers));
164 Ok(self)
165 }
166 }
167
168 generate_set_and_with! {
169 /// Mirror the request's [`Access-Control-Request-Headers`][mdn] header
170 /// back as the value of [`Access-Control-Allow-Headers`][mdn-allow].
171 ///
172 /// Causes the derived `Vary` to advertise
173 /// `Access-Control-Request-Headers`.
174 ///
175 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers
176 /// [mdn-allow]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
177 pub fn allow_headers_mirror_request(mut self) -> Self {
178 self.allow_headers = Some(AllowHeaders::MirrorRequest);
179 self
180 }
181 }
182
183 generate_set_and_with! {
184 /// Set the value of the [`Access-Control-Max-Age`][mdn] header.
185 ///
186 /// By default the header will not be set which disables caching and will
187 /// require a preflight call for all requests.
188 ///
189 /// Note that each browser has a maximum internal value that takes
190 /// precedence when the Access-Control-Max-Age is greater. For more details
191 /// see [mdn].
192 ///
193 /// If you need more flexibility, you can use supply a function which can
194 /// dynamically decide the optional max-age based on the origin and other parts of
195 /// each preflight request.
196 ///
197 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
198 pub fn max_age(mut self, header: AccessControlMaxAge) -> Self {
199 self.max_age = Some(MaxAge::Const(header));
200 self
201 }
202 }
203
204 generate_set_and_with! {
205 /// Set the [`Access-Control-Max-Age`][mdn] header if predicate is satisfied.
206 ///
207 /// See [`Self::with_max_age`] and [`Self::set_max_age`] for more information.
208 ///
209 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
210 pub fn max_age_if(
211 mut self,
212 predicate: impl Fn(&HeaderValue, &RequestParts) -> Option<Seconds> + Send + Sync + 'static
213 ) -> Self {
214 self.max_age = Some(MaxAge::Predicate(Arc::new(predicate)));
215 self
216 }
217 }
218
219 generate_set_and_with! {
220 /// Set the value of the [`Access-Control-Allow-Methods`][mdn] header.
221 ///
222 /// # Errors
223 ///
224 /// Errors in case credentials are allowed and the given header
225 /// contains the wildcard value (`*`).
226 ///
227 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
228 pub fn allow_methods(mut self, methods: AccessControlAllowMethods) -> Result<Self, BoxError> {
229 if methods.is_any() && self.is_allow_credentials_any() {
230 return Err(BoxError::from_static_str("Invalid CORS configuration: Cannot combine `Access-Control-Allow-Credentials: true` with `Access-Control-Allow-Methods: *`"))
231 }
232 self.allow_methods = Some(AllowMethods::Const(methods));
233 Ok(self)
234 }
235 }
236
237 generate_set_and_with! {
238 /// Mirror the request's [`Access-Control-Request-Method`][mdn] header
239 /// back as the value of [`Access-Control-Allow-Methods`][mdn-allow].
240 ///
241 /// Causes the derived `Vary` to advertise
242 /// `Access-Control-Request-Method`.
243 ///
244 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method
245 /// [mdn-allow]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
246 pub fn allow_methods_mirror_request(mut self) -> Self {
247 self.allow_methods = Some(AllowMethods::MirrorRequest);
248 self
249 }
250 }
251
252 generate_set_and_with! {
253 /// Only set the [`Access-Control-Allow-Origin`][mdn] header with the wildcard value (`*`).
254 ///
255 /// # Errors
256 ///
257 /// Errors in case credentials are allowed.
258 ///
259 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
260 pub fn allow_origin_any(mut self) -> Result<Self, BoxError> {
261 if self.is_allow_credentials_any() {
262 return Err(BoxError::from_static_str("Invalid CORS configuration: Cannot combine `Access-Control-Allow-Credentials: true` with `Access-Control-Allow-Origin: *`"))
263 }
264 self.allow_origin = Some(AllowOrigin::Any);
265 Ok(self)
266 }
267 }
268
269 generate_set_and_with! {
270 /// Only set the [`Access-Control-Allow-Origin`][mdn] header with the "null" value when that is the origin.
271 ///
272 /// Note: The value `null` should not be used.
273 /// It may seem safe to return `Access-Control-Allow-Origin: "null"`;
274 /// however, the origin of resources that use a non-hierarchical scheme
275 /// (such as `data:` or `file:`) and sandboxed documents is serialized as `null`.
276 /// Many browsers will grant such documents access to a response with an
277 /// `Access-Control-Allow-Origin: null` header, and any origin can create a
278 /// hostile document with a `null` origin. Therefore, the `null` value for the
279 /// `Access-Control-Allow-Origin` header should be avoided
280 ///
281 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
282 pub fn allow_origin_if_null(mut self) -> Self {
283 self.allow_origin = Some(AllowOrigin::Null);
284 self
285 }
286 }
287
288 generate_set_and_with! {
289 /// Set the value of the [`Access-Control-Allow-Origin`][mdn] header if the predicate satisfied.
290 ///
291 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
292 pub fn allow_origin_if(
293 mut self,
294 predicate: impl Fn(&HeaderValue, &RequestParts) -> bool + Send + Sync + 'static
295 ) -> Self {
296 self.allow_origin = Some(AllowOrigin::Predicate(Arc::new(predicate)));
297 self
298 }
299 }
300
301 generate_set_and_with! {
302 /// Mirror the request's [`Origin`][mdn] header back as the value of
303 /// [`Access-Control-Allow-Origin`][mdn-allow].
304 ///
305 /// Causes the derived `Vary` to advertise `Origin`.
306 ///
307 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin
308 /// [mdn-allow]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
309 pub fn allow_origin_mirror_request(mut self) -> Self {
310 self.allow_origin = Some(AllowOrigin::MirrorRequest);
311 self
312 }
313 }
314
315 generate_set_and_with! {
316 /// Set the value of the [`Access-Control-Expose-Headers`][mdn] header.
317 ///
318 /// # Errors
319 ///
320 /// Errors in case credentials are allowed and the given header
321 /// contains the wildcard value (`*`).
322 ///
323 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
324 pub fn expose_headers(mut self, headers: AccessControlExposeHeaders) -> Result<Self, BoxError> {
325 if headers.is_any() && self.is_allow_credentials_any() {
326 return Err(BoxError::from_static_str("Invalid CORS configuration: Cannot combine `Access-Control-Allow-Credentials: true` with `Access-Control-Expose-Headers: *`"))
327 }
328 self.expose_headers = Some(headers);
329 Ok(self)
330 }
331 }
332
333 generate_set_and_with! {
334 /// Always set the [`Access-Control-Allow-Private-Network`][mdn] header.
335 ///
336 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Private-Network
337 pub fn allow_private_network(mut self) -> Self {
338 self.allow_private_network = Some(AllowPrivateNetwork::Const);
339 self
340 }
341 }
342
343 generate_set_and_with! {
344 /// Set the [`Access-Control-Allow-Private-Network`][mdn] header if predicate is satisfied.
345 ///
346 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Private-Network
347 pub fn allow_private_network_if(
348 mut self,
349 predicate: impl Fn(&HeaderValue, &RequestParts) -> bool + Send + Sync + 'static
350 ) -> Self {
351 self.allow_private_network = Some(AllowPrivateNetwork::Predicate(Arc::new(predicate)));
352 self
353 }
354 }
355
356 generate_set_and_with! {
357 /// Set the value(s) of the [`Vary`][mdn] header.
358 ///
359 /// By default no `Vary` value is configured explicitly. The `Vary`
360 /// header is then derived from whether the other CORS settings are
361 /// request-dependent:
362 ///
363 /// - `Origin` is advertised when `Access-Control-Allow-Origin` depends
364 /// on the request's `Origin` header (e.g. `MirrorRequest`,
365 /// predicate).
366 /// - `Access-Control-Request-Method` is advertised when
367 /// `Access-Control-Allow-Methods` mirrors the request method.
368 /// - `Access-Control-Request-Headers` is advertised when
369 /// `Access-Control-Allow-Headers` mirrors the request headers.
370 /// - If none of those are request-dependent, no `Vary` header is
371 /// emitted by the layer.
372 ///
373 /// Setting `Vary` explicitly pins the value, but the layer still
374 /// appends `Vary: origin` as a safety net when `Access-Control-Allow-Origin`
375 /// is request-dependent and the supplied `Vary` omits it.
376 ///
377 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary
378 pub fn vary(mut self, vary: Option<Vary>) -> Self
379 {
380 self.vary = vary;
381 self
382 }
383 }
384
385 /// Derive a `Vary` header from the currently-configured allow-* settings,
386 /// or `None` if none of them depend on a request header.
387 fn derived_vary(&self) -> Option<Vary> {
388 let mut names = Vec::with_capacity(3);
389 if self
390 .allow_origin
391 .as_ref()
392 .is_some_and(AllowOrigin::varies_with_origin)
393 {
394 names.push(header::ORIGIN);
395 }
396 if self
397 .allow_methods
398 .as_ref()
399 .is_some_and(AllowMethods::varies_with_request_method)
400 {
401 names.push(header::ACCESS_CONTROL_REQUEST_METHOD);
402 }
403 if self
404 .allow_headers
405 .as_ref()
406 .is_some_and(AllowHeaders::varies_with_request_headers)
407 {
408 names.push(header::ACCESS_CONTROL_REQUEST_HEADERS);
409 }
410 NonEmptyVec::from_vec(names).map(Vary::headers)
411 }
412
413 /// Handle OPTIONS request with the inner service.
414 ///
415 /// By default it is not passed on to the inner service,
416 /// and instead just returned with a 200 OK (empty body).
417 ///
418 /// NOTE that this does not stop the response headers from being added,
419 /// it only defines who "creates" the response, the modification happens regardless.
420 pub fn handle_options_request(mut self) -> Self {
421 self.handle_options_request = true;
422 self
423 }
424}
425
426impl Default for CorsLayer {
427 fn default() -> Self {
428 Self::new()
429 }
430}
431
432impl<S> Layer<S> for CorsLayer {
433 type Service = Cors<S>;
434
435 fn layer(&self, inner: S) -> Self::Service {
436 Cors {
437 inner,
438 layer: self.clone(),
439 }
440 }
441
442 fn into_layer(self, inner: S) -> Self::Service {
443 Cors { inner, layer: self }
444 }
445}
446
447/// Middleware which adds headers for [CORS][mdn].
448///
449/// See the [module docs](crate::layer::cors) for an example.
450///
451/// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS
452#[derive(Debug, Clone)]
453pub struct Cors<S> {
454 inner: S,
455 layer: CorsLayer,
456}
457
458impl<S> Cors<S> {
459 /// Create a new `Cors`.
460 ///
461 /// See [`CorsLayer::new`] for more details.
462 pub fn new(inner: S) -> Self {
463 Self {
464 inner,
465 layer: CorsLayer::new(),
466 }
467 }
468
469 /// A permissive configuration.
470 ///
471 /// See [`CorsLayer::permissive`] for more details.
472 pub fn permissive(inner: S) -> Self {
473 Self {
474 inner,
475 layer: CorsLayer::permissive(),
476 }
477 }
478
479 /// A very permissive configuration.
480 ///
481 /// See [`CorsLayer::very_permissive`] for more details.
482 pub fn very_permissive(inner: S) -> Self {
483 Self {
484 inner,
485 layer: CorsLayer::very_permissive(),
486 }
487 }
488
489 define_inner_service_accessors!();
490
491 generate_set_and_with! {
492 /// Always set the [`Access-Control-Allow-Credentials`][mdn] header.
493 ///
494 /// # Errors
495 ///
496 /// Errors in case any of the other CORS headers which
497 /// support the wildcard value have been set to use it.
498 ///
499 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
500 pub fn allow_credentials(mut self) -> Result<Self, BoxError> {
501 self.layer.try_set_allow_credentials()?;
502 Ok(self)
503 }
504 }
505
506 generate_set_and_with! {
507 /// Set the [`Access-Control-Allow-Credentials`][mdn] header if predicate is satisfied.
508 ///
509 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Credentials
510 pub fn allow_credentials_if(
511 mut self,
512 predicate: impl Fn(&HeaderValue, &RequestParts) -> bool + Send + Sync + 'static
513 ) -> Self {
514 self.layer.set_allow_credentials_if(predicate);
515 self
516 }
517 }
518
519 generate_set_and_with! {
520 /// Set the value of the [`Access-Control-Allow-Headers`][mdn] header.
521 ///
522 /// # Errors
523 ///
524 /// Errors in case credentials are allowed and the given header
525 /// contains the wildcard value (`*`).
526 ///
527 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
528 pub fn allow_headers(mut self, headers: AccessControlAllowHeaders) -> Result<Self, BoxError> {
529 self.layer.try_set_allow_headers(headers)?;
530 Ok(self)
531 }
532 }
533
534 generate_set_and_with! {
535 /// Mirror the request's [`Access-Control-Request-Headers`][mdn] header
536 /// back as the value of [`Access-Control-Allow-Headers`][mdn-allow].
537 ///
538 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Headers
539 /// [mdn-allow]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Headers
540 pub fn allow_headers_mirror_request(mut self) -> Self {
541 self.layer.set_allow_headers_mirror_request();
542 self
543 }
544 }
545
546 generate_set_and_with! {
547 /// Set the value of the [`Access-Control-Max-Age`][mdn] header.
548 ///
549 /// By default the header will not be set which disables caching and will
550 /// require a preflight call for all requests.
551 ///
552 /// Note that each browser has a maximum internal value that takes
553 /// precedence when the Access-Control-Max-Age is greater. For more details
554 /// see [mdn].
555 ///
556 /// If you need more flexibility, you can use supply a function which can
557 /// dynamically decide the optional max-age based on the origin and other parts of
558 /// each preflight request.
559 ///
560 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
561 pub fn max_age(mut self, header: AccessControlMaxAge) -> Self {
562 self.layer.set_max_age(header);
563 self
564 }
565 }
566
567 generate_set_and_with! {
568 /// Set the [`Access-Control-Max-Age`][mdn] header if predicate is satisfied.
569 ///
570 /// See [`Self::with_max_age`] and [`Self::set_max_age`] for more information.
571 ///
572 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Max-Age
573 pub fn max_age_if(
574 mut self,
575 predicate: impl Fn(&HeaderValue, &RequestParts) -> Option<Seconds> + Send + Sync + 'static
576 ) -> Self {
577 self.layer.set_max_age_if(predicate);
578 self
579 }
580 }
581
582 generate_set_and_with! {
583 /// Set the value of the [`Access-Control-Allow-Methods`][mdn] header.
584 ///
585 /// # Errors
586 ///
587 /// Errors in case credentials are allowed and the given header
588 /// contains the wildcard value (`*`).
589 ///
590 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
591 pub fn allow_methods(mut self, methods: AccessControlAllowMethods) -> Result<Self, BoxError> {
592 self.layer.try_set_allow_methods(methods)?;
593 Ok(self)
594 }
595 }
596
597 generate_set_and_with! {
598 /// Mirror the request's [`Access-Control-Request-Method`][mdn] header
599 /// back as the value of [`Access-Control-Allow-Methods`][mdn-allow].
600 ///
601 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Request-Method
602 /// [mdn-allow]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Methods
603 pub fn allow_methods_mirror_request(mut self) -> Self {
604 self.layer.set_allow_methods_mirror_request();
605 self
606 }
607 }
608
609 generate_set_and_with! {
610 /// Only set the [`Access-Control-Allow-Origin`][mdn] header with the wildcard value (`*`).
611 ///
612 /// # Errors
613 ///
614 /// Errors in case credentials are allowed.
615 ///
616 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
617 pub fn allow_origin_any(mut self) -> Result<Self, BoxError> {
618 self.layer.try_set_allow_origin_any()?;
619 Ok(self)
620 }
621 }
622
623 generate_set_and_with! {
624 /// Only set the [`Access-Control-Allow-Origin`][mdn] header with the "null" value when that is the origin.
625 ///
626 /// Note: The value `null` should not be used.
627 /// It may seem safe to return `Access-Control-Allow-Origin: "null"`;
628 /// however, the origin of resources that use a non-hierarchical scheme
629 /// (such as `data:` or `file:`) and sandboxed documents is serialized as `null`.
630 /// Many browsers will grant such documents access to a response with an
631 /// `Access-Control-Allow-Origin: null` header, and any origin can create a
632 /// hostile document with a `null` origin. Therefore, the `null` value for the
633 /// `Access-Control-Allow-Origin` header should be avoided
634 ///
635 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
636 pub fn allow_origin_if_null(mut self) -> Self {
637 self.layer.set_allow_origin_if_null();
638 self
639 }
640 }
641
642 generate_set_and_with! {
643 /// Set the value of the [`Access-Control-Allow-Origin`][mdn] header if the predicate satisfied.
644 ///
645 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
646 pub fn allow_origin_if(
647 mut self,
648 predicate: impl Fn(&HeaderValue, &RequestParts) -> bool + Send + Sync + 'static
649 ) -> Self {
650 self.layer.set_allow_origin_if(predicate);
651 self
652 }
653 }
654
655 generate_set_and_with! {
656 /// Mirror the request's [`Origin`][mdn] header back as the value of
657 /// [`Access-Control-Allow-Origin`][mdn-allow].
658 ///
659 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin
660 /// [mdn-allow]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Origin
661 pub fn allow_origin_mirror_request(mut self) -> Self {
662 self.layer.set_allow_origin_mirror_request();
663 self
664 }
665 }
666
667 generate_set_and_with! {
668 /// Set the value of the [`Access-Control-Expose-Headers`][mdn] header.
669 ///
670 /// # Errors
671 ///
672 /// Errors in case credentials are allowed and the given header
673 /// contains the wildcard value (`*`).
674 ///
675 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Expose-Headers
676 pub fn expose_headers(mut self, headers: AccessControlExposeHeaders) -> Result<Self, BoxError> {
677 self.layer.try_set_expose_headers(headers)?;
678 Ok(self)
679 }
680 }
681
682 generate_set_and_with! {
683 /// Always set the [`Access-Control-Allow-Private-Network`][mdn] header.
684 ///
685 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Private-Network
686 pub fn allow_private_network(mut self) -> Self {
687 self.layer.set_allow_private_network();
688 self
689 }
690 }
691
692 generate_set_and_with! {
693 /// Set the [`Access-Control-Allow-Private-Network`][mdn] header if predicate is satisfied.
694 ///
695 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Access-Control-Allow-Private-Network
696 pub fn allow_private_network_if(
697 mut self,
698 predicate: impl Fn(&HeaderValue, &RequestParts) -> bool + Send + Sync + 'static
699 ) -> Self {
700 self.layer.set_allow_private_network_if(predicate);
701 self
702 }
703 }
704
705 generate_set_and_with! {
706 /// Set the value(s) of the [`Vary`][mdn] header. See [`CorsLayer::with_vary`]
707 /// for the derivation rules used when no value is set.
708 ///
709 /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary
710 pub fn vary(mut self, vary: Option<Vary>) -> Self {
711 self.layer.maybe_set_vary(vary);
712 self
713 }
714 }
715}
716
717impl<S, ReqBody, ResBody> Service<Request<ReqBody>> for Cors<S>
718where
719 S: Service<Request<ReqBody>, Output = Response<ResBody>>,
720 ReqBody: Send + 'static,
721 ResBody: Send + 'static,
722{
723 type Output = Response<OptionalBody<ResBody>>;
724 type Error = S::Error;
725
726 async fn serve(&self, req: Request<ReqBody>) -> Result<Self::Output, Self::Error> {
727 let (parts, body) = req.into_parts();
728 let origin = parts.headers.get(&header::ORIGIN);
729
730 let mut headers = HeaderMap::new();
731
732 // These headers are applied to both preflight and subsequent regular CORS requests:
733 // https://fetch.spec.whatwg.org/#http-responses
734 if let Some(allow_credentials) = self.layer.allow_credentials.as_ref() {
735 allow_credentials.extend_headers(&mut headers, origin, &parts);
736 }
737 if let Some(allow_private_network) = self.layer.allow_private_network.as_ref() {
738 allow_private_network.extend_headers(&mut headers, origin, &parts);
739 }
740
741 // Resolve the effective `Vary`. If the user pinned one explicitly we
742 // use it as-is; otherwise we derive it from whether the configured
743 // CORS allow-* settings are request-dependent. If nothing varies, no
744 // `Vary` header is added at all.
745 let effective_vary = self
746 .layer
747 .vary
748 .clone()
749 .or_else(|| self.layer.derived_vary());
750 if let Some(ref vary) = effective_vary {
751 headers.typed_insert(vary);
752 }
753 // When `Access-Control-Allow-Origin` is computed from the request's
754 // `Origin`, the response MUST advertise `Vary: Origin` so shared
755 // caches don't serve one client's CORS allowance to another. A
756 // user-supplied `Vary` may not contain `Origin`; in that case
757 // append an additional `Vary: origin` directive — multiple
758 // `Vary` field-values are merged per RFC 9110 §5.3.5.
759 if let Some(allow_origin) = self.layer.allow_origin.as_ref()
760 && allow_origin.is_request_dependent()
761 && !effective_vary
762 .as_ref()
763 .map(vary_contains_origin)
764 .unwrap_or(false)
765 {
766 headers.append(header::VARY, HeaderValue::from_static("origin"));
767 }
768
769 if let Some(allow_origin) = self.layer.allow_origin.as_ref() {
770 allow_origin.extend_headers(&mut headers, origin, &parts);
771 }
772
773 // Return results immediately upon preflight request
774 if parts.method == Method::OPTIONS {
775 // These headers are applied only to preflight requests
776 if let Some(allow_methods) = &self.layer.allow_methods {
777 allow_methods.extend_headers(&mut headers, &parts);
778 }
779 if let Some(allow_headers) = &self.layer.allow_headers {
780 allow_headers.extend_headers(&mut headers, &parts);
781 }
782 if let Some(max_age) = &self.layer.max_age {
783 max_age.extend_headers(&mut headers, origin, &parts);
784 }
785
786 Ok(if self.layer.handle_options_request {
787 let req = Request::from_parts(parts, body);
788
789 let mut response: Response<ResBody> = self.inner.serve(req).await?;
790 merge_cors_headers(response.headers_mut(), headers);
791
792 response.map(OptionalBody::some)
793 } else {
794 let mut response = Response::new(OptionalBody::none());
795 mem::swap(response.headers_mut(), &mut headers);
796
797 response
798 })
799 } else {
800 // This header is applied only to non-preflight requests
801 if let Some(ref header) = self.layer.expose_headers {
802 headers.typed_insert(header);
803 }
804
805 let req = Request::from_parts(parts, body);
806
807 let mut response: Response<ResBody> = self.inner.serve(req).await?;
808 merge_cors_headers(response.headers_mut(), headers);
809
810 Ok(response.map(OptionalBody::some))
811 }
812 }
813}
814
815/// Whether `vary` advertises a dependency on the `Origin` request header.
816/// `Vary: *` is treated as covering everything, which satisfies the
817/// dynamic-origin invariant.
818fn vary_contains_origin(vary: &Vary) -> bool {
819 match vary.iter_strs() {
820 None => true, // Vary: *
821 Some(mut iter) => iter.any(|name| name == header::ORIGIN),
822 }
823}
824
825/// Merge the locally-built CORS headers into the inner handler's response,
826/// preserving any CORS headers the inner handler already set. `Vary` is
827/// merged via append; everything else is `entry().or_insert(_)` so an
828/// explicit per-handler override always wins over the layer default.
829fn merge_cors_headers(response_headers: &mut HeaderMap, mut cors_headers: HeaderMap) {
830 // Drain all Vary values from the local map and append each to the
831 // response, preserving any Vary entries the inner handler already set.
832 let vary_values: Vec<HeaderValue> =
833 cors_headers.get_all(header::VARY).iter().cloned().collect();
834 cors_headers.remove(header::VARY);
835 for value in vary_values {
836 response_headers.append(header::VARY, value);
837 }
838
839 // For all remaining CORS headers, let the inner handler win — the
840 // CORS layer only supplies the default when no explicit value was set.
841 let mut last_name: Option<header::HeaderName> = None;
842 for (name, value) in cors_headers.drain() {
843 let entry_name = match name {
844 Some(n) => {
845 last_name = Some(n.clone());
846 n
847 }
848 // Continuation of the previous header name in `drain`'s order.
849 None => match last_name.clone() {
850 Some(n) => n,
851 None => continue,
852 },
853 };
854 if !response_headers.contains_key(&entry_name) {
855 response_headers.insert(entry_name, value);
856 }
857 }
858}