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);
264 let mut response = None;
266
267 if let Some(ref details) = request.crash {
269 response = Some(Response::network_error(NetworkError::Crash(
270 details.clone(),
271 )));
272 }
273
274 if request.local_urls_only &&
277 !matches!(
278 request.current_url().scheme(),
279 "about" | "blob" | "data" | "filesystem"
280 )
281 {
282 response = Some(Response::network_error(NetworkError::Internal(
283 "Non-local scheme".into(),
284 )));
285 }
286
287 let policy_container = match &request.policy_container {
290 RequestPolicyContainer::Client => PolicyContainer::default(),
291 RequestPolicyContainer::PolicyContainer(container) => container.to_owned(),
292 };
293 let csp_request = convert_request_to_csp_request(request);
294 if let Some(csp_request) = csp_request.as_ref() {
295 let violations = report_violations_for_request_by_csp(csp_request, &policy_container);
297
298 if !violations.is_empty() {
299 target.process_csp_violations(request, violations);
300 }
301 };
302
303 if should_upgrade_request_to_potentially_trustworty(request, context) ||
308 should_upgrade_mixed_content_request(request, &context.protocols)
309 {
310 trace!(
311 "upgrading {} targeting {:?}",
312 request.current_url(),
313 request.destination
314 );
315 if let Some(new_scheme) = match request.current_url().scheme() {
316 "http" => Some("https"),
317 "ws" => Some("wss"),
318 _ => None,
319 } {
320 request
321 .current_url_mut()
322 .as_mut_url()
323 .set_scheme(new_scheme)
324 .unwrap();
325 }
326 } else {
327 trace!(
328 "not upgrading {} targeting {:?} with {:?}",
329 request.current_url(),
330 request.destination,
331 request.insecure_requests_policy
332 );
333 }
334 if let Some(csp_request) = csp_request.as_ref() {
335 let (check_result, violations) =
339 should_request_be_blocked_by_csp(csp_request, &policy_container);
340
341 if !violations.is_empty() {
342 target.process_csp_violations(request, violations);
343 }
344
345 if check_result == csp::CheckResult::Blocked {
346 warn!("Request blocked by CSP");
347 response = Some(Response::network_error(NetworkError::Internal(
348 "Blocked by Content-Security-Policy".into(),
349 )))
350 }
351 };
352 if should_request_be_blocked_due_to_a_bad_port(&request.current_url()) {
353 response = Some(Response::network_error(NetworkError::Internal(
354 "Request attempted on bad port".into(),
355 )));
356 }
357 if should_request_be_blocked_as_mixed_content(request, &context.protocols) {
358 response = Some(Response::network_error(NetworkError::Internal(
359 "Blocked as mixed content".into(),
360 )));
361 }
362
363 if request.referrer_policy == ReferrerPolicy::EmptyString {
366 request.referrer_policy = policy_container.get_referrer_policy();
367 }
368
369 let referrer_url = match mem::replace(&mut request.referrer, Referrer::NoReferrer) {
370 Referrer::NoReferrer => None,
371 Referrer::ReferrerUrl(referrer_source) | Referrer::Client(referrer_source) => {
372 request.headers.remove(header::REFERER);
373 determine_requests_referrer(
374 request.referrer_policy,
375 referrer_source,
376 request.current_url(),
377 )
378 },
379 };
380 request.referrer = referrer_url.map_or(Referrer::NoReferrer, Referrer::ReferrerUrl);
381
382 context
387 .state
388 .hsts_list
389 .read()
390 .unwrap()
391 .apply_hsts_rules(request.current_url_mut());
392
393 let current_url = request.current_url();
399 let current_scheme = current_url.scheme();
400
401 context
403 .request_interceptor
404 .lock()
405 .unwrap()
406 .intercept_request(request, &mut response, context);
407
408 let mut response = match response {
409 Some(res) => res,
410 None => {
411 let same_origin = if let Origin::Origin(ref origin) = request.origin {
412 *origin == current_url.origin()
413 } else {
414 false
415 };
416
417 if (same_origin && request.response_tainting == ResponseTainting::Basic) ||
420 current_scheme == "data" ||
422 context.protocols.is_fetchable(current_scheme) ||
425 matches!(
427 request.mode,
428 RequestMode::Navigate | RequestMode::WebSocket { .. }
429 )
430 {
431 request.response_tainting = ResponseTainting::Basic;
433
434 scheme_fetch(fetch_params, cache, target, done_chan, context).await
436 } else if request.mode == RequestMode::SameOrigin {
437 Response::network_error(NetworkError::Internal("Cross-origin response".into()))
438 } else if request.mode == RequestMode::NoCors {
439 if request.redirect_mode != RedirectMode::Follow {
441 Response::network_error(NetworkError::Internal(
442 "NoCors requests must follow redirects".into(),
443 ))
444 } else {
445 request.response_tainting = ResponseTainting::Opaque;
447
448 scheme_fetch(fetch_params, cache, target, done_chan, context).await
450 }
451 } else if !matches!(current_scheme, "http" | "https") {
452 Response::network_error(NetworkError::Internal("Non-http scheme".into()))
453 } else if request.use_cors_preflight ||
454 (request.unsafe_request &&
455 (!is_cors_safelisted_method(&request.method) ||
456 request.headers.iter().any(|(name, value)| {
457 !is_cors_safelisted_request_header(&name, &value)
458 })))
459 {
460 request.response_tainting = ResponseTainting::CorsTainting;
462 let response = http_fetch(
464 fetch_params,
465 cache,
466 true,
467 true,
468 false,
469 target,
470 done_chan,
471 context,
472 )
473 .await;
474 if response.is_network_error() {
476 }
478 response
480 } else {
481 request.response_tainting = ResponseTainting::CorsTainting;
483 http_fetch(
485 fetch_params,
486 cache,
487 true,
488 false,
489 false,
490 target,
491 done_chan,
492 context,
493 )
494 .await
495 }
496 },
497 };
498
499 if recursive_flag {
501 return response;
502 }
503
504 let request = &mut fetch_params.request;
506
507 let mut response = if !response.is_network_error() && response.internal_response.is_none() {
509 if request.response_tainting == ResponseTainting::CorsTainting {
511 let header_names: Option<Vec<HeaderName>> = response
513 .headers
514 .typed_get::<AccessControlExposeHeaders>()
515 .map(|v| v.iter().collect());
516 match header_names {
517 Some(ref list)
519 if request.credentials_mode != CredentialsMode::Include &&
520 list.iter().any(|header| header == "*") =>
521 {
522 response.cors_exposed_header_name_list = response
523 .headers
524 .iter()
525 .map(|(name, _)| name.as_str().to_owned())
526 .collect();
527 },
528 Some(list) => {
530 response.cors_exposed_header_name_list =
531 list.iter().map(|h| h.as_str().to_owned()).collect();
532 },
533 _ => (),
534 }
535 }
536
537 let response_type = match request.response_tainting {
539 ResponseTainting::Basic => ResponseType::Basic,
540 ResponseTainting::CorsTainting => ResponseType::Cors,
541 ResponseTainting::Opaque => ResponseType::Opaque,
542 };
543 response.to_filtered(response_type)
544 } else {
545 response
546 };
547
548 let internal_error = {
549 let response_is_network_error = response.is_network_error();
551 let should_replace_with_nosniff_error = !response_is_network_error &&
552 should_be_blocked_due_to_nosniff(request.destination, &response.headers);
553 let should_replace_with_mime_type_error = !response_is_network_error &&
554 should_be_blocked_due_to_mime_type(request.destination, &response.headers);
555 let should_replace_with_mixed_content = !response_is_network_error &&
556 should_response_be_blocked_as_mixed_content(request, &response, &context.protocols);
557 let should_replace_with_csp_error = csp_request.is_some_and(|csp_request| {
558 let (check_result, violations) =
559 should_response_be_blocked_by_csp(&csp_request, &response, &policy_container);
560 if !violations.is_empty() {
561 target.process_csp_violations(request, violations);
562 }
563 check_result == csp::CheckResult::Blocked
564 });
565
566 let mut network_error_response = response
568 .get_network_error()
569 .cloned()
570 .map(Response::network_error);
571
572 let response_type = response.response_type.clone(); let internal_response = if let Some(error_response) = network_error_response.as_mut() {
576 error_response
577 } else {
578 response.actual_response_mut()
579 };
580
581 if internal_response.url_list.is_empty() {
583 internal_response.url_list.clone_from(&request.url_list)
584 }
585
586 let mut blocked_error_response;
592
593 let internal_response = if should_replace_with_nosniff_error {
594 blocked_error_response =
596 Response::network_error(NetworkError::Internal("Blocked by nosniff".into()));
597 &blocked_error_response
598 } else if should_replace_with_mime_type_error {
599 blocked_error_response =
601 Response::network_error(NetworkError::Internal("Blocked by mime type".into()));
602 &blocked_error_response
603 } else if should_replace_with_mixed_content {
604 blocked_error_response =
605 Response::network_error(NetworkError::Internal("Blocked as mixed content".into()));
606 &blocked_error_response
607 } else if should_replace_with_csp_error {
608 blocked_error_response =
609 Response::network_error(NetworkError::Internal("Blocked due to CSP".into()));
610 &blocked_error_response
611 } else {
612 internal_response
613 };
614
615 let internal_response = if !internal_response.is_network_error() &&
621 response_type == ResponseType::Opaque &&
622 internal_response.status.code() == StatusCode::PARTIAL_CONTENT &&
623 internal_response.range_requested &&
624 !request.headers.contains_key(RANGE)
625 {
626 blocked_error_response = Response::network_error(NetworkError::Internal(
628 PARTIAL_RESPONSE_TO_NON_RANGE_REQUEST_ERROR.into(),
629 ));
630 &blocked_error_response
631 } else {
632 internal_response
633 };
634
635 let not_network_error = !response_is_network_error && !internal_response.is_network_error();
640 if not_network_error &&
641 (is_null_body_status(&internal_response.status) ||
642 matches!(request.method, Method::HEAD | Method::CONNECT))
643 {
644 let mut body = internal_response.body.lock().unwrap();
647 *body = ResponseBody::Empty;
648 }
649
650 internal_response.get_network_error().cloned()
651 };
652
653 let mut response = if let Some(error) = internal_error {
655 Response::network_error(error)
656 } else {
657 response
658 };
659
660 let mut response_loaded = false;
662 let mut response = if !response.is_network_error() && !request.integrity_metadata.is_empty() {
663 wait_for_response(request, &mut response, target, done_chan, context).await;
665 response_loaded = true;
666
667 let integrity_metadata = &request.integrity_metadata;
669 if response.termination_reason.is_none() &&
670 !is_response_integrity_valid(integrity_metadata, &response)
671 {
672 Response::network_error(NetworkError::Internal(
673 "Subresource integrity validation failed".into(),
674 ))
675 } else {
676 response
677 }
678 } else {
679 response
680 };
681
682 if request.synchronous {
684 target.process_response(request, &response);
687 if !response_loaded {
688 wait_for_response(request, &mut response, target, done_chan, context).await;
689 }
690 target.process_response_eof(request, &response);
692 return response;
693 }
694
695 if request.body.is_some() && matches!(current_scheme, "http" | "https") {
697 target.process_request_body(request);
702 target.process_request_eof(request);
703 }
704
705 target.process_response(request, &response);
707 send_response_to_devtools(request, context, &response, None);
709
710 if !response_loaded {
712 wait_for_response(request, &mut response, target, done_chan, context).await;
713 }
714
715 target.process_response_eof(request, &response);
717 send_response_to_devtools(request, context, &response, None);
721
722 if let Ok(http_cache) = context.state.http_cache.write() {
723 http_cache.update_awaiting_consumers(request, &response);
724 }
725
726 response
729}
730
731async fn wait_for_response(
732 request: &Request,
733 response: &mut Response,
734 target: Target<'_>,
735 done_chan: &mut DoneChannel,
736 context: &FetchContext,
737) {
738 if let Some(ref mut ch) = *done_chan {
739 let mut devtools_body = context.devtools_chan.as_ref().map(|_| Vec::new());
740 loop {
741 match ch.1.recv().await {
742 Some(Data::Payload(vec)) => {
743 if let Some(body) = devtools_body.as_mut() {
744 body.extend(&vec);
745 }
746 target.process_response_chunk(request, vec);
747 },
748 Some(Data::Done) => {
749 send_response_to_devtools(request, context, response, devtools_body);
750 break;
751 },
752 Some(Data::Cancelled) => {
753 response.aborted.store(true, Ordering::Release);
754 break;
755 },
756 _ => {
757 panic!("fetch worker should always send Done before terminating");
758 },
759 }
760 }
761 } else {
762 let body = response.actual_response().body.lock().unwrap();
763 if let ResponseBody::Done(ref vec) = *body {
764 target.process_response_chunk(request, vec.clone());
768 if context.devtools_chan.is_some() {
769 send_response_to_devtools(request, context, response, Some(vec.clone()));
772 }
773 } else {
774 assert_eq!(*body, ResponseBody::Empty)
775 }
776 }
777}
778
779pub enum RangeRequestBounds {
781 Final(RelativePos),
783 Pending(u64),
786}
787
788impl RangeRequestBounds {
789 pub fn get_final(&self, len: Option<u64>) -> Result<RelativePos, &'static str> {
790 match self {
791 RangeRequestBounds::Final(pos) => {
792 if let Some(len) = len {
793 if pos.start <= len as i64 {
794 return Ok(*pos);
795 }
796 }
797 Err("Tried to process RangeRequestBounds::Final without len")
798 },
799 RangeRequestBounds::Pending(offset) => Ok(RelativePos::from_opts(
800 if let Some(len) = len {
801 Some((len - u64::min(len, *offset)) as i64)
802 } else {
803 Some(0)
804 },
805 None,
806 )),
807 }
808 }
809}
810
811fn create_blank_reply(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
812 let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
813 response
814 .headers
815 .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
816 *response.body.lock().unwrap() = ResponseBody::Done(vec![]);
817 response.status = HttpStatus::default();
818 response
819}
820
821fn create_about_memory(url: ServoUrl, timing_type: ResourceTimingType) -> Response {
822 let mut response = Response::new(url, ResourceFetchTiming::new(timing_type));
823 response
824 .headers
825 .typed_insert(ContentType::from(mime::TEXT_HTML_UTF_8));
826 *response.body.lock().unwrap() =
827 ResponseBody::Done(resources::read_bytes(Resource::AboutMemoryHTML));
828 response.status = HttpStatus::default();
829 response
830}
831
832fn handle_allowcert_request(request: &mut Request, context: &FetchContext) -> io::Result<()> {
834 let error = |string| Err(io::Error::other(string));
835
836 let body = match request.body.as_mut() {
837 Some(body) => body,
838 None => return error("No body found"),
839 };
840
841 let stream = body.take_stream();
842 let stream = stream.lock().unwrap();
843 let (body_chan, body_port) = ipc::channel().unwrap();
844 let _ = stream.send(BodyChunkRequest::Connect(body_chan));
845 let _ = stream.send(BodyChunkRequest::Chunk);
846 let body_bytes = match body_port.recv().ok() {
847 Some(BodyChunkResponse::Chunk(bytes)) => bytes,
848 _ => return error("Certificate not sent in a single chunk"),
849 };
850
851 let split_idx = match body_bytes.iter().position(|b| *b == b'&') {
852 Some(split_idx) => split_idx,
853 None => return error("Could not find ampersand in data"),
854 };
855 let (secret, cert_base64) = body_bytes.split_at(split_idx);
856
857 let secret = str::from_utf8(secret).ok().and_then(|s| s.parse().ok());
858 if secret != Some(*net_traits::PRIVILEGED_SECRET) {
859 return error("Invalid secret sent. Ignoring request");
860 }
861
862 let cert_bytes = match general_purpose::STANDARD_NO_PAD.decode(&cert_base64[1..]) {
863 Ok(bytes) => bytes,
864 Err(_) => return error("Could not decode certificate base64"),
865 };
866
867 context
868 .state
869 .override_manager
870 .add_override(&CertificateDer::from_slice(&cert_bytes).into_owned());
871 Ok(())
872}
873
874async fn scheme_fetch(
876 fetch_params: &mut FetchParams,
877 cache: &mut CorsCache,
878 target: Target<'_>,
879 done_chan: &mut DoneChannel,
880 context: &FetchContext,
881) -> Response {
882 let request = &mut fetch_params.request;
886 let url = request.current_url();
887
888 let scheme = url.scheme();
889 match scheme {
890 "about" if url.path() == "blank" => create_blank_reply(url, request.timing_type()),
891 "about" if url.path() == "memory" => create_about_memory(url, request.timing_type()),
892
893 "chrome" if url.path() == "allowcert" => {
894 if let Err(error) = handle_allowcert_request(request, context) {
895 warn!("Could not handle allowcert request: {error}");
896 }
897 create_blank_reply(url, request.timing_type())
898 },
899
900 "http" | "https" => {
901 http_fetch(
902 fetch_params,
903 cache,
904 false,
905 false,
906 false,
907 target,
908 done_chan,
909 context,
910 )
911 .await
912 },
913
914 _ => match context.protocols.get(scheme) {
915 Some(handler) => handler.load(request, done_chan, context).await,
916 None => Response::network_error(NetworkError::Internal("Unexpected scheme".into())),
917 },
918 }
919}
920
921fn is_null_body_status(status: &HttpStatus) -> bool {
922 matches!(
923 status.try_code(),
924 Some(StatusCode::SWITCHING_PROTOCOLS) |
925 Some(StatusCode::NO_CONTENT) |
926 Some(StatusCode::RESET_CONTENT) |
927 Some(StatusCode::NOT_MODIFIED)
928 )
929}
930
931pub fn should_be_blocked_due_to_nosniff(
933 destination: Destination,
934 response_headers: &HeaderMap,
935) -> bool {
936 if !determine_nosniff(response_headers) {
938 return false;
939 }
940
941 let mime_type = extract_mime_type_as_mime(response_headers);
944
945 #[inline]
947 fn is_javascript_mime_type(mime_type: &Mime) -> bool {
948 let javascript_mime_types: [Mime; 16] = [
949 "application/ecmascript".parse().unwrap(),
950 "application/javascript".parse().unwrap(),
951 "application/x-ecmascript".parse().unwrap(),
952 "application/x-javascript".parse().unwrap(),
953 "text/ecmascript".parse().unwrap(),
954 "text/javascript".parse().unwrap(),
955 "text/javascript1.0".parse().unwrap(),
956 "text/javascript1.1".parse().unwrap(),
957 "text/javascript1.2".parse().unwrap(),
958 "text/javascript1.3".parse().unwrap(),
959 "text/javascript1.4".parse().unwrap(),
960 "text/javascript1.5".parse().unwrap(),
961 "text/jscript".parse().unwrap(),
962 "text/livescript".parse().unwrap(),
963 "text/x-ecmascript".parse().unwrap(),
964 "text/x-javascript".parse().unwrap(),
965 ];
966
967 javascript_mime_types
968 .iter()
969 .any(|mime| mime.type_() == mime_type.type_() && mime.subtype() == mime_type.subtype())
970 }
971
972 match mime_type {
973 Some(ref mime_type) if destination.is_script_like() => !is_javascript_mime_type(mime_type),
975 Some(ref mime_type) if destination == Destination::Style => {
977 mime_type.type_() != mime::TEXT && mime_type.subtype() != mime::CSS
978 },
979
980 None if destination == Destination::Style || destination.is_script_like() => true,
981 _ => false,
983 }
984}
985
986fn should_be_blocked_due_to_mime_type(
988 destination: Destination,
989 response_headers: &HeaderMap,
990) -> bool {
991 let mime_type: mime::Mime = match extract_mime_type_as_mime(response_headers) {
993 Some(mime_type) => mime_type,
994 None => return false,
996 };
997
998 destination.is_script_like() &&
1004 match mime_type.type_() {
1005 mime::AUDIO | mime::VIDEO | mime::IMAGE => true,
1006 mime::TEXT if mime_type.subtype() == mime::CSV => true,
1007 _ => false,
1008 }
1009}
1010
1011pub fn should_request_be_blocked_due_to_a_bad_port(url: &ServoUrl) -> bool {
1013 let is_http_scheme = matches!(url.scheme(), "http" | "https");
1018 let is_bad_port = url.port().is_some_and(is_bad_port);
1019 if is_http_scheme && is_bad_port {
1020 return true;
1021 }
1022
1023 false
1025}
1026
1027pub fn should_request_be_blocked_as_mixed_content(
1029 request: &Request,
1030 protocol_registry: &ProtocolRegistry,
1031) -> bool {
1032 if do_settings_prohibit_mixed_security_contexts(request) ==
1036 MixedSecurityProhibited::NotProhibited
1037 {
1038 return false;
1039 }
1040
1041 if is_url_potentially_trustworthy(protocol_registry, &request.url()) {
1043 return false;
1044 }
1045
1046 if request.destination == Destination::Document {
1051 return false;
1053 }
1054
1055 true
1056}
1057
1058pub fn should_response_be_blocked_as_mixed_content(
1060 request: &Request,
1061 response: &Response,
1062 protocol_registry: &ProtocolRegistry,
1063) -> bool {
1064 if do_settings_prohibit_mixed_security_contexts(request) ==
1068 MixedSecurityProhibited::NotProhibited
1069 {
1070 return false;
1071 }
1072
1073 if response
1075 .actual_response()
1076 .url()
1077 .is_some_and(|response_url| is_url_potentially_trustworthy(protocol_registry, response_url))
1078 {
1079 return false;
1080 }
1081
1082 if request.destination == Destination::Document {
1087 return false;
1089 }
1090
1091 true
1092}
1093
1094fn is_bad_port(port: u16) -> bool {
1096 static BAD_PORTS: [u16; 78] = [
1097 1, 7, 9, 11, 13, 15, 17, 19, 20, 21, 22, 23, 25, 37, 42, 43, 53, 69, 77, 79, 87, 95, 101,
1098 102, 103, 104, 109, 110, 111, 113, 115, 117, 119, 123, 135, 137, 139, 143, 161, 179, 389,
1099 427, 465, 512, 513, 514, 515, 526, 530, 531, 532, 540, 548, 554, 556, 563, 587, 601, 636,
1100 993, 995, 1719, 1720, 1723, 2049, 3659, 4045, 5060, 5061, 6000, 6566, 6665, 6666, 6667,
1101 6668, 6669, 6697, 10080,
1102 ];
1103
1104 BAD_PORTS.binary_search(&port).is_ok()
1105}
1106
1107pub fn is_form_submission_request(request: &Request) -> bool {
1109 let content_type = request.headers.typed_get::<ContentType>();
1110 content_type.is_some_and(|ct| {
1111 let mime: Mime = ct.into();
1112 mime.type_() == mime::APPLICATION && mime.subtype() == mime::WWW_FORM_URLENCODED
1113 })
1114}
1115
1116fn should_upgrade_request_to_potentially_trustworty(
1118 request: &mut Request,
1119 context: &FetchContext,
1120) -> bool {
1121 fn should_upgrade_navigation_request(request: &Request) -> bool {
1122 if is_form_submission_request(request) {
1124 return true;
1125 }
1126
1127 false
1135 }
1136
1137 if request.is_navigation_request() {
1139 if !is_url_potentially_trustworthy(&context.protocols, &request.current_url()) ||
1144 !request.current_url().host_str().is_some_and(|host| {
1145 !context.state.hsts_list.read().unwrap().is_host_secure(host)
1146 })
1147 {
1148 debug!("Appending the Upgrade-Insecure-Requests header to request’s header list");
1149 request
1150 .headers
1151 .insert("Upgrade-Insecure-Requests", HeaderValue::from_static("1"));
1152 }
1153
1154 if !should_upgrade_navigation_request(request) {
1155 return false;
1156 }
1157 }
1158
1159 request.insecure_requests_policy == InsecureRequestsPolicy::Upgrade
1161}
1162
1163#[derive(Debug, PartialEq)]
1164pub enum MixedSecurityProhibited {
1165 Prohibited,
1166 NotProhibited,
1167}
1168
1169fn do_settings_prohibit_mixed_security_contexts(request: &Request) -> MixedSecurityProhibited {
1171 if let Origin::Origin(ref origin) = request.origin {
1172 let is_origin_data_url_worker = matches!(
1174 *origin,
1175 ImmutableOrigin::Opaque(servo_url::OpaqueOrigin::SecureWorkerFromDataUrl(_))
1176 );
1177
1178 if origin.is_potentially_trustworthy() || is_origin_data_url_worker {
1181 return MixedSecurityProhibited::Prohibited;
1182 }
1183 }
1184
1185 if request.has_trustworthy_ancestor_origin {
1189 return MixedSecurityProhibited::Prohibited;
1190 }
1191
1192 MixedSecurityProhibited::NotProhibited
1193}
1194
1195fn should_upgrade_mixed_content_request(
1197 request: &Request,
1198 protocol_registry: &ProtocolRegistry,
1199) -> bool {
1200 let url = request.url();
1201 if is_url_potentially_trustworthy(protocol_registry, &url) {
1203 return false;
1204 }
1205
1206 match url.host() {
1208 Some(Host::Ipv4(_)) | Some(Host::Ipv6(_)) => return false,
1209 _ => (),
1210 }
1211
1212 if do_settings_prohibit_mixed_security_contexts(request) ==
1214 MixedSecurityProhibited::NotProhibited
1215 {
1216 return false;
1217 }
1218
1219 if !matches!(
1221 request.destination,
1222 Destination::Audio | Destination::Image | Destination::Video
1223 ) {
1224 return false;
1225 }
1226
1227 if request.destination == Destination::Image && request.initiator == Initiator::ImageSet {
1229 return false;
1230 }
1231
1232 true
1233}