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