1use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::{io, mem, str};
8
9use base::id::PipelineId;
10use base64::Engine as _;
11use base64::engine::general_purpose;
12use content_security_policy as csp;
13use crossbeam_channel::Sender;
14use devtools_traits::DevtoolsControlMsg;
15use embedder_traits::resources::{self, Resource};
16use headers::{AccessControlExposeHeaders, ContentType, HeaderMapExt};
17use http::header::{self, HeaderMap, HeaderName, RANGE};
18use http::{HeaderValue, Method, StatusCode};
19use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
20use log::{debug, trace, warn};
21use malloc_size_of_derive::MallocSizeOf;
22use mime::{self, Mime};
23use net_traits::fetch::headers::{determine_nosniff, extract_mime_type_as_mime};
24use net_traits::filemanager_thread::{FileTokenCheck, RelativePos};
25use net_traits::http_status::HttpStatus;
26use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer};
27use net_traits::request::{
28 BodyChunkRequest, BodyChunkResponse, CredentialsMode, Destination, Initiator,
29 InsecureRequestsPolicy, Origin, ParserMetadata, RedirectMode, Referrer, Request, RequestId,
30 RequestMode, ResponseTainting, is_cors_safelisted_method, is_cors_safelisted_request_header,
31};
32use net_traits::response::{Response, ResponseBody, ResponseType, TerminationReason};
33use net_traits::{
34 FetchTaskTarget, NetworkError, ReferrerPolicy, ResourceAttribute, ResourceFetchTiming,
35 ResourceTimeValue, ResourceTimingType, WebSocketDomAction, WebSocketNetworkEvent,
36 set_default_accept_language,
37};
38use parking_lot::Mutex;
39use rustc_hash::FxHashMap;
40use rustls_pki_types::CertificateDer;
41use serde::{Deserialize, Serialize};
42use servo_arc::Arc as ServoArc;
43use servo_url::{Host, ImmutableOrigin, ServoUrl};
44use tokio::sync::Mutex as TokioMutex;
45use tokio::sync::mpsc::{UnboundedReceiver as TokioReceiver, UnboundedSender as TokioSender};
46
47use crate::connector::CACertificates;
48use crate::fetch::cors_cache::CorsCache;
49use crate::fetch::fetch_params::{
50 ConsumePreloadedResources, FetchParams, SharedPreloadedResources,
51};
52use crate::filemanager_thread::FileManager;
53use crate::http_loader::{
54 HttpState, determine_requests_referrer, http_fetch, send_early_httprequest_to_devtools,
55 send_response_to_devtools, send_security_info_to_devtools, set_default_accept,
56};
57use crate::protocols::{ProtocolRegistry, is_url_potentially_trustworthy};
58use crate::request_interceptor::RequestInterceptor;
59use crate::subresource_integrity::is_response_integrity_valid;
60
61pub type Target<'a> = &'a mut (dyn FetchTaskTarget + Send);
62
63#[derive(Clone, Deserialize, Serialize)]
64pub enum Data {
65 Payload(Vec<u8>),
66 Done,
67 Cancelled,
68 Error(NetworkError),
69}
70
71pub struct WebSocketChannel {
72 pub sender: IpcSender<WebSocketNetworkEvent>,
73 pub receiver: Option<IpcReceiver<WebSocketDomAction>>,
74}
75
76impl WebSocketChannel {
77 pub fn new(
78 sender: IpcSender<WebSocketNetworkEvent>,
79 receiver: Option<IpcReceiver<WebSocketDomAction>>,
80 ) -> Self {
81 Self { sender, receiver }
82 }
83}
84
85#[derive(Clone, Debug, Deserialize, MallocSizeOf, Serialize)]
87pub struct InFlightKeepAliveRecord {
88 pub(crate) request_id: RequestId,
89 pub(crate) keep_alive_body_length: u64,
91}
92
93pub type SharedInflightKeepAliveRecords =
94 Arc<Mutex<FxHashMap<PipelineId, Vec<InFlightKeepAliveRecord>>>>;
95
96#[derive(Clone)]
97pub struct FetchContext {
98 pub state: Arc<HttpState>,
99 pub user_agent: String,
100 pub devtools_chan: Option<Sender<DevtoolsControlMsg>>,
101 pub filemanager: FileManager,
102 pub file_token: FileTokenCheck,
103 pub request_interceptor: Arc<TokioMutex<RequestInterceptor>>,
104 pub cancellation_listener: Arc<CancellationListener>,
105 pub timing: ServoArc<Mutex<ResourceFetchTiming>>,
106 pub protocols: Arc<ProtocolRegistry>,
107 pub websocket_chan: Option<Arc<Mutex<WebSocketChannel>>>,
108 pub ca_certificates: CACertificates<'static>,
109 pub ignore_certificate_errors: bool,
110 pub preloaded_resources: SharedPreloadedResources,
111 pub in_flight_keep_alive_records: SharedInflightKeepAliveRecords,
112}
113
114#[derive(Default)]
115pub struct CancellationListener {
116 cancelled: AtomicBool,
117}
118
119impl CancellationListener {
120 pub(crate) fn cancelled(&self) -> bool {
121 self.cancelled.load(Ordering::Relaxed)
122 }
123
124 pub(crate) fn cancel(&self) {
125 self.cancelled.store(true, Ordering::Relaxed)
126 }
127}
128pub type DoneChannel = Option<(TokioSender<Data>, TokioReceiver<Data>)>;
129
130pub async fn fetch(request: Request, target: Target<'_>, context: &FetchContext) -> Response {
132 {
135 let mut timing_guard = context.timing.lock();
136 timing_guard.set_attribute(ResourceAttribute::FetchStart);
137 timing_guard.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::FetchStart));
138 }
139 fetch_with_cors_cache(request, &mut CorsCache::default(), target, context).await
140}
141
142pub async fn fetch_with_cors_cache(
146 request: Request,
147 cache: &mut CorsCache,
148 target: Target<'_>,
149 context: &FetchContext,
150) -> Response {
151 let mut fetch_params = FetchParams::new(request);
153 let request = &mut fetch_params.request;
154
155 request.populate_request_from_client();
157
158 if
167 matches!(request.current_url().scheme(), "http" | "https")
169 && matches!(request.mode, RequestMode::SameOrigin | RequestMode::CorsMode | RequestMode::NoCors)
171 && matches!(request.method, Method::GET)
173 && (!request.unsafe_request || request.headers.is_empty())
175 {
176 if let Some(client) = request.client.as_ref() {
178 assert!(request.origin == client.origin);
180 if let Some(candidate) =
188 client.consume_preloaded_resource(request, context.preloaded_resources.clone())
189 {
190 fetch_params.preload_response_candidate = candidate;
191 }
192 }
193 }
194
195 set_default_accept(request);
197
198 set_default_accept_language(&mut request.headers);
201
202 let should_track_in_flight_record = request.keep_alive && request.is_subresource_request();
211 let pipeline_id = request.pipeline_id;
212
213 if should_track_in_flight_record {
214 let record = InFlightKeepAliveRecord {
217 request_id: request.id,
218 keep_alive_body_length: request.keep_alive_body_length(),
219 };
220 let mut in_flight_records = context.in_flight_keep_alive_records.lock();
222 in_flight_records
223 .entry(pipeline_id.expect("Must always set a pipeline ID for keep-alive requests"))
224 .or_default()
225 .push(record);
226 };
227 let request_id = request.id;
228
229 let response = main_fetch(&mut fetch_params, cache, false, target, &mut None, context).await;
231
232 if should_track_in_flight_record {
234 context
235 .in_flight_keep_alive_records
236 .lock()
237 .get_mut(&pipeline_id.expect("Must always set a pipeline ID for keep-alive requests"))
238 .expect("Must always have initialized tracked requests before starting fetch")
239 .retain(|record| record.request_id != request_id);
240 }
241
242 response
245}
246
247pub(crate) fn convert_request_to_csp_request(request: &Request) -> Option<csp::Request> {
248 let origin = match &request.origin {
249 Origin::Client => return None,
250 Origin::Origin(origin) => origin,
251 };
252
253 let mut original_url = request.original_url();
256 if original_url.scheme() == "ws" && request.url().scheme() == "https" {
257 original_url.as_mut_url().set_scheme("wss").unwrap();
258 }
259
260 let csp_request = csp::Request {
261 url: original_url.into_url(),
262 origin: origin.clone().into_url_origin(),
263 redirect_count: request.redirect_count,
264 destination: request.destination,
265 initiator: match request.initiator {
266 Initiator::Download => csp::Initiator::Download,
267 Initiator::ImageSet => csp::Initiator::ImageSet,
268 Initiator::Manifest => csp::Initiator::Manifest,
269 Initiator::Prefetch => csp::Initiator::Prefetch,
270 _ => csp::Initiator::None,
271 },
272 nonce: request.cryptographic_nonce_metadata.clone(),
273 integrity_metadata: request.integrity_metadata.clone(),
274 parser_metadata: match request.parser_metadata {
275 ParserMetadata::ParserInserted => csp::ParserMetadata::ParserInserted,
276 ParserMetadata::NotParserInserted => csp::ParserMetadata::NotParserInserted,
277 ParserMetadata::Default => csp::ParserMetadata::None,
278 },
279 };
280 Some(csp_request)
281}
282
283pub fn should_request_be_blocked_by_csp(
285 csp_request: &csp::Request,
286 policy_container: &PolicyContainer,
287) -> (csp::CheckResult, Vec<csp::Violation>) {
288 policy_container
289 .csp_list
290 .as_ref()
291 .map(|c| c.should_request_be_blocked(csp_request))
292 .unwrap_or((csp::CheckResult::Allowed, Vec::new()))
293}
294
295pub fn report_violations_for_request_by_csp(
297 csp_request: &csp::Request,
298 policy_container: &PolicyContainer,
299) -> Vec<csp::Violation> {
300 policy_container
301 .csp_list
302 .as_ref()
303 .map(|c| c.report_violations_for_request(csp_request))
304 .unwrap_or_default()
305}
306
307fn should_response_be_blocked_by_csp(
308 csp_request: &csp::Request,
309 response: &Response,
310 policy_container: &PolicyContainer,
311) -> (csp::CheckResult, Vec<csp::Violation>) {
312 if response.is_network_error() {
313 return (csp::CheckResult::Allowed, Vec::new());
314 }
315 let csp_response = csp::Response {
316 url: response
317 .actual_response()
318 .url()
319 .cloned()
320 .map(|mut url| {
325 match csp_request.url.scheme() {
326 "ws" | "wss" => {
327 url.as_mut_url()
328 .set_scheme(csp_request.url.scheme())
329 .expect("failed to set URL scheme");
330 },
331 _ => {},
332 };
333 url
334 })
335 .expect("response must have a url")
336 .into_url(),
337 redirect_count: csp_request.redirect_count,
338 };
339 policy_container
340 .csp_list
341 .as_ref()
342 .map(|c| c.should_response_to_request_be_blocked(csp_request, &csp_response))
343 .unwrap_or((csp::CheckResult::Allowed, Vec::new()))
344}
345
346pub async fn main_fetch(
348 fetch_params: &mut FetchParams,
349 cache: &mut CorsCache,
350 recursive_flag: bool,
351 target: Target<'_>,
352 done_chan: &mut DoneChannel,
353 context: &FetchContext,
354) -> Response {
355 let request = &mut fetch_params.request;
357 send_early_httprequest_to_devtools(request, context);
358 let mut response = None;
360
361 if let Some(ref details) = request.crash {
363 response = Some(Response::network_error(NetworkError::Crash(
364 details.clone(),
365 )));
366 }
367
368 if request.local_urls_only &&
371 !matches!(
372 request.current_url().scheme(),
373 "about" | "blob" | "data" | "filesystem"
374 )
375 {
376 response = Some(Response::network_error(NetworkError::UnsupportedScheme));
377 }
378
379 let policy_container = match &request.policy_container {
381 RequestPolicyContainer::Client => unreachable!(),
382 RequestPolicyContainer::PolicyContainer(container) => container.to_owned(),
383 };
384 let csp_request = convert_request_to_csp_request(request);
385 if let Some(csp_request) = csp_request.as_ref() {
386 let violations = report_violations_for_request_by_csp(csp_request, &policy_container);
388
389 if !violations.is_empty() {
390 target.process_csp_violations(request, violations);
391 }
392 };
393
394 if should_upgrade_request_to_potentially_trustworthy(request, context) ||
399 should_upgrade_mixed_content_request(request, &context.protocols)
400 {
401 trace!(
402 "upgrading {} targeting {:?}",
403 request.current_url(),
404 request.destination
405 );
406 if let Some(new_scheme) = match request.current_url().scheme() {
407 "http" => Some("https"),
408 "ws" => Some("wss"),
409 _ => None,
410 } {
411 request
412 .current_url_mut()
413 .as_mut_url()
414 .set_scheme(new_scheme)
415 .unwrap();
416 }
417 } else {
418 trace!(
419 "not upgrading {} targeting {:?} with {:?}",
420 request.current_url(),
421 request.destination,
422 request.insecure_requests_policy
423 );
424 }
425 if let Some(csp_request) = csp_request.as_ref() {
426 let (check_result, violations) =
430 should_request_be_blocked_by_csp(csp_request, &policy_container);
431
432 if !violations.is_empty() {
433 target.process_csp_violations(request, violations);
434 }
435
436 if check_result == csp::CheckResult::Blocked {
437 warn!("Request blocked by CSP");
438 response = Some(Response::network_error(NetworkError::ContentSecurityPolicy))
439 }
440 };
441 if should_request_be_blocked_due_to_a_bad_port(&request.current_url()) {
442 response = Some(Response::network_error(NetworkError::InvalidPort));
443 }
444 if should_request_be_blocked_as_mixed_content(request, &context.protocols) {
445 response = Some(Response::network_error(NetworkError::MixedContent));
446 }
447
448 if request.referrer_policy == ReferrerPolicy::EmptyString {
451 request.referrer_policy = policy_container.get_referrer_policy();
452 }
453
454 let referrer_url = match mem::replace(&mut request.referrer, Referrer::NoReferrer) {
455 Referrer::NoReferrer => None,
456 Referrer::ReferrerUrl(referrer_source) | Referrer::Client(referrer_source) => {
457 request.headers.remove(header::REFERER);
458 determine_requests_referrer(
459 request.referrer_policy,
460 referrer_source,
461 request.current_url(),
462 )
463 },
464 };
465 request.referrer = referrer_url.map_or(Referrer::NoReferrer, Referrer::ReferrerUrl);
466
467 context
472 .state
473 .hsts_list
474 .read()
475 .apply_hsts_rules(request.current_url_mut());
476
477 let current_url = request.current_url();
481 let current_scheme = current_url.scheme();
482
483 context
485 .request_interceptor
486 .lock()
487 .await
488 .intercept_request(request, &mut response, context)
489 .await;
490
491 let mut response = match response {
492 Some(res) => res,
493 None => {
494 let same_origin = if let Origin::Origin(ref origin) = request.origin {
497 *origin == current_url.origin()
498 } else {
499 false
500 };
501
502 if let Some((response, preload_id)) =
504 fetch_params.preload_response_candidate.response().await
505 {
506 response.get_resource_timing().lock().preloaded = true;
507 context
508 .preloaded_resources
509 .lock()
510 .unwrap()
511 .remove(&preload_id);
512 response
513 }
514 else if (same_origin && request.response_tainting == ResponseTainting::Basic) ||
517 current_scheme == "data" ||
519 context.protocols.is_fetchable(current_scheme) ||
522 matches!(
524 request.mode,
525 RequestMode::Navigate | RequestMode::WebSocket { .. }
526 )
527 {
528 request.response_tainting = ResponseTainting::Basic;
530
531 scheme_fetch(fetch_params, cache, target, done_chan, context).await
533 } else if request.mode == RequestMode::SameOrigin {
534 Response::network_error(NetworkError::CrossOriginResponse)
535 } else if request.mode == RequestMode::NoCors {
536 if request.redirect_mode != RedirectMode::Follow {
538 Response::network_error(NetworkError::RedirectError)
539 } else {
540 request.response_tainting = ResponseTainting::Opaque;
542
543 scheme_fetch(fetch_params, cache, target, done_chan, context).await
545 }
546 } else if !matches!(current_scheme, "http" | "https") {
547 Response::network_error(NetworkError::UnsupportedScheme)
548 } else if request.use_cors_preflight ||
549 (request.unsafe_request &&
550 (!is_cors_safelisted_method(&request.method) ||
551 request.headers.iter().any(|(name, value)| {
552 !is_cors_safelisted_request_header(&name, &value)
553 })))
554 {
555 request.response_tainting = ResponseTainting::CorsTainting;
557 let response = http_fetch(
559 fetch_params,
560 cache,
561 true,
562 true,
563 false,
564 target,
565 done_chan,
566 context,
567 )
568 .await;
569 if response.is_network_error() {
571 }
573 response
575 } else {
576 request.response_tainting = ResponseTainting::CorsTainting;
578 http_fetch(
580 fetch_params,
581 cache,
582 true,
583 false,
584 false,
585 target,
586 done_chan,
587 context,
588 )
589 .await
590 }
591 },
592 };
593
594 if recursive_flag {
596 return response;
597 }
598
599 let request = &mut fetch_params.request;
601
602 let mut response = if !response.is_network_error() && response.internal_response.is_none() {
604 if request.response_tainting == ResponseTainting::CorsTainting {
606 let header_names: Option<Vec<HeaderName>> = response
608 .headers
609 .typed_get::<AccessControlExposeHeaders>()
610 .map(|v| v.iter().collect());
611 match header_names {
612 Some(ref list)
614 if request.credentials_mode != CredentialsMode::Include &&
615 list.iter().any(|header| header == "*") =>
616 {
617 response.cors_exposed_header_name_list = response
618 .headers
619 .iter()
620 .map(|(name, _)| name.as_str().to_owned())
621 .collect();
622 },
623 Some(list) => {
625 response.cors_exposed_header_name_list =
626 list.iter().map(|h| h.as_str().to_owned()).collect();
627 },
628 _ => (),
629 }
630 }
631
632 let response_type = match request.response_tainting {
634 ResponseTainting::Basic => ResponseType::Basic,
635 ResponseTainting::CorsTainting => ResponseType::Cors,
636 ResponseTainting::Opaque => ResponseType::Opaque,
637 };
638 response.to_filtered(response_type)
639 } else {
640 response
641 };
642
643 let internal_error = {
644 let response_is_network_error = response.is_network_error();
646 let should_replace_with_nosniff_error = !response_is_network_error &&
647 should_be_blocked_due_to_nosniff(request.destination, &response.headers);
648 let should_replace_with_mime_type_error = !response_is_network_error &&
649 should_be_blocked_due_to_mime_type(request.destination, &response.headers);
650 let should_replace_with_mixed_content = !response_is_network_error &&
651 should_response_be_blocked_as_mixed_content(request, &response, &context.protocols);
652 let should_replace_with_csp_error = csp_request.is_some_and(|csp_request| {
653 let (check_result, violations) =
654 should_response_be_blocked_by_csp(&csp_request, &response, &policy_container);
655 if !violations.is_empty() {
656 target.process_csp_violations(request, violations);
657 }
658 check_result == csp::CheckResult::Blocked
659 });
660
661 let mut network_error_response = response
663 .get_network_error()
664 .cloned()
665 .map(Response::network_error);
666
667 let response_type = response.response_type.clone(); let internal_response = if let Some(error_response) = network_error_response.as_mut() {
671 error_response
672 } else {
673 response.actual_response_mut()
674 };
675
676 if internal_response.url_list.is_empty() {
678 internal_response.url_list.clone_from(&request.url_list)
679 }
680
681 internal_response.redirect_taint = request.redirect_taint_for_request();
683
684 let mut blocked_error_response;
690
691 let internal_response = if should_replace_with_nosniff_error {
692 blocked_error_response = Response::network_error(NetworkError::Nosniff);
694 &blocked_error_response
695 } else if should_replace_with_mime_type_error {
696 blocked_error_response =
698 Response::network_error(NetworkError::MimeType("Blocked by MIME type".into()));
699 &blocked_error_response
700 } else if should_replace_with_mixed_content {
701 blocked_error_response = Response::network_error(NetworkError::MixedContent);
702 &blocked_error_response
703 } else if should_replace_with_csp_error {
704 blocked_error_response = Response::network_error(NetworkError::ContentSecurityPolicy);
705 &blocked_error_response
706 } else {
707 internal_response
708 };
709
710 let internal_response = if !internal_response.is_network_error() &&
716 response_type == ResponseType::Opaque &&
717 internal_response.status.code() == StatusCode::PARTIAL_CONTENT &&
718 internal_response.range_requested &&
719 !request.headers.contains_key(RANGE)
720 {
721 blocked_error_response =
723 Response::network_error(NetworkError::PartialResponseToNonRangeRequestError);
724 &blocked_error_response
725 } else {
726 internal_response
727 };
728
729 let not_network_error = !response_is_network_error && !internal_response.is_network_error();
734 if not_network_error &&
735 (is_null_body_status(&internal_response.status) ||
736 matches!(request.method, Method::HEAD | Method::CONNECT))
737 {
738 let mut body = internal_response.body.lock();
741 *body = ResponseBody::Empty;
742 }
743
744 internal_response.get_network_error().cloned()
745 };
746
747 let mut response = if let Some(error) = internal_error {
749 Response::network_error(error)
750 } else {
751 response
752 };
753
754 let mut response_loaded = false;
756 let mut response = if !response.is_network_error() && !request.integrity_metadata.is_empty() {
757 wait_for_response(request, &mut response, target, done_chan, context).await;
759 response_loaded = true;
760
761 let integrity_metadata = &request.integrity_metadata;
763 if response.termination_reason.is_none() &&
764 !is_response_integrity_valid(integrity_metadata, &response)
765 {
766 Response::network_error(NetworkError::SubresourceIntegrity)
767 } else {
768 response
769 }
770 } else {
771 response
772 };
773
774 if request.synchronous {
776 target.process_response(request, &response);
779 if !response_loaded {
780 wait_for_response(request, &mut response, target, done_chan, context).await;
781 }
782 target.process_response_eof(request, &response);
784 return response;
785 }
786
787 if request.body.is_some() && matches!(current_scheme, "http" | "https") {
789 target.process_request_body(request);
794 target.process_request_eof(request);
795 }
796
797 target.process_response(request, &response);
799 send_response_to_devtools(request, context, &response, None);
801 send_security_info_to_devtools(request, context, &response);
802
803 if !response_loaded {
805 wait_for_response(request, &mut response, target, done_chan, context).await;
806 }
807
808 target.process_response_eof(request, &response);
810 send_response_to_devtools(request, context, &response, None);
814
815 context
816 .state
817 .http_cache
818 .update_awaiting_consumers(request, &response)
819 .await;
820
821 response
824}
825
826async fn wait_for_response(
827 request: &Request,
828 response: &mut Response,
829 target: Target<'_>,
830 done_chan: &mut DoneChannel,
831 context: &FetchContext,
832) {
833 if let Some(ref mut ch) = *done_chan {
834 let mut devtools_body = context.devtools_chan.as_ref().map(|_| Vec::new());
835 loop {
836 match ch.1.recv().await {
837 Some(Data::Payload(vec)) => {
838 if let Some(body) = devtools_body.as_mut() {
839 body.extend(&vec);
840 }
841 target.process_response_chunk(request, vec);
842 },
843 Some(Data::Error(network_error)) => {
844 if network_error == NetworkError::DecompressionError {
845 response.termination_reason = Some(TerminationReason::Fatal);
846 }
847 response.set_network_error(network_error);
848
849 break;
850 },
851 Some(Data::Done) => {
852 send_response_to_devtools(request, context, response, devtools_body);
853 break;
854 },
855 Some(Data::Cancelled) => {
856 response.aborted.store(true, Ordering::Release);
857 break;
858 },
859 _ => {
860 panic!("fetch worker should always send Done before terminating");
861 },
862 }
863 }
864 } else {
865 match *response.actual_response().body.lock() {
866 ResponseBody::Done(ref vec) if !vec.is_empty() => {
867 target.process_response_chunk(request, vec.clone());
871 if context.devtools_chan.is_some() {
872 send_response_to_devtools(request, context, response, Some(vec.clone()));
875 }
876 },
877 ResponseBody::Done(_) | ResponseBody::Empty => {},
878 _ => unreachable!(),
879 }
880 }
881}
882
883pub enum RangeRequestBounds {
885 Final(RelativePos),
887 Pending(u64),
890}
891
892impl RangeRequestBounds {
893 pub fn get_final(&self, len: Option<u64>) -> Result<RelativePos, &'static str> {
894 match self {
895 RangeRequestBounds::Final(pos) => {
896 if let Some(len) = len {
897 if pos.start <= len as i64 {
898 return Ok(*pos);
899 }
900 }
901 Err("Tried to process RangeRequestBounds::Final without len")
902 },
903 RangeRequestBounds::Pending(offset) => Ok(RelativePos::from_opts(
904 if let Some(len) = len {
905 Some((len - u64::min(len, *offset)) as i64)
906 } else {
907 Some(0)
908 },
909 None,
910 )),
911 }
912 }
913}
914
915fn create_blank_reply(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
916 let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
917 response
918 .headers
919 .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
920 *response.body.lock() = ResponseBody::Done(vec![]);
921 response.status = HttpStatus::default();
922 response
923}
924
925fn create_about_memory(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
926 let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
927 response
928 .headers
929 .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
930 *response.body.lock() = ResponseBody::Done(resources::read_bytes(Resource::AboutMemoryHTML));
931 response.status = HttpStatus::default();
932 response
933}
934
935fn handle_allowcert_request(request: &mut Request, context: &FetchContext) -> io::Result<()> {
937 let error = |string| Err(io::Error::other(string));
938
939 let body = match request.body.as_mut() {
940 Some(body) => body,
941 None => return error("No body found"),
942 };
943
944 let stream = body.take_stream();
945 let stream = stream.lock();
946 let (body_chan, body_port) = ipc::channel().unwrap();
947 let _ = stream.send(BodyChunkRequest::Connect(body_chan));
948 let _ = stream.send(BodyChunkRequest::Chunk);
949 let body_bytes = match body_port.recv().ok() {
950 Some(BodyChunkResponse::Chunk(bytes)) => bytes,
951 _ => return error("Certificate not sent in a single chunk"),
952 };
953
954 let split_idx = match body_bytes.iter().position(|b| *b == b'&') {
955 Some(split_idx) => split_idx,
956 None => return error("Could not find ampersand in data"),
957 };
958 let (secret, cert_base64) = body_bytes.split_at(split_idx);
959
960 let secret = str::from_utf8(secret).ok().and_then(|s| s.parse().ok());
961 if secret != Some(*net_traits::PRIVILEGED_SECRET) {
962 return error("Invalid secret sent. Ignoring request");
963 }
964
965 let cert_bytes = match general_purpose::STANDARD_NO_PAD.decode(&cert_base64[1..]) {
966 Ok(bytes) => bytes,
967 Err(_) => return error("Could not decode certificate base64"),
968 };
969
970 context
971 .state
972 .override_manager
973 .add_override(&CertificateDer::from_slice(&cert_bytes).into_owned());
974 Ok(())
975}
976
977async fn scheme_fetch(
979 fetch_params: &mut FetchParams,
980 cache: &mut CorsCache,
981 target: Target<'_>,
982 done_chan: &mut DoneChannel,
983 context: &FetchContext,
984) -> Response {
985 let request = &mut fetch_params.request;
989 let url = request.current_url();
990
991 let scheme = url.scheme();
992 match scheme {
993 "about" if url.path() == "blank" => create_blank_reply(url, request.timing_type()),
994 "about" if url.path() == "memory" => create_about_memory(url, request.timing_type()),
995
996 "chrome" if url.path() == "allowcert" => {
997 if let Err(error) = handle_allowcert_request(request, context) {
998 warn!("Could not handle allowcert request: {error}");
999 }
1000 create_blank_reply(url, request.timing_type())
1001 },
1002
1003 "http" | "https" => {
1004 http_fetch(
1005 fetch_params,
1006 cache,
1007 false,
1008 false,
1009 false,
1010 target,
1011 done_chan,
1012 context,
1013 )
1014 .await
1015 },
1016
1017 _ => match context.protocols.get(scheme) {
1018 Some(handler) => handler.load(request, done_chan, context).await,
1019 None => Response::network_error(NetworkError::UnsupportedScheme),
1020 },
1021 }
1022}
1023
1024fn is_null_body_status(status: &HttpStatus) -> bool {
1025 matches!(
1026 status.try_code(),
1027 Some(StatusCode::SWITCHING_PROTOCOLS) |
1028 Some(StatusCode::NO_CONTENT) |
1029 Some(StatusCode::RESET_CONTENT) |
1030 Some(StatusCode::NOT_MODIFIED)
1031 )
1032}
1033
1034pub fn should_be_blocked_due_to_nosniff(
1036 destination: Destination,
1037 response_headers: &HeaderMap,
1038) -> bool {
1039 if !determine_nosniff(response_headers) {
1041 return false;
1042 }
1043
1044 let mime_type = extract_mime_type_as_mime(response_headers);
1047
1048 #[inline]
1050 fn is_javascript_mime_type(mime_type: &Mime) -> bool {
1051 let javascript_mime_types: [Mime; 16] = [
1052 "application/ecmascript".parse().unwrap(),
1053 "application/javascript".parse().unwrap(),
1054 "application/x-ecmascript".parse().unwrap(),
1055 "application/x-javascript".parse().unwrap(),
1056 "text/ecmascript".parse().unwrap(),
1057 "text/javascript".parse().unwrap(),
1058 "text/javascript1.0".parse().unwrap(),
1059 "text/javascript1.1".parse().unwrap(),
1060 "text/javascript1.2".parse().unwrap(),
1061 "text/javascript1.3".parse().unwrap(),
1062 "text/javascript1.4".parse().unwrap(),
1063 "text/javascript1.5".parse().unwrap(),
1064 "text/jscript".parse().unwrap(),
1065 "text/livescript".parse().unwrap(),
1066 "text/x-ecmascript".parse().unwrap(),
1067 "text/x-javascript".parse().unwrap(),
1068 ];
1069
1070 javascript_mime_types
1071 .iter()
1072 .any(|mime| mime.type_() == mime_type.type_() && mime.subtype() == mime_type.subtype())
1073 }
1074
1075 match mime_type {
1076 Some(ref mime_type) if destination.is_script_like() => !is_javascript_mime_type(mime_type),
1078 Some(ref mime_type) if destination == Destination::Style => {
1080 mime_type.type_() != mime::TEXT && mime_type.subtype() != mime::CSS
1081 },
1082
1083 None if destination == Destination::Style || destination.is_script_like() => true,
1084 _ => false,
1086 }
1087}
1088
1089fn should_be_blocked_due_to_mime_type(
1091 destination: Destination,
1092 response_headers: &HeaderMap,
1093) -> bool {
1094 let mime_type: mime::Mime = match extract_mime_type_as_mime(response_headers) {
1096 Some(mime_type) => mime_type,
1097 None => return false,
1099 };
1100
1101 destination.is_script_like() &&
1107 match mime_type.type_() {
1108 mime::AUDIO | mime::VIDEO | mime::IMAGE => true,
1109 mime::TEXT if mime_type.subtype() == mime::CSV => true,
1110 _ => false,
1111 }
1112}
1113
1114pub fn should_request_be_blocked_due_to_a_bad_port(url: &ServoUrl) -> bool {
1116 let is_http_scheme = matches!(url.scheme(), "http" | "https");
1121 let is_bad_port = url.port().is_some_and(is_bad_port);
1122 if is_http_scheme && is_bad_port {
1123 return true;
1124 }
1125
1126 false
1128}
1129
1130pub fn should_request_be_blocked_as_mixed_content(
1132 request: &Request,
1133 protocol_registry: &ProtocolRegistry,
1134) -> bool {
1135 if do_settings_prohibit_mixed_security_contexts(request) ==
1139 MixedSecurityProhibited::NotProhibited
1140 {
1141 return false;
1142 }
1143
1144 if is_url_potentially_trustworthy(protocol_registry, &request.current_url()) {
1146 return false;
1147 }
1148
1149 if request.destination == Destination::Document {
1154 return false;
1156 }
1157
1158 true
1159}
1160
1161pub fn should_response_be_blocked_as_mixed_content(
1163 request: &Request,
1164 response: &Response,
1165 protocol_registry: &ProtocolRegistry,
1166) -> bool {
1167 if do_settings_prohibit_mixed_security_contexts(request) ==
1171 MixedSecurityProhibited::NotProhibited
1172 {
1173 return false;
1174 }
1175
1176 if response
1178 .actual_response()
1179 .url()
1180 .is_some_and(|response_url| is_url_potentially_trustworthy(protocol_registry, response_url))
1181 {
1182 return false;
1183 }
1184
1185 if request.destination == Destination::Document {
1190 return false;
1192 }
1193
1194 true
1195}
1196
1197fn is_bad_port(port: u16) -> bool {
1199 static BAD_PORTS: [u16; 78] = [
1200 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101,
1201 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389,
1202 427, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636,
1203 993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 5060, 5061, 6000, 6566, 6665, 6666, 6667,
1204 6668, 6669, 6697, 10080,
1205 ];
1206
1207 BAD_PORTS.binary_search(&port).is_ok()
1208}
1209
1210pub fn is_form_submission_request(request: &Request) -> bool {
1212 let content_type = request.headers.typed_get::<ContentType>();
1213 content_type.is_some_and(|ct| {
1214 let mime: Mime = ct.into();
1215 mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED
1216 })
1217}
1218
1219fn should_upgrade_request_to_potentially_trustworthy(
1221 request: &mut Request,
1222 context: &FetchContext,
1223) -> bool {
1224 fn should_upgrade_navigation_request(request: &Request) -> bool {
1225 if is_form_submission_request(request) {
1227 return true;
1228 }
1229
1230 if request
1233 .client
1234 .as_ref()
1235 .is_some_and(|client| client.is_nested_browsing_context)
1236 {
1237 return true;
1238 }
1239
1240 false
1245 }
1246
1247 if request.is_navigation_request() {
1249 if !is_url_potentially_trustworthy(&context.protocols, &request.current_url()) ||
1254 request
1255 .current_url()
1256 .host_str()
1257 .is_none_or(|host| context.state.hsts_list.read().is_host_secure(host))
1258 {
1259 debug!("Appending the Upgrade-Insecure-Requests header to request’s header list");
1260 request
1261 .headers
1262 .insert("Upgrade-Insecure-Requests", HeaderValue::from_static("1"));
1263 }
1264
1265 if !should_upgrade_navigation_request(request) {
1266 return false;
1267 }
1268 }
1269
1270 request
1274 .client
1275 .as_ref()
1276 .is_some_and(|client| client.insecure_requests_policy == InsecureRequestsPolicy::Upgrade)
1277}
1278
1279#[derive(Debug, PartialEq)]
1280pub enum MixedSecurityProhibited {
1281 Prohibited,
1282 NotProhibited,
1283}
1284
1285fn do_settings_prohibit_mixed_security_contexts(request: &Request) -> MixedSecurityProhibited {
1287 if let Origin::Origin(ref origin) = request.origin {
1288 let is_origin_data_url_worker = matches!(
1290 *origin,
1291 ImmutableOrigin::Opaque(servo_url::OpaqueOrigin::SecureWorkerFromDataUrl(_))
1292 );
1293
1294 if origin.is_potentially_trustworthy() || is_origin_data_url_worker {
1297 return MixedSecurityProhibited::Prohibited;
1298 }
1299 }
1300
1301 if request.has_trustworthy_ancestor_origin {
1305 return MixedSecurityProhibited::Prohibited;
1306 }
1307
1308 MixedSecurityProhibited::NotProhibited
1309}
1310
1311fn should_upgrade_mixed_content_request(
1313 request: &Request,
1314 protocol_registry: &ProtocolRegistry,
1315) -> bool {
1316 let url = request.url();
1317 if is_url_potentially_trustworthy(protocol_registry, &url) {
1319 return false;
1320 }
1321
1322 match url.host() {
1324 Some(Host::Ipv4(_)) | Some(Host::Ipv6(_)) => return false,
1325 _ => (),
1326 }
1327
1328 if do_settings_prohibit_mixed_security_contexts(request) ==
1330 MixedSecurityProhibited::NotProhibited
1331 {
1332 return false;
1333 }
1334
1335 if !matches!(
1337 request.destination,
1338 Destination::Audio | Destination::Image | Destination::Video
1339 ) {
1340 return false;
1341 }
1342
1343 if request.destination == Destination::Image && request.initiator == Initiator::ImageSet {
1345 return false;
1346 }
1347
1348 true
1349}