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