diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 0c2347ba306..6acd4cba4a4 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -588,7 +588,7 @@ pub async fn main_fetch( }, }; - // Step 13. + // Step 13. If recursive is true, then return response. if recursive_flag { return response; } @@ -596,7 +596,7 @@ pub async fn main_fetch( // reborrow request to avoid double mutable borrow let request = &mut fetch_params.request; - // Step 14. + // Step 14. If response is not a network error and response is not a filtered response, then: let mut response = if !response.is_network_error() && response.internal_response.is_none() { // Substep 1. if request.response_tainting == ResponseTainting::CorsTainting { @@ -675,6 +675,9 @@ pub async fn main_fetch( internal_response.url_list.clone_from(&request.url_list) } + // Step 17. Set internalResponse’s redirect taint to request’s redirect-taint. + internal_response.redirect_taint = request.redirect_taint_for_request(); + // Step 19. If response is not a network error and any of the following returns blocked // * should internalResponse to request be blocked as mixed content // * should internalResponse to request be blocked by Content Security Policy @@ -745,7 +748,7 @@ pub async fn main_fetch( response }; - // Step 19. + // Step 19. If response is not a network error and any of the following returns blocked let mut response_loaded = false; let mut response = if !response.is_network_error() && !request.integrity_metadata.is_empty() { // Step 19.1. diff --git a/components/net/http_loader.rs b/components/net/http_loader.rs index 55d13edb500..7f1ed960123 100644 --- a/components/net/http_loader.rs +++ b/components/net/http_loader.rs @@ -55,7 +55,9 @@ use net_traits::request::{ is_cors_non_wildcard_request_header_name, is_cors_safelisted_method, is_cors_safelisted_request_header, }; -use net_traits::response::{CacheState, HttpsState, Response, ResponseBody, ResponseType}; +use net_traits::response::{ + CacheState, HttpsState, RedirectTaint, Response, ResponseBody, ResponseType, +}; use net_traits::{ CookieSource, DOCUMENT_ACCEPT_HEADER_VALUE, DebugVec, FetchMetadata, NetworkError, RedirectEndValue, RedirectStartValue, ReferrerPolicy, ResourceAttribute, ResourceFetchTiming, @@ -2558,9 +2560,8 @@ fn cors_check(request: &Request, response: &Response) -> Result<(), ()> { } // Step 4. If the result of byte-serializing a request origin with request is not origin, then return failure. - match request.origin { - Origin::Origin(ref o) if *o.ascii_serialization() == *origin => {}, - _ => return Err(()), + if serialize_request_origin(request).to_string() != origin { + return Err(()); } // Step 5. If request’s credentials mode is not "include", then return success. @@ -2606,38 +2607,6 @@ fn is_redirect_status(status: StatusCode) -> bool { ) } -/// -fn request_has_redirect_tainted_origin(request: &Request) -> bool { - // Step 1. Assert: request’s origin is not "client". - let Origin::Origin(request_origin) = &request.origin else { - panic!("origin cannot be \"client\" at this point in time"); - }; - - // Step 2. Let lastURL be null. - let mut last_url = None; - - // Step 3. For each url of request’s URL list: - for url in &request.url_list { - // Step 3.1 If lastURL is null, then set lastURL to url and continue. - let Some(last_url) = &mut last_url else { - last_url = Some(url); - continue; - }; - - // Step 3.2 If url’s origin is not same origin with lastURL’s origin and - // request’s origin is not same origin with lastURL’s origin, then return true. - if url.origin() != last_url.origin() && *request_origin != last_url.origin() { - return true; - } - - // Step 3.3 Set lastURL to url. - *last_url = url; - } - - // Step 4. Return false. - false -} - /// fn serialize_request_origin(request: &Request) -> headers::Origin { // Step 1. Assert: request’s origin is not "client". @@ -2645,8 +2614,8 @@ fn serialize_request_origin(request: &Request) -> headers::Origin { panic!("origin cannot be \"client\" at this point in time"); }; - // Step 2. If request has a redirect-tainted origin, then return "null". - if request_has_redirect_tainted_origin(request) { + // Step 2. If request’s redirect-taint is not "same-origin", then return "null". + if request.redirect_taint_for_request() != RedirectTaint::SameOrigin { return headers::Origin::NULL; } diff --git a/components/shared/net/request.rs b/components/shared/net/request.rs index d578173a273..954da418c9e 100644 --- a/components/shared/net/request.rs +++ b/components/shared/net/request.rs @@ -22,7 +22,8 @@ use url::Position; use uuid::Uuid; use crate::policy_container::{PolicyContainer, RequestPolicyContainer}; -use crate::response::{HttpsState, Response}; +use crate::pub_domains::is_same_site; +use crate::response::{HttpsState, RedirectTaint, Response}; use crate::{ReferrerPolicy, ResourceTimingType}; #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] @@ -1001,6 +1002,49 @@ impl Request { // Step 5. Return totalRequestLength. total_request_length } + + /// + pub fn redirect_taint_for_request(&self) -> RedirectTaint { + // Step 1. Assert: request’s origin is not "client". + let Origin::Origin(request_origin) = &self.origin else { + unreachable!("origin cannot be \"client\" at this point in time"); + }; + + // Step 2. Let lastURL be null. + let mut last_url = None; + + // Step 3. Let taint be "same-origin". + let mut taint = RedirectTaint::SameOrigin; + + // Step 4. For each url of request’s URL list: + for url in &self.url_list { + // Step 4.1 If lastURL is null, then set lastURL to url and continue. + let Some(last_url) = &mut last_url else { + last_url = Some(url); + continue; + }; + + // Step 4.2. If url’s origin is not same site with lastURL’s origin and + // request’s origin is not same site with lastURL’s origin, then return "cross-site". + if !is_same_site(&url.origin(), &last_url.origin()) && + !is_same_site(request_origin, &last_url.origin()) + { + return RedirectTaint::CrossSite; + } + + // Step 4.3. If url’s origin is not same origin with lastURL’s origin + // and request’s origin is not same origin with lastURL’s origin, then set taint to "same-site". + if url.origin() != last_url.origin() && *request_origin != last_url.origin() { + taint = RedirectTaint::SameSite; + } + + // Step 4.4 Set lastURL to url. + *last_url = url; + } + + // Step 5. Return taint. + taint + } } impl Referrer { diff --git a/components/shared/net/response.rs b/components/shared/net/response.rs index 5569a2e70e9..cfdb9ac8dac 100644 --- a/components/shared/net/response.rs +++ b/components/shared/net/response.rs @@ -58,6 +58,15 @@ impl ResponseBody { } } +/// +#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)] +pub enum RedirectTaint { + #[default] + SameOrigin, + SameSite, + CrossSite, +} + /// [Cache state](https://fetch.spec.whatwg.org/#concept-response-cache-state) #[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, Serialize)] pub enum CacheState { @@ -109,6 +118,8 @@ pub struct Response { pub https_state: HttpsState, pub tls_security_info: Option, pub referrer: Option, + /// + pub redirect_taint: RedirectTaint, pub referrer_policy: ReferrerPolicy, /// [CORS-exposed header-name list](https://fetch.spec.whatwg.org/#concept-response-cors-exposed-header-name-list) pub cors_exposed_header_name_list: Vec, @@ -152,6 +163,7 @@ impl Response { aborted: Arc::new(AtomicBool::new(false)), resource_timing: Arc::new(Mutex::new(resource_timing)), range_requested: false, + redirect_taint: Default::default(), } } @@ -187,6 +199,7 @@ impl Response { ResourceTimingType::Error, ))), range_requested: false, + redirect_taint: Default::default(), } }