diff --git a/components/net/fetch/methods.rs b/components/net/fetch/methods.rs index 697a46fedda..53bc2817292 100644 --- a/components/net/fetch/methods.rs +++ b/components/net/fetch/methods.rs @@ -42,7 +42,7 @@ use crate::fetch::cors_cache::CorsCache; use crate::fetch::headers::determine_nosniff; use crate::filemanager_thread::FileManager; use crate::http_loader::{HttpState, determine_requests_referrer, http_fetch, set_default_accept}; -use crate::protocols::ProtocolRegistry; +use crate::protocols::{ProtocolRegistry, is_url_potentially_trustworthy}; use crate::request_interceptor::RequestInterceptor; use crate::subresource_integrity::is_response_integrity_valid; @@ -247,7 +247,7 @@ pub async fn main_fetch( // Step 4. Upgrade request to a potentially trustworthy URL, if appropriate. if should_upgrade_request_to_potentially_trustworty(request, context) || - should_upgrade_mixed_content_request(request) + should_upgrade_mixed_content_request(request, &context.protocols) { trace!( "upgrading {} targeting {:?}", @@ -294,7 +294,7 @@ pub async fn main_fetch( "Request attempted on bad port".into(), ))); } - if should_request_be_blocked_as_mixed_content(request) { + if should_request_be_blocked_as_mixed_content(request, &context.protocols) { response = Some(Response::network_error(NetworkError::Internal( "Blocked as mixed content".into(), ))); @@ -359,13 +359,16 @@ pub async fn main_fetch( if (same_origin && request.response_tainting == ResponseTainting::Basic) || // request's current URL's scheme is "data" current_scheme == "data" || + // Note: Although it is not part of the specification, we make an exception here + // for custom protocols that are explicitly marked as active for fetch. + context.protocols.is_fetchable(current_scheme) || // request's mode is "navigate" or "websocket" matches!( request.mode, RequestMode::Navigate | RequestMode::WebSocket { .. } ) { - // Substep 1. Set request’s response tainting to "basic". + // Substep 1. Set request's response tainting to "basic". request.response_tainting = ResponseTainting::Basic; // Substep 2. Return the result of running scheme fetch given fetchParams. @@ -373,13 +376,13 @@ pub async fn main_fetch( } else if request.mode == RequestMode::SameOrigin { Response::network_error(NetworkError::Internal("Cross-origin response".into())) } else if request.mode == RequestMode::NoCors { - // Substep 1. If request’s redirect mode is not "follow", then return a network error. + // Substep 1. If request's redirect mode is not "follow", then return a network error. if request.redirect_mode != RedirectMode::Follow { Response::network_error(NetworkError::Internal( "NoCors requests must follow redirects".into(), )) } else { - // Substep 2. Set request’s response tainting to "opaque". + // Substep 2. Set request's response tainting to "opaque". request.response_tainting = ResponseTainting::Opaque; // Substep 3. Return the result of running scheme fetch given fetchParams. @@ -490,7 +493,7 @@ pub async fn main_fetch( let should_replace_with_mime_type_error = !response_is_network_error && should_be_blocked_due_to_mime_type(request.destination, &response.headers); let should_replace_with_mixed_content = !response_is_network_error && - should_response_be_blocked_as_mixed_content(request, &response); + should_response_be_blocked_as_mixed_content(request, &response, &context.protocols); // Step 15. let mut network_error_response = response @@ -933,7 +936,10 @@ pub fn should_request_be_blocked_due_to_a_bad_port(url: &ServoUrl) -> bool { } /// -pub fn should_request_be_blocked_as_mixed_content(request: &Request) -> bool { +pub fn should_request_be_blocked_as_mixed_content( + request: &Request, + protocol_registry: &ProtocolRegistry, +) -> bool { // Step 1. Return allowed if one or more of the following conditions are met: // 1.1. Does settings prohibit mixed security contexts? // returns "Does Not Restrict Mixed Security Contexts" when applied to request’s client. @@ -944,7 +950,7 @@ pub fn should_request_be_blocked_as_mixed_content(request: &Request) -> bool { } // 1.2. request’s URL is a potentially trustworthy URL. - if request.url().is_potentially_trustworthy() { + if is_url_potentially_trustworthy(protocol_registry, &request.url()) { return false; } @@ -961,7 +967,11 @@ pub fn should_request_be_blocked_as_mixed_content(request: &Request) -> bool { } /// -pub fn should_response_be_blocked_as_mixed_content(request: &Request, response: &Response) -> bool { +pub fn should_response_be_blocked_as_mixed_content( + request: &Request, + response: &Response, + protocol_registry: &ProtocolRegistry, +) -> bool { // Step 1. Return allowed if one or more of the following conditions are met: // 1.1. Does settings prohibit mixed security contexts? returns Does Not Restrict Mixed Content // when applied to request’s client. @@ -975,7 +985,7 @@ pub fn should_response_be_blocked_as_mixed_content(request: &Request, response: if response .actual_response() .url() - .is_some_and(|response_url| response_url.is_potentially_trustworthy()) + .is_some_and(|response_url| is_url_potentially_trustworthy(protocol_registry, response_url)) { return false; } @@ -1041,7 +1051,7 @@ fn should_upgrade_request_to_potentially_trustworty( // request’s header list if any of the following criteria are met: // * request’s URL is not a potentially trustworthy URL // * request’s URL's host is not a preloadable HSTS host - if !request.current_url().is_potentially_trustworthy() || + if !is_url_potentially_trustworthy(&context.protocols, &request.current_url()) || !request.current_url().host_str().is_some_and(|host| { !context.state.hsts_list.read().unwrap().is_host_secure(host) }) @@ -1094,10 +1104,13 @@ fn do_settings_prohibit_mixed_security_contexts(request: &Request) -> MixedSecur } /// -fn should_upgrade_mixed_content_request(request: &Request) -> bool { +fn should_upgrade_mixed_content_request( + request: &Request, + protocol_registry: &ProtocolRegistry, +) -> bool { let url = request.url(); // Step 1.1 : request’s URL is a potentially trustworthy URL. - if url.is_potentially_trustworthy() { + if is_url_potentially_trustworthy(protocol_registry, &url) { return false; } diff --git a/components/net/protocols/mod.rs b/components/net/protocols/mod.rs index af1557f63e2..6dc58ceab64 100644 --- a/components/net/protocols/mod.rs +++ b/components/net/protocols/mod.rs @@ -14,6 +14,7 @@ use log::error; use net_traits::filemanager_thread::RelativePos; use net_traits::request::Request; use net_traits::response::Response; +use servo_url::ServoUrl; use crate::fetch::methods::{DoneChannel, FetchContext, RangeRequestBounds}; @@ -47,6 +48,15 @@ pub trait ProtocolHandler: Send + Sync { fn is_fetchable(&self) -> bool { false } + + /// Specify if this custom protocol can be used in a [secure context] + /// + /// Note: this only works for bypassing mixed content checks right now + /// + /// [secure context]: https://developer.mozilla.org/en-US/docs/Web/Security/Secure_Contexts + fn is_secure(&self) -> bool { + false + } } #[derive(Default)] @@ -114,9 +124,22 @@ impl ProtocolRegistry { pub fn is_fetchable(&self, scheme: &str) -> bool { self.handlers .get(scheme) - .map(|handler| handler.is_fetchable()) - .unwrap_or(false) + .is_some_and(|handler| handler.is_fetchable()) } + + pub fn is_secure(&self, scheme: &str) -> bool { + self.handlers + .get(scheme) + .is_some_and(|handler| handler.is_secure()) + } +} + +/// Test if the URL is potentially trustworthy or the custom protocol is registered as secure +pub fn is_url_potentially_trustworthy( + protocol_registry: &ProtocolRegistry, + url: &ServoUrl, +) -> bool { + url.is_potentially_trustworthy() || protocol_registry.is_secure(url.scheme()) } pub fn range_not_satisfiable_error(response: &mut Response) { diff --git a/ports/servoshell/desktop/protocols/urlinfo.rs b/ports/servoshell/desktop/protocols/urlinfo.rs index 4408ce47e52..14ad6bdb549 100644 --- a/ports/servoshell/desktop/protocols/urlinfo.rs +++ b/ports/servoshell/desktop/protocols/urlinfo.rs @@ -46,4 +46,8 @@ impl ProtocolHandler for UrlInfoProtocolHander { fn is_fetchable(&self) -> bool { true } + + fn is_secure(&self) -> bool { + true + } }