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 js::jsval::UndefinedValue;
11use js::rust::HandleValue;
12use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer};
13use net_traits::request::{
14    CorsSettings, CredentialsMode, Destination, InsecureRequestsPolicy, Referrer,
15    Request as NetTraitsRequest, RequestBuilder, RequestId, RequestMode, ServiceWorkersMode,
16};
17use net_traits::{
18    CoreResourceMsg, CoreResourceThread, FetchChannels, FetchMetadata, FetchResponseListener,
19    FetchResponseMsg, FilteredMetadata, Metadata, NetworkError, ResourceFetchTiming,
20    ResourceTimingType, cancel_async_fetch,
21};
22use servo_url::ServoUrl;
23
24use crate::body::BodyMixin;
25use crate::dom::abortsignal::AbortAlgorithm;
26use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
27use crate::dom::bindings::codegen::Bindings::RequestBinding::{
28    RequestInfo, RequestInit, RequestMethods,
29};
30use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
31use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
32use crate::dom::bindings::error::Error;
33use crate::dom::bindings::import::module::SafeJSContext;
34use crate::dom::bindings::inheritance::Castable;
35use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
36use crate::dom::bindings::reflector::DomGlobal;
37use crate::dom::bindings::root::DomRoot;
38use crate::dom::bindings::trace::RootedTraceableBox;
39use crate::dom::csp::{GlobalCspReporting, Violation};
40use crate::dom::globalscope::GlobalScope;
41use crate::dom::headers::Guard;
42use crate::dom::performanceresourcetiming::InitiatorType;
43use crate::dom::promise::Promise;
44use crate::dom::request::Request;
45use crate::dom::response::Response;
46use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
47use crate::network_listener::{self, PreInvoke, ResourceTimingListener, submit_timing_data};
48use crate::realms::{InRealm, enter_realm};
49use crate::script_runtime::CanGc;
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/#abort-fetch>
143fn abort_fetch_call(
144    promise: Rc<Promise>,
145    request: &Request,
146    response_object: Option<&Response>,
147    abort_reason: HandleValue,
148    global: &GlobalScope,
149    cx: SafeJSContext,
150    can_gc: CanGc,
151) {
152    // Step 1. Reject promise with error.
153    promise.reject(cx, abort_reason, can_gc);
154    // Step 2. If request’s body is non-null and is readable, then cancel request’s body with error.
155    if let Some(body) = request.body() {
156        if body.is_readable() {
157            body.cancel(cx, global, abort_reason, can_gc);
158        }
159    }
160    // Step 3. If responseObject is null, then return.
161    // Step 4. Let response be responseObject’s response.
162    let Some(response) = response_object else {
163        return;
164    };
165    // Step 5. If response’s body is non-null and is readable, then error response’s body with error.
166    if let Some(body) = response.body() {
167        if body.is_readable() {
168            body.error(abort_reason, can_gc);
169        }
170    }
171}
172
173/// <https://fetch.spec.whatwg.org/#dom-global-fetch>
174#[allow(non_snake_case)]
175#[cfg_attr(crown, allow(crown::unrooted_must_root))]
176pub(crate) fn Fetch(
177    global: &GlobalScope,
178    input: RequestInfo,
179    init: RootedTraceableBox<RequestInit>,
180    comp: InRealm,
181    can_gc: CanGc,
182) -> Rc<Promise> {
183    // Step 1. Let p be a new promise.
184    let promise = Promise::new_in_current_realm(comp, can_gc);
185    let cx = GlobalScope::get_cx();
186
187    // Step 7. Let responseObject be null.
188    // NOTE: We do initialize the object earlier earlier so we can use it to track errors
189    let response = Response::new(global, can_gc);
190    response.Headers(can_gc).set_guard(Guard::Immutable);
191
192    // Step 2. Let requestObject be the result of invoking the initial value of Request as constructor
193    //         with input and init as arguments. If this throws an exception, reject p with it and return p.
194    let request_object = match Request::Constructor(global, None, can_gc, input, init) {
195        Err(e) => {
196            response.error_stream(e.clone(), can_gc);
197            promise.reject_error(e, can_gc);
198            return promise;
199        },
200        Ok(r) => r,
201    };
202    // Step 3. Let request be requestObject’s request.
203    let request = request_object.get_request();
204    let timing_type = request.timing_type();
205    let request_id = request.id;
206
207    // Step 4. If requestObject’s signal is aborted, then:
208    let signal = request_object.Signal();
209    if signal.aborted() {
210        // Step 4.1. Abort the fetch() call with p, request, null, and requestObject’s signal’s abort reason.
211        rooted!(in(*cx) let mut abort_reason = UndefinedValue());
212        signal.Reason(cx, abort_reason.handle_mut());
213        abort_fetch_call(
214            promise.clone(),
215            &request_object,
216            None,
217            abort_reason.handle(),
218            global,
219            cx,
220            can_gc,
221        );
222        // Step 4.2. Return p.
223        return promise;
224    }
225
226    // Step 5. Let globalObject be request’s client’s global object.
227    // NOTE:   We already get the global object as an argument
228    let mut request_init = request_init_from_request(request);
229    request_init.policy_container =
230        RequestPolicyContainer::PolicyContainer(global.policy_container());
231
232    // Step 6. If globalObject is a ServiceWorkerGlobalScope object, then set request’s
233    //         service-workers mode to "none".
234    if global.is::<ServiceWorkerGlobalScope>() {
235        request_init.service_workers_mode = ServiceWorkersMode::None;
236    }
237
238    // Step 8. Let relevantRealm be this’s relevant realm.
239    //
240    // Is `comp` as argument
241
242    // Step 9. Let locallyAborted be false.
243    // Step 10. Let controller be null.
244    let fetch_context = Arc::new(Mutex::new(FetchContext {
245        fetch_promise: Some(TrustedPromise::new(promise.clone())),
246        response_object: Trusted::new(&*response),
247        request: Trusted::new(&*request_object),
248        global: Trusted::new(global),
249        resource_timing: ResourceFetchTiming::new(timing_type),
250        locally_aborted: false,
251        canceller: FetchCanceller::new(request_id, global.core_resource_thread()),
252    }));
253
254    // Step 11. Add the following abort steps to requestObject’s signal:
255    signal.add(&AbortAlgorithm::Fetch(fetch_context.clone()));
256
257    // Step 12. Set controller to the result of calling fetch given request and
258    // processResponse given response being these steps:
259    global.fetch(
260        request_init,
261        fetch_context,
262        global.task_manager().networking_task_source().to_sendable(),
263    );
264
265    // Step 13. Return p.
266    promise
267}
268
269#[derive(JSTraceable, MallocSizeOf)]
270pub(crate) struct FetchContext {
271    #[ignore_malloc_size_of = "unclear ownership semantics"]
272    fetch_promise: Option<TrustedPromise>,
273    response_object: Trusted<Response>,
274    request: Trusted<Request>,
275    global: Trusted<GlobalScope>,
276    #[no_trace]
277    resource_timing: ResourceFetchTiming,
278    locally_aborted: bool,
279    canceller: FetchCanceller,
280}
281
282impl PreInvoke for FetchContext {}
283
284impl FetchContext {
285    /// Step 11 of <https://fetch.spec.whatwg.org/#dom-global-fetch>
286    pub(crate) fn abort_fetch(
287        &mut self,
288        abort_reason: HandleValue,
289        cx: SafeJSContext,
290        can_gc: CanGc,
291    ) {
292        // Step 11.1. Set locallyAborted to true.
293        self.locally_aborted = true;
294        // Step 11.2. Assert: controller is non-null.
295        //
296        // N/a, that's self
297
298        // Step 11.3. Abort controller with requestObject’s signal’s abort reason.
299        self.canceller.cancel();
300
301        // Step 11.4. Abort the fetch() call with p, request, responseObject,
302        // and requestObject’s signal’s abort reason.
303        let promise = self
304            .fetch_promise
305            .take()
306            .expect("fetch promise is missing")
307            .root();
308        abort_fetch_call(
309            promise,
310            &self.request.root(),
311            Some(&self.response_object.root()),
312            abort_reason,
313            &self.global.root(),
314            cx,
315            can_gc,
316        );
317    }
318}
319
320/// Step 12 of <https://fetch.spec.whatwg.org/#dom-global-fetch>
321impl FetchResponseListener for FetchContext {
322    fn process_request_body(&mut self, _: RequestId) {
323        // TODO
324    }
325
326    fn process_request_eof(&mut self, _: RequestId) {
327        // TODO
328    }
329
330    #[cfg_attr(crown, allow(crown::unrooted_must_root))]
331    fn process_response(
332        &mut self,
333        _: RequestId,
334        fetch_metadata: Result<FetchMetadata, NetworkError>,
335    ) {
336        // Step 12.1. If locallyAborted is true, then abort these steps.
337        if self.locally_aborted {
338            return;
339        }
340        let promise = self
341            .fetch_promise
342            .take()
343            .expect("fetch promise is missing")
344            .root();
345
346        let _ac = enter_realm(&*promise);
347        match fetch_metadata {
348            // Step 12.3. If response is a network error, then reject
349            // p with a TypeError and abort these steps.
350            Err(_) => {
351                promise.reject_error(
352                    Error::Type("Network error occurred".to_string()),
353                    CanGc::note(),
354                );
355                self.fetch_promise = Some(TrustedPromise::new(promise));
356                let response = self.response_object.root();
357                response.set_type(DOMResponseType::Error, CanGc::note());
358                response.error_stream(
359                    Error::Type("Network error occurred".to_string()),
360                    CanGc::note(),
361                );
362                return;
363            },
364            // Step 12.4. Set responseObject to the result of creating a Response object,
365            // given response, "immutable", and relevantRealm.
366            Ok(metadata) => match metadata {
367                FetchMetadata::Unfiltered(m) => {
368                    fill_headers_with_metadata(self.response_object.root(), m, CanGc::note());
369                    self.response_object
370                        .root()
371                        .set_type(DOMResponseType::Default, CanGc::note());
372                },
373                FetchMetadata::Filtered { filtered, .. } => match filtered {
374                    FilteredMetadata::Basic(m) => {
375                        fill_headers_with_metadata(self.response_object.root(), m, CanGc::note());
376                        self.response_object
377                            .root()
378                            .set_type(DOMResponseType::Basic, CanGc::note());
379                    },
380                    FilteredMetadata::Cors(m) => {
381                        fill_headers_with_metadata(self.response_object.root(), m, CanGc::note());
382                        self.response_object
383                            .root()
384                            .set_type(DOMResponseType::Cors, CanGc::note());
385                    },
386                    FilteredMetadata::Opaque => {
387                        self.response_object
388                            .root()
389                            .set_type(DOMResponseType::Opaque, CanGc::note());
390                    },
391                    FilteredMetadata::OpaqueRedirect(url) => {
392                        let r = self.response_object.root();
393                        r.set_type(DOMResponseType::Opaqueredirect, CanGc::note());
394                        r.set_final_url(url);
395                    },
396                },
397            },
398        }
399
400        // Step 12.5. Resolve p with responseObject.
401        promise.resolve_native(&self.response_object.root(), CanGc::note());
402        self.fetch_promise = Some(TrustedPromise::new(promise));
403    }
404
405    fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
406        let response = self.response_object.root();
407        response.stream_chunk(chunk, CanGc::note());
408    }
409
410    fn process_response_eof(
411        &mut self,
412        _: RequestId,
413        _response: Result<ResourceFetchTiming, NetworkError>,
414    ) {
415        let response = self.response_object.root();
416        let _ac = enter_realm(&*response);
417        response.finish(CanGc::note());
418        // TODO
419        // ... trailerObject is not supported in Servo yet.
420    }
421
422    fn resource_timing_mut(&mut self) -> &mut ResourceFetchTiming {
423        &mut self.resource_timing
424    }
425
426    fn resource_timing(&self) -> &ResourceFetchTiming {
427        &self.resource_timing
428    }
429
430    fn submit_resource_timing(&mut self) {
431        // navigation submission is handled in servoparser/mod.rs
432        if self.resource_timing.timing_type == ResourceTimingType::Resource {
433            network_listener::submit_timing(self, CanGc::note())
434        }
435    }
436
437    fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
438        let global = &self.resource_timing_global();
439        global.report_csp_violations(violations, None, None);
440    }
441}
442
443impl ResourceTimingListener for FetchContext {
444    fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
445        (
446            InitiatorType::Fetch,
447            self.resource_timing_global().get_url().clone(),
448        )
449    }
450
451    fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
452        self.response_object.root().global()
453    }
454}
455
456fn fill_headers_with_metadata(r: DomRoot<Response>, m: Metadata, can_gc: CanGc) {
457    r.set_headers(m.headers, can_gc);
458    r.set_status(&m.status);
459    r.set_final_url(m.final_url);
460    r.set_redirected(m.redirected);
461}
462
463pub(crate) trait CspViolationsProcessor {
464    fn process_csp_violations(&self, violations: Vec<Violation>);
465}
466
467/// Convenience function for synchronously loading a whole resource.
468pub(crate) fn load_whole_resource(
469    request: RequestBuilder,
470    core_resource_thread: &CoreResourceThread,
471    global: &GlobalScope,
472    csp_violations_processor: &dyn CspViolationsProcessor,
473    can_gc: CanGc,
474) -> Result<(Metadata, Vec<u8>), NetworkError> {
475    let request = request.https_state(global.get_https_state());
476    let (action_sender, action_receiver) = ipc::channel().unwrap();
477    let url = request.url.clone();
478    core_resource_thread
479        .send(CoreResourceMsg::Fetch(
480            request,
481            FetchChannels::ResponseMsg(action_sender),
482        ))
483        .unwrap();
484
485    let mut buf = vec![];
486    let mut metadata = None;
487    loop {
488        match action_receiver.recv().unwrap() {
489            FetchResponseMsg::ProcessRequestBody(..) | FetchResponseMsg::ProcessRequestEOF(..) => {
490            },
491            FetchResponseMsg::ProcessResponse(_, Ok(m)) => {
492                metadata = Some(match m {
493                    FetchMetadata::Unfiltered(m) => m,
494                    FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
495                })
496            },
497            FetchResponseMsg::ProcessResponseChunk(_, data) => buf.extend_from_slice(&data),
498            FetchResponseMsg::ProcessResponseEOF(_, Ok(_)) => {
499                let metadata = metadata.unwrap();
500                if let Some(timing) = &metadata.timing {
501                    submit_timing_data(global, url, InitiatorType::Other, timing, can_gc);
502                }
503                return Ok((metadata, buf));
504            },
505            FetchResponseMsg::ProcessResponse(_, Err(e)) |
506            FetchResponseMsg::ProcessResponseEOF(_, Err(e)) => return Err(e),
507            FetchResponseMsg::ProcessCspViolations(_, violations) => {
508                csp_violations_processor.process_csp_violations(violations);
509            },
510        }
511    }
512}
513
514/// <https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request>
515#[allow(clippy::too_many_arguments)]
516pub(crate) fn create_a_potential_cors_request(
517    webview_id: Option<WebViewId>,
518    url: ServoUrl,
519    destination: Destination,
520    cors_setting: Option<CorsSettings>,
521    same_origin_fallback: Option<bool>,
522    referrer: Referrer,
523    insecure_requests_policy: InsecureRequestsPolicy,
524    has_trustworthy_ancestor_origin: bool,
525    policy_container: PolicyContainer,
526) -> RequestBuilder {
527    RequestBuilder::new(webview_id, url, referrer)
528        // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request
529        // Step 1
530        .mode(match cors_setting {
531            Some(_) => RequestMode::CorsMode,
532            None if same_origin_fallback == Some(true) => RequestMode::SameOrigin,
533            None => RequestMode::NoCors,
534        })
535        // https://html.spec.whatwg.org/multipage/#create-a-potential-cors-request
536        // Step 3-4
537        .credentials_mode(match cors_setting {
538            Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin,
539            _ => CredentialsMode::Include,
540        })
541        // Step 5
542        .destination(destination)
543        .use_url_credentials(true)
544        .insecure_requests_policy(insecure_requests_policy)
545        .has_trustworthy_ancestor_origin(has_trustworthy_ancestor_origin)
546        .policy_container(policy_container)
547}