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 request::RequestId;
27use rustc_hash::FxHashMap;
28use rustls_pki_types::CertificateDer;
29use serde::{Deserialize, Serialize};
30use servo_rand::RngCore;
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
375/// A fetch task that discards all data it's sent,
376/// useful when speculatively prefetching data that we don't need right
377/// now, but might need in the future.
378pub struct DiscardFetch;
379
380impl FetchTaskTarget for DiscardFetch {
381    fn process_request_body(&mut self, _: &Request) {}
382    fn process_request_eof(&mut self, _: &Request) {}
383    fn process_response(&mut self, _: &Request, _: &Response) {}
384    fn process_response_chunk(&mut self, _: &Request, _: Vec<u8>) {}
385    fn process_response_eof(&mut self, _: &Request, _: &Response) {}
386    fn process_csp_violations(&mut self, _: &Request, _: Vec<csp::Violation>) {}
387}
388
389pub trait Action<Listener> {
390    fn process(self, listener: &mut Listener);
391}
392
393impl<T: FetchResponseListener> Action<T> for FetchResponseMsg {
394    /// Execute the default action on a provided listener.
395    fn process(self, listener: &mut T) {
396        match self {
397            FetchResponseMsg::ProcessRequestBody(request_id) => {
398                listener.process_request_body(request_id)
399            },
400            FetchResponseMsg::ProcessRequestEOF(request_id) => {
401                listener.process_request_eof(request_id)
402            },
403            FetchResponseMsg::ProcessResponse(request_id, meta) => {
404                listener.process_response(request_id, meta)
405            },
406            FetchResponseMsg::ProcessResponseChunk(request_id, data) => {
407                listener.process_response_chunk(request_id, data)
408            },
409            FetchResponseMsg::ProcessResponseEOF(request_id, data) => {
410                match data {
411                    Ok(ref response_resource_timing) => {
412                        // update listener with values from response
413                        *listener.resource_timing_mut() = response_resource_timing.clone();
414                        listener
415                            .process_response_eof(request_id, Ok(response_resource_timing.clone()));
416                        // TODO timing check https://w3c.github.io/resource-timing/#dfn-timing-allow-check
417
418                        listener.submit_resource_timing();
419                    },
420                    // TODO Resources for which the fetch was initiated, but was later aborted
421                    // (e.g. due to a network error) MAY be included as PerformanceResourceTiming
422                    // objects in the Performance Timeline and MUST contain initialized attribute
423                    // values for processed substeps of the processing model.
424                    Err(e) => listener.process_response_eof(request_id, Err(e)),
425                }
426            },
427            FetchResponseMsg::ProcessCspViolations(request_id, violations) => {
428                listener.process_csp_violations(request_id, violations)
429            },
430        }
431    }
432}
433
434/// Handle to an async runtime,
435/// only used to shut it down for now.
436pub trait AsyncRuntime: Send {
437    fn shutdown(&mut self);
438}
439
440/// Handle to a resource thread
441pub type CoreResourceThread = IpcSender<CoreResourceMsg>;
442
443// FIXME: Originally we will construct an Arc<ResourceThread> from ResourceThread
444// in script_thread to avoid some performance pitfall. Now we decide to deal with
445// the "Arc" hack implicitly in future.
446// See discussion: http://logs.glob.uno/?c=mozilla%23servo&s=16+May+2016&e=16+May+2016#c430412
447// See also: https://github.com/servo/servo/blob/735480/components/script/script_thread.rs#L313
448#[derive(Clone, Debug, Deserialize, Serialize)]
449pub struct ResourceThreads {
450    pub core_thread: CoreResourceThread,
451}
452
453impl ResourceThreads {
454    pub fn new(core_thread: CoreResourceThread) -> ResourceThreads {
455        ResourceThreads { core_thread }
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).map_err(IpcError::Bincode)
466    }
467
468    fn sender(&self) -> IpcSender<CoreResourceMsg> {
469        self.core_thread.clone()
470    }
471}
472
473// Ignore the sub-fields
474malloc_size_of_is_0!(ResourceThreads);
475
476#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
477pub enum IncludeSubdomains {
478    Included,
479    NotIncluded,
480}
481
482#[derive(Debug, Deserialize, MallocSizeOf, Serialize)]
483pub enum MessageData {
484    Text(String),
485    Binary(Vec<u8>),
486}
487
488#[derive(Debug, Deserialize, Serialize)]
489pub enum WebSocketDomAction {
490    SendMessage(MessageData),
491    Close(Option<u16>, Option<String>),
492}
493
494#[derive(Debug, Deserialize, Serialize)]
495pub enum WebSocketNetworkEvent {
496    ReportCSPViolations(Vec<csp::Violation>),
497    ConnectionEstablished { protocol_in_use: Option<String> },
498    MessageReceived(MessageData),
499    Close(Option<u16>, String),
500    Fail,
501}
502
503#[derive(Debug, Deserialize, Serialize)]
504/// IPC channels to communicate with the script thread about network or DOM events.
505pub enum FetchChannels {
506    ResponseMsg(IpcSender<FetchResponseMsg>),
507    WebSocket {
508        event_sender: IpcSender<WebSocketNetworkEvent>,
509        action_receiver: IpcReceiver<WebSocketDomAction>,
510    },
511    /// If the fetch is just being done to populate the cache,
512    /// not because the data is needed now.
513    Prefetch,
514}
515
516#[derive(Debug, Deserialize, Serialize)]
517pub enum CoreResourceMsg {
518    Fetch(RequestBuilder, FetchChannels),
519    Cancel(Vec<RequestId>),
520    /// Initiate a fetch in response to processing a redirection
521    FetchRedirect(RequestBuilder, ResponseInit, IpcSender<FetchResponseMsg>),
522    /// Store a cookie for a given originating URL
523    SetCookieForUrl(ServoUrl, Serde<Cookie<'static>>, CookieSource),
524    /// Store a set of cookies for a given originating URL
525    SetCookiesForUrl(ServoUrl, Vec<Serde<Cookie<'static>>>, CookieSource),
526    SetCookieForUrlAsync(
527        CookieStoreId,
528        ServoUrl,
529        Serde<Cookie<'static>>,
530        CookieSource,
531    ),
532    /// Retrieve the stored cookies for a given URL
533    GetCookiesForUrl(ServoUrl, IpcSender<Option<String>>, CookieSource),
534    /// Get a cookie by name for a given originating URL
535    GetCookiesDataForUrl(
536        ServoUrl,
537        IpcSender<Vec<Serde<Cookie<'static>>>>,
538        CookieSource,
539    ),
540    GetCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
541    GetAllCookieDataForUrlAsync(CookieStoreId, ServoUrl, Option<String>),
542    DeleteCookies(ServoUrl),
543    DeleteCookie(ServoUrl, String),
544    DeleteCookieAsync(CookieStoreId, ServoUrl, String),
545    NewCookieListener(CookieStoreId, IpcSender<CookieAsyncResponse>, ServoUrl),
546    RemoveCookieListener(CookieStoreId),
547    /// Get a history state by a given history state id
548    GetHistoryState(HistoryStateId, IpcSender<Option<Vec<u8>>>),
549    /// Set a history state for a given history state id
550    SetHistoryState(HistoryStateId, Vec<u8>),
551    /// Removes history states for the given ids
552    RemoveHistoryStates(Vec<HistoryStateId>),
553    /// Clear the network cache.
554    ClearCache,
555    /// Send the service worker network mediator for an origin to CoreResourceThread
556    NetworkMediator(IpcSender<CustomResponseMediator>, ImmutableOrigin),
557    /// Message forwarded to file manager's handler
558    ToFileManager(FileManagerThreadMsg),
559    /// Break the load handler loop, send a reply when done cleaning up local resources
560    /// and exit
561    Exit(IpcSender<()>),
562}
563
564// FIXME: https://github.com/servo/servo/issues/34591
565#[expect(clippy::large_enum_variant)]
566enum ToFetchThreadMessage {
567    Cancel(Vec<RequestId>, CoreResourceThread),
568    StartFetch(
569        /* request_builder */ RequestBuilder,
570        /* response_init */ Option<ResponseInit>,
571        /* callback  */ BoxedFetchCallback,
572        /* core resource thread channel */ CoreResourceThread,
573    ),
574    FetchResponse(FetchResponseMsg),
575    /// Stop the background thread.
576    Exit,
577}
578
579pub type BoxedFetchCallback = Box<dyn FnMut(FetchResponseMsg) + Send + 'static>;
580
581/// A thread to handle fetches in a Servo process. This thread is responsible for
582/// listening for new fetch requests as well as updates on those operations and forwarding
583/// them to crossbeam channels.
584struct FetchThread {
585    /// A list of active fetches. A fetch is no longer active once the
586    /// [`FetchResponseMsg::ProcessResponseEOF`] is received.
587    active_fetches: FxHashMap<RequestId, BoxedFetchCallback>,
588    /// A crossbeam receiver attached to the router proxy which converts incoming fetch
589    /// updates from IPC messages to crossbeam messages as well as another sender which
590    /// handles requests from clients wanting to do fetches.
591    receiver: Receiver<ToFetchThreadMessage>,
592    /// An [`IpcSender`] that's sent with every fetch request and leads back to our
593    /// router proxy.
594    to_fetch_sender: IpcSender<FetchResponseMsg>,
595}
596
597impl FetchThread {
598    fn spawn() -> (Sender<ToFetchThreadMessage>, JoinHandle<()>) {
599        let (sender, receiver) = unbounded();
600        let (to_fetch_sender, from_fetch_sender) = ipc::channel().unwrap();
601
602        let sender_clone = sender.clone();
603        ROUTER.add_typed_route(
604            from_fetch_sender,
605            Box::new(move |message| {
606                let message: FetchResponseMsg = message.unwrap();
607                let _ = sender_clone.send(ToFetchThreadMessage::FetchResponse(message));
608            }),
609        );
610        let join_handle = thread::Builder::new()
611            .name("FetchThread".to_owned())
612            .spawn(move || {
613                let mut fetch_thread = FetchThread {
614                    active_fetches: FxHashMap::default(),
615                    receiver,
616                    to_fetch_sender,
617                };
618                fetch_thread.run();
619            })
620            .expect("Thread spawning failed");
621        (sender, join_handle)
622    }
623
624    fn run(&mut self) {
625        loop {
626            match self.receiver.recv().unwrap() {
627                ToFetchThreadMessage::StartFetch(
628                    request_builder,
629                    response_init,
630                    callback,
631                    core_resource_thread,
632                ) => {
633                    let request_builder_id = request_builder.id;
634
635                    // Only redirects have a `response_init` field.
636                    let message = match response_init {
637                        Some(response_init) => CoreResourceMsg::FetchRedirect(
638                            request_builder,
639                            response_init,
640                            self.to_fetch_sender.clone(),
641                        ),
642                        None => CoreResourceMsg::Fetch(
643                            request_builder,
644                            FetchChannels::ResponseMsg(self.to_fetch_sender.clone()),
645                        ),
646                    };
647
648                    core_resource_thread.send(message).unwrap();
649
650                    self.active_fetches.insert(request_builder_id, callback);
651                },
652                ToFetchThreadMessage::FetchResponse(fetch_response_msg) => {
653                    let request_id = fetch_response_msg.request_id();
654                    let fetch_finished =
655                        matches!(fetch_response_msg, FetchResponseMsg::ProcessResponseEOF(..));
656
657                    self.active_fetches
658                        .get_mut(&request_id)
659                        .expect("Got fetch response for unknown fetch")(
660                        fetch_response_msg
661                    );
662
663                    if fetch_finished {
664                        self.active_fetches.remove(&request_id);
665                    }
666                },
667                ToFetchThreadMessage::Cancel(request_ids, core_resource_thread) => {
668                    // Errors are ignored here, because Servo sends many cancellation requests when shutting down.
669                    // At this point the networking task might be shut down completely, so just ignore errors
670                    // during this time.
671                    let _ = core_resource_thread.send(CoreResourceMsg::Cancel(request_ids));
672                },
673                ToFetchThreadMessage::Exit => break,
674            }
675        }
676    }
677}
678
679static FETCH_THREAD: OnceLock<Sender<ToFetchThreadMessage>> = OnceLock::new();
680
681/// Start the fetch thread,
682/// and returns the join handle to the background thread.
683pub fn start_fetch_thread() -> JoinHandle<()> {
684    let (sender, join_handle) = FetchThread::spawn();
685    FETCH_THREAD
686        .set(sender)
687        .expect("Fetch thread should be set only once on start-up");
688    join_handle
689}
690
691/// Send the exit message to the background thread,
692/// after which the caller can,
693/// and should,
694/// join on the thread.
695pub fn exit_fetch_thread() {
696    let _ = FETCH_THREAD
697        .get()
698        .expect("Fetch thread should always be initialized on start-up")
699        .send(ToFetchThreadMessage::Exit);
700}
701
702/// Instruct the resource thread to make a new fetch request.
703pub fn fetch_async(
704    core_resource_thread: &CoreResourceThread,
705    request: RequestBuilder,
706    response_init: Option<ResponseInit>,
707    callback: BoxedFetchCallback,
708) {
709    let _ = FETCH_THREAD
710        .get()
711        .expect("Fetch thread should always be initialized on start-up")
712        .send(ToFetchThreadMessage::StartFetch(
713            request,
714            response_init,
715            callback,
716            core_resource_thread.clone(),
717        ));
718}
719
720/// Instruct the resource thread to cancel an existing request. Does nothing if the
721/// request has already completed or has not been fetched yet.
722pub fn cancel_async_fetch(request_ids: Vec<RequestId>, core_resource_thread: &CoreResourceThread) {
723    let _ = FETCH_THREAD
724        .get()
725        .expect("Fetch thread should always be initialized on start-up")
726        .send(ToFetchThreadMessage::Cancel(
727            request_ids,
728            core_resource_thread.clone(),
729        ));
730}
731
732#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
733pub struct ResourceCorsData {
734    /// CORS Preflight flag
735    pub preflight: bool,
736    /// Origin of CORS Request
737    pub origin: ServoUrl,
738}
739
740#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
741pub struct ResourceFetchTiming {
742    pub domain_lookup_start: Option<CrossProcessInstant>,
743    pub timing_check_passed: bool,
744    pub timing_type: ResourceTimingType,
745    /// Number of redirects until final resource (currently limited to 20)
746    pub redirect_count: u16,
747    pub request_start: Option<CrossProcessInstant>,
748    pub secure_connection_start: Option<CrossProcessInstant>,
749    pub response_start: Option<CrossProcessInstant>,
750    pub fetch_start: Option<CrossProcessInstant>,
751    pub response_end: Option<CrossProcessInstant>,
752    pub redirect_start: Option<CrossProcessInstant>,
753    pub redirect_end: Option<CrossProcessInstant>,
754    pub connect_start: Option<CrossProcessInstant>,
755    pub connect_end: Option<CrossProcessInstant>,
756    pub start_time: Option<CrossProcessInstant>,
757}
758
759pub enum RedirectStartValue {
760    #[allow(dead_code)]
761    Zero,
762    FetchStart,
763}
764
765pub enum RedirectEndValue {
766    Zero,
767    ResponseEnd,
768}
769
770// TODO: refactor existing code to use this enum for setting time attributes
771// suggest using this with all time attributes in the future
772pub enum ResourceTimeValue {
773    Zero,
774    Now,
775    FetchStart,
776    RedirectStart,
777}
778
779pub enum ResourceAttribute {
780    RedirectCount(u16),
781    DomainLookupStart,
782    RequestStart,
783    ResponseStart,
784    RedirectStart(RedirectStartValue),
785    RedirectEnd(RedirectEndValue),
786    FetchStart,
787    ConnectStart(CrossProcessInstant),
788    ConnectEnd(CrossProcessInstant),
789    SecureConnectionStart,
790    ResponseEnd,
791    StartTime(ResourceTimeValue),
792}
793
794#[derive(Clone, Copy, Debug, Deserialize, MallocSizeOf, PartialEq, Serialize)]
795pub enum ResourceTimingType {
796    Resource,
797    Navigation,
798    Error,
799    None,
800}
801
802impl ResourceFetchTiming {
803    pub fn new(timing_type: ResourceTimingType) -> ResourceFetchTiming {
804        ResourceFetchTiming {
805            timing_type,
806            timing_check_passed: true,
807            domain_lookup_start: None,
808            redirect_count: 0,
809            secure_connection_start: None,
810            request_start: None,
811            response_start: None,
812            fetch_start: None,
813            redirect_start: None,
814            redirect_end: None,
815            connect_start: None,
816            connect_end: None,
817            response_end: None,
818            start_time: None,
819        }
820    }
821
822    // TODO currently this is being set with precise time ns when it should be time since
823    // time origin (as described in Performance::now)
824    pub fn set_attribute(&mut self, attribute: ResourceAttribute) {
825        let should_attribute_always_be_updated = matches!(
826            attribute,
827            ResourceAttribute::FetchStart |
828                ResourceAttribute::ResponseEnd |
829                ResourceAttribute::StartTime(_)
830        );
831        if !self.timing_check_passed && !should_attribute_always_be_updated {
832            return;
833        }
834        let now = Some(CrossProcessInstant::now());
835        match attribute {
836            ResourceAttribute::DomainLookupStart => self.domain_lookup_start = now,
837            ResourceAttribute::RedirectCount(count) => self.redirect_count = count,
838            ResourceAttribute::RequestStart => self.request_start = now,
839            ResourceAttribute::ResponseStart => self.response_start = now,
840            ResourceAttribute::RedirectStart(val) => match val {
841                RedirectStartValue::Zero => self.redirect_start = None,
842                RedirectStartValue::FetchStart => {
843                    if self.redirect_start.is_none() {
844                        self.redirect_start = self.fetch_start
845                    }
846                },
847            },
848            ResourceAttribute::RedirectEnd(val) => match val {
849                RedirectEndValue::Zero => self.redirect_end = None,
850                RedirectEndValue::ResponseEnd => self.redirect_end = self.response_end,
851            },
852            ResourceAttribute::FetchStart => self.fetch_start = now,
853            ResourceAttribute::ConnectStart(instant) => self.connect_start = Some(instant),
854            ResourceAttribute::ConnectEnd(instant) => self.connect_end = Some(instant),
855            ResourceAttribute::SecureConnectionStart => self.secure_connection_start = now,
856            ResourceAttribute::ResponseEnd => self.response_end = now,
857            ResourceAttribute::StartTime(val) => match val {
858                ResourceTimeValue::RedirectStart
859                    if self.redirect_start.is_none() || !self.timing_check_passed => {},
860                _ => self.start_time = self.get_time_value(val),
861            },
862        }
863    }
864
865    fn get_time_value(&self, time: ResourceTimeValue) -> Option<CrossProcessInstant> {
866        match time {
867            ResourceTimeValue::Zero => None,
868            ResourceTimeValue::Now => Some(CrossProcessInstant::now()),
869            ResourceTimeValue::FetchStart => self.fetch_start,
870            ResourceTimeValue::RedirectStart => self.redirect_start,
871        }
872    }
873
874    pub fn mark_timing_check_failed(&mut self) {
875        self.timing_check_passed = false;
876        self.domain_lookup_start = None;
877        self.redirect_count = 0;
878        self.request_start = None;
879        self.response_start = None;
880        self.redirect_start = None;
881        self.connect_start = None;
882        self.connect_end = None;
883    }
884}
885
886/// Metadata about a loaded resource, such as is obtained from HTTP headers.
887#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
888pub struct Metadata {
889    /// Final URL after redirects.
890    pub final_url: ServoUrl,
891
892    /// Location URL from the response headers.
893    pub location_url: Option<Result<ServoUrl, String>>,
894
895    #[ignore_malloc_size_of = "Defined in hyper"]
896    /// MIME type / subtype.
897    pub content_type: Option<Serde<ContentType>>,
898
899    /// Character set.
900    pub charset: Option<String>,
901
902    #[ignore_malloc_size_of = "Defined in hyper"]
903    /// Headers
904    pub headers: Option<Serde<HeaderMap>>,
905
906    /// HTTP Status
907    pub status: HttpStatus,
908
909    /// Is successful HTTPS connection
910    pub https_state: HttpsState,
911
912    /// Referrer Url
913    pub referrer: Option<ServoUrl>,
914
915    /// Referrer Policy of the Request used to obtain Response
916    pub referrer_policy: ReferrerPolicy,
917    /// Performance information for navigation events
918    pub timing: Option<ResourceFetchTiming>,
919    /// True if the request comes from a redirection
920    pub redirected: bool,
921}
922
923impl Metadata {
924    /// Metadata with defaults for everything optional.
925    pub fn default(url: ServoUrl) -> Self {
926        Metadata {
927            final_url: url,
928            location_url: None,
929            content_type: None,
930            charset: None,
931            headers: None,
932            status: HttpStatus::default(),
933            https_state: HttpsState::None,
934            referrer: None,
935            referrer_policy: ReferrerPolicy::EmptyString,
936            timing: None,
937            redirected: false,
938        }
939    }
940
941    /// Extract the parts of a Mime that we care about.
942    pub fn set_content_type(&mut self, content_type: Option<&Mime>) {
943        if self.headers.is_none() {
944            self.headers = Some(Serde(HeaderMap::new()));
945        }
946
947        if let Some(mime) = content_type {
948            self.headers
949                .as_mut()
950                .unwrap()
951                .typed_insert(ContentType::from(mime.clone()));
952            if let Some(charset) = mime.get_param(mime::CHARSET) {
953                self.charset = Some(charset.to_string());
954            }
955            self.content_type = Some(Serde(ContentType::from(mime.clone())));
956        }
957    }
958
959    /// Set the referrer policy associated with the loaded resource.
960    pub fn set_referrer_policy(&mut self, referrer_policy: ReferrerPolicy) {
961        if referrer_policy == ReferrerPolicy::EmptyString {
962            return;
963        }
964
965        if self.headers.is_none() {
966            self.headers = Some(Serde(HeaderMap::new()));
967        }
968
969        self.referrer_policy = referrer_policy;
970
971        self.headers
972            .as_mut()
973            .unwrap()
974            .typed_insert::<ReferrerPolicyHeader>(referrer_policy.into());
975    }
976}
977
978/// The creator of a given cookie
979#[derive(Clone, Copy, Debug, Deserialize, PartialEq, Serialize)]
980pub enum CookieSource {
981    /// An HTTP API
982    HTTP,
983    /// A non-HTTP API
984    NonHTTP,
985}
986
987#[derive(Clone, Debug, Deserialize, Serialize)]
988pub struct CookieChange {
989    changed: Vec<Serde<Cookie<'static>>>,
990    deleted: Vec<Serde<Cookie<'static>>>,
991}
992
993#[derive(Clone, Debug, Deserialize, Serialize)]
994pub enum CookieData {
995    Change(CookieChange),
996    Get(Option<Serde<Cookie<'static>>>),
997    GetAll(Vec<Serde<Cookie<'static>>>),
998    Set(Result<(), ()>),
999    Delete(Result<(), ()>),
1000}
1001
1002#[derive(Clone, Debug, Deserialize, Serialize)]
1003pub struct CookieAsyncResponse {
1004    pub data: CookieData,
1005}
1006
1007/// Network errors that have to be exported out of the loaders
1008#[derive(Clone, Debug, Deserialize, Eq, MallocSizeOf, PartialEq, Serialize)]
1009pub enum NetworkError {
1010    /// Could be any of the internal errors, like unsupported scheme, connection errors, etc.
1011    Internal(String),
1012    LoadCancelled,
1013    /// SSL validation error, to be converted to Resource::BadCertHTML in the HTML parser.
1014    SslValidation(String, Vec<u8>),
1015    /// Crash error, to be converted to Resource::Crash in the HTML parser.
1016    Crash(String),
1017}
1018
1019impl NetworkError {
1020    pub fn from_hyper_error(error: &HyperError, certificate: Option<CertificateDer>) -> Self {
1021        let error_string = error.to_string();
1022        match certificate {
1023            Some(certificate) => NetworkError::SslValidation(error_string, certificate.to_vec()),
1024            _ => NetworkError::Internal(error_string),
1025        }
1026    }
1027
1028    pub fn from_http_error(error: &HttpError) -> Self {
1029        NetworkError::Internal(error.to_string())
1030    }
1031}
1032
1033/// Normalize `slice`, as defined by
1034/// [the Fetch Spec](https://fetch.spec.whatwg.org/#concept-header-value-normalize).
1035pub fn trim_http_whitespace(mut slice: &[u8]) -> &[u8] {
1036    const HTTP_WS_BYTES: &[u8] = b"\x09\x0A\x0D\x20";
1037
1038    loop {
1039        match slice.split_first() {
1040            Some((first, remainder)) if HTTP_WS_BYTES.contains(first) => slice = remainder,
1041            _ => break,
1042        }
1043    }
1044
1045    loop {
1046        match slice.split_last() {
1047            Some((last, remainder)) if HTTP_WS_BYTES.contains(last) => slice = remainder,
1048            _ => break,
1049        }
1050    }
1051
1052    slice
1053}
1054
1055pub fn http_percent_encode(bytes: &[u8]) -> String {
1056    // This encode set is used for HTTP header values and is defined at
1057    // https://tools.ietf.org/html/rfc5987#section-3.2
1058    const HTTP_VALUE: &percent_encoding::AsciiSet = &percent_encoding::CONTROLS
1059        .add(b' ')
1060        .add(b'"')
1061        .add(b'%')
1062        .add(b'\'')
1063        .add(b'(')
1064        .add(b')')
1065        .add(b'*')
1066        .add(b',')
1067        .add(b'/')
1068        .add(b':')
1069        .add(b';')
1070        .add(b'<')
1071        .add(b'-')
1072        .add(b'>')
1073        .add(b'?')
1074        .add(b'[')
1075        .add(b'\\')
1076        .add(b']')
1077        .add(b'{')
1078        .add(b'}');
1079
1080    percent_encoding::percent_encode(bytes, HTTP_VALUE).to_string()
1081}
1082
1083pub fn set_default_accept_language(headers: &mut HeaderMap) {
1084    if headers.contains_key(header::ACCEPT_LANGUAGE) {
1085        return;
1086    }
1087
1088    // TODO(eijebong): Change this once typed headers are done
1089    headers.insert(
1090        header::ACCEPT_LANGUAGE,
1091        HeaderValue::from_static("en-US,en;q=0.5"),
1092    );
1093}
1094
1095pub static PRIVILEGED_SECRET: LazyLock<u32> =
1096    LazyLock::new(|| servo_rand::ServoRng::default().next_u32());