ntex_cors/
lib.rs

1#![allow(
2    clippy::borrow_interior_mutable_const,
3    clippy::type_complexity,
4    clippy::mutable_key_type
5)]
6//! Cross-origin resource sharing (CORS) for ntex applications
7//!
8//! CORS middleware could be used with application and with resource.
9//! Cors middleware could be used as parameter for `App::wrap()`,
10//! `Resource::wrap()` or `Scope::wrap()` methods.
11//!
12//! # Example
13//!
14//! ```rust,no_run
15//! use ntex_cors::Cors;
16//! use ntex::{http, web};
17//! use ntex::web::{App, HttpRequest, HttpResponse};
18//!
19//! async fn index(req: HttpRequest) -> &'static str {
20//!     "Hello world"
21//! }
22//!
23//! #[ntex::main]
24//! async fn main() -> std::io::Result<()> {
25//!     web::server(|| App::new()
26//!         .wrap(
27//!             Cors::new() // <- Construct CORS middleware builder
28//!               .allowed_origin("https://www.rust-lang.org/")
29//!               .allowed_methods(vec![http::Method::GET, http::Method::POST])
30//!               .allowed_headers(vec![http::header::AUTHORIZATION, http::header::ACCEPT])
31//!               .allowed_header(http::header::CONTENT_TYPE)
32//!               .max_age(3600)
33//!               .finish())
34//!         .service(
35//!             web::resource("/index.html")
36//!               .route(web::get().to(index))
37//!               .route(web::head().to(|| async { HttpResponse::MethodNotAllowed() }))
38//!         ))
39//!         .bind("127.0.0.1:8080")?
40//!         .run()
41//!         .await
42//! }
43//! ```
44//! In this example custom *CORS* middleware get registered for "/index.html"
45//! endpoint.
46//!
47//! Cors middleware automatically handle *OPTIONS* preflight request.
48use std::{
49    collections::HashSet, convert::TryFrom, iter::FromIterator, marker::PhantomData, rc::Rc,
50};
51
52use derive_more::Display;
53use ntex::http::header::{self, HeaderName, HeaderValue};
54use ntex::http::{error::HttpError, HeaderMap, Method, RequestHead, StatusCode, Uri};
55use ntex::service::{Middleware, Service, ServiceCtx};
56use ntex::util::Either;
57use ntex::web::{
58    DefaultError, ErrorRenderer, HttpResponse, WebRequest, WebResponse, WebResponseError,
59};
60
61/// A set of errors that can occur during processing CORS
62#[derive(Debug, Display)]
63pub enum CorsError {
64    /// The HTTP request header `Origin` is required but was not provided
65    #[display(fmt = "The HTTP request header `Origin` is required but was not provided")]
66    MissingOrigin,
67    /// The HTTP request header `Origin` could not be parsed correctly.
68    #[display(fmt = "The HTTP request header `Origin` could not be parsed correctly.")]
69    BadOrigin,
70    /// The request header `Access-Control-Request-Method` is required but is
71    /// missing
72    #[display(
73        fmt = "The request header `Access-Control-Request-Method` is required but is missing"
74    )]
75    MissingRequestMethod,
76    /// The request header `Access-Control-Request-Method` has an invalid value
77    #[display(fmt = "The request header `Access-Control-Request-Method` has an invalid value")]
78    BadRequestMethod,
79    /// The request header `Access-Control-Request-Headers`  has an invalid
80    /// value
81    #[display(
82        fmt = "The request header `Access-Control-Request-Headers`  has an invalid value"
83    )]
84    BadRequestHeaders,
85    /// Origin is not allowed to make this request
86    #[display(fmt = "Origin is not allowed to make this request")]
87    OriginNotAllowed,
88    /// Requested method is not allowed
89    #[display(fmt = "Requested method is not allowed")]
90    MethodNotAllowed,
91    /// One or more headers requested are not allowed
92    #[display(fmt = "One or more headers requested are not allowed")]
93    HeadersNotAllowed,
94}
95
96/// DefaultError renderer support
97impl WebResponseError<DefaultError> for CorsError {
98    fn status_code(&self) -> StatusCode {
99        StatusCode::BAD_REQUEST
100    }
101}
102
103/// An enum signifying that some of type T is allowed, or `All` (everything is
104/// allowed).
105///
106/// `Default` is implemented for this enum and is `All`.
107#[derive(Default, Clone, Debug, Eq, PartialEq)]
108pub enum AllOrSome<T> {
109    /// Everything is allowed. Usually equivalent to the "*" value.
110    #[default]
111    All,
112    /// Only some of `T` is allowed
113    Some(T),
114}
115
116impl<T> AllOrSome<T> {
117    /// Returns whether this is an `All` variant
118    pub fn is_all(&self) -> bool {
119        match *self {
120            AllOrSome::All => true,
121            AllOrSome::Some(_) => false,
122        }
123    }
124
125    /// Returns whether this is a `Some` variant
126    pub fn is_some(&self) -> bool {
127        !self.is_all()
128    }
129
130    /// Returns &T
131    pub fn as_ref(&self) -> Option<&T> {
132        match *self {
133            AllOrSome::All => None,
134            AllOrSome::Some(ref t) => Some(t),
135        }
136    }
137}
138
139/// Structure that follows the builder pattern for building `Cors` middleware
140/// structs.
141///
142/// To construct a cors:
143///
144///   1. Call [`Cors::build`](struct.Cors.html#method.build) to start building.
145///   2. Use any of the builder methods to set fields in the backend.
146/// 3. Call [finish](struct.Cors.html#method.finish) to retrieve the
147/// constructed backend.
148///
149/// # Example
150///
151/// ```rust
152/// use ntex_cors::Cors;
153/// use ntex::http::header;
154///
155/// let cors = Cors::new()
156///     .allowed_origin("https://www.rust-lang.org/")
157///     .allowed_methods(vec!["GET", "POST"])
158///     .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
159///     .allowed_header(header::CONTENT_TYPE)
160///     .max_age(3600);
161/// ```
162#[derive(Default)]
163pub struct Cors {
164    cors: Option<Inner>,
165    methods: bool,
166    expose_hdrs: HashSet<HeaderName>,
167    error: Option<HttpError>,
168}
169
170impl Cors {
171    /// Build a new CORS middleware instance
172    pub fn new() -> Self {
173        Cors {
174            cors: Some(Inner {
175                origins: AllOrSome::All,
176                origins_str: None,
177                methods: HashSet::new(),
178                headers: AllOrSome::All,
179                expose_hdrs: None,
180                max_age: None,
181                preflight: true,
182                send_wildcard: false,
183                supports_credentials: false,
184                vary_header: true,
185            }),
186            methods: false,
187            error: None,
188            expose_hdrs: HashSet::new(),
189        }
190    }
191
192    /// Build a new CORS default middleware
193    pub fn default<Err>() -> CorsFactory<Err> {
194        let inner = Inner {
195            origins: AllOrSome::default(),
196            origins_str: None,
197            methods: HashSet::from_iter(
198                vec![
199                    Method::GET,
200                    Method::HEAD,
201                    Method::POST,
202                    Method::OPTIONS,
203                    Method::PUT,
204                    Method::PATCH,
205                    Method::DELETE,
206                ]
207                .into_iter(),
208            ),
209            headers: AllOrSome::All,
210            expose_hdrs: None,
211            max_age: None,
212            preflight: true,
213            send_wildcard: false,
214            supports_credentials: false,
215            vary_header: true,
216        };
217        CorsFactory { inner: Rc::new(inner), _t: PhantomData }
218    }
219
220    /// Add an origin that are allowed to make requests.
221    /// Will be verified against the `Origin` request header.
222    ///
223    /// When `All` is set, and `send_wildcard` is set, "*" will be sent in
224    /// the `Access-Control-Allow-Origin` response header. Otherwise, the
225    /// client's `Origin` request header will be echoed back in the
226    /// `Access-Control-Allow-Origin` response header.
227    ///
228    /// When `Some` is set, the client's `Origin` request header will be
229    /// checked in a case-sensitive manner.
230    ///
231    /// This is the `list of origins` in the
232    /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
233    ///
234    /// Defaults to `All`.
235    ///
236    /// Builder panics if supplied origin is not valid uri.
237    pub fn allowed_origin(mut self, origin: &str) -> Self {
238        if let Some(cors) = cors(&mut self.cors, &self.error) {
239            match Uri::try_from(origin) {
240                Ok(_) => {
241                    // If the origin is "*", set the origins to `All`
242                    if origin.trim() == "*" {
243                        cors.origins = AllOrSome::All;
244                        return self;
245                    }
246                    if cors.origins.is_all() {
247                        cors.origins = AllOrSome::Some(HashSet::new());
248                    }
249                    if let AllOrSome::Some(ref mut origins) = cors.origins {
250                        origins.insert(origin.to_owned());
251                    }
252                }
253                Err(e) => {
254                    self.error = Some(e.into());
255                }
256            }
257        }
258        self
259    }
260
261    /// Set a list of methods which the allowed origins are allowed to access
262    /// for requests.
263    ///
264    /// This is the `list of methods` in the
265    /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
266    ///
267    /// You can use either `&str` (e.g. `"GET"`) or directly use the [`http::Method`] enum
268    /// (e.g. `http::Method::GET`). Using [`http::Method`] is recommended for better type safety
269    /// and to avoid runtime errors due to typos.
270    ///
271    /// # Example
272    /// ```rust,ignore
273    /// use ntex::http::Method;
274    ///
275    /// cors.allowed_methods(vec![Method::GET, Method::POST]);
276    /// ```
277    ///
278    /// The default is `[GET, HEAD, POST, OPTIONS, PUT, PATCH, DELETE]`.
279    pub fn allowed_methods<U, M>(mut self, methods: U) -> Self
280    where
281        U: IntoIterator<Item = M>,
282        Method: TryFrom<M>,
283        <Method as TryFrom<M>>::Error: Into<HttpError>,
284    {
285        self.methods = true;
286        if let Some(cors) = cors(&mut self.cors, &self.error) {
287            for m in methods {
288                match Method::try_from(m) {
289                    Ok(method) => {
290                        cors.methods.insert(method);
291                    }
292                    Err(e) => {
293                        self.error = Some(e.into());
294                        break;
295                    }
296                }
297            }
298        }
299        self
300    }
301
302    /// Set an allowed header
303    pub fn allowed_header<H>(mut self, header: H) -> Self
304    where
305        HeaderName: TryFrom<H>,
306        <HeaderName as TryFrom<H>>::Error: Into<HttpError>,
307    {
308        if let Some(cors) = cors(&mut self.cors, &self.error) {
309            match HeaderName::try_from(header) {
310                Ok(method) => {
311                    if cors.headers.is_all() {
312                        cors.headers = AllOrSome::Some(HashSet::new());
313                    }
314                    if let AllOrSome::Some(ref mut headers) = cors.headers {
315                        headers.insert(method);
316                    }
317                }
318                Err(e) => self.error = Some(e.into()),
319            }
320        }
321        self
322    }
323
324    /// Set a list of header field names which can be used when
325    /// this resource is accessed by allowed origins.
326    ///
327    /// If `All` is set, whatever is requested by the client in
328    /// `Access-Control-Request-Headers` will be echoed back in the
329    /// `Access-Control-Allow-Headers` header.
330    ///
331    /// This is the `list of headers` in the
332    /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
333    ///
334    /// Defaults to `All`.
335    pub fn allowed_headers<U, H>(mut self, headers: U) -> Self
336    where
337        U: IntoIterator<Item = H>,
338        HeaderName: TryFrom<H>,
339        <HeaderName as TryFrom<H>>::Error: Into<HttpError>,
340    {
341        if let Some(cors) = cors(&mut self.cors, &self.error) {
342            for h in headers {
343                match HeaderName::try_from(h) {
344                    Ok(method) => {
345                        if cors.headers.is_all() {
346                            cors.headers = AllOrSome::Some(HashSet::new());
347                        }
348                        if let AllOrSome::Some(ref mut headers) = cors.headers {
349                            headers.insert(method);
350                        }
351                    }
352                    Err(e) => {
353                        self.error = Some(e.into());
354                        break;
355                    }
356                }
357            }
358        }
359        self
360    }
361
362    /// Set a list of headers which are safe to expose to the API of a CORS API
363    /// specification. This corresponds to the
364    /// `Access-Control-Expose-Headers` response header.
365    ///
366    /// This is the `list of exposed headers` in the
367    /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
368    ///
369    /// This defaults to an empty set.
370    pub fn expose_headers<U, H>(mut self, headers: U) -> Self
371    where
372        U: IntoIterator<Item = H>,
373        HeaderName: TryFrom<H>,
374        <HeaderName as TryFrom<H>>::Error: Into<HttpError>,
375    {
376        for h in headers {
377            match HeaderName::try_from(h) {
378                Ok(method) => {
379                    self.expose_hdrs.insert(method);
380                }
381                Err(e) => {
382                    self.error = Some(e.into());
383                    break;
384                }
385            }
386        }
387        self
388    }
389
390    /// Set a maximum time for which this CORS request maybe cached.
391    /// This value is set as the `Access-Control-Max-Age` header.
392    ///
393    /// This defaults to `None` (unset).
394    pub fn max_age(mut self, max_age: usize) -> Self {
395        if let Some(cors) = cors(&mut self.cors, &self.error) {
396            cors.max_age = Some(max_age)
397        }
398        self
399    }
400
401    /// Set a wildcard origins
402    ///
403    /// If send wildcard is set and the `allowed_origins` parameter is `All`, a
404    /// wildcard `Access-Control-Allow-Origin` response header is sent,
405    /// rather than the request’s `Origin` header.
406    ///
407    /// This is the `supports credentials flag` in the
408    /// [Resource Processing Model](https://www.w3.org/TR/cors/#resource-processing-model).
409    ///
410    /// This **CANNOT** be used in conjunction with `allowed_origins` set to
411    /// `All` and `allow_credentials` set to `true`. Depending on the mode
412    /// of usage, this will either result in an `Error::
413    /// CredentialsWithWildcardOrigin` error during ntex launch or runtime.
414    ///
415    /// Defaults to `false`.
416    pub fn send_wildcard(mut self) -> Self {
417        if let Some(cors) = cors(&mut self.cors, &self.error) {
418            cors.send_wildcard = true
419        }
420        self
421    }
422
423    /// Allows users to make authenticated requests
424    ///
425    /// If true, injects the `Access-Control-Allow-Credentials` header in
426    /// responses. This allows cookies and credentials to be submitted
427    /// across domains.
428    ///
429    /// This option cannot be used in conjunction with an `allowed_origin` set
430    /// to `All` and `send_wildcards` set to `true`.
431    ///
432    /// Defaults to `false`.
433    ///
434    /// Builder panics if credentials are allowed, but the Origin is set to "*".
435    /// This is not allowed by W3C
436    pub fn supports_credentials(mut self) -> Self {
437        if let Some(cors) = cors(&mut self.cors, &self.error) {
438            cors.supports_credentials = true
439        }
440        self
441    }
442
443    /// Disable `Vary` header support.
444    ///
445    /// When enabled the header `Vary: Origin` will be returned as per the W3
446    /// implementation guidelines.
447    ///
448    /// Setting this header when the `Access-Control-Allow-Origin` is
449    /// dynamically generated (e.g. when there is more than one allowed
450    /// origin, and an Origin than '*' is returned) informs CDNs and other
451    /// caches that the CORS headers are dynamic, and cannot be cached.
452    ///
453    /// By default `vary` header support is enabled.
454    pub fn disable_vary_header(mut self) -> Self {
455        if let Some(cors) = cors(&mut self.cors, &self.error) {
456            cors.vary_header = false
457        }
458        self
459    }
460
461    /// Disable *preflight* request support.
462    ///
463    /// When enabled cors middleware automatically handles *OPTIONS* request.
464    /// This is useful application level middleware.
465    ///
466    /// By default *preflight* support is enabled.
467    pub fn disable_preflight(mut self) -> Self {
468        if let Some(cors) = cors(&mut self.cors, &self.error) {
469            cors.preflight = false
470        }
471        self
472    }
473
474    /// Construct cors middleware
475    pub fn finish<Err>(self) -> CorsFactory<Err> {
476        let mut slf = if !self.methods {
477            self.allowed_methods(vec![
478                Method::GET,
479                Method::HEAD,
480                Method::POST,
481                Method::OPTIONS,
482                Method::PUT,
483                Method::PATCH,
484                Method::DELETE,
485            ])
486        } else {
487            self
488        };
489
490        if let Some(e) = slf.error.take() {
491            panic!("{}", e);
492        }
493
494        let mut cors = slf.cors.take().expect("cannot reuse CorsBuilder");
495
496        if cors.supports_credentials && cors.send_wildcard && cors.origins.is_all() {
497            panic!("Credentials are allowed, but the Origin is set to \"*\"");
498        }
499
500        if let AllOrSome::Some(ref origins) = cors.origins {
501            let s = origins.iter().fold(String::new(), |s, v| format!("{}, {}", s, v));
502            cors.origins_str = Some(HeaderValue::try_from(&s[2..]).unwrap());
503        }
504
505        if !slf.expose_hdrs.is_empty() {
506            cors.expose_hdrs = Some(
507                HeaderValue::try_from(
508                    &slf.expose_hdrs
509                        .iter()
510                        .fold(String::new(), |s, v| format!("{}, {}", s, v.as_str()))[2..],
511                )
512                .unwrap(),
513            );
514        }
515
516        CorsFactory { inner: Rc::new(cors), _t: PhantomData }
517    }
518}
519
520fn cors<'a>(parts: &'a mut Option<Inner>, err: &Option<HttpError>) -> Option<&'a mut Inner> {
521    if err.is_some() {
522        return None;
523    }
524    parts.as_mut()
525}
526
527struct Inner {
528    methods: HashSet<Method>,
529    origins: AllOrSome<HashSet<String>>,
530    origins_str: Option<HeaderValue>,
531    headers: AllOrSome<HashSet<HeaderName>>,
532    expose_hdrs: Option<HeaderValue>,
533    max_age: Option<usize>,
534    preflight: bool,
535    send_wildcard: bool,
536    supports_credentials: bool,
537    vary_header: bool,
538}
539
540impl Inner {
541    fn validate_origin(&self, req: &RequestHead) -> Result<(), CorsError> {
542        if let Some(hdr) = req.headers().get(&header::ORIGIN) {
543            if let Ok(origin) = hdr.to_str() {
544                return match self.origins {
545                    AllOrSome::All => Ok(()),
546                    AllOrSome::Some(ref allowed_origins) => allowed_origins
547                        .get(origin)
548                        .map(|_| ())
549                        .ok_or(CorsError::OriginNotAllowed),
550                };
551            }
552            Err(CorsError::BadOrigin)
553        } else {
554            match self.origins {
555                AllOrSome::All => Ok(()),
556                _ => Err(CorsError::MissingOrigin),
557            }
558        }
559    }
560
561    fn access_control_allow_origin(&self, headers: &HeaderMap) -> Option<HeaderValue> {
562        match self.origins {
563            AllOrSome::All => {
564                if self.send_wildcard {
565                    Some(HeaderValue::from_static("*"))
566                } else {
567                    headers.get(&header::ORIGIN).cloned()
568                }
569            }
570            AllOrSome::Some(ref origins) => {
571                if let Some(origin) =
572                    headers.get(&header::ORIGIN).filter(|o| match o.to_str() {
573                        Ok(os) => origins.contains(os),
574                        _ => false,
575                    })
576                {
577                    Some(origin.clone())
578                } else {
579                    Some(self.origins_str.as_ref().unwrap().clone())
580                }
581            }
582        }
583    }
584
585    fn validate_allowed_method(&self, req: &RequestHead) -> Result<(), CorsError> {
586        if let Some(hdr) = req.headers().get(&header::ACCESS_CONTROL_REQUEST_METHOD) {
587            if let Ok(meth) = hdr.to_str() {
588                if let Ok(method) = Method::try_from(meth) {
589                    return self
590                        .methods
591                        .get(&method)
592                        .map(|_| ())
593                        .ok_or(CorsError::MethodNotAllowed);
594                }
595            }
596            Err(CorsError::BadRequestMethod)
597        } else {
598            Err(CorsError::MissingRequestMethod)
599        }
600    }
601
602    fn validate_allowed_headers(&self, req: &RequestHead) -> Result<(), CorsError> {
603        match self.headers {
604            AllOrSome::All => Ok(()),
605            AllOrSome::Some(ref allowed_headers) => {
606                if let Some(hdr) = req.headers().get(&header::ACCESS_CONTROL_REQUEST_HEADERS) {
607                    if let Ok(headers) = hdr.to_str() {
608                        let mut hdrs = HashSet::new();
609                        for hdr in headers.split(',') {
610                            match HeaderName::try_from(hdr.trim()) {
611                                Ok(hdr) => hdrs.insert(hdr),
612                                Err(_) => return Err(CorsError::BadRequestHeaders),
613                            };
614                        }
615                        // `Access-Control-Request-Headers` must contain 1 or more
616                        // `field-name`.
617                        if !hdrs.is_empty() {
618                            if !hdrs.is_subset(allowed_headers) {
619                                return Err(CorsError::HeadersNotAllowed);
620                            }
621                            return Ok(());
622                        }
623                    }
624                    Err(CorsError::BadRequestHeaders)
625                } else {
626                    Ok(())
627                }
628            }
629        }
630    }
631
632    fn preflight_check(
633        &self,
634        req: &RequestHead,
635    ) -> Result<Either<HttpResponse, ()>, CorsError> {
636        if self.preflight && Method::OPTIONS == req.method {
637            self.validate_origin(req)
638                .and_then(|_| self.validate_allowed_method(req))
639                .and_then(|_| self.validate_allowed_headers(req))?;
640
641            // allowed headers
642            let headers = if let Some(headers) = self.headers.as_ref() {
643                Some(
644                    HeaderValue::try_from(
645                        &headers
646                            .iter()
647                            .fold(String::new(), |s, v| s + "," + v.as_str())
648                            .as_str()[1..],
649                    )
650                    .unwrap(),
651                )
652            } else {
653                req.headers.get(&header::ACCESS_CONTROL_REQUEST_HEADERS).cloned()
654            };
655
656            let res = HttpResponse::Ok()
657                .if_some(self.max_age.as_ref(), |max_age, resp| {
658                    let _ = resp.header(
659                        header::ACCESS_CONTROL_MAX_AGE,
660                        format!("{}", max_age).as_str(),
661                    );
662                })
663                .if_some(headers, |headers, resp| {
664                    let _ = resp.header(header::ACCESS_CONTROL_ALLOW_HEADERS, headers);
665                })
666                .if_some(self.access_control_allow_origin(req.headers()), |origin, resp| {
667                    let _ = resp.header(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
668                })
669                .if_true(self.supports_credentials, |resp| {
670                    resp.header(header::ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
671                })
672                .header(
673                    header::ACCESS_CONTROL_ALLOW_METHODS,
674                    &self
675                        .methods
676                        .iter()
677                        .fold(String::new(), |s, v| s + "," + v.as_str())
678                        .as_str()[1..],
679                )
680                .finish()
681                .into_body();
682
683            Ok(Either::Left(res))
684        } else {
685            if req.headers.contains_key(&header::ORIGIN) {
686                // Only check requests with a origin header.
687                self.validate_origin(req)?;
688            }
689            Ok(Either::Right(()))
690        }
691    }
692
693    fn handle_response(&self, headers: &mut HeaderMap, allowed_origin: Option<HeaderValue>) {
694        if let Some(origin) = allowed_origin {
695            headers.insert(header::ACCESS_CONTROL_ALLOW_ORIGIN, origin);
696        };
697
698        if let Some(ref expose) = self.expose_hdrs {
699            headers.insert(header::ACCESS_CONTROL_EXPOSE_HEADERS, expose.clone());
700        }
701        if self.supports_credentials {
702            headers.insert(
703                header::ACCESS_CONTROL_ALLOW_CREDENTIALS,
704                HeaderValue::from_static("true"),
705            );
706        }
707        if self.vary_header {
708            let value = if let Some(hdr) = headers.get(&header::VARY) {
709                let mut val: Vec<u8> = Vec::with_capacity(hdr.as_bytes().len() + 8);
710                val.extend(hdr.as_bytes());
711                val.extend(b", Origin");
712                HeaderValue::try_from(&val[..]).unwrap()
713            } else {
714                HeaderValue::from_static("Origin")
715            };
716            headers.insert(header::VARY, value);
717        }
718    }
719}
720
721/// `Middleware` for Cross-origin resource sharing support
722///
723/// The Cors struct contains the settings for CORS requests to be validated and
724/// for responses to be generated.
725pub struct CorsFactory<Err> {
726    inner: Rc<Inner>,
727    _t: PhantomData<Err>,
728}
729
730impl<S, Err> Middleware<S> for CorsFactory<Err>
731where
732    S: Service<WebRequest<Err>, Response = WebResponse>,
733{
734    type Service = CorsMiddleware<S>;
735
736    fn create(&self, service: S) -> Self::Service {
737        CorsMiddleware { service, inner: self.inner.clone() }
738    }
739}
740
741/// `Middleware` for Cross-origin resource sharing support
742///
743/// The Cors struct contains the settings for CORS requests to be validated and
744/// for responses to be generated.
745#[derive(Clone)]
746pub struct CorsMiddleware<S> {
747    service: S,
748    inner: Rc<Inner>,
749}
750
751impl<S, Err> Service<WebRequest<Err>> for CorsMiddleware<S>
752where
753    S: Service<WebRequest<Err>, Response = WebResponse>,
754    Err: ErrorRenderer,
755    Err::Container: From<S::Error>,
756    CorsError: WebResponseError<Err>,
757{
758    type Response = WebResponse;
759    type Error = S::Error;
760
761    ntex::forward_ready!(service);
762    ntex::forward_shutdown!(service);
763
764    async fn call(
765        &self,
766        req: WebRequest<Err>,
767        ctx: ServiceCtx<'_, Self>,
768    ) -> Result<Self::Response, S::Error> {
769        match self.inner.preflight_check(req.head()) {
770            Ok(Either::Left(res)) => Ok(req.into_response(res)),
771            Ok(Either::Right(_)) => {
772                let inner = self.inner.clone();
773                let has_origin = req.headers().contains_key(&header::ORIGIN);
774                let allowed_origin = inner.access_control_allow_origin(req.headers());
775
776                let mut res = ctx.call(&self.service, req).await?;
777
778                if has_origin {
779                    inner.handle_response(res.headers_mut(), allowed_origin);
780                }
781                Ok(res)
782            }
783            Err(e) => Ok(req.render_error(e)),
784        }
785    }
786}
787
788#[cfg(test)]
789mod tests {
790    use ntex::service::{fn_service, Middleware, Pipeline};
791    use ntex::web::{self, test, test::TestRequest};
792
793    use super::*;
794
795    #[ntex::test]
796    #[should_panic(expected = "Credentials are allowed, but the Origin is set to")]
797    async fn cors_validates_illegal_allow_credentials() {
798        let _cors =
799            Cors::new().supports_credentials().send_wildcard().finish::<web::DefaultError>();
800    }
801
802    #[ntex::test]
803    async fn validate_origin_allows_all_origins() {
804        let cors = Cors::new().finish().create(test::ok_service()).into();
805        let req =
806            TestRequest::with_header("Origin", "https://www.example.com").to_srv_request();
807
808        let resp = test::call_service(&cors, req).await;
809        assert_eq!(resp.status(), StatusCode::OK);
810    }
811
812    #[ntex::test]
813    async fn default() {
814        let cors = Cors::default().create(test::ok_service()).into();
815        let req =
816            TestRequest::with_header("Origin", "https://www.example.com").to_srv_request();
817
818        let resp = test::call_service(&cors, req).await;
819        assert_eq!(resp.status(), StatusCode::OK);
820    }
821
822    #[ntex::test]
823    async fn test_preflight() {
824        let cors: Pipeline<_> = Cors::new()
825            .send_wildcard()
826            .max_age(3600)
827            .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
828            .allowed_headers(vec![header::AUTHORIZATION, header::ACCEPT])
829            .allowed_header(header::CONTENT_TYPE)
830            .finish()
831            .create(test::ok_service())
832            .into();
833
834        let req = TestRequest::with_header("Origin", "https://www.example.com")
835            .method(Method::OPTIONS)
836            .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "X-Not-Allowed")
837            .to_srv_request();
838
839        assert!(cors.get_ref().inner.validate_allowed_method(req.head()).is_err());
840        assert!(cors.get_ref().inner.validate_allowed_headers(req.head()).is_err());
841        let resp = test::call_service(&cors, req).await;
842        assert_eq!(resp.status(), StatusCode::BAD_REQUEST);
843
844        let req = TestRequest::with_header("Origin", "https://www.example.com")
845            .header(header::ACCESS_CONTROL_REQUEST_METHOD, "put")
846            .method(Method::OPTIONS)
847            .to_srv_request();
848
849        assert!(cors.get_ref().inner.validate_allowed_method(req.head()).is_err());
850        assert!(cors.get_ref().inner.validate_allowed_headers(req.head()).is_ok());
851
852        let req = TestRequest::with_header("Origin", "https://www.example.com")
853            .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
854            .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "AUTHORIZATION,ACCEPT")
855            .method(Method::OPTIONS)
856            .to_srv_request();
857
858        let resp = test::call_service(&cors, req).await;
859        assert_eq!(
860            &b"*"[..],
861            resp.headers().get(&header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()
862        );
863        assert_eq!(
864            &b"3600"[..],
865            resp.headers().get(&header::ACCESS_CONTROL_MAX_AGE).unwrap().as_bytes()
866        );
867        let hdr = resp
868            .headers()
869            .get(&header::ACCESS_CONTROL_ALLOW_HEADERS)
870            .unwrap()
871            .to_str()
872            .unwrap();
873        assert!(hdr.contains("authorization"));
874        assert!(hdr.contains("accept"));
875        assert!(hdr.contains("content-type"));
876
877        let methods =
878            resp.headers().get(header::ACCESS_CONTROL_ALLOW_METHODS).unwrap().to_str().unwrap();
879        assert!(methods.contains("POST"));
880        assert!(methods.contains("GET"));
881        assert!(methods.contains("OPTIONS"));
882
883        // Rc::get_mut(&mut cors.inner).unwrap().preflight = false;
884
885        // let req = TestRequest::with_header("Origin", "https://www.example.com")
886        //     .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
887        //     .header(header::ACCESS_CONTROL_REQUEST_HEADERS, "AUTHORIZATION,ACCEPT")
888        //     .method(Method::OPTIONS)
889        //     .to_srv_request();
890
891        // let resp = test::call_service(&cors, req).await;
892        // assert_eq!(resp.status(), StatusCode::OK);
893    }
894
895    // #[ntex::test]
896    // #[should_panic(expected = "MissingOrigin")]
897    // async fn test_validate_missing_origin() {
898    //    let cors = Cors::build()
899    //        .allowed_origin("https://www.example.com")
900    //        .finish();
901    //    let mut req = HttpRequest::default();
902    //    cors.start(&req).unwrap();
903    // }
904
905    #[ntex::test]
906    #[should_panic(expected = "OriginNotAllowed")]
907    async fn test_validate_not_allowed_origin() {
908        let cors: Pipeline<_> = Cors::new()
909            .allowed_origin("https://www.example.com")
910            .finish()
911            .create(test::ok_service::<web::DefaultError>())
912            .into();
913
914        let req = TestRequest::with_header("Origin", "https://www.unknown.com")
915            .method(Method::GET)
916            .to_srv_request();
917        cors.get_ref().inner.validate_origin(req.head()).unwrap();
918        cors.get_ref().inner.validate_allowed_method(req.head()).unwrap();
919        cors.get_ref().inner.validate_allowed_headers(req.head()).unwrap();
920    }
921
922    #[ntex::test]
923    async fn test_validate_origin() {
924        let cors = Cors::new()
925            .allowed_origin("https://www.example.com")
926            .finish()
927            .create(test::ok_service())
928            .into();
929
930        let req = TestRequest::with_header("Origin", "https://www.example.com")
931            .method(Method::GET)
932            .to_srv_request();
933
934        let resp = test::call_service(&cors, req).await;
935        assert_eq!(resp.status(), StatusCode::OK);
936    }
937
938    #[ntex::test]
939    async fn test_no_origin_response() {
940        let cors = Cors::new().disable_preflight().finish().create(test::ok_service()).into();
941
942        let req = TestRequest::default().method(Method::GET).to_srv_request();
943        let resp = test::call_service(&cors, req).await;
944        assert!(resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).is_none());
945
946        let req = TestRequest::with_header("Origin", "https://www.example.com")
947            .method(Method::OPTIONS)
948            .to_srv_request();
949        let resp = test::call_service(&cors, req).await;
950        assert_eq!(
951            &b"https://www.example.com"[..],
952            resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()
953        );
954    }
955
956    #[ntex::test]
957    async fn test_response() {
958        let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
959        let cors = Cors::new()
960            .send_wildcard()
961            .disable_preflight()
962            .max_age(3600)
963            .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
964            .allowed_headers(exposed_headers.clone())
965            .expose_headers(exposed_headers.clone())
966            .allowed_header(header::CONTENT_TYPE)
967            .finish()
968            .create(test::ok_service())
969            .into();
970
971        let req = TestRequest::with_header("Origin", "https://www.example.com")
972            .method(Method::OPTIONS)
973            .to_srv_request();
974
975        let resp = test::call_service(&cors, req).await;
976        assert_eq!(
977            &b"*"[..],
978            resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()
979        );
980        assert_eq!(&b"Origin"[..], resp.headers().get(header::VARY).unwrap().as_bytes());
981
982        {
983            let headers = resp
984                .headers()
985                .get(header::ACCESS_CONTROL_EXPOSE_HEADERS)
986                .unwrap()
987                .to_str()
988                .unwrap()
989                .split(',')
990                .map(|s| s.trim())
991                .collect::<Vec<&str>>();
992
993            for h in exposed_headers {
994                assert!(headers.contains(&h.as_str()));
995            }
996        }
997
998        let exposed_headers = vec![header::AUTHORIZATION, header::ACCEPT];
999        let cors =
1000            Cors::new()
1001                .send_wildcard()
1002                .disable_preflight()
1003                .max_age(3600)
1004                .allowed_methods(vec![Method::GET, Method::OPTIONS, Method::POST])
1005                .allowed_headers(exposed_headers.clone())
1006                .expose_headers(exposed_headers.clone())
1007                .allowed_header(header::CONTENT_TYPE)
1008                .finish()
1009                .create(fn_service(|req: WebRequest<DefaultError>| async move {
1010                    Ok::<_, std::convert::Infallible>(req.into_response(
1011                        HttpResponse::Ok().header(header::VARY, "Accept").finish(),
1012                    ))
1013                }))
1014                .into();
1015        let req = TestRequest::with_header("Origin", "https://www.example.com")
1016            .method(Method::OPTIONS)
1017            .to_srv_request();
1018        let resp = test::call_service(&cors, req).await;
1019        assert_eq!(
1020            &b"Accept, Origin"[..],
1021            resp.headers().get(header::VARY).unwrap().as_bytes()
1022        );
1023
1024        let cors = Cors::new()
1025            .disable_vary_header()
1026            .allowed_origin("https://www.example.com")
1027            .allowed_origin("https://www.google.com")
1028            .finish()
1029            .create(test::ok_service())
1030            .into();
1031
1032        let req = TestRequest::with_header("Origin", "https://www.example.com")
1033            .method(Method::OPTIONS)
1034            .header(header::ACCESS_CONTROL_REQUEST_METHOD, "POST")
1035            .to_srv_request();
1036        let resp = test::call_service(&cors, req).await;
1037
1038        let origins_str =
1039            resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().to_str().unwrap();
1040
1041        assert_eq!("https://www.example.com", origins_str);
1042    }
1043
1044    #[ntex::test]
1045    async fn test_multiple_origins() {
1046        let cors = Cors::new()
1047            .allowed_origin("https://example.com")
1048            .allowed_origin("https://example.org")
1049            .allowed_methods(vec![Method::GET])
1050            .finish()
1051            .create(test::ok_service())
1052            .into();
1053
1054        let req = TestRequest::with_header("Origin", "https://example.com")
1055            .method(Method::GET)
1056            .to_srv_request();
1057
1058        let resp = test::call_service(&cors, req).await;
1059        assert_eq!(
1060            &b"https://example.com"[..],
1061            resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()
1062        );
1063
1064        let req = TestRequest::with_header("Origin", "https://example.org")
1065            .method(Method::GET)
1066            .to_srv_request();
1067
1068        let resp = test::call_service(&cors, req).await;
1069        assert_eq!(
1070            &b"https://example.org"[..],
1071            resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()
1072        );
1073    }
1074
1075    #[ntex::test]
1076    async fn test_multiple_origins_preflight() {
1077        let cors = Cors::new()
1078            .allowed_origin("https://example.com")
1079            .allowed_origin("https://example.org")
1080            .allowed_methods(vec![Method::GET])
1081            .finish()
1082            .create(test::ok_service())
1083            .into();
1084
1085        let req = TestRequest::with_header("Origin", "https://example.com")
1086            .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
1087            .method(Method::OPTIONS)
1088            .to_srv_request();
1089
1090        let resp = test::call_service(&cors, req).await;
1091        assert_eq!(
1092            &b"https://example.com"[..],
1093            resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()
1094        );
1095
1096        let req = TestRequest::with_header("Origin", "https://example.org")
1097            .header(header::ACCESS_CONTROL_REQUEST_METHOD, "GET")
1098            .method(Method::OPTIONS)
1099            .to_srv_request();
1100
1101        let resp = test::call_service(&cors, req).await;
1102        assert_eq!(
1103            &b"https://example.org"[..],
1104            resp.headers().get(header::ACCESS_CONTROL_ALLOW_ORIGIN).unwrap().as_bytes()
1105        );
1106    }
1107
1108    #[ntex::test]
1109    async fn test_set_allowed_origin_to_all() {
1110        let cors = Cors::new().allowed_origin("*").finish().create(test::ok_service()).into();
1111
1112        let req = TestRequest::with_header("Origin", "https://www.example.com")
1113            .method(Method::GET)
1114            .to_srv_request();
1115
1116        let resp = test::call_service(&cors, req).await;
1117        assert_eq!(resp.status(), StatusCode::OK);
1118    }
1119}