script/
fetch.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
5use std::rc::Rc;
6use std::sync::{Arc, Mutex};
7
8use base::id::WebViewId;
9use ipc_channel::ipc;
10use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer};
11use net_traits::request::{
12    CorsSettings, CredentialsMode, Destination, InsecureRequestsPolicy, Referrer,
13    Request as NetTraitsRequest, RequestBuilder, RequestId, RequestMode, ServiceWorkersMode,
14};
15use net_traits::{
16    CoreResourceMsg, CoreResourceThread, FetchChannels, FetchMetadata, FetchResponseListener,
17    FetchResponseMsg, FilteredMetadata, Metadata, NetworkError, ResourceFetchTiming,
18    ResourceTimingType, cancel_async_fetch,
19};
20use servo_url::ServoUrl;
21
22use crate::dom::bindings::codegen::Bindings::RequestBinding::{
23    RequestInfo, RequestInit, RequestMethods,
24};
25use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
26use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
27use crate::dom::bindings::error::Error;
28use crate::dom::bindings::inheritance::Castable;
29use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
30use crate::dom::bindings::reflector::DomGlobal;
31use crate::dom::bindings::root::DomRoot;
32use crate::dom::bindings::trace::RootedTraceableBox;
33use crate::dom::csp::{GlobalCspReporting, Violation};
34use crate::dom::globalscope::GlobalScope;
35use crate::dom::headers::Guard;
36use crate::dom::performanceresourcetiming::InitiatorType;
37use crate::dom::promise::Promise;
38use crate::dom::request::Request;
39use crate::dom::response::Response;
40use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
41use crate::network_listener::{self, PreInvoke, ResourceTimingListener, submit_timing_data};
42use crate::realms::{InRealm, enter_realm};
43use crate::script_runtime::CanGc;
44
45struct FetchContext {
46    fetch_promise: Option<TrustedPromise>,
47    response_object: Trusted<Response>,
48    resource_timing: ResourceFetchTiming,
49}
50
51/// RAII fetch canceller object.
52/// By default initialized to having a
53/// request associated with it, which can be manually cancelled with `cancel`,
54/// or automatically cancelled on drop.
55/// Calling `ignore` will sever the relationship with the request,
56/// meaning it cannot be cancelled through this canceller from that point on.
57#[derive(Default, JSTraceable, MallocSizeOf)]
58pub(crate) struct FetchCanceller {
59    #[no_trace]
60    request_id: Option<RequestId>,
61    #[no_trace]
62    core_resource_thread: Option<CoreResourceThread>,
63}
64
65impl FetchCanceller {
66    /// Create a FetchCanceller associated with a request,
67    // and a particular(public vs private) resource thread.
68    pub(crate) fn new(request_id: RequestId, core_resource_thread: CoreResourceThread) -> Self {
69        Self {
70            request_id: Some(request_id),
71            core_resource_thread: Some(core_resource_thread),
72        }
73    }
74
75    /// Cancel a fetch if it is ongoing
76    pub(crate) fn cancel(&mut self) {
77        if let Some(request_id) = self.request_id.take() {
78            // stop trying to make fetch happen
79            // it's not going to happen
80
81            if let Some(ref core_resource_thread) = self.core_resource_thread {
82                // No error handling here. Cancellation is a courtesy call,
83                // we don't actually care if the other side heard.
84                cancel_async_fetch(vec![request_id], core_resource_thread);
85            }
86        }
87    }
88
89    /// Use this if you don't want it to send a cancellation request
90    /// on drop (e.g. if the fetch completes)
91    pub(crate) fn ignore(&mut self) {
92        let _ = self.request_id.take();
93    }
94}
95
96impl Drop for FetchCanceller {
97    fn drop(&mut self) {
98        self.cancel()
99    }
100}
101
102fn request_init_from_request(request: NetTraitsRequest) -> RequestBuilder {
103    RequestBuilder {
104        id: request.id,
105        method: request.method.clone(),
106        url: request.url(),
107        headers: request.headers.clone(),
108        unsafe_request: request.unsafe_request,
109        body: request.body.clone(),
110        service_workers_mode: ServiceWorkersMode::All,
111        destination: request.destination,
112        synchronous: request.synchronous,
113        mode: request.mode.clone(),
114        cache_mode: request.cache_mode,
115        use_cors_preflight: request.use_cors_preflight,
116        credentials_mode: request.credentials_mode,
117        use_url_credentials: request.use_url_credentials,
118        origin: GlobalScope::current()
119            .expect("No current global object")
120            .origin()
121            .immutable()
122            .clone(),
123        referrer: request.referrer.clone(),
124        referrer_policy: request.referrer_policy,
125        pipeline_id: request.pipeline_id,
126        target_webview_id: request.target_webview_id,
127        redirect_mode: request.redirect_mode,
128        integrity_metadata: request.integrity_metadata.clone(),
129        cryptographic_nonce_metadata: request.cryptographic_nonce_metadata.clone(),
130        url_list: vec![],
131        parser_metadata: request.parser_metadata,
132        initiator: request.initiator,
133        policy_container: request.policy_container,
134        insecure_requests_policy: request.insecure_requests_policy,
135        has_trustworthy_ancestor_origin: request.has_trustworthy_ancestor_origin,
136        https_state: request.https_state,
137        response_tainting: request.response_tainting,
138        crash: None,
139    }
140}
141
142/// <https://fetch.spec.whatwg.org/#fetch-method>
143#[allow(non_snake_case)]
144#[cfg_attr(crown, allow(crown::unrooted_must_root))]
145pub(crate) fn Fetch(
146    global: &GlobalScope,
147    input: RequestInfo,
148    init: RootedTraceableBox<RequestInit>,
149    comp: InRealm,
150    can_gc: CanGc,
151) -> Rc<Promise> {
152    // Step 1. Let p be a new promise.
153    let promise = Promise::new_in_current_realm(comp, can_gc);
154
155    // Step 7. Let responseObject be null.
156    // NOTE: We do initialize the object earlier earlier so we can use it to track errors
157    let response = Response::new(global, can_gc);
158    response.Headers(can_gc).set_guard(Guard::Immutable);
159
160    // Step 2. Let requestObject be the result of invoking the initial value of Request as constructor
161    //         with input and init as arguments. If this throws an exception, reject p with it and return p.
162    let request = match Request::Constructor(global, None, can_gc, input, init) {
163        Err(e) => {
164            response.error_stream(e.clone(), can_gc);
165            promise.reject_error(e, can_gc);
166            return promise;
167        },
168        Ok(r) => {
169            // Step 3. Let request be requestObject’s request.
170            r.get_request()
171        },
172    };
173    let timing_type = request.timing_type();
174
175    let mut request_init = request_init_from_request(request);
176    request_init.policy_container =
177        RequestPolicyContainer::PolicyContainer(global.policy_container());
178
179    // TODO: Step 4. If requestObject’s signal is aborted, then: [..]
180
181    // Step 5. Let globalObject be request’s client’s global object.
182    // NOTE:   We already get the global object as an argument
183
184    // Step 6. If globalObject is a ServiceWorkerGlobalScope object, then set request’s
185    //         service-workers mode to "none".
186    if global.is::<ServiceWorkerGlobalScope>() {
187        request_init.service_workers_mode = ServiceWorkersMode::None;
188    }
189
190    // TODO: Steps 8-11, abortcontroller stuff
191
192    // Step 12. Set controller to the result of calling fetch given request and
193    //           processResponse given response being these steps: [..]
194    let fetch_context = Arc::new(Mutex::new(FetchContext {
195        fetch_promise: Some(TrustedPromise::new(promise.clone())),
196        response_object: Trusted::new(&*response),
197        resource_timing: ResourceFetchTiming::new(timing_type),
198    }));
199
200    global.fetch(
201        request_init,
202        fetch_context,
203        global.task_manager().networking_task_source().to_sendable(),
204    );
205
206    // Step 13. Return p.
207    promise
208}
209
210impl PreInvoke for FetchContext {}
211
212impl FetchResponseListener for FetchContext {
213    fn process_request_body(&mut self, _: RequestId) {
214        // TODO
215    }
216
217    fn process_request_eof(&mut self, _: RequestId) {
218        // TODO
219    }
220
221    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
222    fn process_response(
223        &mut self,
224        _: RequestId,
225        fetch_metadata: Result<FetchMetadata, NetworkError>,
226    ) {
227        let promise = self
228            .fetch_promise
229            .take()
230            .expect("fetch promise is missing")
231            .root();
232
233        let _ac = enter_realm(&*promise);
234        match fetch_metadata {
235            // Step 4.1
236            Err(_) => {
237                promise.reject_error(
238                    Error::Type("Network error occurred".to_string()),
239                    CanGc::note(),
240                );
241                self.fetch_promise = Some(TrustedPromise::new(promise));
242                let response = self.response_object.root();
243                response.set_type(DOMResponseType::Error, CanGc::note());
244                response.error_stream(
245                    Error::Type("Network error occurred".to_string()),
246                    CanGc::note(),
247                );
248                return;
249            },
250            // Step 4.2
251            Ok(metadata) => match metadata {
252                FetchMetadata::Unfiltered(m) => {
253                    fill_headers_with_metadata(self.response_object.root(), m, CanGc::note());
254                    self.response_object
255                        .root()
256                        .set_type(DOMResponseType::Default, CanGc::note());
257                },
258                FetchMetadata::Filtered { filtered, .. } => match filtered {
259                    FilteredMetadata::Basic(m) => {
260                        fill_headers_with_metadata(self.response_object.root(), m, CanGc::note());
261                        self.response_object
262                            .root()
263                            .set_type(DOMResponseType::Basic, CanGc::note());
264                    },
265                    FilteredMetadata::Cors(m) => {
266                        fill_headers_with_metadata(self.response_object.root(), m, CanGc::note());
267                        self.response_object
268                            .root()
269                            .set_type(DOMResponseType::Cors, CanGc::note());
270                    },
271                    FilteredMetadata::Opaque => {
272                        self.response_object
273                            .root()
274                            .set_type(DOMResponseType::Opaque, CanGc::note());
275                    },
276                    FilteredMetadata::OpaqueRedirect(url) => {
277                        let r = self.response_object.root();
278                        r.set_type(DOMResponseType::Opaqueredirect, CanGc::note());
279                        r.set_final_url(url);
280                    },
281                },
282            },
283        }
284
285        // Step 4.3
286        promise.resolve_native(&self.response_object.root(), CanGc::note());
287        self.fetch_promise = Some(TrustedPromise::new(promise));
288    }
289
290    fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
291        let response = self.response_object.root();
292        response.stream_chunk(chunk, CanGc::note());
293    }
294
295    fn process_response_eof(
296        &mut self,
297        _: RequestId,
298        _response: Result<ResourceFetchTiming, NetworkError>,
299    ) {
300        let response = self.response_object.root();
301        let _ac = enter_realm(&*response);
302        response.finish(CanGc::note());
303        // TODO
304        // ... trailerObject is not supported in Servo yet.
305    }
306
307    fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
308        &mut self.resource_timing
309    }
310
311    fn resource_timing(&self) -> &ResourceFetchTiming {
312        &self.resource_timing
313    }
314
315    fn submit_resource_timing(&mut self) {
316        // navigation submission is handled in servoparser/mod.rs
317        if self.resource_timing.timing_type == ResourceTimingType::Resource {
318            network_listener::submit_timing(self, CanGc::note())
319        }
320    }
321
322    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
323        let global = &self.resource_timing_global();
324        global.report_csp_violations(violations, None, None);
325    }
326}
327
328impl ResourceTimingListener for FetchContext {
329    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
330        (
331            InitiatorType::Fetch,
332            self.resource_timing_global().get_url().clone(),
333        )
334    }
335
336    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
337        self.response_object.root().global()
338    }
339}
340
341fn fill_headers_with_metadata(r: DomRoot<Response>, m: Metadata, can_gc: CanGc) {
342    r.set_headers(m.headers, can_gc);
343    r.set_status(&m.status);
344    r.set_final_url(m.final_url);
345    r.set_redirected(m.redirected);
346}
347
348pub(crate) trait CspViolationsProcessor {
349    fn process_csp_violations(&self, violations: Vec<Violation>);
350}
351
352/// Convenience function for synchronously loading a whole resource.
353pub(crate) fn load_whole_resource(
354    request: RequestBuilder,
355    core_resource_thread: &CoreResourceThread,
356    global: &GlobalScope,
357    csp_violations_processor: &dyn CspViolationsProcessor,
358    can_gc: CanGc,
359) -> Result<(Metadata, Vec<u8>), NetworkError> {
360    let request = request.https_state(global.get_https_state());
361    let (action_sender, action_receiver) = ipc::channel().unwrap();
362    let url = request.url.clone();
363    core_resource_thread
364        .send(CoreResourceMsg::Fetch(
365            request,
366            FetchChannels::ResponseMsg(action_sender),
367        ))
368        .unwrap();
369
370    let mut buf = vec![];
371    let mut metadata = None;
372    loop {
373        match action_receiver.recv().unwrap() {
374            FetchResponseMsg::ProcessRequestBody(..) | FetchResponseMsg::ProcessRequestEOF(..) => {
375            },
376            FetchResponseMsg::ProcessResponse(_, Ok(m)) => {
377                metadata = Some(match m {
378                    FetchMetadata::Unfiltered(m) => m,
379                    FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
380                })
381            },
382            FetchResponseMsg::ProcessResponseChunk(_, data) => buf.extend_from_slice(&data),
383            FetchResponseMsg::ProcessResponseEOF(_, Ok(_)) => {
384                let metadata = metadata.unwrap();
385                if let Some(timing) = &metadata.timing {
386                    submit_timing_data(global, url, InitiatorType::Other, timing, can_gc);
387                }
388                return Ok((metadata, buf));
389            },
390            FetchResponseMsg::ProcessResponse(_, Err(e)) |
391            FetchResponseMsg::ProcessResponseEOF(_, Err(e)) => return Err(e),
392            FetchResponseMsg::ProcessCspViolations(_, violations) => {
393                csp_violations_processor.process_csp_violations(violations);
394            },
395        }
396    }
397}
398
399/// <https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request>
400#[allow(clippy::too_many_arguments)]
401pub(crate) fn create_a_potential_cors_request(
402    webview_id: Option<WebViewId>,
403    url: ServoUrl,
404    destination: Destination,
405    cors_setting: Option<CorsSettings>,
406    same_origin_fallback: Option<bool>,
407    referrer: Referrer,
408    insecure_requests_policy: InsecureRequestsPolicy,
409    has_trustworthy_ancestor_origin: bool,
410    policy_container: PolicyContainer,
411) -> RequestBuilder {
412    RequestBuilder::new(webview_id, url, referrer)
413        // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request
414        // Step 1
415        .mode(match cors_setting {
416            Some(_) => RequestMode::CorsMode,
417            None if same_origin_fallback == Some(true) => RequestMode::SameOrigin,
418            None => RequestMode::NoCors,
419        })
420        // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request
421        // Step 3-4
422        .credentials_mode(match cors_setting {
423            Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin,
424            _ => CredentialsMode::Include,
425        })
426        // Step 5
427        .destination(destination)
428        .use_url_credentials(true)
429        .insecure_requests_policy(insecure_requests_policy)
430        .has_trustworthy_ancestor_origin(has_trustworthy_ancestor_origin)
431        .policy_container(policy_container)
432}