From bfe6ece45bc8c4f9503fffd40d7d68117a058faa Mon Sep 17 00:00:00 2001 From: Sergio Benitez Date: Fri, 5 May 2023 17:43:33 -0700 Subject: [PATCH] Clean up and complete PR. --- core/http/src/header/proxy_proto.rs | 22 ++-- core/lib/src/config/config.rs | 10 +- core/lib/src/cookies.rs | 90 +++++++------ core/lib/src/local/asynchronous/response.rs | 5 +- core/lib/src/local/client.rs | 3 +- core/lib/src/request/from_request.rs | 10 +- core/lib/src/request/request.rs | 139 ++++++++++++-------- core/lib/tests/config-proxy-proto-header.rs | 46 +++---- site/guide/9-configuration.md | 9 +- 9 files changed, 184 insertions(+), 150 deletions(-) diff --git a/core/http/src/header/proxy_proto.rs b/core/http/src/header/proxy_proto.rs index 7c3e19e74d..8866b92187 100644 --- a/core/http/src/header/proxy_proto.rs +++ b/core/http/src/header/proxy_proto.rs @@ -1,23 +1,25 @@ use std::fmt; +use uncased::{UncasedStr, AsUncased}; + /// A protocol used to identify a specific protocol forwarded by an HTTP proxy. -// Names are case-insensitive +/// Value are case-insensitive. #[derive(Clone, Debug, Eq, PartialEq)] pub enum ProxyProto<'a> { - /// `http` value, Hypertext Transfer Protocol + /// `http` value, Hypertext Transfer Protocol. Http, - /// `https` value, Hypertext Transfer Protocol Secure + /// `https` value, Hypertext Transfer Protocol Secure. Https, - /// Any other protocol name not known to us - Unknown(&'a uncased::UncasedStr), + /// Any protocol name other than `http` or `https`. + Unknown(&'a UncasedStr), } impl<'a> From<&'a str> for ProxyProto<'a> { - fn from(s: &'a str) -> ProxyProto<'a> { - match s.to_lowercase().as_str() { - "http" => ProxyProto::Http, - "https" => ProxyProto::Https, - _ => ProxyProto::Unknown(s.into()), + fn from(value: &'a str) -> ProxyProto<'a> { + match value.as_uncased() { + v if v == "http" => ProxyProto::Http, + v if v == "https" => ProxyProto::Https, + v => ProxyProto::Unknown(v) } } } diff --git a/core/lib/src/config/config.rs b/core/lib/src/config/config.rs index 5b57163d7e..ee48f82d75 100644 --- a/core/lib/src/config/config.rs +++ b/core/lib/src/config/config.rs @@ -534,6 +534,9 @@ impl Config { /// The stringy parameter name for setting/extracting [`Config::ip_header`]. pub const IP_HEADER: &'static str = "ip_header"; + /// The stringy parameter name for setting/extracting [`Config::proxy_proto_header`]. + pub const PROXY_PROTO_HEADER: &'static str = "proxy_proto_header"; + /// The stringy parameter name for setting/extracting [`Config::limits`]. pub const LIMITS: &'static str = "limits"; @@ -557,10 +560,9 @@ impl Config { /// An array of all of the stringy parameter names. pub const PARAMETERS: &'static [&'static str] = &[ - Self::ADDRESS, Self::PORT, Self::WORKERS, Self::MAX_BLOCKING, - Self::KEEP_ALIVE, Self::IDENT, Self::IP_HEADER, Self::LIMITS, Self::TLS, - Self::SECRET_KEY, Self::TEMP_DIR, Self::LOG_LEVEL, Self::SHUTDOWN, - Self::CLI_COLORS, + Self::ADDRESS, Self::PORT, Self::WORKERS, Self::MAX_BLOCKING, Self::KEEP_ALIVE, + Self::IDENT, Self::IP_HEADER, Self::PROXY_PROTO_HEADER, Self::LIMITS, Self::TLS, + Self::SECRET_KEY, Self::TEMP_DIR, Self::LOG_LEVEL, Self::SHUTDOWN, Self::CLI_COLORS, ]; } diff --git a/core/lib/src/cookies.rs b/core/lib/src/cookies.rs index 419e248c28..62957e05ba 100644 --- a/core/lib/src/cookies.rs +++ b/core/lib/src/cookies.rs @@ -2,7 +2,6 @@ use std::fmt; use parking_lot::Mutex; -use crate::Config; use crate::http::private::cookie; #[doc(inline)] @@ -154,19 +153,14 @@ pub use self::cookie::{Cookie, SameSite, Iter}; pub struct CookieJar<'a> { jar: cookie::CookieJar, ops: Mutex>, - config: &'a Config, - secure_context: bool, + pub(crate) state: CookieState<'a>, } -impl<'a> Clone for CookieJar<'a> { - fn clone(&self) -> Self { - CookieJar { - jar: self.jar.clone(), - ops: Mutex::new(self.ops.lock().clone()), - config: self.config, - secure_context: self.secure_context, - } - } +#[derive(Copy, Clone)] +pub(crate) struct CookieState<'a> { + pub secure: bool, + #[cfg_attr(not(feature = "secrets"), allow(unused))] + pub config: &'a crate::Config, } #[derive(Clone)] @@ -175,30 +169,12 @@ enum Op { Remove(Cookie<'static>, bool), } -impl Op { - fn cookie(&self) -> &Cookie<'static> { - match self { - Op::Add(c, _) | Op::Remove(c, _) => c - } - } -} - impl<'a> CookieJar<'a> { - #[inline(always)] - pub(crate) fn new(config: &'a Config, secure_context: bool) -> Self { - CookieJar::from(cookie::CookieJar::new(), config, secure_context) - } - - pub(crate) fn from( - jar: cookie::CookieJar, - config: &'a Config, - secure_context: bool, - ) -> Self { + pub(crate) fn new(base: Option, state: impl Into>) -> Self { CookieJar { - jar, - config, + jar: base.unwrap_or_default(), ops: Mutex::new(Vec::new()), - secure_context, + state: state.into(), } } @@ -247,7 +223,7 @@ impl<'a> CookieJar<'a> { #[cfg(feature = "secrets")] #[cfg_attr(nightly, doc(cfg(feature = "secrets")))] pub fn get_private(&self, name: &str) -> Option> { - self.jar.private(&self.config.secret_key.key).get(name) + self.jar.private(&self.state.config.secret_key.key).get(name) } /// Returns a reference to the _original or pending_ `Cookie` inside this @@ -320,7 +296,7 @@ impl<'a> CookieJar<'a> { /// ``` pub fn add>>(&self, cookie: C) { let mut cookie = cookie.into(); - Self::set_defaults(self.secure_context, &mut cookie); + self.set_defaults(&mut cookie); self.ops.lock().push(Op::Add(cookie, false)); } @@ -357,7 +333,7 @@ impl<'a> CookieJar<'a> { #[cfg_attr(nightly, doc(cfg(feature = "secrets")))] pub fn add_private>>(&self, cookie: C) { let mut cookie = cookie.into(); - Self::set_private_defaults(self.secure_context, &mut cookie); + self.set_private_defaults(&mut cookie); self.ops.lock().push(Op::Add(cookie, true)); } @@ -487,7 +463,7 @@ impl<'a> CookieJar<'a> { Op::Add(c, false) => jar.add(c), #[cfg(feature = "secrets")] Op::Add(c, true) => { - jar.private_mut(&self.config.secret_key.key).add(c); + jar.private_mut(&self.state.config.secret_key.key).add(c); } Op::Remove(mut c, _) => { if self.jar.get(c.name()).is_some() { @@ -516,7 +492,7 @@ impl<'a> CookieJar<'a> { #[cfg_attr(nightly, doc(cfg(feature = "secrets")))] #[inline(always)] pub(crate) fn add_original_private(&mut self, cookie: Cookie<'static>) { - self.jar.private_mut(&self.config.secret_key.key).add_original(cookie); + self.jar.private_mut(&self.state.config.secret_key.key).add_original(cookie); } /// For each property mentioned below, this method checks if there is a @@ -528,7 +504,7 @@ impl<'a> CookieJar<'a> { /// /// Furthermore, if TLS is enabled or handled by a proxy, the `Secure` /// cookie flag is set. - fn set_defaults(secure_context: bool, cookie: &mut Cookie<'static>) { + fn set_defaults(&self, cookie: &mut Cookie<'static>) { if cookie.path().is_none() { cookie.set_path("/"); } @@ -537,7 +513,7 @@ impl<'a> CookieJar<'a> { cookie.set_same_site(SameSite::Strict); } - if cookie.secure().is_none() && secure_context { + if cookie.secure().is_none() && self.state.secure { cookie.set_secure(true); } } @@ -570,7 +546,7 @@ impl<'a> CookieJar<'a> { /// cookie flag is set. #[cfg(feature = "secrets")] #[cfg_attr(nightly, doc(cfg(feature = "secrets")))] - fn set_private_defaults(secure_context: bool, cookie: &mut Cookie<'static>) { + fn set_private_defaults(&self, cookie: &mut Cookie<'static>) { if cookie.path().is_none() { cookie.set_path("/"); } @@ -587,7 +563,7 @@ impl<'a> CookieJar<'a> { cookie.set_expires(time::OffsetDateTime::now_utc() + time::Duration::weeks(1)); } - if cookie.secure().is_none() && secure_context { + if cookie.secure().is_none() && self.state.secure { cookie.set_secure(true); } } @@ -607,3 +583,33 @@ impl fmt::Debug for CookieJar<'_> { .finish() } } + +impl<'a> Clone for CookieJar<'a> { + fn clone(&self) -> Self { + CookieJar { + jar: self.jar.clone(), + ops: Mutex::new(self.ops.lock().clone()), + state: self.state, + } + } +} + +impl Op { + fn cookie(&self) -> &Cookie<'static> { + match self { + Op::Add(c, _) | Op::Remove(c, _) => c + } + } +} + +impl<'r> From<&'_ crate::Request<'r>> for CookieState<'r> { + fn from(r: &'_ crate::Request<'r>) -> Self { + CookieState { secure: r.context_is_likely_secure(), config: &r.rocket().config } + } +} + +impl<'r> From<&'r crate::Rocket> for CookieState<'r> { + fn from(r: &'r crate::Rocket) -> Self { + CookieState { secure: r.config.tls_enabled(), config: &r.config } + } +} diff --git a/core/lib/src/local/asynchronous/response.rs b/core/lib/src/local/asynchronous/response.rs index 1f81f2c445..6021f2cbd9 100644 --- a/core/lib/src/local/asynchronous/response.rs +++ b/core/lib/src/local/asynchronous/response.rs @@ -88,10 +88,7 @@ impl<'c> LocalResponse<'c> { async move { let response: Response<'c> = f(request).await; - let mut cookies = CookieJar::new( - request.rocket().config(), - request.context_is_likely_secure(), - ); + let mut cookies = CookieJar::new(None, request); for cookie in response.cookies() { cookies.add_original(cookie.into_owned()); } diff --git a/core/lib/src/local/client.rs b/core/lib/src/local/client.rs index 65a946468f..f2b3b922d0 100644 --- a/core/lib/src/local/client.rs +++ b/core/lib/src/local/client.rs @@ -181,9 +181,8 @@ macro_rules! pub_client_impl { /// ``` #[inline(always)] pub fn cookies(&self) -> crate::http::CookieJar<'_> { - let config = &self.rocket().config(); let jar = self._with_raw_cookies(|jar| jar.clone()); - crate::http::CookieJar::from(jar, config, config.tls_enabled()) + crate::http::CookieJar::new(Some(jar), self.rocket()) } req_method!($import, "GET", get, Method::Get); diff --git a/core/lib/src/request/from_request.rs b/core/lib/src/request/from_request.rs index 4d29a49893..7d8cde1dd3 100644 --- a/core/lib/src/request/from_request.rs +++ b/core/lib/src/request/from_request.rs @@ -160,11 +160,11 @@ pub type Outcome = outcome::Outcome; /// via [`Request::client_ip()`]. If the client's IP address is not known, /// the request is forwarded with a 500 Internal Server Error status. /// -/// * **Protocol** +/// * **ProxyProto** /// -/// Extracts the protocol of the incoming request as a [`Protocol`] via -/// [`Request::forwarded_proto()`] (HTTP or HTTPS). If the used protocol is -/// not known, the request is forwarded. +/// Extracts the protocol of the incoming request as a [`ProxyProto`] via +/// [`Request::proxy_proto()`] (HTTP or HTTPS). If value of the header is +/// not known, the request is forwarded with a 404 Not Found status. /// /// * **SocketAddr** /// @@ -483,7 +483,7 @@ impl<'r> FromRequest<'r> for ProxyProto<'r> { async fn from_request(request: &'r Request<'_>) -> Outcome { match request.proxy_proto() { Some(proto) => Success(proto), - None => Forward(()) + None => Forward(Status::NotFound), } } } diff --git a/core/lib/src/request/request.rs b/core/lib/src/request/request.rs index 21b14128b7..2489d170f1 100644 --- a/core/lib/src/request/request.rs +++ b/core/lib/src/request/request.rs @@ -97,7 +97,7 @@ impl<'r> Request<'r> { state: RequestState { rocket, route: Atomic::new(None), - cookies: CookieJar::new(rocket.config(), rocket.config().tls_enabled()), + cookies: CookieJar::new(None, rocket), accept: InitCell::new(), content_type: InitCell::new(), cache: Arc::new(::new()), @@ -386,39 +386,59 @@ impl<'r> Request<'r> { }) } - /// Returns what protocol for a connection was handled by a proxy by - /// inspecting [`proxy_proto_header`](crate::Config::proxy_proto_header) of - /// the request if such a header is configured, exists and contains a known - /// setting ("http" or "https"). + /// Returns the [`ProxyProto`] associated with the current request. + /// + /// The value is determined by inspecting the header named + /// [`proxy_proto_header`](crate::Config::proxy_proto_header), if + /// configured. If parameter isn't configured or the request doesn't contain + /// a header named as indicated, this method returns `None`. /// /// # Example /// /// ```rust /// use rocket::http::{Header, ProxyProto}; /// - /// # let client = rocket::local::blocking::Client::debug_with(vec![]).unwrap(); - /// # let request = client.get("/"); - /// assert_eq!(request.proxy_proto(), None); - /// - /// // `proxy_proto_header` defaults to `None`. `x-forwarded-proto` is considered the de-facto standard - /// # let figment = rocket::Config::figment().merge(("proxy_proto_header", "x-forwarded-proto")); - /// # let client = rocket::local::blocking::Client::debug(rocket::custom(figment)).unwrap(); - /// # let request = client.get("/"); - /// let request = request.header(Header::new("x-forwarded-proto", "https")); - /// assert_eq!(request.proxy_proto(), Some(ProxyProto::Https)); + /// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap(); + /// # let req = c.get("/"); + /// // By default, no `proxy_proto_header` is configured. + /// let req = req.header(Header::new("x-forwarded-proto", "https")); + /// assert_eq!(req.proxy_proto(), None); + /// + /// // We can configured one by setting the `proxy_proto_header` parameter. + /// // Here we set it to `x-forwarded-proto`, considered de-facto standard. + /// # let figment = rocket::figment::Figment::from(rocket::Config::debug_default()); + /// let figment = figment.merge(("proxy_proto_header", "x-forwarded-proto")); + /// # let c = rocket::local::blocking::Client::debug(rocket::custom(figment)).unwrap(); + /// # let req = c.get("/"); + /// let req = req.header(Header::new("x-forwarded-proto", "https")); + /// assert_eq!(req.proxy_proto(), Some(ProxyProto::Https)); + /// + /// # let req = c.get("/"); + /// let req = req.header(Header::new("x-forwarded-proto", "http")); + /// assert_eq!(req.proxy_proto(), Some(ProxyProto::Http)); + /// + /// # let req = c.get("/"); + /// let req = req.header(Header::new("x-forwarded-proto", "xproto")); + /// assert_eq!(req.proxy_proto(), Some(ProxyProto::Unknown("xproto".into()))); /// ``` pub fn proxy_proto(&self) -> Option> { - let proxy_proto_header = self.rocket().config.proxy_proto_header.as_ref()?.as_str(); - self.headers() - .get_one(proxy_proto_header) - .and_then(|proto| Some(proto.into())) + self.rocket().config + .proxy_proto_header + .as_ref() + .and_then(|header| self.headers().get_one(header.as_str())) + .map(ProxyProto::from) } - /// Returns whether we are *likely* in a secure context; either because tls - /// is enabled in the Rocket config, or a secure connection was handled by - /// a proxy. The later is determined by inspecting the header configured in - /// [`proxy_proto_header`](crate::Config::proxy_proto_header) of the request - /// if such a header is configured, exists and contains the value "https" + /// Returns whether we are *likely* in a secure context. + /// + /// A request is in a "secure context" if it was initially sent over a + /// secure (TLS, via HTTPS) connection. If TLS is configured and enabled, + /// then the request is guaranteed to be in a secure context. Otherwise, if + /// [`Request::proxy_proto()`] evaluates to `Https`, then we are _likely_ to + /// be in a secure context. We say _likely_ because it is entirely possible + /// for the header to indicate that the connection is being proxied via + /// HTTPS while reality differs. As such, this values should not be trusted + /// when security is a concern. /// /// # Example /// @@ -426,15 +446,19 @@ impl<'r> Request<'r> { /// use rocket::http::{Header, ProxyProto}; /// /// # let client = rocket::local::blocking::Client::debug_with(vec![]).unwrap(); - /// # let request = client.get("/"); - /// assert_eq!(request.context_is_likely_secure(), false); - /// - /// // `proxy_proto_header` defaults to `None`. `x-forwarded-proto` is considered the de-facto standard - /// # let figment = rocket::Config::figment().merge(("proxy_proto_header", "x-forwarded-proto")); - /// # let client = rocket::local::blocking::Client::debug(rocket::custom(figment)).unwrap(); - /// # let request = client.get("/"); - /// let request = request.header(Header::new("x-forwarded-proto", "https")); - /// assert_eq!(request.context_is_likely_secure(), true); + /// # let req = client.get("/"); + /// // If TLS and proxy_proto are disabled, we are not in a secure context. + /// assert_eq!(req.context_is_likely_secure(), false); + /// + /// // Configuring proxy_proto and receiving a header value of `https` is + /// // interpreted as likely being in a secure context. + /// // Here we set it to `x-forwarded-proto`, considered de-facto standard. + /// # let figment = rocket::figment::Figment::from(rocket::Config::debug_default()); + /// let figment = figment.merge(("proxy_proto_header", "x-forwarded-proto")); + /// # let c = rocket::local::blocking::Client::debug(rocket::custom(figment)).unwrap(); + /// # let req = c.get("/"); + /// let req = req.header(Header::new("x-forwarded-proto", "https")); + /// assert_eq!(req.context_is_likely_secure(), true); /// ``` pub fn context_is_likely_secure(&self) -> bool { self.rocket().config.tls_enabled() || self.proxy_proto() == Some(ProxyProto::Https) @@ -601,9 +625,9 @@ impl<'r> Request<'r> { /// ``` #[inline] pub fn content_type(&self) -> Option<&ContentType> { - self.state.content_type.get_or_init(|| { - self.headers().get_one("Content-Type").and_then(|v| v.parse().ok()) - }).as_ref() + self.state.content_type + .get_or_init(|| self.headers().get_one("Content-Type").and_then(|v| v.parse().ok())) + .as_ref() } /// Returns the Accept header of `self`. If the header is not present, @@ -621,9 +645,9 @@ impl<'r> Request<'r> { /// ``` #[inline] pub fn accept(&self) -> Option<&Accept> { - self.state.accept.get_or_init(|| { - self.headers().get_one("Accept").and_then(|v| v.parse().ok()) - }).as_ref() + self.state.accept + .get_or_init(|| self.headers().get_one("Accept").and_then(|v| v.parse().ok())) + .as_ref() } /// Returns the media type "format" of the request. @@ -988,6 +1012,10 @@ impl<'r> Request<'r> { if self.accept().is_none() || replace { self.state.accept = InitCell::new(); } + } else if self.rocket().config.proxy_proto_header.as_ref().map_or(false, |h| h == name) { + if !self.cookies().state.secure || replace { + self.cookies_mut().state.secure = self.context_is_likely_secure(); + } } } @@ -1091,6 +1119,20 @@ impl<'r> Request<'r> { hyper.uri.host().map(|h| Host::new(Authority::new(None, h, hyper.uri.port_u16()))) }; + // Set the request cookies, if they exist. + for header in hyper.headers.get_all("Cookie") { + let raw_str = match std::str::from_utf8(header.as_bytes()) { + Ok(string) => string, + Err(_) => continue + }; + + for cookie_str in raw_str.split(';').map(|s| s.trim()) { + if let Ok(cookie) = Cookie::parse_encoded(cookie_str) { + request.state.cookies.add_original(cookie.into_owned()); + } + } + } + // Set the rest of the headers. This is rather unfortunate and slow. for (name, value) in hyper.headers.iter() { // FIXME: This is rather unfortunate. Header values needn't be UTF8. @@ -1106,25 +1148,6 @@ impl<'r> Request<'r> { request.add_header(Header::new(name.as_str(), value)); } - request.state.cookies = CookieJar::new( - rocket.config(), - request.context_is_likely_secure(), - ); - - // Set the request cookies, if they exist. - for header in hyper.headers.get_all("Cookie") { - let raw_str = match std::str::from_utf8(header.as_bytes()) { - Ok(string) => string, - Err(_) => continue - }; - - for cookie_str in raw_str.split(';').map(|s| s.trim()) { - if let Ok(cookie) = Cookie::parse_encoded(cookie_str) { - request.state.cookies.add_original(cookie.into_owned()); - } - } - } - if errors.is_empty() { Ok(request) } else { diff --git a/core/lib/tests/config-proxy-proto-header.rs b/core/lib/tests/config-proxy-proto-header.rs index bf184419dd..48dc5af37a 100644 --- a/core/lib/tests/config-proxy-proto-header.rs +++ b/core/lib/tests/config-proxy-proto-header.rs @@ -1,25 +1,30 @@ +use rocket::http::ProxyProto; + #[macro_use] extern crate rocket; #[get("/")] -fn inspect_proto(proto: Option) -> String { +fn inspect_proto(proto: Option) -> String { proto .map(|proto| match proto { - rocket::http::ProxyProto::Http => "http".to_owned(), - rocket::http::ProxyProto::Https => "https".to_owned(), - rocket::http::ProxyProto::Unknown(s) => s.to_string(), + ProxyProto::Http => "http".to_owned(), + ProxyProto::Https => "https".to_owned(), + ProxyProto::Unknown(s) => s.to_string(), }) .unwrap_or("".to_owned()) } mod tests { - use rocket::{figment::Figment, http::Header, local::blocking::Client, Build, Rocket, Route}; + use rocket::{Rocket, Build, Route}; + use rocket::http::Header; + use rocket::local::blocking::Client; + use rocket::figment::Figment; fn routes() -> Vec { routes![super::inspect_proto] } - fn rocket_with_custom_proxy_proto_header(header: Option<&'static str>) -> Rocket { + fn rocket_with_proto_header(header: Option<&'static str>) -> Rocket { let mut config = rocket::Config::debug_default(); config.proxy_proto_header = header.map(|h| h.into()); rocket::custom(config).mount("/", routes()) @@ -27,37 +32,34 @@ mod tests { #[test] fn check_proxy_proto_header_works() { - let client = - Client::debug(rocket_with_custom_proxy_proto_header(Some("X-Url-Scheme"))).unwrap(); - let response = client - .get("/") + let rocket = rocket_with_proto_header(Some("X-Url-Scheme")); + let client = Client::debug(rocket).unwrap(); + let response = client.get("/") .header(Header::new("X-Forwarded-Proto", "https")) .header(Header::new("X-Url-Scheme", "http")) .dispatch(); - assert_eq!(response.into_string(), Some("http".into())); + assert_eq!(response.into_string().unwrap(), "http"); - let response = client - .get("/") + let response = client.get("/") .header(Header::new("X-Url-Scheme", "https")) .dispatch(); - assert_eq!(response.into_string(), Some("https".into())); + assert_eq!(response.into_string().unwrap(), "https"); let response = client.get("/").dispatch(); - assert_eq!(response.into_string(), Some("".into())); + assert_eq!(response.into_string().unwrap(), ""); } #[test] fn check_proxy_proto_header_works_again() { - let client = - Client::debug(rocket_with_custom_proxy_proto_header(Some("x-url-scheme"))).unwrap(); + let client = Client::debug(rocket_with_proto_header(Some("x-url-scheme"))).unwrap(); let response = client .get("/") .header(Header::new("X-Url-Scheme", "https")) .dispatch(); - assert_eq!(response.into_string(), Some("https".into())); + assert_eq!(response.into_string().unwrap(), "https"); let config = Figment::from(rocket::Config::debug_default()) .merge(("proxy_proto_header", "x-url-scheme")); @@ -68,7 +70,7 @@ mod tests { .header(Header::new("X-url-Scheme", "https")) .dispatch(); - assert_eq!(response.into_string(), Some("https".into())); + assert_eq!(response.into_string().unwrap(), "https"); } #[test] @@ -84,9 +86,8 @@ mod tests { #[test] fn check_no_proxy_proto_header_works() { - let client = Client::debug(rocket_with_custom_proxy_proto_header(None)).unwrap(); - let response = client - .get("/") + let client = Client::debug(rocket_with_proto_header(None)).unwrap(); + let response = client.get("/") .header(Header::new("X-Forwarded-Proto", "https")) .dispatch(); @@ -105,6 +106,7 @@ mod tests { let config = Figment::from(rocket::Config::debug_default()) .merge(("proxy_proto_header", "x-forwarded-proto")); + let client = Client::debug(rocket::custom(config).mount("/", routes())).unwrap(); let response = client .get("/") diff --git a/site/guide/9-configuration.md b/site/guide/9-configuration.md index a796d5d072..09ff01d9e2 100644 --- a/site/guide/9-configuration.md +++ b/site/guide/9-configuration.md @@ -25,7 +25,7 @@ values: | `max_blocking`* | `usize` | Limit on threads to start for blocking tasks. | `512` | | `ident` | `string`, `false` | If and how to identify via the `Server` header. | `"Rocket"` | | `ip_header` | `string`, `false` | IP header to inspect to get [client's real IP]. | `"X-Real-IP"` | -| `proxy_proto_header` | `string`, `false` | Header to inspect for identifying the protocol. | `"X-Forwarded-For"` | +| `proxy_proto_header` | `string`, `false` | Header identifying client to proxy protocol. | `None` | | `keep_alive` | `u32` | Keep-alive timeout seconds; disabled when `0`. | `5` | | `log_level` | [`LogLevel`] | Max level to log. (off/normal/debug/critical) | `normal`/`critical` | | `cli_colors` | `bool` | Whether to use colors and emoji when logging. | `true` | @@ -36,8 +36,10 @@ values: | `ctrlc` | `bool` | Whether `ctrl-c` initiates a server shutdown. | `true` | | `shutdown`* | [`Shutdown`] | Graceful shutdown configuration. | [`Shutdown::default()`] | -* Note: the `workers`, `max_blocking`, and `shutdown.force` configuration -parameters are only read from the [default provider](#default-provider). + +* Note: the `workers`, `max_blocking`, and `shutdown.force` configuration +parameters are only read when using the [default provider](#default-provider). + [client's real IP]: @api/rocket/request/struct.Request.html#method.real_ip @@ -130,6 +132,7 @@ port = 9001 [release] port = 9999 ip_header = false +proxy_proto_header = "X-Forwarded-Proto" # NOTE: Don't (!) use this key! Generate your own and keep it private! # e.g. via `head -c64 /dev/urandom | base64` secret_key = "hPrYyЭRiMyµ5sBB1π+CMæ1køFsåqKvBiQJxBVHQk="