1#![allow(
2 clippy::borrow_interior_mutable_const,
3 clippy::type_complexity,
4 clippy::mutable_key_type
5)]
6use 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#[derive(Debug, Display)]
63pub enum CorsError {
64 #[display(fmt = "The HTTP request header `Origin` is required but was not provided")]
66 MissingOrigin,
67 #[display(fmt = "The HTTP request header `Origin` could not be parsed correctly.")]
69 BadOrigin,
70 #[display(
73 fmt = "The request header `Access-Control-Request-Method` is required but is missing"
74 )]
75 MissingRequestMethod,
76 #[display(fmt = "The request header `Access-Control-Request-Method` has an invalid value")]
78 BadRequestMethod,
79 #[display(
82 fmt = "The request header `Access-Control-Request-Headers` has an invalid value"
83 )]
84 BadRequestHeaders,
85 #[display(fmt = "Origin is not allowed to make this request")]
87 OriginNotAllowed,
88 #[display(fmt = "Requested method is not allowed")]
90 MethodNotAllowed,
91 #[display(fmt = "One or more headers requested are not allowed")]
93 HeadersNotAllowed,
94}
95
96impl WebResponseError<DefaultError> for CorsError {
98 fn status_code(&self) -> StatusCode {
99 StatusCode::BAD_REQUEST
100 }
101}
102
103#[derive(Default, Clone, Debug, Eq, PartialEq)]
108pub enum AllOrSome<T> {
109 #[default]
111 All,
112 Some(T),
114}
115
116impl<T> AllOrSome<T> {
117 pub fn is_all(&self) -> bool {
119 match *self {
120 AllOrSome::All => true,
121 AllOrSome::Some(_) => false,
122 }
123 }
124
125 pub fn is_some(&self) -> bool {
127 !self.is_all()
128 }
129
130 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#[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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
721pub 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#[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 }
894
895 #[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}