net_traits/
lib.rs

1/* This Source Code Form is subject to the terms of the Mozilla Public
2 * License, v. 2.0. If a copy of the MPL was not distributed with this
3 * file, You can obtain one at https://mozilla.org/MPL/2.0/. */
4
5#![deny(unsafe_code)]
6
7use std::fmt::Display;
8use std::sync::{LazyLock, OnceLock};
9use std::thread::{self, JoinHandle};
10
11use base::cross_process_instant::CrossProcessInstant;
12use base::id::{CookieStoreId, HistoryStateId};
13use base::{IpcSend, IpcSendResult};
14use content_security_policy::{self as csp};
15use cookie::Cookie;
16use crossbeam_channel::{Receiver, Sender, unbounded};
17use headers::{ContentType, HeaderMapExt, ReferrerPolicy as ReferrerPolicyHeader};
18use http::{Error as HttpError, HeaderMap, HeaderValue, StatusCode, header};
19use hyper_serde::Serde;
20use hyper_util::client::legacy::Error as HyperError;
21use ipc_channel::ipc::{self, IpcError, IpcReceiver, IpcSender};
22use ipc_channel::router::ROUTER;
23use malloc_size_of::malloc_size_of_is_0;
24use malloc_size_of_derive::MallocSizeOf;
25use mime::Mime;
26use rand::{RngCore, rng};
27use request::RequestId;
28use rustc_hash::FxHashMap;
29use rustls_pki_types::CertificateDer;
30use serde::{Deserialize, Serialize};
31use servo_url::{ImmutableOrigin, ServoUrl};
32
33use crate::filemanager_thread::FileManagerThreadMsg;
34use crate::http_status::HttpStatus;
35use crate::request::{Request, RequestBuilder};
36use crate::response::{HttpsState, Response, ResponseInit};
37
38pub mod blob_url_store;
39pub mod filemanager_thread;
40pub mod http_status;
41pub mod image_cache;
42pub mod mime_classifier;
43pub mod policy_container;
44pub mod pub_domains;
45pub mod quality;
46pub mod request;
47pub mod response;
48
49/// <https://fetch.spec.whatwg.org/#document-accept-header-value>
50pub const DOCUMENT_ACCEPT_HEADER_VALUE: HeaderValue =
51    HeaderValue::from_static("text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8");
52
53/// An implementation of the [Fetch specification](https://fetch.spec.whatwg.org/)
54pub mod fetch {
55    pub mod headers;
56}
57
58/// A loading context, for context-specific sniffing, as defined in
59/// <https://mimesniff.spec.whatwg.org/#context-specific-sniffing>
60#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
61pub enum LoadContext {
62    Browsing,
63    Image,
64    AudioVideo,
65    Plugin,
66    Style,
67    Script,
68    Font,
69    TextTrack,
70    CacheManifest,
71}
72
73#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
74pub struct CustomResponse {
75    #[ignore_malloc_size_of = "Defined in hyper"]
76    #[serde(
77        deserialize_with = "::hyper_serde::deserialize",
78        serialize_with = "::hyper_serde::serialize"
79    )]
80    pub headers: HeaderMap,
81    #[ignore_malloc_size_of = "Defined in hyper"]
82    #[serde(
83        deserialize_with = "::hyper_serde::deserialize",
84        serialize_with = "::hyper_serde::serialize"
85    )]
86    pub raw_status: (StatusCode, String),
87    pub body: Vec<u8>,
88}
89
90impl CustomResponse {
91    pub fn new(
92        headers: HeaderMap,
93        raw_status: (StatusCode, String),
94        body: Vec<u8>,
95    ) -> CustomResponse {
96        CustomResponse {
97            headers,
98            raw_status,
99            body,
100        }
101    }
102}
103
104#[derive(Clone, Debug, Deserialize, Serialize)]
105pub struct CustomResponseMediator {
106    pub response_chan: IpcSender<Option<CustomResponse>>,
107    pub load_url: ServoUrl,
108}
109
110/// [Policies](https://w3c.github.io/webappsec-referrer-policy/#referrer-policy-states)
111/// for providing a referrer header for a request
112#[derive(Clone, Copy, Debug, Default, Deserialize, MallocSizeOf, PartialEq, Serialize)]
113pub enum ReferrerPolicy {
114    /// ""
115    EmptyString,
116    /// "no-referrer"
117    NoReferrer,
118    /// "no-referrer-when-downgrade"
119    NoReferrerWhenDowngrade,
120    /// "origin"
121    Origin,
122    /// "same-origin"
123    SameOrigin,
124    /// "origin-when-cross-origin"
125    OriginWhenCrossOrigin,
126    /// "unsafe-url"
127    UnsafeUrl,
128    /// "strict-origin"
129    StrictOrigin,
130    /// "strict-origin-when-cross-origin"
131    #[default]
132    StrictOriginWhenCrossOrigin,
133}
134
135impl ReferrerPolicy {
136    /// <https://html.spec.whatwg.org/multipage/#meta-referrer>
137    pub fn from_with_legacy(value: &str) -> Self {
138        // Step 5. If value is one of the values given in the first column of the following table,
139        // then set value to the value given in the second column:
140        match value.to_ascii_lowercase().as_str() {
141            "never" => ReferrerPolicy::NoReferrer,
142            "default" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
143            "always" => ReferrerPolicy::UnsafeUrl,
144            "origin-when-crossorigin" => ReferrerPolicy::OriginWhenCrossOrigin,
145            _ => ReferrerPolicy::from(value),
146        }
147    }
148
149    /// <https://w3c.github.io/webappsec-referrer-policy/#parse-referrer-policy-from-header>
150    pub fn parse_header_for_response(headers: &Option<Serde<HeaderMap>>) -> Self {
151        // Step 4. Return policy.
152        headers
153            .as_ref()
154            // Step 1. Let policy-tokens be the result of extracting header list values given `Referrer-Policy` and response’s header list.
155            .and_then(|headers| headers.typed_get::<ReferrerPolicyHeader>())
156            // Step 2-3.
157            .into()
158    }
159}
160
161impl From<&str> for ReferrerPolicy {
162    /// <https://html.spec.whatwg.org/multipage/#referrer-policy-attribute>
163    fn from(value: &str) -> Self {
164        match value.to_ascii_lowercase().as_str() {
165            "no-referrer" => ReferrerPolicy::NoReferrer,
166            "no-referrer-when-downgrade" => ReferrerPolicy::NoReferrerWhenDowngrade,
167            "origin" => ReferrerPolicy::Origin,
168            "same-origin" => ReferrerPolicy::SameOrigin,
169            "strict-origin" => ReferrerPolicy::StrictOrigin,
170            "strict-origin-when-cross-origin" => ReferrerPolicy::StrictOriginWhenCrossOrigin,
171            "origin-when-cross-origin" => ReferrerPolicy::OriginWhenCrossOrigin,
172            "unsafe-url" => ReferrerPolicy::UnsafeUrl,
173            _ => ReferrerPolicy::EmptyString,
174        }
175    }
176}
177
178impl Display for ReferrerPolicy {
179    fn fmt(&self, formatter: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
180        let string = match self {
181            ReferrerPolicy::EmptyString => "",
182            ReferrerPolicy::NoReferrer => "no-referrer",
183            ReferrerPolicy::NoReferrerWhenDowngrade => "no-referrer-when-downgrade",
184            ReferrerPolicy::Origin => "origin",
185            ReferrerPolicy::SameOrigin => "same-origin",
186            ReferrerPolicy::OriginWhenCrossOrigin => "origin-when-cross-origin",
187            ReferrerPolicy::UnsafeUrl => "unsafe-url",
188            ReferrerPolicy::StrictOrigin => "strict-origin",
189            ReferrerPolicy::StrictOriginWhenCrossOrigin => "strict-origin-when-cross-origin",
190        };
191        write!(formatter, "{string}")
192    }
193}
194
195/// <https://w3c.github.io/webappsec-referrer-policy/#parse-referrer-policy-from-header>
196impl From<Option<ReferrerPolicyHeader>> for ReferrerPolicy {
197    fn from(header: Option<ReferrerPolicyHeader>) -> Self {
198        // Step 2. Let policy be the empty string.
199        // Step 3. For each token in policy-tokens, if token is a referrer policy and token is not the empty string, then set policy to token.
200        header.map_or(ReferrerPolicy::EmptyString, |policy| match policy {
201            ReferrerPolicyHeader::NO_REFERRER => ReferrerPolicy::NoReferrer,
202            ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE => {
203                ReferrerPolicy::NoReferrerWhenDowngrade
204            },
205            ReferrerPolicyHeader::SAME_ORIGIN => ReferrerPolicy::SameOrigin,
206            ReferrerPolicyHeader::ORIGIN => ReferrerPolicy::Origin,
207            ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN => ReferrerPolicy::OriginWhenCrossOrigin,
208            ReferrerPolicyHeader::UNSAFE_URL => ReferrerPolicy::UnsafeUrl,
209            ReferrerPolicyHeader::STRICT_ORIGIN => ReferrerPolicy::StrictOrigin,
210            ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN => {
211                ReferrerPolicy::StrictOriginWhenCrossOrigin
212            },
213        })
214    }
215}
216
217impl From<ReferrerPolicy> for ReferrerPolicyHeader {
218    fn from(referrer_policy: ReferrerPolicy) -> Self {
219        match referrer_policy {
220            ReferrerPolicy::NoReferrer => ReferrerPolicyHeader::NO_REFERRER,
221            ReferrerPolicy::NoReferrerWhenDowngrade => {
222                ReferrerPolicyHeader::NO_REFERRER_WHEN_DOWNGRADE
223            },
224            ReferrerPolicy::SameOrigin => ReferrerPolicyHeader::SAME_ORIGIN,
225            ReferrerPolicy::Origin => ReferrerPolicyHeader::ORIGIN,
226            ReferrerPolicy::OriginWhenCrossOrigin => ReferrerPolicyHeader::ORIGIN_WHEN_CROSS_ORIGIN,
227            ReferrerPolicy::UnsafeUrl => ReferrerPolicyHeader::UNSAFE_URL,
228            ReferrerPolicy::StrictOrigin => ReferrerPolicyHeader::STRICT_ORIGIN,
229            ReferrerPolicy::EmptyString | ReferrerPolicy::StrictOriginWhenCrossOrigin => {
230                ReferrerPolicyHeader::STRICT_ORIGIN_WHEN_CROSS_ORIGIN
231            },
232        }
233    }
234}
235
236// FIXME: https://github.com/servo/servo/issues/34591
237#[expect(clippy::large_enum_variant)]
238#[derive(Debug, Deserialize, Serialize)]
239pub enum FetchResponseMsg {
240    // todo: should have fields for transmitted/total bytes
241    ProcessRequestBody(RequestId),
242    ProcessRequestEOF(RequestId),
243    // todo: send more info about the response (or perhaps the entire Response)
244    ProcessResponse(RequestId, Result<FetchMetadata, NetworkError>),
245    ProcessResponseChunk(RequestId, Vec<u8>),
246    ProcessResponseEOF(RequestId, Result<ResourceFetchTiming, NetworkError>),
247    ProcessCspViolations(RequestId, Vec<csp::Violation>),
248}
249
250impl FetchResponseMsg {
251    pub fn request_id(&self) -> RequestId {
252        match self {
253            FetchResponseMsg::ProcessRequestBody(id) |
254            FetchResponseMsg::ProcessRequestEOF(id) |
255            FetchResponseMsg::ProcessResponse(id, ..) |
256            FetchResponseMsg::ProcessResponseChunk(id, ..) |
257            FetchResponseMsg::ProcessResponseEOF(id, ..) |
258            FetchResponseMsg::ProcessCspViolations(id, ..) => *id,
259        }
260    }
261}
262
263pub trait FetchTaskTarget {
264    /// <https://fetch.spec.whatwg.org/#process-request-body>
265    ///
266    /// Fired when a chunk of the request body is transmitted
267    fn process_request_body(&mut self, request: &Request);
268
269    /// <https://fetch.spec.whatwg.org/#process-request-end-of-file>
270    ///
271    /// Fired when the entire request finishes being transmitted
272    fn process_request_eof(&mut self, request: &Request);
273
274    /// <https://fetch.spec.whatwg.org/#process-response>
275    ///
276    /// Fired when headers are received
277    fn process_response(&mut self, request: &Request, response: &Response);
278
279    /// Fired when a chunk of response content is received
280    fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>);
281
282    /// <https://fetch.spec.whatwg.org/#process-response-end-of-file>
283    ///
284    /// Fired when the response is fully fetched
285    fn process_response_eof(&mut self, request: &Request, response: &Response);
286
287    fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>);
288}
289
290#[derive(Clone, Debug, Deserialize, Serialize)]
291pub enum FilteredMetadata {
292    Basic(Metadata),
293    Cors(Metadata),
294    Opaque,
295    OpaqueRedirect(ServoUrl),
296}
297
298// FIXME: https://github.com/servo/servo/issues/34591
299#[expect(clippy::large_enum_variant)]
300#[derive(Clone, Debug, Deserialize, Serialize)]
301pub enum FetchMetadata {
302    Unfiltered(Metadata),
303    Filtered {
304        filtered: FilteredMetadata,
305        unsafe_: Metadata,
306    },
307}
308
309impl FetchMetadata {
310    pub fn metadata(&self) -> &Metadata {
311        match self {
312            Self::Unfiltered(metadata) => metadata,
313            Self::Filtered { unsafe_, .. } => unsafe_,
314        }
315    }
316}
317
318pub trait FetchResponseListener {
319    fn process_request_body(&mut self, request_id: RequestId);
320    fn process_request_eof(&mut self, request_id: RequestId);
321    fn process_response(
322        &mut self,
323        request_id: RequestId,
324        metadata: Result<FetchMetadata, NetworkError>,
325    );
326    fn process_response_chunk(&mut self, request_id: RequestId, chunk: Vec<u8>);
327    fn process_response_eof(
328        &mut self,
329        request_id: RequestId,
330        response: Result<ResourceFetchTiming, NetworkError>,
331    );
332    fn resource_timing(&self) -> &ResourceFetchTiming;
333    fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming;
334    fn submit_resource_timing(&mut self);
335    fn process_csp_violations(&mut self, request_id: RequestId, violations: Vec<csp::Violation>);
336}
337
338impl FetchTaskTarget for IpcSender<FetchResponseMsg> {
339    fn process_request_body(&mut self, request: &Request) {
340        let _ = self.send(FetchResponseMsg::ProcessRequestBody(request.id));
341    }
342
343    fn process_request_eof(&mut self, request: &Request) {
344        let _ = self.send(FetchResponseMsg::ProcessRequestEOF(request.id));
345    }
346
347    fn process_response(&mut self, request: &Request, response: &Response) {
348        let _ = self.send(FetchResponseMsg::ProcessResponse(
349            request.id,
350            response.metadata(),
351        ));
352    }
353
354    fn process_response_chunk(&mut self, request: &Request, chunk: Vec<u8>) {
355        let _ = self.send(FetchResponseMsg::ProcessResponseChunk(request.id, chunk));
356    }
357
358    fn process_response_eof(&mut self, request: &Request, response: &Response) {
359        let payload = if let Some(network_error) = response.get_network_error() {
360            Err(network_error.clone())
361        } else {
362            Ok(response.get_resource_timing().lock().unwrap().clone())
363        };
364
365        let _ = self.send(FetchResponseMsg::ProcessResponseEOF(request.id, payload));
366    }
367
368    fn process_csp_violations(&mut self, request: &Request, violations: Vec<csp::Violation>) {
369        let _ = self.send(FetchResponseMsg::ProcessCspViolations(
370            request.id, violations,
371        ));
372    }
373}
374
375impl FetchTaskTarget for IpcSender<WebSocketNetworkEvent> {
376    fn process_request_body(&mut self, _: &Request) {}
377    fn process_request_eof(&mut self, _: &Request) {}
378    fn process_response(&mut self, _: &Request, response: &Response) {
379        if response.is_network_error() {
380            let _ = self.send(WebSocketNetworkEvent::Fail);
381        }
382    }
383    fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
384    fn process_response_eof(&mut self, _: &Request, _: &Response) {}
385    fn process_csp_violations(&mut self, _: &Request, violations: Vec<csp::Violation>) {
386        let _ = self.send(WebSocketNetworkEvent::ReportCSPViolations(violations));
387    }
388}
389
390/// A fetch task that discards all data it's sent,
391/// useful when speculatively prefetching data that we don't need right
392/// now, but might need in the future.
393pub struct DiscardFetch;
394
395impl FetchTaskTarget for DiscardFetch {
396    fn process_request_body(&mut self, _: &Request) {}
397    fn process_request_eof(&mut self, _: &Request) {}
398    fn process_response(&mut self, _: &Request, _: &Response) {}
399    fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
400    fn process_response_eof(&mut self, _: &Request, _: &Response) {}
401    fn process_csp_violations(&mut self, _: &Request, _: Vec<csp::Violation>) {}
402}
403
404pub trait Action<Listener> {
405    fn process(self, listener: &mut Listener);
406}
407
408impl<T: FetchResponseListener> Action<T> for FetchResponseMsg {
409    /// Execute the default action on a provided listener.
410    fn process(self, listener: &mut T) {
411        match self {
412            FetchResponseMsg::ProcessRequestBody(request_id) => {
413                listener.process_request_body(request_id)
414            },
415            FetchResponseMsg::ProcessRequestEOF(request_id) => {
416                listener.process_request_eof(request_id)
417            },
418            FetchResponseMsg::ProcessResponse(request_id, meta) => {
419                listener.process_response(request_id, meta)
420            },
421            FetchResponseMsg::ProcessResponseChunk(request_id, data) => {
422                listener.process_response_chunk(request_id, data)
423            },
424            FetchResponseMsg::ProcessResponseEOF(request_id, data) => {
425                match data {
426                    Ok(ref response_resource_timing) => {
427                        // update listener with values from response
428                        *listener.resource_timing_mut() = response_resource_timing.clone();
429                        listener
430                            .process_response_eof(request_id, Ok(response_resource_timing.clone()));
431                        // TODO timing check https://w3c.github.io/resource-timing/#dfn-timing-allow-check
432
433                        listener.submit_resource_timing();
434                    },
435                    // TODO Resources for which the fetch was initiated, but was later aborted
436                    // (e.g. due to a network error) MAY be included as PerformanceResourceTiming
437                    // objects in the Performance Timeline and MUST contain initialized attribute
438                    // values for processed substeps of the processing model.
439                    Err(e) => listener.process_response_eof(request_id, Err(e)),
440                }
441            },
442            FetchResponseMsg::ProcessCspViolations(request_id, violations) => {
443                listener.process_csp_violations(request_id, violations)
444            },
445        }
446    }
447}
448
449/// Handle to an async runtime,
450/// only used to shut it down for now.
451pub trait AsyncRuntime: Send {
452    fn shutdown(&mut self);
453}
454
455/// Handle to a resource thread
456pub type CoreResourceThread = IpcSender<CoreResourceMsg>;
457
458// FIXME: Originally we will construct an Arc<ResourceThread> from ResourceThread
459// in script_thread to avoid some performance pitfall. Now we decide to deal with
460// the "Arc" hack implicitly in future.
461// See discussion: http://logs.glob.uno/?c=mozilla%23servo&s=16+May+2016&e=16+May+2016#c430412
462// See also: https://github.com/servo/servo/blob/735480/components/script/script_thread.rs#L313
463#[derive(Clone, Debug, Deserialize, Serialize)]
464pub struct ResourceThreads {
465    pub core_thread: CoreResourceThread,
466}
467
468impl ResourceThreads {
469    pub fn new(core_thread: CoreResourceThread) -> ResourceThreads {
470        ResourceThreads { core_thread }
471    }
472
473    pub fn clear_cache(&self) {
474        let _ = self.core_thread.send(CoreResourceMsg::ClearCache);
475    }
476}
477
478impl IpcSend<CoreResourceMsg> for ResourceThreads {
479    fn send(&self, msg: CoreResourceMsg) -> IpcSendResult {
480        self.core_thread.send(msg).map_err(IpcError::Bincode)
481    }
482
483    fn sender(&self) -> IpcSender<CoreResourceMsg> {
484        self.core_thread.clone()
485    }
486}
487
488// Ignore the sub-fields
489malloc_size_of_is_0!(ResourceThreads);
490
491#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
492pub enum IncludeSubdomains {
493    Included,
494    NotIncluded,
495}
496
497#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
498pub enum MessageData {
499    Text(String),
500    Binary(Vec<u8>),
501}
502
503#[derive(Debug, Deserialize, Serialize)]
504pub enum WebSocketDomAction {
505    SendMessage(MessageData),
506    Close(Option<u16>, Option<String>),
507}
508
509#[derive(Debug, Deserialize, Serialize)]
510pub enum WebSocketNetworkEvent {
511    ReportCSPViolations(Vec<csp::Violation>),
512    ConnectionEstablished { protocol_in_use: Option<String> },
513    MessageReceived(MessageData),
514    Close(Option<u16>, String),
515    Fail,
516}
517
518#[derive(Debug, Deserialize, Serialize)]
519/// IPC channels to communicate with the script thread about network or DOM events.
520pub enum FetchChannels {
521    ResponseMsg(IpcSender<FetchResponseMsg>),
522    WebSocket {
523        event_sender: IpcSender<WebSocketNetworkEvent>,
524        action_receiver: IpcReceiver<WebSocketDomAction>,
525    },
526    /// If the fetch is just being done to populate the cache,
527    /// not because the data is needed now.
528    Prefetch,
529}
530
531#[derive(Debug, Deserialize, Serialize)]
532pub enum CoreResourceMsg {
533    Fetch(RequestBuilder, FetchChannels),
534    Cancel(Vec<RequestId>),
535    /// Initiate a fetch in response to processing a redirection
536    FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
537    /// Store a cookie for a given originating URL
538    SetCookieForUrl(ServoUrl, Serde<Cookie<'static>>, CookieSource),
539    /// Store a set of cookies for a given originating URL
540    SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
541    SetCookieForUrlAsync(
542        CookieStoreId,
543        ServoUrl,
544        Serde<Cookie<'static>>,
545        CookieSource,
546    ),
547    /// Retrieve the stored cookies for a given URL
548    GetCookiesForUrl(ServoUrl, IpcSender<Option<String>>, CookieSource),
549    /// Get a cookie by name for a given originating URL
550    GetCookiesDataForUrl(
551        ServoUrl,
552        IpcSender<Vec<Serde<Cookie<'static>>>>,
553        CookieSource,
554    ),
555    GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
556    GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
557    DeleteCookies(ServoUrl),
558    DeleteCookie(ServoUrl, String),
559    DeleteCookieAsync(CookieStoreId, ServoUrl, String),
560    NewCookieListener(CookieStoreId, IpcSender<CookieAsyncResponse>, ServoUrl),
561    RemoveCookieListener(CookieStoreId),
562    /// Get a history state by a given history state id
563    GetHistoryState(HistoryStateId, IpcSender<Option<Vec<u8>>>),
564    /// Set a history state for a given history state id
565    SetHistoryState(HistoryStateId, Vec<u8>),
566    /// Removes history states for the given ids
567    RemoveHistoryStates(Vec<HistoryStateId>),
568    /// Clear the network cache.
569    ClearCache,
570    /// Send the service worker network mediator for an origin to CoreResourceThread
571    NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
572    /// Message forwarded to file manager's handler
573    ToFileManager(FileManagerThreadMsg),
574    /// Break the load handler loop, send a reply when done cleaning up local resources
575    /// and exit
576    Exit(IpcSender<()>),
577}
578
579// FIXME: https://github.com/servo/servo/issues/34591
580#[expect(clippy::large_enum_variant)]
581enum ToFetchThreadMessage {
582    Cancel(Vec<RequestId>, CoreResourceThread),
583    StartFetch(
584        /* request_builder */ RequestBuilder,
585        /* response_init */ Option<ResponseInit>,
586        /* callback  */ BoxedFetchCallback,
587        /* core resource thread channel */ CoreResourceThread,
588    ),
589    FetchResponse(FetchResponseMsg),
590    /// Stop the background thread.
591    Exit,
592}
593
594pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
595
596/// A thread to handle fetches in a Servo process. This thread is responsible for
597/// listening for new fetch requests as well as updates on those operations and forwarding
598/// them to crossbeam channels.
599struct FetchThread {
600    /// A list of active fetches. A fetch is no longer active once the
601    /// [`FetchResponseMsg::ProcessResponseEOF`] is received.
602    active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
603    /// A crossbeam receiver attached to the router proxy which converts incoming fetch
604    /// updates from IPC messages to crossbeam messages as well as another sender which
605    /// handles requests from clients wanting to do fetches.
606    receiver: Receiver<ToFetchThreadMessage>,
607    /// An [`IpcSender`] that's sent with every fetch request and leads back to our
608    /// router proxy.
609    to_fetch_sender: IpcSender<FetchResponseMsg>,
610}
611
612impl FetchThread {
613    fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
614        let (sender, receiver) = unbounded();
615        let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
616
617        let sender_clone = sender.clone();
618        ROUTER.add_typed_route(
619            from_fetch_sender,
620            Box::new(move |message| {
621                let message: FetchResponseMsg = message.unwrap();
622                let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
623            }),
624        );
625        let join_handle = thread::Builder::new()
626            .name("FetchThread".to_owned())
627            .spawn(move || {
628                let mut fetch_thread = FetchThread {
629                    active_fetches: FxHashMap::default(),
630                    receiver,
631                    to_fetch_sender,
632                };
633                fetch_thread.run();
634            })
635            .expect("Thread spawning failed");
636        (sender, join_handle)
637    }
638
639    fn run(&mut self) {
640        loop {
641            match self.receiver.recv().unwrap() {
642                ToFetchThreadMessage::StartFetch(
643                    request_builder,
644                    response_init,
645                    callback,
646                    core_resource_thread,
647                ) => {
648                    let request_builder_id = request_builder.id;
649
650                    // Only redirects have a `response_init` field.
651                    let message = match response_init {
652                        Some(response_init) => CoreResourceMsg::FetchRedirect(
653                            request_builder,
654                            response_init,
655                            self.to_fetch_sender.clone(),
656                        ),
657                        None => CoreResourceMsg::Fetch(
658                            request_builder,
659                            FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
660                        ),
661                    };
662
663                    core_resource_thread.send(message).unwrap();
664
665                    self.active_fetches.insert(request_builder_id, callback);
666                },
667                ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
668                    let request_id = fetch_response_msg.request_id();
669                    let fetch_finished =
670                        matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
671
672                    self.active_fetches
673                        .get_mut(&request_id)
674                        .expect("Got fetch response for unknown fetch")(
675                        fetch_response_msg
676                    );
677
678                    if fetch_finished {
679                        self.active_fetches.remove(&request_id);
680                    }
681                },
682                ToFetchThreadMessage::Cancel(request_ids, core_resource_thread) => {
683                    // Errors are ignored here, because Servo sends many cancellation requests when shutting down.
684                    // At this point the networking task might be shut down completely, so just ignore errors
685                    // during this time.
686                    let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
687                },
688                ToFetchThreadMessage::Exit => break,
689            }
690        }
691    }
692}
693
694static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
695
696/// Start the fetch thread,
697/// and returns the join handle to the background thread.
698pub fn start_fetch_thread() -> JoinHandle<()> {
699    let (sender, join_handle) = FetchThread::spawn();
700    FETCH_THREAD
701        .set(sender)
702        .expect("Fetch thread should be set only once on start-up");
703    join_handle
704}
705
706/// Send the exit message to the background thread,
707/// after which the caller can,
708/// and should,
709/// join on the thread.
710pub fn exit_fetch_thread() {
711    let _ = FETCH_THREAD
712        .get()
713        .expect("Fetch thread should always be initialized on start-up")
714        .send(ToFetchThreadMessage::Exit);
715}
716
717/// Instruct the resource thread to make a new fetch request.
718pub fn fetch_async(
719    core_resource_thread: &CoreResourceThread,
720    request: RequestBuilder,
721    response_init: Option<ResponseInit>,
722    callback: BoxedFetchCallback,
723) {
724    let _ = FETCH_THREAD
725        .get()
726        .expect("Fetch thread should always be initialized on start-up")
727        .send(ToFetchThreadMessage::StartFetch(
728            request,
729            response_init,
730            callback,
731            core_resource_thread.clone(),
732        ));
733}
734
735/// Instruct the resource thread to cancel an existing request. Does nothing if the
736/// request has already completed or has not been fetched yet.
737pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
738    let _ = FETCH_THREAD
739        .get()
740        .expect("Fetch thread should always be initialized on start-up")
741        .send(ToFetchThreadMessage::Cancel(
742            request_ids,
743            core_resource_thread.clone(),
744        ));
745}
746
747#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
748pub struct ResourceCorsData {
749    /// CORS Preflight flag
750    pub preflight: bool,
751    /// Origin of CORS Request
752    pub origin: ServoUrl,
753}
754
755#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
756pub struct ResourceFetchTiming {
757    pub domain_lookup_start: Option<CrossProcessInstant>,
758    pub timing_check_passed: bool,
759    pub timing_type: ResourceTimingType,
760    /// Number of redirects until final resource (currently limited to 20)
761    pub redirect_count: u16,
762    pub request_start: Option<CrossProcessInstant>,
763    pub secure_connection_start: Option<CrossProcessInstant>,
764    pub response_start: Option<CrossProcessInstant>,
765    pub fetch_start: Option<CrossProcessInstant>,
766    pub response_end: Option<CrossProcessInstant>,
767    pub redirect_start: Option<CrossProcessInstant>,
768    pub redirect_end: Option<CrossProcessInstant>,
769    pub connect_start: Option<CrossProcessInstant>,
770    pub connect_end: Option<CrossProcessInstant>,
771    pub start_time: Option<CrossProcessInstant>,
772}
773
774pub enum RedirectStartValue {
775    #[allow(dead_code)]
776    Zero,
777    FetchStart,
778}
779
780pub enum RedirectEndValue {
781    Zero,
782    ResponseEnd,
783}
784
785// TODO: refactor existing code to use this enum for setting time attributes
786// suggest using this with all time attributes in the future
787pub enum ResourceTimeValue {
788    Zero,
789    Now,
790    FetchStart,
791    RedirectStart,
792}
793
794pub enum ResourceAttribute {
795    RedirectCount(u16),
796    DomainLookupStart,
797    RequestStart,
798    ResponseStart,
799    RedirectStart(RedirectStartValue),
800    RedirectEnd(RedirectEndValue),
801    FetchStart,
802    ConnectStart(CrossProcessInstant),
803    ConnectEnd(CrossProcessInstant),
804    SecureConnectionStart,
805    ResponseEnd,
806    StartTime(ResourceTimeValue),
807}
808
809#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
810pub enum ResourceTimingType {
811    Resource,
812    Navigation,
813    Error,
814    None,
815}
816
817impl ResourceFetchTiming {
818    pub fn new(timing_type: ResourceTimingType) -> ResourceFetchTiming {
819        ResourceFetchTiming {
820            timing_type,
821            timing_check_passed: true,
822            domain_lookup_start: None,
823            redirect_count: 0,
824            secure_connection_start: None,
825            request_start: None,
826            response_start: None,
827            fetch_start: None,
828            redirect_start: None,
829            redirect_end: None,
830            connect_start: None,
831            connect_end: None,
832            response_end: None,
833            start_time: None,
834        }
835    }
836
837    // TODO currently this is being set with precise time ns when it should be time since
838    // time origin (as described in Performance::now)
839    pub fn set_attribute(&mut self, attribute: ResourceAttribute) {
840        let should_attribute_always_be_updated = matches!(
841            attribute,
842            ResourceAttribute::FetchStart |
843                ResourceAttribute::ResponseEnd |
844                ResourceAttribute::StartTime(_)
845        );
846        if !self.timing_check_passed && !should_attribute_always_be_updated {
847            return;
848        }
849        let now = Some(CrossProcessInstant::now());
850        match attribute {
851            ResourceAttribute::DomainLookupStart => self.domain_lookup_start = now,
852            ResourceAttribute::RedirectCount(count) => self.redirect_count = count,
853            ResourceAttribute::RequestStart => self.request_start = now,
854            ResourceAttribute::ResponseStart => self.response_start = now,
855            ResourceAttribute::RedirectStart(val) => match val {
856                RedirectStartValue::Zero => self.redirect_start = None,
857                RedirectStartValue::FetchStart => {
858                    if self.redirect_start.is_none() {
859                        self.redirect_start = self.fetch_start
860                    }
861                },
862            },
863            ResourceAttribute::RedirectEnd(val) => match val {
864                RedirectEndValue::Zero => self.redirect_end = None,
865                RedirectEndValue::ResponseEnd => self.redirect_end = self.response_end,
866            },
867            ResourceAttribute::FetchStart => self.fetch_start = now,
868            ResourceAttribute::ConnectStart(instant) => self.connect_start = Some(instant),
869            ResourceAttribute::ConnectEnd(instant) => self.connect_end = Some(instant),
870            ResourceAttribute::SecureConnectionStart => self.secure_connection_start = now,
871            ResourceAttribute::ResponseEnd => self.response_end = now,
872            ResourceAttribute::StartTime(val) => match val {
873                ResourceTimeValue::RedirectStart
874                    if self.redirect_start.is_none() || !self.timing_check_passed => {},
875                _ => self.start_time = self.get_time_value(val),
876            },
877        }
878    }
879
880    fn get_time_value(&self, time: ResourceTimeValue) -> Option<CrossProcessInstant> {
881        match time {
882            ResourceTimeValue::Zero => None,
883            ResourceTimeValue::Now => Some(CrossProcessInstant::now()),
884            ResourceTimeValue::FetchStart => self.fetch_start,
885            ResourceTimeValue::RedirectStart => self.redirect_start,
886        }
887    }
888
889    pub fn mark_timing_check_failed(&mut self) {
890        self.timing_check_passed = false;
891        self.domain_lookup_start = None;
892        self.redirect_count = 0;
893        self.request_start = None;
894        self.response_start = None;
895        self.redirect_start = None;
896        self.connect_start = None;
897        self.connect_end = None;
898    }
899}
900
901/// Metadata about a loaded resource, such as is obtained from HTTP headers.
902#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
903pub struct Metadata {
904    /// Final URL after redirects.
905    pub final_url: ServoUrl,
906
907    /// Location URL from the response headers.
908    pub location_url: Option<Result<ServoUrl, String>>,
909
910    #[ignore_malloc_size_of = "Defined in hyper"]
911    /// MIME type / subtype.
912    pub content_type: Option<Serde<ContentType>>,
913
914    /// Character set.
915    pub charset: Option<String>,
916
917    #[ignore_malloc_size_of = "Defined in hyper"]
918    /// Headers
919    pub headers: Option<Serde<HeaderMap>>,
920
921    /// HTTP Status
922    pub status: HttpStatus,
923
924    /// Is successful HTTPS connection
925    pub https_state: HttpsState,
926
927    /// Referrer Url
928    pub referrer: Option<ServoUrl>,
929
930    /// Referrer Policy of the Request used to obtain Response
931    pub referrer_policy: ReferrerPolicy,
932    /// Performance information for navigation events
933    pub timing: Option<ResourceFetchTiming>,
934    /// True if the request comes from a redirection
935    pub redirected: bool,
936}
937
938impl Metadata {
939    /// Metadata with defaults for everything optional.
940    pub fn default(url: ServoUrl) -> Self {
941        Metadata {
942            final_url: url,
943            location_url: None,
944            content_type: None,
945            charset: None,
946            headers: None,
947            status: HttpStatus::default(),
948            https_state: HttpsState::None,
949            referrer: None,
950            referrer_policy: ReferrerPolicy::EmptyString,
951            timing: None,
952            redirected: false,
953        }
954    }
955
956    /// Extract the parts of a Mime that we care about.
957    pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
958        if self.headers.is_none() {
959            self.headers = Some(Serde(HeaderMap::new()));
960        }
961
962        if let Some(mime) = content_type {
963            self.headers
964                .as_mut()
965                .unwrap()
966                .typed_insert(ContentType::from(mime.clone()));
967            if let Some(charset) = mime.get_param(mime::CHARSET) {
968                self.charset = Some(charset.to_string());
969            }
970            self.content_type = Some(Serde(ContentType::from(mime.clone())));
971        }
972    }
973
974    /// Set the referrer policy associated with the loaded resource.
975    pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
976        if referrer_policy == ReferrerPolicy::EmptyString {
977            return;
978        }
979
980        if self.headers.is_none() {
981            self.headers = Some(Serde(HeaderMap::new()));
982        }
983
984        self.referrer_policy = referrer_policy;
985
986        self.headers
987            .as_mut()
988            .unwrap()
989            .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
990    }
991}
992
993/// The creator of a given cookie
994#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
995pub enum CookieSource {
996    /// An HTTP API
997    HTTP,
998    /// A non-HTTP API
999    NonHTTP,
1000}
1001
1002#[derive(Clone, Debug, Deserialize, Serialize)]
1003pub struct CookieChange {
1004    changed: Vec<Serde<Cookie<'static>>>,
1005    deleted: Vec<Serde<Cookie<'static>>>,
1006}
1007
1008#[derive(Clone, Debug, Deserialize, Serialize)]
1009pub enum CookieData {
1010    Change(CookieChange),
1011    Get(Option<Serde<Cookie<'static>>>),
1012    GetAll(Vec<Serde<Cookie<'static>>>),
1013    Set(Result<(), ()>),
1014    Delete(Result<(), ()>),
1015}
1016
1017#[derive(Clone, Debug, Deserialize, Serialize)]
1018pub struct CookieAsyncResponse {
1019    pub data: CookieData,
1020}
1021
1022/// Network errors that have to be exported out of the loaders
1023#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1024pub enum NetworkError {
1025    /// Could be any of the internal errors, like unsupported scheme, connection errors, etc.
1026    Internal(String),
1027    LoadCancelled,
1028    /// SSL validation error, to be converted to Resource::BadCertHTML in the HTML parser.
1029    SslValidation(String, Vec<u8>),
1030    /// Crash error, to be converted to Resource::Crash in the HTML parser.
1031    Crash(String),
1032}
1033
1034impl NetworkError {
1035    pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
1036        let error_string = error.to_string();
1037        match certificate {
1038            Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
1039            _ => NetworkError::Internal(error_string),
1040        }
1041    }
1042
1043    pub fn from_http_error(error: &HttpError) -> Self {
1044        NetworkError::Internal(error.to_string())
1045    }
1046}
1047
1048/// Normalize `slice`, as defined by
1049/// [the Fetch Spec](https://fetch.spec.whatwg.org/#concept-header-value-normalize).
1050pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1051    const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1052
1053    loop {
1054        match slice.split_first() {
1055            Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1056            _ => break,
1057        }
1058    }
1059
1060    loop {
1061        match slice.split_last() {
1062            Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1063            _ => break,
1064        }
1065    }
1066
1067    slice
1068}
1069
1070pub fn http_percent_encode(bytes: &[u8]) -> String {
1071    // This encode set is used for HTTP header values and is defined at
1072    // https://tools.ietf.org/html/rfc5987#section-3.2
1073    const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
1074        .add(b' ')
1075        .add(b'"')
1076        .add(b'%')
1077        .add(b'\'')
1078        .add(b'(')
1079        .add(b')')
1080        .add(b'*')
1081        .add(b',')
1082        .add(b'/')
1083        .add(b':')
1084        .add(b';')
1085        .add(b'<')
1086        .add(b'-')
1087        .add(b'>')
1088        .add(b'?')
1089        .add(b'[')
1090        .add(b'\\')
1091        .add(b']')
1092        .add(b'{')
1093        .add(b'}');
1094
1095    percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
1096}
1097
1098/// Step 12 of <https://fetch.spec.whatwg.org/#concept-fetch>
1099pub fn set_default_accept_language(headers: &mut HeaderMap) {
1100    // If request’s header list does not contain `Accept-Language`,
1101    // then user agents should append (`Accept-Language, an appropriate header value) to request’s header list.
1102    if headers.contains_key(header::ACCEPT_LANGUAGE) {
1103        return;
1104    }
1105
1106    // TODO(eijebong): Change this once typed headers are done
1107    headers.insert(
1108        header::ACCEPT_LANGUAGE,
1109        HeaderValue::from_static("en-US,en;q=0.5"),
1110    );
1111}
1112
1113pub static PRIVILEGED_SECRET: LazyLock<u32> = LazyLock::new(|| rng().next_u32());