1use std::sync::atomic::{AtomicBool, Ordering};
6use std::sync::{Arc, Mutex};
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;
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, Window, 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, set_default_accept_language,
34};
35use rustls_pki_types::CertificateDer;
36use serde::{Deserialize, Serialize};
37use servo_arc::Arc as ServoArc;
38use servo_url::{Host, ImmutableOrigin, ServoUrl};
39use tokio::sync::mpsc::{UnboundedReceiver as TokioReceiver, UnboundedSender as TokioSender};
40
41use super::fetch_params::FetchParams;
42use crate::fetch::cors_cache::CorsCache;
43use crate::fetch::headers::determine_nosniff;
44use crate::filemanager_thread::FileManager;
45use crate::http_loader::{
46 HttpState, determine_requests_referrer, http_fetch, send_early_httprequest_to_devtools,
47 send_response_to_devtools, set_default_accept,
48};
49use crate::protocols::{ProtocolRegistry, is_url_potentially_trustworthy};
50use crate::request_interceptor::RequestInterceptor;
51use crate::subresource_integrity::is_response_integrity_valid;
52
53const PARTIAL_RESPONSE_TO_NON_RANGE_REQUEST_ERROR: &str = "Refusing to provide partial response\
54from earlier ranged request to API that did not make a range request";
55
56pub type Target<'a> = &'a mut (dyn FetchTaskTarget + Send);
57
58#[derive(Clone, Deserialize, Serialize)]
59pub enum Data {
60 Payload(Vec<u8>),
61 Done,
62 Cancelled,
63}
64
65pub struct FetchContext {
66 pub state: Arc<HttpState>,
67 pub user_agent: String,
68 pub devtools_chan: Option<Arc<Mutex<Sender<DevtoolsControlMsg>>>>,
69 pub filemanager: Arc<Mutex<FileManager>>,
70 pub file_token: FileTokenCheck,
71 pub request_interceptor: Arc<Mutex<RequestInterceptor>>,
72 pub cancellation_listener: Arc<CancellationListener>,
73 pub timing: ServoArc<Mutex<ResourceFetchTiming>>,
74 pub protocols: Arc<ProtocolRegistry>,
75}
76
77#[derive(Default)]
78pub struct CancellationListener {
79 cancelled: AtomicBool,
80}
81
82impl CancellationListener {
83 pub(crate) fn cancelled(&self) -> bool {
84 self.cancelled.load(Ordering::Relaxed)
85 }
86
87 pub(crate) fn cancel(&self) {
88 self.cancelled.store(true, Ordering::Relaxed)
89 }
90}
91pub type DoneChannel = Option<(TokioSender<Data>, TokioReceiver<Data>)>;
92
93pub async fn fetch(request: Request, target: Target<'_>, context: &FetchContext) {
95 {
98 let mut timing_guard = context.timing.lock().unwrap();
99 timing_guard.set_attribute(ResourceAttribute::FetchStart);
100 timing_guard.set_attribute(ResourceAttribute::StartTime(ResourceTimeValue::FetchStart));
101 }
102 fetch_with_cors_cache(request, &mut CorsCache::default(), target, context).await;
103}
104
105pub async fn fetch_with_cors_cache(
109 request: Request,
110 cache: &mut CorsCache,
111 target: Target<'_>,
112 context: &FetchContext,
113) {
114 let mut fetch_params = FetchParams::new(request);
116 let request = &mut fetch_params.request;
117
118 if request.window == Window::Client {
121 } else {
123 request.window = Window::NoWindow;
124 }
125
126 if request.origin == Origin::Client {
129 unimplemented!()
131 }
132
133 if let RequestPolicyContainer::Client = request.policy_container {
143 request.policy_container =
149 RequestPolicyContainer::PolicyContainer(PolicyContainer::default());
150 }
151
152 set_default_accept(request);
154
155 set_default_accept_language(&mut request.headers);
158
159 if request.is_subresource_request() {
166 }
168
169 main_fetch(&mut fetch_params, cache, false, target, &mut None, context).await;
171
172 }
175
176pub(crate) fn convert_request_to_csp_request(request: &Request) -> Option<csp::Request> {
177 let origin = match &request.origin {
178 Origin::Client => return None,
179 Origin::Origin(origin) => origin,
180 };
181 let csp_request = csp::Request {
182 url: request.url().into_url(),
183 origin: origin.clone().into_url_origin(),
184 redirect_count: request.redirect_count,
185 destination: request.destination,
186 initiator: match request.initiator {
187 Initiator::Download => csp::Initiator::Download,
188 Initiator::ImageSet => csp::Initiator::ImageSet,
189 Initiator::Manifest => csp::Initiator::Manifest,
190 Initiator::Prefetch => csp::Initiator::Prefetch,
191 _ => csp::Initiator::None,
192 },
193 nonce: request.cryptographic_nonce_metadata.clone(),
194 integrity_metadata: request.integrity_metadata.clone(),
195 parser_metadata: match request.parser_metadata {
196 ParserMetadata::ParserInserted => csp::ParserMetadata::ParserInserted,
197 ParserMetadata::NotParserInserted => csp::ParserMetadata::NotParserInserted,
198 ParserMetadata::Default => csp::ParserMetadata::None,
199 },
200 };
201 Some(csp_request)
202}
203
204pub fn should_request_be_blocked_by_csp(
206 csp_request: &csp::Request,
207 policy_container: &PolicyContainer,
208) -> (csp::CheckResult, Vec<csp::Violation>) {
209 policy_container
210 .csp_list
211 .as_ref()
212 .map(|c| c.should_request_be_blocked(csp_request))
213 .unwrap_or((csp::CheckResult::Allowed, Vec::new()))
214}
215
216pub fn report_violations_for_request_by_csp(
218 csp_request: &csp::Request,
219 policy_container: &PolicyContainer,
220) -> Vec<csp::Violation> {
221 policy_container
222 .csp_list
223 .as_ref()
224 .map(|c| c.report_violations_for_request(csp_request))
225 .unwrap_or_default()
226}
227
228fn should_response_be_blocked_by_csp(
229 csp_request: &csp::Request,
230 response: &Response,
231 policy_container: &PolicyContainer,
232) -> (csp::CheckResult, Vec<csp::Violation>) {
233 if response.is_network_error() {
234 return (csp::CheckResult::Allowed, Vec::new());
235 }
236 let csp_response = csp::Response {
237 url: response
238 .actual_response()
239 .url()
240 .cloned()
241 .expect("response must have a url")
242 .into_url(),
243 redirect_count: csp_request.redirect_count,
244 };
245 policy_container
246 .csp_list
247 .as_ref()
248 .map(|c| c.should_response_to_request_be_blocked(csp_request, &csp_response))
249 .unwrap_or((csp::CheckResult::Allowed, Vec::new()))
250}
251
252pub async fn main_fetch(
254 fetch_params: &mut FetchParams,
255 cache: &mut CorsCache,
256 recursive_flag: bool,
257 target: Target<'_>,
258 done_chan: &mut DoneChannel,
259 context: &FetchContext,
260) -> Response {
261 let request = &mut fetch_params.request;
263 send_early_httprequest_to_devtools(request, context);
265 let mut response = None;
267
268 if let Some(ref details) = request.crash {
270 response = Some(Response::network_error(NetworkError::Crash(
271 details.clone(),
272 )));
273 }
274
275 if request.local_urls_only &&
278 !matches!(
279 request.current_url().scheme(),
280 "about" | "blob" | "data" | "filesystem"
281 )
282 {
283 response = Some(Response::network_error(NetworkError::Internal(
284 "Non-local scheme".into(),
285 )));
286 }
287
288 let policy_container = match &request.policy_container {
291 RequestPolicyContainer::Client => PolicyContainer::default(),
292 RequestPolicyContainer::PolicyContainer(container) => container.to_owned(),
293 };
294 let csp_request = convert_request_to_csp_request(request);
295 if let Some(csp_request) = csp_request.as_ref() {
296 let violations = report_violations_for_request_by_csp(csp_request, &policy_container);
298
299 if !violations.is_empty() {
300 target.process_csp_violations(request, violations);
301 }
302 };
303
304 if should_upgrade_request_to_potentially_trustworty(request, context) ||
309 should_upgrade_mixed_content_request(request, &context.protocols)
310 {
311 trace!(
312 "upgrading {} targeting {:?}",
313 request.current_url(),
314 request.destination
315 );
316 if let Some(new_scheme) = match request.current_url().scheme() {
317 "http" => Some("https"),
318 "ws" => Some("wss"),
319 _ => None,
320 } {
321 request
322 .current_url_mut()
323 .as_mut_url()
324 .set_scheme(new_scheme)
325 .unwrap();
326 }
327 } else {
328 trace!(
329 "not upgrading {} targeting {:?} with {:?}",
330 request.current_url(),
331 request.destination,
332 request.insecure_requests_policy
333 );
334 }
335 if let Some(csp_request) = csp_request.as_ref() {
336 let (check_result, violations) =
340 should_request_be_blocked_by_csp(csp_request, &policy_container);
341
342 if !violations.is_empty() {
343 target.process_csp_violations(request, violations);
344 }
345
346 if check_result == csp::CheckResult::Blocked {
347 warn!("Request blocked by CSP");
348 response = Some(Response::network_error(NetworkError::Internal(
349 "Blocked by Content-Security-Policy".into(),
350 )))
351 }
352 };
353 if should_request_be_blocked_due_to_a_bad_port(&request.current_url()) {
354 response = Some(Response::network_error(NetworkError::Internal(
355 "Request attempted on bad port".into(),
356 )));
357 }
358 if should_request_be_blocked_as_mixed_content(request, &context.protocols) {
359 response = Some(Response::network_error(NetworkError::Internal(
360 "Blocked as mixed content".into(),
361 )));
362 }
363
364 if request.referrer_policy == ReferrerPolicy::EmptyString {
367 request.referrer_policy = policy_container.get_referrer_policy();
368 }
369
370 let referrer_url = match mem::replace(&mut request.referrer, Referrer::NoReferrer) {
371 Referrer::NoReferrer => None,
372 Referrer::ReferrerUrl(referrer_source) | Referrer::Client(referrer_source) => {
373 request.headers.remove(header::REFERER);
374 determine_requests_referrer(
375 request.referrer_policy,
376 referrer_source,
377 request.current_url(),
378 )
379 },
380 };
381 request.referrer = referrer_url.map_or(Referrer::NoReferrer, Referrer::ReferrerUrl);
382
383 context
388 .state
389 .hsts_list
390 .read()
391 .unwrap()
392 .apply_hsts_rules(request.current_url_mut());
393
394 let current_url = request.current_url();
400 let current_scheme = current_url.scheme();
401
402 context
404 .request_interceptor
405 .lock()
406 .unwrap()
407 .intercept_request(request, &mut response, context);
408
409 let mut response = match response {
410 Some(res) => res,
411 None => {
412 let same_origin = if let Origin::Origin(ref origin) = request.origin {
413 *origin == current_url.origin()
414 } else {
415 false
416 };
417
418 if (same_origin && request.response_tainting == ResponseTainting::Basic) ||
421 current_scheme == "data" ||
423 context.protocols.is_fetchable(current_scheme) ||
426 matches!(
428 request.mode,
429 RequestMode::Navigate | RequestMode::WebSocket { .. }
430 )
431 {
432 request.response_tainting = ResponseTainting::Basic;
434
435 scheme_fetch(fetch_params, cache, target, done_chan, context).await
437 } else if request.mode == RequestMode::SameOrigin {
438 Response::network_error(NetworkError::Internal("Cross-origin response".into()))
439 } else if request.mode == RequestMode::NoCors {
440 if request.redirect_mode != RedirectMode::Follow {
442 Response::network_error(NetworkError::Internal(
443 "NoCors requests must follow redirects".into(),
444 ))
445 } else {
446 request.response_tainting = ResponseTainting::Opaque;
448
449 scheme_fetch(fetch_params, cache, target, done_chan, context).await
451 }
452 } else if !matches!(current_scheme, "http" | "https") {
453 Response::network_error(NetworkError::Internal("Non-http scheme".into()))
454 } else if request.use_cors_preflight ||
455 (request.unsafe_request &&
456 (!is_cors_safelisted_method(&request.method) ||
457 request.headers.iter().any(|(name, value)| {
458 !is_cors_safelisted_request_header(&name, &value)
459 })))
460 {
461 request.response_tainting = ResponseTainting::CorsTainting;
463 let response = http_fetch(
465 fetch_params,
466 cache,
467 true,
468 true,
469 false,
470 target,
471 done_chan,
472 context,
473 )
474 .await;
475 if response.is_network_error() {
477 }
479 response
481 } else {
482 request.response_tainting = ResponseTainting::CorsTainting;
484 http_fetch(
486 fetch_params,
487 cache,
488 true,
489 false,
490 false,
491 target,
492 done_chan,
493 context,
494 )
495 .await
496 }
497 },
498 };
499
500 if recursive_flag {
502 return response;
503 }
504
505 let request = &mut fetch_params.request;
507
508 let mut response = if !response.is_network_error() && response.internal_response.is_none() {
510 if request.response_tainting == ResponseTainting::CorsTainting {
512 let header_names: Option<Vec<HeaderName>> = response
514 .headers
515 .typed_get::<AccessControlExposeHeaders>()
516 .map(|v| v.iter().collect());
517 match header_names {
518 Some(ref list)
520 if request.credentials_mode != CredentialsMode::Include &&
521 list.iter().any(|header| header == "*") =>
522 {
523 response.cors_exposed_header_name_list = response
524 .headers
525 .iter()
526 .map(|(name, _)| name.as_str().to_owned())
527 .collect();
528 },
529 Some(list) => {
531 response.cors_exposed_header_name_list =
532 list.iter().map(|h| h.as_str().to_owned()).collect();
533 },
534 _ => (),
535 }
536 }
537
538 let response_type = match request.response_tainting {
540 ResponseTainting::Basic => ResponseType::Basic,
541 ResponseTainting::CorsTainting => ResponseType::Cors,
542 ResponseTainting::Opaque => ResponseType::Opaque,
543 };
544 response.to_filtered(response_type)
545 } else {
546 response
547 };
548
549 let internal_error = {
550 let response_is_network_error = response.is_network_error();
552 let should_replace_with_nosniff_error = !response_is_network_error &&
553 should_be_blocked_due_to_nosniff(request.destination, &response.headers);
554 let should_replace_with_mime_type_error = !response_is_network_error &&
555 should_be_blocked_due_to_mime_type(request.destination, &response.headers);
556 let should_replace_with_mixed_content = !response_is_network_error &&
557 should_response_be_blocked_as_mixed_content(request, &response, &context.protocols);
558 let should_replace_with_csp_error = csp_request.is_some_and(|csp_request| {
559 let (check_result, violations) =
560 should_response_be_blocked_by_csp(&csp_request, &response, &policy_container);
561 if !violations.is_empty() {
562 target.process_csp_violations(request, violations);
563 }
564 check_result == csp::CheckResult::Blocked
565 });
566
567 let mut network_error_response = response
569 .get_network_error()
570 .cloned()
571 .map(Response::network_error);
572
573 let response_type = response.response_type.clone(); let internal_response = if let Some(error_response) = network_error_response.as_mut() {
577 error_response
578 } else {
579 response.actual_response_mut()
580 };
581
582 if internal_response.url_list.is_empty() {
584 internal_response.url_list.clone_from(&request.url_list)
585 }
586
587 let mut blocked_error_response;
593
594 let internal_response = if should_replace_with_nosniff_error {
595 blocked_error_response =
597 Response::network_error(NetworkError::Internal("Blocked by nosniff".into()));
598 &blocked_error_response
599 } else if should_replace_with_mime_type_error {
600 blocked_error_response =
602 Response::network_error(NetworkError::Internal("Blocked by mime type".into()));
603 &blocked_error_response
604 } else if should_replace_with_mixed_content {
605 blocked_error_response =
606 Response::network_error(NetworkError::Internal("Blocked as mixed content".into()));
607 &blocked_error_response
608 } else if should_replace_with_csp_error {
609 blocked_error_response =
610 Response::network_error(NetworkError::Internal("Blocked due to CSP".into()));
611 &blocked_error_response
612 } else {
613 internal_response
614 };
615
616 let internal_response = if !internal_response.is_network_error() &&
622 response_type == ResponseType::Opaque &&
623 internal_response.status.code() == StatusCode::PARTIAL_CONTENT &&
624 internal_response.range_requested &&
625 !request.headers.contains_key(RANGE)
626 {
627 blocked_error_response = Response::network_error(NetworkError::Internal(
629 PARTIAL_RESPONSE_TO_NON_RANGE_REQUEST_ERROR.into(),
630 ));
631 &blocked_error_response
632 } else {
633 internal_response
634 };
635
636 let not_network_error = !response_is_network_error && !internal_response.is_network_error();
641 if not_network_error &&
642 (is_null_body_status(&internal_response.status) ||
643 matches!(request.method, Method::HEAD | Method::CONNECT))
644 {
645 let mut body = internal_response.body.lock().unwrap();
648 *body = ResponseBody::Empty;
649 }
650
651 internal_response.get_network_error().cloned()
652 };
653
654 let mut response = if let Some(error) = internal_error {
656 Response::network_error(error)
657 } else {
658 response
659 };
660
661 let mut response_loaded = false;
663 let mut response = if !response.is_network_error() && !request.integrity_metadata.is_empty() {
664 wait_for_response(request, &mut response, target, done_chan, context).await;
666 response_loaded = true;
667
668 let integrity_metadata = &request.integrity_metadata;
670 if response.termination_reason.is_none() &&
671 !is_response_integrity_valid(integrity_metadata, &response)
672 {
673 Response::network_error(NetworkError::Internal(
674 "Subresource integrity validation failed".into(),
675 ))
676 } else {
677 response
678 }
679 } else {
680 response
681 };
682
683 if request.synchronous {
685 target.process_response(request, &response);
688 if !response_loaded {
689 wait_for_response(request, &mut response, target, done_chan, context).await;
690 }
691 target.process_response_eof(request, &response);
693 return response;
694 }
695
696 if request.body.is_some() && matches!(current_scheme, "http" | "https") {
698 target.process_request_body(request);
703 target.process_request_eof(request);
704 }
705
706 target.process_response(request, &response);
708 send_response_to_devtools(request, context, &response, None);
710
711 if !response_loaded {
713 wait_for_response(request, &mut response, target, done_chan, context).await;
714 }
715
716 target.process_response_eof(request, &response);
718 send_response_to_devtools(request, context, &response, None);
722
723 if let Ok(http_cache) = context.state.http_cache.write() {
724 http_cache.update_awaiting_consumers(request, &response);
725 }
726
727 response
730}
731
732async fn wait_for_response(
733 request: &Request,
734 response: &mut Response,
735 target: Target<'_>,
736 done_chan: &mut DoneChannel,
737 context: &FetchContext,
738) {
739 if let Some(ref mut ch) = *done_chan {
740 let mut devtools_body = context.devtools_chan.as_ref().map(|_| Vec::new());
741 loop {
742 match ch.1.recv().await {
743 Some(Data::Payload(vec)) => {
744 if let Some(body) = devtools_body.as_mut() {
745 body.extend(&vec);
746 }
747 target.process_response_chunk(request, vec);
748 },
749 Some(Data::Done) => {
750 send_response_to_devtools(request, context, response, devtools_body);
751 break;
752 },
753 Some(Data::Cancelled) => {
754 response.aborted.store(true, Ordering::Release);
755 break;
756 },
757 _ => {
758 panic!("fetch worker should always send Done before terminating");
759 },
760 }
761 }
762 } else {
763 let body = response.actual_response().body.lock().unwrap();
764 if let ResponseBody::Done(ref vec) = *body {
765 target.process_response_chunk(request, vec.clone());
769 if context.devtools_chan.is_some() {
770 send_response_to_devtools(request, context, response, Some(vec.clone()));
773 }
774 } else {
775 assert_eq!(*body, ResponseBody::Empty)
776 }
777 }
778}
779
780pub enum RangeRequestBounds {
782 Final(RelativePos),
784 Pending(u64),
787}
788
789impl RangeRequestBounds {
790 pub fn get_final(&self, len: Option<u64>) -> Result<RelativePos, &'static str> {
791 match self {
792 RangeRequestBounds::Final(pos) => {
793 if let Some(len) = len {
794 if pos.start <= len as i64 {
795 return Ok(*pos);
796 }
797 }
798 Err("Tried to process RangeRequestBounds::Final without len")
799 },
800 RangeRequestBounds::Pending(offset) => Ok(RelativePos::from_opts(
801 if let Some(len) = len {
802 Some((len - u64::min(len, *offset)) as i64)
803 } else {
804 Some(0)
805 },
806 None,
807 )),
808 }
809 }
810}
811
812fn create_blank_reply(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
813 let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
814 response
815 .headers
816 .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
817 *response.body.lock().unwrap() = ResponseBody::Done(vec![]);
818 response.status = HttpStatus::default();
819 response
820}
821
822fn create_about_memory(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
823 let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
824 response
825 .headers
826 .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
827 *response.body.lock().unwrap() =
828 ResponseBody::Done(resources::read_bytes(Resource::AboutMemoryHTML));
829 response.status = HttpStatus::default();
830 response
831}
832
833fn handle_allowcert_request(request: &mut Request, context: &FetchContext) -> io::Result<()> {
835 let error = |string| Err(io::Error::other(string));
836
837 let body = match request.body.as_mut() {
838 Some(body) => body,
839 None => return error("No body found"),
840 };
841
842 let stream = body.take_stream();
843 let stream = stream.lock().unwrap();
844 let (body_chan, body_port) = ipc::channel().unwrap();
845 let _ = stream.send(BodyChunkRequest::Connect(body_chan));
846 let _ = stream.send(BodyChunkRequest::Chunk);
847 let body_bytes = match body_port.recv().ok() {
848 Some(BodyChunkResponse::Chunk(bytes)) => bytes,
849 _ => return error("Certificate not sent in a single chunk"),
850 };
851
852 let split_idx = match body_bytes.iter().position(|b| *b == b'&') {
853 Some(split_idx) => split_idx,
854 None => return error("Could not find ampersand in data"),
855 };
856 let (secret, cert_base64) = body_bytes.split_at(split_idx);
857
858 let secret = str::from_utf8(secret).ok().and_then(|s| s.parse().ok());
859 if secret != Some(*net_traits::PRIVILEGED_SECRET) {
860 return error("Invalid secret sent. Ignoring request");
861 }
862
863 let cert_bytes = match general_purpose::STANDARD_NO_PAD.decode(&cert_base64[1..]) {
864 Ok(bytes) => bytes,
865 Err(_) => return error("Could not decode certificate base64"),
866 };
867
868 context
869 .state
870 .override_manager
871 .add_override(&CertificateDer::from_slice(&cert_bytes).into_owned());
872 Ok(())
873}
874
875async fn scheme_fetch(
877 fetch_params: &mut FetchParams,
878 cache: &mut CorsCache,
879 target: Target<'_>,
880 done_chan: &mut DoneChannel,
881 context: &FetchContext,
882) -> Response {
883 let request = &mut fetch_params.request;
887 let url = request.current_url();
888
889 let scheme = url.scheme();
890 match scheme {
891 "about" if url.path() == "blank" => create_blank_reply(url, request.timing_type()),
892 "about" if url.path() == "memory" => create_about_memory(url, request.timing_type()),
893
894 "chrome" if url.path() == "allowcert" => {
895 if let Err(error) = handle_allowcert_request(request, context) {
896 warn!("Could not handle allowcert request: {error}");
897 }
898 create_blank_reply(url, request.timing_type())
899 },
900
901 "http" | "https" => {
902 http_fetch(
903 fetch_params,
904 cache,
905 false,
906 false,
907 false,
908 target,
909 done_chan,
910 context,
911 )
912 .await
913 },
914
915 _ => match context.protocols.get(scheme) {
916 Some(handler) => handler.load(request, done_chan, context).await,
917 None => Response::network_error(NetworkError::Internal("Unexpected scheme".into())),
918 },
919 }
920}
921
922fn is_null_body_status(status: &HttpStatus) -> bool {
923 matches!(
924 status.try_code(),
925 Some(StatusCode::SWITCHING_PROTOCOLS) |
926 Some(StatusCode::NO_CONTENT) |
927 Some(StatusCode::RESET_CONTENT) |
928 Some(StatusCode::NOT_MODIFIED)
929 )
930}
931
932pub fn should_be_blocked_due_to_nosniff(
934 destination: Destination,
935 response_headers: &HeaderMap,
936) -> bool {
937 if !determine_nosniff(response_headers) {
939 return false;
940 }
941
942 let mime_type = extract_mime_type_as_mime(response_headers);
945
946 #[inline]
948 fn is_javascript_mime_type(mime_type: &Mime) -> bool {
949 let javascript_mime_types: [Mime; 16] = [
950 "application/ecmascript".parse().unwrap(),
951 "application/javascript".parse().unwrap(),
952 "application/x-ecmascript".parse().unwrap(),
953 "application/x-javascript".parse().unwrap(),
954 "text/ecmascript".parse().unwrap(),
955 "text/javascript".parse().unwrap(),
956 "text/javascript1.0".parse().unwrap(),
957 "text/javascript1.1".parse().unwrap(),
958 "text/javascript1.2".parse().unwrap(),
959 "text/javascript1.3".parse().unwrap(),
960 "text/javascript1.4".parse().unwrap(),
961 "text/javascript1.5".parse().unwrap(),
962 "text/jscript".parse().unwrap(),
963 "text/livescript".parse().unwrap(),
964 "text/x-ecmascript".parse().unwrap(),
965 "text/x-javascript".parse().unwrap(),
966 ];
967
968 javascript_mime_types
969 .iter()
970 .any(|mime| mime.type_() == mime_type.type_() && mime.subtype() == mime_type.subtype())
971 }
972
973 match mime_type {
974 Some(ref mime_type) if destination.is_script_like() => !is_javascript_mime_type(mime_type),
976 Some(ref mime_type) if destination == Destination::Style => {
978 mime_type.type_() != mime::TEXT && mime_type.subtype() != mime::CSS
979 },
980
981 None if destination == Destination::Style || destination.is_script_like() => true,
982 _ => false,
984 }
985}
986
987fn should_be_blocked_due_to_mime_type(
989 destination: Destination,
990 response_headers: &HeaderMap,
991) -> bool {
992 let mime_type: mime::Mime = match extract_mime_type_as_mime(response_headers) {
994 Some(mime_type) => mime_type,
995 None => return false,
997 };
998
999 destination.is_script_like() &&
1005 match mime_type.type_() {
1006 mime::AUDIO | mime::VIDEO | mime::IMAGE => true,
1007 mime::TEXT if mime_type.subtype() == mime::CSV => true,
1008 _ => false,
1009 }
1010}
1011
1012pub fn should_request_be_blocked_due_to_a_bad_port(url: &ServoUrl) -> bool {
1014 let is_http_scheme = matches!(url.scheme(), "http" | "https");
1019 let is_bad_port = url.port().is_some_and(is_bad_port);
1020 if is_http_scheme && is_bad_port {
1021 return true;
1022 }
1023
1024 false
1026}
1027
1028pub fn should_request_be_blocked_as_mixed_content(
1030 request: &Request,
1031 protocol_registry: &ProtocolRegistry,
1032) -> bool {
1033 if do_settings_prohibit_mixed_security_contexts(request) ==
1037 MixedSecurityProhibited::NotProhibited
1038 {
1039 return false;
1040 }
1041
1042 if is_url_potentially_trustworthy(protocol_registry, &request.url()) {
1044 return false;
1045 }
1046
1047 if request.destination == Destination::Document {
1052 return false;
1054 }
1055
1056 true
1057}
1058
1059pub fn should_response_be_blocked_as_mixed_content(
1061 request: &Request,
1062 response: &Response,
1063 protocol_registry: &ProtocolRegistry,
1064) -> bool {
1065 if do_settings_prohibit_mixed_security_contexts(request) ==
1069 MixedSecurityProhibited::NotProhibited
1070 {
1071 return false;
1072 }
1073
1074 if response
1076 .actual_response()
1077 .url()
1078 .is_some_and(|response_url| is_url_potentially_trustworthy(protocol_registry, response_url))
1079 {
1080 return false;
1081 }
1082
1083 if request.destination == Destination::Document {
1088 return false;
1090 }
1091
1092 true
1093}
1094
1095fn is_bad_port(port: u16) -> bool {
1097 static BAD_PORTS: [u16; 78] = [
1098 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101,
1099 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389,
1100 427, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636,
1101 993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 5060, 5061, 6000, 6566, 6665, 6666, 6667,
1102 6668, 6669, 6697, 10080,
1103 ];
1104
1105 BAD_PORTS.binary_search(&port).is_ok()
1106}
1107
1108pub fn is_form_submission_request(request: &Request) -> bool {
1110 let content_type = request.headers.typed_get::<ContentType>();
1111 content_type.is_some_and(|ct| {
1112 let mime: Mime = ct.into();
1113 mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED
1114 })
1115}
1116
1117fn should_upgrade_request_to_potentially_trustworty(
1119 request: &mut Request,
1120 context: &FetchContext,
1121) -> bool {
1122 fn should_upgrade_navigation_request(request: &Request) -> bool {
1123 if is_form_submission_request(request) {
1125 return true;
1126 }
1127
1128 false
1136 }
1137
1138 if request.is_navigation_request() {
1140 if !is_url_potentially_trustworthy(&context.protocols, &request.current_url()) ||
1145 !request.current_url().host_str().is_some_and(|host| {
1146 !context.state.hsts_list.read().unwrap().is_host_secure(host)
1147 })
1148 {
1149 debug!("Appending the Upgrade-Insecure-Requests header to request’s header list");
1150 request
1151 .headers
1152 .insert("Upgrade-Insecure-Requests", HeaderValue::from_static("1"));
1153 }
1154
1155 if !should_upgrade_navigation_request(request) {
1156 return false;
1157 }
1158 }
1159
1160 request.insecure_requests_policy == InsecureRequestsPolicy::Upgrade
1162}
1163
1164#[derive(Debug, PartialEq)]
1165pub enum MixedSecurityProhibited {
1166 Prohibited,
1167 NotProhibited,
1168}
1169
1170fn do_settings_prohibit_mixed_security_contexts(request: &Request) -> MixedSecurityProhibited {
1172 if let Origin::Origin(ref origin) = request.origin {
1173 let is_origin_data_url_worker = matches!(
1175 *origin,
1176 ImmutableOrigin::Opaque(servo_url::OpaqueOrigin::SecureWorkerFromDataUrl(_))
1177 );
1178
1179 if origin.is_potentially_trustworthy() || is_origin_data_url_worker {
1182 return MixedSecurityProhibited::Prohibited;
1183 }
1184 }
1185
1186 if request.has_trustworthy_ancestor_origin {
1190 return MixedSecurityProhibited::Prohibited;
1191 }
1192
1193 MixedSecurityProhibited::NotProhibited
1194}
1195
1196fn should_upgrade_mixed_content_request(
1198 request: &Request,
1199 protocol_registry: &ProtocolRegistry,
1200) -> bool {
1201 let url = request.url();
1202 if is_url_potentially_trustworthy(protocol_registry, &url) {
1204 return false;
1205 }
1206
1207 match url.host() {
1209 Some(Host::Ipv4(_)) | Some(Host::Ipv6(_)) => return false,
1210 _ => (),
1211 }
1212
1213 if do_settings_prohibit_mixed_security_contexts(request) ==
1215 MixedSecurityProhibited::NotProhibited
1216 {
1217 return false;
1218 }
1219
1220 if !matches!(
1222 request.destination,
1223 Destination::Audio | Destination::Image | Destination::Video
1224 ) {
1225 return false;
1226 }
1227
1228 if request.destination == Destination::Image && request.initiator == Initiator::ImageSet {
1230 return false;
1231 }
1232
1233 true
1234}