1use std::collections::HashSet;
6use std::iter::FromIterator;
7use std::sync::Arc as StdArc;
8use std::time::{Duration, SystemTime};
9
10use async_recursion::async_recursion;
11use content_security_policy::percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode};
12use devtools_traits::ChromeToDevtoolsControlMsg;
13use embedder_traits::{AuthenticationResponse, GenericEmbedderProxy};
14use futures::{TryFutureExt, TryStreamExt, future};
15use headers::authorization::Basic;
16use headers::{
17 AccessControlAllowCredentials, AccessControlAllowHeaders, AccessControlAllowMethods,
18 AccessControlMaxAge, AccessControlRequestMethod, Authorization, CacheControl, ContentLength,
19 HeaderMapExt, IfModifiedSince, LastModified, Pragma, Referer, StrictTransportSecurity,
20 UserAgent,
21};
22use http::header::{
23 self, ACCEPT, ACCESS_CONTROL_ALLOW_ORIGIN, ACCESS_CONTROL_REQUEST_HEADERS, AUTHORIZATION,
24 CONTENT_ENCODING, CONTENT_LANGUAGE, CONTENT_LOCATION, CONTENT_TYPE, HeaderValue, RANGE,
25 WWW_AUTHENTICATE,
26};
27use http::{HeaderMap, Method, Request as HyperRequest, StatusCode};
28use http_body_util::combinators::BoxBody;
29use http_body_util::{BodyExt, Full};
30use hyper::Response as HyperResponse;
31use hyper::body::{Bytes, Frame};
32use hyper::ext::ReasonPhrase;
33use hyper::header::{HeaderName, TRANSFER_ENCODING};
34use ipc_channel::IpcError;
35use ipc_channel::ipc::{self, IpcSender};
36use ipc_channel::router::ROUTER;
37use log::{debug, error, info, log_enabled, warn};
38use malloc_size_of::{MallocSizeOf, MallocSizeOfOps};
39use net_traits::blob_url_store::UrlWithBlobClaim;
40use net_traits::fetch::headers::get_value_from_header_list;
41use net_traits::http_status::HttpStatus;
42use net_traits::policy_container::{EmbedderPolicyValue, RequestPolicyContainer};
43use net_traits::pub_domains::{is_same_site, reg_suffix};
44use net_traits::request::{
45 BodyChunkRequest, BodyChunkResponse, CacheMode, CredentialsMode, Destination, Initiator,
46 Origin, RedirectMode, Referrer, Request, RequestBuilder, RequestClient, RequestMode,
47 ResponseTainting, ServiceWorkersMode, TraversableForUserPrompts, get_cors_unsafe_header_names,
48 is_cors_non_wildcard_request_header_name, is_cors_safelisted_method,
49 is_cors_safelisted_request_header,
50};
51use net_traits::response::{CacheState, RedirectTaint, Response, ResponseBody, ResponseType};
52use net_traits::{
53 CookieSource, DOCUMENT_ACCEPT_HEADER_VALUE, NetworkError, RedirectEndValue, RedirectStartValue,
54 ReferrerPolicy, ResourceAttribute, ResourceFetchTimingContainer, ResourceTimeValue,
55 TlsSecurityInfo, TlsSecurityState,
56};
57use parking_lot::{Mutex, RwLock};
58use profile_traits::mem::{Report, ReportKind};
59use profile_traits::path;
60#[cfg(feature = "tracing")]
61use profile_traits::trace_span;
62use rustc_hash::FxHashMap;
63use servo_base::cross_process_instant::CrossProcessInstant;
64use servo_base::generic_channel::GenericSharedMemory;
65use servo_base::id::{BrowsingContextId, HistoryStateId, PipelineId};
66use servo_url::{ImmutableOrigin, ServoUrl};
67use tokio::sync::mpsc::{
68 Receiver as TokioReceiver, Sender as TokioSender, UnboundedReceiver, UnboundedSender, channel,
69 unbounded_channel,
70};
71use tokio_stream::wrappers::ReceiverStream;
72#[cfg(feature = "tracing")]
73use tracing::Instrument;
74
75use crate::async_runtime::spawn_task;
76use crate::connector::{
77 CertificateErrorOverrideManager, ServoClient, TlsHandshakeInfo, create_tls_config,
78};
79use crate::cookie::ServoCookie;
80use crate::cookie_storage::CookieStorage;
81use crate::decoder::Decoder;
82use crate::devtools::{
83 prepare_devtools_request, send_request_to_devtools, send_response_values_to_devtools,
84};
85use crate::embedder::NetToEmbedderMsg;
86use crate::fetch::cors_cache::CorsCache;
87use crate::fetch::fetch_params::FetchParams;
88use crate::fetch::headers::{SecFetchDest, SecFetchMode, SecFetchSite, SecFetchUser};
89use crate::fetch::methods::{Data, DoneChannel, FetchContext, Target, main_fetch};
90use crate::hsts::HstsList;
91use crate::http_cache::{
92 CacheKey, CachedResourcesOrGuard, HttpCache, construct_response, invalidate_cached_resources,
93 refresh,
94};
95use crate::resource_thread::{AuthCache, AuthCacheEntry};
96use crate::websocket_loader::start_websocket;
97
98#[derive(Clone, Debug, Eq, PartialEq)]
100pub enum HttpCacheEntryState {
101 ReadyToConstruct,
105 PendingStore(usize),
107}
108
109pub struct HttpState {
110 pub hsts_list: RwLock<HstsList>,
111 pub cookie_jar: RwLock<CookieStorage>,
112 pub http_cache: HttpCache,
113 pub auth_cache: RwLock<AuthCache>,
114 pub history_states: RwLock<FxHashMap<HistoryStateId, Vec<u8>>>,
115 pub client: ServoClient,
116 pub override_manager: CertificateErrorOverrideManager,
117 pub embedder_proxy: GenericEmbedderProxy<NetToEmbedderMsg>,
118}
119
120impl HttpState {
121 pub(crate) fn memory_reports(&self, suffix: &str, ops: &mut MallocSizeOfOps) -> Vec<Report> {
122 vec![
123 Report {
124 path: path!["memory-cache", suffix],
125 kind: ReportKind::ExplicitJemallocHeapSize,
126 size: self.http_cache.size_of(ops),
127 },
128 Report {
129 path: path!["hsts-list", suffix],
130 kind: ReportKind::ExplicitJemallocHeapSize,
131 size: self.hsts_list.read().size_of(ops),
132 },
133 ]
134 }
135
136 async fn request_authentication(
137 &self,
138 request: &Request,
139 response: &Response,
140 ) -> Option<AuthenticationResponse> {
141 let webview_id = request.target_webview_id?;
143 let for_proxy = response.status == StatusCode::PROXY_AUTHENTICATION_REQUIRED;
144
145 if request.mode != RequestMode::Navigate {
147 return None;
148 }
149
150 let (sender, receiver) = tokio::sync::oneshot::channel();
151 self.embedder_proxy
152 .send(NetToEmbedderMsg::RequestAuthentication(
153 webview_id,
154 request.url(),
155 for_proxy,
156 sender,
157 ));
158 receiver.await.ok()?
159 }
160}
161
162pub(crate) fn set_default_accept(request: &mut Request) {
164 if request.headers.contains_key(header::ACCEPT) {
166 return;
167 }
168
169 let value = if request.initiator == Initiator::Prefetch {
171 DOCUMENT_ACCEPT_HEADER_VALUE
172 } else {
173 match request.destination {
176 Destination::Document | Destination::Frame | Destination::IFrame => {
177 DOCUMENT_ACCEPT_HEADER_VALUE
178 },
179 Destination::Image => {
180 HeaderValue::from_static("image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5")
181 },
182 Destination::Json => HeaderValue::from_static("application/json,*/*;q=0.5"),
183 Destination::Style => HeaderValue::from_static("text/css,*/*;q=0.1"),
184 _ => HeaderValue::from_static("*/*"),
186 }
187 };
188
189 request.headers.insert(header::ACCEPT, value);
191}
192
193fn set_default_accept_encoding(headers: &mut HeaderMap) {
194 if headers.contains_key(header::ACCEPT_ENCODING) {
195 return;
196 }
197
198 headers.insert(
200 header::ACCEPT_ENCODING,
201 HeaderValue::from_static("gzip, deflate, br, zstd"),
202 );
203}
204
205fn no_referrer_when_downgrade(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
207 if referrer_url.is_potentially_trustworthy() && !current_url.is_potentially_trustworthy() {
209 return None;
210 }
211 strip_url_for_use_as_referrer(referrer_url, false)
213}
214
215fn strict_origin(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
217 if referrer_url.is_potentially_trustworthy() && !current_url.is_potentially_trustworthy() {
219 return None;
220 }
221 strip_url_for_use_as_referrer(referrer_url, true)
223}
224
225fn strict_origin_when_cross_origin(
227 referrer_url: ServoUrl,
228 current_url: ServoUrl,
229) -> Option<ServoUrl> {
230 if referrer_url.origin() == current_url.origin() {
232 return strip_url_for_use_as_referrer(referrer_url, false);
233 }
234 if referrer_url.is_potentially_trustworthy() && !current_url.is_potentially_trustworthy() {
236 return None;
237 }
238 strip_url_for_use_as_referrer(referrer_url, true)
240}
241
242fn is_schemelessy_same_site(site_a: &ImmutableOrigin, site_b: &ImmutableOrigin) -> bool {
244 if !site_a.is_tuple() && !site_b.is_tuple() && site_a == site_b {
246 true
247 } else if site_a.is_tuple() && site_b.is_tuple() {
248 let host_a = site_a.host().map(|h| h.to_string()).unwrap_or_default();
250 let host_b = site_b.host().map(|h| h.to_string()).unwrap_or_default();
251
252 let host_a_reg = reg_suffix(&host_a);
253 let host_b_reg = reg_suffix(&host_b);
254
255 (site_a.host() == site_b.host() && host_a_reg.is_empty()) ||
257 (host_a_reg == host_b_reg && !host_a_reg.is_empty())
258 } else {
259 false
261 }
262}
263
264fn strip_url_for_use_as_referrer(mut url: ServoUrl, origin_only: bool) -> Option<ServoUrl> {
266 const MAX_REFERRER_URL_LENGTH: usize = 4096;
267 if url.is_local_scheme() {
269 return None;
270 }
271 {
273 let url = url.as_mut_url();
274 let _ = url.set_username("");
275 let _ = url.set_password(None);
276 url.set_fragment(None);
277 if origin_only || url.as_str().len() > MAX_REFERRER_URL_LENGTH {
281 url.set_path("");
282 url.set_query(None);
283 }
284 }
285 Some(url)
287}
288
289fn same_origin(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
291 if referrer_url.origin() == current_url.origin() {
293 return strip_url_for_use_as_referrer(referrer_url, false);
294 }
295 None
297}
298
299fn origin_when_cross_origin(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
301 if referrer_url.origin() == current_url.origin() {
303 return strip_url_for_use_as_referrer(referrer_url, false);
304 }
305 strip_url_for_use_as_referrer(referrer_url, true)
307}
308
309pub fn determine_requests_referrer(
311 referrer_policy: ReferrerPolicy,
312 referrer_source: ServoUrl,
313 current_url: ServoUrl,
314) -> Option<ServoUrl> {
315 match referrer_policy {
316 ReferrerPolicy::EmptyString | ReferrerPolicy::NoReferrer => None,
317 ReferrerPolicy::Origin => strip_url_for_use_as_referrer(referrer_source, true),
318 ReferrerPolicy::UnsafeUrl => strip_url_for_use_as_referrer(referrer_source, false),
319 ReferrerPolicy::StrictOrigin => strict_origin(referrer_source, current_url),
320 ReferrerPolicy::StrictOriginWhenCrossOrigin => {
321 strict_origin_when_cross_origin(referrer_source, current_url)
322 },
323 ReferrerPolicy::SameOrigin => same_origin(referrer_source, current_url),
324 ReferrerPolicy::OriginWhenCrossOrigin => {
325 origin_when_cross_origin(referrer_source, current_url)
326 },
327 ReferrerPolicy::NoReferrerWhenDowngrade => {
328 no_referrer_when_downgrade(referrer_source, current_url)
329 },
330 }
331}
332
333fn set_request_cookies(
334 url: &ServoUrl,
335 headers: &mut HeaderMap,
336 cookie_jar: &RwLock<CookieStorage>,
337) {
338 let mut cookie_jar = cookie_jar.write();
339 cookie_jar.remove_expired_cookies_for_url(url);
340 if let Some(cookie_list) = cookie_jar.cookies_for_url(url, CookieSource::HTTP) {
341 headers.insert(
342 header::COOKIE,
343 HeaderValue::from_bytes(cookie_list.as_bytes()).unwrap(),
344 );
345 }
346}
347
348fn set_cookie_for_url(cookie_jar: &RwLock<CookieStorage>, request: &ServoUrl, cookie_val: &str) {
349 let mut cookie_jar = cookie_jar.write();
350 let source = CookieSource::HTTP;
351
352 if let Some(cookie) = ServoCookie::from_cookie_string(cookie_val, request, source) {
353 cookie_jar.push(cookie, request, source);
354 }
355}
356
357fn set_cookies_from_headers(
358 url: &ServoUrl,
359 headers: &HeaderMap,
360 cookie_jar: &RwLock<CookieStorage>,
361) {
362 for cookie in headers.get_all(header::SET_COOKIE) {
363 let cookie_bytes = cookie.as_bytes();
364 if !ServoCookie::is_valid_name_or_value(cookie_bytes) {
365 continue;
366 }
367 if let Ok(cookie_str) = std::str::from_utf8(cookie_bytes) {
368 set_cookie_for_url(cookie_jar, url, cookie_str);
369 }
370 }
371}
372
373fn build_tls_security_info(handshake: &TlsHandshakeInfo, hsts_enabled: bool) -> TlsSecurityInfo {
374 let state = if handshake.protocol_version.is_none() || handshake.cipher_suite.is_none() {
382 TlsSecurityState::Insecure
384 } else {
385 TlsSecurityState::Secure
387 };
388
389 TlsSecurityInfo {
390 state,
391 weakness_reasons: Vec::new(), protocol_version: handshake.protocol_version.clone(),
393 cipher_suite: handshake.cipher_suite.clone(),
394 kea_group_name: handshake.kea_group_name.clone(),
395 signature_scheme_name: handshake.signature_scheme_name.clone(),
396 alpn_protocol: handshake.alpn_protocol.clone(),
397 certificate_chain_der: handshake.certificate_chain_der.clone(),
398 certificate_transparency: None,
399 hsts: hsts_enabled,
400 hpkp: false,
401 used_ech: handshake.used_ech,
402 used_delegated_credentials: false,
403 used_ocsp: false,
404 used_private_dns: false,
405 }
406}
407
408fn auth_from_cache(
409 auth_cache: &RwLock<AuthCache>,
410 origin: &ImmutableOrigin,
411) -> Option<Authorization<Basic>> {
412 if let Some(auth_entry) = auth_cache.read().entries.get(&origin.ascii_serialization()) {
413 let user_name = &auth_entry.user_name;
414 let password = &auth_entry.password;
415 Some(Authorization::basic(user_name, password))
416 } else {
417 None
418 }
419}
420
421enum BodyChunk {
424 Chunk(GenericSharedMemory),
426 Done,
428}
429
430enum BodyStream {
432 Chunked(TokioReceiver<Result<Frame<Bytes>, hyper::Error>>),
435 Buffered(UnboundedReceiver<BodyChunk>),
438}
439
440enum BodySink {
443 Chunked(TokioSender<Result<Frame<Bytes>, hyper::Error>>),
445 Buffered(UnboundedSender<BodyChunk>),
449}
450
451impl BodySink {
452 fn transmit_bytes(&self, bytes: GenericSharedMemory) {
453 match self {
454 BodySink::Chunked(sender) => {
455 let sender = sender.clone();
456 spawn_task(async move {
457 let _ = sender
458 .send(Ok(Frame::data(Bytes::copy_from_slice(&bytes))))
459 .await;
460 });
461 },
462 BodySink::Buffered(sender) => {
463 let _ = sender.send(BodyChunk::Chunk(bytes));
464 },
465 }
466 }
467
468 fn close(&self) {
469 match self {
470 BodySink::Chunked(_) => { },
471 BodySink::Buffered(sender) => {
472 let _ = sender.send(BodyChunk::Done);
473 },
474 }
475 }
476}
477
478fn request_body_stream_closed_error(action: &str) -> NetworkError {
479 NetworkError::Crash(format!(
480 "Request body stream has already been closed while trying to {action}."
481 ))
482}
483
484fn log_request_body_stream_closed(action: &str, error: Option<&IpcError>) {
485 match error {
486 Some(error) => {
487 error!("Request body stream has already been closed while trying to {action}: {error}")
488 },
489 None => error!("Request body stream has already been closed while trying to {action}."),
490 }
491}
492
493fn log_fetch_terminated_send_failure(terminated_with_error: bool, context: &str) {
494 warn!(
495 "Failed to notify request-body stream termination state ({terminated_with_error}) while {context} because the receiver was already dropped."
496 );
497}
498
499const FRAGMENT: &AsciiSet = &CONTROLS.add(b'|').add(b'{').add(b'}');
500
501#[allow(clippy::too_many_arguments)]
502#[servo_tracing::instrument(skip_all, fields(url=url.as_str()))]
503async fn obtain_response(
505 client: &ServoClient,
506 url: &ServoUrl,
507 method: &Method,
508 request_headers: &mut HeaderMap,
509 body_sender: Option<StdArc<Mutex<Option<IpcSender<BodyChunkRequest>>>>>,
510 source_is_null: bool,
511 pipeline_id: &Option<PipelineId>,
512 request_id: Option<&str>,
513 destination: Destination,
514 is_xhr: bool,
515 context: &FetchContext,
516 fetch_terminated: UnboundedSender<bool>,
517 browsing_context_id: Option<BrowsingContextId>,
518) -> Result<(HyperResponse<Decoder>, Option<ChromeToDevtoolsControlMsg>), NetworkError> {
519 let mut headers = request_headers.clone();
520
521 let devtools_bytes = StdArc::new(Mutex::new(vec![]));
522
523 let encoded_url = utf8_percent_encode(url.as_str(), FRAGMENT).to_string();
525
526 let request = if let Some(chunk_requester) = body_sender {
527 let (sink, stream) = if source_is_null {
528 headers.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
531
532 let (sender, receiver) = channel(1);
533 (BodySink::Chunked(sender), BodyStream::Chunked(receiver))
534 } else {
535 let (sender, receiver) = unbounded_channel();
542 (BodySink::Buffered(sender), BodyStream::Buffered(receiver))
543 };
544
545 obtain_response_setup_router_callback(
546 devtools_bytes.clone(),
547 chunk_requester,
548 sink,
549 fetch_terminated,
550 )?;
551
552 let body = match stream {
553 BodyStream::Chunked(receiver) => {
554 let stream = ReceiverStream::new(receiver);
555 BoxBody::new(http_body_util::StreamBody::new(stream))
556 },
557 BodyStream::Buffered(mut receiver) => {
558 let mut body = vec![];
560 loop {
561 match receiver.recv().await {
562 Some(BodyChunk::Chunk(bytes)) => {
563 body.extend_from_slice(&bytes);
564 },
565 Some(BodyChunk::Done) => break,
566 None => warn!("Failed to read all chunks from request body."),
567 }
568 }
569 Full::new(body.into()).map_err(|_| unreachable!()).boxed()
570 },
571 };
572 HyperRequest::builder()
573 .method(method)
574 .uri(encoded_url)
575 .body(body)
576 } else {
577 HyperRequest::builder()
578 .method(method)
579 .uri(encoded_url)
580 .body(
581 http_body_util::Empty::new()
582 .map_err(|_| unreachable!())
583 .boxed(),
584 )
585 };
586
587 let connect_start = CrossProcessInstant::now();
590 context.timing.set_attributes(&[
591 ResourceAttribute::DomainLookupStart,
592 ResourceAttribute::ConnectStart(connect_start),
593 ]);
594
595 if url.scheme() == "https" {
599 context
600 .timing
601 .set_attribute(ResourceAttribute::SecureConnectionStart);
602 }
603
604 let mut request = match request {
605 Ok(request) => request,
606 Err(error) => return Err(NetworkError::HttpError(error.to_string())),
607 };
608 *request.headers_mut() = headers.clone();
609
610 let connect_end = CrossProcessInstant::now();
611 context
612 .timing
613 .set_attribute(ResourceAttribute::ConnectEnd(connect_end));
614
615 let request_id = request_id.map(|v| v.to_owned());
616 let pipeline_id = *pipeline_id;
617 let closure_url = url.clone();
618 let method = method.clone();
619 let send_start = CrossProcessInstant::now();
620
621 let host = request.uri().host().unwrap_or("").to_owned();
622 let override_manager = context.state.override_manager.clone();
623 let headers = headers.clone();
624 let is_secure_scheme = url.is_secure_scheme();
625
626 context
630 .timing
631 .set_attribute(ResourceAttribute::RequestStart);
632
633 let client_future = client
634 .request(request)
635 .and_then(move |res| {
636 let send_end = CrossProcessInstant::now();
637
638 let msg = if let Some(request_id) = request_id {
641 if let Some(pipeline_id) = pipeline_id {
642 if let Some(browsing_context_id) = browsing_context_id {
643 Some(prepare_devtools_request(
644 request_id,
645 closure_url,
646 method.clone(),
647 headers,
648 Some(devtools_bytes.lock().clone()),
649 pipeline_id,
650 (connect_end - connect_start).unsigned_abs(),
651 (send_end - send_start).unsigned_abs(),
652 destination,
653 is_xhr,
654 browsing_context_id,
655 ))
656 } else {
657 debug!("Not notifying devtools (no browsing_context_id)");
658 None
659 }
660 } else {
665 debug!("Not notifying devtools (no pipeline_id)");
666 None
667 }
668 } else {
669 debug!("Not notifying devtools (no request_id)");
670 None
671 };
672
673 future::ready(Ok((
674 Decoder::detect(res.map(|r| r.boxed()), is_secure_scheme),
675 msg,
676 )))
677 })
678 .map_err(move |error| {
679 warn!("network error: {error:?}");
680 NetworkError::from_hyper_error(
681 &error,
682 override_manager.remove_certificate_failing_verification(host.as_str()),
683 )
684 });
685
686 #[cfg(feature = "tracing")]
687 {
688 client_future.instrument(trace_span!("HyperRequest")).await
689 }
690
691 #[cfg(not(feature = "tracing"))]
692 {
693 client_future.await
694 }
695}
696
697fn obtain_response_setup_router_callback(
699 devtools_bytes: StdArc<Mutex<Vec<u8>>>,
700 chunk_requester: StdArc<Mutex<Option<IpcSender<BodyChunkRequest>>>>,
701 sink: BodySink,
702 fetch_terminated: UnboundedSender<bool>,
703) -> Result<(), NetworkError> {
704 let (body_chan, body_port) = ipc::channel().unwrap();
705
706 {
707 let mut lock = chunk_requester.lock();
708 if let Some(chunk_requester) = lock.as_mut() {
709 if let Err(error) = chunk_requester.send(BodyChunkRequest::Connect(body_chan)) {
710 log_request_body_stream_closed("connect to the request body stream", Some(&error));
711 return Err(request_body_stream_closed_error(
712 "connect to the request body stream",
713 ));
714 }
715
716 if let Err(error) = chunk_requester.send(BodyChunkRequest::Chunk) {
719 log_request_body_stream_closed(
720 "request the first request body chunk",
721 Some(&error),
722 );
723 return Err(request_body_stream_closed_error(
724 "request the first request body chunk",
725 ));
726 }
727 } else {
728 log_request_body_stream_closed("connect to the request body stream", None);
729 return Err(request_body_stream_closed_error(
730 "connect to the request body stream",
731 ));
732 }
733 }
734
735 ROUTER.add_typed_route(
736 body_port,
737 Box::new(move |message| {
738 info!("Received message");
739 let bytes = match message.unwrap() {
740 BodyChunkResponse::Chunk(bytes) => bytes,
741 BodyChunkResponse::Done => {
742 if fetch_terminated.send(false).is_err() {
744 log_fetch_terminated_send_failure(
745 false,
746 "handling request body completion",
747 );
748 }
749 sink.close();
750
751 return;
752 },
753 BodyChunkResponse::Error => {
754 if fetch_terminated.send(true).is_err() {
758 log_fetch_terminated_send_failure(
759 true,
760 "handling request body stream error",
761 );
762 }
763 sink.close();
764
765 return;
766 },
767 };
768
769 devtools_bytes.lock().extend_from_slice(&bytes);
770
771 sink.transmit_bytes(bytes);
774
775 let mut chunk_requester = chunk_requester.lock();
778 if let Some(chunk_requester) = chunk_requester.as_mut() {
779 if let Err(error) = chunk_requester.send(BodyChunkRequest::Chunk) {
780 log_request_body_stream_closed(
781 "request the next request body chunk",
782 Some(&error),
783 );
784 if fetch_terminated.send(true).is_err() {
785 log_fetch_terminated_send_failure(
786 true,
787 "handling failure to request the next request body chunk",
788 );
789 }
790 sink.close();
791 }
792 } else {
793 log_request_body_stream_closed("request the next request body chunk", None);
794 if fetch_terminated.send(true).is_err() {
795 log_fetch_terminated_send_failure(
796 true,
797 "handling a closed request body stream while requesting the next chunk",
798 );
799 }
800 sink.close();
801 }
802 }),
803 );
804
805 Ok(())
806}
807
808#[async_recursion]
810#[allow(clippy::too_many_arguments)]
811pub(crate) async fn http_fetch(
812 fetch_params: &mut FetchParams,
813 cache: &mut CorsCache,
814 cors_flag: bool,
815 cors_preflight_flag: bool,
816 authentication_fetch_flag: bool,
817 target: Target<'async_recursion>,
818 done_chan: &mut DoneChannel,
819 context: &FetchContext,
820) -> Response {
821 *done_chan = None;
823 let request = &mut fetch_params.request;
825
826 let mut response: Option<Response> = None;
828
829 if request.service_workers_mode == ServiceWorkersMode::All {
831 if let Some(ref res) = response {
836 if (res.response_type == ResponseType::Opaque && request.mode != RequestMode::NoCors) ||
844 (res.response_type == ResponseType::OpaqueRedirect &&
845 request.redirect_mode != RedirectMode::Manual) ||
846 (res.url_list.len() > 1 && request.redirect_mode != RedirectMode::Follow) ||
847 res.is_network_error()
848 {
849 return Response::network_error(NetworkError::ConnectionFailure);
850 }
851
852 }
855 }
856
857 if response.is_none() {
859 if cors_preflight_flag {
861 let method_cache_match = cache.match_method(request, request.method.clone());
862
863 let method_mismatch = !method_cache_match &&
867 (!is_cors_safelisted_method(&request.method) || request.use_cors_preflight);
868
869 let header_mismatch = request.headers.iter().any(|(name, value)| {
872 !cache.match_header(request, name) &&
873 !is_cors_safelisted_request_header(&name, &value)
874 });
875
876 if method_mismatch || header_mismatch {
878 let preflight_response = cors_preflight_fetch(request, cache, context).await;
881 if let Some(error) = preflight_response.get_network_error() {
883 return Response::network_error(error.clone());
884 }
885 }
886 }
887
888 if request.redirect_mode == RedirectMode::Follow {
891 request.service_workers_mode = ServiceWorkersMode::None;
892 }
893
894 let mut fetch_result = http_network_or_cache_fetch(
897 fetch_params,
898 authentication_fetch_flag,
899 cors_flag,
900 done_chan,
901 context,
902 )
903 .await;
904
905 if cors_flag && cors_check(&fetch_params.request, &fetch_result).is_err() {
908 return Response::network_error(NetworkError::CorsGeneral);
909 }
910
911 if let Err(()) = tao_check(&fetch_params.request, &fetch_result) {
914 context.timing.inner().mark_timing_check_failed();
915 }
916 fetch_result.return_internal = false;
917 response = Some(fetch_result);
918 }
919
920 let request = &mut fetch_params.request;
921
922 let mut response = response.unwrap();
924
925 if (request.response_tainting == ResponseTainting::Opaque ||
929 response.response_type == ResponseType::Opaque) &&
930 request.client.as_ref().is_some_and(|client| {
931 cross_origin_resource_policy_check(
932 &request.origin,
933 client,
934 &response,
935 ForNavigation::No,
936 ) == CrossOriginResourcePolicy::Blocked
937 })
938 {
939 return Response::network_error(NetworkError::CrossOriginResponse);
940 }
941
942 if response
944 .actual_response()
945 .status
946 .try_code()
947 .is_some_and(is_redirect_status)
948 {
949 if response.actual_response().status != StatusCode::SEE_OTHER {
956 }
958
959 response = match request.redirect_mode {
961 RedirectMode::Error => Response::network_error(NetworkError::RedirectError),
963 RedirectMode::Manual => {
964 if request.mode == RequestMode::Navigate {
967 let location_url =
971 location_url_for_response(&response, request.current_url().fragment());
972 response.actual_response_mut().location_url = location_url;
973 response
974 } else {
975 response.to_filtered(ResponseType::OpaqueRedirect)
978 }
979 },
980 RedirectMode::Follow => {
981 response.return_internal = true;
985
986 http_redirect_fetch(
989 fetch_params,
990 cache,
991 response,
992 cors_flag,
993 target,
994 done_chan,
995 context,
996 )
997 .await
998 },
999 };
1000 }
1001
1002 response.return_internal = true;
1004 context
1005 .timing
1006 .set_attribute(ResourceAttribute::RedirectCount(
1007 fetch_params.request.redirect_count as u16,
1008 ));
1009
1010 response.resource_timing = context.timing.clone();
1011
1012 response
1014}
1015
1016fn tao_check(request: &Request, response: &Response) -> Result<(), ()> {
1018 let Origin::Origin(ref request_origin) = request.origin else {
1020 unreachable!("origin cannot be \"client\" at this point");
1021 };
1022
1023 let values: Vec<&str> = response
1028 .headers
1029 .get_all("Timing-Allow-Origin")
1030 .iter()
1031 .map(|header_value| header_value.to_str().unwrap_or(""))
1032 .collect();
1033
1034 if values.contains(&"*") {
1036 return Ok(());
1037 }
1038
1039 if values
1042 .iter()
1043 .any(|header_str| *header_str == request_origin.ascii_serialization())
1044 {
1045 return Ok(());
1046 }
1047
1048 if request.mode == RequestMode::Navigate && request.current_url().origin() != *request_origin {
1051 return Err(());
1052 }
1053
1054 if request.response_tainting == ResponseTainting::Basic {
1056 return Ok(());
1057 }
1058
1059 Err(())
1061}
1062
1063struct RedirectEndTimer(Option<ResourceFetchTimingContainer>);
1065
1066impl RedirectEndTimer {
1067 fn neuter(&mut self) {
1068 self.0 = None;
1069 }
1070}
1071
1072impl Drop for RedirectEndTimer {
1073 fn drop(&mut self) {
1074 let RedirectEndTimer(resource_fetch_timing_opt) = self;
1075
1076 resource_fetch_timing_opt.as_ref().map_or((), |t| {
1077 t.set_attribute(ResourceAttribute::RedirectEnd(RedirectEndValue::Zero));
1078 })
1079 }
1080}
1081
1082static REQUEST_BODY_HEADER_NAMES: &[HeaderName] = &[
1084 CONTENT_ENCODING,
1085 CONTENT_LANGUAGE,
1086 CONTENT_LOCATION,
1087 CONTENT_TYPE,
1088];
1089
1090fn location_url_for_response(
1092 response: &Response,
1093 request_fragment: Option<&str>,
1094) -> Option<Result<ServoUrl, String>> {
1095 assert!(
1097 response
1098 .actual_response()
1099 .status
1100 .try_code()
1101 .is_some_and(is_redirect_status)
1102 );
1103 let mut location = response
1105 .actual_response()
1106 .headers
1107 .get(header::LOCATION)
1108 .and_then(|header_value| {
1109 HeaderValue::to_str(header_value)
1110 .map(|location_string| {
1111 ServoUrl::parse_with_base(response.actual_response().url(), location_string)
1113 .map_err(|error| error.to_string())
1114 })
1115 .ok()
1116 });
1117
1118 if let Some(Ok(ref mut location)) = location &&
1120 location.fragment().is_none()
1121 {
1122 location.set_fragment(request_fragment);
1123 }
1124 location
1126}
1127
1128#[async_recursion]
1130pub async fn http_redirect_fetch(
1131 fetch_params: &mut FetchParams,
1132 cache: &mut CorsCache,
1133 mut response: Response,
1134 cors_flag: bool,
1135 target: Target<'async_recursion>,
1136 done_chan: &mut DoneChannel,
1137 context: &FetchContext,
1138) -> Response {
1139 let mut redirect_end_timer = RedirectEndTimer(Some(context.timing.clone()));
1140
1141 let request = &mut fetch_params.request;
1143
1144 assert!(response.return_internal);
1146
1147 let location_url = location_url_for_response(&response, request.current_url().fragment());
1149 response.actual_response_mut().location_url = location_url.clone();
1150
1151 let location_url = match location_url {
1152 None => return response,
1154 Some(Err(err)) => {
1156 return Response::network_error(NetworkError::ResourceLoadError(
1157 "Location URL parse failure: ".to_owned() + &err,
1158 ));
1159 },
1160 Some(Ok(url)) if !matches!(url.scheme(), "http" | "https") => {
1162 return Response::network_error(NetworkError::UnsupportedScheme);
1163 },
1164 Some(Ok(url)) => url,
1165 };
1166
1167 context.timing.set_attributes(&[
1172 ResourceAttribute::RedirectStart(RedirectStartValue::FetchStart),
1173 ResourceAttribute::FetchStart,
1174 ResourceAttribute::StartTime(ResourceTimeValue::FetchStart),
1175 ResourceAttribute::StartTime(ResourceTimeValue::RedirectStart),
1176 ]);
1177
1178 if request.redirect_count >= 20 {
1180 return Response::network_error(NetworkError::TooManyRedirects);
1181 }
1182
1183 request.redirect_count += 1;
1185
1186 let same_origin = match request.origin {
1189 Origin::Origin(ref origin) => *origin == location_url.origin(),
1190 Origin::Client => panic!(
1191 "Request origin should not be client for {}",
1192 request.current_url()
1193 ),
1194 };
1195
1196 let has_credentials = has_credentials(&location_url);
1197
1198 if request.mode == RequestMode::CorsMode && !same_origin && has_credentials {
1199 return Response::network_error(NetworkError::CorsCredentials);
1200 }
1201
1202 if cors_flag && location_url.origin() != request.current_url().origin() {
1203 request.origin = Origin::Origin(ImmutableOrigin::new_opaque());
1204 }
1205
1206 if cors_flag && has_credentials {
1208 return Response::network_error(NetworkError::CorsCredentials);
1209 }
1210
1211 if response.actual_response().status != StatusCode::SEE_OTHER &&
1214 request.body.as_ref().is_some_and(|b| b.source_is_null())
1215 {
1216 return Response::network_error(NetworkError::ConnectionFailure);
1217 }
1218
1219 if response
1221 .actual_response()
1222 .status
1223 .try_code()
1224 .is_some_and(|code| {
1225 ((code == StatusCode::MOVED_PERMANENTLY || code == StatusCode::FOUND) &&
1227 request.method == Method::POST) ||
1228 (code == StatusCode::SEE_OTHER &&
1230 request.method != Method::HEAD &&
1231 request.method != Method::GET)
1232 })
1233 {
1234 request.method = Method::GET;
1236 request.body = None;
1237 for name in REQUEST_BODY_HEADER_NAMES {
1239 request.headers.remove(name);
1240 }
1241 }
1242
1243 if location_url.origin() != request.current_url().origin() {
1247 request.headers.remove(AUTHORIZATION);
1250 }
1251
1252 if let Some(body) = request.body.as_mut() {
1255 body.extract_source();
1256 }
1257
1258 request
1262 .url_list
1263 .push(UrlWithBlobClaim::from_url_without_having_claimed_blob(
1264 location_url,
1265 ));
1266
1267 set_requests_referrer_policy_on_redirect(request, response.actual_response());
1269
1270 let recursive_flag = request.redirect_mode != RedirectMode::Manual;
1273
1274 let fetch_response = main_fetch(
1276 fetch_params,
1277 cache,
1278 recursive_flag,
1279 target,
1280 done_chan,
1281 context,
1282 )
1283 .await;
1284
1285 context.timing.set_attribute(ResourceAttribute::RedirectEnd(
1287 RedirectEndValue::ResponseEnd,
1288 ));
1289 redirect_end_timer.neuter();
1290
1291 fetch_response
1292}
1293
1294#[async_recursion]
1296#[servo_tracing::instrument(skip_all,fields(url=fetch_params.request.url().as_str()))]
1297async fn http_network_or_cache_fetch(
1298 fetch_params: &mut FetchParams,
1299 authentication_fetch_flag: bool,
1300 cors_flag: bool,
1301 done_chan: &mut DoneChannel,
1302 context: &FetchContext,
1303) -> Response {
1304 let http_fetch_params: &mut FetchParams;
1306 let mut fetch_params_copy: FetchParams;
1307
1308 let mut response: Option<Response> = None;
1312
1313 let mut revalidating_flag = false;
1315
1316 let http_request = if fetch_params.request.traversable_for_user_prompts ==
1320 TraversableForUserPrompts::NoTraversable &&
1321 fetch_params.request.redirect_mode == RedirectMode::Error
1322 {
1323 http_fetch_params = fetch_params;
1324 &mut http_fetch_params.request
1325 }
1326 else {
1328 fetch_params_copy =
1331 std::mem::replace(fetch_params, FetchParams::new(fetch_params.request.clone()));
1332 http_fetch_params = &mut fetch_params_copy;
1333
1334 &mut http_fetch_params.request
1335 };
1336
1337 let include_credentials = match http_request.credentials_mode {
1339 CredentialsMode::Include => true,
1341 CredentialsMode::CredentialsSameOrigin
1343 if http_request.response_tainting == ResponseTainting::Basic =>
1344 {
1345 true
1346 },
1347 _ => false,
1348 };
1349
1350 let content_length = http_request
1357 .body
1358 .as_ref()
1359 .and_then(|body| body.len().map(|size| size as u64));
1360
1361 let mut content_length_header_value = None;
1363
1364 if http_request.body.is_none() && matches!(http_request.method, Method::POST | Method::PUT) {
1367 content_length_header_value = Some(0);
1368 }
1369
1370 if let Some(content_length) = content_length {
1374 content_length_header_value = Some(content_length);
1375 };
1376
1377 if let Some(content_length_header_value) = content_length_header_value {
1380 http_request
1381 .headers
1382 .typed_insert(ContentLength(content_length_header_value));
1383 }
1384
1385 if http_request.keep_alive &&
1387 let Some(content_length) = content_length
1388 {
1389 let in_flight_keep_alive_bytes: u64 = context
1394 .in_flight_keep_alive_records
1395 .lock()
1396 .get(
1397 &http_request
1398 .pipeline_id
1399 .expect("Must always set a pipeline ID for keep-alive requests"),
1400 )
1401 .map(|records| {
1402 records
1406 .iter()
1407 .map(|record| {
1408 if record.request_id == http_request.id {
1409 0
1412 } else {
1413 record.keep_alive_body_length
1414 }
1415 })
1416 .sum()
1417 })
1418 .unwrap_or_default();
1419 if content_length + in_flight_keep_alive_bytes > 64 * 1024 {
1421 return Response::network_error(NetworkError::TooManyInFlightKeepAliveRequests);
1422 }
1423 }
1424
1425 match http_request.referrer {
1427 Referrer::ReferrerUrl(ref http_request_referrer) |
1428 Referrer::Client(ref http_request_referrer) => {
1429 if let Ok(referer) = http_request_referrer.as_str().parse::<Referer>() {
1432 http_request.headers.typed_insert(referer);
1434 } else {
1435 error!("Failed to parse {} as referrer", http_request_referrer);
1439 }
1440 },
1441 _ => {},
1442 };
1443
1444 append_a_request_origin_header(http_request);
1446
1447 append_the_fetch_metadata_headers(http_request);
1449
1450 if http_request.initiator == Initiator::Prefetch &&
1453 let Ok(value) = HeaderValue::from_str("prefetch")
1454 {
1455 http_request.headers.insert("Sec-Purpose", value);
1456 }
1457
1458 if !http_request.headers.contains_key(header::USER_AGENT) {
1461 http_request
1462 .headers
1463 .typed_insert::<UserAgent>(context.user_agent.parse().unwrap());
1464 }
1465
1466 append_cache_data_to_headers(http_request);
1468
1469 if http_request.headers.contains_key(header::RANGE) &&
1472 let Ok(value) = HeaderValue::from_str("identity")
1473 {
1474 http_request.headers.insert("Accept-Encoding", value);
1475 }
1476
1477 http_request.headers.remove(header::HOST);
1481 set_default_accept_encoding(&mut http_request.headers);
1483
1484 let current_url = http_request.current_url();
1485
1486 if include_credentials {
1489 set_request_cookies(
1493 ¤t_url,
1494 &mut http_request.headers,
1495 &context.state.cookie_jar,
1496 );
1497 if !http_request.headers.contains_key(header::AUTHORIZATION) {
1499 let mut authorization_value = None;
1501
1502 if let Some(basic) = auth_from_cache(&context.state.auth_cache, ¤t_url.origin()) &&
1504 (!http_request.use_url_credentials || !has_credentials(¤t_url))
1505 {
1506 authorization_value = Some(basic);
1507 }
1508
1509 if authentication_fetch_flag &&
1511 authorization_value.is_none() &&
1512 has_credentials(¤t_url)
1513 {
1514 authorization_value = Some(Authorization::basic(
1515 current_url.username(),
1516 current_url.password().unwrap_or(""),
1517 ));
1518 }
1519
1520 if let Some(basic) = authorization_value {
1522 http_request.headers.typed_insert(basic);
1523 }
1524 }
1525 }
1526
1527 let should_wait = {
1529 let mut cache_guard = block_for_cache_ready(
1531 context,
1532 http_request,
1533 done_chan,
1534 &mut revalidating_flag,
1535 &mut response,
1536 )
1537 .await;
1538
1539 if response.is_none() {
1543 if http_request.cache_mode == CacheMode::OnlyIfCached {
1545 return Response::network_error(NetworkError::CacheError);
1547 }
1548
1549 drop(cache_guard);
1552 let forward_response =
1553 http_network_fetch(http_fetch_params, include_credentials, done_chan, context)
1554 .await;
1555
1556 let http_request = &mut http_fetch_params.request;
1557 let request_key = CacheKey::new(http_request);
1558 cache_guard = context
1559 .state
1560 .http_cache
1561 .get_or_guard(request_key.clone())
1562 .await;
1563 if forward_response.status.in_range(200..=399) && !http_request.method.is_safe() {
1567 if let Some(guard) = cache_guard.try_as_mut() {
1568 invalidate_cached_resources(guard);
1569 }
1570 context
1571 .state
1572 .http_cache
1573 .invalidate_related_urls(http_request, &forward_response, &request_key)
1574 .await;
1575 }
1576
1577 if revalidating_flag && forward_response.status == StatusCode::NOT_MODIFIED {
1579 *done_chan = None;
1582 if let Some(guard) = cache_guard.try_as_mut() {
1583 response = refresh(http_request, forward_response.clone(), done_chan, guard);
1584 }
1585
1586 if let Some(response) = &mut response {
1587 response.cache_state = CacheState::Validated;
1588 }
1589 }
1590
1591 if response.is_none() {
1593 let forward_response = response.insert(forward_response);
1595
1596 if http_request.cache_mode != CacheMode::NoStore {
1599 cache_guard.insert(http_request, forward_response);
1602 }
1603 }
1604 false
1605 } else {
1606 true
1607 }
1608 }; if should_wait {
1611 wait_for_inflight_requests(done_chan, &mut response).await;
1616 }
1617
1618 let http_request = &mut http_fetch_params.request;
1619 let mut response = response.unwrap();
1620
1621 response.url_list = http_request
1623 .url_list
1624 .iter()
1625 .map(|claimed_url| claimed_url.url())
1626 .collect();
1627
1628 if http_request.headers.contains_key(RANGE) {
1630 response.range_requested = true;
1631 }
1632
1633 response.request_includes_credentials = include_credentials;
1635
1636 if response.status.try_code() == Some(StatusCode::UNAUTHORIZED) &&
1642 !cors_flag &&
1643 include_credentials &&
1644 response.headers.contains_key(WWW_AUTHENTICATE)
1645 {
1646 let request = &mut fetch_params.request;
1649
1650 if request.body.is_some() {
1652 }
1654
1655 if !request.use_url_credentials || authentication_fetch_flag {
1657 let Some(credentials) = context
1658 .state
1659 .request_authentication(request, &response)
1660 .await
1661 else {
1662 return response;
1663 };
1664
1665 if let Err(err) = request
1666 .current_url_mut()
1667 .set_username(&credentials.username)
1668 {
1669 error!("error setting username for url: {:?}", err);
1670 return response;
1671 };
1672
1673 if let Err(err) = request
1674 .current_url_mut()
1675 .set_password(Some(&credentials.password))
1676 {
1677 error!("error setting password for url: {:?}", err);
1678 return response;
1679 };
1680 }
1681
1682 *done_chan = None;
1685
1686 response = http_network_or_cache_fetch(
1688 fetch_params,
1689 true, cors_flag,
1691 done_chan,
1692 context,
1693 )
1694 .await;
1695 }
1696
1697 if response.status == StatusCode::PROXY_AUTHENTICATION_REQUIRED {
1699 let request = &mut fetch_params.request;
1700 if request.traversable_for_user_prompts == TraversableForUserPrompts::NoTraversable {
1703 return Response::network_error(NetworkError::ResourceLoadError(
1704 "Can't find Window object".into(),
1705 ));
1706 }
1707
1708 let Some(credentials) = context
1716 .state
1717 .request_authentication(request, &response)
1718 .await
1719 else {
1720 return response;
1721 };
1722
1723 let entry = AuthCacheEntry {
1725 user_name: credentials.username,
1726 password: credentials.password,
1727 };
1728 {
1729 let mut auth_cache = context.state.auth_cache.write();
1730 let key = request.current_url().origin().ascii_serialization();
1731 auth_cache.entries.insert(key, entry);
1732 }
1733
1734 *done_chan = None;
1737
1738 response = http_network_or_cache_fetch(
1740 fetch_params,
1741 false, cors_flag,
1743 done_chan,
1744 context,
1745 )
1746 .await;
1747 }
1748
1749 if authentication_fetch_flag {
1757 }
1759
1760 response
1762}
1763
1764#[servo_tracing::instrument(skip_all)]
1772async fn block_for_cache_ready<'a>(
1773 context: &'a FetchContext,
1774 http_request: &mut Request,
1775 done_chan: &mut DoneChannel,
1776 revalidating_flag: &mut bool,
1777 response: &mut Option<Response>,
1778) -> CachedResourcesOrGuard<'a> {
1779 let entry_key = CacheKey::new(http_request);
1780 let guard_result = context.state.http_cache.get_or_guard(entry_key).await;
1781
1782 match guard_result {
1783 CachedResourcesOrGuard::Guard(_) => {
1784 *done_chan = None;
1785 },
1786 CachedResourcesOrGuard::Value(ref cached_resources) => {
1787 let stored_response = construct_response(http_request, done_chan, cached_resources);
1793 if let Some(response_from_cache) = stored_response {
1795 let response_headers = response_from_cache.response.headers.clone();
1796 let (cached_response, needs_revalidation) =
1798 match (http_request.cache_mode, &http_request.mode) {
1799 (CacheMode::ForceCache, _) => (Some(response_from_cache.response), false),
1800 (CacheMode::OnlyIfCached, &RequestMode::SameOrigin) => {
1801 (Some(response_from_cache.response), false)
1802 },
1803 (CacheMode::OnlyIfCached, _) |
1804 (CacheMode::NoStore, _) |
1805 (CacheMode::Reload, _) => (None, false),
1806 (_, _) => (
1807 Some(response_from_cache.response),
1808 response_from_cache.needs_validation,
1809 ),
1810 };
1811
1812 if needs_revalidation {
1813 *revalidating_flag = true;
1814 if let Some(http_date) = response_headers.typed_get::<LastModified>() {
1816 let http_date: SystemTime = http_date.into();
1817 http_request
1818 .headers
1819 .typed_insert(IfModifiedSince::from(http_date));
1820 }
1821 if let Some(entity_tag) = response_headers.get(header::ETAG) {
1822 http_request
1823 .headers
1824 .insert(header::IF_NONE_MATCH, entity_tag.clone());
1825 }
1826 } else {
1827 *response = cached_response;
1829 if let Some(response) = response {
1830 response.cache_state = CacheState::Local;
1831 }
1832 }
1833 if response.is_none() {
1834 *done_chan = None;
1837 }
1838 }
1839 },
1840 }
1841 guard_result
1842}
1843
1844async fn wait_for_inflight_requests(done_chan: &mut DoneChannel, response: &mut Option<Response>) {
1847 if let Some(ref mut ch) = *done_chan {
1848 assert!(response.is_some());
1852
1853 loop {
1854 match ch.1.recv().await {
1855 Some(Data::Payload(_)) => {},
1856 Some(Data::Done) => break, Some(Data::Cancelled) => {
1858 break;
1860 },
1861 _ => panic!("HTTP cache should always send Done or Cancelled"),
1862 }
1863 }
1864 }
1865 *done_chan = None;
1867}
1868
1869#[derive(PartialEq)]
1873enum CrossOriginResourcePolicy {
1874 Allowed,
1875 Blocked,
1876}
1877
1878enum ForNavigation {
1879 #[expect(dead_code)]
1880 Yes,
1881 No,
1882}
1883
1884fn cross_origin_resource_policy_check(
1886 origin: &Origin,
1887 request_client: &RequestClient,
1888 response: &Response,
1889 for_navigation: ForNavigation,
1890) -> CrossOriginResourcePolicy {
1891 let RequestPolicyContainer::PolicyContainer(ref policy_container) =
1897 request_client.policy_container
1898 else {
1899 return CrossOriginResourcePolicy::Blocked;
1900 };
1901
1902 let embedder_policy = &policy_container.embedder_policy;
1903
1904 if cross_origin_resource_policy_internal_check(
1907 origin,
1908 EmbedderPolicyValue::UnsafeNone,
1909 response,
1910 &for_navigation,
1911 ) == CrossOriginResourcePolicy::Blocked
1912 {
1913 return CrossOriginResourcePolicy::Blocked;
1914 }
1915
1916 if cross_origin_resource_policy_internal_check(
1924 origin,
1925 embedder_policy.value,
1926 response,
1927 &for_navigation,
1928 ) == CrossOriginResourcePolicy::Allowed
1929 {
1930 return CrossOriginResourcePolicy::Allowed;
1931 }
1932
1933 CrossOriginResourcePolicy::Blocked
1938}
1939
1940fn cross_origin_resource_policy_internal_check(
1942 origin: &Origin,
1943 embedder_policy_value: EmbedderPolicyValue,
1944 response: &Response,
1945 for_navigation: &ForNavigation,
1946) -> CrossOriginResourcePolicy {
1947 if let ForNavigation::Yes = for_navigation &&
1949 let EmbedderPolicyValue::UnsafeNone = embedder_policy_value
1950 {
1951 return CrossOriginResourcePolicy::Allowed;
1952 }
1953
1954 let policy = response
1956 .headers
1957 .get(HeaderName::from_static("cross-origin-resource-policy"))
1958 .and_then(|h| h.to_str().ok());
1959
1960 let policy = policy
1962 .filter(|&s| s == "same-origin" || s == "same-site" || s == "cross-origin")
1963 .or(match embedder_policy_value {
1965 EmbedderPolicyValue::UnsafeNone => None,
1967 EmbedderPolicyValue::RequireCorp => Some("same-origin"),
1969 });
1970
1971 match policy {
1973 Some("same-origin") => {
1974 if let Origin::Origin(request_origin) = origin &&
1976 response
1977 .url()
1978 .is_some_and(|url| request_origin == &url.origin())
1979 {
1980 return CrossOriginResourcePolicy::Allowed;
1981 }
1982
1983 CrossOriginResourcePolicy::Blocked
1985 },
1986 Some("same-site") => {
1987 if let Some(response_url) = response.url() {
1988 if let Origin::Origin(request_origin) = origin &&
1992 is_schemelessy_same_site(request_origin, &response_url.origin()) &&
1993 (request_origin.scheme() == Some("https") ||
1994 response_url.scheme() != "https")
1995 {
1996 return CrossOriginResourcePolicy::Allowed;
1997 }
1998 }
1999 CrossOriginResourcePolicy::Blocked
2001 },
2002 _ => CrossOriginResourcePolicy::Allowed,
2005 }
2006}
2007
2008struct ResponseEndTimer(Option<ResourceFetchTimingContainer>);
2010
2011impl ResponseEndTimer {
2012 fn neuter(&mut self) {
2013 self.0 = None;
2014 }
2015}
2016
2017impl Drop for ResponseEndTimer {
2018 fn drop(&mut self) {
2019 let ResponseEndTimer(resource_fetch_timing_opt) = self;
2020
2021 resource_fetch_timing_opt.as_ref().map_or((), |t| {
2022 t.set_attribute(ResourceAttribute::ResponseEnd);
2023 })
2024 }
2025}
2026
2027#[servo_tracing::instrument(skip_all,fields(url=fetch_params.request.url().as_str()))]
2029async fn http_network_fetch(
2030 fetch_params: &mut FetchParams,
2031 credentials_flag: bool,
2032 done_chan: &mut DoneChannel,
2033 context: &FetchContext,
2034) -> Response {
2035 let mut response_end_timer = ResponseEndTimer(Some(context.timing.clone()));
2036
2037 let request = &mut fetch_params.request;
2039
2040 let url = request.current_url();
2050 let request_id = request.id.0.to_string();
2051 if log_enabled!(log::Level::Info) {
2052 info!("{:?} request for {}", request.method, url);
2053 for header in request.headers.iter() {
2054 debug!(" - {:?}", header);
2055 }
2056 }
2057
2058 let is_xhr = request.destination == Destination::None;
2062
2063 let (fetch_terminated_sender, mut fetch_terminated_receiver) = unbounded_channel();
2065
2066 let body = request.body.as_ref().map(|body| body.clone_stream());
2067
2068 if body.is_none() {
2069 let _ = fetch_terminated_sender.send(false);
2074 }
2075
2076 let browsing_context_id = request.target_webview_id.map(Into::into);
2077
2078 let (res, msg) = match &request.mode {
2082 RequestMode::WebSocket {
2084 protocols,
2085 original_url: _,
2086 } => {
2087 let (resource_event_sender, dom_action_receiver) = {
2090 let mut websocket_chan = context.websocket_chan.as_ref().unwrap().lock();
2091 (
2092 websocket_chan.sender.clone(),
2093 websocket_chan.receiver.take().unwrap(),
2094 )
2095 };
2096
2097 let mut tls_config = create_tls_config(
2098 context.ca_certificates.clone(),
2099 context.ignore_certificate_errors,
2100 context.state.override_manager.clone(),
2101 );
2102 tls_config.alpn_protocols = vec!["http/1.1".to_string().into()];
2103
2104 let response = match start_websocket(
2105 context.state.clone(),
2106 resource_event_sender,
2107 protocols,
2108 request,
2109 tls_config,
2110 dom_action_receiver,
2111 )
2112 .await
2113 {
2114 Ok(response) => response,
2115 Err(error) => {
2116 return Response::network_error(NetworkError::WebsocketConnectionFailure(
2117 format!("{error:?}"),
2118 ));
2119 },
2120 };
2121
2122 let response = response.map(|r| match r {
2123 Some(body) => Full::from(body).map_err(|_| unreachable!()).boxed(),
2124 None => http_body_util::Empty::new()
2125 .map_err(|_| unreachable!())
2126 .boxed(),
2127 });
2128 (Decoder::detect(response, url.is_secure_scheme()), None)
2129 },
2130 _ => {
2133 let response_future = obtain_response(
2134 &context.state.client,
2135 &url,
2136 &request.method,
2137 &mut request.headers,
2138 body,
2139 request
2140 .body
2141 .as_ref()
2142 .is_some_and(|body| body.source_is_null()),
2143 &request.pipeline_id,
2144 Some(&request_id),
2145 request.destination,
2146 is_xhr,
2147 context,
2148 fetch_terminated_sender,
2149 browsing_context_id,
2150 );
2151
2152 let (res, msg) = match response_future.await {
2154 Ok(wrapped_response) => wrapped_response,
2155 Err(error) => return Response::network_error(error),
2156 };
2157 (res, msg)
2158 },
2159 };
2160
2161 if log_enabled!(log::Level::Info) {
2162 debug!("{:?} response for {}", res.version(), url);
2163 for header in res.headers().iter() {
2164 debug!(" - {:?}", header);
2165 }
2166 }
2167
2168 match fetch_terminated_receiver.recv().await {
2171 Some(true) => return Response::network_error(NetworkError::ConnectionFailure),
2172 Some(false) => {},
2173 _ => warn!("Failed to receive confirmation request was streamed without error."),
2174 }
2175
2176 let timing = context.timing.inner().clone();
2177 let mut response = Response::new(url.clone(), timing);
2178
2179 if let Some(handshake_info) = res.extensions().get::<TlsHandshakeInfo>() {
2180 let mut hsts_enabled = url
2181 .host_str()
2182 .is_some_and(|host| context.state.hsts_list.read().is_host_secure(host));
2183
2184 if url.scheme() == "https" &&
2185 let Some(sts) = res.headers().typed_get::<StrictTransportSecurity>()
2186 {
2187 hsts_enabled = sts.max_age().as_secs() > 0;
2189 }
2190 response.tls_security_info = Some(build_tls_security_info(handshake_info, hsts_enabled));
2191 }
2192
2193 let status_text = res
2194 .extensions()
2195 .get::<ReasonPhrase>()
2196 .map(ReasonPhrase::as_bytes)
2197 .or_else(|| res.status().canonical_reason().map(str::as_bytes))
2198 .map(Vec::from)
2199 .unwrap_or_default();
2200 response.status = HttpStatus::new(res.status(), status_text);
2201
2202 info!("got {:?} response for {:?}", res.status(), request.url());
2203 response.headers = res.headers().clone();
2204 response.referrer = request.referrer.to_url().cloned();
2205 response.referrer_policy = request.referrer_policy;
2206
2207 let res_body = response.body.clone();
2208
2209 let (done_sender, done_receiver) = unbounded_channel();
2211 *done_chan = Some((done_sender.clone(), done_receiver));
2212
2213 let devtools_sender = context.devtools_chan.clone();
2214 let cancellation_listener = context.cancellation_listener.clone();
2215 if cancellation_listener.cancelled() {
2216 return Response::network_error(NetworkError::LoadCancelled);
2217 }
2218
2219 *res_body.lock() = ResponseBody::Receiving(vec![]);
2220 let res_body2 = res_body.clone();
2221
2222 if let Some(ref sender) = devtools_sender &&
2223 let Some(m) = msg
2224 {
2225 send_request_to_devtools(m, sender);
2226 }
2227
2228 let done_sender2 = done_sender.clone();
2229 let done_sender3 = done_sender.clone();
2230 let timing_ptr2 = context.timing.clone();
2231 let timing_ptr3 = context.timing.clone();
2232 let devtools_request = request.clone();
2233 let url1 = devtools_request.url();
2234 let url2 = url1.clone();
2235
2236 let status = response.status.clone();
2237 let headers = response.headers.clone();
2238 let devtools_chan = context.devtools_chan.clone();
2239
2240 spawn_task(
2241 res.into_body()
2242 .try_fold(res_body, move |res_body, chunk| {
2243 if cancellation_listener.cancelled() {
2244 *res_body.lock() = ResponseBody::Done(vec![]);
2245 let _ = done_sender.send(Data::Cancelled);
2246 return future::ready(Err(std::io::Error::new(
2247 std::io::ErrorKind::Interrupted,
2248 "Fetch aborted",
2249 )));
2250 }
2251 if let ResponseBody::Receiving(ref mut body) = *res_body.lock() {
2252 let bytes = chunk;
2253 body.extend_from_slice(&bytes);
2254 let _ = done_sender.send(Data::Payload(bytes.to_vec()));
2255 }
2256 future::ready(Ok(res_body))
2257 })
2258 .and_then(move |res_body| {
2259 debug!("successfully finished response for {:?}", url1);
2260 let mut body = res_body.lock();
2261 let completed_body = match *body {
2262 ResponseBody::Receiving(ref mut body) => std::mem::take(body),
2263 _ => vec![],
2264 };
2265 let devtools_response_body = completed_body.clone();
2266 *body = ResponseBody::Done(completed_body);
2267 send_response_values_to_devtools(
2268 Some(headers),
2269 status,
2270 Some(devtools_response_body),
2271 CacheState::None,
2272 &devtools_request,
2273 devtools_chan,
2274 );
2275 timing_ptr2.set_attribute(ResourceAttribute::ResponseEnd);
2276 let _ = done_sender2.send(Data::Done);
2277 future::ready(Ok(()))
2278 })
2279 .map_err(move |error| {
2280 if let std::io::ErrorKind::InvalidData = error.kind() {
2281 debug!("Content decompression error for {:?}", url2);
2282 let _ = done_sender3.send(Data::Error(NetworkError::DecompressionError));
2283 let mut body = res_body2.lock();
2284
2285 *body = ResponseBody::Done(vec![]);
2286 }
2287 debug!("finished response for {:?}", url2);
2288 let mut body = res_body2.lock();
2289 let completed_body = match *body {
2290 ResponseBody::Receiving(ref mut body) => std::mem::take(body),
2291 _ => vec![],
2292 };
2293 *body = ResponseBody::Done(completed_body);
2294 timing_ptr3.set_attribute(ResourceAttribute::ResponseEnd);
2295 let _ = done_sender3.send(Data::Done);
2296 }),
2297 );
2298
2299 if credentials_flag {
2317 set_cookies_from_headers(&url, &response.headers, &context.state.cookie_jar);
2318 }
2319 context
2320 .state
2321 .hsts_list
2322 .write()
2323 .update_hsts_list_from_response(&url, &response.headers);
2324
2325 response_end_timer.neuter();
2339 response
2340}
2341
2342async fn cors_preflight_fetch(
2344 request: &Request,
2345 cache: &mut CorsCache,
2346 context: &FetchContext,
2347) -> Response {
2348 let mut preflight = RequestBuilder::new(
2353 request.target_webview_id,
2354 request.current_url_with_blob_claim(),
2355 request.referrer.clone(),
2356 )
2357 .method(Method::OPTIONS)
2358 .origin(match &request.origin {
2359 Origin::Client => {
2360 unreachable!("We shouldn't get Client origin in cors_preflight_fetch.")
2361 },
2362 Origin::Origin(origin) => origin.clone(),
2363 })
2364 .pipeline_id(request.pipeline_id)
2365 .initiator(request.initiator)
2366 .destination(request.destination)
2367 .referrer_policy(request.referrer_policy)
2368 .mode(RequestMode::CorsMode)
2369 .response_tainting(ResponseTainting::CorsTainting)
2370 .policy_container(match &request.policy_container {
2371 RequestPolicyContainer::Client => {
2372 unreachable!("We should have a policy container for request in cors_preflight_fetch")
2373 },
2374 RequestPolicyContainer::PolicyContainer(policy_container) => policy_container.clone(),
2375 })
2376 .url_list(
2377 request
2378 .url_list
2379 .iter()
2380 .map(|claimed_url| claimed_url.url())
2381 .collect(),
2382 )
2383 .build();
2384
2385 preflight
2387 .headers
2388 .insert(ACCEPT, HeaderValue::from_static("*/*"));
2389
2390 preflight
2392 .headers
2393 .typed_insert::<AccessControlRequestMethod>(AccessControlRequestMethod::from(
2394 request.method.clone(),
2395 ));
2396
2397 let headers = get_cors_unsafe_header_names(&request.headers);
2399
2400 if !headers.is_empty() {
2402 preflight.headers.insert(
2405 ACCESS_CONTROL_REQUEST_HEADERS,
2406 HeaderValue::from_bytes(itertools::join(headers.iter(), ",").as_bytes())
2407 .unwrap_or(HeaderValue::from_static("")),
2408 );
2409 }
2410
2411 let mut fetch_params = FetchParams::new(preflight);
2414 let response =
2415 http_network_or_cache_fetch(&mut fetch_params, false, false, &mut None, context).await;
2416
2417 if cors_check(request, &response).is_ok() && response.status.code().is_success() {
2419 let mut methods = if response
2422 .headers
2423 .contains_key(header::ACCESS_CONTROL_ALLOW_METHODS)
2424 {
2425 match response.headers.typed_get::<AccessControlAllowMethods>() {
2426 Some(methods) => methods.iter().collect(),
2427 None => {
2429 return Response::network_error(NetworkError::CorsAllowMethods);
2430 },
2431 }
2432 } else {
2433 vec![]
2434 };
2435
2436 let header_names = if response
2439 .headers
2440 .contains_key(header::ACCESS_CONTROL_ALLOW_HEADERS)
2441 {
2442 match response.headers.typed_get::<AccessControlAllowHeaders>() {
2443 Some(names) => names.iter().collect(),
2444 None => {
2446 return Response::network_error(NetworkError::CorsAllowHeaders);
2447 },
2448 }
2449 } else {
2450 vec![]
2451 };
2452
2453 debug!(
2454 "CORS check: Allowed methods: {:?}, current method: {:?}",
2455 methods, request.method
2456 );
2457
2458 if methods.is_empty() && request.use_cors_preflight {
2461 methods = vec![request.method.clone()];
2462 }
2463
2464 if methods
2467 .iter()
2468 .all(|method| *method.as_str() != *request.method.as_ref()) &&
2469 !is_cors_safelisted_method(&request.method) &&
2470 (request.credentials_mode == CredentialsMode::Include ||
2471 methods.iter().all(|method| method.as_ref() != "*"))
2472 {
2473 return Response::network_error(NetworkError::CorsMethod);
2474 }
2475
2476 debug!(
2477 "CORS check: Allowed headers: {:?}, current headers: {:?}",
2478 header_names, request.headers
2479 );
2480
2481 if request.headers.iter().any(|(name, _)| {
2484 is_cors_non_wildcard_request_header_name(name) &&
2485 header_names.iter().all(|header_name| header_name != name)
2486 }) {
2487 return Response::network_error(NetworkError::CorsAuthorization);
2488 }
2489
2490 let unsafe_names = get_cors_unsafe_header_names(&request.headers);
2494 let header_names_set: HashSet<&HeaderName> = HashSet::from_iter(header_names.iter());
2495 let header_names_contains_star = header_names
2496 .iter()
2497 .any(|header_name| header_name.as_str() == "*");
2498 for unsafe_name in unsafe_names.iter() {
2499 if !header_names_set.contains(unsafe_name) &&
2500 (request.credentials_mode == CredentialsMode::Include ||
2501 !header_names_contains_star)
2502 {
2503 return Response::network_error(NetworkError::CorsHeaders);
2504 }
2505 }
2506
2507 let max_age: Option<Duration> = response
2510 .headers
2511 .typed_get::<AccessControlMaxAge>()
2512 .map(|acma| acma.into());
2513
2514 let max_age = max_age.unwrap_or(Duration::from_secs(5));
2516
2517 for method in &methods {
2528 cache.match_method_and_update(request, method.clone(), max_age);
2529 }
2530
2531 for header_name in &header_names {
2536 cache.match_header_and_update(request, header_name, max_age);
2537 }
2538
2539 return response;
2541 }
2542
2543 Response::network_error(NetworkError::CorsGeneral)
2545}
2546
2547fn cors_check(request: &Request, response: &Response) -> Result<(), ()> {
2549 let Some(origins) =
2551 get_value_from_header_list(ACCESS_CONTROL_ALLOW_ORIGIN.as_str(), &response.headers)
2552 else {
2553 return Err(());
2555 };
2556 let origin = origins.into_iter().map(char::from).collect::<String>();
2557
2558 if request.credentials_mode != CredentialsMode::Include && origin == "*" {
2560 return Ok(());
2561 }
2562
2563 if serialize_request_origin(request).to_string() != origin {
2565 return Err(());
2566 }
2567
2568 if request.credentials_mode != CredentialsMode::Include {
2570 return Ok(());
2571 }
2572
2573 let credentials = response
2575 .headers
2576 .typed_get::<AccessControlAllowCredentials>();
2577
2578 if credentials.is_some() {
2580 return Ok(());
2581 }
2582
2583 Err(())
2585}
2586
2587fn has_credentials(url: &ServoUrl) -> bool {
2588 !url.username().is_empty() || url.password().is_some()
2589}
2590
2591fn is_no_store_cache(headers: &HeaderMap) -> bool {
2592 headers.contains_key(header::IF_MODIFIED_SINCE) |
2593 headers.contains_key(header::IF_NONE_MATCH) |
2594 headers.contains_key(header::IF_UNMODIFIED_SINCE) |
2595 headers.contains_key(header::IF_MATCH) |
2596 headers.contains_key(header::IF_RANGE)
2597}
2598
2599fn is_redirect_status(status: StatusCode) -> bool {
2601 matches!(
2602 status,
2603 StatusCode::MOVED_PERMANENTLY |
2604 StatusCode::FOUND |
2605 StatusCode::SEE_OTHER |
2606 StatusCode::TEMPORARY_REDIRECT |
2607 StatusCode::PERMANENT_REDIRECT
2608 )
2609}
2610
2611fn serialize_request_origin(request: &Request) -> headers::Origin {
2613 let Origin::Origin(origin) = &request.origin else {
2615 panic!("origin cannot be \"client\" at this point in time");
2616 };
2617
2618 if request.redirect_taint_for_request() != RedirectTaint::SameOrigin {
2620 return headers::Origin::NULL;
2621 }
2622
2623 serialize_origin(origin)
2625}
2626
2627pub fn serialize_origin(origin: &ImmutableOrigin) -> headers::Origin {
2629 match origin {
2630 ImmutableOrigin::Opaque(_) => headers::Origin::NULL,
2631 ImmutableOrigin::Tuple(scheme, host, port) => {
2632 let port = match (scheme.as_ref(), port) {
2635 ("http" | "ws", 80) | ("https" | "wss", 443) | ("ftp", 21) => None,
2636 _ => Some(*port),
2637 };
2638
2639 headers::Origin::try_from_parts(scheme, &host.to_string(), port)
2641 .unwrap_or(headers::Origin::NULL)
2642 },
2643 }
2644}
2645
2646#[expect(
2648 clippy::collapsible_match,
2649 reason = "The current way follows the spec more closely"
2650)]
2651fn append_a_request_origin_header(request: &mut Request) {
2652 let Origin::Origin(request_origin) = &request.origin else {
2654 panic!("origin cannot be \"client\" at this point in time");
2655 };
2656
2657 let mut serialized_origin = serialize_request_origin(request);
2659
2660 if request.response_tainting == ResponseTainting::CorsTainting ||
2663 matches!(request.mode, RequestMode::WebSocket { .. })
2664 {
2665 request.headers.typed_insert(serialized_origin);
2666 }
2667 else if !matches!(request.method, Method::GET | Method::HEAD) {
2669 if request.mode != RequestMode::CorsMode {
2671 match request.referrer_policy {
2672 ReferrerPolicy::NoReferrer => {
2673 serialized_origin = headers::Origin::NULL;
2675 },
2676 ReferrerPolicy::NoReferrerWhenDowngrade |
2677 ReferrerPolicy::StrictOrigin |
2678 ReferrerPolicy::StrictOriginWhenCrossOrigin => {
2679 if let ImmutableOrigin::Tuple(scheme, _, _) = &request_origin &&
2682 scheme == "https" &&
2683 request.current_url().scheme() != "https"
2684 {
2685 serialized_origin = headers::Origin::NULL;
2686 }
2687 },
2688 ReferrerPolicy::SameOrigin => {
2689 if *request_origin != request.current_url().origin() {
2692 serialized_origin = headers::Origin::NULL;
2693 }
2694 },
2695 _ => {
2696 },
2698 };
2699 }
2700
2701 request.headers.typed_insert(serialized_origin);
2703 }
2704}
2705
2706fn append_the_fetch_metadata_headers(r: &mut Request) {
2708 if !r.url().is_potentially_trustworthy() {
2710 return;
2711 }
2712
2713 set_the_sec_fetch_dest_header(r);
2715
2716 set_the_sec_fetch_mode_header(r);
2718
2719 set_the_sec_fetch_site_header(r);
2721
2722 set_the_sec_fetch_user_header(r);
2724}
2725
2726fn append_cache_data_to_headers(http_request: &mut Request) {
2728 match http_request.cache_mode {
2729 CacheMode::Default if is_no_store_cache(&http_request.headers) => {
2733 http_request.cache_mode = CacheMode::NoStore;
2734 },
2735
2736 CacheMode::NoCache if !http_request.headers.contains_key(header::CACHE_CONTROL) => {
2746 http_request
2747 .headers
2748 .typed_insert(CacheControl::new().with_max_age(Duration::from_secs(0)));
2749 },
2750
2751 CacheMode::Reload | CacheMode::NoStore => {
2753 if !http_request.headers.contains_key(header::PRAGMA) {
2756 http_request.headers.typed_insert(Pragma::no_cache());
2757 }
2758
2759 if !http_request.headers.contains_key(header::CACHE_CONTROL) {
2762 http_request
2763 .headers
2764 .typed_insert(CacheControl::new().with_no_cache());
2765 }
2766 },
2767
2768 _ => {},
2769 }
2770}
2771
2772fn set_the_sec_fetch_dest_header(r: &mut Request) {
2774 debug_assert!(r.url().is_potentially_trustworthy());
2776
2777 let header = r.destination;
2781
2782 r.headers.typed_insert(SecFetchDest(header));
2784}
2785
2786fn set_the_sec_fetch_mode_header(r: &mut Request) {
2788 debug_assert!(r.url().is_potentially_trustworthy());
2790
2791 let header = &r.mode;
2794
2795 r.headers.typed_insert(SecFetchMode::from(header));
2797}
2798
2799fn set_the_sec_fetch_site_header(r: &mut Request) {
2801 let Origin::Origin(request_origin) = &r.origin else {
2804 panic!("request origin cannot be \"client\" at this point")
2805 };
2806
2807 debug_assert!(r.url().is_potentially_trustworthy());
2809
2810 let mut header = SecFetchSite::SameOrigin;
2813
2814 if header != SecFetchSite::None {
2819 for url in &r.url_list {
2820 if url.origin() == *request_origin {
2822 continue;
2823 }
2824
2825 header = SecFetchSite::CrossSite;
2827
2828 if !is_same_site(request_origin, &url.origin()) {
2830 break;
2831 }
2832
2833 header = SecFetchSite::SameSite;
2835 }
2836 }
2837
2838 r.headers.typed_insert(header);
2840}
2841
2842fn set_the_sec_fetch_user_header(r: &mut Request) {
2844 debug_assert!(r.url().is_potentially_trustworthy());
2846
2847 if !r.is_navigation_request() {
2850 return;
2851 }
2852
2853 let header = SecFetchUser;
2856
2857 r.headers.typed_insert(header);
2859}
2860
2861fn set_requests_referrer_policy_on_redirect(request: &mut Request, response: &Response) {
2863 let referrer_policy: ReferrerPolicy = response
2866 .headers
2867 .typed_get::<headers::ReferrerPolicy>()
2868 .into();
2869
2870 if referrer_policy != ReferrerPolicy::EmptyString {
2872 request.referrer_policy = referrer_policy;
2873 }
2874}