Files
servo/components/shared/net/request.rs
Narfinger f7d77754ff base: Implement MallocSizeOf for some more types (#43858)
This implements MallocSizeOf for a couple more types and removes some
"ignore_malloc_size_of" throughout the codebase.
- std::path::PathBuf
- tokio::sync::oneshot::Sender
- http::HeaderMap (with a reasonable approximation of iterating over all
headers)
- data_url::Mime by looking at the inner type
- http::Method: Is an enum internally
- urlpattern::Urlpattern: Iterating over all public fields that are
strings as an approximation.

Testing: We cannot test if MallocSizeOf is correct currently.

Signed-off-by: Narfinger <Narfinger@users.noreply.github.com>
2026-04-02 08:13:08 +00:00

1275 lines
45 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/* 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::policy_container::{PolicyContainer, RequestPolicyContainer};
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)]
/// 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,
}
/// <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.clone(),
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: RequestPolicyContainer,
/// <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://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: ServoUrl,
/// <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/#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,
pub insecure_requests_policy: InsecureRequestsPolicy,
pub has_trustworthy_ancestor_origin: bool,
/// <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,
// to keep track of redirects
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 https_state: HttpsState,
pub response_tainting: ResponseTainting,
/// Servo internal: if crash details are present, trigger a crash error page with these details.
pub crash: Option<String>,
}
impl RequestBuilder {
pub fn new(webview_id: Option<WebViewId>, url: ServoUrl, referrer: Referrer) -> RequestBuilder {
RequestBuilder {
id: RequestId::default(),
preload_id: None,
method: Method::GET,
url,
headers: HeaderMap::new(),
unsafe_request: false,
body: None,
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(),
insecure_requests_policy: InsecureRequestsPolicy::DoNotUpgrade,
has_trustworthy_ancestor_origin: false,
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,
https_state: HttpsState::None,
response_tainting: ResponseTainting::Basic,
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
}
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 https_state(mut self, https_state: HttpsState) -> RequestBuilder {
self.https_state = https_state;
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
}
pub fn insecure_requests_policy(
mut self,
insecure_requests_policy: InsecureRequestsPolicy,
) -> RequestBuilder {
self.insecure_requests_policy = insecure_requests_policy;
self
}
pub fn has_trustworthy_ancestor_origin(
mut self,
has_trustworthy_ancestor_origin: bool,
) -> RequestBuilder {
self.has_trustworthy_ancestor_origin = has_trustworthy_ancestor_origin;
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 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,
self.https_state,
);
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.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 = self.url_list;
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.insecure_requests_policy = self.insecure_requests_policy;
request.has_trustworthy_ancestor_origin = self.has_trustworthy_ancestor_origin;
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-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,
// Use the last method on url_list to act as spec current url field, and
// first method to act as spec url field
/// <https://fetch.spec.whatwg.org/#concept-request-url-list>
pub url_list: Vec<ServoUrl>,
/// <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,
/// <https://w3c.github.io/webappsec-upgrade-insecure-requests/#insecure-requests-policy>
pub insecure_requests_policy: InsecureRequestsPolicy,
pub has_trustworthy_ancestor_origin: bool,
pub https_state: HttpsState,
/// Servo internal: if crash details are present, trigger a crash error page with these details.
pub crash: Option<String>,
}
impl Request {
pub fn new(
id: RequestId,
url: ServoUrl,
origin: Option<Origin>,
referrer: Referrer,
pipeline_id: Option<PipelineId>,
webview_id: Option<WebViewId>,
https_state: HttpsState,
) -> Request {
Request {
id,
preload_id: None,
method: Method::GET,
local_urls_only: false,
headers: HeaderMap::new(),
unsafe_request: false,
body: None,
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,
insecure_requests_policy: InsecureRequestsPolicy::DoNotUpgrade,
has_trustworthy_ancestor_origin: false,
https_state,
crash: None,
}
}
/// <https://fetch.spec.whatwg.org/#concept-request-url>
pub fn url(&self) -> ServoUrl {
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.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 requests traversable for user prompts is "client":
if self.traversable_for_user_prompts == TraversableForUserPrompts::Client {
// Step 1.1. Set requests traversable for user prompts to "no-traversable".
self.traversable_for_user_prompts = TraversableForUserPrompts::NoTraversable;
// Step 1.2. If requests client is non-null:
if self.client.is_some() {
// Step 1.2.1. Let global be requests clients global object.
// TODO
// Step 1.2.2. If global is a Window object and globals navigable is not null,
// then set requests traversable for user prompts to globals navigables traversable navigable.
self.traversable_for_user_prompts =
TraversableForUserPrompts::TraversableNavigable(Default::default());
}
}
// Step 2. If requests origin is "client":
if self.origin == Origin::Client {
let Some(client) = self.client.as_ref() else {
// Step 2.1. Assert: requests client is non-null.
unreachable!();
};
// Step 2.2. Set requests origin to requests clients origin.
self.origin = client.origin.clone();
}
// Step 3. If requests policy container is "client":
if matches!(self.policy_container, RequestPolicyContainer::Client) {
// Step 3.1. If requests client is non-null, then set requests
// policy container to a clone of requests clients policy container. [HTML]
if let Some(client) = self.client.as_ref() {
self.policy_container = client.policy_container.clone();
} else {
// Step 3.2. Otherwise, set requests 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 requests 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 requests 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 requests header list, increment totalRequestLength
// by names length + values length.
total_request_length += self.headers.total_size();
// Step 4. Increment totalRequestLength by requests bodys 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: requests 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 requests 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 urls origin is not same site with lastURLs origin and
// requests origin is not same site with lastURLs 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 urls origin is not same origin with lastURLs origin
// and requests origin is not same origin with lastURLs 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 {
if 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: &str) -> RequestBody {
let content_bytes = GenericSharedMemory::from_bytes(content.as_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))
}