1use std::sync::Arc;
6use std::sync::atomic::{AtomicBool, Ordering};
7use std::{io, mem, str};
8
9use base64::Engine as _;
10use base64::engine::general_purpose;
11use content_security_policy as csp;
12use crossbeam_channel::Sender;
13use devtools_traits::DevtoolsControlMsg;
14use embedder_traits::resources::{self, Resource};
15use headers::{AccessControlExposeHeaders, ContentType, HeaderMapExt};
16use http::header::{self, HeaderMap, HeaderName, RANGE};
17use http::{HeaderValue, Method, StatusCode};
18use ipc_channel::ipc::{self, IpcReceiver, IpcSender};
19use log::{debug, trace, warn};
20use mime::{self, Mime};
21use net_traits::fetch::headers::extract_mime_type_as_mime;
22use net_traits::filemanager_thread::{FileTokenCheck, RelativePos};
23use net_traits::http_status::HttpStatus;
24use net_traits::policy_container::{PolicyContainer, RequestPolicyContainer};
25use net_traits::request::{
26 BodyChunkRequest, BodyChunkResponse, CredentialsMode, Destination, Initiator,
27 InsecureRequestsPolicy, Origin, ParserMetadata, RedirectMode, Referrer, Request, RequestMode,
28 ResponseTainting, is_cors_safelisted_method, is_cors_safelisted_request_header,
29};
30use net_traits::response::{Response, ResponseBody, ResponseType};
31use net_traits::{
32 FetchTaskTarget, NetworkError, ReferrerPolicy, ResourceAttribute, ResourceFetchTiming,
33 ResourceTimeValue, ResourceTimingType, WebSocketDomAction, WebSocketNetworkEvent,
34 set_default_accept_language,
35};
36use parking_lot::Mutex;
37use rustls_pki_types::CertificateDer;
38use serde::{Deserialize, Serialize};
39use servo_arc::Arc as ServoArc;
40use servo_url::{Host, ImmutableOrigin, ServoUrl};
41use tokio::sync::mpsc::{UnboundedReceiver as TokioReceiver, UnboundedSender as TokioSender};
42
43use crate::connector::CACertificates;
44use crate::fetch::cors_cache::CorsCache;
45use crate::fetch::fetch_params::{FetchParams, PreloadResponseCandidate};
46use crate::fetch::headers::determine_nosniff;
47use crate::filemanager_thread::FileManager;
48use crate::http_loader::{
49 HttpState, determine_requests_referrer, http_fetch, send_early_httprequest_to_devtools,
50 send_response_to_devtools, set_default_accept,
51};
52use crate::protocols::{ProtocolRegistry, is_url_potentially_trustworthy};
53use crate::request_interceptor::RequestInterceptor;
54use crate::subresource_integrity::is_response_integrity_valid;
55
56const PARTIAL_RESPONSE_TO_NON_RANGE_REQUEST_ERROR: &str = "Refusing to provide partial response\
57from earlier ranged request to API that did not make a range request";
58
59pub type Target<'a> = &'a mut (dyn FetchTaskTarget + Send);
60
61#[derive(Clone, Deserialize, Serialize)]
62pub enum Data {
63 Payload(Vec<u8>),
64 Done,
65 Cancelled,
66}
67
68pub struct WebSocketChannel {
69 pub sender: IpcSender<WebSocketNetworkEvent>,
70 pub receiver: Option<IpcReceiver<WebSocketDomAction>>,
71}
72
73impl WebSocketChannel {
74 pub fn new(
75 sender: IpcSender<WebSocketNetworkEvent>,
76 receiver: Option<IpcReceiver<WebSocketDomAction>>,
77 ) -> Self {
78 Self { sender, receiver }
79 }
80}
81
82#[derive(Clone)]
83pub struct FetchContext {
84 pub state: Arc<HttpState>,
85 pub user_agent: String,
86 pub devtools_chan: Option<Arc<Mutex<Sender<DevtoolsControlMsg>>>>,
87 pub filemanager: Arc<Mutex<FileManager>>,
88 pub file_token: FileTokenCheck,
89 pub request_interceptor: Arc<Mutex<RequestInterceptor>>,
90 pub cancellation_listener: Arc<CancellationListener>,
91 pub timing: ServoArc<Mutex<ResourceFetchTiming>>,
92 pub protocols: Arc<ProtocolRegistry>,
93 pub websocket_chan: Option<Arc<Mutex<WebSocketChannel>>>,
94 pub ca_certificates: CACertificates,
95 pub ignore_certificate_errors: bool,
96}
97
98#[derive(Default)]
99pub struct CancellationListener {
100 cancelled: AtomicBool,
101}
102
103impl CancellationListener {
104 pub(crate) fn cancelled(&self) -> bool {
105 self.cancelled.load(Ordering::Relaxed)
106 }
107
108 pub(crate) fn cancel(&self) {
109 self.cancelled.store(true, Ordering::Relaxed)
110 }
111}
112pub type DoneChannel = Option<(TokioSender<Data>, TokioReceiver<Data>)>;
113
114pub async fn fetch(request: Request, target: Target<'_>, context: &FetchContext) -> Response {
116 {
119 let mut timing_guard = context.timing.lock();
120 timing_guard.set_attribute(ResourceAttribute::FetchStart);
121 timing_guard.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::FetchStart));
122 }
123 fetch_with_cors_cache(request, &mut CorsCache::default(), target, context).await
124}
125
126pub async fn fetch_with_cors_cache(
130 request: Request,
131 cache: &mut CorsCache,
132 target: Target<'_>,
133 context: &FetchContext,
134) -> Response {
135 let mut fetch_params = FetchParams::new(request);
137 let request = &mut fetch_params.request;
138
139 request.populate_request_from_client();
141
142 if
151 matches!(request.current_url().scheme(), "http" | "https")
153 && matches!(request.mode, RequestMode::SameOrigin | RequestMode::CorsMode | RequestMode::NoCors)
155 && matches!(request.method, Method::GET)
157 && (!request.unsafe_request || request.headers.is_empty())
159 {
160 if let Some(client) = request.client.as_ref() {
162 assert!(request.origin == client.origin);
164 let on_preloaded_response_available = |response| {
167 fetch_params.preload_response_candidate =
168 PreloadResponseCandidate::Response(Box::new(response))
169 };
170 let found_preloaded_resource =
174 client.consume_preloaded_resource(request, on_preloaded_response_available);
175 if found_preloaded_resource &&
178 matches!(
179 fetch_params.preload_response_candidate,
180 PreloadResponseCandidate::None
181 )
182 {
183 fetch_params.preload_response_candidate = PreloadResponseCandidate::Pending;
184 }
185 }
186 }
187
188 set_default_accept(request);
190
191 set_default_accept_language(&mut request.headers);
194
195 if request.is_subresource_request() {
202 }
204
205 main_fetch(&mut fetch_params, cache, false, target, &mut None, context).await
207
208 }
211
212pub(crate) fn convert_request_to_csp_request(request: &Request) -> Option<csp::Request> {
213 let origin = match &request.origin {
214 Origin::Client => return None,
215 Origin::Origin(origin) => origin,
216 };
217
218 let mut original_url = request.original_url();
221 if original_url.scheme() == "ws" && request.url().scheme() == "https" {
222 original_url.as_mut_url().set_scheme("wss").unwrap();
223 }
224
225 let csp_request = csp::Request {
226 url: original_url.into_url(),
227 origin: origin.clone().into_url_origin(),
228 redirect_count: request.redirect_count,
229 destination: request.destination,
230 initiator: match request.initiator {
231 Initiator::Download => csp::Initiator::Download,
232 Initiator::ImageSet => csp::Initiator::ImageSet,
233 Initiator::Manifest => csp::Initiator::Manifest,
234 Initiator::Prefetch => csp::Initiator::Prefetch,
235 _ => csp::Initiator::None,
236 },
237 nonce: request.cryptographic_nonce_metadata.clone(),
238 integrity_metadata: request.integrity_metadata.clone(),
239 parser_metadata: match request.parser_metadata {
240 ParserMetadata::ParserInserted => csp::ParserMetadata::ParserInserted,
241 ParserMetadata::NotParserInserted => csp::ParserMetadata::NotParserInserted,
242 ParserMetadata::Default => csp::ParserMetadata::None,
243 },
244 };
245 Some(csp_request)
246}
247
248pub fn should_request_be_blocked_by_csp(
250 csp_request: &csp::Request,
251 policy_container: &PolicyContainer,
252) -> (csp::CheckResult, Vec<csp::Violation>) {
253 policy_container
254 .csp_list
255 .as_ref()
256 .map(|c| c.should_request_be_blocked(csp_request))
257 .unwrap_or((csp::CheckResult::Allowed, Vec::new()))
258}
259
260pub fn report_violations_for_request_by_csp(
262 csp_request: &csp::Request,
263 policy_container: &PolicyContainer,
264) -> Vec<csp::Violation> {
265 policy_container
266 .csp_list
267 .as_ref()
268 .map(|c| c.report_violations_for_request(csp_request))
269 .unwrap_or_default()
270}
271
272fn should_response_be_blocked_by_csp(
273 csp_request: &csp::Request,
274 response: &Response,
275 policy_container: &PolicyContainer,
276) -> (csp::CheckResult, Vec<csp::Violation>) {
277 if response.is_network_error() {
278 return (csp::CheckResult::Allowed, Vec::new());
279 }
280 let csp_response = csp::Response {
281 url: response
282 .actual_response()
283 .url()
284 .cloned()
285 .map(|mut url| {
290 match csp_request.url.scheme() {
291 "ws" | "wss" => {
292 url.as_mut_url()
293 .set_scheme(csp_request.url.scheme())
294 .expect("failed to set URL scheme");
295 },
296 _ => {},
297 };
298 url
299 })
300 .expect("response must have a url")
301 .into_url(),
302 redirect_count: csp_request.redirect_count,
303 };
304 policy_container
305 .csp_list
306 .as_ref()
307 .map(|c| c.should_response_to_request_be_blocked(csp_request, &csp_response))
308 .unwrap_or((csp::CheckResult::Allowed, Vec::new()))
309}
310
311pub async fn main_fetch(
313 fetch_params: &mut FetchParams,
314 cache: &mut CorsCache,
315 recursive_flag: bool,
316 target: Target<'_>,
317 done_chan: &mut DoneChannel,
318 context: &FetchContext,
319) -> Response {
320 let request = &mut fetch_params.request;
322 send_early_httprequest_to_devtools(request, context);
323 let mut response = None;
325
326 if let Some(ref details) = request.crash {
328 response = Some(Response::network_error(NetworkError::Crash(
329 details.clone(),
330 )));
331 }
332
333 if request.local_urls_only &&
336 !matches!(
337 request.current_url().scheme(),
338 "about" | "blob" | "data" | "filesystem"
339 )
340 {
341 response = Some(Response::network_error(NetworkError::Internal(
342 "Non-local scheme".into(),
343 )));
344 }
345
346 let policy_container = match &request.policy_container {
348 RequestPolicyContainer::Client => unreachable!(),
349 RequestPolicyContainer::PolicyContainer(container) => container.to_owned(),
350 };
351 let csp_request = convert_request_to_csp_request(request);
352 if let Some(csp_request) = csp_request.as_ref() {
353 let violations = report_violations_for_request_by_csp(csp_request, &policy_container);
355
356 if !violations.is_empty() {
357 target.process_csp_violations(request, violations);
358 }
359 };
360
361 if should_upgrade_request_to_potentially_trustworthy(request, context) ||
366 should_upgrade_mixed_content_request(request, &context.protocols)
367 {
368 trace!(
369 "upgrading {} targeting {:?}",
370 request.current_url(),
371 request.destination
372 );
373 if let Some(new_scheme) = match request.current_url().scheme() {
374 "http" => Some("https"),
375 "ws" => Some("wss"),
376 _ => None,
377 } {
378 request
379 .current_url_mut()
380 .as_mut_url()
381 .set_scheme(new_scheme)
382 .unwrap();
383 }
384 } else {
385 trace!(
386 "not upgrading {} targeting {:?} with {:?}",
387 request.current_url(),
388 request.destination,
389 request.insecure_requests_policy
390 );
391 }
392 if let Some(csp_request) = csp_request.as_ref() {
393 let (check_result, violations) =
397 should_request_be_blocked_by_csp(csp_request, &policy_container);
398
399 if !violations.is_empty() {
400 target.process_csp_violations(request, violations);
401 }
402
403 if check_result == csp::CheckResult::Blocked {
404 warn!("Request blocked by CSP");
405 response = Some(Response::network_error(NetworkError::Internal(
406 "Blocked by Content-Security-Policy".into(),
407 )))
408 }
409 };
410 if should_request_be_blocked_due_to_a_bad_port(&request.current_url()) {
411 response = Some(Response::network_error(NetworkError::Internal(
412 "Request attempted on bad port".into(),
413 )));
414 }
415 if should_request_be_blocked_as_mixed_content(request, &context.protocols) {
416 response = Some(Response::network_error(NetworkError::Internal(
417 "Blocked as mixed content".into(),
418 )));
419 }
420
421 if request.referrer_policy == ReferrerPolicy::EmptyString {
424 request.referrer_policy = policy_container.get_referrer_policy();
425 }
426
427 let referrer_url = match mem::replace(&mut request.referrer, Referrer::NoReferrer) {
428 Referrer::NoReferrer => None,
429 Referrer::ReferrerUrl(referrer_source) | Referrer::Client(referrer_source) => {
430 request.headers.remove(header::REFERER);
431 determine_requests_referrer(
432 request.referrer_policy,
433 referrer_source,
434 request.current_url(),
435 )
436 },
437 };
438 request.referrer = referrer_url.map_or(Referrer::NoReferrer, Referrer::ReferrerUrl);
439
440 context
445 .state
446 .hsts_list
447 .read()
448 .apply_hsts_rules(request.current_url_mut());
449
450 let current_url = request.current_url();
454 let current_scheme = current_url.scheme();
455
456 context
458 .request_interceptor
459 .lock()
460 .intercept_request(request, &mut response, context);
461
462 let mut response = match response {
463 Some(res) => res,
464 None => {
465 let same_origin = if let Origin::Origin(ref origin) = request.origin {
468 *origin == current_url.origin()
469 } else {
470 false
471 };
472
473 if let PreloadResponseCandidate::Response(response) =
475 &fetch_params.preload_response_candidate
476 {
477 *response.clone()
483 }
484 else if (same_origin && request.response_tainting == ResponseTainting::Basic) ||
487 current_scheme == "data" ||
489 context.protocols.is_fetchable(current_scheme) ||
492 matches!(
494 request.mode,
495 RequestMode::Navigate | RequestMode::WebSocket { .. }
496 )
497 {
498 request.response_tainting = ResponseTainting::Basic;
500
501 scheme_fetch(fetch_params, cache, target, done_chan, context).await
503 } else if request.mode == RequestMode::SameOrigin {
504 Response::network_error(NetworkError::Internal("Cross-origin response".into()))
505 } else if request.mode == RequestMode::NoCors {
506 if request.redirect_mode != RedirectMode::Follow {
508 Response::network_error(NetworkError::Internal(
509 "NoCors requests must follow redirects".into(),
510 ))
511 } else {
512 request.response_tainting = ResponseTainting::Opaque;
514
515 scheme_fetch(fetch_params, cache, target, done_chan, context).await
517 }
518 } else if !matches!(current_scheme, "http" | "https") {
519 Response::network_error(NetworkError::Internal("Non-http scheme".into()))
520 } else if request.use_cors_preflight ||
521 (request.unsafe_request &&
522 (!is_cors_safelisted_method(&request.method) ||
523 request.headers.iter().any(|(name, value)| {
524 !is_cors_safelisted_request_header(&name, &value)
525 })))
526 {
527 request.response_tainting = ResponseTainting::CorsTainting;
529 let response = http_fetch(
531 fetch_params,
532 cache,
533 true,
534 true,
535 false,
536 target,
537 done_chan,
538 context,
539 )
540 .await;
541 if response.is_network_error() {
543 }
545 response
547 } else {
548 request.response_tainting = ResponseTainting::CorsTainting;
550 http_fetch(
552 fetch_params,
553 cache,
554 true,
555 false,
556 false,
557 target,
558 done_chan,
559 context,
560 )
561 .await
562 }
563 },
564 };
565
566 if recursive_flag {
568 return response;
569 }
570
571 let request = &mut fetch_params.request;
573
574 let mut response = if !response.is_network_error() && response.internal_response.is_none() {
576 if request.response_tainting == ResponseTainting::CorsTainting {
578 let header_names: Option<Vec<HeaderName>> = response
580 .headers
581 .typed_get::<AccessControlExposeHeaders>()
582 .map(|v| v.iter().collect());
583 match header_names {
584 Some(ref list)
586 if request.credentials_mode != CredentialsMode::Include &&
587 list.iter().any(|header| header == "*") =>
588 {
589 response.cors_exposed_header_name_list = response
590 .headers
591 .iter()
592 .map(|(name, _)| name.as_str().to_owned())
593 .collect();
594 },
595 Some(list) => {
597 response.cors_exposed_header_name_list =
598 list.iter().map(|h| h.as_str().to_owned()).collect();
599 },
600 _ => (),
601 }
602 }
603
604 let response_type = match request.response_tainting {
606 ResponseTainting::Basic => ResponseType::Basic,
607 ResponseTainting::CorsTainting => ResponseType::Cors,
608 ResponseTainting::Opaque => ResponseType::Opaque,
609 };
610 response.to_filtered(response_type)
611 } else {
612 response
613 };
614
615 let internal_error = {
616 let response_is_network_error = response.is_network_error();
618 let should_replace_with_nosniff_error = !response_is_network_error &&
619 should_be_blocked_due_to_nosniff(request.destination, &response.headers);
620 let should_replace_with_mime_type_error = !response_is_network_error &&
621 should_be_blocked_due_to_mime_type(request.destination, &response.headers);
622 let should_replace_with_mixed_content = !response_is_network_error &&
623 should_response_be_blocked_as_mixed_content(request, &response, &context.protocols);
624 let should_replace_with_csp_error = csp_request.is_some_and(|csp_request| {
625 let (check_result, violations) =
626 should_response_be_blocked_by_csp(&csp_request, &response, &policy_container);
627 if !violations.is_empty() {
628 target.process_csp_violations(request, violations);
629 }
630 check_result == csp::CheckResult::Blocked
631 });
632
633 let mut network_error_response = response
635 .get_network_error()
636 .cloned()
637 .map(Response::network_error);
638
639 let response_type = response.response_type.clone(); let internal_response = if let Some(error_response) = network_error_response.as_mut() {
643 error_response
644 } else {
645 response.actual_response_mut()
646 };
647
648 if internal_response.url_list.is_empty() {
650 internal_response.url_list.clone_from(&request.url_list)
651 }
652
653 let mut blocked_error_response;
659
660 let internal_response = if should_replace_with_nosniff_error {
661 blocked_error_response =
663 Response::network_error(NetworkError::Internal("Blocked by nosniff".into()));
664 &blocked_error_response
665 } else if should_replace_with_mime_type_error {
666 blocked_error_response =
668 Response::network_error(NetworkError::Internal("Blocked by mime type".into()));
669 &blocked_error_response
670 } else if should_replace_with_mixed_content {
671 blocked_error_response =
672 Response::network_error(NetworkError::Internal("Blocked as mixed content".into()));
673 &blocked_error_response
674 } else if should_replace_with_csp_error {
675 blocked_error_response =
676 Response::network_error(NetworkError::Internal("Blocked due to CSP".into()));
677 &blocked_error_response
678 } else {
679 internal_response
680 };
681
682 let internal_response = if !internal_response.is_network_error() &&
688 response_type == ResponseType::Opaque &&
689 internal_response.status.code() == StatusCode::PARTIAL_CONTENT &&
690 internal_response.range_requested &&
691 !request.headers.contains_key(RANGE)
692 {
693 blocked_error_response = Response::network_error(NetworkError::Internal(
695 PARTIAL_RESPONSE_TO_NON_RANGE_REQUEST_ERROR.into(),
696 ));
697 &blocked_error_response
698 } else {
699 internal_response
700 };
701
702 let not_network_error = !response_is_network_error && !internal_response.is_network_error();
707 if not_network_error &&
708 (is_null_body_status(&internal_response.status) ||
709 matches!(request.method, Method::HEAD | Method::CONNECT))
710 {
711 let mut body = internal_response.body.lock();
714 *body = ResponseBody::Empty;
715 }
716
717 internal_response.get_network_error().cloned()
718 };
719
720 let mut response = if let Some(error) = internal_error {
722 Response::network_error(error)
723 } else {
724 response
725 };
726
727 let mut response_loaded = false;
729 let mut response = if !response.is_network_error() && !request.integrity_metadata.is_empty() {
730 wait_for_response(request, &mut response, target, done_chan, context).await;
732 response_loaded = true;
733
734 let integrity_metadata = &request.integrity_metadata;
736 if response.termination_reason.is_none() &&
737 !is_response_integrity_valid(integrity_metadata, &response)
738 {
739 Response::network_error(NetworkError::Internal(
740 "Subresource integrity validation failed".into(),
741 ))
742 } else {
743 response
744 }
745 } else {
746 response
747 };
748
749 if request.synchronous {
751 target.process_response(request, &response);
754 if !response_loaded {
755 wait_for_response(request, &mut response, target, done_chan, context).await;
756 }
757 target.process_response_eof(request, &response);
759 return response;
760 }
761
762 if request.body.is_some() && matches!(current_scheme, "http" | "https") {
764 target.process_request_body(request);
769 target.process_request_eof(request);
770 }
771
772 target.process_response(request, &response);
774 send_response_to_devtools(request, context, &response, None);
776
777 if !response_loaded {
779 wait_for_response(request, &mut response, target, done_chan, context).await;
780 }
781
782 target.process_response_eof(request, &response);
784 send_response_to_devtools(request, context, &response, None);
788
789 let http_cache = context.state.http_cache.write();
790 http_cache.update_awaiting_consumers(request, &response);
791
792 response
795}
796
797async fn wait_for_response(
798 request: &Request,
799 response: &mut Response,
800 target: Target<'_>,
801 done_chan: &mut DoneChannel,
802 context: &FetchContext,
803) {
804 if let Some(ref mut ch) = *done_chan {
805 let mut devtools_body = context.devtools_chan.as_ref().map(|_| Vec::new());
806 loop {
807 match ch.1.recv().await {
808 Some(Data::Payload(vec)) => {
809 if let Some(body) = devtools_body.as_mut() {
810 body.extend(&vec);
811 }
812 target.process_response_chunk(request, vec);
813 },
814 Some(Data::Done) => {
815 send_response_to_devtools(request, context, response, devtools_body);
816 break;
817 },
818 Some(Data::Cancelled) => {
819 response.aborted.store(true, Ordering::Release);
820 break;
821 },
822 _ => {
823 panic!("fetch worker should always send Done before terminating");
824 },
825 }
826 }
827 } else {
828 match *response.actual_response().body.lock() {
829 ResponseBody::Done(ref vec) if !vec.is_empty() => {
830 target.process_response_chunk(request, vec.clone());
834 if context.devtools_chan.is_some() {
835 send_response_to_devtools(request, context, response, Some(vec.clone()));
838 }
839 },
840 ResponseBody::Done(_) | ResponseBody::Empty => {},
841 _ => unreachable!(),
842 }
843 }
844}
845
846pub enum RangeRequestBounds {
848 Final(RelativePos),
850 Pending(u64),
853}
854
855impl RangeRequestBounds {
856 pub fn get_final(&self, len: Option<u64>) -> Result<RelativePos, &'static str> {
857 match self {
858 RangeRequestBounds::Final(pos) => {
859 if let Some(len) = len {
860 if pos.start <= len as i64 {
861 return Ok(*pos);
862 }
863 }
864 Err("Tried to process RangeRequestBounds::Final without len")
865 },
866 RangeRequestBounds::Pending(offset) => Ok(RelativePos::from_opts(
867 if let Some(len) = len {
868 Some((len - u64::min(len, *offset)) as i64)
869 } else {
870 Some(0)
871 },
872 None,
873 )),
874 }
875 }
876}
877
878fn create_blank_reply(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
879 let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
880 response
881 .headers
882 .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
883 *response.body.lock() = ResponseBody::Done(vec![]);
884 response.status = HttpStatus::default();
885 response
886}
887
888fn create_about_memory(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
889 let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
890 response
891 .headers
892 .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
893 *response.body.lock() = ResponseBody::Done(resources::read_bytes(Resource::AboutMemoryHTML));
894 response.status = HttpStatus::default();
895 response
896}
897
898fn handle_allowcert_request(request: &mut Request, context: &FetchContext) -> io::Result<()> {
900 let error = |string| Err(io::Error::other(string));
901
902 let body = match request.body.as_mut() {
903 Some(body) => body,
904 None => return error("No body found"),
905 };
906
907 let stream = body.take_stream();
908 let stream = stream.lock();
909 let (body_chan, body_port) = ipc::channel().unwrap();
910 let _ = stream.send(BodyChunkRequest::Connect(body_chan));
911 let _ = stream.send(BodyChunkRequest::Chunk);
912 let body_bytes = match body_port.recv().ok() {
913 Some(BodyChunkResponse::Chunk(bytes)) => bytes,
914 _ => return error("Certificate not sent in a single chunk"),
915 };
916
917 let split_idx = match body_bytes.iter().position(|b| *b == b'&') {
918 Some(split_idx) => split_idx,
919 None => return error("Could not find ampersand in data"),
920 };
921 let (secret, cert_base64) = body_bytes.split_at(split_idx);
922
923 let secret = str::from_utf8(secret).ok().and_then(|s| s.parse().ok());
924 if secret != Some(*net_traits::PRIVILEGED_SECRET) {
925 return error("Invalid secret sent. Ignoring request");
926 }
927
928 let cert_bytes = match general_purpose::STANDARD_NO_PAD.decode(&cert_base64[1..]) {
929 Ok(bytes) => bytes,
930 Err(_) => return error("Could not decode certificate base64"),
931 };
932
933 context
934 .state
935 .override_manager
936 .add_override(&CertificateDer::from_slice(&cert_bytes).into_owned());
937 Ok(())
938}
939
940async fn scheme_fetch(
942 fetch_params: &mut FetchParams,
943 cache: &mut CorsCache,
944 target: Target<'_>,
945 done_chan: &mut DoneChannel,
946 context: &FetchContext,
947) -> Response {
948 let request = &mut fetch_params.request;
952 let url = request.current_url();
953
954 let scheme = url.scheme();
955 match scheme {
956 "about" if url.path() == "blank" => create_blank_reply(url, request.timing_type()),
957 "about" if url.path() == "memory" => create_about_memory(url, request.timing_type()),
958
959 "chrome" if url.path() == "allowcert" => {
960 if let Err(error) = handle_allowcert_request(request, context) {
961 warn!("Could not handle allowcert request: {error}");
962 }
963 create_blank_reply(url, request.timing_type())
964 },
965
966 "http" | "https" => {
967 http_fetch(
968 fetch_params,
969 cache,
970 false,
971 false,
972 false,
973 target,
974 done_chan,
975 context,
976 )
977 .await
978 },
979
980 _ => match context.protocols.get(scheme) {
981 Some(handler) => handler.load(request, done_chan, context).await,
982 None => Response::network_error(NetworkError::Internal("Unexpected scheme".into())),
983 },
984 }
985}
986
987fn is_null_body_status(status: &HttpStatus) -> bool {
988 matches!(
989 status.try_code(),
990 Some(StatusCode::SWITCHING_PROTOCOLS) |
991 Some(StatusCode::NO_CONTENT) |
992 Some(StatusCode::RESET_CONTENT) |
993 Some(StatusCode::NOT_MODIFIED)
994 )
995}
996
997pub fn should_be_blocked_due_to_nosniff(
999 destination: Destination,
1000 response_headers: &HeaderMap,
1001) -> bool {
1002 if !determine_nosniff(response_headers) {
1004 return false;
1005 }
1006
1007 let mime_type = extract_mime_type_as_mime(response_headers);
1010
1011 #[inline]
1013 fn is_javascript_mime_type(mime_type: &Mime) -> bool {
1014 let javascript_mime_types: [Mime; 16] = [
1015 "application/ecmascript".parse().unwrap(),
1016 "application/javascript".parse().unwrap(),
1017 "application/x-ecmascript".parse().unwrap(),
1018 "application/x-javascript".parse().unwrap(),
1019 "text/ecmascript".parse().unwrap(),
1020 "text/javascript".parse().unwrap(),
1021 "text/javascript1.0".parse().unwrap(),
1022 "text/javascript1.1".parse().unwrap(),
1023 "text/javascript1.2".parse().unwrap(),
1024 "text/javascript1.3".parse().unwrap(),
1025 "text/javascript1.4".parse().unwrap(),
1026 "text/javascript1.5".parse().unwrap(),
1027 "text/jscript".parse().unwrap(),
1028 "text/livescript".parse().unwrap(),
1029 "text/x-ecmascript".parse().unwrap(),
1030 "text/x-javascript".parse().unwrap(),
1031 ];
1032
1033 javascript_mime_types
1034 .iter()
1035 .any(|mime| mime.type_() == mime_type.type_() && mime.subtype() == mime_type.subtype())
1036 }
1037
1038 match mime_type {
1039 Some(ref mime_type) if destination.is_script_like() => !is_javascript_mime_type(mime_type),
1041 Some(ref mime_type) if destination == Destination::Style => {
1043 mime_type.type_() != mime::TEXT && mime_type.subtype() != mime::CSS
1044 },
1045
1046 None if destination == Destination::Style || destination.is_script_like() => true,
1047 _ => false,
1049 }
1050}
1051
1052fn should_be_blocked_due_to_mime_type(
1054 destination: Destination,
1055 response_headers: &HeaderMap,
1056) -> bool {
1057 let mime_type: mime::Mime = match extract_mime_type_as_mime(response_headers) {
1059 Some(mime_type) => mime_type,
1060 None => return false,
1062 };
1063
1064 destination.is_script_like() &&
1070 match mime_type.type_() {
1071 mime::AUDIO | mime::VIDEO | mime::IMAGE => true,
1072 mime::TEXT if mime_type.subtype() == mime::CSV => true,
1073 _ => false,
1074 }
1075}
1076
1077pub fn should_request_be_blocked_due_to_a_bad_port(url: &ServoUrl) -> bool {
1079 let is_http_scheme = matches!(url.scheme(), "http" | "https");
1084 let is_bad_port = url.port().is_some_and(is_bad_port);
1085 if is_http_scheme && is_bad_port {
1086 return true;
1087 }
1088
1089 false
1091}
1092
1093pub fn should_request_be_blocked_as_mixed_content(
1095 request: &Request,
1096 protocol_registry: &ProtocolRegistry,
1097) -> bool {
1098 if do_settings_prohibit_mixed_security_contexts(request) ==
1102 MixedSecurityProhibited::NotProhibited
1103 {
1104 return false;
1105 }
1106
1107 if is_url_potentially_trustworthy(protocol_registry, &request.url()) {
1109 return false;
1110 }
1111
1112 if request.destination == Destination::Document {
1117 return false;
1119 }
1120
1121 true
1122}
1123
1124pub fn should_response_be_blocked_as_mixed_content(
1126 request: &Request,
1127 response: &Response,
1128 protocol_registry: &ProtocolRegistry,
1129) -> bool {
1130 if do_settings_prohibit_mixed_security_contexts(request) ==
1134 MixedSecurityProhibited::NotProhibited
1135 {
1136 return false;
1137 }
1138
1139 if response
1141 .actual_response()
1142 .url()
1143 .is_some_and(|response_url| is_url_potentially_trustworthy(protocol_registry, response_url))
1144 {
1145 return false;
1146 }
1147
1148 if request.destination == Destination::Document {
1153 return false;
1155 }
1156
1157 true
1158}
1159
1160fn is_bad_port(port: u16) -> bool {
1162 static BAD_PORTS: [u16; 78] = [
1163 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101,
1164 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389,
1165 427, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636,
1166 993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 5060, 5061, 6000, 6566, 6665, 6666, 6667,
1167 6668, 6669, 6697, 10080,
1168 ];
1169
1170 BAD_PORTS.binary_search(&port).is_ok()
1171}
1172
1173pub fn is_form_submission_request(request: &Request) -> bool {
1175 let content_type = request.headers.typed_get::<ContentType>();
1176 content_type.is_some_and(|ct| {
1177 let mime: Mime = ct.into();
1178 mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED
1179 })
1180}
1181
1182fn should_upgrade_request_to_potentially_trustworthy(
1184 request: &mut Request,
1185 context: &FetchContext,
1186) -> bool {
1187 fn should_upgrade_navigation_request(request: &Request) -> bool {
1188 if is_form_submission_request(request) {
1190 return true;
1191 }
1192
1193 false
1201 }
1202
1203 if request.is_navigation_request() {
1205 if !is_url_potentially_trustworthy(&context.protocols, &request.current_url()) ||
1210 request
1211 .current_url()
1212 .host_str()
1213 .is_none_or(|host| context.state.hsts_list.read().is_host_secure(host))
1214 {
1215 debug!("Appending the Upgrade-Insecure-Requests header to request’s header list");
1216 request
1217 .headers
1218 .insert("Upgrade-Insecure-Requests", HeaderValue::from_static("1"));
1219 }
1220
1221 if !should_upgrade_navigation_request(request) {
1222 return false;
1223 }
1224 }
1225
1226 request.insecure_requests_policy == InsecureRequestsPolicy::Upgrade
1228}
1229
1230#[derive(Debug, PartialEq)]
1231pub enum MixedSecurityProhibited {
1232 Prohibited,
1233 NotProhibited,
1234}
1235
1236fn do_settings_prohibit_mixed_security_contexts(request: &Request) -> MixedSecurityProhibited {
1238 if let Origin::Origin(ref origin) = request.origin {
1239 let is_origin_data_url_worker = matches!(
1241 *origin,
1242 ImmutableOrigin::Opaque(servo_url::OpaqueOrigin::SecureWorkerFromDataUrl(_))
1243 );
1244
1245 if origin.is_potentially_trustworthy() || is_origin_data_url_worker {
1248 return MixedSecurityProhibited::Prohibited;
1249 }
1250 }
1251
1252 if request.has_trustworthy_ancestor_origin {
1256 return MixedSecurityProhibited::Prohibited;
1257 }
1258
1259 MixedSecurityProhibited::NotProhibited
1260}
1261
1262fn should_upgrade_mixed_content_request(
1264 request: &Request,
1265 protocol_registry: &ProtocolRegistry,
1266) -> bool {
1267 let url = request.url();
1268 if is_url_potentially_trustworthy(protocol_registry, &url) {
1270 return false;
1271 }
1272
1273 match url.host() {
1275 Some(Host::Ipv4(_)) | Some(Host::Ipv6(_)) => return false,
1276 _ => (),
1277 }
1278
1279 if do_settings_prohibit_mixed_security_contexts(request) ==
1281 MixedSecurityProhibited::NotProhibited
1282 {
1283 return false;
1284 }
1285
1286 if !matches!(
1288 request.destination,
1289 Destination::Audio | Destination::Image | Destination::Video
1290 ) {
1291 return false;
1292 }
1293
1294 if request.destination == Destination::Image && request.initiator == Initiator::ImageSet {
1296 return false;
1297 }
1298
1299 true
1300}