From 016c3814129b160b8fdecb5ad9b0330c0f97c8f1 Mon Sep 17 00:00:00 2001 From: Aminu Oluwaseun Joshua Date: Sat, 9 May 2026 11:57:01 +0100 Subject: [PATCH 1/7] relaxed Vary header defaults Signed-off-by: Aminu Oluwaseun Joshua --- tower-http/src/cors/mod.rs | 19 +---------------- tower-http/src/cors/tests.rs | 41 ++++++++++++++++++++++++++++++------ tower-http/src/cors/vary.rs | 10 +-------- 3 files changed, 37 insertions(+), 33 deletions(-) diff --git a/tower-http/src/cors/mod.rs b/tower-http/src/cors/mod.rs index e7aca714..857fc18d 100644 --- a/tower-http/src/cors/mod.rs +++ b/tower-http/src/cors/mod.rs @@ -51,10 +51,7 @@ use allow_origin::AllowOriginFuture; use bytes::{BufMut, BytesMut}; -use http::{ - header::{self, HeaderName}, - HeaderMap, HeaderValue, Method, Request, Response, -}; +use http::{header, HeaderMap, HeaderValue, Method, Request, Response}; use pin_project_lite::pin_project; use std::{ future::Future, @@ -432,9 +429,6 @@ impl CorsLayer { /// Set the value(s) of the [`Vary`][mdn] header. /// - /// In contrast to the other headers, this one has a non-empty default of - /// [`preflight_request_headers()`]. - /// /// You only need to set this if you want to remove some of these defaults, /// or if you use a closure for one of the other headers and want to add a /// vary header accordingly. @@ -807,14 +801,3 @@ fn ensure_usable_cors_rules(layer: &CorsLayer) { ); } } - -/// Returns an iterator over the three request headers that may be involved in a CORS preflight request. -/// -/// This is the default set of header names returned in the `vary` header -pub fn preflight_request_headers() -> impl Iterator { - IntoIterator::into_iter([ - header::ORIGIN, - header::ACCESS_CONTROL_REQUEST_METHOD, - header::ACCESS_CONTROL_REQUEST_HEADERS, - ]) -} diff --git a/tower-http/src/cors/tests.rs b/tower-http/src/cors/tests.rs index 8f3f4acb..901d0e30 100644 --- a/tower-http/src/cors/tests.rs +++ b/tower-http/src/cors/tests.rs @@ -1,33 +1,62 @@ use std::convert::Infallible; -use crate::test_helpers::Body; -use http::{header, HeaderValue, Request, Response}; +use crate::{cors::Vary, test_helpers::Body}; +use http::{header, HeaderName, HeaderValue, Request, Response}; use tower::{service_fn, util::ServiceExt, Layer}; use crate::cors::{AllowOrigin, CorsLayer}; +const DEFAULT_VARY_HEADERS: HeaderValue = HeaderValue::from_static("accept, accept-encoding"); +const CUSTOM_VARY_HEADERS: [HeaderName; 3] = [ + header::ORIGIN, + header::ACCESS_CONTROL_REQUEST_METHOD, + header::ACCESS_CONTROL_REQUEST_HEADERS, +]; + #[tokio::test] #[allow( clippy::declare_interior_mutable_const, clippy::borrow_interior_mutable_const )] async fn vary_set_by_inner_service() { - const CUSTOM_VARY_HEADERS: HeaderValue = HeaderValue::from_static("accept, accept-encoding"); + async fn inner_svc(_: Request) -> Result, Infallible> { + Ok(Response::builder() + .header(header::VARY, DEFAULT_VARY_HEADERS) + .body(Body::empty()) + .unwrap()) + } + + let svc = CorsLayer::permissive().layer(service_fn(inner_svc)); + let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); + let mut vary_headers = res.headers().get_all(header::VARY).into_iter(); + assert_eq!(vary_headers.next(), Some(&DEFAULT_VARY_HEADERS)); + assert_eq!(vary_headers.next(), None); +} + +#[tokio::test] +#[allow( + clippy::declare_interior_mutable_const, + clippy::borrow_interior_mutable_const +)] +async fn include_custom_permissive_to_vary_set_by_inner_service() { const PERMISSIVE_CORS_VARY_HEADERS: HeaderValue = HeaderValue::from_static( "origin, access-control-request-method, access-control-request-headers", ); async fn inner_svc(_: Request) -> Result, Infallible> { Ok(Response::builder() - .header(header::VARY, CUSTOM_VARY_HEADERS) + .header(header::VARY, DEFAULT_VARY_HEADERS) .body(Body::empty()) .unwrap()) } - let svc = CorsLayer::permissive().layer(service_fn(inner_svc)); + let svc = CorsLayer::permissive() + .vary(Vary::list(CUSTOM_VARY_HEADERS)) + .layer(service_fn(inner_svc)); + let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); let mut vary_headers = res.headers().get_all(header::VARY).into_iter(); - assert_eq!(vary_headers.next(), Some(&CUSTOM_VARY_HEADERS)); + assert_eq!(vary_headers.next(), Some(&DEFAULT_VARY_HEADERS)); assert_eq!(vary_headers.next(), Some(&PERMISSIVE_CORS_VARY_HEADERS)); assert_eq!(vary_headers.next(), None); } diff --git a/tower-http/src/cors/vary.rs b/tower-http/src/cors/vary.rs index 3ebe4a27..742f6b60 100644 --- a/tower-http/src/cors/vary.rs +++ b/tower-http/src/cors/vary.rs @@ -1,14 +1,12 @@ use http::header::{self, HeaderName, HeaderValue}; -use super::preflight_request_headers; - /// Holds configuration for how to set the [`Vary`][mdn] header. /// /// See [`CorsLayer::vary`] for more details. /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary /// [`CorsLayer::vary`]: super::CorsLayer::vary -#[derive(Clone, Debug)] +#[derive(Clone, Debug, Default)] pub struct Vary(Vec); impl Vary { @@ -38,12 +36,6 @@ impl Vary { } } -impl Default for Vary { - fn default() -> Self { - Self::list(preflight_request_headers()) - } -} - impl From<[HeaderName; N]> for Vary { fn from(arr: [HeaderName; N]) -> Self { Self::list(arr) From 4c8930fd755e0ef1031b7a8f033e2bcd7c30a869 Mon Sep 17 00:00:00 2001 From: Aminu Oluwaseun Joshua Date: Sat, 9 May 2026 12:09:23 +0100 Subject: [PATCH 2/7] refactor: renaming const Signed-off-by: Aminu Oluwaseun Joshua --- tower-http/src/cors/tests.rs | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/tower-http/src/cors/tests.rs b/tower-http/src/cors/tests.rs index 901d0e30..05ba563a 100644 --- a/tower-http/src/cors/tests.rs +++ b/tower-http/src/cors/tests.rs @@ -6,8 +6,8 @@ use tower::{service_fn, util::ServiceExt, Layer}; use crate::cors::{AllowOrigin, CorsLayer}; -const DEFAULT_VARY_HEADERS: HeaderValue = HeaderValue::from_static("accept, accept-encoding"); -const CUSTOM_VARY_HEADERS: [HeaderName; 3] = [ +const INITIAL_VARY_HEADERS: HeaderValue = HeaderValue::from_static("accept, accept-encoding"); +const ADDITIONAL_VARY_HEADERS: [HeaderName; 3] = [ header::ORIGIN, header::ACCESS_CONTROL_REQUEST_METHOD, header::ACCESS_CONTROL_REQUEST_HEADERS, @@ -21,7 +21,7 @@ const CUSTOM_VARY_HEADERS: [HeaderName; 3] = [ async fn vary_set_by_inner_service() { async fn inner_svc(_: Request) -> Result, Infallible> { Ok(Response::builder() - .header(header::VARY, DEFAULT_VARY_HEADERS) + .header(header::VARY, INITIAL_VARY_HEADERS) .body(Body::empty()) .unwrap()) } @@ -29,7 +29,7 @@ async fn vary_set_by_inner_service() { let svc = CorsLayer::permissive().layer(service_fn(inner_svc)); let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); let mut vary_headers = res.headers().get_all(header::VARY).into_iter(); - assert_eq!(vary_headers.next(), Some(&DEFAULT_VARY_HEADERS)); + assert_eq!(vary_headers.next(), Some(&INITIAL_VARY_HEADERS)); assert_eq!(vary_headers.next(), None); } @@ -45,18 +45,18 @@ async fn include_custom_permissive_to_vary_set_by_inner_service() { async fn inner_svc(_: Request) -> Result, Infallible> { Ok(Response::builder() - .header(header::VARY, DEFAULT_VARY_HEADERS) + .header(header::VARY, INITIAL_VARY_HEADERS) .body(Body::empty()) .unwrap()) } let svc = CorsLayer::permissive() - .vary(Vary::list(CUSTOM_VARY_HEADERS)) + .vary(Vary::list(ADDITIONAL_VARY_HEADERS)) .layer(service_fn(inner_svc)); let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); let mut vary_headers = res.headers().get_all(header::VARY).into_iter(); - assert_eq!(vary_headers.next(), Some(&DEFAULT_VARY_HEADERS)); + assert_eq!(vary_headers.next(), Some(&INITIAL_VARY_HEADERS)); assert_eq!(vary_headers.next(), Some(&PERMISSIVE_CORS_VARY_HEADERS)); assert_eq!(vary_headers.next(), None); } From 6b5eaea18f2d6a65986f7c2f6065386c6daff788 Mon Sep 17 00:00:00 2001 From: Aminu Oluwaseun Joshua Date: Wed, 13 May 2026 22:23:20 +0100 Subject: [PATCH 3/7] Update permissive to omit the Vary header on some conditions Signed-off-by: Aminu Oluwaseun Joshua --- tower-http/src/cors/mod.rs | 42 +++++++++++++++++++++-- tower-http/src/cors/tests.rs | 64 +++++++++++++++++++++++++++++------- tower-http/src/cors/vary.rs | 20 ++++++++++- 3 files changed, 110 insertions(+), 16 deletions(-) diff --git a/tower-http/src/cors/mod.rs b/tower-http/src/cors/mod.rs index 857fc18d..39d1ef03 100644 --- a/tower-http/src/cors/mod.rs +++ b/tower-http/src/cors/mod.rs @@ -51,7 +51,7 @@ use allow_origin::AllowOriginFuture; use bytes::{BufMut, BytesMut}; -use http::{header, HeaderMap, HeaderValue, Method, Request, Response}; +use http::{header, HeaderMap, HeaderName, HeaderValue, Method, Request, Response}; use pin_project_lite::pin_project; use std::{ future::Future, @@ -129,11 +129,33 @@ impl CorsLayer { /// - All origins allowed. /// - All headers exposed. pub fn permissive() -> Self { - Self::new() + let mut layer = Self::new() .allow_headers(Any) .allow_methods(Any) .allow_origin(Any) - .expose_headers(Any) + .expose_headers(Any); + + let mut vary = Vary::default(); + + if layer.allow_origin.is_wildcard() { + vary = vary.without_header(header::ORIGIN); + } + + if layer.allow_headers.is_wildcard() { + vary = vary.without_header(header::ACCESS_CONTROL_REQUEST_HEADERS); + } + + if layer.allow_methods.is_wildcard() { + vary = vary.without_header(header::ACCESS_CONTROL_REQUEST_METHOD); + } + + layer.vary = if vary.is_empty() { + Vary::list([]) + } else { + vary + }; + + layer } /// A very permissive configuration: @@ -429,6 +451,9 @@ impl CorsLayer { /// Set the value(s) of the [`Vary`][mdn] header. /// + /// In contrast to the other headers, this one has a non-empty default of + /// [`preflight_request_headers()`]. + /// /// You only need to set this if you want to remove some of these defaults, /// or if you use a closure for one of the other headers and want to add a /// vary header accordingly. @@ -801,3 +826,14 @@ fn ensure_usable_cors_rules(layer: &CorsLayer) { ); } } + +/// Returns an iterator over the three request headers that may be involved in a CORS preflight request. +/// +/// This is the default set of header names returned in the `vary` header +pub fn preflight_request_headers() -> impl Iterator { + IntoIterator::into_iter([ + header::ORIGIN, + header::ACCESS_CONTROL_REQUEST_METHOD, + header::ACCESS_CONTROL_REQUEST_HEADERS, + ]) +} diff --git a/tower-http/src/cors/tests.rs b/tower-http/src/cors/tests.rs index 05ba563a..f20030e8 100644 --- a/tower-http/src/cors/tests.rs +++ b/tower-http/src/cors/tests.rs @@ -18,19 +18,18 @@ const ADDITIONAL_VARY_HEADERS: [HeaderName; 3] = [ clippy::declare_interior_mutable_const, clippy::borrow_interior_mutable_const )] -async fn vary_set_by_inner_service() { - async fn inner_svc(_: Request) -> Result, Infallible> { - Ok(Response::builder() - .header(header::VARY, INITIAL_VARY_HEADERS) - .body(Body::empty()) - .unwrap()) - } +async fn permissive_vary_header_is_empty() { + let svc = CorsLayer::permissive().layer(service_fn(|_: Request| async { + Ok::<_, Infallible>(Response::new(Body::empty())) + })); - let svc = CorsLayer::permissive().layer(service_fn(inner_svc)); - let res = svc.oneshot(Request::new(Body::empty())).await.unwrap(); - let mut vary_headers = res.headers().get_all(header::VARY).into_iter(); - assert_eq!(vary_headers.next(), Some(&INITIAL_VARY_HEADERS)); - assert_eq!(vary_headers.next(), None); + let req = Request::builder().body(Body::empty()).unwrap(); + + let res = svc.oneshot(req).await.unwrap(); + assert!( + res.headers().get(header::VARY).is_none(), + "Vary header should be omitted for permissive config" + ); } #[tokio::test] @@ -61,6 +60,47 @@ async fn include_custom_permissive_to_vary_set_by_inner_service() { assert_eq!(vary_headers.next(), None); } +#[tokio::test] +async fn permissive_with_custom_vary_builder() { + let custom_vary = HeaderValue::from_static("x-foo"); + let svc = CorsLayer::permissive() + .vary(Vary::list([header::HeaderName::from_static("x-foo")])) + .layer(service_fn(|_: Request| async { + Ok::<_, Infallible>(Response::new(Body::empty())) + })); + + let req = Request::builder().body(Body::empty()).unwrap(); + let res = svc.oneshot(req).await.unwrap(); + let vary = res.headers().get(header::VARY); + assert_eq!(vary, Some(&custom_vary)); +} + +#[tokio::test] +async fn permissive_with_inner_and_builder_vary() { + let custom_vary = HeaderValue::from_static("x-foo"); + let inner_vary = HeaderValue::from_static("accept-encoding"); + let svc = CorsLayer::permissive() + .vary(Vary::list([header::HeaderName::from_static("x-foo")])) + .layer(service_fn(|_: Request| { + let inner_vary = inner_vary.clone(); + async move { + Ok::<_, Infallible>( + Response::builder() + .header(header::VARY, inner_vary) + .body(Body::empty()) + .unwrap(), + ) + } + })); + + let req = Request::builder().body(Body::empty()).unwrap(); + let res = svc.oneshot(req).await.unwrap(); + let mut vary_headers = res.headers().get_all(header::VARY).iter(); + assert_eq!(vary_headers.next(), Some(&inner_vary)); + assert_eq!(vary_headers.next(), Some(&custom_vary)); + assert_eq!(vary_headers.next(), None); +} + #[tokio::test] async fn test_allow_origin_async_predicate() { #[derive(Clone)] diff --git a/tower-http/src/cors/vary.rs b/tower-http/src/cors/vary.rs index 742f6b60..84d11cdf 100644 --- a/tower-http/src/cors/vary.rs +++ b/tower-http/src/cors/vary.rs @@ -1,12 +1,14 @@ use http::header::{self, HeaderName, HeaderValue}; +use crate::cors::preflight_request_headers; + /// Holds configuration for how to set the [`Vary`][mdn] header. /// /// See [`CorsLayer::vary`] for more details. /// /// [mdn]: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Vary /// [`CorsLayer::vary`]: super::CorsLayer::vary -#[derive(Clone, Debug, Default)] +#[derive(Clone, Debug)] pub struct Vary(Vec); impl Vary { @@ -34,6 +36,22 @@ impl Vary { .expect("comma-separated list of HeaderValues is always a valid HeaderValue"); Some((header::VARY, header_val)) } + + pub(super) fn without_header(mut self, header: HeaderName) -> Self { + self.0 + .retain(|h| h != &HeaderValue::from_str(header.as_str()).unwrap()); + self + } + + pub(super) fn is_empty(&self) -> bool { + self.0.is_empty() + } +} + +impl Default for Vary { + fn default() -> Self { + Self::list(preflight_request_headers()) + } } impl From<[HeaderName; N]> for Vary { From bddf7a363ba53d408573f8395b38300c6e2c4358 Mon Sep 17 00:00:00 2001 From: Aminu Oluwaseun Joshua Date: Wed, 13 May 2026 23:31:10 +0100 Subject: [PATCH 4/7] cleaner header removal handling Signed-off-by: Aminu Oluwaseun Joshua --- tower-http/src/cors/vary.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tower-http/src/cors/vary.rs b/tower-http/src/cors/vary.rs index 84d11cdf..d4c85a75 100644 --- a/tower-http/src/cors/vary.rs +++ b/tower-http/src/cors/vary.rs @@ -38,8 +38,8 @@ impl Vary { } pub(super) fn without_header(mut self, header: HeaderName) -> Self { - self.0 - .retain(|h| h != &HeaderValue::from_str(header.as_str()).unwrap()); + let value: HeaderValue = header.into(); + self.0.retain(|h| h != value); self } From 162198b6125667813dcd08714b38b4851c07a2b9 Mon Sep 17 00:00:00 2001 From: Aminu Oluwaseun Joshua Date: Thu, 14 May 2026 15:34:49 +0100 Subject: [PATCH 5/7] move clear up into allow* Signed-off-by: Aminu Oluwaseun Joshua --- tower-http/src/cors/mod.rs | 85 ++++++++++++++++++++++++++----------- tower-http/src/cors/vary.rs | 4 -- 2 files changed, 61 insertions(+), 28 deletions(-) diff --git a/tower-http/src/cors/mod.rs b/tower-http/src/cors/mod.rs index 39d1ef03..25ffbeba 100644 --- a/tower-http/src/cors/mod.rs +++ b/tower-http/src/cors/mod.rs @@ -96,6 +96,7 @@ pub struct CorsLayer { expose_headers: ExposeHeaders, max_age: MaxAge, vary: Vary, + is_vary_custom: bool, } #[allow(clippy::declare_interior_mutable_const)] @@ -119,6 +120,7 @@ impl CorsLayer { expose_headers: Default::default(), max_age: Default::default(), vary: Default::default(), + is_vary_custom: false, } } @@ -129,33 +131,11 @@ impl CorsLayer { /// - All origins allowed. /// - All headers exposed. pub fn permissive() -> Self { - let mut layer = Self::new() + Self::new() .allow_headers(Any) .allow_methods(Any) .allow_origin(Any) - .expose_headers(Any); - - let mut vary = Vary::default(); - - if layer.allow_origin.is_wildcard() { - vary = vary.without_header(header::ORIGIN); - } - - if layer.allow_headers.is_wildcard() { - vary = vary.without_header(header::ACCESS_CONTROL_REQUEST_HEADERS); - } - - if layer.allow_methods.is_wildcard() { - vary = vary.without_header(header::ACCESS_CONTROL_REQUEST_METHOD); - } - - layer.vary = if vary.is_empty() { - Vary::list([]) - } else { - vary - }; - - layer + .expose_headers(Any) } /// A very permissive configuration: @@ -221,6 +201,26 @@ impl CorsLayer { T: Into, { self.allow_headers = headers.into(); + if !self.is_vary_custom { + if self.allow_headers.is_wildcard() { + self.vary = self + .vary + .without_header(header::ACCESS_CONTROL_REQUEST_HEADERS); + } else { + // Ensure header is present + let mut vary = self.vary.clone(); + let name = header::ACCESS_CONTROL_REQUEST_HEADERS; + if !vary.to_header().map_or(false, |(_, v)| { + v.to_str() + .unwrap_or("") + .split(',') + .any(|s| s.trim().eq_ignore_ascii_case(name.as_str())) + }) { + vary = Vary::list([name]); + } + self.vary = vary; + } + } self } @@ -297,6 +297,25 @@ impl CorsLayer { T: Into, { self.allow_methods = methods.into(); + if !self.is_vary_custom { + if self.allow_methods.is_wildcard() { + self.vary = self + .vary + .without_header(header::ACCESS_CONTROL_REQUEST_METHOD); + } else { + let mut vary = self.vary.clone(); + let name = header::ACCESS_CONTROL_REQUEST_METHOD; + if !vary.to_header().map_or(false, |(_, v)| { + v.to_str() + .unwrap_or("") + .split(',') + .any(|s| s.trim().eq_ignore_ascii_case(name.as_str())) + }) { + vary = Vary::list([name]); + } + self.vary = vary; + } + } self } @@ -400,6 +419,23 @@ impl CorsLayer { T: Into, { self.allow_origin = origin.into(); + if !self.is_vary_custom { + if self.allow_origin.is_wildcard() { + self.vary = self.vary.without_header(header::ORIGIN); + } else { + let mut vary = self.vary.clone(); + let name = header::ORIGIN; + if !vary.to_header().map_or(false, |(_, v)| { + v.to_str() + .unwrap_or("") + .split(',') + .any(|s| s.trim().eq_ignore_ascii_case(name.as_str())) + }) { + vary = Vary::list([name]); + } + self.vary = vary; + } + } self } @@ -464,6 +500,7 @@ impl CorsLayer { T: Into, { self.vary = headers.into(); + self.is_vary_custom = true; self } } diff --git a/tower-http/src/cors/vary.rs b/tower-http/src/cors/vary.rs index d4c85a75..ace284dc 100644 --- a/tower-http/src/cors/vary.rs +++ b/tower-http/src/cors/vary.rs @@ -42,10 +42,6 @@ impl Vary { self.0.retain(|h| h != value); self } - - pub(super) fn is_empty(&self) -> bool { - self.0.is_empty() - } } impl Default for Vary { From 1a1e08275f719dff8d8024fd3cacd4b7329cee8b Mon Sep 17 00:00:00 2001 From: Aminu Oluwaseun Joshua Date: Sat, 16 May 2026 04:00:52 +0100 Subject: [PATCH 6/7] improved maintainability Signed-off-by: Aminu Oluwaseun Joshua --- tower-http/src/cors/mod.rs | 107 +++++++++++++++++------------------- tower-http/src/cors/vary.rs | 6 -- 2 files changed, 50 insertions(+), 63 deletions(-) diff --git a/tower-http/src/cors/mod.rs b/tower-http/src/cors/mod.rs index 25ffbeba..9718f7ae 100644 --- a/tower-http/src/cors/mod.rs +++ b/tower-http/src/cors/mod.rs @@ -201,26 +201,6 @@ impl CorsLayer { T: Into, { self.allow_headers = headers.into(); - if !self.is_vary_custom { - if self.allow_headers.is_wildcard() { - self.vary = self - .vary - .without_header(header::ACCESS_CONTROL_REQUEST_HEADERS); - } else { - // Ensure header is present - let mut vary = self.vary.clone(); - let name = header::ACCESS_CONTROL_REQUEST_HEADERS; - if !vary.to_header().map_or(false, |(_, v)| { - v.to_str() - .unwrap_or("") - .split(',') - .any(|s| s.trim().eq_ignore_ascii_case(name.as_str())) - }) { - vary = Vary::list([name]); - } - self.vary = vary; - } - } self } @@ -297,25 +277,6 @@ impl CorsLayer { T: Into, { self.allow_methods = methods.into(); - if !self.is_vary_custom { - if self.allow_methods.is_wildcard() { - self.vary = self - .vary - .without_header(header::ACCESS_CONTROL_REQUEST_METHOD); - } else { - let mut vary = self.vary.clone(); - let name = header::ACCESS_CONTROL_REQUEST_METHOD; - if !vary.to_header().map_or(false, |(_, v)| { - v.to_str() - .unwrap_or("") - .split(',') - .any(|s| s.trim().eq_ignore_ascii_case(name.as_str())) - }) { - vary = Vary::list([name]); - } - self.vary = vary; - } - } self } @@ -419,23 +380,6 @@ impl CorsLayer { T: Into, { self.allow_origin = origin.into(); - if !self.is_vary_custom { - if self.allow_origin.is_wildcard() { - self.vary = self.vary.without_header(header::ORIGIN); - } else { - let mut vary = self.vary.clone(); - let name = header::ORIGIN; - if !vary.to_header().map_or(false, |(_, v)| { - v.to_str() - .unwrap_or("") - .split(',') - .any(|s| s.trim().eq_ignore_ascii_case(name.as_str())) - }) { - vary = Vary::list([name]); - } - self.vary = vary; - } - } self } @@ -549,9 +493,36 @@ impl Layer for CorsLayer { fn layer(&self, inner: S) -> Self::Service { ensure_usable_cors_rules(self); + // Clone the layer to modify Vary header logic + let mut layer = self.clone(); + + // Only set Vary if not custom + if !layer.is_vary_custom { + // If all origins, methods, and headers are allowed, omit Vary + let all_origins = layer.allow_origin.is_wildcard(); + let all_methods = layer.allow_methods.is_wildcard(); + let all_headers = layer.allow_headers.is_wildcard(); + if all_origins && all_methods && all_headers { + layer.vary = Vary::list([]); + } else { + // Otherwise, set Vary to the appropriate headers + let mut vary_headers = Vec::new(); + if !all_origins { + vary_headers.push(header::ORIGIN); + } + if !all_methods { + vary_headers.push(header::ACCESS_CONTROL_REQUEST_METHOD); + } + if !all_headers { + vary_headers.push(header::ACCESS_CONTROL_REQUEST_HEADERS); + } + layer.vary = Vary::list(vary_headers); + } + } + Cors { inner, - layer: self.clone(), + layer, } } } @@ -697,6 +668,28 @@ impl Cors { F: FnOnce(CorsLayer) -> CorsLayer, { self.layer = f(self.layer); + + // Centralize Vary header logic here as well + if !self.layer.is_vary_custom { + let all_origins = self.layer.allow_origin.is_wildcard(); + let all_methods = self.layer.allow_methods.is_wildcard(); + let all_headers = self.layer.allow_headers.is_wildcard(); + if all_origins && all_methods && all_headers { + self.layer.vary = Vary::list([]); + } else { + let mut vary_headers = Vec::new(); + if !all_origins { + vary_headers.push(header::ORIGIN); + } + if !all_methods { + vary_headers.push(header::ACCESS_CONTROL_REQUEST_METHOD); + } + if !all_headers { + vary_headers.push(header::ACCESS_CONTROL_REQUEST_HEADERS); + } + self.layer.vary = Vary::list(vary_headers); + } + } self } } diff --git a/tower-http/src/cors/vary.rs b/tower-http/src/cors/vary.rs index ace284dc..21a3486d 100644 --- a/tower-http/src/cors/vary.rs +++ b/tower-http/src/cors/vary.rs @@ -36,12 +36,6 @@ impl Vary { .expect("comma-separated list of HeaderValues is always a valid HeaderValue"); Some((header::VARY, header_val)) } - - pub(super) fn without_header(mut self, header: HeaderName) -> Self { - let value: HeaderValue = header.into(); - self.0.retain(|h| h != value); - self - } } impl Default for Vary { From 47242bdba1268d2309895523c5d190b92e3384c2 Mon Sep 17 00:00:00 2001 From: Aminu Oluwaseun Joshua Date: Sat, 16 May 2026 09:43:32 +0100 Subject: [PATCH 7/7] fix fmt fail Signed-off-by: Aminu Oluwaseun Joshua --- tower-http/src/cors/mod.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/tower-http/src/cors/mod.rs b/tower-http/src/cors/mod.rs index 9718f7ae..fdc1a409 100644 --- a/tower-http/src/cors/mod.rs +++ b/tower-http/src/cors/mod.rs @@ -520,10 +520,7 @@ impl Layer for CorsLayer { } } - Cors { - inner, - layer, - } + Cors { inner, layer } } }