mirror of https://github.com/rwf2/Rocket.git
parent
f49ee7da00
commit
c58b43700c
|
@ -4,7 +4,7 @@
|
|||
//! These types will, with certainty, be removed with time, but they reside here
|
||||
//! while necessary.
|
||||
|
||||
#[doc(hidden)] pub use hyper::{Body, Error, Request, Response};
|
||||
#[doc(hidden)] pub use hyper::{Body, Error, Request, Response, Version};
|
||||
#[doc(hidden)] pub use hyper::body::{Bytes, HttpBody, Sender as BodySender};
|
||||
#[doc(hidden)] pub use hyper::rt::Executor;
|
||||
#[doc(hidden)] pub use hyper::server::Server;
|
||||
|
|
|
@ -27,6 +27,17 @@ pub fn authority_from_str(s: &str) -> Result<Authority<'_>, Error<'_>> {
|
|||
Ok(parse!(authority: RawInput::new(s.as_bytes()))?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn authority_from_bytes(s: &[u8]) -> Result<Authority<'_>, Error<'_>> {
|
||||
Ok(parse!(authority: RawInput::new(s))?)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn scheme_from_str(s: &str) -> Result<&str, Error<'_>> {
|
||||
let _validated = parse!(scheme: RawInput::new(s.as_bytes()))?;
|
||||
Ok(s)
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn absolute_from_str(s: &str) -> Result<Absolute<'_>, Error<'_>> {
|
||||
Ok(parse!(absolute: RawInput::new(s.as_bytes()))?)
|
||||
|
|
|
@ -81,12 +81,18 @@ pub fn authority<'a>(input: &mut RawInput<'a>) -> Result<'a, Authority<'a>> {
|
|||
}
|
||||
|
||||
#[parser]
|
||||
pub fn absolute<'a>(input: &mut RawInput<'a>) -> Result<'a, Absolute<'a>> {
|
||||
pub fn scheme<'a>(input: &mut RawInput<'a>) -> Result<'a, Extent<&'a [u8]>> {
|
||||
let scheme = take_some_while(is_scheme_char)?;
|
||||
if !scheme.get(0).map_or(false, |b| b.is_ascii_alphabetic()) {
|
||||
parse_error!("invalid scheme")?;
|
||||
}
|
||||
|
||||
scheme
|
||||
}
|
||||
|
||||
#[parser]
|
||||
pub fn absolute<'a>(input: &mut RawInput<'a>) -> Result<'a, Absolute<'a>> {
|
||||
let scheme = scheme()?;
|
||||
let (_, (authority, path), query) = (eat(b':')?, hier_part()?, query()?);
|
||||
unsafe { Absolute::raw(input.start.into(), scheme, authority, path, query) }
|
||||
}
|
||||
|
|
|
@ -426,7 +426,7 @@ impl<'a> Absolute<'a> {
|
|||
Absolute::const_new(scheme, authority.into(), path, query.into())
|
||||
}
|
||||
|
||||
/// PRIVATE. Used by codegen.
|
||||
/// PRIVATE. Used by codegen and `Host`.
|
||||
#[doc(hidden)]
|
||||
pub const fn const_new(
|
||||
scheme: &'a str,
|
||||
|
|
|
@ -46,7 +46,7 @@ use crate::uri::{as_utf8_unchecked, error::Error};
|
|||
#[derive(Debug, Clone)]
|
||||
pub struct Authority<'a> {
|
||||
pub(crate) source: Option<Cow<'a, str>>,
|
||||
user_info: Option<IndexedStr<'a>>,
|
||||
pub(crate) user_info: Option<IndexedStr<'a>>,
|
||||
host: IndexedStr<'a>,
|
||||
port: Option<u16>,
|
||||
}
|
||||
|
@ -68,7 +68,8 @@ impl<'a> Authority<'a> {
|
|||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
/// PRIVATE. Used by core.
|
||||
#[doc(hidden)]
|
||||
pub fn new(
|
||||
user_info: impl Into<Option<&'a str>>,
|
||||
host: &'a str,
|
||||
|
@ -166,14 +167,10 @@ impl<'a> Authority<'a> {
|
|||
|
||||
/// Returns the host part of the authority URI.
|
||||
///
|
||||
///
|
||||
/// If the host was provided in brackets (such as for IPv6 addresses), the
|
||||
/// brackets will not be part of the returned string.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
///
|
||||
/// let uri = uri!("domain.com:123");
|
||||
/// assert_eq!(uri.host(), "domain.com");
|
||||
///
|
||||
|
|
|
@ -0,0 +1,326 @@
|
|||
use std::fmt::{self, Display};
|
||||
|
||||
use crate::uncased::UncasedStr;
|
||||
use crate::uri::error::Error;
|
||||
use crate::uri::{Absolute, Authority};
|
||||
|
||||
/// A domain and port identified by a client as the server being messaged.
|
||||
///
|
||||
/// For requests made via HTTP/1.1, a host is identified via the `HOST` header.
|
||||
/// In HTTP/2 and HTTP/3, this information is instead communicated via the
|
||||
/// `:authority` and `:port` pseudo-header request fields. It is a
|
||||
/// client-controlled value via which the client communicates to the server the
|
||||
/// domain name and port it is attemping to communicate with. The following
|
||||
/// diagram illustrates the syntactic structure of a `Host`:
|
||||
///
|
||||
/// ```text
|
||||
/// some.domain.foo:8088
|
||||
/// |-----------| |--|
|
||||
/// domain port
|
||||
/// ```
|
||||
///
|
||||
/// Only the domain part is required. Its value is case-insensitive.
|
||||
///
|
||||
/// # URI Construction
|
||||
///
|
||||
/// A `Host` is _not_ a [`Uri`](crate::uri::Uri), and none of Rocket's APIs will
|
||||
/// accept a `Host` value as such. This is because doing so would facilitate the
|
||||
/// construction of URIs to internal routes in a manner controllable by an
|
||||
/// attacker, inevitably leading to "HTTP Host header attacks".
|
||||
///
|
||||
/// Instead, a `Host` must be checked before being converted to a [`Uri`]
|
||||
/// value. The [`Host::to_authority`] and [`Host::to_absolute`] methods provide
|
||||
/// these mechanisms:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # type Token = String;
|
||||
/// use rocket::http::uri::Host;
|
||||
///
|
||||
/// // A sensitive URI we want to prefix with safe hosts.
|
||||
/// #[get("/token?<secret>")]
|
||||
/// fn token(secret: Token) { /* .. */ }
|
||||
///
|
||||
/// // Whitelist of known hosts. In a real setting, you might retrieve this
|
||||
/// // list from config at ignite-time using tools like `AdHoc::config()`.
|
||||
/// const WHITELIST: [Host<'static>; 4] = [
|
||||
/// Host::new(uri!("rocket.rs")),
|
||||
/// Host::new(uri!("rocket.rs:443")),
|
||||
/// Host::new(uri!("guide.rocket.rs")),
|
||||
/// Host::new(uri!("guide.rocket.rs:443")),
|
||||
/// ];
|
||||
///
|
||||
/// // Use `Host::to_absolute()` to case-insensitively check a host against a
|
||||
/// // whitelist, returning an `Absolute` usable as a `uri!()` prefix.
|
||||
/// let host = Host::new(uri!("guide.ROCKET.rs"));
|
||||
/// let prefix = host.to_absolute("https", &WHITELIST);
|
||||
///
|
||||
/// // Since `guide.rocket.rs` is in the whitelist, `prefix` is `Some`.
|
||||
/// assert!(prefix.is_some());
|
||||
/// if let Some(prefix) = prefix {
|
||||
/// // We can use this prefix to safely construct URIs.
|
||||
/// let uri = uri!(prefix, token("some-secret-token"));
|
||||
/// assert_eq!(uri, "https://guide.ROCKET.rs/token?secret=some-secret-token");
|
||||
/// }
|
||||
/// ```
|
||||
///
|
||||
/// # (De)serialization
|
||||
///
|
||||
/// `Host` is both `Serialize` and `Deserialize`:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[cfg(feature = "serde")] mod serde {
|
||||
/// # use serde_ as serde;
|
||||
/// use serde::{Serialize, Deserialize};
|
||||
/// use rocket::http::uri::Host;
|
||||
///
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// # #[serde(crate = "serde_")]
|
||||
/// struct UriOwned {
|
||||
/// uri: Host<'static>,
|
||||
/// }
|
||||
///
|
||||
/// #[derive(Deserialize, Serialize)]
|
||||
/// # #[serde(crate = "serde_")]
|
||||
/// struct UriBorrowed<'a> {
|
||||
/// uri: Host<'a>,
|
||||
/// }
|
||||
/// # }
|
||||
/// ```
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Host<'a>(Authority<'a>);
|
||||
|
||||
impl<'a> Host<'a> {
|
||||
/// Create a new `Host` from an `Authority`. Only the `host` and `port`
|
||||
/// parts are preserved.
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Host;
|
||||
///
|
||||
/// let host = Host::new(uri!("developer.mozilla.org"));
|
||||
/// assert_eq!(host.to_string(), "developer.mozilla.org");
|
||||
///
|
||||
/// let host = Host::new(uri!("foo:bar@developer.mozilla.org:1234"));
|
||||
/// assert_eq!(host.to_string(), "developer.mozilla.org:1234");
|
||||
///
|
||||
/// let host = Host::new(uri!("rocket.rs:443"));
|
||||
/// assert_eq!(host.to_string(), "rocket.rs:443");
|
||||
/// ```
|
||||
pub const fn new(authority: Authority<'a>) -> Self {
|
||||
Host(authority)
|
||||
}
|
||||
|
||||
/// Parses the string `string` into a `Host`. Parsing will never allocate.
|
||||
/// Returns an `Error` if `string` is not a valid authority URI, meaning
|
||||
/// that this parser accepts a `user_info` part for compatability but
|
||||
/// discards it.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Host;
|
||||
///
|
||||
/// // Parse from a valid authority URI.
|
||||
/// let host = Host::parse("user:pass@domain").expect("valid host");
|
||||
/// assert_eq!(host.domain(), "domain");
|
||||
/// assert_eq!(host.port(), None);
|
||||
///
|
||||
/// // Parse from a valid host.
|
||||
/// let host = Host::parse("domain:311").expect("valid host");
|
||||
/// assert_eq!(host.domain(), "doMaIN");
|
||||
/// assert_eq!(host.port(), Some(311));
|
||||
///
|
||||
/// // Invalid hosts fail to parse.
|
||||
/// Host::parse("https://rocket.rs").expect_err("invalid host");
|
||||
///
|
||||
/// // Prefer to use `uri!()` when the input is statically known:
|
||||
/// let host = Host::new(uri!("domain"));
|
||||
/// assert_eq!(host.domain(), "domain");
|
||||
/// assert_eq!(host.port(), None);
|
||||
/// ```
|
||||
pub fn parse(string: &'a str) -> Result<Host<'a>, Error<'a>> {
|
||||
Host::parse_bytes(string.as_bytes())
|
||||
}
|
||||
|
||||
/// PRIVATE: Used by core.
|
||||
#[doc(hidden)]
|
||||
pub fn parse_bytes(bytes: &'a [u8]) -> Result<Host<'a>, Error<'a>> {
|
||||
crate::parse::uri::authority_from_bytes(bytes).map(Host::new)
|
||||
}
|
||||
|
||||
/// Parses the string `string` into an `Host`. Parsing never allocates
|
||||
/// on success. May allocate on error.
|
||||
///
|
||||
/// This method should be used instead of [`Host::parse()`] when the source
|
||||
/// is already a `String`. Returns an `Error` if `string` is not a valid
|
||||
/// authority URI, meaning that this parser accepts a `user_info` part for
|
||||
/// compatability but discards it.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # extern crate rocket;
|
||||
/// use rocket::http::uri::Host;
|
||||
///
|
||||
/// let source = format!("rocket.rs:8000");
|
||||
/// let host = Host::parse_owned(source).expect("valid host");
|
||||
/// assert_eq!(host.domain(), "rocket.rs");
|
||||
/// assert_eq!(host.port(), Some(8000));
|
||||
/// ```
|
||||
pub fn parse_owned(string: String) -> Result<Host<'static>, Error<'static>> {
|
||||
Authority::parse_owned(string).map(Host::new)
|
||||
}
|
||||
|
||||
/// Returns the case-insensitive domain part of the host.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Host;
|
||||
///
|
||||
/// let host = Host::new(uri!("domain.com:123"));
|
||||
/// assert_eq!(host.domain(), "domain.com");
|
||||
///
|
||||
/// let host = Host::new(uri!("username:password@domain:123"));
|
||||
/// assert_eq!(host.domain(), "domain");
|
||||
///
|
||||
/// let host = Host::new(uri!("[1::2]:123"));
|
||||
/// assert_eq!(host.domain(), "[1::2]");
|
||||
/// ```
|
||||
#[inline]
|
||||
pub fn domain(&self) -> &UncasedStr {
|
||||
self.0.host().into()
|
||||
}
|
||||
|
||||
/// Returns the port part of the host, if there is one.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Host;
|
||||
///
|
||||
/// // With a port.
|
||||
/// let host = Host::new(uri!("domain:123"));
|
||||
/// assert_eq!(host.port(), Some(123));
|
||||
///
|
||||
/// let host = Host::new(uri!("domain.com:8181"));
|
||||
/// assert_eq!(host.port(), Some(8181));
|
||||
///
|
||||
/// // Without a port.
|
||||
/// let host = Host::new(uri!("domain.foo.bar.tld"));
|
||||
/// assert_eq!(host.port(), None);
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn port(&self) -> Option<u16> {
|
||||
self.0.port()
|
||||
}
|
||||
|
||||
/// Checks `self` against `whitelist`. If `self` is in `whitelist`, returns
|
||||
/// an [`Authority`] URI representing self. Otherwise, returns `None`.
|
||||
/// Domain comparison is case-insensitive.
|
||||
///
|
||||
/// See [URI construction](Self#uri-construction) for more.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Host;
|
||||
///
|
||||
/// let whitelist = &[Host::new(uri!("domain.tld"))];
|
||||
///
|
||||
/// // A host in the whitelist returns `Some`.
|
||||
/// let host = Host::new(uri!("domain.tld"));
|
||||
/// let uri = host.to_authority(whitelist);
|
||||
/// assert!(uri.is_some());
|
||||
/// assert_eq!(uri.unwrap().to_string(), "domain.tld");
|
||||
///
|
||||
/// let host = Host::new(uri!("foo:bar@doMaIN.tLd"));
|
||||
/// let uri = host.to_authority(whitelist);
|
||||
/// assert!(uri.is_some());
|
||||
/// assert_eq!(uri.unwrap().to_string(), "doMaIN.tLd");
|
||||
///
|
||||
/// // A host _not_ in the whitelist returns `None`.
|
||||
/// let host = Host::new(uri!("domain.tld:1234"));
|
||||
/// let uri = host.to_authority(whitelist);
|
||||
/// assert!(uri.is_none());
|
||||
/// ```
|
||||
pub fn to_authority<'h, W>(&self, whitelist: W) -> Option<Authority<'a>>
|
||||
where W: IntoIterator<Item = &'h Host<'h>>
|
||||
{
|
||||
let mut auth = whitelist.into_iter().any(|h| h == self).then(|| self.0.clone())?;
|
||||
auth.user_info = None;
|
||||
Some(auth)
|
||||
}
|
||||
|
||||
/// Checks `self` against `whitelist`. If `self` is in `whitelist`, returns
|
||||
/// an [`Absolute`] URI representing `self` with scheme `scheme`. Otherwise,
|
||||
/// returns `None`. Domain comparison is case-insensitive.
|
||||
///
|
||||
/// See [URI construction](Self#uri-construction) for more.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// use rocket::http::uri::Host;
|
||||
///
|
||||
/// let whitelist = &[Host::new(uri!("domain.tld:443"))];
|
||||
///
|
||||
/// // A host in the whitelist returns `Some`.
|
||||
/// let host = Host::new(uri!("user@domain.tld:443"));
|
||||
/// let uri = host.to_absolute("http", whitelist);
|
||||
/// assert!(uri.is_some());
|
||||
/// assert_eq!(uri.unwrap().to_string(), "http://domain.tld:443");
|
||||
///
|
||||
/// let host = Host::new(uri!("domain.TLD:443"));
|
||||
/// let uri = host.to_absolute("https", whitelist);
|
||||
/// assert!(uri.is_some());
|
||||
/// assert_eq!(uri.unwrap().to_string(), "https://domain.TLD:443");
|
||||
///
|
||||
/// // A host _not_ in the whitelist returns `None`.
|
||||
/// let host = Host::new(uri!("domain.tld"));
|
||||
/// let uri = host.to_absolute("http", whitelist);
|
||||
/// assert!(uri.is_none());
|
||||
/// ```
|
||||
pub fn to_absolute<'h, W>(&self, scheme: &'a str, whitelist: W) -> Option<Absolute<'a>>
|
||||
where W: IntoIterator<Item = &'h Host<'h>>
|
||||
{
|
||||
let scheme = crate::parse::uri::scheme_from_str(scheme).ok()?;
|
||||
let authority = self.to_authority(whitelist)?;
|
||||
Some(Absolute::const_new(scheme, Some(authority), "", None))
|
||||
}
|
||||
}
|
||||
|
||||
impl_serde!(Host<'a>, "an HTTP host");
|
||||
|
||||
impl_base_traits!(Host, domain, port);
|
||||
|
||||
impl crate::ext::IntoOwned for Host<'_> {
|
||||
type Owned = Host<'static>;
|
||||
|
||||
fn into_owned(self) -> Host<'static> {
|
||||
Host(self.0.into_owned())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> From<Authority<'a>> for Host<'a> {
|
||||
fn from(auth: Authority<'a>) -> Self {
|
||||
Host::new(auth)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Host<'_> {
|
||||
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||
self.domain().fmt(f)?;
|
||||
if let Some(port) = self.port() {
|
||||
write!(f, ":{}", port)?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
|
@ -9,6 +9,7 @@ mod absolute;
|
|||
mod segments;
|
||||
mod path_query;
|
||||
mod asterisk;
|
||||
mod host;
|
||||
|
||||
pub mod error;
|
||||
pub mod fmt;
|
||||
|
@ -24,3 +25,4 @@ pub use self::segments::*;
|
|||
pub use self::reference::*;
|
||||
pub use self::path_query::*;
|
||||
pub use self::asterisk::*;
|
||||
pub use self::host::*;
|
||||
|
|
|
@ -389,8 +389,26 @@ macro_rules! impl_serde {
|
|||
};
|
||||
}
|
||||
|
||||
/// Implements PartialEq, Eq, Hash, TryFrom, and IntoOwned for a URI.
|
||||
/// Implements traits from `impl_base_traits` and IntoOwned for a URI.
|
||||
macro_rules! impl_traits {
|
||||
($T:ident, $($field:ident),* $(,)?) => {
|
||||
impl_base_traits!($T, $($field),*);
|
||||
|
||||
impl crate::ext::IntoOwned for $T<'_> {
|
||||
type Owned = $T<'static>;
|
||||
|
||||
fn into_owned(self) -> $T<'static> {
|
||||
$T {
|
||||
source: self.source.into_owned(),
|
||||
$($field: self.$field.into_owned()),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Implements PartialEq, Eq, Hash, and TryFrom.
|
||||
macro_rules! impl_base_traits {
|
||||
($T:ident, $($field:ident),* $(,)?) => {
|
||||
impl std::convert::TryFrom<String> for $T<'static> {
|
||||
type Error = Error<'static>;
|
||||
|
@ -448,16 +466,5 @@ macro_rules! impl_traits {
|
|||
$(self.$field().hash(state);)*
|
||||
}
|
||||
}
|
||||
|
||||
impl crate::ext::IntoOwned for $T<'_> {
|
||||
type Owned = $T<'static>;
|
||||
|
||||
fn into_owned(self) -> $T<'static> {
|
||||
$T {
|
||||
source: self.source.into_owned(),
|
||||
$($field: self.$field.into_owned()),*
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -13,9 +13,10 @@ use crate::request::{FromParam, FromSegments, FromRequest, Outcome};
|
|||
use crate::form::{self, ValueField, FromForm};
|
||||
|
||||
use crate::{Rocket, Route, Orbit};
|
||||
use crate::http::{hyper, uri::{Origin, Segments, fmt::Path}, uncased::UncasedStr};
|
||||
use crate::http::{Method, Header, HeaderMap};
|
||||
use crate::http::uri::{fmt::Path, Origin, Segments, Host, Authority};
|
||||
use crate::http::{hyper, Method, Header, HeaderMap};
|
||||
use crate::http::{ContentType, Accept, MediaType, CookieJar, Cookie};
|
||||
use crate::http::uncased::UncasedStr;
|
||||
use crate::data::Limits;
|
||||
|
||||
/// The type of an incoming web request.
|
||||
|
@ -27,6 +28,7 @@ use crate::data::Limits;
|
|||
pub struct Request<'r> {
|
||||
method: Atomic<Method>,
|
||||
uri: Origin<'r>,
|
||||
host: Option<Host<'r>>,
|
||||
headers: HeaderMap<'r>,
|
||||
remote: Option<SocketAddr>,
|
||||
pub(crate) state: RequestState<'r>,
|
||||
|
@ -46,6 +48,7 @@ impl Request<'_> {
|
|||
Request {
|
||||
method: Atomic::new(self.method()),
|
||||
uri: self.uri.clone(),
|
||||
host: self.host.clone(),
|
||||
headers: self.headers.clone(),
|
||||
remote: self.remote,
|
||||
state: self.state.clone(),
|
||||
|
@ -76,6 +79,7 @@ impl<'r> Request<'r> {
|
|||
) -> Request<'r> {
|
||||
Request {
|
||||
uri,
|
||||
host: None,
|
||||
method: Atomic::new(method),
|
||||
headers: HeaderMap::new(),
|
||||
remote: None,
|
||||
|
@ -168,6 +172,119 @@ impl<'r> Request<'r> {
|
|||
self.uri = uri;
|
||||
}
|
||||
|
||||
/// Returns the [`Host`] identified in the request, if any.
|
||||
///
|
||||
/// If the request is made via HTTP/1.1 (or earlier), this method returns
|
||||
/// the value in the `HOST` header without the deprecated `user_info`
|
||||
/// component. Otherwise, this method returns the contents of the
|
||||
/// `:authority` pseudo-header request field.
|
||||
///
|
||||
/// # ⚠️ DANGER ⚠️
|
||||
///
|
||||
/// Using the user-controlled `host` to construct URLs is a security hazard!
|
||||
/// _Never_ do so without first validating the host against a whitelist. For
|
||||
/// this reason, Rocket disallows constructing host-prefixed URIs with
|
||||
/// [`uri!`]. _Always_ use [`uri!`] to construct URIs.
|
||||
///
|
||||
/// [`uri!`]: crate::uri!
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Retrieve the raw host, unusable to construct safe URIs:
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::http::uri::Host;
|
||||
/// # use rocket::uri;
|
||||
/// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
|
||||
/// # let mut req = c.get("/");
|
||||
/// # let request = req.inner_mut();
|
||||
///
|
||||
/// assert_eq!(request.host(), None);
|
||||
///
|
||||
/// request.set_host(Host::from(uri!("rocket.rs")));
|
||||
/// let host = request.host().unwrap();
|
||||
/// assert_eq!(host.domain(), "rocket.rs");
|
||||
/// assert_eq!(host.port(), None);
|
||||
///
|
||||
/// request.set_host(Host::from(uri!("rocket.rs:2392")));
|
||||
/// let host = request.host().unwrap();
|
||||
/// assert_eq!(host.domain(), "rocket.rs");
|
||||
/// assert_eq!(host.port(), Some(2392));
|
||||
/// ```
|
||||
///
|
||||
/// Retrieve the raw host, check it against a whitelist, and construct a
|
||||
/// URI:
|
||||
///
|
||||
/// ```rust
|
||||
/// # #[macro_use] extern crate rocket;
|
||||
/// # type Token = String;
|
||||
/// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
|
||||
/// # let mut req = c.get("/");
|
||||
/// # let request = req.inner_mut();
|
||||
/// use rocket::http::uri::Host;
|
||||
///
|
||||
/// // A sensitive URI we want to prefix with safe hosts.
|
||||
/// #[get("/token?<secret>")]
|
||||
/// fn token(secret: Token) { /* .. */ }
|
||||
///
|
||||
/// // Whitelist of known hosts. In a real setting, you might retrieve this
|
||||
/// // list from config at ignite-time using tools like `AdHoc::config()`.
|
||||
/// const WHITELIST: [Host<'static>; 3] = [
|
||||
/// Host::new(uri!("rocket.rs")),
|
||||
/// Host::new(uri!("rocket.rs:443")),
|
||||
/// Host::new(uri!("guide.rocket.rs:443")),
|
||||
/// ];
|
||||
///
|
||||
/// // A request with a host of "rocket.rs". Note the case-insensitivity.
|
||||
/// request.set_host(Host::from(uri!("ROCKET.rs")));
|
||||
/// let prefix = request.host().and_then(|h| h.to_absolute("https", &WHITELIST));
|
||||
///
|
||||
/// // `rocket.rs` is in the whitelist, so we'll get back a `Some`.
|
||||
/// assert!(prefix.is_some());
|
||||
/// if let Some(prefix) = prefix {
|
||||
/// // We can use this prefix to safely construct URIs.
|
||||
/// let uri = uri!(prefix, token("some-secret-token"));
|
||||
/// assert_eq!(uri, "https://ROCKET.rs/token?secret=some-secret-token");
|
||||
/// }
|
||||
///
|
||||
/// // A request with a host of "attacker-controlled.com".
|
||||
/// request.set_host(Host::from(uri!("attacker-controlled.com")));
|
||||
/// let prefix = request.host().and_then(|h| h.to_absolute("https", &WHITELIST));
|
||||
///
|
||||
/// // `attacker-controlled.come` is _not_ on the whitelist.
|
||||
/// assert!(prefix.is_none());
|
||||
/// assert!(request.host().is_some());
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn host(&self) -> Option<&Host<'r>> {
|
||||
self.host.as_ref()
|
||||
}
|
||||
|
||||
/// Sets the host of `self` to `host`.
|
||||
///
|
||||
/// # Example
|
||||
///
|
||||
/// Set the host to `rocket.rs:443`.
|
||||
///
|
||||
/// ```rust
|
||||
/// use rocket::http::uri::Host;
|
||||
/// # use rocket::uri;
|
||||
/// # let c = rocket::local::blocking::Client::debug_with(vec![]).unwrap();
|
||||
/// # let mut req = c.get("/");
|
||||
/// # let request = req.inner_mut();
|
||||
///
|
||||
/// assert_eq!(request.host(), None);
|
||||
///
|
||||
/// request.set_host(Host::from(uri!("rocket.rs:443")));
|
||||
/// let host = request.host().unwrap();
|
||||
/// assert_eq!(host.domain(), "rocket.rs");
|
||||
/// assert_eq!(host.port(), Some(443));
|
||||
/// ```
|
||||
#[inline(always)]
|
||||
pub fn set_host(&mut self, host: Host<'r>) {
|
||||
self.host = Some(host);
|
||||
}
|
||||
|
||||
/// Returns the raw address of the remote connection that initiated this
|
||||
/// request if the address is known. If the address is not known, `None` is
|
||||
/// returned.
|
||||
|
@ -850,6 +967,14 @@ impl<'r> Request<'r> {
|
|||
let mut request = Request::new(rocket, method, uri);
|
||||
request.set_remote(addr);
|
||||
|
||||
// Determine the host. On HTTP < 2, use the `HOST` header. Otherwise,
|
||||
// use the `:authority` pseudo-header which hyper makes part of the URI.
|
||||
request.host = if hyper.version < hyper::Version::HTTP_2 {
|
||||
hyper.headers.get("host").and_then(|h| Host::parse_bytes(h.as_bytes()).ok())
|
||||
} else {
|
||||
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()) {
|
||||
|
|
Loading…
Reference in New Issue