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::Origin::Origin as SpecificOrigin;
45use net_traits::request::{
46 BodyChunkRequest, BodyChunkResponse, CacheMode, CredentialsMode, Destination, Initiator,
47 Origin, RedirectMode, Referrer, Request, RequestBuilder, RequestClient, RequestMode,
48 ResponseTainting, ServiceWorkersMode, TraversableForUserPrompts, get_cors_unsafe_header_names,
49 is_cors_non_wildcard_request_header_name, is_cors_safelisted_method,
50 is_cors_safelisted_request_header,
51};
52use net_traits::response::{CacheState, RedirectTaint, Response, ResponseBody, ResponseType};
53use net_traits::{
54 CookieSource, DOCUMENT_ACCEPT_HEADER_VALUE, NetworkError, RedirectEndValue, RedirectStartValue,
55 ReferrerPolicy, ResourceAttribute, ResourceFetchTimingContainer, ResourceTimeValue,
56 TlsSecurityInfo, TlsSecurityState,
57};
58use parking_lot::{Mutex, RwLock};
59use profile_traits::mem::{Report, ReportKind};
60use profile_traits::path;
61#[cfg(feature = "tracing")]
62use profile_traits::trace_span;
63use rustc_hash::FxHashMap;
64use servo_base::cross_process_instant::CrossProcessInstant;
65use servo_base::generic_channel::GenericSharedMemory;
66use servo_base::id::{BrowsingContextId, HistoryStateId, PipelineId};
67use servo_url::{ImmutableOrigin, ServoUrl};
68use tokio::sync::mpsc::{
69 Receiver as TokioReceiver, Sender as TokioSender, UnboundedReceiver, UnboundedSender, channel,
70 unbounded_channel,
71};
72use tokio_stream::wrappers::ReceiverStream;
73#[cfg(feature = "tracing")]
74use tracing::Instrument;
75
76use crate::async_runtime::spawn_task;
77use crate::connector::{
78 CertificateErrorOverrideManager, ServoClient, TlsHandshakeInfo, create_tls_config,
79};
80use crate::cookie::ServoCookie;
81use crate::cookie_storage::CookieStorage;
82use crate::decoder::Decoder;
83use crate::devtools::{
84 prepare_devtools_request, send_request_to_devtools, send_response_values_to_devtools,
85};
86use crate::embedder::NetToEmbedderMsg;
87use crate::fetch::cors_cache::CorsCache;
88use crate::fetch::fetch_params::FetchParams;
89use crate::fetch::headers::{SecFetchDest, SecFetchMode, SecFetchSite, SecFetchUser};
90use crate::fetch::methods::{Data, DoneChannel, FetchContext, Target, main_fetch};
91use crate::hsts::HstsList;
92use crate::http_cache::{
93 CacheKey, CachedResourcesOrGuard, HttpCache, construct_response, invalidate_cached_resources,
94 refresh,
95};
96use crate::resource_thread::{AuthCache, AuthCacheEntry};
97use crate::websocket_loader::start_websocket;
98
99#[derive(Clone, Debug, Eq, PartialEq)]
101pub enum HttpCacheEntryState {
102 ReadyToConstruct,
106 PendingStore(usize),
108}
109
110pub struct HttpState {
111 pub hsts_list: RwLock<HstsList>,
112 pub cookie_jar: RwLock<CookieStorage>,
113 pub http_cache: HttpCache,
114 pub auth_cache: RwLock<AuthCache>,
115 pub history_states: RwLock<FxHashMap<HistoryStateId, Vec<u8>>>,
116 pub client: ServoClient,
117 pub override_manager: CertificateErrorOverrideManager,
118 pub embedder_proxy: GenericEmbedderProxy<NetToEmbedderMsg>,
119}
120
121impl HttpState {
122 pub(crate) fn memory_reports(&self, suffix: &str, ops: &mut MallocSizeOfOps) -> Vec<Report> {
123 vec![
124 Report {
125 path: path!["memory-cache", suffix],
126 kind: ReportKind::ExplicitJemallocHeapSize,
127 size: self.http_cache.size_of(ops),
128 },
129 Report {
130 path: path!["hsts-list", suffix],
131 kind: ReportKind::ExplicitJemallocHeapSize,
132 size: self.hsts_list.read().size_of(ops),
133 },
134 ]
135 }
136
137 async fn request_authentication(
138 &self,
139 request: &Request,
140 response: &Response,
141 ) -> Option<AuthenticationResponse> {
142 let webview_id = request.target_webview_id?;
144 let for_proxy = response.status == StatusCode::PROXY_AUTHENTICATION_REQUIRED;
145
146 if request.mode != RequestMode::Navigate {
148 return None;
149 }
150
151 let (sender, receiver) = tokio::sync::oneshot::channel();
152 self.embedder_proxy
153 .send(NetToEmbedderMsg::RequestAuthentication(
154 webview_id,
155 request.url(),
156 for_proxy,
157 sender,
158 ));
159 receiver.await.ok()?
160 }
161}
162
163pub(crate) fn set_default_accept(request: &mut Request) {
165 if request.headers.contains_key(header::ACCEPT) {
167 return;
168 }
169
170 let value = if request.initiator == Initiator::Prefetch {
172 DOCUMENT_ACCEPT_HEADER_VALUE
173 } else {
174 match request.destination {
177 Destination::Document | Destination::Frame | Destination::IFrame => {
178 DOCUMENT_ACCEPT_HEADER_VALUE
179 },
180 Destination::Image => {
181 HeaderValue::from_static("image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5")
182 },
183 Destination::Json => HeaderValue::from_static("application/json,*/*;q=0.5"),
184 Destination::Style => HeaderValue::from_static("text/css,*/*;q=0.1"),
185 _ => HeaderValue::from_static("*/*"),
187 }
188 };
189
190 request.headers.insert(header::ACCEPT, value);
192}
193
194fn set_default_accept_encoding(headers: &mut HeaderMap) {
195 if headers.contains_key(header::ACCEPT_ENCODING) {
196 return;
197 }
198
199 headers.insert(
201 header::ACCEPT_ENCODING,
202 HeaderValue::from_static("gzip, deflate, br, zstd"),
203 );
204}
205
206fn no_referrer_when_downgrade(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
208 if referrer_url.is_potentially_trustworthy() && !current_url.is_potentially_trustworthy() {
210 return None;
211 }
212 strip_url_for_use_as_referrer(referrer_url, false)
214}
215
216fn strict_origin(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
218 if referrer_url.is_potentially_trustworthy() && !current_url.is_potentially_trustworthy() {
220 return None;
221 }
222 strip_url_for_use_as_referrer(referrer_url, true)
224}
225
226fn strict_origin_when_cross_origin(
228 referrer_url: ServoUrl,
229 current_url: ServoUrl,
230) -> Option<ServoUrl> {
231 if referrer_url.origin() == current_url.origin() {
233 return strip_url_for_use_as_referrer(referrer_url, false);
234 }
235 if referrer_url.is_potentially_trustworthy() && !current_url.is_potentially_trustworthy() {
237 return None;
238 }
239 strip_url_for_use_as_referrer(referrer_url, true)
241}
242
243fn is_schemelessy_same_site(site_a: &ImmutableOrigin, site_b: &ImmutableOrigin) -> bool {
245 if !site_a.is_tuple() && !site_b.is_tuple() && site_a == site_b {
247 true
248 } else if site_a.is_tuple() && site_b.is_tuple() {
249 let host_a = site_a.host().map(|h| h.to_string()).unwrap_or_default();
251 let host_b = site_b.host().map(|h| h.to_string()).unwrap_or_default();
252
253 let host_a_reg = reg_suffix(&host_a);
254 let host_b_reg = reg_suffix(&host_b);
255
256 (site_a.host() == site_b.host() && host_a_reg.is_empty()) ||
258 (host_a_reg == host_b_reg && !host_a_reg.is_empty())
259 } else {
260 false
262 }
263}
264
265fn strip_url_for_use_as_referrer(mut url: ServoUrl, origin_only: bool) -> Option<ServoUrl> {
267 const MAX_REFERRER_URL_LENGTH: usize = 4096;
268 if url.is_local_scheme() {
270 return None;
271 }
272 {
274 let url = url.as_mut_url();
275 let _ = url.set_username("");
276 let _ = url.set_password(None);
277 url.set_fragment(None);
278 if origin_only || url.as_str().len() > MAX_REFERRER_URL_LENGTH {
282 url.set_path("");
283 url.set_query(None);
284 }
285 }
286 Some(url)
288}
289
290fn same_origin(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
292 if referrer_url.origin() == current_url.origin() {
294 return strip_url_for_use_as_referrer(referrer_url, false);
295 }
296 None
298}
299
300fn origin_when_cross_origin(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
302 if referrer_url.origin() == current_url.origin() {
304 return strip_url_for_use_as_referrer(referrer_url, false);
305 }
306 strip_url_for_use_as_referrer(referrer_url, true)
308}
309
310pub fn determine_requests_referrer(
312 referrer_policy: ReferrerPolicy,
313 referrer_source: ServoUrl,
314 current_url: ServoUrl,
315) -> Option<ServoUrl> {
316 match referrer_policy {
317 ReferrerPolicy::EmptyString | ReferrerPolicy::NoReferrer => None,
318 ReferrerPolicy::Origin => strip_url_for_use_as_referrer(referrer_source, true),
319 ReferrerPolicy::UnsafeUrl => strip_url_for_use_as_referrer(referrer_source, false),
320 ReferrerPolicy::StrictOrigin => strict_origin(referrer_source, current_url),
321 ReferrerPolicy::StrictOriginWhenCrossOrigin => {
322 strict_origin_when_cross_origin(referrer_source, current_url)
323 },
324 ReferrerPolicy::SameOrigin => same_origin(referrer_source, current_url),
325 ReferrerPolicy::OriginWhenCrossOrigin => {
326 origin_when_cross_origin(referrer_source, current_url)
327 },
328 ReferrerPolicy::NoReferrerWhenDowngrade => {
329 no_referrer_when_downgrade(referrer_source, current_url)
330 },
331 }
332}
333
334fn set_request_cookies(
335 url: &ServoUrl,
336 headers: &mut HeaderMap,
337 cookie_jar: &RwLock<CookieStorage>,
338) {
339 let mut cookie_jar = cookie_jar.write();
340 cookie_jar.remove_expired_cookies_for_url(url);
341 if let Some(cookie_list) = cookie_jar.cookies_for_url(url, CookieSource::HTTP) {
342 headers.insert(
343 header::COOKIE,
344 HeaderValue::from_bytes(cookie_list.as_bytes()).unwrap(),
345 );
346 }
347}
348
349fn set_cookie_for_url(cookie_jar: &RwLock<CookieStorage>, request: &ServoUrl, cookie_val: &str) {
350 let mut cookie_jar = cookie_jar.write();
351 let source = CookieSource::HTTP;
352
353 if let Some(cookie) = ServoCookie::from_cookie_string(cookie_val, request, source) {
354 cookie_jar.push(cookie, request, source);
355 }
356}
357
358fn set_cookies_from_headers(
359 url: &ServoUrl,
360 headers: &HeaderMap,
361 cookie_jar: &RwLock<CookieStorage>,
362) {
363 for cookie in headers.get_all(header::SET_COOKIE) {
364 let cookie_bytes = cookie.as_bytes();
365 if !ServoCookie::is_valid_name_or_value(cookie_bytes) {
366 continue;
367 }
368 if let Ok(cookie_str) = std::str::from_utf8(cookie_bytes) {
369 set_cookie_for_url(cookie_jar, url, cookie_str);
370 }
371 }
372}
373
374fn build_tls_security_info(handshake: &TlsHandshakeInfo, hsts_enabled: bool) -> TlsSecurityInfo {
375 let state = if handshake.protocol_version.is_none() || handshake.cipher_suite.is_none() {
383 TlsSecurityState::Insecure
385 } else {
386 TlsSecurityState::Secure
388 };
389
390 TlsSecurityInfo {
391 state,
392 weakness_reasons: Vec::new(), protocol_version: handshake.protocol_version.clone(),
394 cipher_suite: handshake.cipher_suite.clone(),
395 kea_group_name: handshake.kea_group_name.clone(),
396 signature_scheme_name: handshake.signature_scheme_name.clone(),
397 alpn_protocol: handshake.alpn_protocol.clone(),
398 certificate_chain_der: handshake.certificate_chain_der.clone(),
399 certificate_transparency: None,
400 hsts: hsts_enabled,
401 hpkp: false,
402 used_ech: handshake.used_ech,
403 used_delegated_credentials: false,
404 used_ocsp: false,
405 used_private_dns: false,
406 }
407}
408
409fn auth_from_cache(
410 auth_cache: &RwLock<AuthCache>,
411 origin: &ImmutableOrigin,
412) -> Option<Authorization<Basic>> {
413 if let Some(auth_entry) = auth_cache.read().entries.get(&origin.ascii_serialization()) {
414 let user_name = &auth_entry.user_name;
415 let password = &auth_entry.password;
416 Some(Authorization::basic(user_name, password))
417 } else {
418 None
419 }
420}
421
422enum BodyChunk {
425 Chunk(GenericSharedMemory),
427 Done,
429}
430
431enum BodyStream {
433 Chunked(TokioReceiver<Result<Frame<Bytes>, hyper::Error>>),
436 Buffered(UnboundedReceiver<BodyChunk>),
439}
440
441enum BodySink {
444 Chunked(TokioSender<Result<Frame<Bytes>, hyper::Error>>),
446 Buffered(UnboundedSender<BodyChunk>),
450}
451
452impl BodySink {
453 fn transmit_bytes(&self, bytes: GenericSharedMemory) {
454 match self {
455 BodySink::Chunked(sender) => {
456 let sender = sender.clone();
457 spawn_task(async move {
458 let _ = sender
459 .send(Ok(Frame::data(Bytes::copy_from_slice(&bytes))))
460 .await;
461 });
462 },
463 BodySink::Buffered(sender) => {
464 let _ = sender.send(BodyChunk::Chunk(bytes));
465 },
466 }
467 }
468
469 fn close(&self) {
470 match self {
471 BodySink::Chunked(_) => { },
472 BodySink::Buffered(sender) => {
473 let _ = sender.send(BodyChunk::Done);
474 },
475 }
476 }
477}
478
479fn request_body_stream_closed_error(action: &str) -> NetworkError {
480 NetworkError::Crash(format!(
481 "Request body stream has already been closed while trying to {action}."
482 ))
483}
484
485fn log_request_body_stream_closed(action: &str, error: Option<&IpcError>) {
486 match error {
487 Some(error) => {
488 error!("Request body stream has already been closed while trying to {action}: {error}")
489 },
490 None => error!("Request body stream has already been closed while trying to {action}."),
491 }
492}
493
494fn log_fetch_terminated_send_failure(terminated_with_error: bool, context: &str) {
495 warn!(
496 "Failed to notify request-body stream termination state ({terminated_with_error}) while {context} because the receiver was already dropped."
497 );
498}
499
500const FRAGMENT: &AsciiSet = &CONTROLS.add(b'|').add(b'{').add(b'}');
501
502#[allow(clippy::too_many_arguments)]
503#[servo_tracing::instrument(skip_all, fields(url=url.as_str()))]
504async fn obtain_response(
506 client: &ServoClient,
507 url: &ServoUrl,
508 method: &Method,
509 request_headers: &mut HeaderMap,
510 body_sender: Option<StdArc<Mutex<Option<IpcSender<BodyChunkRequest>>>>>,
511 source_is_null: bool,
512 pipeline_id: &Option<PipelineId>,
513 request_id: Option<&str>,
514 destination: Destination,
515 is_xhr: bool,
516 context: &FetchContext,
517 fetch_terminated: UnboundedSender<bool>,
518 browsing_context_id: Option<BrowsingContextId>,
519) -> Result<(HyperResponse<Decoder>, Option<ChromeToDevtoolsControlMsg>), NetworkError> {
520 let mut headers = request_headers.clone();
521
522 let devtools_bytes = StdArc::new(Mutex::new(vec![]));
523
524 let encoded_url = utf8_percent_encode(url.as_str(), FRAGMENT).to_string();
526
527 let request = if let Some(chunk_requester) = body_sender {
528 let (sink, stream) = if source_is_null {
529 headers.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
532
533 let (sender, receiver) = channel(1);
534 (BodySink::Chunked(sender), BodyStream::Chunked(receiver))
535 } else {
536 let (sender, receiver) = unbounded_channel();
543 (BodySink::Buffered(sender), BodyStream::Buffered(receiver))
544 };
545
546 obtain_response_setup_router_callback(
547 devtools_bytes.clone(),
548 chunk_requester,
549 sink,
550 fetch_terminated,
551 )?;
552
553 let body = match stream {
554 BodyStream::Chunked(receiver) => {
555 let stream = ReceiverStream::new(receiver);
556 BoxBody::new(http_body_util::StreamBody::new(stream))
557 },
558 BodyStream::Buffered(mut receiver) => {
559 let mut body = vec![];
561 loop {
562 match receiver.recv().await {
563 Some(BodyChunk::Chunk(bytes)) => {
564 body.extend_from_slice(&bytes);
565 },
566 Some(BodyChunk::Done) => break,
567 None => warn!("Failed to read all chunks from request body."),
568 }
569 }
570 Full::new(body.into()).map_err(|_| unreachable!()).boxed()
571 },
572 };
573 HyperRequest::builder()
574 .method(method)
575 .uri(encoded_url)
576 .body(body)
577 } else {
578 HyperRequest::builder()
579 .method(method)
580 .uri(encoded_url)
581 .body(
582 http_body_util::Empty::new()
583 .map_err(|_| unreachable!())
584 .boxed(),
585 )
586 };
587
588 let connect_start = CrossProcessInstant::now();
591 context.timing.set_attributes(&[
592 ResourceAttribute::DomainLookupStart,
593 ResourceAttribute::ConnectStart(connect_start),
594 ]);
595
596 if url.scheme() == "https" {
600 context
601 .timing
602 .set_attribute(ResourceAttribute::SecureConnectionStart);
603 }
604
605 let mut request = match request {
606 Ok(request) => request,
607 Err(error) => return Err(NetworkError::HttpError(error.to_string())),
608 };
609 *request.headers_mut() = headers.clone();
610
611 let connect_end = CrossProcessInstant::now();
612 context
613 .timing
614 .set_attribute(ResourceAttribute::ConnectEnd(connect_end));
615
616 let request_id = request_id.map(|v| v.to_owned());
617 let pipeline_id = *pipeline_id;
618 let closure_url = url.clone();
619 let method = method.clone();
620 let send_start = CrossProcessInstant::now();
621
622 let host = request.uri().host().unwrap_or("").to_owned();
623 let override_manager = context.state.override_manager.clone();
624 let headers = headers.clone();
625 let is_secure_scheme = url.is_secure_scheme();
626
627 let client_future = client
628 .request(request)
629 .and_then(move |res| {
630 let send_end = CrossProcessInstant::now();
631
632 let msg = if let Some(request_id) = request_id {
635 if let Some(pipeline_id) = pipeline_id {
636 if let Some(browsing_context_id) = browsing_context_id {
637 Some(prepare_devtools_request(
638 request_id,
639 closure_url,
640 method.clone(),
641 headers,
642 Some(devtools_bytes.lock().clone()),
643 pipeline_id,
644 (connect_end - connect_start).unsigned_abs(),
645 (send_end - send_start).unsigned_abs(),
646 destination,
647 is_xhr,
648 browsing_context_id,
649 ))
650 } else {
651 debug!("Not notifying devtools (no browsing_context_id)");
652 None
653 }
654 } else {
659 debug!("Not notifying devtools (no pipeline_id)");
660 None
661 }
662 } else {
663 debug!("Not notifying devtools (no request_id)");
664 None
665 };
666
667 future::ready(Ok((
668 Decoder::detect(res.map(|r| r.boxed()), is_secure_scheme),
669 msg,
670 )))
671 })
672 .map_err(move |error| {
673 warn!("network error: {error:?}");
674 NetworkError::from_hyper_error(
675 &error,
676 override_manager.remove_certificate_failing_verification(host.as_str()),
677 )
678 });
679
680 #[cfg(feature = "tracing")]
681 {
682 client_future.instrument(trace_span!("HyperRequest")).await
683 }
684
685 #[cfg(not(feature = "tracing"))]
686 {
687 client_future.await
688 }
689}
690
691fn obtain_response_setup_router_callback(
693 devtools_bytes: StdArc<Mutex<Vec<u8>>>,
694 chunk_requester: StdArc<Mutex<Option<IpcSender<BodyChunkRequest>>>>,
695 sink: BodySink,
696 fetch_terminated: UnboundedSender<bool>,
697) -> Result<(), NetworkError> {
698 let (body_chan, body_port) = ipc::channel().unwrap();
699
700 {
701 let mut lock = chunk_requester.lock();
702 if let Some(chunk_requester) = lock.as_mut() {
703 if let Err(error) = chunk_requester.send(BodyChunkRequest::Connect(body_chan)) {
704 log_request_body_stream_closed("connect to the request body stream", Some(&error));
705 return Err(request_body_stream_closed_error(
706 "connect to the request body stream",
707 ));
708 }
709
710 if let Err(error) = chunk_requester.send(BodyChunkRequest::Chunk) {
713 log_request_body_stream_closed(
714 "request the first request body chunk",
715 Some(&error),
716 );
717 return Err(request_body_stream_closed_error(
718 "request the first request body chunk",
719 ));
720 }
721 } else {
722 log_request_body_stream_closed("connect to the request body stream", None);
723 return Err(request_body_stream_closed_error(
724 "connect to the request body stream",
725 ));
726 }
727 }
728
729 ROUTER.add_typed_route(
730 body_port,
731 Box::new(move |message| {
732 info!("Received message");
733 let bytes = match message.unwrap() {
734 BodyChunkResponse::Chunk(bytes) => bytes,
735 BodyChunkResponse::Done => {
736 if fetch_terminated.send(false).is_err() {
738 log_fetch_terminated_send_failure(
739 false,
740 "handling request body completion",
741 );
742 }
743 sink.close();
744
745 return;
746 },
747 BodyChunkResponse::Error => {
748 if fetch_terminated.send(true).is_err() {
752 log_fetch_terminated_send_failure(
753 true,
754 "handling request body stream error",
755 );
756 }
757 sink.close();
758
759 return;
760 },
761 };
762
763 devtools_bytes.lock().extend_from_slice(&bytes);
764
765 sink.transmit_bytes(bytes);
768
769 let mut chunk_requester = chunk_requester.lock();
772 if let Some(chunk_requester) = chunk_requester.as_mut() {
773 if let Err(error) = chunk_requester.send(BodyChunkRequest::Chunk) {
774 log_request_body_stream_closed(
775 "request the next request body chunk",
776 Some(&error),
777 );
778 if fetch_terminated.send(true).is_err() {
779 log_fetch_terminated_send_failure(
780 true,
781 "handling failure to request the next request body chunk",
782 );
783 }
784 sink.close();
785 }
786 } else {
787 log_request_body_stream_closed("request the next request body chunk", None);
788 if fetch_terminated.send(true).is_err() {
789 log_fetch_terminated_send_failure(
790 true,
791 "handling a closed request body stream while requesting the next chunk",
792 );
793 }
794 sink.close();
795 }
796 }),
797 );
798
799 Ok(())
800}
801
802#[async_recursion]
804#[allow(clippy::too_many_arguments)]
805pub(crate) async fn http_fetch(
806 fetch_params: &mut FetchParams,
807 cache: &mut CorsCache,
808 cors_flag: bool,
809 cors_preflight_flag: bool,
810 authentication_fetch_flag: bool,
811 target: Target<'async_recursion>,
812 done_chan: &mut DoneChannel,
813 context: &FetchContext,
814) -> Response {
815 *done_chan = None;
817 let request = &mut fetch_params.request;
819
820 let mut response: Option<Response> = None;
822
823 if request.service_workers_mode == ServiceWorkersMode::All {
825 if let Some(ref res) = response {
830 if (res.response_type == ResponseType::Opaque && request.mode != RequestMode::NoCors) ||
838 (res.response_type == ResponseType::OpaqueRedirect &&
839 request.redirect_mode != RedirectMode::Manual) ||
840 (res.url_list.len() > 1 && request.redirect_mode != RedirectMode::Follow) ||
841 res.is_network_error()
842 {
843 return Response::network_error(NetworkError::ConnectionFailure);
844 }
845
846 }
849 }
850
851 if response.is_none() {
853 if cors_preflight_flag {
855 let method_cache_match = cache.match_method(request, request.method.clone());
856
857 let method_mismatch = !method_cache_match &&
858 (!is_cors_safelisted_method(&request.method) || request.use_cors_preflight);
859 let header_mismatch = request.headers.iter().any(|(name, value)| {
860 !cache.match_header(request, name) &&
861 !is_cors_safelisted_request_header(&name, &value)
862 });
863
864 if method_mismatch || header_mismatch {
865 let preflight_response = cors_preflight_fetch(request, cache, context).await;
868 if let Some(error) = preflight_response.get_network_error() {
870 return Response::network_error(error.clone());
871 }
872 }
873 }
874
875 if request.redirect_mode == RedirectMode::Follow {
878 request.service_workers_mode = ServiceWorkersMode::None;
879 }
880
881 context
885 .timing
886 .set_attribute(ResourceAttribute::RequestStart);
887
888 let mut fetch_result = http_network_or_cache_fetch(
891 fetch_params,
892 authentication_fetch_flag,
893 cors_flag,
894 done_chan,
895 context,
896 )
897 .await;
898
899 if cors_flag && cors_check(&fetch_params.request, &fetch_result).is_err() {
902 return Response::network_error(NetworkError::CorsGeneral);
903 }
904
905 fetch_result.return_internal = false;
908 response = Some(fetch_result);
909 }
910
911 let request = &mut fetch_params.request;
912
913 let mut response = response.unwrap();
915
916 if (request.response_tainting == ResponseTainting::Opaque ||
920 response.response_type == ResponseType::Opaque) &&
921 request.client.as_ref().is_some_and(|client| {
922 cross_origin_resource_policy_check(
923 &request.origin,
924 client,
925 &response,
926 ForNavigation::No,
927 ) == CrossOriginResourcePolicy::Blocked
928 })
929 {
930 return Response::network_error(NetworkError::CrossOriginResponse);
931 }
932
933 if response
935 .actual_response()
936 .status
937 .try_code()
938 .is_some_and(is_redirect_status)
939 {
940 if response.actual_response().status != StatusCode::SEE_OTHER {
944 }
946
947 response = match request.redirect_mode {
949 RedirectMode::Error => Response::network_error(NetworkError::RedirectError),
951 RedirectMode::Manual => {
952 if request.mode == RequestMode::Navigate {
955 let location_url =
959 location_url_for_response(&response, request.current_url().fragment());
960 response.actual_response_mut().location_url = location_url;
961 response
962 } else {
963 response.to_filtered(ResponseType::OpaqueRedirect)
965 }
966 },
967 RedirectMode::Follow => {
968 response.return_internal = true;
970 http_redirect_fetch(
971 fetch_params,
972 cache,
973 response,
974 cors_flag,
975 target,
976 done_chan,
977 context,
978 )
979 .await
980 },
981 };
982 }
983
984 response.return_internal = true;
986 context
987 .timing
988 .set_attribute(ResourceAttribute::RedirectCount(
989 fetch_params.request.redirect_count as u16,
990 ));
991
992 response.resource_timing = context.timing.clone();
993
994 response
996}
997
998struct RedirectEndTimer(Option<ResourceFetchTimingContainer>);
1000
1001impl RedirectEndTimer {
1002 fn neuter(&mut self) {
1003 self.0 = None;
1004 }
1005}
1006
1007impl Drop for RedirectEndTimer {
1008 fn drop(&mut self) {
1009 let RedirectEndTimer(resource_fetch_timing_opt) = self;
1010
1011 resource_fetch_timing_opt.as_ref().map_or((), |t| {
1012 t.set_attribute(ResourceAttribute::RedirectEnd(RedirectEndValue::Zero));
1013 })
1014 }
1015}
1016
1017static REQUEST_BODY_HEADER_NAMES: &[HeaderName] = &[
1019 CONTENT_ENCODING,
1020 CONTENT_LANGUAGE,
1021 CONTENT_LOCATION,
1022 CONTENT_TYPE,
1023];
1024
1025fn location_url_for_response(
1027 response: &Response,
1028 request_fragment: Option<&str>,
1029) -> Option<Result<ServoUrl, String>> {
1030 assert!(
1032 response
1033 .actual_response()
1034 .status
1035 .try_code()
1036 .is_some_and(is_redirect_status)
1037 );
1038 let mut location = response
1040 .actual_response()
1041 .headers
1042 .get(header::LOCATION)
1043 .and_then(|header_value| {
1044 HeaderValue::to_str(header_value)
1045 .map(|location_string| {
1046 ServoUrl::parse_with_base(response.actual_response().url(), location_string)
1048 .map_err(|error| error.to_string())
1049 })
1050 .ok()
1051 });
1052
1053 if let Some(Ok(ref mut location)) = location &&
1055 location.fragment().is_none()
1056 {
1057 location.set_fragment(request_fragment);
1058 }
1059 location
1061}
1062
1063#[async_recursion]
1065pub async fn http_redirect_fetch(
1066 fetch_params: &mut FetchParams,
1067 cache: &mut CorsCache,
1068 mut response: Response,
1069 cors_flag: bool,
1070 target: Target<'async_recursion>,
1071 done_chan: &mut DoneChannel,
1072 context: &FetchContext,
1073) -> Response {
1074 let mut redirect_end_timer = RedirectEndTimer(Some(context.timing.clone()));
1075
1076 let request = &mut fetch_params.request;
1078
1079 assert!(response.return_internal);
1081
1082 let location_url = location_url_for_response(&response, request.current_url().fragment());
1084 response.actual_response_mut().location_url = location_url.clone();
1085
1086 let location_url = match location_url {
1087 None => return response,
1089 Some(Err(err)) => {
1091 return Response::network_error(NetworkError::ResourceLoadError(
1092 "Location URL parse failure: ".to_owned() + &err,
1093 ));
1094 },
1095 Some(Ok(url)) if !matches!(url.scheme(), "http" | "https") => {
1097 return Response::network_error(NetworkError::UnsupportedScheme);
1098 },
1099 Some(Ok(url)) => url,
1100 };
1101
1102 context.timing.set_attributes(&[
1107 ResourceAttribute::RedirectStart(RedirectStartValue::FetchStart),
1108 ResourceAttribute::FetchStart,
1109 ResourceAttribute::StartTime(ResourceTimeValue::FetchStart),
1110 ResourceAttribute::StartTime(ResourceTimeValue::RedirectStart),
1111 ]);
1112
1113 if request.redirect_count >= 20 {
1115 return Response::network_error(NetworkError::TooManyRedirects);
1116 }
1117
1118 request.redirect_count += 1;
1120
1121 let same_origin = match request.origin {
1124 Origin::Origin(ref origin) => *origin == location_url.origin(),
1125 Origin::Client => panic!(
1126 "Request origin should not be client for {}",
1127 request.current_url()
1128 ),
1129 };
1130
1131 let has_credentials = has_credentials(&location_url);
1132
1133 if request.mode == RequestMode::CorsMode && !same_origin && has_credentials {
1134 return Response::network_error(NetworkError::CorsCredentials);
1135 }
1136
1137 if cors_flag && location_url.origin() != request.current_url().origin() {
1138 request.origin = Origin::Origin(ImmutableOrigin::new_opaque());
1139 }
1140
1141 if cors_flag && has_credentials {
1143 return Response::network_error(NetworkError::CorsCredentials);
1144 }
1145
1146 if response.actual_response().status != StatusCode::SEE_OTHER &&
1149 request.body.as_ref().is_some_and(|b| b.source_is_null())
1150 {
1151 return Response::network_error(NetworkError::ConnectionFailure);
1152 }
1153
1154 if response
1156 .actual_response()
1157 .status
1158 .try_code()
1159 .is_some_and(|code| {
1160 ((code == StatusCode::MOVED_PERMANENTLY || code == StatusCode::FOUND) &&
1162 request.method == Method::POST) ||
1163 (code == StatusCode::SEE_OTHER &&
1165 request.method != Method::HEAD &&
1166 request.method != Method::GET)
1167 })
1168 {
1169 request.method = Method::GET;
1171 request.body = None;
1172 for name in REQUEST_BODY_HEADER_NAMES {
1174 request.headers.remove(name);
1175 }
1176 }
1177
1178 if location_url.origin() != request.current_url().origin() {
1182 request.headers.remove(AUTHORIZATION);
1185 }
1186
1187 if let Some(body) = request.body.as_mut() {
1190 body.extract_source();
1191 }
1192
1193 request
1197 .url_list
1198 .push(UrlWithBlobClaim::from_url_without_having_claimed_blob(
1199 location_url,
1200 ));
1201
1202 set_requests_referrer_policy_on_redirect(request, response.actual_response());
1204
1205 let recursive_flag = request.redirect_mode != RedirectMode::Manual;
1208
1209 let fetch_response = main_fetch(
1211 fetch_params,
1212 cache,
1213 recursive_flag,
1214 target,
1215 done_chan,
1216 context,
1217 )
1218 .await;
1219
1220 context.timing.set_attribute(ResourceAttribute::RedirectEnd(
1222 RedirectEndValue::ResponseEnd,
1223 ));
1224 redirect_end_timer.neuter();
1225
1226 fetch_response
1227}
1228
1229#[async_recursion]
1231#[servo_tracing::instrument(skip_all,fields(url=fetch_params.request.url().as_str()))]
1232async fn http_network_or_cache_fetch(
1233 fetch_params: &mut FetchParams,
1234 authentication_fetch_flag: bool,
1235 cors_flag: bool,
1236 done_chan: &mut DoneChannel,
1237 context: &FetchContext,
1238) -> Response {
1239 let http_fetch_params: &mut FetchParams;
1241 let mut fetch_params_copy: FetchParams;
1242
1243 let mut response: Option<Response> = None;
1247
1248 let mut revalidating_flag = false;
1250
1251 let http_request = if fetch_params.request.traversable_for_user_prompts ==
1255 TraversableForUserPrompts::NoTraversable &&
1256 fetch_params.request.redirect_mode == RedirectMode::Error
1257 {
1258 http_fetch_params = fetch_params;
1259 &mut http_fetch_params.request
1260 }
1261 else {
1263 fetch_params_copy =
1266 std::mem::replace(fetch_params, FetchParams::new(fetch_params.request.clone()));
1267 http_fetch_params = &mut fetch_params_copy;
1268
1269 &mut http_fetch_params.request
1270 };
1271
1272 let include_credentials = match http_request.credentials_mode {
1274 CredentialsMode::Include => true,
1276 CredentialsMode::CredentialsSameOrigin
1278 if http_request.response_tainting == ResponseTainting::Basic =>
1279 {
1280 true
1281 },
1282 _ => false,
1283 };
1284
1285 let content_length = http_request
1292 .body
1293 .as_ref()
1294 .and_then(|body| body.len().map(|size| size as u64));
1295
1296 let mut content_length_header_value = None;
1298
1299 if http_request.body.is_none() && matches!(http_request.method, Method::POST | Method::PUT) {
1302 content_length_header_value = Some(0);
1303 }
1304
1305 if let Some(content_length) = content_length {
1309 content_length_header_value = Some(content_length);
1310 };
1311
1312 if let Some(content_length_header_value) = content_length_header_value {
1315 http_request
1316 .headers
1317 .typed_insert(ContentLength(content_length_header_value));
1318 }
1319
1320 if http_request.keep_alive &&
1322 let Some(content_length) = content_length
1323 {
1324 let in_flight_keep_alive_bytes: u64 = context
1329 .in_flight_keep_alive_records
1330 .lock()
1331 .get(
1332 &http_request
1333 .pipeline_id
1334 .expect("Must always set a pipeline ID for keep-alive requests"),
1335 )
1336 .map(|records| {
1337 records
1341 .iter()
1342 .map(|record| {
1343 if record.request_id == http_request.id {
1344 0
1347 } else {
1348 record.keep_alive_body_length
1349 }
1350 })
1351 .sum()
1352 })
1353 .unwrap_or_default();
1354 if content_length + in_flight_keep_alive_bytes > 64 * 1024 {
1356 return Response::network_error(NetworkError::TooManyInFlightKeepAliveRequests);
1357 }
1358 }
1359
1360 match http_request.referrer {
1362 Referrer::ReferrerUrl(ref http_request_referrer) |
1363 Referrer::Client(ref http_request_referrer) => {
1364 if let Ok(referer) = http_request_referrer.as_str().parse::<Referer>() {
1367 http_request.headers.typed_insert(referer);
1369 } else {
1370 error!("Failed to parse {} as referrer", http_request_referrer);
1374 }
1375 },
1376 _ => {},
1377 };
1378
1379 append_a_request_origin_header(http_request);
1381
1382 append_the_fetch_metadata_headers(http_request);
1384
1385 if http_request.initiator == Initiator::Prefetch &&
1388 let Ok(value) = HeaderValue::from_str("prefetch")
1389 {
1390 http_request.headers.insert("Sec-Purpose", value);
1391 }
1392
1393 if !http_request.headers.contains_key(header::USER_AGENT) {
1396 http_request
1397 .headers
1398 .typed_insert::<UserAgent>(context.user_agent.parse().unwrap());
1399 }
1400
1401 append_cache_data_to_headers(http_request);
1403
1404 if http_request.headers.contains_key(header::RANGE) &&
1407 let Ok(value) = HeaderValue::from_str("identity")
1408 {
1409 http_request.headers.insert("Accept-Encoding", value);
1410 }
1411
1412 http_request.headers.remove(header::HOST);
1416 set_default_accept_encoding(&mut http_request.headers);
1418
1419 let current_url = http_request.current_url();
1420
1421 if include_credentials {
1424 set_request_cookies(
1428 ¤t_url,
1429 &mut http_request.headers,
1430 &context.state.cookie_jar,
1431 );
1432 if !http_request.headers.contains_key(header::AUTHORIZATION) {
1434 let mut authorization_value = None;
1436
1437 if let Some(basic) = auth_from_cache(&context.state.auth_cache, ¤t_url.origin()) &&
1439 (!http_request.use_url_credentials || !has_credentials(¤t_url))
1440 {
1441 authorization_value = Some(basic);
1442 }
1443
1444 if authentication_fetch_flag &&
1446 authorization_value.is_none() &&
1447 has_credentials(¤t_url)
1448 {
1449 authorization_value = Some(Authorization::basic(
1450 current_url.username(),
1451 current_url.password().unwrap_or(""),
1452 ));
1453 }
1454
1455 if let Some(basic) = authorization_value {
1457 http_request.headers.typed_insert(basic);
1458 }
1459 }
1460 }
1461
1462 let should_wait = {
1464 let mut cache_guard = block_for_cache_ready(
1466 context,
1467 http_request,
1468 done_chan,
1469 &mut revalidating_flag,
1470 &mut response,
1471 )
1472 .await;
1473
1474 if response.is_none() {
1478 if http_request.cache_mode == CacheMode::OnlyIfCached {
1480 return Response::network_error(NetworkError::CacheError);
1482 }
1483
1484 drop(cache_guard);
1487 let forward_response =
1488 http_network_fetch(http_fetch_params, include_credentials, done_chan, context)
1489 .await;
1490
1491 let http_request = &mut http_fetch_params.request;
1492 let request_key = CacheKey::new(http_request);
1493 cache_guard = context
1494 .state
1495 .http_cache
1496 .get_or_guard(request_key.clone())
1497 .await;
1498 if forward_response.status.in_range(200..=399) && !http_request.method.is_safe() {
1502 if let Some(guard) = cache_guard.try_as_mut() {
1503 invalidate_cached_resources(guard);
1504 }
1505 context
1506 .state
1507 .http_cache
1508 .invalidate_related_urls(http_request, &forward_response, &request_key)
1509 .await;
1510 }
1511
1512 if revalidating_flag && forward_response.status == StatusCode::NOT_MODIFIED {
1514 *done_chan = None;
1517 if let Some(guard) = cache_guard.try_as_mut() {
1518 response = refresh(http_request, forward_response.clone(), done_chan, guard);
1519 }
1520
1521 if let Some(response) = &mut response {
1522 response.cache_state = CacheState::Validated;
1523 }
1524 }
1525
1526 if response.is_none() {
1528 let forward_response = response.insert(forward_response);
1530
1531 if http_request.cache_mode != CacheMode::NoStore {
1534 cache_guard.insert(http_request, forward_response);
1537 }
1538 }
1539 false
1540 } else {
1541 true
1542 }
1543 }; if should_wait {
1546 wait_for_inflight_requests(done_chan, &mut response).await;
1551 }
1552
1553 let http_request = &mut http_fetch_params.request;
1554 let mut response = response.unwrap();
1555
1556 response.url_list = http_request
1558 .url_list
1559 .iter()
1560 .map(|claimed_url| claimed_url.url())
1561 .collect();
1562
1563 if http_request.headers.contains_key(RANGE) {
1565 response.range_requested = true;
1566 }
1567
1568 response.request_includes_credentials = include_credentials;
1570
1571 if response.status.try_code() == Some(StatusCode::UNAUTHORIZED) &&
1577 !cors_flag &&
1578 include_credentials &&
1579 response.headers.contains_key(WWW_AUTHENTICATE)
1580 {
1581 let request = &mut fetch_params.request;
1584
1585 if request.body.is_some() {
1587 }
1589
1590 if !request.use_url_credentials || authentication_fetch_flag {
1592 let Some(credentials) = context
1593 .state
1594 .request_authentication(request, &response)
1595 .await
1596 else {
1597 return response;
1598 };
1599
1600 if let Err(err) = request
1601 .current_url_mut()
1602 .set_username(&credentials.username)
1603 {
1604 error!("error setting username for url: {:?}", err);
1605 return response;
1606 };
1607
1608 if let Err(err) = request
1609 .current_url_mut()
1610 .set_password(Some(&credentials.password))
1611 {
1612 error!("error setting password for url: {:?}", err);
1613 return response;
1614 };
1615 }
1616
1617 *done_chan = None;
1620
1621 response = http_network_or_cache_fetch(
1623 fetch_params,
1624 true, cors_flag,
1626 done_chan,
1627 context,
1628 )
1629 .await;
1630 }
1631
1632 if response.status == StatusCode::PROXY_AUTHENTICATION_REQUIRED {
1634 let request = &mut fetch_params.request;
1635 if request.traversable_for_user_prompts == TraversableForUserPrompts::NoTraversable {
1638 return Response::network_error(NetworkError::ResourceLoadError(
1639 "Can't find Window object".into(),
1640 ));
1641 }
1642
1643 let Some(credentials) = context
1651 .state
1652 .request_authentication(request, &response)
1653 .await
1654 else {
1655 return response;
1656 };
1657
1658 let entry = AuthCacheEntry {
1660 user_name: credentials.username,
1661 password: credentials.password,
1662 };
1663 {
1664 let mut auth_cache = context.state.auth_cache.write();
1665 let key = request.current_url().origin().ascii_serialization();
1666 auth_cache.entries.insert(key, entry);
1667 }
1668
1669 *done_chan = None;
1672
1673 response = http_network_or_cache_fetch(
1675 fetch_params,
1676 false, cors_flag,
1678 done_chan,
1679 context,
1680 )
1681 .await;
1682 }
1683
1684 if authentication_fetch_flag {
1692 }
1694
1695 response
1697}
1698
1699#[servo_tracing::instrument(skip_all)]
1707async fn block_for_cache_ready<'a>(
1708 context: &'a FetchContext,
1709 http_request: &mut Request,
1710 done_chan: &mut DoneChannel,
1711 revalidating_flag: &mut bool,
1712 response: &mut Option<Response>,
1713) -> CachedResourcesOrGuard<'a> {
1714 let entry_key = CacheKey::new(http_request);
1715 let guard_result = context.state.http_cache.get_or_guard(entry_key).await;
1716
1717 match guard_result {
1718 CachedResourcesOrGuard::Guard(_) => {
1719 *done_chan = None;
1720 },
1721 CachedResourcesOrGuard::Value(ref cached_resources) => {
1722 let stored_response = construct_response(http_request, done_chan, cached_resources);
1728 if let Some(response_from_cache) = stored_response {
1730 let response_headers = response_from_cache.response.headers.clone();
1731 let (cached_response, needs_revalidation) =
1733 match (http_request.cache_mode, &http_request.mode) {
1734 (CacheMode::ForceCache, _) => (Some(response_from_cache.response), false),
1735 (CacheMode::OnlyIfCached, &RequestMode::SameOrigin) => {
1736 (Some(response_from_cache.response), false)
1737 },
1738 (CacheMode::OnlyIfCached, _) |
1739 (CacheMode::NoStore, _) |
1740 (CacheMode::Reload, _) => (None, false),
1741 (_, _) => (
1742 Some(response_from_cache.response),
1743 response_from_cache.needs_validation,
1744 ),
1745 };
1746
1747 if needs_revalidation {
1748 *revalidating_flag = true;
1749 if let Some(http_date) = response_headers.typed_get::<LastModified>() {
1751 let http_date: SystemTime = http_date.into();
1752 http_request
1753 .headers
1754 .typed_insert(IfModifiedSince::from(http_date));
1755 }
1756 if let Some(entity_tag) = response_headers.get(header::ETAG) {
1757 http_request
1758 .headers
1759 .insert(header::IF_NONE_MATCH, entity_tag.clone());
1760 }
1761 } else {
1762 *response = cached_response;
1764 if let Some(response) = response {
1765 response.cache_state = CacheState::Local;
1766 }
1767 }
1768 if response.is_none() {
1769 *done_chan = None;
1772 }
1773 }
1774 },
1775 }
1776 guard_result
1777}
1778
1779async fn wait_for_inflight_requests(done_chan: &mut DoneChannel, response: &mut Option<Response>) {
1782 if let Some(ref mut ch) = *done_chan {
1783 assert!(response.is_some());
1787
1788 loop {
1789 match ch.1.recv().await {
1790 Some(Data::Payload(_)) => {},
1791 Some(Data::Done) => break, Some(Data::Cancelled) => {
1793 break;
1795 },
1796 _ => panic!("HTTP cache should always send Done or Cancelled"),
1797 }
1798 }
1799 }
1800 *done_chan = None;
1802}
1803
1804#[derive(PartialEq)]
1808enum CrossOriginResourcePolicy {
1809 Allowed,
1810 Blocked,
1811}
1812
1813enum ForNavigation {
1814 #[expect(dead_code)]
1815 Yes,
1816 No,
1817}
1818
1819fn cross_origin_resource_policy_check(
1821 origin: &Origin,
1822 request_client: &RequestClient,
1823 response: &Response,
1824 for_navigation: ForNavigation,
1825) -> CrossOriginResourcePolicy {
1826 let RequestPolicyContainer::PolicyContainer(ref policy_container) =
1832 request_client.policy_container
1833 else {
1834 return CrossOriginResourcePolicy::Blocked;
1835 };
1836
1837 let embedder_policy = &policy_container.embedder_policy;
1838
1839 if cross_origin_resource_policy_internal_check(
1842 origin,
1843 EmbedderPolicyValue::UnsafeNone,
1844 response,
1845 &for_navigation,
1846 ) == CrossOriginResourcePolicy::Blocked
1847 {
1848 return CrossOriginResourcePolicy::Blocked;
1849 }
1850
1851 if cross_origin_resource_policy_internal_check(
1859 origin,
1860 embedder_policy.value,
1861 response,
1862 &for_navigation,
1863 ) == CrossOriginResourcePolicy::Allowed
1864 {
1865 return CrossOriginResourcePolicy::Allowed;
1866 }
1867
1868 CrossOriginResourcePolicy::Blocked
1873}
1874
1875fn cross_origin_resource_policy_internal_check(
1877 origin: &Origin,
1878 embedder_policy_value: EmbedderPolicyValue,
1879 response: &Response,
1880 for_navigation: &ForNavigation,
1881) -> CrossOriginResourcePolicy {
1882 if let ForNavigation::Yes = for_navigation &&
1884 let EmbedderPolicyValue::UnsafeNone = embedder_policy_value
1885 {
1886 return CrossOriginResourcePolicy::Allowed;
1887 }
1888
1889 let policy = response
1891 .headers
1892 .get(HeaderName::from_static("cross-origin-resource-policy"))
1893 .and_then(|h| h.to_str().ok());
1894
1895 let policy = policy
1897 .filter(|&s| s == "same-origin" || s == "same-site" || s == "cross-origin")
1898 .or(match embedder_policy_value {
1900 EmbedderPolicyValue::UnsafeNone => None,
1902 EmbedderPolicyValue::RequireCorp => Some("same-origin"),
1904 });
1905
1906 match policy {
1908 Some("same-origin") => {
1909 if let Origin::Origin(request_origin) = origin &&
1911 response
1912 .url()
1913 .is_some_and(|url| request_origin == &url.origin())
1914 {
1915 return CrossOriginResourcePolicy::Allowed;
1916 }
1917
1918 CrossOriginResourcePolicy::Blocked
1920 },
1921 Some("same-site") => {
1922 if let Some(response_url) = response.url() {
1923 if let Origin::Origin(request_origin) = origin &&
1927 is_schemelessy_same_site(request_origin, &response_url.origin()) &&
1928 (request_origin.scheme() == Some("https") ||
1929 response_url.scheme() != "https")
1930 {
1931 return CrossOriginResourcePolicy::Allowed;
1932 }
1933 }
1934 CrossOriginResourcePolicy::Blocked
1936 },
1937 _ => CrossOriginResourcePolicy::Allowed,
1940 }
1941}
1942
1943struct ResponseEndTimer(Option<ResourceFetchTimingContainer>);
1945
1946impl ResponseEndTimer {
1947 fn neuter(&mut self) {
1948 self.0 = None;
1949 }
1950}
1951
1952impl Drop for ResponseEndTimer {
1953 fn drop(&mut self) {
1954 let ResponseEndTimer(resource_fetch_timing_opt) = self;
1955
1956 resource_fetch_timing_opt.as_ref().map_or((), |t| {
1957 t.set_attribute(ResourceAttribute::ResponseEnd);
1958 })
1959 }
1960}
1961
1962#[servo_tracing::instrument(skip_all,fields(url=fetch_params.request.url().as_str()))]
1964async fn http_network_fetch(
1965 fetch_params: &mut FetchParams,
1966 credentials_flag: bool,
1967 done_chan: &mut DoneChannel,
1968 context: &FetchContext,
1969) -> Response {
1970 let mut response_end_timer = ResponseEndTimer(Some(context.timing.clone()));
1971
1972 let request = &mut fetch_params.request;
1974
1975 let url = request.current_url();
1986 let request_id = request.id.0.to_string();
1987 if log_enabled!(log::Level::Info) {
1988 info!("{:?} request for {}", request.method, url);
1989 for header in request.headers.iter() {
1990 debug!(" - {:?}", header);
1991 }
1992 }
1993
1994 let is_xhr = request.destination == Destination::None;
1998
1999 let (fetch_terminated_sender, mut fetch_terminated_receiver) = unbounded_channel();
2001
2002 let body = request.body.as_ref().map(|body| body.clone_stream());
2003
2004 if body.is_none() {
2005 let _ = fetch_terminated_sender.send(false);
2010 }
2011
2012 let browsing_context_id = request.target_webview_id.map(Into::into);
2013
2014 let (res, msg) = match &request.mode {
2015 RequestMode::WebSocket {
2016 protocols,
2017 original_url: _,
2018 } => {
2019 let (resource_event_sender, dom_action_receiver) = {
2022 let mut websocket_chan = context.websocket_chan.as_ref().unwrap().lock();
2023 (
2024 websocket_chan.sender.clone(),
2025 websocket_chan.receiver.take().unwrap(),
2026 )
2027 };
2028
2029 let mut tls_config = create_tls_config(
2030 context.ca_certificates.clone(),
2031 context.ignore_certificate_errors,
2032 context.state.override_manager.clone(),
2033 );
2034 tls_config.alpn_protocols = vec!["http/1.1".to_string().into()];
2035
2036 let response = match start_websocket(
2037 context.state.clone(),
2038 resource_event_sender,
2039 protocols,
2040 request,
2041 tls_config,
2042 dom_action_receiver,
2043 )
2044 .await
2045 {
2046 Ok(response) => response,
2047 Err(error) => {
2048 return Response::network_error(NetworkError::WebsocketConnectionFailure(
2049 format!("{error:?}"),
2050 ));
2051 },
2052 };
2053
2054 let response = response.map(|r| match r {
2055 Some(body) => Full::from(body).map_err(|_| unreachable!()).boxed(),
2056 None => http_body_util::Empty::new()
2057 .map_err(|_| unreachable!())
2058 .boxed(),
2059 });
2060 (Decoder::detect(response, url.is_secure_scheme()), None)
2061 },
2062 _ => {
2063 let response_future = obtain_response(
2064 &context.state.client,
2065 &url,
2066 &request.method,
2067 &mut request.headers,
2068 body,
2069 request
2070 .body
2071 .as_ref()
2072 .is_some_and(|body| body.source_is_null()),
2073 &request.pipeline_id,
2074 Some(&request_id),
2075 request.destination,
2076 is_xhr,
2077 context,
2078 fetch_terminated_sender,
2079 browsing_context_id,
2080 );
2081
2082 let (res, msg) = match response_future.await {
2084 Ok(wrapped_response) => wrapped_response,
2085 Err(error) => return Response::network_error(error),
2086 };
2087 (res, msg)
2088 },
2089 };
2090
2091 if log_enabled!(log::Level::Info) {
2092 debug!("{:?} response for {}", res.version(), url);
2093 for header in res.headers().iter() {
2094 debug!(" - {:?}", header);
2095 }
2096 }
2097
2098 match fetch_terminated_receiver.recv().await {
2101 Some(true) => return Response::network_error(NetworkError::ConnectionFailure),
2102 Some(false) => {},
2103 _ => warn!("Failed to receive confirmation request was streamed without error."),
2104 }
2105
2106 let header_strings: Vec<&str> = res
2107 .headers()
2108 .get_all("Timing-Allow-Origin")
2109 .iter()
2110 .map(|header_value| header_value.to_str().unwrap_or(""))
2111 .collect();
2112 let wildcard_present = header_strings.contains(&"*");
2113 let req_origin_in_timing_allow = header_strings
2117 .iter()
2118 .any(|header_str| match request.origin {
2119 SpecificOrigin(ref immutable_request_origin) => {
2120 *header_str == immutable_request_origin.ascii_serialization()
2121 },
2122 _ => false,
2123 });
2124
2125 let is_same_origin = request.url_list.iter().all(|url| match request.origin {
2126 SpecificOrigin(ref immutable_request_origin) => url.origin() == *immutable_request_origin,
2127 _ => false,
2128 });
2129
2130 if !(is_same_origin || req_origin_in_timing_allow || wildcard_present) {
2131 context.timing.inner().mark_timing_check_failed();
2132 }
2133
2134 let timing = context.timing.inner().clone();
2135 let mut response = Response::new(url.clone(), timing);
2136
2137 if let Some(handshake_info) = res.extensions().get::<TlsHandshakeInfo>() {
2138 let mut hsts_enabled = url
2139 .host_str()
2140 .is_some_and(|host| context.state.hsts_list.read().is_host_secure(host));
2141
2142 if url.scheme() == "https" &&
2143 let Some(sts) = res.headers().typed_get::<StrictTransportSecurity>()
2144 {
2145 hsts_enabled = sts.max_age().as_secs() > 0;
2147 }
2148 response.tls_security_info = Some(build_tls_security_info(handshake_info, hsts_enabled));
2149 }
2150
2151 let status_text = res
2152 .extensions()
2153 .get::<ReasonPhrase>()
2154 .map(ReasonPhrase::as_bytes)
2155 .or_else(|| res.status().canonical_reason().map(str::as_bytes))
2156 .map(Vec::from)
2157 .unwrap_or_default();
2158 response.status = HttpStatus::new(res.status(), status_text);
2159
2160 info!("got {:?} response for {:?}", res.status(), request.url());
2161 response.headers = res.headers().clone();
2162 response.referrer = request.referrer.to_url().cloned();
2163 response.referrer_policy = request.referrer_policy;
2164
2165 let res_body = response.body.clone();
2166
2167 let (done_sender, done_receiver) = unbounded_channel();
2169 *done_chan = Some((done_sender.clone(), done_receiver));
2170
2171 let devtools_sender = context.devtools_chan.clone();
2172 let cancellation_listener = context.cancellation_listener.clone();
2173 if cancellation_listener.cancelled() {
2174 return Response::network_error(NetworkError::LoadCancelled);
2175 }
2176
2177 *res_body.lock() = ResponseBody::Receiving(vec![]);
2178 let res_body2 = res_body.clone();
2179
2180 if let Some(ref sender) = devtools_sender &&
2181 let Some(m) = msg
2182 {
2183 send_request_to_devtools(m, sender);
2184 }
2185
2186 let done_sender2 = done_sender.clone();
2187 let done_sender3 = done_sender.clone();
2188 let timing_ptr2 = context.timing.clone();
2189 let timing_ptr3 = context.timing.clone();
2190 let devtools_request = request.clone();
2191 let url1 = devtools_request.url();
2192 let url2 = url1.clone();
2193
2194 let status = response.status.clone();
2195 let headers = response.headers.clone();
2196 let devtools_chan = context.devtools_chan.clone();
2197
2198 spawn_task(
2199 res.into_body()
2200 .try_fold(res_body, move |res_body, chunk| {
2201 if cancellation_listener.cancelled() {
2202 *res_body.lock() = ResponseBody::Done(vec![]);
2203 let _ = done_sender.send(Data::Cancelled);
2204 return future::ready(Err(std::io::Error::new(
2205 std::io::ErrorKind::Interrupted,
2206 "Fetch aborted",
2207 )));
2208 }
2209 if let ResponseBody::Receiving(ref mut body) = *res_body.lock() {
2210 let bytes = chunk;
2211 body.extend_from_slice(&bytes);
2212 let _ = done_sender.send(Data::Payload(bytes.to_vec()));
2213 }
2214 future::ready(Ok(res_body))
2215 })
2216 .and_then(move |res_body| {
2217 debug!("successfully finished response for {:?}", url1);
2218 let mut body = res_body.lock();
2219 let completed_body = match *body {
2220 ResponseBody::Receiving(ref mut body) => std::mem::take(body),
2221 _ => vec![],
2222 };
2223 let devtools_response_body = completed_body.clone();
2224 *body = ResponseBody::Done(completed_body);
2225 send_response_values_to_devtools(
2226 Some(headers),
2227 status,
2228 Some(devtools_response_body),
2229 CacheState::None,
2230 &devtools_request,
2231 devtools_chan,
2232 );
2233 timing_ptr2.set_attribute(ResourceAttribute::ResponseEnd);
2234 let _ = done_sender2.send(Data::Done);
2235 future::ready(Ok(()))
2236 })
2237 .map_err(move |error| {
2238 if let std::io::ErrorKind::InvalidData = error.kind() {
2239 debug!("Content decompression error for {:?}", url2);
2240 let _ = done_sender3.send(Data::Error(NetworkError::DecompressionError));
2241 let mut body = res_body2.lock();
2242
2243 *body = ResponseBody::Done(vec![]);
2244 }
2245 debug!("finished response for {:?}", url2);
2246 let mut body = res_body2.lock();
2247 let completed_body = match *body {
2248 ResponseBody::Receiving(ref mut body) => std::mem::take(body),
2249 _ => vec![],
2250 };
2251 *body = ResponseBody::Done(completed_body);
2252 timing_ptr3.set_attribute(ResourceAttribute::ResponseEnd);
2253 let _ = done_sender3.send(Data::Done);
2254 }),
2255 );
2256
2257 if credentials_flag {
2275 set_cookies_from_headers(&url, &response.headers, &context.state.cookie_jar);
2276 }
2277 context
2278 .state
2279 .hsts_list
2280 .write()
2281 .update_hsts_list_from_response(&url, &response.headers);
2282
2283 response_end_timer.neuter();
2297 response
2298}
2299
2300async fn cors_preflight_fetch(
2302 request: &Request,
2303 cache: &mut CorsCache,
2304 context: &FetchContext,
2305) -> Response {
2306 let mut preflight = RequestBuilder::new(
2311 request.target_webview_id,
2312 request.current_url_with_blob_claim(),
2313 request.referrer.clone(),
2314 )
2315 .method(Method::OPTIONS)
2316 .origin(match &request.origin {
2317 Origin::Client => {
2318 unreachable!("We shouldn't get Client origin in cors_preflight_fetch.")
2319 },
2320 Origin::Origin(origin) => origin.clone(),
2321 })
2322 .pipeline_id(request.pipeline_id)
2323 .initiator(request.initiator)
2324 .destination(request.destination)
2325 .referrer_policy(request.referrer_policy)
2326 .mode(RequestMode::CorsMode)
2327 .response_tainting(ResponseTainting::CorsTainting)
2328 .policy_container(match &request.policy_container {
2329 RequestPolicyContainer::Client => {
2330 unreachable!("We should have a policy container for request in cors_preflight_fetch")
2331 },
2332 RequestPolicyContainer::PolicyContainer(policy_container) => policy_container.clone(),
2333 })
2334 .url_list(
2335 request
2336 .url_list
2337 .iter()
2338 .map(|claimed_url| claimed_url.url())
2339 .collect(),
2340 )
2341 .build();
2342
2343 preflight
2345 .headers
2346 .insert(ACCEPT, HeaderValue::from_static("*/*"));
2347
2348 preflight
2350 .headers
2351 .typed_insert::<AccessControlRequestMethod>(AccessControlRequestMethod::from(
2352 request.method.clone(),
2353 ));
2354
2355 let headers = get_cors_unsafe_header_names(&request.headers);
2357
2358 if !headers.is_empty() {
2360 preflight.headers.insert(
2363 ACCESS_CONTROL_REQUEST_HEADERS,
2364 HeaderValue::from_bytes(itertools::join(headers.iter(), ",").as_bytes())
2365 .unwrap_or(HeaderValue::from_static("")),
2366 );
2367 }
2368
2369 let mut fetch_params = FetchParams::new(preflight);
2372 let response =
2373 http_network_or_cache_fetch(&mut fetch_params, false, false, &mut None, context).await;
2374
2375 if cors_check(request, &response).is_ok() && response.status.code().is_success() {
2377 let mut methods = if response
2380 .headers
2381 .contains_key(header::ACCESS_CONTROL_ALLOW_METHODS)
2382 {
2383 match response.headers.typed_get::<AccessControlAllowMethods>() {
2384 Some(methods) => methods.iter().collect(),
2385 None => {
2387 return Response::network_error(NetworkError::CorsAllowMethods);
2388 },
2389 }
2390 } else {
2391 vec![]
2392 };
2393
2394 let header_names = if response
2397 .headers
2398 .contains_key(header::ACCESS_CONTROL_ALLOW_HEADERS)
2399 {
2400 match response.headers.typed_get::<AccessControlAllowHeaders>() {
2401 Some(names) => names.iter().collect(),
2402 None => {
2404 return Response::network_error(NetworkError::CorsAllowHeaders);
2405 },
2406 }
2407 } else {
2408 vec![]
2409 };
2410
2411 debug!(
2412 "CORS check: Allowed methods: {:?}, current method: {:?}",
2413 methods, request.method
2414 );
2415
2416 if methods.is_empty() && request.use_cors_preflight {
2419 methods = vec![request.method.clone()];
2420 }
2421
2422 if methods
2425 .iter()
2426 .all(|method| *method.as_str() != *request.method.as_ref()) &&
2427 !is_cors_safelisted_method(&request.method) &&
2428 (request.credentials_mode == CredentialsMode::Include ||
2429 methods.iter().all(|method| method.as_ref() != "*"))
2430 {
2431 return Response::network_error(NetworkError::CorsMethod);
2432 }
2433
2434 debug!(
2435 "CORS check: Allowed headers: {:?}, current headers: {:?}",
2436 header_names, request.headers
2437 );
2438
2439 if request.headers.iter().any(|(name, _)| {
2442 is_cors_non_wildcard_request_header_name(name) &&
2443 header_names.iter().all(|header_name| header_name != name)
2444 }) {
2445 return Response::network_error(NetworkError::CorsAuthorization);
2446 }
2447
2448 let unsafe_names = get_cors_unsafe_header_names(&request.headers);
2452 let header_names_set: HashSet<&HeaderName> = HashSet::from_iter(header_names.iter());
2453 let header_names_contains_star = header_names
2454 .iter()
2455 .any(|header_name| header_name.as_str() == "*");
2456 for unsafe_name in unsafe_names.iter() {
2457 if !header_names_set.contains(unsafe_name) &&
2458 (request.credentials_mode == CredentialsMode::Include ||
2459 !header_names_contains_star)
2460 {
2461 return Response::network_error(NetworkError::CorsHeaders);
2462 }
2463 }
2464
2465 let max_age: Option<Duration> = response
2468 .headers
2469 .typed_get::<AccessControlMaxAge>()
2470 .map(|acma| acma.into());
2471
2472 let max_age = max_age.unwrap_or(Duration::from_secs(5));
2474
2475 for method in &methods {
2486 cache.match_method_and_update(request, method.clone(), max_age);
2487 }
2488
2489 for header_name in &header_names {
2494 cache.match_header_and_update(request, header_name, max_age);
2495 }
2496
2497 return response;
2499 }
2500
2501 Response::network_error(NetworkError::CorsGeneral)
2503}
2504
2505fn cors_check(request: &Request, response: &Response) -> Result<(), ()> {
2507 let Some(origins) =
2509 get_value_from_header_list(ACCESS_CONTROL_ALLOW_ORIGIN.as_str(), &response.headers)
2510 else {
2511 return Err(());
2513 };
2514 let origin = origins.into_iter().map(char::from).collect::<String>();
2515
2516 if request.credentials_mode != CredentialsMode::Include && origin == "*" {
2518 return Ok(());
2519 }
2520
2521 if serialize_request_origin(request).to_string() != origin {
2523 return Err(());
2524 }
2525
2526 if request.credentials_mode != CredentialsMode::Include {
2528 return Ok(());
2529 }
2530
2531 let credentials = response
2533 .headers
2534 .typed_get::<AccessControlAllowCredentials>();
2535
2536 if credentials.is_some() {
2538 return Ok(());
2539 }
2540
2541 Err(())
2543}
2544
2545fn has_credentials(url: &ServoUrl) -> bool {
2546 !url.username().is_empty() || url.password().is_some()
2547}
2548
2549fn is_no_store_cache(headers: &HeaderMap) -> bool {
2550 headers.contains_key(header::IF_MODIFIED_SINCE) |
2551 headers.contains_key(header::IF_NONE_MATCH) |
2552 headers.contains_key(header::IF_UNMODIFIED_SINCE) |
2553 headers.contains_key(header::IF_MATCH) |
2554 headers.contains_key(header::IF_RANGE)
2555}
2556
2557fn is_redirect_status(status: StatusCode) -> bool {
2559 matches!(
2560 status,
2561 StatusCode::MOVED_PERMANENTLY |
2562 StatusCode::FOUND |
2563 StatusCode::SEE_OTHER |
2564 StatusCode::TEMPORARY_REDIRECT |
2565 StatusCode::PERMANENT_REDIRECT
2566 )
2567}
2568
2569fn serialize_request_origin(request: &Request) -> headers::Origin {
2571 let Origin::Origin(origin) = &request.origin else {
2573 panic!("origin cannot be \"client\" at this point in time");
2574 };
2575
2576 if request.redirect_taint_for_request() != RedirectTaint::SameOrigin {
2578 return headers::Origin::NULL;
2579 }
2580
2581 serialize_origin(origin)
2583}
2584
2585pub fn serialize_origin(origin: &ImmutableOrigin) -> headers::Origin {
2587 match origin {
2588 ImmutableOrigin::Opaque(_) => headers::Origin::NULL,
2589 ImmutableOrigin::Tuple(scheme, host, port) => {
2590 let port = match (scheme.as_ref(), port) {
2593 ("http" | "ws", 80) | ("https" | "wss", 443) | ("ftp", 21) => None,
2594 _ => Some(*port),
2595 };
2596
2597 headers::Origin::try_from_parts(scheme, &host.to_string(), port)
2599 .unwrap_or(headers::Origin::NULL)
2600 },
2601 }
2602}
2603
2604#[expect(
2606 clippy::collapsible_match,
2607 reason = "The current way follows the spec more closely"
2608)]
2609fn append_a_request_origin_header(request: &mut Request) {
2610 let Origin::Origin(request_origin) = &request.origin else {
2612 panic!("origin cannot be \"client\" at this point in time");
2613 };
2614
2615 let mut serialized_origin = serialize_request_origin(request);
2617
2618 if request.response_tainting == ResponseTainting::CorsTainting ||
2621 matches!(request.mode, RequestMode::WebSocket { .. })
2622 {
2623 request.headers.typed_insert(serialized_origin);
2624 }
2625 else if !matches!(request.method, Method::GET | Method::HEAD) {
2627 if request.mode != RequestMode::CorsMode {
2629 match request.referrer_policy {
2630 ReferrerPolicy::NoReferrer => {
2631 serialized_origin = headers::Origin::NULL;
2633 },
2634 ReferrerPolicy::NoReferrerWhenDowngrade |
2635 ReferrerPolicy::StrictOrigin |
2636 ReferrerPolicy::StrictOriginWhenCrossOrigin => {
2637 if let ImmutableOrigin::Tuple(scheme, _, _) = &request_origin &&
2640 scheme == "https" &&
2641 request.current_url().scheme() != "https"
2642 {
2643 serialized_origin = headers::Origin::NULL;
2644 }
2645 },
2646 ReferrerPolicy::SameOrigin => {
2647 if *request_origin != request.current_url().origin() {
2650 serialized_origin = headers::Origin::NULL;
2651 }
2652 },
2653 _ => {
2654 },
2656 };
2657 }
2658
2659 request.headers.typed_insert(serialized_origin);
2661 }
2662}
2663
2664fn append_the_fetch_metadata_headers(r: &mut Request) {
2666 if !r.url().is_potentially_trustworthy() {
2668 return;
2669 }
2670
2671 set_the_sec_fetch_dest_header(r);
2673
2674 set_the_sec_fetch_mode_header(r);
2676
2677 set_the_sec_fetch_site_header(r);
2679
2680 set_the_sec_fetch_user_header(r);
2682}
2683
2684fn append_cache_data_to_headers(http_request: &mut Request) {
2686 match http_request.cache_mode {
2687 CacheMode::Default if is_no_store_cache(&http_request.headers) => {
2691 http_request.cache_mode = CacheMode::NoStore;
2692 },
2693
2694 CacheMode::NoCache if !http_request.headers.contains_key(header::CACHE_CONTROL) => {
2704 http_request
2705 .headers
2706 .typed_insert(CacheControl::new().with_max_age(Duration::from_secs(0)));
2707 },
2708
2709 CacheMode::Reload | CacheMode::NoStore => {
2711 if !http_request.headers.contains_key(header::PRAGMA) {
2714 http_request.headers.typed_insert(Pragma::no_cache());
2715 }
2716
2717 if !http_request.headers.contains_key(header::CACHE_CONTROL) {
2720 http_request
2721 .headers
2722 .typed_insert(CacheControl::new().with_no_cache());
2723 }
2724 },
2725
2726 _ => {},
2727 }
2728}
2729
2730fn set_the_sec_fetch_dest_header(r: &mut Request) {
2732 debug_assert!(r.url().is_potentially_trustworthy());
2734
2735 let header = r.destination;
2739
2740 r.headers.typed_insert(SecFetchDest(header));
2742}
2743
2744fn set_the_sec_fetch_mode_header(r: &mut Request) {
2746 debug_assert!(r.url().is_potentially_trustworthy());
2748
2749 let header = &r.mode;
2752
2753 r.headers.typed_insert(SecFetchMode::from(header));
2755}
2756
2757fn set_the_sec_fetch_site_header(r: &mut Request) {
2759 let Origin::Origin(request_origin) = &r.origin else {
2762 panic!("request origin cannot be \"client\" at this point")
2763 };
2764
2765 debug_assert!(r.url().is_potentially_trustworthy());
2767
2768 let mut header = SecFetchSite::SameOrigin;
2771
2772 if header != SecFetchSite::None {
2777 for url in &r.url_list {
2778 if url.origin() == *request_origin {
2780 continue;
2781 }
2782
2783 header = SecFetchSite::CrossSite;
2785
2786 if !is_same_site(request_origin, &url.origin()) {
2788 break;
2789 }
2790
2791 header = SecFetchSite::SameSite;
2793 }
2794 }
2795
2796 r.headers.typed_insert(header);
2798}
2799
2800fn set_the_sec_fetch_user_header(r: &mut Request) {
2802 debug_assert!(r.url().is_potentially_trustworthy());
2804
2805 if !r.is_navigation_request() {
2808 return;
2809 }
2810
2811 let header = SecFetchUser;
2814
2815 r.headers.typed_insert(header);
2817}
2818
2819fn set_requests_referrer_policy_on_redirect(request: &mut Request, response: &Response) {
2821 let referrer_policy: ReferrerPolicy = response
2824 .headers
2825 .typed_get::<headers::ReferrerPolicy>()
2826 .into();
2827
2828 if referrer_policy != ReferrerPolicy::EmptyString {
2830 request.referrer_policy = referrer_policy;
2831 }
2832}