1use std::cell::Cell;
6use std::rc::Rc;
7use std::time::Duration;
8
9use base::id::WebViewId;
10use ipc_channel::ipc;
11use js::jsapi::{ExceptionStackBehavior, JS_IsExceptionPending};
12use js::jsval::UndefinedValue;
13use js::rust::HandleValue;
14use js::rust::wrappers::JS_SetPendingException;
15use net_traits::request::{
16 CorsSettings, CredentialsMode, Destination, Referrer, Request as NetTraitsRequest,
17 RequestBuilder, RequestId, RequestMode, ServiceWorkersMode,
18};
19use net_traits::{
20 CoreResourceMsg, CoreResourceThread, FetchChannels, FetchMetadata, FetchResponseMsg,
21 FilteredMetadata, Metadata, NetworkError, ResourceFetchTiming, cancel_async_fetch,
22};
23use rustc_hash::FxHashMap;
24use script_bindings::cformat;
25use serde::{Deserialize, Serialize};
26use servo_url::ServoUrl;
27use timers::TimerEventRequest;
28use uuid::Uuid;
29
30use crate::body::BodyMixin;
31use crate::dom::abortsignal::AbortAlgorithm;
32use crate::dom::bindings::codegen::Bindings::AbortSignalBinding::AbortSignalMethods;
33use crate::dom::bindings::codegen::Bindings::RequestBinding::{
34 RequestInfo, RequestInit, RequestMethods,
35};
36use crate::dom::bindings::codegen::Bindings::ResponseBinding::Response_Binding::ResponseMethods;
37use crate::dom::bindings::codegen::Bindings::ResponseBinding::ResponseType as DOMResponseType;
38use crate::dom::bindings::codegen::Bindings::WindowBinding::{DeferredRequestInit, WindowMethods};
39use crate::dom::bindings::error::{Error, Fallible};
40use crate::dom::bindings::import::module::SafeJSContext;
41use crate::dom::bindings::inheritance::Castable;
42use crate::dom::bindings::num::Finite;
43use crate::dom::bindings::refcounted::{Trusted, TrustedPromise};
44use crate::dom::bindings::reflector::DomGlobal;
45use crate::dom::bindings::root::DomRoot;
46use crate::dom::bindings::trace::RootedTraceableBox;
47use crate::dom::csp::{GlobalCspReporting, Violation};
48use crate::dom::fetchlaterresult::FetchLaterResult;
49use crate::dom::globalscope::GlobalScope;
50use crate::dom::headers::Guard;
51use crate::dom::performance::performanceresourcetiming::InitiatorType;
52use crate::dom::promise::Promise;
53use crate::dom::request::Request;
54use crate::dom::response::Response;
55use crate::dom::serviceworkerglobalscope::ServiceWorkerGlobalScope;
56use crate::dom::window::Window;
57use crate::network_listener::{
58 self, FetchResponseListener, NetworkListener, ResourceTimingListener, submit_timing_data,
59};
60use crate::realms::{InRealm, enter_realm};
61use crate::script_runtime::CanGc;
62
63#[derive(Default, JSTraceable, MallocSizeOf)]
68pub(crate) struct FetchCanceller {
69 #[no_trace]
70 request_id: Option<RequestId>,
71 #[no_trace]
72 core_resource_thread: Option<CoreResourceThread>,
73 keep_alive: bool,
74}
75
76impl FetchCanceller {
77 pub(crate) fn new(
80 request_id: RequestId,
81 keep_alive: bool,
82 core_resource_thread: CoreResourceThread,
83 ) -> Self {
84 Self {
85 request_id: Some(request_id),
86 core_resource_thread: Some(core_resource_thread),
87 keep_alive,
88 }
89 }
90
91 pub(crate) fn keep_alive(&self) -> bool {
92 self.keep_alive
93 }
94
95 fn cancel(&mut self) {
96 if let Some(request_id) = self.request_id.take() {
97 if let Some(ref core_resource_thread) = self.core_resource_thread {
101 cancel_async_fetch(vec![request_id], core_resource_thread);
104 }
105 }
106 }
107
108 pub(crate) fn ignore(&mut self) {
111 let _ = self.request_id.take();
112 }
113
114 pub(crate) fn abort(&mut self) {
116 self.cancel();
117 }
118
119 pub(crate) fn terminate(&mut self) {
121 self.cancel();
122 }
123}
124
125#[derive(Clone, Copy, Debug, Deserialize, Eq, Hash, MallocSizeOf, PartialEq, Serialize)]
126pub(crate) struct DeferredFetchRecordId(Uuid);
128
129impl Default for DeferredFetchRecordId {
130 fn default() -> Self {
131 Self(Uuid::new_v4())
132 }
133}
134
135pub(crate) type QueuedDeferredFetchRecord = Rc<DeferredFetchRecord>;
136
137#[derive(Default, MallocSizeOf)]
139pub(crate) struct FetchGroup {
140 #[conditional_malloc_size_of]
142 pub(crate) deferred_fetch_records: FxHashMap<DeferredFetchRecordId, QueuedDeferredFetchRecord>,
143}
144
145fn request_init_from_request(request: NetTraitsRequest, global: &GlobalScope) -> RequestBuilder {
146 let mut builder =
147 RequestBuilder::new(request.target_webview_id, request.url(), request.referrer)
148 .method(request.method)
149 .headers(request.headers)
150 .unsafe_request(request.unsafe_request)
151 .body(request.body)
152 .destination(request.destination)
153 .synchronous(request.synchronous)
154 .mode(request.mode)
155 .cache_mode(request.cache_mode)
156 .use_cors_preflight(request.use_cors_preflight)
157 .credentials_mode(request.credentials_mode)
158 .use_url_credentials(request.use_url_credentials)
159 .referrer_policy(request.referrer_policy)
160 .pipeline_id(request.pipeline_id)
161 .redirect_mode(request.redirect_mode)
162 .integrity_metadata(request.integrity_metadata)
163 .cryptographic_nonce_metadata(request.cryptographic_nonce_metadata)
164 .parser_metadata(request.parser_metadata)
165 .initiator(request.initiator)
166 .client(global.request_client())
167 .insecure_requests_policy(request.insecure_requests_policy)
168 .has_trustworthy_ancestor_origin(request.has_trustworthy_ancestor_origin)
169 .https_state(request.https_state)
170 .response_tainting(request.response_tainting);
171 builder.id = request.id;
172 builder
173}
174
175fn abort_fetch_call(
177 promise: Rc<Promise>,
178 request: &Request,
179 response_object: Option<&Response>,
180 abort_reason: HandleValue,
181 global: &GlobalScope,
182 cx: SafeJSContext,
183 can_gc: CanGc,
184) {
185 promise.reject(cx, abort_reason, can_gc);
187 if let Some(body) = request.body() {
189 if body.is_readable() {
190 body.cancel(cx, global, abort_reason, can_gc);
191 }
192 }
193 let Some(response) = response_object else {
196 return;
197 };
198 if let Some(body) = response.body() {
200 if body.is_readable() {
201 body.error(abort_reason, can_gc);
202 }
203 }
204}
205
206#[expect(non_snake_case)]
208pub(crate) fn Fetch(
209 global: &GlobalScope,
210 input: RequestInfo,
211 init: RootedTraceableBox<RequestInit>,
212 comp: InRealm,
213 can_gc: CanGc,
214) -> Rc<Promise> {
215 let promise = Promise::new_in_current_realm(comp, can_gc);
217 let cx = GlobalScope::get_cx();
218
219 let response = Response::new(global, can_gc);
222 response.Headers(can_gc).set_guard(Guard::Immutable);
223
224 let request_object = match Request::Constructor(global, None, can_gc, input, init) {
227 Err(e) => {
228 response.error_stream(e.clone(), can_gc);
229 promise.reject_error(e, can_gc);
230 return promise;
231 },
232 Ok(r) => r,
233 };
234 let request = request_object.get_request();
236 let request_id = request.id;
237
238 let signal = request_object.Signal();
240 if signal.aborted() {
241 rooted!(in(*cx) let mut abort_reason = UndefinedValue());
243 signal.Reason(cx, abort_reason.handle_mut());
244 abort_fetch_call(
245 promise.clone(),
246 &request_object,
247 None,
248 abort_reason.handle(),
249 global,
250 cx,
251 can_gc,
252 );
253 return promise;
255 }
256
257 let keep_alive = request.keep_alive;
258 let mut request_init = request_init_from_request(request, global);
261
262 if global.is::<ServiceWorkerGlobalScope>() {
265 request_init.service_workers_mode = ServiceWorkersMode::None;
266 }
267
268 let fetch_context = FetchContext {
275 fetch_promise: Some(TrustedPromise::new(promise.clone())),
276 response_object: Trusted::new(&*response),
277 request: Trusted::new(&*request_object),
278 global: Trusted::new(global),
279 locally_aborted: false,
280 canceller: FetchCanceller::new(request_id, keep_alive, global.core_resource_thread()),
281 url: request_init.url.clone(),
282 };
283 let network_listener = NetworkListener::new(
284 fetch_context,
285 global.task_manager().networking_task_source().to_sendable(),
286 );
287 let fetch_context = network_listener.context.clone();
288
289 signal.add(&AbortAlgorithm::Fetch(fetch_context));
291
292 global.fetch_with_network_listener(request_init, network_listener);
295
296 promise
298}
299
300fn queue_deferred_fetch(
302 request: NetTraitsRequest,
303 activate_after: Finite<f64>,
304 global: &GlobalScope,
305) -> DeferredFetchRecordId {
306 let trusted_global = Trusted::new(global);
307 let mut request = request;
308 request.client = Some(global.request_client());
310 request.populate_request_from_client();
311 request.service_workers_mode = ServiceWorkersMode::None;
313 request.keep_alive = true;
315 let deferred_record = Rc::new(DeferredFetchRecord {
317 request,
318 invoke_state: Cell::new(DeferredFetchRecordInvokeState::Pending),
319 activated: Cell::new(false),
320 });
321 let deferred_fetch_record_id = global.append_deferred_fetch(deferred_record);
323 global.schedule_timer(TimerEventRequest {
325 callback: Box::new(move || {
326 let global = trusted_global.root();
328 global.deferred_fetch_record_for_id(&deferred_fetch_record_id).process(&global);
329
330 let trusted_global = trusted_global.clone();
335 global.task_manager().deferred_fetch_task_source().queue(
336 task!(notify_deferred_record: move || {
337 trusted_global.root().deferred_fetch_record_for_id(&deferred_fetch_record_id).activate();
338 }),
339 );
340 }),
341 duration: Duration::from_millis(*activate_after as u64),
343 });
344 deferred_fetch_record_id
346}
347
348#[expect(non_snake_case, unsafe_code)]
350pub(crate) fn FetchLater(
351 window: &Window,
352 input: RequestInfo,
353 init: RootedTraceableBox<DeferredRequestInit>,
354 can_gc: CanGc,
355) -> Fallible<DomRoot<FetchLaterResult>> {
356 let global_scope = window.upcast();
357 let document = window.Document();
358 let request_object = Request::constructor(global_scope, None, can_gc, input, &init.parent)?;
361 let signal = request_object.Signal();
363 if signal.aborted() {
364 let cx = GlobalScope::get_cx();
365 rooted!(in(*cx) let mut abort_reason = UndefinedValue());
366 signal.Reason(cx, abort_reason.handle_mut());
367 unsafe {
368 assert!(!JS_IsExceptionPending(*cx));
369 JS_SetPendingException(*cx, abort_reason.handle(), ExceptionStackBehavior::Capture);
370 }
371 return Err(Error::JSFailed);
372 }
373 let request = request_object.get_request();
375 let mut activate_after = Finite::wrap(0_f64);
377 if let Some(init_activate_after) = init.activateAfter.as_ref() {
380 activate_after = *init_activate_after;
381 }
382 if *activate_after < 0.0 {
384 return Err(Error::Range(c"activateAfter must be at least 0".to_owned()));
385 }
386 if !document.is_fully_active() {
388 return Err(Error::Type(c"Document is not fully active".to_owned()));
389 }
390 let url = request.url();
391 if !matches!(url.scheme(), "http" | "https") {
393 return Err(Error::Type(c"URL is not http(s)".to_owned()));
394 }
395 if !url.is_potentially_trustworthy() {
397 return Err(Error::Type(c"URL is not trustworthy".to_owned()));
398 }
399 if let Some(body) = request.body.as_ref() {
401 if body.len().is_none_or(|len| len == 0) {
402 return Err(Error::Type(c"Body is empty".to_owned()));
403 }
404 }
405 let quota = document.available_deferred_fetch_quota(request.url().origin());
408 let requested = request.total_request_length() as isize;
409 if quota < requested {
410 return Err(Error::QuotaExceeded {
411 quota: Some(Finite::wrap(quota as f64)),
412 requested: Some(Finite::wrap(requested as f64)),
413 });
414 }
415 let deferred_record_id = queue_deferred_fetch(request, activate_after, global_scope);
419 signal.add(&AbortAlgorithm::FetchLater(deferred_record_id));
421 Ok(FetchLaterResult::new(window, deferred_record_id, can_gc))
423}
424
425#[derive(Clone, Copy, MallocSizeOf, PartialEq)]
427pub(crate) enum DeferredFetchRecordInvokeState {
428 Pending,
429 Sent,
430 Aborted,
431}
432
433#[derive(MallocSizeOf)]
435pub(crate) struct DeferredFetchRecord {
436 pub(crate) request: NetTraitsRequest,
438 pub(crate) invoke_state: Cell<DeferredFetchRecordInvokeState>,
440 activated: Cell<bool>,
441}
442
443impl DeferredFetchRecord {
444 fn activate(&self) {
446 self.activated.set(true);
448 }
449 pub(crate) fn abort(&self) {
451 self.invoke_state
453 .set(DeferredFetchRecordInvokeState::Aborted);
454 }
455 pub(crate) fn activated_getter_steps(&self) -> bool {
457 self.activated.get()
459 }
460 pub(crate) fn process(&self, global: &GlobalScope) {
462 if self.invoke_state.get() != DeferredFetchRecordInvokeState::Pending {
464 return;
465 }
466 self.invoke_state.set(DeferredFetchRecordInvokeState::Sent);
468 let url = self.request.url().clone();
470 let fetch_later_listener = FetchLaterListener {
471 url,
472 global: Trusted::new(global),
473 };
474 let request_init = request_init_from_request(self.request.clone(), global);
475 global.fetch(
476 request_init,
477 fetch_later_listener,
478 global.task_manager().networking_task_source().to_sendable(),
479 );
480 }
482}
483
484#[derive(JSTraceable, MallocSizeOf)]
485pub(crate) struct FetchContext {
486 #[ignore_malloc_size_of = "unclear ownership semantics"]
487 fetch_promise: Option<TrustedPromise>,
488 response_object: Trusted<Response>,
489 request: Trusted<Request>,
490 global: Trusted<GlobalScope>,
491 locally_aborted: bool,
492 canceller: FetchCanceller,
493 #[no_trace]
494 url: ServoUrl,
495}
496
497impl FetchContext {
498 pub(crate) fn abort_fetch(
500 &mut self,
501 abort_reason: HandleValue,
502 cx: SafeJSContext,
503 can_gc: CanGc,
504 ) {
505 self.locally_aborted = true;
507 self.canceller.abort();
513
514 let promise = self
517 .fetch_promise
518 .take()
519 .expect("fetch promise is missing")
520 .root();
521 abort_fetch_call(
522 promise,
523 &self.request.root(),
524 Some(&self.response_object.root()),
525 abort_reason,
526 &self.global.root(),
527 cx,
528 can_gc,
529 );
530 }
531}
532
533impl FetchResponseListener for FetchContext {
535 fn process_request_body(&mut self, _: RequestId) {
536 }
538
539 fn process_request_eof(&mut self, _: RequestId) {
540 }
542
543 fn process_response(
544 &mut self,
545 _: RequestId,
546 fetch_metadata: Result<FetchMetadata, NetworkError>,
547 ) {
548 if self.locally_aborted {
550 return;
551 }
552 let promise = self
553 .fetch_promise
554 .take()
555 .expect("fetch promise is missing")
556 .root();
557
558 let _ac = enter_realm(&*promise);
559 match fetch_metadata {
560 Err(error) => {
563 promise.reject_error(
564 Error::Type(cformat!("Network error: {:?}", error)),
565 CanGc::note(),
566 );
567 self.fetch_promise = Some(TrustedPromise::new(promise));
568 let response = self.response_object.root();
569 response.set_type(DOMResponseType::Error, CanGc::note());
570 response.error_stream(
571 Error::Type(c"Network error occurred".to_owned()),
572 CanGc::note(),
573 );
574 return;
575 },
576 Ok(metadata) => match metadata {
579 FetchMetadata::Unfiltered(m) => {
580 fill_headers_with_metadata(self.response_object.root(), m, CanGc::note());
581 self.response_object
582 .root()
583 .set_type(DOMResponseType::Default, CanGc::note());
584 },
585 FetchMetadata::Filtered { filtered, .. } => match filtered {
586 FilteredMetadata::Basic(m) => {
587 fill_headers_with_metadata(self.response_object.root(), m, CanGc::note());
588 self.response_object
589 .root()
590 .set_type(DOMResponseType::Basic, CanGc::note());
591 },
592 FilteredMetadata::Cors(m) => {
593 fill_headers_with_metadata(self.response_object.root(), m, CanGc::note());
594 self.response_object
595 .root()
596 .set_type(DOMResponseType::Cors, CanGc::note());
597 },
598 FilteredMetadata::Opaque => {
599 self.response_object
600 .root()
601 .set_type(DOMResponseType::Opaque, CanGc::note());
602 },
603 FilteredMetadata::OpaqueRedirect(url) => {
604 let r = self.response_object.root();
605 r.set_type(DOMResponseType::Opaqueredirect, CanGc::note());
606 r.set_final_url(url);
607 },
608 },
609 },
610 }
611
612 promise.resolve_native(&self.response_object.root(), CanGc::note());
614 self.fetch_promise = Some(TrustedPromise::new(promise));
615 }
616
617 fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
618 let response = self.response_object.root();
619 response.stream_chunk(chunk, CanGc::note());
620 }
621
622 fn process_response_eof(
623 self,
624 cx: &mut js::context::JSContext,
625 _: RequestId,
626 response: Result<(), NetworkError>,
627 timing: ResourceFetchTiming,
628 ) {
629 let response_object = self.response_object.root();
630 let _ac = enter_realm(&*response_object);
631 if let Err(ref error) = response {
632 if *error == NetworkError::DecompressionError {
633 response_object.error_stream(
634 Error::Type(c"Network error occurred".to_owned()),
635 CanGc::from_cx(cx),
636 );
637 }
638 }
639 response_object.finish(CanGc::from_cx(cx));
640 network_listener::submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
645 }
646
647 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
648 let global = &self.resource_timing_global();
649 global.report_csp_violations(violations, None, None);
650 }
651}
652
653impl ResourceTimingListener for FetchContext {
654 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
655 (InitiatorType::Fetch, self.url.clone())
656 }
657
658 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
659 self.response_object.root().global()
660 }
661}
662
663struct FetchLaterListener {
664 url: ServoUrl,
666 global: Trusted<GlobalScope>,
668}
669
670impl FetchResponseListener for FetchLaterListener {
671 fn process_request_body(&mut self, _: RequestId) {}
672
673 fn process_request_eof(&mut self, _: RequestId) {}
674
675 fn process_response(
676 &mut self,
677 _: RequestId,
678 fetch_metadata: Result<FetchMetadata, NetworkError>,
679 ) {
680 _ = fetch_metadata;
681 }
682
683 fn process_response_chunk(&mut self, _: RequestId, chunk: Vec<u8>) {
684 _ = chunk;
685 }
686
687 fn process_response_eof(
688 self,
689 cx: &mut js::context::JSContext,
690 _: RequestId,
691 response: Result<(), NetworkError>,
692 timing: ResourceFetchTiming,
693 ) {
694 network_listener::submit_timing(&self, &response, &timing, CanGc::from_cx(cx));
695 }
696
697 fn process_csp_violations(&mut self, _request_id: RequestId, violations: Vec<Violation>) {
698 let global = self.resource_timing_global();
699 global.report_csp_violations(violations, None, None);
700 }
701}
702
703impl ResourceTimingListener for FetchLaterListener {
704 fn resource_timing_information(&self) -> (InitiatorType, ServoUrl) {
705 (InitiatorType::Fetch, self.url.clone())
706 }
707
708 fn resource_timing_global(&self) -> DomRoot<GlobalScope> {
709 self.global.root()
710 }
711}
712
713fn fill_headers_with_metadata(r: DomRoot<Response>, m: Metadata, can_gc: CanGc) {
714 r.set_headers(m.headers, can_gc);
715 r.set_status(&m.status);
716 r.set_final_url(m.final_url);
717 r.set_redirected(m.redirected);
718}
719
720pub(crate) trait CspViolationsProcessor {
721 fn process_csp_violations(&self, violations: Vec<Violation>);
722}
723
724pub(crate) fn load_whole_resource(
726 request: RequestBuilder,
727 core_resource_thread: &CoreResourceThread,
728 global: &GlobalScope,
729 csp_violations_processor: &dyn CspViolationsProcessor,
730 cx: &mut js::context::JSContext,
731) -> Result<(Metadata, Vec<u8>, bool), NetworkError> {
732 let request = request.https_state(global.get_https_state());
733 let (action_sender, action_receiver) = ipc::channel().unwrap();
734 let url = request.url.clone();
735 core_resource_thread
736 .send(CoreResourceMsg::Fetch(
737 request,
738 FetchChannels::ResponseMsg(action_sender),
739 ))
740 .unwrap();
741
742 let mut buf = vec![];
743 let mut metadata = None;
744 let mut muted_errors = false;
745 loop {
746 match action_receiver.recv().unwrap() {
747 FetchResponseMsg::ProcessRequestBody(..) | FetchResponseMsg::ProcessRequestEOF(..) => {
748 },
749 FetchResponseMsg::ProcessResponse(_, Ok(m)) => {
750 muted_errors = m.is_cors_cross_origin();
751 metadata = Some(match m {
752 FetchMetadata::Unfiltered(m) => m,
753 FetchMetadata::Filtered { unsafe_, .. } => unsafe_,
754 })
755 },
756 FetchResponseMsg::ProcessResponseChunk(_, data) => buf.extend_from_slice(&data),
757 FetchResponseMsg::ProcessResponseEOF(_, Ok(_), _) => {
758 let metadata = metadata.unwrap();
759 if let Some(timing) = &metadata.timing {
760 submit_timing_data(
761 global,
762 url,
763 InitiatorType::Other,
764 timing,
765 CanGc::from_cx(cx),
766 );
767 }
768 return Ok((metadata, buf, muted_errors));
769 },
770 FetchResponseMsg::ProcessResponse(_, Err(e)) |
771 FetchResponseMsg::ProcessResponseEOF(_, Err(e), _) => return Err(e),
772 FetchResponseMsg::ProcessCspViolations(_, violations) => {
773 csp_violations_processor.process_csp_violations(violations);
774 },
775 }
776 }
777}
778
779pub(crate) trait RequestWithGlobalScope {
780 fn with_global_scope(self, global: &GlobalScope) -> Self;
781}
782
783impl RequestWithGlobalScope for RequestBuilder {
784 fn with_global_scope(self, global: &GlobalScope) -> Self {
785 self.insecure_requests_policy(global.insecure_requests_policy())
786 .has_trustworthy_ancestor_origin(global.has_trustworthy_ancestor_or_current_origin())
787 .policy_container(global.policy_container())
788 .client(global.request_client())
789 .pipeline_id(Some(global.pipeline_id()))
790 .origin(global.origin().immutable().clone())
791 .https_state(global.get_https_state())
792 }
793}
794
795#[allow(clippy::too_many_arguments)]
797pub(crate) fn create_a_potential_cors_request(
798 webview_id: Option<WebViewId>,
799 url: ServoUrl,
800 destination: Destination,
801 cors_setting: Option<CorsSettings>,
802 same_origin_fallback: Option<bool>,
803 referrer: Referrer,
804) -> RequestBuilder {
805 RequestBuilder::new(webview_id, url, referrer)
806 .mode(match cors_setting {
808 Some(_) => RequestMode::CorsMode,
809 None if same_origin_fallback == Some(true) => RequestMode::SameOrigin,
811 None => RequestMode::NoCors,
812 })
813 .credentials_mode(match cors_setting {
814 Some(CorsSettings::Anonymous) => CredentialsMode::CredentialsSameOrigin,
816 _ => CredentialsMode::Include,
818 })
819 .destination(destination)
822 .use_url_credentials(true)
823}