From 46d3d94fbb0e524d9c7eb3063b27b18af7f3506d Mon Sep 17 00:00:00 2001 From: Yong Wen Chua Date: Mon, 18 Mar 2019 09:32:25 +0800 Subject: [PATCH] Fix issue validating opaque origins --- src/headers.rs | 10 ++++- src/lib.rs | 111 ++++++++++++++++++++++++++++++++++++------------- 2 files changed, 90 insertions(+), 31 deletions(-) diff --git a/src/headers.rs b/src/headers.rs index 6e0a099..2b5b2e2 100644 --- a/src/headers.rs +++ b/src/headers.rs @@ -71,6 +71,8 @@ pub enum Origin { Null, /// A well-formed origin that was parsed by [`url::Url::origin`] Parsed(url::Origin), + /// An unknown "opaque" origin that could not be parsed + Opaque(String), } impl Origin { @@ -86,6 +88,7 @@ impl Origin { match self { Origin::Null => false, Origin::Parsed(ref parsed) => parsed.is_tuple(), + Origin::Opaque(_) => false, } } } @@ -97,7 +100,11 @@ impl FromStr for Origin { if input.to_lowercase() == "null" { Ok(Origin::Null) } else { - Ok(Origin::Parsed(crate::to_origin(input)?)) + // Ok(Origin::Parsed(crate::to_origin(input)?)) + match crate::to_origin(input)? { + url::Origin::Opaque(_) => Ok(Origin::Opaque(input.to_string())), + parsed @ url::Origin::Tuple(..) => Ok(Origin::Parsed(parsed)), + } } } } @@ -107,6 +114,7 @@ impl fmt::Display for Origin { match self { Origin::Null => write!(f, "null"), Origin::Parsed(ref parsed) => write!(f, "{}", parsed.ascii_serialization()), + Origin::Opaque(ref opaque) => write!(f, "{}", opaque), } } } diff --git a/src/lib.rs b/src/lib.rs index cc3fb17..343978b 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -282,12 +282,12 @@ use std::marker::PhantomData; use std::ops::Deref; use std::str::FromStr; -use ::log::{error, info, log}; +use ::log::{debug, error, info, log}; use regex::RegexSet; use rocket::http::{self, Status}; use rocket::request::{FromRequest, Request}; use rocket::response; -use rocket::{error_, info_, log_, Outcome, State}; +use rocket::{debug_, error_, info_, log_, Outcome, State}; #[cfg(feature = "serialization")] use serde_derive::{Deserialize, Serialize}; @@ -356,33 +356,55 @@ impl Error { impl fmt::Display for Error { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { match self { - Error::MissingOrigin => write!(f, "The request header `Origin` is required but is missing"), + Error::MissingOrigin => write!( + f, + "The request header `Origin` is \ + required but is missing" + ), Error::BadOrigin(_) => write!(f, "The request header `Origin` contains an invalid URL"), - Error::MissingRequestMethod => { write!(f, + Error::MissingRequestMethod => write!( + f, "The request header `Access-Control-Request-Method` \ - is required but is missing") - } - Error::BadRequestMethod => { write!(f, - "The request header `Access-Control-Request-Method` has an invalid value") - } - Error::MissingRequestHeaders => { write!(f, + is required but is missing" + ), + Error::BadRequestMethod => write!( + f, + "The request header `Access-Control-Request-Method` has an invalid value" + ), + Error::MissingRequestHeaders => write!( + f, "The request header `Access-Control-Request-Headers` \ - is required but is missing") - } - Error::OriginNotAllowed(origin) => write!(f, "Origin '{}' is not allowed to request", origin), + is required but is missing" + ), + Error::OriginNotAllowed(origin) => write!( + f, + "Origin '{}' is \ + not allowed to request", + origin + ), Error::MethodNotAllowed(method) => write!(f, "Method '{}' is not allowed", &method), Error::HeadersNotAllowed => write!(f, "Headers are not allowed"), - Error::CredentialsWithWildcardOrigin => { write!(f, + Error::CredentialsWithWildcardOrigin => write!( + f, "Credentials are allowed, but the Origin is set to \"*\". \ - This is not allowed by W3C") - } - Error::MissingCorsInRocketState => { write!(f, - "A CORS Request Guard was used, but no CORS Options was available in Rocket's state") - } - Error::MissingInjectedHeader => write!(f, + This is not allowed by W3C" + ), + Error::MissingCorsInRocketState => write!( + f, + "A CORS Request Guard was used, but no CORS Options \ + was available in Rocket's state" + ), + Error::MissingInjectedHeader => { + write!(f, "The `on_response` handler of Fairing could not find the injected header from the \ - Request. Either some other fairing has removed it, or this is a bug."), - Error::OpaqueAllowedOrigin(ref origin) => write!(f, "The configured Origin '{}' not have a parsable Origin. Use a regex instead.", origin), + Request. Either some other fairing has removed it, or this is a bug.") + } + Error::OpaqueAllowedOrigin(ref origin) => write!( + f, + "The configured Origin '{}' does \ + not have a parsable Origin. Use a regex instead.", + origin + ), Error::RegexError(ref e) => write!(f, "{}", e), } } @@ -664,13 +686,16 @@ impl ParsedAllowedOrigins { } fn verify(&self, origin: &Origin) -> bool { - info_!("Verifying origin: {}", origin); match origin { Origin::Null => { info_!("Origin is null. Allowing? {}", self.allow_null); self.allow_null } Origin::Parsed(ref parsed) => { + assert!( + parsed.is_tuple(), + "Parsed Origin is not tuple. This is a bug. Please report" + ); // Verify by exact, then regex if self.exact.get(parsed).is_some() { info_!("Origin has an exact match"); @@ -678,6 +703,18 @@ impl ParsedAllowedOrigins { } if let Some(regex_set) = &self.regex { let regex_match = regex_set.is_match(&parsed.ascii_serialization()); + debug_!("Matching against regex set {:#?}", regex_set); + info_!("Origin has a regex match? {}", regex_match); + return regex_match; + } + + info!("Origin does not match anything"); + false + } + Origin::Opaque(ref opaque) => { + if let Some(regex_set) = &self.regex { + let regex_match = regex_set.is_match(opaque); + debug_!("Matching against regex set {:#?}", regex_set); info_!("Origin has a regex match? {}", regex_match); return regex_match; } @@ -992,7 +1029,7 @@ impl Cors { pub fn from_options(options: &CorsOptions) -> Result { options.validate()?; - let allowed_origins = parse_origins(&options.allowed_origins)?; + let allowed_origins = parse_allowed_origins(&options.allowed_origins)?; Ok(Cors { allowed_origins, @@ -1407,7 +1444,9 @@ fn to_origin>(origin: S) -> Result { } /// Parse and process allowed origins -fn parse_origins(origins: &AllowedOrigins) -> Result, Error> { +fn parse_allowed_origins( + origins: &AllowedOrigins, +) -> Result, Error> { match origins { AllOrSome::All => Ok(AllOrSome::All), AllOrSome::Some(origins) => { @@ -1950,7 +1989,7 @@ mod tests { fn validate_origin_allows_origin() { let url = "https://www.example.com"; let origin = not_err!(to_parsed_origin(&url)); - let allowed_origins = not_err!(parse_origins(&AllowedOrigins::some_exact(&[ + let allowed_origins = not_err!(parse_allowed_origins(&AllowedOrigins::some_exact(&[ "https://www.example.com" ]))); @@ -1969,7 +2008,7 @@ mod tests { for (url, allowed_origin) in cases { let origin = not_err!(to_parsed_origin(&url)); - let allowed_origins = not_err!(parse_origins(&AllowedOrigins::some_exact(&[ + let allowed_origins = not_err!(parse_allowed_origins(&AllowedOrigins::some_exact(&[ allowed_origin ]))); @@ -1981,16 +2020,28 @@ mod tests { fn validate_origin_validates_regex() { let url = "https://www.example-something.com"; let origin = not_err!(to_parsed_origin(&url)); - let allowed_origins = not_err!(parse_origins(&AllowedOrigins::some_regex(&[ + let allowed_origins = not_err!(parse_allowed_origins(&AllowedOrigins::some_regex(&[ "^https://www.example-[A-z0-9]+.com$" ]))); not_err!(validate_origin(&origin, &allowed_origins)); } + #[test] + fn validate_origin_validates_opaque_origins() { + let url = "moz-extension://8c7c4444-e29f-…cb8-1ade813dbd12/js/content.js:505"; + let origin = not_err!(to_parsed_origin(&url)); + println!("{:#?}", origin); + let allowed_origins = not_err!(parse_allowed_origins(&AllowedOrigins::some_regex(&[ + "moz-extension://.*" + ]))); + + not_err!(validate_origin(&origin, &allowed_origins)); + } + #[test] fn validate_origin_validates_mixed_settings() { - let allowed_origins = not_err!(parse_origins(&AllowedOrigins::some( + let allowed_origins = not_err!(parse_allowed_origins(&AllowedOrigins::some( &["https://www.acme.com"], &["^https://www.example-[A-z0-9]+.com$"] ))); @@ -2009,7 +2060,7 @@ mod tests { fn validate_origin_rejects_invalid_origin() { let url = "https://www.acme.com"; let origin = not_err!(to_parsed_origin(&url)); - let allowed_origins = not_err!(parse_origins(&AllowedOrigins::some_exact(&[ + let allowed_origins = not_err!(parse_allowed_origins(&AllowedOrigins::some_exact(&[ "https://www.example.com" ])));