Skip to main content

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}