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