1use std::collections::HashSet;
6use std::iter::FromIterator;
7use std::sync::Arc as StdArc;
8use std::sync::atomic::{AtomicBool, Ordering};
9use std::time::{Duration, SystemTime};
10
11use async_recursion::async_recursion;
12use content_security_policy::percent_encoding::{AsciiSet, CONTROLS, utf8_percent_encode};
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, DiscardFetch, NetworkError, RedirectEndValue,
54 RedirectStartValue, ReferrerPolicy, ResourceAttribute, ResourceFetchTimingContainer,
55 ResourceTimeValue, ServoCipherSuite, ServoNamedGroup, ServoProtocolVersion, TlsSecurityInfo,
56 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_config::pref;
68use servo_url::{ImmutableOrigin, ServoUrl};
69use tokio::sync::mpsc::{
70 Receiver as TokioReceiver, Sender as TokioSender, UnboundedReceiver, UnboundedSender, channel,
71 unbounded_channel,
72};
73use tokio_stream::wrappers::ReceiverStream;
74#[cfg(feature = "tracing")]
75use tracing::Instrument;
76use uuid::Uuid;
77
78use crate::async_runtime::spawn_task;
79use crate::connector::{
80 CertificateErrorOverrideManager, ServoClient, TlsHandshakeInfo, create_tls_config,
81};
82use crate::cookie::ServoCookie;
83use crate::cookie_storage::CookieStorage;
84use crate::decoder::Decoder;
85#[cfg(feature = "devtools")]
86use crate::devtools::{
87 prepare_devtools_request, send_request_to_devtools, send_response_values_to_devtools,
88};
89use crate::embedder::NetToEmbedderMsg;
90use crate::fetch::cors_cache::CorsCache;
91use crate::fetch::fetch_params::FetchParams;
92use crate::fetch::headers::{SecFetchDest, SecFetchMode, SecFetchSite, SecFetchUser};
93use crate::fetch::methods::{Data, DoneChannel, FetchContext, Target, fetch, main_fetch};
94use crate::hsts::HstsList;
95use crate::http_cache::{
96 CacheKey, CachedResourcesOrGuard, HttpCache, ValidationStatus, construct_response,
97 invalidate_cached_resources, refresh,
98};
99use crate::resource_thread::{AuthCache, AuthCacheEntry};
100use crate::websocket_loader::start_websocket;
101
102#[cfg(feature = "devtools")]
103type DevtoolsResponse = Option<devtools_traits::ChromeToDevtoolsControlMsg>;
104
105#[cfg(not(feature = "devtools"))]
106type DevtoolsResponse = Option<()>;
107
108#[derive(Clone, Debug, Eq, PartialEq)]
110pub enum HttpCacheEntryState {
111 ReadyToConstruct,
115 PendingStore(usize),
117}
118
119pub struct HttpState {
120 pub hsts_list: RwLock<HstsList>,
121 pub cookie_jar: RwLock<CookieStorage>,
122 pub http_cache: HttpCache,
123 pub auth_cache: RwLock<AuthCache>,
124 pub history_states: RwLock<FxHashMap<HistoryStateId, Vec<u8>>>,
125 pub client: ServoClient,
126 pub override_manager: CertificateErrorOverrideManager,
127 pub embedder_proxy: GenericEmbedderProxy<NetToEmbedderMsg>,
128}
129
130impl HttpState {
131 pub(crate) fn memory_reports(&self, suffix: &str, ops: &mut MallocSizeOfOps) -> Vec<Report> {
132 vec![
133 Report {
134 path: path!["memory-cache", suffix],
135 kind: ReportKind::ExplicitJemallocHeapSize,
136 size: self.http_cache.size_of(ops),
137 },
138 Report {
139 path: path!["hsts-list", suffix],
140 kind: ReportKind::ExplicitJemallocHeapSize,
141 size: self.hsts_list.read().size_of(ops),
142 },
143 Report {
144 path: path!["auth cache", suffix],
145 kind: ReportKind::ExplicitJemallocHeapSize,
146 size: self.auth_cache.read().size_of(ops),
147 },
148 Report {
149 path: path!["cookie storage", suffix],
150 kind: ReportKind::ExplicitJemallocHeapSize,
151 size: self.cookie_jar.read().size_of(ops),
152 },
153 ]
154 }
155
156 async fn request_authentication(
157 &self,
158 request: &Request,
159 response: &Response,
160 ) -> Option<AuthenticationResponse> {
161 let webview_id = request.target_webview_id?;
163 let for_proxy = response.status == StatusCode::PROXY_AUTHENTICATION_REQUIRED;
164
165 if request.mode != RequestMode::Navigate {
167 return None;
168 }
169
170 let (sender, receiver) = tokio::sync::oneshot::channel();
171 self.embedder_proxy
172 .send(NetToEmbedderMsg::RequestAuthentication(
173 webview_id,
174 request.url(),
175 for_proxy,
176 sender,
177 ));
178 receiver.await.ok()?
179 }
180}
181
182pub(crate) fn set_default_accept(request: &mut Request) {
184 if request.headers.contains_key(header::ACCEPT) {
186 return;
187 }
188
189 let value = if request.initiator == Initiator::Prefetch {
191 DOCUMENT_ACCEPT_HEADER_VALUE
192 } else {
193 match request.destination {
196 Destination::Document | Destination::Frame | Destination::IFrame => {
197 DOCUMENT_ACCEPT_HEADER_VALUE
198 },
199 Destination::Image => {
200 HeaderValue::from_static("image/png,image/svg+xml,image/*;q=0.8,*/*;q=0.5")
201 },
202 Destination::Json => HeaderValue::from_static("application/json,*/*;q=0.5"),
203 Destination::Style => HeaderValue::from_static("text/css,*/*;q=0.1"),
204 Destination::Text => HeaderValue::from_static("text/plain,*/*;q=0.5"),
205 _ => HeaderValue::from_static("*/*"),
207 }
208 };
209
210 request.headers.insert(header::ACCEPT, value);
212}
213
214fn set_default_accept_encoding(headers: &mut HeaderMap) {
215 if headers.contains_key(header::ACCEPT_ENCODING) {
216 return;
217 }
218
219 headers.insert(
221 header::ACCEPT_ENCODING,
222 HeaderValue::from_static("gzip, deflate, br, zstd"),
223 );
224}
225
226fn no_referrer_when_downgrade(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
228 if referrer_url.is_potentially_trustworthy() && !current_url.is_potentially_trustworthy() {
230 return None;
231 }
232 strip_url_for_use_as_referrer(referrer_url, false)
234}
235
236fn strict_origin(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
238 if referrer_url.is_potentially_trustworthy() && !current_url.is_potentially_trustworthy() {
240 return None;
241 }
242 strip_url_for_use_as_referrer(referrer_url, true)
244}
245
246fn strict_origin_when_cross_origin(
248 referrer_url: ServoUrl,
249 current_url: ServoUrl,
250) -> Option<ServoUrl> {
251 if referrer_url.origin() == current_url.origin() {
253 return strip_url_for_use_as_referrer(referrer_url, false);
254 }
255 if referrer_url.is_potentially_trustworthy() && !current_url.is_potentially_trustworthy() {
257 return None;
258 }
259 strip_url_for_use_as_referrer(referrer_url, true)
261}
262
263fn is_schemelessy_same_site(site_a: &ImmutableOrigin, site_b: &ImmutableOrigin) -> bool {
265 if !site_a.is_tuple() && !site_b.is_tuple() && site_a == site_b {
267 true
268 } else if site_a.is_tuple() && site_b.is_tuple() {
269 let host_a = site_a.host().map(|h| h.to_string()).unwrap_or_default();
271 let host_b = site_b.host().map(|h| h.to_string()).unwrap_or_default();
272
273 let host_a_reg = reg_suffix(&host_a);
274 let host_b_reg = reg_suffix(&host_b);
275
276 (site_a.host() == site_b.host() && host_a_reg.is_empty()) ||
278 (host_a_reg == host_b_reg && !host_a_reg.is_empty())
279 } else {
280 false
282 }
283}
284
285fn strip_url_for_use_as_referrer(mut url: ServoUrl, origin_only: bool) -> Option<ServoUrl> {
287 const MAX_REFERRER_URL_LENGTH: usize = 4096;
288 if url.is_local_scheme() {
290 return None;
291 }
292 {
294 let url = url.as_mut_url();
295 let _ = url.set_username("");
296 let _ = url.set_password(None);
297 url.set_fragment(None);
298 if origin_only || url.as_str().len() > MAX_REFERRER_URL_LENGTH {
302 url.set_path("");
303 url.set_query(None);
304 }
305 }
306 Some(url)
308}
309
310fn same_origin(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
312 if referrer_url.origin() == current_url.origin() {
314 return strip_url_for_use_as_referrer(referrer_url, false);
315 }
316 None
318}
319
320fn origin_when_cross_origin(referrer_url: ServoUrl, current_url: ServoUrl) -> Option<ServoUrl> {
322 if referrer_url.origin() == current_url.origin() {
324 return strip_url_for_use_as_referrer(referrer_url, false);
325 }
326 strip_url_for_use_as_referrer(referrer_url, true)
328}
329
330pub fn determine_requests_referrer(
332 referrer_policy: ReferrerPolicy,
333 referrer_source: ServoUrl,
334 current_url: ServoUrl,
335) -> Option<ServoUrl> {
336 match referrer_policy {
337 ReferrerPolicy::EmptyString | ReferrerPolicy::NoReferrer => None,
338 ReferrerPolicy::Origin => strip_url_for_use_as_referrer(referrer_source, true),
339 ReferrerPolicy::UnsafeUrl => strip_url_for_use_as_referrer(referrer_source, false),
340 ReferrerPolicy::StrictOrigin => strict_origin(referrer_source, current_url),
341 ReferrerPolicy::StrictOriginWhenCrossOrigin => {
342 strict_origin_when_cross_origin(referrer_source, current_url)
343 },
344 ReferrerPolicy::SameOrigin => same_origin(referrer_source, current_url),
345 ReferrerPolicy::OriginWhenCrossOrigin => {
346 origin_when_cross_origin(referrer_source, current_url)
347 },
348 ReferrerPolicy::NoReferrerWhenDowngrade => {
349 no_referrer_when_downgrade(referrer_source, current_url)
350 },
351 }
352}
353
354fn set_request_cookies(
355 url: &ServoUrl,
356 headers: &mut HeaderMap,
357 cookie_jar: &RwLock<CookieStorage>,
358) {
359 let mut cookie_jar = cookie_jar.write();
360 cookie_jar.remove_expired_cookies_for_url(url);
361 if let Some(cookie_list) = cookie_jar.cookies_for_url(url, CookieSource::HTTP) &&
362 let Ok(cookie_list_header_value) = HeaderValue::from_bytes(cookie_list.as_bytes())
363 {
364 headers.insert(header::COOKIE, cookie_list_header_value);
365 }
366}
367
368fn set_cookie_for_url(cookie_jar: &RwLock<CookieStorage>, request: &ServoUrl, cookie_val: &str) {
369 let mut cookie_jar = cookie_jar.write();
370 let source = CookieSource::HTTP;
371
372 if let Some(cookie) = ServoCookie::from_cookie_string(cookie_val, request, source) {
373 cookie_jar.push(cookie, request, source);
374 }
375}
376
377fn set_cookies_from_headers(
378 url: &ServoUrl,
379 headers: &HeaderMap,
380 cookie_jar: &RwLock<CookieStorage>,
381) {
382 for cookie in headers.get_all(header::SET_COOKIE) {
383 let cookie_bytes = cookie.as_bytes();
384 if !ServoCookie::is_valid_name_or_value(cookie_bytes) {
385 continue;
386 }
387 if let Ok(cookie_str) = std::str::from_utf8(cookie_bytes) {
388 set_cookie_for_url(cookie_jar, url, cookie_str);
389 }
390 }
391}
392
393fn build_tls_security_info(handshake: &TlsHandshakeInfo, hsts_enabled: bool) -> TlsSecurityInfo {
394 let state = if handshake.protocol_version.is_none() || handshake.cipher_suite.is_none() {
402 TlsSecurityState::Insecure
404 } else {
405 TlsSecurityState::Secure
407 };
408
409 TlsSecurityInfo {
410 state,
411 weakness_reasons: Vec::new(), protocol_version: handshake.protocol_version.map(ServoProtocolVersion),
413 cipher_suite: handshake.cipher_suite.map(ServoCipherSuite),
414 kea_group_name: handshake.kea_group_name.map(ServoNamedGroup),
415 signature_scheme_name: handshake.signature_scheme_name.clone(),
416 alpn_protocol: handshake.alpn_protocol.clone(),
417 certificate_chain_der: handshake.certificate_chain_der.clone(),
418 certificate_transparency: None,
419 hsts: hsts_enabled,
420 hpkp: false,
421 used_ech: handshake.used_ech,
422 used_delegated_credentials: false,
423 used_ocsp: false,
424 used_private_dns: false,
425 }
426}
427
428fn auth_from_cache(
429 auth_cache: &RwLock<AuthCache>,
430 origin: &ImmutableOrigin,
431) -> Option<Authorization<Basic>> {
432 if let Some(auth_entry) = auth_cache
433 .read()
434 .entries
435 .get(origin.ascii_serialization().as_ref())
436 {
437 let user_name = &auth_entry.user_name;
438 let password = &auth_entry.password;
439 Some(Authorization::basic(user_name, password))
440 } else {
441 None
442 }
443}
444
445enum BodyChunk {
448 Chunk(GenericSharedMemory),
450 Done,
452}
453
454enum BodyStream {
456 Chunked(TokioReceiver<Result<Frame<Bytes>, hyper::Error>>),
459 Buffered(UnboundedReceiver<BodyChunk>),
462}
463
464enum BodySink {
467 Chunked(TokioSender<Result<Frame<Bytes>, hyper::Error>>),
469 Buffered(UnboundedSender<BodyChunk>),
473}
474
475impl BodySink {
476 fn transmit_bytes(&self, bytes: GenericSharedMemory) {
477 match self {
478 BodySink::Chunked(sender) => {
479 let sender = sender.clone();
480 spawn_task(async move {
481 let _ = sender
482 .send(Ok(Frame::data(Bytes::copy_from_slice(&bytes))))
483 .await;
484 });
485 },
486 BodySink::Buffered(sender) => {
487 let _ = sender.send(BodyChunk::Chunk(bytes));
488 },
489 }
490 }
491
492 fn close(self) {
493 match self {
494 BodySink::Chunked(_) => {},
495 BodySink::Buffered(sender) => {
496 let _ = sender.send(BodyChunk::Done);
497 },
498 }
499 }
500}
501
502fn request_body_stream_closed_error(action: &str) -> NetworkError {
503 NetworkError::Crash(format!(
504 "Request body stream has already been closed while trying to {action}."
505 ))
506}
507
508fn log_request_body_stream_closed(action: &str, error: Option<&IpcError>) {
509 match error {
510 Some(error) => {
511 error!("Request body stream has already been closed while trying to {action}: {error}")
512 },
513 None => error!("Request body stream has already been closed while trying to {action}."),
514 }
515}
516
517fn log_fetch_terminated_send_failure(terminated_with_error: bool, context: &str) {
518 warn!(
519 "Failed to notify request-body stream termination state ({terminated_with_error}) while {context} because the receiver was already dropped."
520 );
521}
522
523const FRAGMENT: &AsciiSet = &CONTROLS.add(b'|').add(b'{').add(b'}');
524
525#[expect(clippy::too_many_arguments)]
526#[servo_tracing::instrument(skip_all, fields(url=url.as_str()))]
527async fn obtain_response(
529 client: &ServoClient,
530 url: &ServoUrl,
531 method: &Method,
532 request_headers: &mut HeaderMap,
533 body_sender: Option<StdArc<Mutex<Option<IpcSender<BodyChunkRequest>>>>>,
534 source_is_null: bool,
535 pipeline_id: &Option<PipelineId>,
536 request_id: Option<&Uuid>,
537 destination: Destination,
538 is_xhr: bool,
539 context: &FetchContext,
540 fetch_terminated: UnboundedSender<bool>,
541 browsing_context_id: Option<BrowsingContextId>,
542) -> Result<(HyperResponse<Decoder>, DevtoolsResponse), NetworkError> {
543 #[cfg(not(feature = "devtools"))]
544 let _ = (destination, is_xhr, browsing_context_id);
545
546 let mut headers = request_headers.clone();
547
548 #[cfg(feature = "devtools")]
549 let devtools_bytes = StdArc::new(Mutex::new(vec![]));
550
551 let encoded_url = utf8_percent_encode(url.as_str(), FRAGMENT).to_string();
553
554 let request = if let Some(chunk_requester) = body_sender {
555 let (sink, stream) = if source_is_null {
556 headers.insert(TRANSFER_ENCODING, HeaderValue::from_static("chunked"));
559
560 let (sender, receiver) = channel(1);
561 (BodySink::Chunked(sender), BodyStream::Chunked(receiver))
562 } else {
563 let (sender, receiver) = unbounded_channel();
570 (BodySink::Buffered(sender), BodyStream::Buffered(receiver))
571 };
572
573 #[cfg(feature = "devtools")]
574 obtain_response_setup_router_callback(
575 Some(devtools_bytes.clone()),
576 chunk_requester,
577 sink,
578 fetch_terminated,
579 )?;
580
581 #[cfg(not(feature = "devtools"))]
582 obtain_response_setup_router_callback(None, chunk_requester, sink, fetch_terminated)?;
583
584 let body = match stream {
585 BodyStream::Chunked(receiver) => {
586 let stream = ReceiverStream::new(receiver);
587 BoxBody::new(http_body_util::StreamBody::new(stream))
588 },
589 BodyStream::Buffered(mut receiver) => {
590 let mut body = vec![];
592 loop {
593 match receiver.recv().await {
594 Some(BodyChunk::Chunk(bytes)) => {
595 body.extend_from_slice(&bytes);
596 },
597 Some(BodyChunk::Done) => break,
598 None => warn!("Failed to read all chunks from request body."),
599 }
600 }
601 Full::new(body.into()).map_err(|_| unreachable!()).boxed()
602 },
603 };
604 HyperRequest::builder()
605 .method(method)
606 .uri(encoded_url)
607 .body(body)
608 } else {
609 HyperRequest::builder()
610 .method(method)
611 .uri(encoded_url)
612 .body(
613 http_body_util::Empty::new()
614 .map_err(|_| unreachable!())
615 .boxed(),
616 )
617 };
618
619 let connect_start = CrossProcessInstant::now();
622 context.timing.set_attributes(&[
623 ResourceAttribute::DomainLookupStart,
624 ResourceAttribute::ConnectStart(connect_start),
625 ]);
626
627 if url.scheme() == "https" {
631 context
632 .timing
633 .set_attribute(ResourceAttribute::SecureConnectionStart);
634 }
635
636 let mut request = match request {
637 Ok(request) => request,
638 Err(error) => return Err(NetworkError::HttpError(error.to_string())),
639 };
640 *request.headers_mut() = headers.clone();
641
642 let connect_end = CrossProcessInstant::now();
643 context
644 .timing
645 .set_attribute(ResourceAttribute::ConnectEnd(connect_end));
646
647 let host = request.uri().host().unwrap_or("").to_owned();
648 let override_manager = context.state.override_manager.clone();
649 #[cfg(feature = "devtools")]
650 let (request_id, pipeline_id, closure_url, method, send_start, headers) = (
651 request_id.map(|value| value.to_owned()),
652 *pipeline_id,
653 url.clone(),
654 method.clone(),
655 CrossProcessInstant::now(),
656 headers.clone(),
657 );
658 let is_secure_scheme = url.is_secure_scheme();
659
660 context
664 .timing
665 .set_attribute(ResourceAttribute::RequestStart);
666
667 let client_future = client
668 .request(request)
669 .and_then(move |res| {
670 #[cfg(feature = "devtools")]
672 let msg = if let Some(request_id) = request_id {
673 let send_end = CrossProcessInstant::now();
674 if let Some(pipeline_id) = pipeline_id {
675 if let Some(browsing_context_id) = browsing_context_id {
676 Some(prepare_devtools_request(
677 request_id,
678 closure_url,
679 method.clone(),
680 headers,
681 Some(devtools_bytes.lock().clone()),
682 pipeline_id,
683 (connect_end - connect_start).unsigned_abs(),
684 (send_end - send_start).unsigned_abs(),
685 destination,
686 is_xhr,
687 browsing_context_id,
688 ))
689 } else {
690 debug!("Not notifying devtools (no browsing_context_id)");
691 None
692 }
693 } else {
698 debug!("Not notifying devtools (no pipeline_id)");
699 None
700 }
701 } else {
702 debug!("Not notifying devtools (no request_id)");
703 None
704 };
705
706 future::ready(Ok((
707 Decoder::detect(res.map(|r| r.boxed()), is_secure_scheme),
708 #[cfg(feature = "devtools")]
709 msg,
710 #[cfg(not(feature = "devtools"))]
711 None,
712 )))
713 })
714 .map_err(move |error| {
715 warn!("network error: {error:?}");
716 NetworkError::from_hyper_error(
717 &error,
718 override_manager.remove_certificate_failing_verification(host.as_str()),
719 )
720 });
721
722 #[cfg(feature = "tracing")]
723 {
724 client_future.instrument(trace_span!("HyperRequest")).await
725 }
726
727 #[cfg(not(feature = "tracing"))]
728 {
729 client_future.await
730 }
731}
732
733fn obtain_response_setup_router_callback(
735 devtools_bytes: Option<StdArc<Mutex<Vec<u8>>>>,
736 chunk_requester: StdArc<Mutex<Option<IpcSender<BodyChunkRequest>>>>,
737 sink: BodySink,
738 fetch_terminated: UnboundedSender<bool>,
739) -> Result<(), NetworkError> {
740 let (body_chan, body_port) = ipc::channel().unwrap();
741
742 {
743 let mut lock = chunk_requester.lock();
744 if let Some(chunk_requester) = lock.as_mut() {
745 if let Err(error) = chunk_requester.send(BodyChunkRequest::Connect(body_chan)) {
746 log_request_body_stream_closed("connect to the request body stream", Some(&error));
747 return Err(request_body_stream_closed_error(
748 "connect to the request body stream",
749 ));
750 }
751
752 if let Err(error) = chunk_requester.send(BodyChunkRequest::Chunk) {
755 log_request_body_stream_closed(
756 "request the first request body chunk",
757 Some(&error),
758 );
759 return Err(request_body_stream_closed_error(
760 "request the first request body chunk",
761 ));
762 }
763 } else {
764 log_request_body_stream_closed("connect to the request body stream", None);
765 return Err(request_body_stream_closed_error(
766 "connect to the request body stream",
767 ));
768 }
769 }
770
771 let mut sink = Some(sink);
772
773 ROUTER.add_typed_route(
774 body_port,
775 Box::new(move |message| {
776 info!("Received message");
777 let bytes = match message.unwrap() {
778 BodyChunkResponse::Chunk(bytes) => bytes,
779 BodyChunkResponse::Done => {
780 if fetch_terminated.send(false).is_err() {
783 log_fetch_terminated_send_failure(
784 false,
785 "handling request body completion",
786 );
787 }
788 if let Some(sink) = sink.take() {
789 sink.close();
790 }
791
792 return;
793 },
794 BodyChunkResponse::Error => {
795 if fetch_terminated.send(true).is_err() {
799 log_fetch_terminated_send_failure(
800 true,
801 "handling request body stream error",
802 );
803 }
804 if let Some(sink) = sink.take() {
805 sink.close();
806 }
807
808 return;
809 },
810 };
811
812 #[cfg(feature = "devtools")]
813 if let Some(devtools_bytes) = &devtools_bytes {
814 devtools_bytes.lock().extend_from_slice(&bytes);
815 }
816
817 {
820 let Some(sink) = sink.as_ref() else {
821 return;
822 };
823 sink.transmit_bytes(bytes);
824 }
825
826 let mut chunk_requester = chunk_requester.lock();
829 if let Some(chunk_requester) = chunk_requester.as_mut() {
830 if let Err(error) = chunk_requester.send(BodyChunkRequest::Chunk) {
831 log_request_body_stream_closed(
832 "request the next request body chunk",
833 Some(&error),
834 );
835 if fetch_terminated.send(true).is_err() {
836 log_fetch_terminated_send_failure(
837 true,
838 "handling failure to request the next request body chunk",
839 );
840 }
841 if let Some(sink) = sink.take() {
842 sink.close();
843 }
844 }
845 } else {
846 log_request_body_stream_closed("request the next request body chunk", None);
847 if fetch_terminated.send(true).is_err() {
848 log_fetch_terminated_send_failure(
849 true,
850 "handling a closed request body stream while requesting the next chunk",
851 );
852 }
853 if let Some(sink) = sink.take() {
854 sink.close();
855 }
856 }
857 }),
858 );
859
860 Ok(())
861}
862
863#[async_recursion]
865#[expect(clippy::too_many_arguments)]
866pub(crate) async fn http_fetch(
867 fetch_params: &mut FetchParams,
868 cache: &mut CorsCache,
869 cors_flag: bool,
870 cors_preflight_flag: bool,
871 authentication_fetch_flag: bool,
872 target: Target<'async_recursion>,
873 done_chan: &mut DoneChannel,
874 context: &FetchContext,
875) -> Response {
876 *done_chan = None;
878 let request = &mut fetch_params.request;
880
881 let mut response: Option<Response> = None;
883
884 if request.service_workers_mode == ServiceWorkersMode::All {
886 if let Some(ref res) = response {
891 if (res.response_type == ResponseType::Opaque && request.mode != RequestMode::NoCors) ||
899 (res.response_type == ResponseType::OpaqueRedirect &&
900 request.redirect_mode != RedirectMode::Manual) ||
901 (res.url_list.len() > 1 && request.redirect_mode != RedirectMode::Follow) ||
902 res.is_network_error()
903 {
904 return Response::network_error(NetworkError::ConnectionFailure);
905 }
906
907 }
910 }
911
912 if response.is_none() {
914 if cors_preflight_flag {
916 let method_cache_match = cache.match_method(request, request.method.clone());
917
918 let method_mismatch = !method_cache_match &&
922 (!is_cors_safelisted_method(&request.method) || request.use_cors_preflight);
923
924 let header_mismatch = request.headers.iter().any(|(name, value)| {
927 !cache.match_header(request, name) &&
928 !is_cors_safelisted_request_header(&name, &value)
929 });
930
931 if method_mismatch || header_mismatch {
933 let preflight_response = cors_preflight_fetch(request, cache, context).await;
936 if let Some(error) = preflight_response.get_network_error() {
938 return Response::network_error(error.clone());
939 }
940 }
941 }
942
943 if request.redirect_mode == RedirectMode::Follow {
946 request.service_workers_mode = ServiceWorkersMode::None;
947 }
948
949 let mut fetch_result = http_network_or_cache_fetch(
952 fetch_params,
953 authentication_fetch_flag,
954 cors_flag,
955 done_chan,
956 context,
957 )
958 .await;
959
960 if cors_flag && cors_check(&fetch_params.request, &fetch_result).is_err() {
963 return Response::network_error(NetworkError::CorsGeneral);
964 }
965
966 if let Err(()) = tao_check(&fetch_params.request, &fetch_result) {
969 context.timing.inner().mark_timing_check_failed();
970 }
971 fetch_result.return_internal = false;
972 response = Some(fetch_result);
973 }
974
975 let request = &mut fetch_params.request;
976
977 let mut response = response.unwrap();
979
980 if (request.response_tainting == ResponseTainting::Opaque ||
984 response.response_type == ResponseType::Opaque) &&
985 request.client.as_ref().is_some_and(|client| {
986 cross_origin_resource_policy_check(
987 &request.origin,
988 client,
989 &response,
990 ForNavigation::No,
991 ) == CrossOriginResourcePolicy::Blocked
992 })
993 {
994 return Response::network_error(NetworkError::CrossOriginResponse);
995 }
996
997 if response
999 .actual_response()
1000 .status
1001 .try_code()
1002 .is_some_and(is_redirect_status)
1003 {
1004 if response.actual_response().status != StatusCode::SEE_OTHER {
1011 }
1013
1014 response = match request.redirect_mode {
1016 RedirectMode::Error => Response::network_error(NetworkError::RedirectError),
1018 RedirectMode::Manual => {
1019 if request.mode == RequestMode::Navigate {
1022 let location_url =
1026 location_url_for_response(&response, request.current_url().fragment());
1027 response.actual_response_mut().location_url = location_url;
1028 response
1029 } else {
1030 response.to_filtered(ResponseType::OpaqueRedirect)
1033 }
1034 },
1035 RedirectMode::Follow => {
1036 response.return_internal = true;
1040
1041 http_redirect_fetch(
1044 fetch_params,
1045 cache,
1046 response,
1047 cors_flag,
1048 target,
1049 done_chan,
1050 context,
1051 )
1052 .await
1053 },
1054 };
1055 }
1056
1057 response.return_internal = true;
1059 context
1060 .timing
1061 .set_attribute(ResourceAttribute::RedirectCount(
1062 fetch_params.request.redirect_count as u16,
1063 ));
1064
1065 response.resource_timing = context.timing.clone();
1066
1067 response
1069}
1070
1071fn tao_check(request: &Request, response: &Response) -> Result<(), ()> {
1073 let Origin::Origin(ref request_origin) = request.origin else {
1075 unreachable!("origin cannot be \"client\" at this point");
1076 };
1077
1078 let values: Vec<&str> = response
1083 .headers
1084 .get_all("Timing-Allow-Origin")
1085 .iter()
1086 .map(|header_value| header_value.to_str().unwrap_or(""))
1087 .collect();
1088
1089 if values.contains(&"*") {
1091 return Ok(());
1092 }
1093
1094 if values
1097 .iter()
1098 .any(|header_str| *header_str == request_origin.ascii_serialization().as_ref())
1099 {
1100 return Ok(());
1101 }
1102
1103 if request.mode == RequestMode::Navigate && request.current_url().origin() != *request_origin {
1106 return Err(());
1107 }
1108
1109 if request.response_tainting == ResponseTainting::Basic {
1111 return Ok(());
1112 }
1113
1114 Err(())
1116}
1117
1118struct RedirectEndTimer(Option<ResourceFetchTimingContainer>);
1120
1121impl RedirectEndTimer {
1122 fn neuter(&mut self) {
1123 self.0 = None;
1124 }
1125}
1126
1127impl Drop for RedirectEndTimer {
1128 fn drop(&mut self) {
1129 let RedirectEndTimer(resource_fetch_timing_opt) = self;
1130
1131 resource_fetch_timing_opt.as_ref().map_or((), |t| {
1132 t.set_attribute(ResourceAttribute::RedirectEnd(RedirectEndValue::Zero));
1133 })
1134 }
1135}
1136
1137static REQUEST_BODY_HEADER_NAMES: &[HeaderName] = &[
1139 CONTENT_ENCODING,
1140 CONTENT_LANGUAGE,
1141 CONTENT_LOCATION,
1142 CONTENT_TYPE,
1143];
1144
1145fn location_url_for_response(
1147 response: &Response,
1148 request_fragment: Option<&str>,
1149) -> Option<Result<ServoUrl, String>> {
1150 assert!(
1152 response
1153 .actual_response()
1154 .status
1155 .try_code()
1156 .is_some_and(is_redirect_status)
1157 );
1158 let mut location = response
1160 .actual_response()
1161 .headers
1162 .get(header::LOCATION)
1163 .and_then(|header_value| {
1164 HeaderValue::to_str(header_value)
1165 .map(|location_string| {
1166 ServoUrl::parse_with_base(response.actual_response().url(), location_string)
1168 .map_err(|error| error.to_string())
1169 })
1170 .ok()
1171 });
1172
1173 if let Some(Ok(ref mut location)) = location &&
1175 location.fragment().is_none()
1176 {
1177 location.set_fragment(request_fragment);
1178 }
1179 location
1181}
1182
1183#[async_recursion]
1185pub async fn http_redirect_fetch(
1186 fetch_params: &mut FetchParams,
1187 cache: &mut CorsCache,
1188 mut response: Response,
1189 cors_flag: bool,
1190 target: Target<'async_recursion>,
1191 done_chan: &mut DoneChannel,
1192 context: &FetchContext,
1193) -> Response {
1194 let mut redirect_end_timer = RedirectEndTimer(Some(context.timing.clone()));
1195
1196 let request = &mut fetch_params.request;
1198
1199 assert!(response.return_internal);
1201
1202 let location_url = location_url_for_response(&response, request.current_url().fragment());
1204 response.actual_response_mut().location_url = location_url.clone();
1205
1206 let location_url = match location_url {
1207 None => return response,
1209 Some(Err(err)) => {
1211 return Response::network_error(NetworkError::ResourceLoadError(
1212 "Location URL parse failure: ".to_owned() + &err,
1213 ));
1214 },
1215 Some(Ok(url)) if !matches!(url.scheme(), "http" | "https") => {
1217 return Response::network_error(NetworkError::UnsupportedScheme);
1218 },
1219 Some(Ok(url)) => url,
1220 };
1221
1222 context.timing.set_attributes(&[
1227 ResourceAttribute::RedirectStart(RedirectStartValue::FetchStart),
1228 ResourceAttribute::FetchStart,
1229 ResourceAttribute::StartTime(ResourceTimeValue::FetchStart),
1230 ResourceAttribute::StartTime(ResourceTimeValue::RedirectStart),
1231 ]);
1232
1233 if request.redirect_count >= 20 {
1235 return Response::network_error(NetworkError::TooManyRedirects);
1236 }
1237
1238 request.redirect_count += 1;
1240
1241 let same_origin = match request.origin {
1244 Origin::Origin(ref origin) => *origin == location_url.origin(),
1245 Origin::Client => panic!(
1246 "Request origin should not be client for {}",
1247 request.current_url()
1248 ),
1249 };
1250
1251 let has_credentials = has_credentials(&location_url);
1252
1253 if request.mode == RequestMode::CorsMode && !same_origin && has_credentials {
1254 return Response::network_error(NetworkError::CorsCredentials);
1255 }
1256
1257 if cors_flag && location_url.origin() != request.current_url().origin() {
1258 request.origin = Origin::Origin(ImmutableOrigin::new_opaque());
1259 }
1260
1261 if cors_flag && has_credentials {
1263 return Response::network_error(NetworkError::CorsCredentials);
1264 }
1265
1266 if response.actual_response().status != StatusCode::SEE_OTHER &&
1269 request.body.as_ref().is_some_and(|b| b.source_is_null())
1270 {
1271 return Response::network_error(NetworkError::ConnectionFailure);
1272 }
1273
1274 if response
1276 .actual_response()
1277 .status
1278 .try_code()
1279 .is_some_and(|code| {
1280 ((code == StatusCode::MOVED_PERMANENTLY || code == StatusCode::FOUND) &&
1282 request.method == Method::POST) ||
1283 (code == StatusCode::SEE_OTHER &&
1285 request.method != Method::HEAD &&
1286 request.method != Method::GET)
1287 })
1288 {
1289 request.method = Method::GET;
1291 request.body = None;
1292 for name in REQUEST_BODY_HEADER_NAMES {
1294 request.headers.remove(name);
1295 }
1296 }
1297
1298 if location_url.origin() != request.current_url().origin() {
1302 request.headers.remove(AUTHORIZATION);
1305 }
1306
1307 if let Some(body) = request.body.as_mut() {
1310 body.extract_source();
1311 }
1312
1313 request
1317 .url_list
1318 .push(UrlWithBlobClaim::from_url_without_having_claimed_blob(
1319 location_url,
1320 ));
1321
1322 set_requests_referrer_policy_on_redirect(request, response.actual_response());
1324
1325 let recursive_flag = request.redirect_mode != RedirectMode::Manual;
1328
1329 let fetch_response = main_fetch(
1331 fetch_params,
1332 cache,
1333 recursive_flag,
1334 target,
1335 done_chan,
1336 context,
1337 )
1338 .await;
1339
1340 context.timing.set_attribute(ResourceAttribute::RedirectEnd(
1342 RedirectEndValue::ResponseEnd,
1343 ));
1344 redirect_end_timer.neuter();
1345
1346 fetch_response
1347}
1348
1349#[async_recursion]
1351#[servo_tracing::instrument(skip_all,fields(url=fetch_params.request.url().as_str()))]
1352async fn http_network_or_cache_fetch(
1353 fetch_params: &mut FetchParams,
1354 authentication_fetch_flag: bool,
1355 cors_flag: bool,
1356 done_chan: &mut DoneChannel,
1357 context: &FetchContext,
1358) -> Response {
1359 let http_fetch_params: &mut FetchParams;
1361 let mut fetch_params_copy: FetchParams;
1362
1363 let mut response: Option<Response> = None;
1367
1368 let mut revalidating_flag = false;
1370
1371 let http_request = if fetch_params.request.traversable_for_user_prompts ==
1375 TraversableForUserPrompts::NoTraversable &&
1376 fetch_params.request.redirect_mode == RedirectMode::Error
1377 {
1378 http_fetch_params = fetch_params;
1379 &mut http_fetch_params.request
1380 }
1381 else {
1383 fetch_params_copy =
1386 std::mem::replace(fetch_params, FetchParams::new(fetch_params.request.clone()));
1387 http_fetch_params = &mut fetch_params_copy;
1388
1389 &mut http_fetch_params.request
1390 };
1391
1392 let include_credentials = match http_request.credentials_mode {
1394 CredentialsMode::Include => true,
1396 CredentialsMode::CredentialsSameOrigin
1398 if http_request.response_tainting == ResponseTainting::Basic =>
1399 {
1400 true
1401 },
1402 _ => false,
1403 };
1404
1405 let content_length = http_request
1412 .body
1413 .as_ref()
1414 .and_then(|body| body.len().map(|size| size as u64));
1415
1416 let mut content_length_header_value = None;
1418
1419 if http_request.body.is_none() && matches!(http_request.method, Method::POST | Method::PUT) {
1422 content_length_header_value = Some(0);
1423 }
1424
1425 if let Some(content_length) = content_length {
1429 content_length_header_value = Some(content_length);
1430 };
1431
1432 if let Some(content_length_header_value) = content_length_header_value {
1435 http_request
1436 .headers
1437 .typed_insert(ContentLength(content_length_header_value));
1438 }
1439
1440 if http_request.keep_alive &&
1442 let Some(content_length) = content_length
1443 {
1444 let in_flight_keep_alive_bytes: u64 = context
1449 .in_flight_keep_alive_records
1450 .lock()
1451 .get(
1452 &http_request
1453 .pipeline_id
1454 .expect("Must always set a pipeline ID for keep-alive requests"),
1455 )
1456 .map(|records| {
1457 records
1461 .iter()
1462 .map(|record| {
1463 if record.request_id == http_request.id {
1464 0
1467 } else {
1468 record.keep_alive_body_length
1469 }
1470 })
1471 .sum()
1472 })
1473 .unwrap_or_default();
1474 if content_length + in_flight_keep_alive_bytes > 64 * 1024 {
1476 return Response::network_error(NetworkError::TooManyInFlightKeepAliveRequests);
1477 }
1478 }
1479
1480 match http_request.referrer {
1482 Referrer::ReferrerUrl(ref http_request_referrer) |
1483 Referrer::Client(ref http_request_referrer) => {
1484 if let Ok(referer) = http_request_referrer.as_str().parse::<Referer>() {
1487 http_request.headers.typed_insert(referer);
1489 } else {
1490 error!("Failed to parse {} as referrer", http_request_referrer);
1494 }
1495 },
1496 _ => {},
1497 };
1498
1499 append_a_request_origin_header(http_request);
1501
1502 append_the_fetch_metadata_headers(http_request);
1504
1505 if http_request.initiator == Initiator::Prefetch &&
1508 let Ok(value) = HeaderValue::from_str("prefetch")
1509 {
1510 http_request.headers.insert("Sec-Purpose", value);
1511 }
1512
1513 if !http_request.headers.contains_key(header::USER_AGENT) {
1516 http_request
1517 .headers
1518 .typed_insert::<UserAgent>(context.user_agent.parse().unwrap());
1519 }
1520
1521 append_cache_data_to_headers(http_request);
1523
1524 if http_request.headers.contains_key(header::RANGE) &&
1527 let Ok(value) = HeaderValue::from_str("identity")
1528 {
1529 http_request.headers.insert("Accept-Encoding", value);
1530 }
1531
1532 http_request.headers.remove(header::HOST);
1536 set_default_accept_encoding(&mut http_request.headers);
1538
1539 let current_url = http_request.current_url();
1540
1541 if include_credentials {
1544 set_request_cookies(
1548 ¤t_url,
1549 &mut http_request.headers,
1550 &context.state.cookie_jar,
1551 );
1552 if !http_request.headers.contains_key(header::AUTHORIZATION) {
1554 let mut authorization_value = None;
1556
1557 if let Some(basic) = auth_from_cache(&context.state.auth_cache, ¤t_url.origin()) &&
1559 (!http_request.use_url_credentials || !has_credentials(¤t_url))
1560 {
1561 authorization_value = Some(basic);
1562 }
1563
1564 if authentication_fetch_flag &&
1566 authorization_value.is_none() &&
1567 has_credentials(¤t_url)
1568 {
1569 authorization_value = Some(Authorization::basic(
1570 current_url.username(),
1571 current_url.password().unwrap_or(""),
1572 ));
1573 }
1574
1575 if let Some(basic) = authorization_value {
1577 http_request.headers.typed_insert(basic);
1578 }
1579 }
1580 }
1581
1582 let should_wait = {
1584 let mut cache_guard = block_for_cache_ready(
1586 context,
1587 http_request,
1588 done_chan,
1589 &mut revalidating_flag,
1590 &mut response,
1591 )
1592 .await;
1593
1594 if response.is_none() {
1598 if http_request.cache_mode == CacheMode::OnlyIfCached {
1600 return Response::network_error(NetworkError::CacheError);
1602 }
1603
1604 drop(cache_guard);
1607 let forward_response =
1608 http_network_fetch(http_fetch_params, include_credentials, done_chan, context)
1609 .await;
1610
1611 let http_request = &mut http_fetch_params.request;
1612 let request_key = CacheKey::new(http_request);
1613 cache_guard = context
1614 .state
1615 .http_cache
1616 .get_or_guard(request_key.clone())
1617 .await;
1618 if forward_response.status.in_range(200..=399) && !http_request.method.is_safe() {
1622 if let Some(guard) = cache_guard.try_as_mut() {
1623 invalidate_cached_resources(guard);
1624 }
1625 context
1626 .state
1627 .http_cache
1628 .invalidate_related_urls(http_request, &forward_response, &request_key)
1629 .await;
1630 }
1631
1632 if revalidating_flag && forward_response.status == StatusCode::NOT_MODIFIED {
1634 *done_chan = None;
1637 if let Some(guard) = cache_guard.try_as_mut() {
1638 response = refresh(http_request, forward_response.clone(), done_chan, guard);
1639 }
1640
1641 if let Some(response) = &mut response {
1642 response.cache_state = CacheState::Validated;
1643 }
1644 }
1645
1646 if response.is_none() {
1648 let forward_response = response.insert(forward_response);
1650
1651 if http_request.cache_mode != CacheMode::NoStore {
1654 cache_guard.insert(http_request, forward_response);
1657 }
1658 }
1659 false
1660 } else {
1661 true
1662 }
1663 }; if should_wait {
1666 wait_for_inflight_requests(done_chan, &mut response).await;
1671 }
1672
1673 let http_request = &mut http_fetch_params.request;
1674 let mut response = response.unwrap();
1675
1676 response.url_list = http_request
1678 .url_list
1679 .iter()
1680 .map(|claimed_url| claimed_url.url())
1681 .collect();
1682
1683 if http_request.headers.contains_key(RANGE) {
1685 response.range_requested = true;
1686 }
1687
1688 response.request_includes_credentials = include_credentials;
1690
1691 if response.status.try_code() == Some(StatusCode::UNAUTHORIZED) &&
1697 !cors_flag &&
1698 include_credentials &&
1699 response.headers.contains_key(WWW_AUTHENTICATE)
1700 {
1701 let request = &mut fetch_params.request;
1704
1705 if request
1708 .body
1709 .as_ref()
1710 .is_some_and(|body| body.source_is_null())
1711 {
1712 return Response::network_error(NetworkError::ConnectionFailure);
1713 }
1714
1715 if !request.use_url_credentials || authentication_fetch_flag {
1717 let Some(credentials) = context
1718 .state
1719 .request_authentication(request, &response)
1720 .await
1721 else {
1722 return response;
1723 };
1724
1725 if let Err(err) = request
1726 .current_url_mut()
1727 .set_username(&credentials.username)
1728 {
1729 error!("error setting username for url: {:?}", err);
1730 return response;
1731 };
1732
1733 if let Err(err) = request
1734 .current_url_mut()
1735 .set_password(Some(&credentials.password))
1736 {
1737 error!("error setting password for url: {:?}", err);
1738 return response;
1739 };
1740 }
1741
1742 *done_chan = None;
1745
1746 response = http_network_or_cache_fetch(
1748 fetch_params,
1749 true, cors_flag,
1751 done_chan,
1752 context,
1753 )
1754 .await;
1755 }
1756
1757 if response.status == StatusCode::PROXY_AUTHENTICATION_REQUIRED {
1759 let request = &mut fetch_params.request;
1760 if request.traversable_for_user_prompts == TraversableForUserPrompts::NoTraversable {
1763 return Response::network_error(NetworkError::ResourceLoadError(
1764 "Can't find Window object".into(),
1765 ));
1766 }
1767
1768 let Some(credentials) = context
1776 .state
1777 .request_authentication(request, &response)
1778 .await
1779 else {
1780 return response;
1781 };
1782
1783 let entry = AuthCacheEntry {
1785 user_name: credentials.username,
1786 password: credentials.password,
1787 };
1788 {
1789 let mut auth_cache = context.state.auth_cache.write();
1790 let key = request
1791 .current_url()
1792 .origin()
1793 .ascii_serialization()
1794 .into_owned();
1795 auth_cache.entries.insert(key, entry);
1796 }
1797
1798 *done_chan = None;
1801
1802 response = http_network_or_cache_fetch(
1804 fetch_params,
1805 false, cors_flag,
1807 done_chan,
1808 context,
1809 )
1810 .await;
1811 }
1812
1813 if authentication_fetch_flag {
1821 }
1823
1824 response
1826}
1827
1828#[servo_tracing::instrument(skip_all)]
1836async fn block_for_cache_ready<'a>(
1837 context: &'a FetchContext,
1838 http_request: &mut Request,
1839 done_chan: &mut DoneChannel,
1840 revalidating_flag: &mut bool,
1841 response: &mut Option<Response>,
1842) -> CachedResourcesOrGuard<'a> {
1843 let entry_key = CacheKey::new(http_request);
1844 let guard_result = context.state.http_cache.get_or_guard(entry_key).await;
1845
1846 match guard_result {
1847 CachedResourcesOrGuard::Guard(_) => {
1848 *done_chan = None;
1849 },
1850 CachedResourcesOrGuard::Value(ref cached_resources) => {
1851 let stored_response = construct_response(http_request, done_chan, cached_resources);
1857 if let Some(response_from_cache) = stored_response {
1859 let validation_status = &response_from_cache.validation_status;
1860 let revalidation_guard = &response_from_cache.revalidation_guard;
1861
1862 let (cached_response, needs_synchronous_revalidation) =
1864 match (http_request.cache_mode, &http_request.mode) {
1865 (CacheMode::ForceCache, _) => (Some(response_from_cache.response), false),
1866 (CacheMode::OnlyIfCached, &RequestMode::SameOrigin) => {
1867 (Some(response_from_cache.response), false)
1868 },
1869 (CacheMode::OnlyIfCached, _) |
1870 (CacheMode::NoStore, _) |
1871 (CacheMode::Reload, _) => (None, false),
1872 (_, _) => (
1873 Some(response_from_cache.response),
1874 *validation_status ==
1875 (ValidationStatus::Stale {
1876 revalidate_in_background: false,
1877 }),
1878 ),
1879 };
1880
1881 if needs_synchronous_revalidation {
1882 *revalidating_flag = true;
1883 let response_headers = &cached_response
1884 .as_ref()
1885 .expect("needs_synchronous_revalidation implies a cached response")
1886 .headers;
1887 if let Some(http_date) = response_headers.typed_get::<LastModified>() {
1889 let http_date: SystemTime = http_date.into();
1890 http_request
1891 .headers
1892 .typed_insert(IfModifiedSince::from(http_date));
1893 }
1894 if let Some(entity_tag) = response_headers.get(header::ETAG) {
1895 http_request
1896 .headers
1897 .insert(header::IF_NONE_MATCH, entity_tag.clone());
1898 }
1899 } else {
1900 let revalidate_in_background = *validation_status ==
1903 (ValidationStatus::Stale {
1904 revalidate_in_background: true,
1905 });
1906 if revalidate_in_background && cached_response.is_some() {
1907 spawn_stale_while_revalidate(
1908 context,
1909 http_request,
1910 revalidation_guard.clone(),
1911 );
1912 }
1913 *response = cached_response;
1914 if let Some(response) = response {
1915 response.cache_state = CacheState::Local;
1916 }
1917 }
1918 if response.is_none() {
1919 *done_chan = None;
1922 }
1923 }
1924 },
1925 }
1926 guard_result
1927}
1928
1929fn spawn_stale_while_revalidate(
1932 context: &FetchContext,
1933 http_request: &Request,
1934 revalidation_guard: StdArc<AtomicBool>,
1935) {
1936 if revalidation_guard
1938 .compare_exchange(false, true, Ordering::AcqRel, Ordering::Acquire)
1939 .is_err()
1940 {
1941 return;
1942 }
1943
1944 let mut revalidation_request = http_request.clone();
1947 revalidation_request.cache_mode = CacheMode::NoCache;
1948
1949 revalidation_request.service_workers_mode = ServiceWorkersMode::None;
1951
1952 let context = context.clone();
1953 debug!(
1954 "spawning stale-while-revalidate background revalidation for {:?}",
1955 revalidation_request.current_url()
1956 );
1957 spawn_task(async move {
1958 let mut target = DiscardFetch;
1959
1960 let _ = fetch(revalidation_request, &mut target, &context).await;
1961 revalidation_guard.store(false, Ordering::Release);
1962 });
1963}
1964
1965async fn wait_for_inflight_requests(done_chan: &mut DoneChannel, response: &mut Option<Response>) {
1968 if let Some(ref mut ch) = *done_chan {
1969 assert!(response.is_some());
1973
1974 loop {
1975 match ch.1.recv().await {
1976 Some(Data::ContentLength(_)) | Some(Data::Payload(_)) | Some(Data::Error(_)) => {},
1977 Some(Data::Done) => break, Some(Data::Cancelled) => {
1979 break;
1981 },
1982 None => panic!("HTTP cache should always send Done or Cancelled"),
1983 }
1984 }
1985 }
1986 *done_chan = None;
1988}
1989
1990#[derive(PartialEq)]
1994enum CrossOriginResourcePolicy {
1995 Allowed,
1996 Blocked,
1997}
1998
1999enum ForNavigation {
2000 #[expect(dead_code)]
2001 Yes,
2002 No,
2003}
2004
2005fn cross_origin_resource_policy_check(
2007 origin: &Origin,
2008 request_client: &RequestClient,
2009 response: &Response,
2010 for_navigation: ForNavigation,
2011) -> CrossOriginResourcePolicy {
2012 let embedder_policy = &request_client.policy_container.embedder_policy;
2018
2019 if cross_origin_resource_policy_internal_check(
2022 origin,
2023 EmbedderPolicyValue::UnsafeNone,
2024 response,
2025 &for_navigation,
2026 ) == CrossOriginResourcePolicy::Blocked
2027 {
2028 return CrossOriginResourcePolicy::Blocked;
2029 }
2030
2031 if cross_origin_resource_policy_internal_check(
2039 origin,
2040 embedder_policy.value,
2041 response,
2042 &for_navigation,
2043 ) == CrossOriginResourcePolicy::Allowed
2044 {
2045 return CrossOriginResourcePolicy::Allowed;
2046 }
2047
2048 CrossOriginResourcePolicy::Blocked
2053}
2054
2055fn cross_origin_resource_policy_internal_check(
2057 origin: &Origin,
2058 embedder_policy_value: EmbedderPolicyValue,
2059 response: &Response,
2060 for_navigation: &ForNavigation,
2061) -> CrossOriginResourcePolicy {
2062 if let ForNavigation::Yes = for_navigation &&
2064 let EmbedderPolicyValue::UnsafeNone = embedder_policy_value
2065 {
2066 return CrossOriginResourcePolicy::Allowed;
2067 }
2068
2069 let policy = response
2071 .headers
2072 .get(HeaderName::from_static("cross-origin-resource-policy"))
2073 .and_then(|h| h.to_str().ok());
2074
2075 let policy = policy
2077 .filter(|&s| s == "same-origin" || s == "same-site" || s == "cross-origin")
2078 .or(match embedder_policy_value {
2080 EmbedderPolicyValue::UnsafeNone => None,
2082 EmbedderPolicyValue::RequireCorp => Some("same-origin"),
2084 });
2085
2086 match policy {
2088 Some("same-origin") => {
2089 if let Origin::Origin(request_origin) = origin &&
2091 response
2092 .url()
2093 .is_some_and(|url| request_origin == &url.origin())
2094 {
2095 return CrossOriginResourcePolicy::Allowed;
2096 }
2097
2098 CrossOriginResourcePolicy::Blocked
2100 },
2101 Some("same-site") => {
2102 if let Some(response_url) = response.url() {
2103 if let Origin::Origin(request_origin) = origin &&
2107 is_schemelessy_same_site(request_origin, &response_url.origin()) &&
2108 (request_origin.scheme() == Some("https") ||
2109 response_url.scheme() != "https")
2110 {
2111 return CrossOriginResourcePolicy::Allowed;
2112 }
2113 }
2114 CrossOriginResourcePolicy::Blocked
2116 },
2117 _ => CrossOriginResourcePolicy::Allowed,
2120 }
2121}
2122
2123struct ResponseEndTimer(Option<ResourceFetchTimingContainer>);
2125
2126impl ResponseEndTimer {
2127 fn neuter(&mut self) {
2128 self.0 = None;
2129 }
2130}
2131
2132impl Drop for ResponseEndTimer {
2133 fn drop(&mut self) {
2134 let ResponseEndTimer(resource_fetch_timing_opt) = self;
2135
2136 resource_fetch_timing_opt.as_ref().map_or((), |t| {
2137 t.set_attribute(ResourceAttribute::ResponseEnd);
2138 })
2139 }
2140}
2141
2142#[servo_tracing::instrument(skip_all,fields(url=fetch_params.request.url().as_str()))]
2144async fn http_network_fetch(
2145 fetch_params: &mut FetchParams,
2146 credentials_flag: bool,
2147 done_chan: &mut DoneChannel,
2148 context: &FetchContext,
2149) -> Response {
2150 let mut response_end_timer = ResponseEndTimer(Some(context.timing.clone()));
2151
2152 let request = &mut fetch_params.request;
2154
2155 let url = request.current_url();
2165 let request_id = request.id.0;
2166 if log_enabled!(log::Level::Info) {
2167 info!("{:?} request for {}", request.method, url);
2168 for header in request.headers.iter() {
2169 debug!(" - {:?}", header);
2170 }
2171 }
2172
2173 let is_xhr = request.destination == Destination::None;
2177
2178 let (fetch_terminated_sender, mut fetch_terminated_receiver) = unbounded_channel();
2180
2181 let body = request.body.as_ref().map(|body| body.clone_stream());
2182
2183 if body.is_none() {
2184 let _ = fetch_terminated_sender.send(false);
2189 }
2190
2191 let browsing_context_id = request.target_webview_id.map(Into::into);
2192
2193 let (response_stream, msg) = match &request.mode {
2197 RequestMode::WebSocket {
2199 protocols,
2200 original_url: _,
2201 } => {
2202 let (resource_event_sender, dom_action_receiver) = {
2205 let mut websocket_chan = context.websocket_chan.as_ref().unwrap().lock();
2206 (
2207 websocket_chan.sender.clone(),
2208 websocket_chan.receiver.take().unwrap(),
2209 )
2210 };
2211
2212 let mut tls_config = create_tls_config(
2213 context.ca_certificates.clone(),
2214 context.ignore_certificate_errors,
2215 context.state.override_manager.clone(),
2216 );
2217 tls_config.alpn_protocols = vec!["http/1.1".into()];
2218
2219 let response = match start_websocket(
2220 context.state.clone(),
2221 resource_event_sender,
2222 protocols,
2223 request,
2224 tls_config,
2225 dom_action_receiver,
2226 )
2227 .await
2228 {
2229 Ok(response) => response,
2230 Err(error) => {
2231 return Response::network_error(NetworkError::WebsocketConnectionFailure(
2232 format!("{error:?}"),
2233 ));
2234 },
2235 };
2236
2237 let response = response.map(|r| match r {
2238 Some(body) => Full::from(body).map_err(|_| unreachable!()).boxed(),
2239 None => http_body_util::Empty::new()
2240 .map_err(|_| unreachable!())
2241 .boxed(),
2242 });
2243 (Decoder::detect(response, url.is_secure_scheme()), None)
2244 },
2245 _ => {
2248 let response_future = obtain_response(
2249 &context.state.client,
2250 &url,
2251 &request.method,
2252 &mut request.headers,
2253 body,
2254 request
2255 .body
2256 .as_ref()
2257 .is_some_and(|body| body.source_is_null()),
2258 &request.pipeline_id,
2259 Some(&request_id),
2260 request.destination,
2261 is_xhr,
2262 context,
2263 fetch_terminated_sender,
2264 browsing_context_id,
2265 );
2266
2267 let (response_stream, msg) = match response_future.await {
2269 Ok(wrapped_response) => wrapped_response,
2270 Err(error) => return Response::network_error(error),
2271 };
2272 (response_stream, msg)
2273 },
2274 };
2275
2276 if log_enabled!(log::Level::Info) {
2277 debug!("{:?} response for {}", response_stream.version(), url);
2278 for header in response_stream.headers().iter() {
2279 debug!(" - {:?}", header);
2280 }
2281 }
2282
2283 match fetch_terminated_receiver.recv().await {
2286 Some(true) => return Response::network_error(NetworkError::ConnectionFailure),
2287 Some(false) => {},
2288 _ => warn!("Failed to receive confirmation request was streamed without error."),
2289 }
2290
2291 let timing = context.timing.inner().clone();
2292 let mut response = Response::new(url.clone(), timing);
2293
2294 if let Some(handshake_info) = response_stream.extensions().get::<TlsHandshakeInfo>() {
2295 let mut hsts_enabled = url
2296 .host_str()
2297 .is_some_and(|host| context.state.hsts_list.read().is_host_secure(host));
2298
2299 if url.scheme() == "https" &&
2300 let Some(strict_transport_security) = response_stream
2301 .headers()
2302 .typed_get::<StrictTransportSecurity>()
2303 {
2304 hsts_enabled = strict_transport_security.max_age().as_secs() > 0;
2306 }
2307 response.tls_security_info = Some(build_tls_security_info(handshake_info, hsts_enabled));
2308 }
2309
2310 let status_text = response_stream
2311 .extensions()
2312 .get::<ReasonPhrase>()
2313 .map(ReasonPhrase::as_bytes)
2314 .or_else(|| {
2315 response_stream
2316 .status()
2317 .canonical_reason()
2318 .map(str::as_bytes)
2319 })
2320 .map(Vec::from)
2321 .unwrap_or_default();
2322 response.status = HttpStatus::new(response_stream.status(), status_text);
2323
2324 info!(
2325 "got {:?} response for {:?}",
2326 response_stream.status(),
2327 request.url()
2328 );
2329 response.headers = response_stream.headers().clone();
2330 response.referrer = request.referrer.to_url().cloned();
2331 response.referrer_policy = request.referrer_policy;
2332
2333 let response_body = response.body.clone();
2334
2335 let (done_sender, done_receiver) = unbounded_channel();
2337 *done_chan = Some((done_sender.clone(), done_receiver));
2338
2339 let cancellation_listener = context.cancellation_listener.clone();
2340 if cancellation_listener.cancelled() {
2341 return Response::network_error(NetworkError::LoadCancelled);
2342 }
2343
2344 #[cfg(feature = "devtools")]
2345 let (devtools_request, status, headers, devtools_chan) = {
2346 if let Some(ref sender) = context.devtools_chan &&
2347 let Some(msg) = msg
2348 {
2349 send_request_to_devtools(msg, sender);
2350 }
2351 (
2352 request.clone(),
2353 response.status.clone(),
2354 response.headers.clone(),
2355 context.devtools_chan.clone(),
2356 )
2357 };
2358
2359 let done_sender2 = done_sender.clone();
2360 let done_sender3 = done_sender.clone();
2361 let timing_ptr2 = context.timing.clone();
2362 let timing_ptr3 = context.timing.clone();
2363 let url1 = request.url();
2364 let url2 = url1.clone();
2365
2366 let prealloc_size: usize = if let Some(possible_length) = response_stream
2367 .headers()
2368 .get(http::header::CONTENT_LENGTH)
2369 .and_then(|header_value| header_value.to_str().ok())
2370 .and_then(|s| s.parse::<usize>().ok())
2371 {
2372 let multiplier: usize = if response_stream.body().is_encoded() {
2375 5
2376 } else {
2377 1
2378 };
2379 possible_length.saturating_mul(multiplier)
2380 } else {
2381 4096
2384 }
2385 .min(pref!(network_max_content_length) as usize);
2386 let _ = done_sender.send(Data::ContentLength(prealloc_size));
2387 *response_body.lock() = ResponseBody::Receiving(Vec::with_capacity(prealloc_size));
2388 let response_body2 = response_body.clone();
2389
2390 spawn_task(
2391 response_stream
2392 .into_body()
2393 .try_fold(response_body, move |response_body_accumulator, chunk| {
2394 if cancellation_listener.cancelled() {
2395 *response_body_accumulator.lock() = ResponseBody::Done(vec![]);
2396 let _ = done_sender.send(Data::Cancelled);
2397 return future::ready(Err(std::io::Error::new(
2398 std::io::ErrorKind::Interrupted,
2399 "Fetch aborted",
2400 )));
2401 }
2402 if let ResponseBody::Receiving(ref mut body) = *response_body_accumulator.lock() {
2403 body.extend_from_slice(&chunk);
2404 let _ = done_sender.send(Data::Payload(chunk));
2405 }
2406 future::ready(Ok(response_body_accumulator))
2407 })
2408 .and_then(move |complete_response_body| {
2409 debug!("successfully finished response for {:?}", url1);
2410 let mut body = complete_response_body.lock();
2411 let mut completed_body = match *body {
2412 ResponseBody::Receiving(ref mut body) => std::mem::take(body),
2413 _ => vec![],
2414 };
2415 completed_body.shrink_to_fit();
2417 #[cfg(feature = "devtools")]
2420 let devtools_response_body =
2421 devtools_chan.is_some().then(|| completed_body.clone());
2422 *body = ResponseBody::Done(completed_body);
2423 #[cfg(feature = "devtools")]
2424 send_response_values_to_devtools(
2425 Some(headers),
2426 status,
2427 devtools_response_body,
2428 CacheState::None,
2429 &devtools_request,
2430 devtools_chan,
2431 );
2432 timing_ptr2.set_attribute(ResourceAttribute::ResponseEnd);
2433 let _ = done_sender2.send(Data::Done);
2434 future::ready(Ok(()))
2435 })
2436 .map_err(move |error| {
2437 if let std::io::ErrorKind::InvalidData = error.kind() {
2438 debug!("Content decompression error for {:?}", url2);
2439 let _ = done_sender3.send(Data::Error(NetworkError::DecompressionError));
2440 let mut body = response_body2.lock();
2441
2442 *body = ResponseBody::Done(vec![]);
2443 }
2444 debug!("finished response for {:?}", url2);
2445 let mut body = response_body2.lock();
2446 let completed_body = match *body {
2447 ResponseBody::Receiving(ref mut body) => std::mem::take(body),
2448 _ => vec![],
2449 };
2450 *body = ResponseBody::Done(completed_body);
2451 timing_ptr3.set_attribute(ResourceAttribute::ResponseEnd);
2452 let _ = done_sender3.send(Data::Done);
2453 }),
2454 );
2455
2456 if credentials_flag {
2474 set_cookies_from_headers(&url, &response.headers, &context.state.cookie_jar);
2475 }
2476 context
2477 .state
2478 .hsts_list
2479 .write()
2480 .update_hsts_list_from_response(&url, &response.headers);
2481
2482 response_end_timer.neuter();
2496 response
2497}
2498
2499async fn cors_preflight_fetch(
2501 request: &Request,
2502 cache: &mut CorsCache,
2503 context: &FetchContext,
2504) -> Response {
2505 let mut preflight = RequestBuilder::new(
2510 request.target_webview_id,
2511 request.current_url_with_blob_claim(),
2512 request.referrer.clone(),
2513 )
2514 .method(Method::OPTIONS)
2515 .origin(match &request.origin {
2516 Origin::Client => {
2517 unreachable!("We shouldn't get Client origin in cors_preflight_fetch.")
2518 },
2519 Origin::Origin(origin) => origin.clone(),
2520 })
2521 .pipeline_id(request.pipeline_id)
2522 .initiator(request.initiator)
2523 .destination(request.destination)
2524 .referrer_policy(request.referrer_policy)
2525 .mode(RequestMode::CorsMode)
2526 .response_tainting(ResponseTainting::CorsTainting)
2527 .policy_container(match &request.policy_container {
2528 RequestPolicyContainer::Client => {
2529 unreachable!("We should have a policy container for request in cors_preflight_fetch")
2530 },
2531 RequestPolicyContainer::PolicyContainer(policy_container) => policy_container.clone(),
2532 })
2533 .url_list(
2534 request
2535 .url_list
2536 .iter()
2537 .map(|claimed_url| claimed_url.url())
2538 .collect(),
2539 )
2540 .build();
2541
2542 preflight
2544 .headers
2545 .insert(ACCEPT, HeaderValue::from_static("*/*"));
2546
2547 preflight
2549 .headers
2550 .typed_insert::<AccessControlRequestMethod>(AccessControlRequestMethod::from(
2551 request.method.clone(),
2552 ));
2553
2554 let headers = get_cors_unsafe_header_names(&request.headers);
2556
2557 if !headers.is_empty() {
2559 preflight.headers.insert(
2562 ACCESS_CONTROL_REQUEST_HEADERS,
2563 HeaderValue::from_bytes(itertools::join(headers.iter(), ",").as_bytes())
2564 .unwrap_or(HeaderValue::from_static("")),
2565 );
2566 }
2567
2568 let mut fetch_params = FetchParams::new(preflight);
2571 let response =
2572 http_network_or_cache_fetch(&mut fetch_params, false, false, &mut None, context).await;
2573
2574 if cors_check(request, &response).is_ok() && response.status.code().is_success() {
2576 let mut methods = if response
2579 .headers
2580 .contains_key(header::ACCESS_CONTROL_ALLOW_METHODS)
2581 {
2582 match response.headers.typed_get::<AccessControlAllowMethods>() {
2583 Some(methods) => methods.iter().collect(),
2584 None => {
2586 return Response::network_error(NetworkError::CorsAllowMethods);
2587 },
2588 }
2589 } else {
2590 vec![]
2591 };
2592
2593 let header_names = if response
2596 .headers
2597 .contains_key(header::ACCESS_CONTROL_ALLOW_HEADERS)
2598 {
2599 match response.headers.typed_get::<AccessControlAllowHeaders>() {
2600 Some(names) => names.iter().collect(),
2601 None => {
2603 return Response::network_error(NetworkError::CorsAllowHeaders);
2604 },
2605 }
2606 } else {
2607 vec![]
2608 };
2609
2610 debug!(
2611 "CORS check: Allowed methods: {:?}, current method: {:?}",
2612 methods, request.method
2613 );
2614
2615 if methods.is_empty() && request.use_cors_preflight {
2618 methods = vec![request.method.clone()];
2619 }
2620
2621 if methods
2624 .iter()
2625 .all(|method| *method.as_str() != *request.method.as_ref()) &&
2626 !is_cors_safelisted_method(&request.method) &&
2627 (request.credentials_mode == CredentialsMode::Include ||
2628 methods.iter().all(|method| method.as_ref() != "*"))
2629 {
2630 return Response::network_error(NetworkError::CorsMethod);
2631 }
2632
2633 debug!(
2634 "CORS check: Allowed headers: {:?}, current headers: {:?}",
2635 header_names, request.headers
2636 );
2637
2638 let header_names_set: HashSet<&HeaderName> = HashSet::from_iter(header_names.iter());
2644 if request.headers.iter().any(|(name, _)| {
2645 is_cors_non_wildcard_request_header_name(name) &&
2646 !header_names_set.contains(name) &&
2647 !header_names_set.contains(&HeaderName::from_static("*"))
2648 }) {
2649 return Response::network_error(NetworkError::CorsAuthorization);
2650 }
2651
2652 let unsafe_names = get_cors_unsafe_header_names(&request.headers);
2656 for unsafe_name in unsafe_names.iter() {
2657 if !header_names_set.contains(unsafe_name) &&
2658 (request.credentials_mode == CredentialsMode::Include ||
2659 !header_names_set.contains(&HeaderName::from_static("*")))
2660 {
2661 return Response::network_error(NetworkError::CorsHeaders);
2662 }
2663 }
2664
2665 let max_age: Option<Duration> = response
2668 .headers
2669 .typed_get::<AccessControlMaxAge>()
2670 .map(|acma| acma.into());
2671
2672 let max_age = max_age.unwrap_or(Duration::from_secs(5));
2674
2675 for method in &methods {
2686 cache.match_method_and_update(request, method.clone(), max_age);
2687 }
2688
2689 for header_name in &header_names {
2694 cache.match_header_and_update(request, header_name, max_age);
2695 }
2696
2697 return response;
2699 }
2700
2701 Response::network_error(NetworkError::CorsGeneral)
2703}
2704
2705fn cors_check(request: &Request, response: &Response) -> Result<(), ()> {
2707 let Some(origins) =
2709 get_value_from_header_list(ACCESS_CONTROL_ALLOW_ORIGIN.as_str(), &response.headers)
2710 else {
2711 return Err(());
2713 };
2714 let origin = String::from_utf8(origins).map_err(|_| ())?;
2715
2716 if request.credentials_mode != CredentialsMode::Include && origin == "*" {
2718 return Ok(());
2719 }
2720
2721 if serialize_request_origin(request).to_string() != origin {
2723 return Err(());
2724 }
2725
2726 if request.credentials_mode != CredentialsMode::Include {
2728 return Ok(());
2729 }
2730
2731 let credentials = response
2733 .headers
2734 .typed_get::<AccessControlAllowCredentials>();
2735
2736 if credentials.is_some() {
2738 return Ok(());
2739 }
2740
2741 Err(())
2743}
2744
2745fn has_credentials(url: &ServoUrl) -> bool {
2746 !url.username().is_empty() || url.password().is_some()
2747}
2748
2749fn is_no_store_cache(headers: &HeaderMap) -> bool {
2750 headers.contains_key(header::IF_MODIFIED_SINCE) |
2751 headers.contains_key(header::IF_NONE_MATCH) |
2752 headers.contains_key(header::IF_UNMODIFIED_SINCE) |
2753 headers.contains_key(header::IF_MATCH) |
2754 headers.contains_key(header::IF_RANGE)
2755}
2756
2757fn is_redirect_status(status: StatusCode) -> bool {
2759 matches!(
2760 status,
2761 StatusCode::MOVED_PERMANENTLY |
2762 StatusCode::FOUND |
2763 StatusCode::SEE_OTHER |
2764 StatusCode::TEMPORARY_REDIRECT |
2765 StatusCode::PERMANENT_REDIRECT
2766 )
2767}
2768
2769fn serialize_request_origin(request: &Request) -> headers::Origin {
2771 let Origin::Origin(origin) = &request.origin else {
2773 panic!("origin cannot be \"client\" at this point in time");
2774 };
2775
2776 if request.redirect_taint_for_request() != RedirectTaint::SameOrigin {
2778 return headers::Origin::NULL;
2779 }
2780
2781 serialize_origin(origin)
2783}
2784
2785pub fn serialize_origin(origin: &ImmutableOrigin) -> headers::Origin {
2787 match origin {
2788 ImmutableOrigin::Opaque(_) => headers::Origin::NULL,
2789 ImmutableOrigin::Tuple(scheme, host, port) => {
2790 let port = match (scheme.as_ref(), port) {
2793 ("http" | "ws", 80) | ("https" | "wss", 443) | ("ftp", 21) => None,
2794 _ => Some(*port),
2795 };
2796
2797 headers::Origin::try_from_parts(scheme, &host.to_string(), port)
2799 .unwrap_or(headers::Origin::NULL)
2800 },
2801 }
2802}
2803
2804#[expect(
2806 clippy::collapsible_match,
2807 reason = "The current way follows the spec more closely"
2808)]
2809fn append_a_request_origin_header(request: &mut Request) {
2810 let Origin::Origin(request_origin) = &request.origin else {
2812 panic!("origin cannot be \"client\" at this point in time");
2813 };
2814
2815 let mut serialized_origin = serialize_request_origin(request);
2817
2818 if request.response_tainting == ResponseTainting::CorsTainting ||
2821 matches!(request.mode, RequestMode::WebSocket { .. })
2822 {
2823 request.headers.typed_insert(serialized_origin);
2824 }
2825 else if !matches!(request.method, Method::GET | Method::HEAD) {
2827 if request.mode != RequestMode::CorsMode {
2829 match request.referrer_policy {
2830 ReferrerPolicy::NoReferrer => {
2831 serialized_origin = headers::Origin::NULL;
2833 },
2834 ReferrerPolicy::NoReferrerWhenDowngrade |
2835 ReferrerPolicy::StrictOrigin |
2836 ReferrerPolicy::StrictOriginWhenCrossOrigin => {
2837 if let ImmutableOrigin::Tuple(scheme, _, _) = &request_origin &&
2840 scheme == "https" &&
2841 request.current_url().scheme() != "https"
2842 {
2843 serialized_origin = headers::Origin::NULL;
2844 }
2845 },
2846 ReferrerPolicy::SameOrigin => {
2847 if *request_origin != request.current_url().origin() {
2850 serialized_origin = headers::Origin::NULL;
2851 }
2852 },
2853 _ => {
2854 },
2856 };
2857 }
2858
2859 request.headers.typed_insert(serialized_origin);
2861 }
2862}
2863
2864fn append_the_fetch_metadata_headers(r: &mut Request) {
2866 if !r.current_url().is_potentially_trustworthy() {
2868 return;
2869 }
2870
2871 set_the_sec_fetch_dest_header(r);
2873
2874 set_the_sec_fetch_mode_header(r);
2876
2877 set_the_sec_fetch_site_header(r);
2879
2880 set_the_sec_fetch_user_header(r);
2882}
2883
2884fn append_cache_data_to_headers(http_request: &mut Request) {
2886 match http_request.cache_mode {
2887 CacheMode::Default if is_no_store_cache(&http_request.headers) => {
2891 http_request.cache_mode = CacheMode::NoStore;
2892 },
2893
2894 CacheMode::NoCache if !http_request.headers.contains_key(header::CACHE_CONTROL) => {
2904 http_request
2905 .headers
2906 .typed_insert(CacheControl::new().with_max_age(Duration::from_secs(0)));
2907 },
2908
2909 CacheMode::Reload | CacheMode::NoStore => {
2911 if !http_request.headers.contains_key(header::PRAGMA) {
2914 http_request.headers.typed_insert(Pragma::no_cache());
2915 }
2916
2917 if !http_request.headers.contains_key(header::CACHE_CONTROL) {
2920 http_request
2921 .headers
2922 .typed_insert(CacheControl::new().with_no_cache());
2923 }
2924 },
2925
2926 _ => {},
2927 }
2928}
2929
2930fn set_the_sec_fetch_dest_header(r: &mut Request) {
2932 debug_assert!(r.current_url().is_potentially_trustworthy());
2934
2935 let header = r.destination;
2939
2940 r.headers.typed_insert(SecFetchDest(header));
2942}
2943
2944fn set_the_sec_fetch_mode_header(r: &mut Request) {
2946 debug_assert!(r.current_url().is_potentially_trustworthy());
2948
2949 let header = &r.mode;
2952
2953 r.headers.typed_insert(SecFetchMode::from(header));
2955}
2956
2957fn set_the_sec_fetch_site_header(r: &mut Request) {
2959 let Origin::Origin(request_origin) = &r.origin else {
2962 panic!("request origin cannot be \"client\" at this point")
2963 };
2964
2965 debug_assert!(r.current_url().is_potentially_trustworthy());
2967
2968 let mut header = SecFetchSite::SameOrigin;
2971
2972 if header != SecFetchSite::None {
2977 for url in &r.url_list {
2978 if url.origin() == *request_origin {
2980 continue;
2981 }
2982
2983 header = SecFetchSite::CrossSite;
2985
2986 if !is_same_site(request_origin, &url.origin()) {
2988 break;
2989 }
2990
2991 header = SecFetchSite::SameSite;
2993 }
2994 }
2995
2996 r.headers.typed_insert(header);
2998}
2999
3000fn set_the_sec_fetch_user_header(r: &mut Request) {
3002 debug_assert!(r.current_url().is_potentially_trustworthy());
3004
3005 if !r.is_navigation_request() {
3008 return;
3009 }
3010
3011 let header = SecFetchUser;
3014
3015 r.headers.typed_insert(header);
3017}
3018
3019fn set_requests_referrer_policy_on_redirect(request: &mut Request, response: &Response) {
3021 let referrer_policy: ReferrerPolicy = response
3024 .headers
3025 .typed_get::<headers::ReferrerPolicy>()
3026 .into();
3027
3028 if referrer_policy != ReferrerPolicy::EmptyString {
3030 request.referrer_policy = referrer_policy;
3031 }
3032}