/
githubmirror
/
servo
Обзор
Документация
Войти
/
githubmirror
/
servo
Код
Запросы
0
Пакеты
0
Релизы
0
Аналитика
Безопасность
main
components/shared/net/request.rs
1 300 строк
46 KB
Taym Haddadi
fetch: implement Request navigation flags (#46917)
01 авг 2026, 23:19
Не верифицирован
01 авг 2026, 23:19
d97c8c9
Код
Авторство
О чём код?
/* This Source Code Form is subject to the terms of the Mozilla Public * License, v. 2.0. If a copy of the MPL was not distributed with this * file, You can obtain one at https://mozilla.org/MPL/2.0/. */ use std::sync::Arc; use content_security_policy::{self as csp}; use http::header::{AUTHORIZATION, HeaderName}; use http::{HeaderMap, Method}; use ipc_channel::ipc::{self, IpcReceiver, IpcSender}; use ipc_channel::router::ROUTER; use log::error; use malloc_size_of_derive::MallocSizeOf; use mime::Mime; use parking_lot::Mutex; use rustc_hash::FxHashMap; use serde::{Deserialize, Serialize}; use servo_base::generic_channel::GenericSharedMemory; use servo_base::id::{PipelineId, WebViewId}; use servo_url::{ImmutableOrigin, ServoUrl}; use tokio::sync::oneshot::Sender as TokioSender; use url::Position; use uuid::Uuid; use crate::ReferrerPolicy; use crate::blob_url_store::UrlWithBlobClaim; use crate::policy_container::{PolicyContainer, RequestPolicyContainer}; use crate::pub_domains::is_same_site; use crate::resource_fetch_timing::ResourceTimingType; use crate::response::{RedirectTaint, Response}; #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] /// An id to differentiate one network request from another. pub struct RequestId(pub Uuid); impl Default for RequestId { fn default() -> Self { Self(Uuid::new_v4()) } } /// An [initiator](https://fetch.spec.whatwg.org/#concept-request-initiator) #[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum Initiator { None, Download, ImageSet, Manifest, XSLT, Prefetch, Link, } /// A request [destination](https://fetch.spec.whatwg.org/#concept-request-destination) pub use csp::Destination; /// A request [origin](https://fetch.spec.whatwg.org/#concept-request-origin) #[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum Origin { Client, Origin(ImmutableOrigin), } impl Origin { pub fn is_opaque(&self) -> bool { matches!(self, Origin::Origin(ImmutableOrigin::Opaque(_))) } } /// A [referer](https://fetch.spec.whatwg.org/#concept-request-referrer) #[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum Referrer { NoReferrer, /// Contains the url that "client" would be resolved to. See /// [https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer](https://w3c.github.io/webappsec-referrer-policy/#determine-requests-referrer) /// /// If you are unsure you should probably use /// [`GlobalScope::get_referrer`](https://doc.servo.org/script/dom/globalscope/struct.GlobalScope.html#method.get_referrer) Client(ServoUrl), ReferrerUrl(ServoUrl), } /// A [request mode](https://fetch.spec.whatwg.org/#concept-request-mode) #[derive(Clone, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub enum RequestMode { Navigate, SameOrigin, NoCors, CorsMode, WebSocket { protocols: Vec<String>, original_url: ServoUrl, }, } /// Request [credentials mode](https://fetch.spec.whatwg.org/#concept-request-credentials-mode) #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub enum CredentialsMode { Omit, CredentialsSameOrigin, Include, } /// [Cache mode](https://fetch.spec.whatwg.org/#concept-request-cache-mode) #[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum CacheMode { Default, NoStore, Reload, NoCache, ForceCache, OnlyIfCached, } /// [Service-workers mode](https://fetch.spec.whatwg.org/#request-service-workers-mode) #[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum ServiceWorkersMode { All, None, } /// [Redirect mode](https://fetch.spec.whatwg.org/#concept-request-redirect-mode) #[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum RedirectMode { Follow, Error, Manual, } /// [Response tainting](https://fetch.spec.whatwg.org/#concept-request-response-tainting) #[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum ResponseTainting { Basic, CorsTainting, Opaque, } /// Servo-internal to keep track of which requests originate from Servo internal implementation #[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum InternalRequest { Yes, #[default] No, } /// <https://html.spec.whatwg.org/multipage/#preload-key> #[derive(Clone, Debug, Eq, Hash, Deserialize, MallocSizeOf, Serialize, PartialEq)] pub struct PreloadKey { /// <https://html.spec.whatwg.org/multipage/#preload-url> pub url: ServoUrl, /// <https://html.spec.whatwg.org/multipage/#preload-destination> pub destination: Destination, /// <https://html.spec.whatwg.org/multipage/#preload-mode> pub mode: RequestMode, /// <https://html.spec.whatwg.org/multipage/#preload-credentials-mode> pub credentials_mode: CredentialsMode, } impl PreloadKey { pub fn new(request: &RequestBuilder) -> Self { Self { url: request.url.url(), destination: request.destination, mode: request.mode.clone(), credentials_mode: request.credentials_mode, } } } #[derive(PartialEq, Eq, Clone, Debug, Serialize, Deserialize, Hash, MallocSizeOf)] pub struct PreloadId(pub Uuid); impl Default for PreloadId { fn default() -> Self { Self(Uuid::new_v4()) } } /// <https://html.spec.whatwg.org/multipage/#preload-entry> #[derive(Debug, MallocSizeOf)] pub struct PreloadEntry { /// <https://html.spec.whatwg.org/multipage/#preload-integrity-metadata> pub integrity_metadata: String, /// <https://html.spec.whatwg.org/multipage/#preload-response> pub response: Option<Response>, /// <https://html.spec.whatwg.org/multipage/#preload-on-response-available> pub on_response_available: Option<TokioSender<Response>>, } impl PreloadEntry { pub fn new(integrity_metadata: String) -> Self { Self { integrity_metadata, response: None, on_response_available: None, } } /// Part of step 11.5 of <https://html.spec.whatwg.org/multipage/#preload> pub fn with_response(&mut self, response: Response) { // Step 11.5. If entry's on response available is null, then set entry's response to response; // otherwise call entry's on response available given response. if let Some(sender) = self.on_response_available.take() { let _ = sender.send(response); } else { self.response = Some(response); } } } pub type PreloadedResources = FxHashMap<PreloadKey, PreloadId>; /// <https://fetch.spec.whatwg.org/#concept-request-client> #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct RequestClient { /// <https://html.spec.whatwg.org/multipage/#map-of-preloaded-resources> pub preloaded_resources: PreloadedResources, /// <https://html.spec.whatwg.org/multipage/#concept-settings-object-policy-container> pub policy_container: PolicyContainer, /// <https://html.spec.whatwg.org/multipage/#concept-settings-object-origin> pub origin: Origin, /// <https://html.spec.whatwg.org/multipage/#nested-browsing-context> pub is_nested_browsing_context: bool, /// <https://w3c.github.io/webappsec-upgrade-insecure-requests/#insecure-requests-policy> pub insecure_requests_policy: InsecureRequestsPolicy, /// <https://w3c.github.io/webappsec-secure-contexts/#potentially-trustworthy-origin> pub has_trustworthy_ancestor_origin: bool, } /// <https://html.spec.whatwg.org/multipage/#system-visibility-state> #[derive(Clone, Copy, Default, MallocSizeOf, PartialEq)] pub enum SystemVisibilityState { #[default] Hidden, Visible, } /// <https://html.spec.whatwg.org/multipage/#traversable-navigable> #[derive(Clone, Copy, Default, MallocSizeOf, PartialEq)] pub struct TraversableNavigable { /// <https://html.spec.whatwg.org/multipage/#tn-current-session-history-step> current_session_history_step: u8, // TODO: https://html.spec.whatwg.org/multipage/#tn-session-history-entries // TODO: https://html.spec.whatwg.org/multipage/#tn-session-history-traversal-queue /// <https://html.spec.whatwg.org/multipage/#tn-running-nested-apply-history-step> running_nested_apply_history_step: bool, /// <https://html.spec.whatwg.org/multipage/#system-visibility-state> system_visibility_state: SystemVisibilityState, /// <https://html.spec.whatwg.org/multipage/#is-created-by-web-content> is_created_by_web_content: bool, } /// <https://fetch.spec.whatwg.org/#concept-request-window> #[derive(Clone, Copy, MallocSizeOf, PartialEq)] pub enum TraversableForUserPrompts { NoTraversable, Client, TraversableNavigable(TraversableNavigable), } /// [CORS settings attribute](https://html.spec.whatwg.org/multipage/#attr-crossorigin-anonymous) #[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)] pub enum CorsSettings { Anonymous, UseCredentials, } impl CorsSettings { /// <https://html.spec.whatwg.org/multipage/#cors-settings-attribute> pub fn from_enumerated_attribute(value: &str) -> CorsSettings { match value.to_ascii_lowercase().as_str() { "anonymous" => CorsSettings::Anonymous, "use-credentials" => CorsSettings::UseCredentials, _ => CorsSettings::Anonymous, } } } /// [Parser Metadata](https://fetch.spec.whatwg.org/#concept-request-parser-metadata) #[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum ParserMetadata { Default, ParserInserted, NotParserInserted, } /// <https://fetch.spec.whatwg.org/#concept-body-source> #[derive(Clone, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum BodySource { Null, Object, } /// Messages used to implement <https://fetch.spec.whatwg.org/#concept-request-transmit-body> /// which are sent from script to net. #[derive(Debug, Deserialize, Serialize)] pub enum BodyChunkResponse { /// A chunk of bytes. Chunk(GenericSharedMemory), /// The body is done. Done, /// There was an error streaming the body, /// terminate fetch. Error, } /// Messages used to implement <https://fetch.spec.whatwg.org/#concept-request-transmit-body> /// which are sent from net to script /// (with the exception of Done, which is sent from script to script). #[derive(Debug, Deserialize, Serialize)] pub enum BodyChunkRequest { /// Connect a fetch in `net`, with a stream of bytes from `script`. Connect(IpcSender<BodyChunkResponse>), /// Re-extract a new stream from the source, following a redirect. Extract(IpcReceiver<BodyChunkRequest>), /// Ask for another chunk. Chunk, /// Signal the stream is done(sent from script to script). Done, /// Signal the stream has errored(sent from script to script). Error, } /// A process local view into <https://fetch.spec.whatwg.org/#bodies>. /// After IPC serialization, each process gets its own shared sender state for the same body /// stream. the net side fetch entry points own clearing their local copy once that fetch invocation /// reaches its terminal state. Redirect replay can later deserialize a fresh "RequestBody", so /// lower level fetch steps cannot always clean up immediately. #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct RequestBody { /// Net's channel to communicate with script re this body. #[conditional_malloc_size_of] body_chunk_request_channel: Arc<Mutex<Option<IpcSender<BodyChunkRequest>>>>, /// <https://fetch.spec.whatwg.org/#concept-body-source> source: BodySource, /// <https://fetch.spec.whatwg.org/#concept-body-total-bytes> total_bytes: Option<usize>, } impl RequestBody { pub fn new( body_chunk_request_channel: IpcSender<BodyChunkRequest>, source: BodySource, total_bytes: Option<usize>, ) -> Self { RequestBody { body_chunk_request_channel: Arc::new(Mutex::new(Some(body_chunk_request_channel))), source, total_bytes, } } /// Step 12 of <https://fetch.spec.whatwg.org/#concept-http-redirect-fetch> pub fn extract_source(&mut self) { match self.source { BodySource::Null => panic!("Null sources should never be re-directed."), BodySource::Object => { let (chan, port) = ipc::channel().unwrap(); let mut lock = self.body_chunk_request_channel.lock(); let Some(selfchan) = lock.as_mut() else { error!( "Could not re-extract the request body source because the body stream has already been closed." ); return; }; if let Err(error) = selfchan.send(BodyChunkRequest::Extract(port)) { error!( "Could not re-extract the request body source because the body stream has already been closed: {error}" ); return; } *selfchan = chan; }, } } /// This is the current process shared optional sender for requesting body chunks. pub fn clone_stream(&self) -> Arc<Mutex<Option<IpcSender<BodyChunkRequest>>>> { self.body_chunk_request_channel.clone() } /// Clears the current process shared sender state for this "RequestBody" copy. /// /// This does not notify or mutate other deserialized "RequestBody" values in other processes. /// Can be called multiple times. pub fn close_stream(&self) { self.body_chunk_request_channel.lock().take(); } pub fn source_is_null(&self) -> bool { self.source == BodySource::Null } #[expect(clippy::len_without_is_empty)] pub fn len(&self) -> Option<usize> { self.total_bytes } } trait RequestBodySize { fn body_length(&self) -> usize; } impl RequestBodySize for Option<RequestBody> { fn body_length(&self) -> usize { self.as_ref() .and_then(|body| body.len()) .unwrap_or_default() } } #[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)] pub enum InsecureRequestsPolicy { DoNotUpgrade, Upgrade, } pub trait RequestHeadersSize { fn total_size(&self) -> usize; } impl RequestHeadersSize for HeaderMap { fn total_size(&self) -> usize { self.iter() .map(|(name, value)| name.as_str().len() + value.len()) .sum() } } #[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)] pub struct RequestBuilder { pub id: RequestId, pub preload_id: Option<PreloadId>, /// <https://fetch.spec.whatwg.org/#concept-request-method> #[serde( deserialize_with = "::hyper_serde::deserialize", serialize_with = "::hyper_serde::serialize" )] pub method: Method, /// <https://fetch.spec.whatwg.org/#concept-request-url> pub url: UrlWithBlobClaim, /// <https://fetch.spec.whatwg.org/#concept-request-header-list> #[serde( deserialize_with = "::hyper_serde::deserialize", serialize_with = "::hyper_serde::serialize" )] pub headers: HeaderMap, /// <https://fetch.spec.whatwg.org/#unsafe-request-flag> pub unsafe_request: bool, /// <https://fetch.spec.whatwg.org/#concept-request-body> pub body: Option<RequestBody>, /// <https://fetch.spec.whatwg.org/#concept-request-reload-navigation-flag> /// A request has an associated reload-navigation flag. Unless stated otherwise, it is unset. pub reload_navigation: bool, /// <https://fetch.spec.whatwg.org/#concept-request-history-navigation-flag> /// A request has an associated history-navigation flag. Unless stated otherwise, it is unset. pub history_navigation: bool, /// <https://fetch.spec.whatwg.org/#request-service-workers-mode> pub service_workers_mode: ServiceWorkersMode, pub client: Option<RequestClient>, /// <https://fetch.spec.whatwg.org/#concept-request-destination> pub destination: Destination, pub synchronous: bool, pub mode: RequestMode, /// <https://fetch.spec.whatwg.org/#concept-request-cache-mode> pub cache_mode: CacheMode, /// <https://fetch.spec.whatwg.org/#use-cors-preflight-flag> pub use_cors_preflight: bool, /// <https://fetch.spec.whatwg.org/#request-keepalive-flag> pub keep_alive: bool, /// <https://fetch.spec.whatwg.org/#concept-request-credentials-mode> pub credentials_mode: CredentialsMode, pub use_url_credentials: bool, /// <https://fetch.spec.whatwg.org/#concept-request-origin> pub origin: Origin, /// <https://fetch.spec.whatwg.org/#concept-request-policy-container> pub policy_container: RequestPolicyContainer, /// <https://fetch.spec.whatwg.org/#concept-request-referrer> pub referrer: Referrer, /// <https://fetch.spec.whatwg.org/#concept-request-referrer-policy> pub referrer_policy: ReferrerPolicy, pub pipeline_id: Option<PipelineId>, pub target_webview_id: Option<WebViewId>, /// <https://fetch.spec.whatwg.org/#concept-request-redirect-mode> pub redirect_mode: RedirectMode, /// <https://fetch.spec.whatwg.org/#concept-request-integrity-metadata> pub integrity_metadata: String, /// <https://fetch.spec.whatwg.org/#concept-request-nonce-metadata> pub cryptographic_nonce_metadata: String, /// <https://fetch.spec.whatwg.org/#concept-request-url-list> pub url_list: Vec<ServoUrl>, /// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata> pub parser_metadata: ParserMetadata, /// <https://fetch.spec.whatwg.org/#concept-request-initiator> pub initiator: Initiator, pub response_tainting: ResponseTainting, /// Servo internal: if crash details are present, trigger a crash error page with these details. pub crash: Option<String>, /// Servo internal: whether this request originates from Servo internal implementation pub is_internal_request: InternalRequest, } impl RequestBuilder { pub fn new( webview_id: Option<WebViewId>, url: UrlWithBlobClaim, referrer: Referrer, ) -> RequestBuilder { RequestBuilder { id: RequestId::default(), preload_id: None, method: Method::GET, url, headers: HeaderMap::new(), unsafe_request: false, body: None, reload_navigation: false, history_navigation: false, service_workers_mode: ServiceWorkersMode::All, destination: Destination::None, synchronous: false, mode: RequestMode::NoCors, cache_mode: CacheMode::Default, use_cors_preflight: false, keep_alive: false, credentials_mode: CredentialsMode::CredentialsSameOrigin, use_url_credentials: false, origin: Origin::Client, client: None, policy_container: RequestPolicyContainer::default(), referrer, referrer_policy: ReferrerPolicy::EmptyString, pipeline_id: None, target_webview_id: webview_id, redirect_mode: RedirectMode::Follow, integrity_metadata: "".to_owned(), cryptographic_nonce_metadata: "".to_owned(), url_list: vec![], parser_metadata: ParserMetadata::Default, initiator: Initiator::None, response_tainting: ResponseTainting::Basic, is_internal_request: Default::default(), crash: None, } } pub fn preload_id(mut self, preload_id: PreloadId) -> RequestBuilder { self.preload_id = Some(preload_id); self } /// <https://fetch.spec.whatwg.org/#concept-request-initiator> pub fn initiator(mut self, initiator: Initiator) -> RequestBuilder { self.initiator = initiator; self } /// <https://fetch.spec.whatwg.org/#concept-request-method> pub fn method(mut self, method: Method) -> RequestBuilder { self.method = method; self } /// <https://fetch.spec.whatwg.org/#concept-request-header-list> pub fn headers(mut self, headers: HeaderMap) -> RequestBuilder { self.headers = headers; self } /// <https://fetch.spec.whatwg.org/#unsafe-request-flag> pub fn unsafe_request(mut self, unsafe_request: bool) -> RequestBuilder { self.unsafe_request = unsafe_request; self } /// <https://fetch.spec.whatwg.org/#concept-request-body> pub fn body(mut self, body: Option<RequestBody>) -> RequestBuilder { self.body = body; self } /// <https://fetch.spec.whatwg.org/#concept-request-destination> pub fn destination(mut self, destination: Destination) -> RequestBuilder { self.destination = destination; self } pub fn synchronous(mut self, synchronous: bool) -> RequestBuilder { self.synchronous = synchronous; self } pub fn mode(mut self, mode: RequestMode) -> RequestBuilder { self.mode = mode; self } /// <https://fetch.spec.whatwg.org/#use-cors-preflight-flag> pub fn use_cors_preflight(mut self, use_cors_preflight: bool) -> RequestBuilder { self.use_cors_preflight = use_cors_preflight; self } /// <https://fetch.spec.whatwg.org/#request-keepalive-flag> pub fn keep_alive(mut self, keep_alive: bool) -> RequestBuilder { self.keep_alive = keep_alive; self } /// <https://fetch.spec.whatwg.org/#concept-request-credentials-mode> pub fn credentials_mode(mut self, credentials_mode: CredentialsMode) -> RequestBuilder { self.credentials_mode = credentials_mode; self } pub fn use_url_credentials(mut self, use_url_credentials: bool) -> RequestBuilder { self.use_url_credentials = use_url_credentials; self } /// <https://fetch.spec.whatwg.org/#concept-request-origin> pub fn origin(mut self, origin: ImmutableOrigin) -> RequestBuilder { self.origin = Origin::Origin(origin); self } /// <https://fetch.spec.whatwg.org/#concept-request-referrer-policy> pub fn referrer_policy(mut self, referrer_policy: ReferrerPolicy) -> RequestBuilder { self.referrer_policy = referrer_policy; self } /// <https://fetch.spec.whatwg.org/#concept-request-url-list> pub fn url_list(mut self, url_list: Vec<ServoUrl>) -> RequestBuilder { self.url_list = url_list; self } pub fn pipeline_id(mut self, pipeline_id: Option<PipelineId>) -> RequestBuilder { self.pipeline_id = pipeline_id; self } /// <https://fetch.spec.whatwg.org/#concept-request-redirect-mode> pub fn redirect_mode(mut self, redirect_mode: RedirectMode) -> RequestBuilder { self.redirect_mode = redirect_mode; self } /// <https://fetch.spec.whatwg.org/#concept-request-integrity-metadata> pub fn integrity_metadata(mut self, integrity_metadata: String) -> RequestBuilder { self.integrity_metadata = integrity_metadata; self } /// <https://fetch.spec.whatwg.org/#concept-request-nonce-metadata> pub fn cryptographic_nonce_metadata(mut self, nonce_metadata: String) -> RequestBuilder { self.cryptographic_nonce_metadata = nonce_metadata; self } /// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata> pub fn parser_metadata(mut self, parser_metadata: ParserMetadata) -> RequestBuilder { self.parser_metadata = parser_metadata; self } pub fn response_tainting(mut self, response_tainting: ResponseTainting) -> RequestBuilder { self.response_tainting = response_tainting; self } pub fn crash(mut self, crash: Option<String>) -> Self { self.crash = crash; self } /// <https://fetch.spec.whatwg.org/#concept-request-policy-container> pub fn policy_container(mut self, policy_container: PolicyContainer) -> RequestBuilder { self.policy_container = RequestPolicyContainer::PolicyContainer(policy_container); self } /// <https://fetch.spec.whatwg.org/#concept-request-client> pub fn client(mut self, client: RequestClient) -> RequestBuilder { self.client = Some(client); self } /// <https://fetch.spec.whatwg.org/#request-service-workers-mode> pub fn service_workers_mode( mut self, service_workers_mode: ServiceWorkersMode, ) -> RequestBuilder { self.service_workers_mode = service_workers_mode; self } /// <https://fetch.spec.whatwg.org/#concept-request-cache-mode> pub fn cache_mode(mut self, cache_mode: CacheMode) -> RequestBuilder { self.cache_mode = cache_mode; self } pub fn is_internal_request(mut self, is_internal_request: InternalRequest) -> RequestBuilder { self.is_internal_request = is_internal_request; self } pub fn build(self) -> Request { let mut request = Request::new( self.id, self.url.clone(), Some(self.origin), self.referrer, self.pipeline_id, self.target_webview_id, ); request.preload_id = self.preload_id; request.initiator = self.initiator; request.method = self.method; request.headers = self.headers; request.unsafe_request = self.unsafe_request; request.body = self.body; request.reload_navigation = self.reload_navigation; request.history_navigation = self.history_navigation; request.service_workers_mode = self.service_workers_mode; request.destination = self.destination; request.synchronous = self.synchronous; request.mode = self.mode; request.use_cors_preflight = self.use_cors_preflight; request.keep_alive = self.keep_alive; request.credentials_mode = self.credentials_mode; request.use_url_credentials = self.use_url_credentials; request.cache_mode = self.cache_mode; request.referrer_policy = self.referrer_policy; request.redirect_mode = self.redirect_mode; let mut url_list: Vec<_> = self .url_list .into_iter() .map(UrlWithBlobClaim::from_url_without_having_claimed_blob) .collect(); if url_list.is_empty() { url_list.push(self.url); } request.redirect_count = url_list.len() as u32 - 1; request.url_list = url_list; request.integrity_metadata = self.integrity_metadata; request.cryptographic_nonce_metadata = self.cryptographic_nonce_metadata; request.parser_metadata = self.parser_metadata; request.response_tainting = self.response_tainting; request.crash = self.crash; request.client = self.client; request.policy_container = self.policy_container; request.is_internal_request = self.is_internal_request; request } /// The body length for a keep-alive request. Is 0 if this request is not keep-alive pub fn keep_alive_body_length(&self) -> u64 { assert!(self.keep_alive); self.body.body_length() as u64 } } /// A [Request](https://fetch.spec.whatwg.org/#concept-request) as defined by /// the Fetch spec. #[derive(Clone, MallocSizeOf)] pub struct Request { /// The unique id of this request so that the task that triggered it can route /// messages to the correct listeners. This is a UUID that is generated when a request /// is being built. pub id: RequestId, pub preload_id: Option<PreloadId>, /// <https://fetch.spec.whatwg.org/#concept-request-method> pub method: Method, /// <https://fetch.spec.whatwg.org/#local-urls-only-flag> pub local_urls_only: bool, /// <https://fetch.spec.whatwg.org/#concept-request-header-list> pub headers: HeaderMap, /// <https://fetch.spec.whatwg.org/#unsafe-request-flag> pub unsafe_request: bool, /// <https://fetch.spec.whatwg.org/#concept-request-body> pub body: Option<RequestBody>, /// <https://fetch.spec.whatwg.org/#concept-request-reload-navigation-flag> /// A request has an associated reload-navigation flag. Unless stated otherwise, it is unset. pub reload_navigation: bool, /// <https://fetch.spec.whatwg.org/#concept-request-history-navigation-flag> /// A request has an associated history-navigation flag. Unless stated otherwise, it is unset. pub history_navigation: bool, /// <https://fetch.spec.whatwg.org/#concept-request-client> pub client: Option<RequestClient>, /// <https://fetch.spec.whatwg.org/#concept-request-window> pub traversable_for_user_prompts: TraversableForUserPrompts, pub target_webview_id: Option<WebViewId>, /// <https://fetch.spec.whatwg.org/#request-keepalive-flag> pub keep_alive: bool, /// <https://fetch.spec.whatwg.org/#request-service-workers-mode> pub service_workers_mode: ServiceWorkersMode, /// <https://fetch.spec.whatwg.org/#concept-request-initiator> pub initiator: Initiator, /// <https://fetch.spec.whatwg.org/#concept-request-destination> pub destination: Destination, // TODO: priority object /// <https://fetch.spec.whatwg.org/#concept-request-origin> pub origin: Origin, /// <https://fetch.spec.whatwg.org/#concept-request-referrer> pub referrer: Referrer, /// <https://fetch.spec.whatwg.org/#concept-request-referrer-policy> pub referrer_policy: ReferrerPolicy, pub pipeline_id: Option<PipelineId>, /// <https://fetch.spec.whatwg.org/#synchronous-flag> pub synchronous: bool, /// <https://fetch.spec.whatwg.org/#concept-request-mode> pub mode: RequestMode, /// <https://fetch.spec.whatwg.org/#use-cors-preflight-flag> pub use_cors_preflight: bool, /// <https://fetch.spec.whatwg.org/#concept-request-credentials-mode> pub credentials_mode: CredentialsMode, /// <https://fetch.spec.whatwg.org/#concept-request-use-url-credentials-flag> pub use_url_credentials: bool, /// <https://fetch.spec.whatwg.org/#concept-request-cache-mode> pub cache_mode: CacheMode, /// <https://fetch.spec.whatwg.org/#concept-request-redirect-mode> pub redirect_mode: RedirectMode, /// <https://fetch.spec.whatwg.org/#concept-request-integrity-metadata> pub integrity_metadata: String, /// <https://fetch.spec.whatwg.org/#concept-request-nonce-metadata> pub cryptographic_nonce_metadata: String, /// <https://fetch.spec.whatwg.org/#concept-request-url-list> pub url_list: Vec<UrlWithBlobClaim>, /// <https://fetch.spec.whatwg.org/#concept-request-redirect-count> pub redirect_count: u32, /// <https://fetch.spec.whatwg.org/#concept-request-response-tainting> pub response_tainting: ResponseTainting, /// <https://fetch.spec.whatwg.org/#concept-request-parser-metadata> pub parser_metadata: ParserMetadata, /// <https://fetch.spec.whatwg.org/#concept-request-policy-container> pub policy_container: RequestPolicyContainer, /// Servo internal: if crash details are present, trigger a crash error page with these details. pub crash: Option<String>, /// Servo internal: whether this request originates from Servo internal implementation pub is_internal_request: InternalRequest, } impl Request { pub fn new( id: RequestId, url: UrlWithBlobClaim, origin: Option<Origin>, referrer: Referrer, pipeline_id: Option<PipelineId>, webview_id: Option<WebViewId>, ) -> Request { Request { id, preload_id: None, method: Method::GET, local_urls_only: false, headers: HeaderMap::new(), unsafe_request: false, body: None, reload_navigation: false, history_navigation: false, client: None, traversable_for_user_prompts: TraversableForUserPrompts::Client, keep_alive: false, service_workers_mode: ServiceWorkersMode::All, initiator: Initiator::None, destination: Destination::None, origin: origin.unwrap_or(Origin::Client), referrer, referrer_policy: ReferrerPolicy::EmptyString, pipeline_id, target_webview_id: webview_id, synchronous: false, mode: RequestMode::NoCors, use_cors_preflight: false, credentials_mode: CredentialsMode::CredentialsSameOrigin, use_url_credentials: false, cache_mode: CacheMode::Default, redirect_mode: RedirectMode::Follow, integrity_metadata: String::new(), cryptographic_nonce_metadata: String::new(), url_list: vec![url], parser_metadata: ParserMetadata::Default, redirect_count: 0, response_tainting: ResponseTainting::Basic, policy_container: RequestPolicyContainer::Client, is_internal_request: Default::default(), crash: None, } } /// <https://fetch.spec.whatwg.org/#concept-request-url> pub fn url(&self) -> ServoUrl { self.url_list.first().unwrap().url() } pub fn url_with_blob_claim(&self) -> UrlWithBlobClaim { self.url_list.first().unwrap().clone() } pub fn original_url(&self) -> ServoUrl { match self.mode { RequestMode::WebSocket { protocols: _, ref original_url, } => original_url.clone(), _ => self.url(), } } /// <https://fetch.spec.whatwg.org/#concept-request-current-url> pub fn current_url(&self) -> ServoUrl { self.current_url_with_blob_claim().url() } /// <https://fetch.spec.whatwg.org/#concept-request-current-url> pub fn current_url_with_blob_claim(&self) -> UrlWithBlobClaim { self.url_list.last().unwrap().clone() } /// <https://fetch.spec.whatwg.org/#concept-request-current-url> pub fn current_url_mut(&mut self) -> &mut ServoUrl { self.url_list.last_mut().unwrap() } /// <https://fetch.spec.whatwg.org/#navigation-request> pub fn is_navigation_request(&self) -> bool { matches!( self.destination, Destination::Document | Destination::Embed | Destination::Frame | Destination::IFrame | Destination::Object ) } /// <https://fetch.spec.whatwg.org/#subresource-request> pub fn is_subresource_request(&self) -> bool { matches!( self.destination, Destination::Audio | Destination::Font | Destination::Image | Destination::Manifest | Destination::Script | Destination::Style | Destination::Track | Destination::Video | Destination::Xslt | Destination::None ) } pub fn timing_type(&self) -> ResourceTimingType { if self.is_navigation_request() { ResourceTimingType::Navigation } else { ResourceTimingType::Resource } } /// <https://fetch.spec.whatwg.org/#populate-request-from-client> pub fn populate_request_from_client(&mut self) { // Step 1. If request’s traversable for user prompts is "client": if self.traversable_for_user_prompts == TraversableForUserPrompts::Client { // Step 1.1. Set request’s traversable for user prompts to "no-traversable". self.traversable_for_user_prompts = TraversableForUserPrompts::NoTraversable; // Step 1.2. If request’s client is non-null: if self.client.is_some() { // Step 1.2.1. Let global be request’s client’s global object. // TODO // Step 1.2.2. If global is a Window object and global’s navigable is not null, // then set request’s traversable for user prompts to global’s navigable’s traversable navigable. self.traversable_for_user_prompts = TraversableForUserPrompts::TraversableNavigable(Default::default()); } } // Step 2. If request’s origin is "client": if self.origin == Origin::Client { let Some(client) = self.client.as_ref() else { // Step 2.1. Assert: request’s client is non-null. unreachable!(); }; // Step 2.2. Set request’s origin to request’s client’s origin. self.origin = client.origin.clone(); } // Step 3. If request’s policy container is "client": if matches!(self.policy_container, RequestPolicyContainer::Client) { // Step 3.1. If request’s client is non-null, then set request’s // policy container to a clone of request’s client’s policy container. [HTML] if let Some(client) = self.client.as_ref() { self.policy_container = RequestPolicyContainer::PolicyContainer(client.policy_container.clone()); } else { // Step 3.2. Otherwise, set request’s policy container to a new policy container. self.policy_container = RequestPolicyContainer::PolicyContainer(PolicyContainer::default()); } } } /// The body length for a keep-alive request. Is 0 if this request is not keep-alive pub fn keep_alive_body_length(&self) -> u64 { assert!(self.keep_alive); self.body.body_length() as u64 } /// <https://fetch.spec.whatwg.org/#total-request-length> pub fn total_request_length(&self) -> usize { // Step 1. Let totalRequestLength be the length of request’s URL, serialized with exclude fragment set to true. let mut total_request_length = self.url()[..Position::AfterQuery].len(); // Step 2. Increment totalRequestLength by the length of request’s referrer, serialized. total_request_length += self .referrer .to_url() .map(|url| url.as_str().len()) .unwrap_or_default(); // Step 3. For each (name, value) of request’s header list, increment totalRequestLength // by name’s length + value’s length. total_request_length += self.headers.total_size(); // Step 4. Increment totalRequestLength by request’s body’s length. total_request_length += self.body.body_length(); // Step 5. Return totalRequestLength. total_request_length } /// <https://fetch.spec.whatwg.org/#concept-request-tainted-origin> 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 { pub fn to_url(&self) -> Option<&ServoUrl> { match *self { Referrer::NoReferrer => None, Referrer::Client(ref url) => Some(url), Referrer::ReferrerUrl(ref url) => Some(url), } } } // https://fetch.spec.whatwg.org/#cors-unsafe-request-header-byte // TODO: values in the control-code range are being quietly stripped out by // HeaderMap and never reach this function to be loudly rejected! fn is_cors_unsafe_request_header_byte(value: &u8) -> bool { matches!(value, 0x00..=0x08 | 0x10..=0x19 | 0x22 | 0x28 | 0x29 | 0x3A | 0x3C | 0x3E | 0x3F | 0x40 | 0x5B | 0x5C | 0x5D | 0x7B | 0x7D | 0x7F ) } // https://fetch.spec.whatwg.org/#cors-safelisted-request-header // subclause `accept` fn is_cors_safelisted_request_accept(value: &[u8]) -> bool { !(value.iter().any(is_cors_unsafe_request_header_byte)) } // https://fetch.spec.whatwg.org/#cors-safelisted-request-header // subclauses `accept-language`, `content-language` fn is_cors_safelisted_language(value: &[u8]) -> bool { value.iter().all(|&x| { matches!(x, 0x30..=0x39 | 0x41..=0x5A | 0x61..=0x7A | 0x20 | 0x2A | 0x2C | 0x2D | 0x2E | 0x3B | 0x3D ) }) } // https://fetch.spec.whatwg.org/#cors-safelisted-request-header // subclause `content-type` pub fn is_cors_safelisted_request_content_type(value: &[u8]) -> bool { // step 1 if value.iter().any(is_cors_unsafe_request_header_byte) { return false; } // step 2 let value_string = if let Ok(s) = std::str::from_utf8(value) { s } else { return false; }; let value_mime_result: Result<Mime, _> = value_string.parse(); match value_mime_result { Err(_) => false, // step 3 Ok(value_mime) => match (value_mime.type_(), value_mime.subtype()) { (mime::APPLICATION, mime::WWW_FORM_URLENCODED) | (mime::MULTIPART, mime::FORM_DATA) | (mime::TEXT, mime::PLAIN) => true, _ => false, // step 4 }, } } // TODO: "DPR", "Downlink", "Save-Data", "Viewport-Width", "Width": // ... once parsed, the value should not be failure. // https://fetch.spec.whatwg.org/#cors-safelisted-request-header pub fn is_cors_safelisted_request_header<N: AsRef<str>, V: AsRef<[u8]>>( name: &N, value: &V, ) -> bool { let name: &str = name.as_ref(); let value: &[u8] = value.as_ref(); if value.len() > 128 { return false; } match name { "accept" => is_cors_safelisted_request_accept(value), "accept-language" | "content-language" => is_cors_safelisted_language(value), "content-type" => is_cors_safelisted_request_content_type(value), "range" => is_cors_safelisted_request_range(value), _ => false, } } pub fn is_cors_safelisted_request_range(value: &[u8]) -> bool { if let Ok(value_str) = std::str::from_utf8(value) { return validate_range_header(value_str); } false } fn validate_range_header(value: &str) -> bool { let trimmed = value.trim(); if !trimmed.starts_with("bytes=") { return false; } if let Some(range) = trimmed.strip_prefix("bytes=") { let mut parts = range.split('-'); let start = parts.next(); let end = parts.next(); if let Some(start) = start && let Ok(start_num) = start.parse::<u64>() { return match end { Some(e) if !e.is_empty() => { e.parse::<u64>().is_ok_and(|end_num| start_num <= end_num) }, _ => true, }; } } false } /// <https://fetch.spec.whatwg.org/#cors-safelisted-method> pub fn is_cors_safelisted_method(method: &Method) -> bool { matches!(*method, Method::GET | Method::HEAD | Method::POST) } /// <https://fetch.spec.whatwg.org/#cors-non-wildcard-request-header-name> pub fn is_cors_non_wildcard_request_header_name(name: &HeaderName) -> bool { name == AUTHORIZATION } /// <https://fetch.spec.whatwg.org/#cors-unsafe-request-header-names> pub fn get_cors_unsafe_header_names(headers: &HeaderMap) -> Vec<HeaderName> { // Step 1 let mut unsafe_names: Vec<&HeaderName> = vec![]; // Step 2 let mut potentillay_unsafe_names: Vec<&HeaderName> = vec![]; // Step 3 let mut safelist_value_size = 0; // Step 4 for (name, value) in headers.iter() { if !is_cors_safelisted_request_header(&name, &value) { unsafe_names.push(name); } else { potentillay_unsafe_names.push(name); safelist_value_size += value.as_ref().len(); } } // Step 5 if safelist_value_size > 1024 { unsafe_names.extend_from_slice(&potentillay_unsafe_names); } // Step 6 convert_header_names_to_sorted_lowercase_set(unsafe_names) } /// <https://fetch.spec.whatwg.org/#ref-for-convert-header-names-to-a-sorted-lowercase-set> pub fn convert_header_names_to_sorted_lowercase_set( header_names: Vec<&HeaderName>, ) -> Vec<HeaderName> { // HeaderName does not implement the needed traits to use a BTreeSet // So create a new Vec, sort, then dedup let mut ordered_set = header_names.to_vec(); ordered_set.sort_by(|a, b| a.as_str().partial_cmp(b.as_str()).unwrap()); ordered_set.dedup(); ordered_set.into_iter().cloned().collect() } pub fn create_request_body_with_content(content: String) -> RequestBody { let content_bytes = GenericSharedMemory::from_vec(content.into_bytes()); let content_len = content_bytes.len(); let (chunk_request_sender, chunk_request_receiver) = ipc::channel().unwrap(); ROUTER.add_typed_route( chunk_request_receiver, Box::new(move |message| { let request = message.unwrap(); if let BodyChunkRequest::Connect(sender) = request { let _ = sender.send(BodyChunkResponse::Chunk(content_bytes.clone())); let _ = sender.send(BodyChunkResponse::Done); } }), ); RequestBody::new(chunk_request_sender, BodySource::Object, Some(content_len)) }