1use super::blockers::{
2 block_websites::block_xhr,
3 ignore_script_embedded, ignore_script_xhr, ignore_script_xhr_media,
4 intercept_manager::NetworkInterceptManager,
5 scripts::{
6 URL_IGNORE_SCRIPT_BASE_PATHS, URL_IGNORE_SCRIPT_STYLES_PATHS, URL_IGNORE_TRIE,
7 URL_IGNORE_TRIE_PATHS,
8 },
9 xhr::IGNORE_XHR_ASSETS,
10};
11use crate::auth::Credentials;
12use crate::cmd::CommandChain;
13use crate::handler::http::HttpRequest;
14use aho_corasick::AhoCorasick;
15use case_insensitive_string::CaseInsensitiveString;
16use chromiumoxide_cdp::cdp::browser_protocol::network::{
17 EmulateNetworkConditionsParams, EventLoadingFailed, EventLoadingFinished,
18 EventRequestServedFromCache, EventRequestWillBeSent, EventResponseReceived, Headers,
19 InterceptionId, RequestId, ResourceType, Response, SetCacheDisabledParams,
20 SetExtraHttpHeadersParams,
21};
22use chromiumoxide_cdp::cdp::browser_protocol::{
23 fetch::{
24 self, AuthChallengeResponse, AuthChallengeResponseResponse, ContinueRequestParams,
25 ContinueWithAuthParams, DisableParams, EventAuthRequired, EventRequestPaused,
26 RequestPattern,
27 },
28 network::SetBypassServiceWorkerParams,
29};
30use chromiumoxide_cdp::cdp::browser_protocol::{
31 network::EnableParams, security::SetIgnoreCertificateErrorsParams,
32};
33use chromiumoxide_types::{Command, Method, MethodId};
34use hashbrown::{HashMap, HashSet};
35use lazy_static::lazy_static;
36use reqwest::header::PROXY_AUTHORIZATION;
37use std::collections::VecDeque;
38use std::time::Duration;
39
40lazy_static! {
41 static ref JS_FRAMEWORK_ALLOW: Vec<&'static str> = vec![
43 "jquery", "angular",
45 "react", "vue", "bootstrap",
48 "d3",
49 "lodash",
50 "ajax",
51 "application",
52 "app", "main",
54 "index",
55 "bundle",
56 "vendor",
57 "runtime",
58 "polyfill",
59 "scripts",
60 "es2015.",
61 "es2020.",
62 "webpack",
63 "/wp-content/js/", "https://m.stripe.network/",
66 "https://challenges.cloudflare.com/",
67 "https://www.google.com/recaptcha/api.js",
68 "https://google.com/recaptcha/api.js",
69 "https://js.stripe.com/",
70 "https://cdn.prod.website-files.com/", "https://cdnjs.cloudflare.com/", "https://code.jquery.com/jquery-"
73 ];
74
75 pub static ref ALLOWED_MATCHER: AhoCorasick = AhoCorasick::new(JS_FRAMEWORK_ALLOW.iter()).unwrap();
77
78 pub static ref JS_FRAMEWORK_PATH: phf::Set<&'static str> = {
80 phf::phf_set! {
81 "_astro/", "_app/immutable"
83 }
84 };
85
86 pub static ref IGNORE_CONTENT_TYPES: phf::Set<&'static str> = phf::phf_set! {
88 "application/pdf",
89 "application/zip",
90 "application/x-rar-compressed",
91 "application/x-tar",
92 "image/png",
93 "image/jpeg",
94 "image/gif",
95 "image/bmp",
96 "image/svg+xml",
97 "video/mp4",
98 "video/x-msvideo",
99 "video/x-matroska",
100 "video/webm",
101 "audio/mpeg",
102 "audio/ogg",
103 "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
104 "application/vnd.ms-excel",
105 "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
106 "application/vnd.ms-powerpoint",
107 "application/vnd.openxmlformats-officedocument.presentationml.presentation",
108 "application/x-7z-compressed",
109 "application/x-rpm",
110 "application/x-shockwave-flash",
111 "application/rtf",
112 };
113
114 pub static ref IGNORE_VISUAL_RESOURCE_MAP: phf::Set<&'static str> = phf::phf_set! {
116 "Image",
117 "Media",
118 "Font"
119 };
120
121 pub static ref IGNORE_NETWORKING_RESOURCE_MAP: phf::Set<&'static str> = phf::phf_set! {
123 "CspViolationReport",
124 "Manifest",
125 "Other",
126 "Prefetch",
127 "Ping",
128 };
129
130 pub static ref CSS_EXTENSION: CaseInsensitiveString = CaseInsensitiveString::from("css");
132
133 pub static ref INIT_CHAIN: Vec<(std::borrow::Cow<'static, str>, serde_json::Value)> = {
135 let enable = EnableParams::default();
136
137 if let Ok(c) = serde_json::to_value(&enable) {
138 vec![(enable.identifier(), c)]
139 } else {
140 vec![]
141 }
142 };
143
144 pub static ref INIT_CHAIN_IGNORE_HTTP_ERRORS: Vec<(std::borrow::Cow<'static, str>, serde_json::Value)> = {
146 let enable = EnableParams::default();
147 let mut v = vec![];
148 if let Ok(c) = serde_json::to_value(&enable) {
149 v.push((enable.identifier(), c));
150 }
151 let ignore = SetIgnoreCertificateErrorsParams::new(true);
152 if let Ok(ignored) = serde_json::to_value(&ignore) {
153 v.push((ignore.identifier(), ignored));
154 }
155
156 v
157 };
158
159 pub static ref ENABLE_FETCH: chromiumoxide_cdp::cdp::browser_protocol::fetch::EnableParams = {
161 fetch::EnableParams::builder()
162 .handle_auth_requests(true)
163 .pattern(RequestPattern::builder().url_pattern("*").build())
164 .build()
165 };
166}
167
168pub(crate) fn is_redirect_status(status: i64) -> bool {
170 matches!(status, 301 | 302 | 303 | 307 | 308)
171}
172
173#[derive(Debug)]
174pub struct NetworkManager {
176 queued_events: VecDeque<NetworkEvent>,
177 ignore_httpserrors: bool,
178 requests: HashMap<RequestId, HttpRequest>,
179 requests_will_be_sent: HashMap<RequestId, EventRequestWillBeSent>,
181 extra_headers: std::collections::HashMap<String, String>,
182 request_id_to_interception_id: HashMap<RequestId, InterceptionId>,
183 user_cache_disabled: bool,
184 attempted_authentications: HashSet<RequestId>,
185 credentials: Option<Credentials>,
186 user_request_interception_enabled: bool,
188 protocol_request_interception_enabled: bool,
189 offline: bool,
190 request_timeout: Duration,
191 pub ignore_visuals: bool,
194 pub block_stylesheets: bool,
196 pub block_javascript: bool,
198 pub block_analytics: bool,
200 pub only_html: bool,
202 pub xml_document: bool,
204 pub intercept_manager: NetworkInterceptManager,
206 pub document_reload_tracker: u8,
208 pub document_target_domain: String,
210}
211
212impl NetworkManager {
213 pub fn new(ignore_httpserrors: bool, request_timeout: Duration) -> Self {
214 Self {
215 queued_events: Default::default(),
216 ignore_httpserrors,
217 requests: Default::default(),
218 requests_will_be_sent: Default::default(),
219 extra_headers: Default::default(),
220 request_id_to_interception_id: Default::default(),
221 user_cache_disabled: false,
222 attempted_authentications: Default::default(),
223 credentials: None,
224 user_request_interception_enabled: false,
225 protocol_request_interception_enabled: false,
226 offline: false,
227 request_timeout,
228 ignore_visuals: false,
229 block_javascript: false,
230 block_stylesheets: false,
231 block_analytics: true,
232 only_html: false,
233 xml_document: false,
234 intercept_manager: NetworkInterceptManager::Unknown,
235 document_reload_tracker: 0,
236 document_target_domain: String::new(),
237 }
238 }
239
240 pub fn init_commands(&self) -> CommandChain {
241 let cmds = if self.ignore_httpserrors {
242 INIT_CHAIN_IGNORE_HTTP_ERRORS.clone()
243 } else {
244 INIT_CHAIN.clone()
245 };
246
247 CommandChain::new(cmds, self.request_timeout)
248 }
249
250 pub(crate) fn push_cdp_request<T: Command>(&mut self, cmd: T) {
251 let method = cmd.identifier();
252 if let Ok(params) = serde_json::to_value(cmd) {
253 self.queued_events
254 .push_back(NetworkEvent::SendCdpRequest((method, params)));
255 }
256 }
257
258 pub fn poll(&mut self) -> Option<NetworkEvent> {
260 self.queued_events.pop_front()
261 }
262
263 pub fn extra_headers(&self) -> &std::collections::HashMap<String, String> {
264 &self.extra_headers
265 }
266
267 pub fn set_extra_headers(&mut self, headers: std::collections::HashMap<String, String>) {
268 self.extra_headers = headers;
269 self.extra_headers.remove(PROXY_AUTHORIZATION.as_str());
270 if let Ok(headers) = serde_json::to_value(&self.extra_headers) {
271 self.push_cdp_request(SetExtraHttpHeadersParams::new(Headers::new(headers)));
272 }
273 }
274
275 pub fn set_service_worker_enabled(&mut self, bypass: bool) {
276 self.push_cdp_request(SetBypassServiceWorkerParams::new(bypass));
277 }
278
279 pub fn set_request_interception(&mut self, enabled: bool) {
280 self.user_request_interception_enabled = enabled;
281 self.update_protocol_request_interception();
282 }
283
284 pub fn set_cache_enabled(&mut self, enabled: bool) {
285 let run = self.user_cache_disabled != !enabled;
286 self.user_cache_disabled = !enabled;
287 if run {
288 self.update_protocol_cache_disabled();
289 }
290 }
291
292 pub fn disable_request_intercept(&mut self) {
293 self.protocol_request_interception_enabled = true;
294 }
295
296 pub fn update_protocol_cache_disabled(&mut self) {
297 self.push_cdp_request(SetCacheDisabledParams::new(self.user_cache_disabled));
298 }
299
300 pub fn authenticate(&mut self, credentials: Credentials) {
301 self.credentials = Some(credentials);
302 self.update_protocol_request_interception();
303 self.protocol_request_interception_enabled = true;
304 }
305
306 fn update_protocol_request_interception(&mut self) {
307 let enabled = self.user_request_interception_enabled || self.credentials.is_some();
308
309 if enabled == self.protocol_request_interception_enabled {
310 return;
311 }
312
313 if enabled {
314 self.push_cdp_request(ENABLE_FETCH.clone())
315 } else {
316 self.push_cdp_request(DisableParams::default())
317 }
318 }
319
320 pub(crate) fn ignore_script(
322 &self,
323 url: &str,
324 block_analytics: bool,
325 intercept_manager: NetworkInterceptManager,
326 ) -> bool {
327 let mut ignore_script = block_analytics && URL_IGNORE_TRIE.contains_prefix(url);
328
329 if !ignore_script {
330 if let Some(index) = url.find("//") {
331 let pos = index + 2;
332
333 if pos < url.len() {
335 if let Some(slash_index) = url[pos..].find('/') {
337 let base_path_index = pos + slash_index + 1;
338
339 if url.len() > base_path_index {
340 let new_url: &str = &url[base_path_index..];
341
342 if !ignore_script
344 && intercept_manager == NetworkInterceptManager::Unknown
345 {
346 let hydration_file =
347 JS_FRAMEWORK_PATH.iter().any(|p| new_url.starts_with(p));
348
349 if hydration_file && new_url.ends_with(".js") {
351 ignore_script = true;
352 }
353 }
354
355 if !ignore_script
356 && URL_IGNORE_SCRIPT_BASE_PATHS.contains_prefix(new_url)
357 {
358 ignore_script = true;
359 }
360
361 if !ignore_script
362 && self.ignore_visuals
363 && URL_IGNORE_SCRIPT_STYLES_PATHS.contains_prefix(new_url)
364 {
365 ignore_script = true;
366 }
367 }
368 }
369 }
370 }
371 }
372
373 if !ignore_script && block_analytics {
375 ignore_script = URL_IGNORE_TRIE_PATHS.contains_prefix(url);
376 }
377
378 ignore_script
379 }
380
381 fn skip_xhr(
383 &self,
384 skip_networking: bool,
385 event: &EventRequestPaused,
386 network_event: bool,
387 ) -> bool {
388 if !skip_networking && network_event {
390 let request_url = event.request.url.as_str();
391
392 let skip_analytics =
394 self.block_analytics && (ignore_script_xhr(request_url) || block_xhr(request_url));
395
396 if skip_analytics {
397 true
398 } else if self.block_stylesheets || self.ignore_visuals {
399 let block_css = self.block_stylesheets;
400 let block_media = self.ignore_visuals;
401
402 let mut block_request = false;
403
404 if let Some(position) = request_url.rfind('.') {
405 let hlen = request_url.len();
406 let has_asset = hlen - position;
407
408 if has_asset >= 3 {
409 let next_position = position + 1;
410
411 if block_media
412 && IGNORE_XHR_ASSETS.contains::<CaseInsensitiveString>(
413 &request_url[next_position..].into(),
414 )
415 {
416 block_request = true;
417 } else if block_css {
418 block_request =
419 CaseInsensitiveString::from(request_url[next_position..].as_bytes())
420 .contains(&**CSS_EXTENSION)
421 }
422 }
423 }
424
425 if !block_request {
426 block_request = ignore_script_xhr_media(request_url);
427 }
428
429 block_request
430 } else {
431 skip_networking
432 }
433 } else {
434 skip_networking
435 }
436 }
437
438 #[cfg(not(feature = "adblock"))]
439 pub fn on_fetch_request_paused(&mut self, event: &EventRequestPaused) {
440 use super::blockers::block_websites::block_website;
441
442 if self.user_request_interception_enabled && self.protocol_request_interception_enabled {
443 return;
444 }
445
446 if !self.user_request_interception_enabled && self.protocol_request_interception_enabled {
447 self.push_cdp_request(ContinueRequestParams::new(event.request_id.clone()))
448 } else {
449 if let Some(network_id) = event.network_id.as_ref() {
450 if let Some(request_will_be_sent) =
451 self.requests_will_be_sent.remove(network_id.as_ref())
452 {
453 self.on_request(&request_will_be_sent, Some(event.request_id.clone().into()));
454 } else {
455 let current_url = event.request.url.as_str();
456 let javascript_resource = event.resource_type == ResourceType::Script;
457 let document_resource = event.resource_type == ResourceType::Document;
458 let network_resource = !document_resource
459 && (event.resource_type == ResourceType::Xhr
460 || event.resource_type == ResourceType::Fetch
461 || event.resource_type == ResourceType::WebSocket);
462
463 let skip_networking =
464 IGNORE_NETWORKING_RESOURCE_MAP.contains(event.resource_type.as_ref());
465
466 let skip_networking = skip_networking || self.document_reload_tracker >= 3;
467 let mut replacer = None;
468
469 if document_resource {
470 if self.document_target_domain == current_url {
471 self.document_reload_tracker += 1;
473 } else if !self.document_target_domain.is_empty()
474 && event.redirected_request_id.is_some()
475 {
476 let (http_document_replacement, mut https_document_replacement) =
477 if self.document_target_domain.starts_with("http://") {
478 (
479 self.document_target_domain.replace("http://", "http//"),
480 self.document_target_domain.replace("http://", "https://"),
481 )
482 } else {
483 (
484 self.document_target_domain.replace("https://", "https//"),
485 self.document_target_domain.replace("https://", "http://"),
486 )
487 };
488
489 let trailing = https_document_replacement.ends_with('/');
490
491 if trailing {
492 https_document_replacement.pop();
493 }
494
495 if https_document_replacement.ends_with('/') {
496 https_document_replacement.pop();
497 }
498
499 let redirect_mask = format!(
500 "{}{}",
501 https_document_replacement, http_document_replacement
502 );
503
504 if current_url == redirect_mask {
506 replacer = Some(if trailing {
507 format!("{}/", https_document_replacement)
508 } else {
509 https_document_replacement
510 });
511 }
512 }
513
514 if self.document_target_domain.is_empty() && current_url.ends_with(".xml") {
515 self.xml_document = true;
516 }
517
518 self.document_target_domain = event.request.url.clone();
519 }
520
521 let current_url = match &replacer {
522 Some(r) => r,
523 _ => &event.request.url,
524 }
525 .as_str();
526
527 let skip_networking = if !skip_networking {
529 if self.xml_document && current_url.ends_with(".xsl") {
531 false
532 } else {
533 self.ignore_visuals
534 && (IGNORE_VISUAL_RESOURCE_MAP
535 .contains(event.resource_type.as_ref()))
536 || self.block_stylesheets
537 && ResourceType::Stylesheet == event.resource_type
538 || self.block_javascript
539 && javascript_resource
540 && self.intercept_manager == NetworkInterceptManager::Unknown
541 && !ALLOWED_MATCHER.is_match(current_url)
542 }
543 } else {
544 skip_networking
545 };
546
547 let skip_networking = if !skip_networking
548 && (self.only_html || self.ignore_visuals)
549 && (javascript_resource || document_resource)
550 {
551 ignore_script_embedded(current_url)
552 } else {
553 skip_networking
554 };
555
556 let skip_networking = if !skip_networking && javascript_resource {
558 self.ignore_script(
559 current_url,
560 self.block_analytics,
561 self.intercept_manager,
562 )
563 } else {
564 skip_networking
565 };
566
567 let skip_networking = self.skip_xhr(skip_networking, &event, network_resource);
569
570 let skip_networking = if !skip_networking
572 && (javascript_resource || network_resource || document_resource)
573 {
574 self.intercept_manager.intercept_detection(
575 ¤t_url,
576 self.ignore_visuals,
577 network_resource,
578 )
579 } else {
580 skip_networking
581 };
582
583 let skip_networking =
584 if !skip_networking && (javascript_resource || network_resource) {
585 block_website(¤t_url)
586 } else {
587 skip_networking
588 };
589
590 if skip_networking {
591 tracing::debug!("Blocked: {:?} - {}", event.resource_type, current_url);
592 let fullfill_params =
593 crate::handler::network::fetch::FulfillRequestParams::new(
594 event.request_id.clone(),
595 200,
596 );
597 self.push_cdp_request(fullfill_params);
598 } else {
599 tracing::debug!("Allowed: {:?} - {}", event.resource_type, current_url);
600 let mut continue_params =
601 ContinueRequestParams::new(event.request_id.clone());
602
603 if replacer.is_some() {
604 continue_params.url = Some(current_url.into());
605 continue_params.intercept_response = Some(false);
606 }
607
608 self.push_cdp_request(continue_params)
609 }
610 }
611 } else {
612 self.push_cdp_request(ContinueRequestParams::new(event.request_id.clone()))
613 }
614 }
615 }
616
617 #[cfg(feature = "adblock")]
618 pub fn on_fetch_request_paused(&mut self, event: &EventRequestPaused) {
619 if self.user_request_interception_enabled && self.protocol_request_interception_enabled {
620 return;
621 }
622
623 if !self.user_request_interception_enabled && self.protocol_request_interception_enabled {
624 self.push_cdp_request(ContinueRequestParams::new(event.request_id.clone()))
625 } else {
626 if let Some(network_id) = event.network_id.as_ref() {
627 if let Some(request_will_be_sent) =
628 self.requests_will_be_sent.remove(network_id.as_ref())
629 {
630 self.on_request(&request_will_be_sent, Some(event.request_id.clone().into()));
631 } else {
632 let current_url = event.request.url.as_str();
633 let javascript_resource = event.resource_type == ResourceType::Script;
634 let document_resource = event.resource_type == ResourceType::Document;
635 let network_resource = !document_resource
636 && (event.resource_type == ResourceType::Xhr
637 || event.resource_type == ResourceType::Fetch
638 || event.resource_type == ResourceType::WebSocket);
639 let mut replacer = None;
640
641 let skip_networking =
643 IGNORE_NETWORKING_RESOURCE_MAP.contains(event.resource_type.as_ref());
644
645 let skip_networking = skip_networking || self.document_reload_tracker >= 3;
646
647 if document_resource {
648 if self.document_target_domain == current_url {
649 self.document_reload_tracker += 1;
651 } else if !self.document_target_domain.is_empty()
652 && event.redirected_request_id.is_some()
653 {
654 let (http_document_replacement, mut https_document_replacement) =
655 if self.document_target_domain.starts_with("http://") {
656 (
657 self.document_target_domain.replace("http://", "http//"),
658 self.document_target_domain.replace("http://", "https://"),
659 )
660 } else {
661 (
662 self.document_target_domain.replace("https://", "https//"),
663 self.document_target_domain.replace("https://", "http://"),
664 )
665 };
666
667 let trailing = https_document_replacement.ends_with('/');
668
669 if trailing {
670 https_document_replacement.pop();
671 }
672
673 if https_document_replacement.ends_with('/') {
674 https_document_replacement.pop();
675 }
676
677 let redirect_mask = format!(
678 "{}{}",
679 https_document_replacement, http_document_replacement
680 );
681
682 if current_url == redirect_mask {
684 replacer = Some(if trailing {
685 format!("{}/", https_document_replacement)
686 } else {
687 https_document_replacement
688 });
689 }
690 }
691
692 if self.document_target_domain.is_empty() && current_url.ends_with(".xml") {
693 self.xml_document = true;
694 }
695
696 self.document_target_domain = event.request.url.clone();
697 }
698
699 let current_url = match &replacer {
700 Some(r) => r,
701 _ => &event.request.url,
702 }
703 .as_str();
704
705 let skip_networking = if !skip_networking {
707 if self.xml_document && current_url.ends_with(".xsl") {
709 false
710 } else {
711 self.ignore_visuals
712 && (IGNORE_VISUAL_RESOURCE_MAP
713 .contains(event.resource_type.as_ref()))
714 || self.block_stylesheets
715 && ResourceType::Stylesheet == event.resource_type
716 || self.block_javascript
717 && javascript_resource
718 && self.intercept_manager == NetworkInterceptManager::Unknown
719 && !ALLOWED_MATCHER.is_match(current_url)
720 }
721 } else {
722 skip_networking
723 };
724
725 let skip_networking = if !skip_networking {
726 self.detect_ad(event)
727 } else {
728 skip_networking
729 };
730
731 let skip_networking = if !skip_networking
732 && (self.only_html || self.ignore_visuals)
733 && (javascript_resource || document_resource)
734 {
735 ignore_script_embedded(current_url)
736 } else {
737 skip_networking
738 };
739
740 let skip_networking = if !skip_networking && javascript_resource {
742 self.ignore_script(
743 current_url,
744 self.block_analytics,
745 self.intercept_manager,
746 )
747 } else {
748 skip_networking
749 };
750
751 let skip_networking = self.skip_xhr(skip_networking, &event, network_resource);
753
754 let skip_networking = if !skip_networking
756 && (javascript_resource || network_resource || document_resource)
757 {
758 self.intercept_manager.intercept_detection(
759 &event.request.url,
760 self.ignore_visuals,
761 network_resource,
762 )
763 } else {
764 skip_networking
765 };
766
767 let skip_networking = if !skip_networking
768 && (javascript_resource || network_resource)
769 {
770 crate::handler::blockers::block_websites::block_website(&event.request.url)
771 } else {
772 skip_networking
773 };
774
775 if skip_networking {
776 tracing::debug!("Blocked: {:?} - {}", event.resource_type, current_url);
777
778 let fullfill_params =
779 crate::handler::network::fetch::FulfillRequestParams::new(
780 event.request_id.clone(),
781 200,
782 );
783 self.push_cdp_request(fullfill_params);
784 } else {
785 tracing::debug!("Allowed: {:?} - {}", event.resource_type, current_url);
786
787 let mut continue_params =
788 ContinueRequestParams::new(event.request_id.clone());
789
790 if replacer.is_some() {
791 continue_params.url = Some(current_url.into());
792 continue_params.intercept_response = Some(false);
793 }
794 }
795 }
796 } else {
797 self.push_cdp_request(ContinueRequestParams::new(event.request_id.clone()))
798 }
799 }
800
801 }
805
806 #[cfg(feature = "adblock")]
808 pub fn detect_ad(&self, event: &EventRequestPaused) -> bool {
809 use adblock::{
810 lists::{FilterSet, ParseOptions, RuleTypes},
811 Engine,
812 };
813
814 lazy_static::lazy_static! {
815 static ref AD_ENGINE: Engine = {
816 let mut filter_set = FilterSet::new(false);
817 let mut rules = ParseOptions::default();
818 rules.rule_types = RuleTypes::All;
819
820 filter_set.add_filters(
821 &*crate::handler::blockers::adblock_patterns::ADBLOCK_PATTERNS,
822 rules,
823 );
824
825 Engine::from_filter_set(filter_set, true)
826 };
827 };
828
829 let blockable = ResourceType::Image == event.resource_type
830 || event.resource_type == ResourceType::Media
831 || event.resource_type == ResourceType::Stylesheet
832 || event.resource_type == ResourceType::Document
833 || event.resource_type == ResourceType::Fetch
834 || event.resource_type == ResourceType::Xhr;
835
836 let u = &event.request.url;
837
838 let block_request = blockable
839 && {
841 let request = adblock::request::Request::preparsed(
842 &u,
843 "example.com",
844 "example.com",
845 &event.resource_type.as_ref().to_lowercase(),
846 !event.request.is_same_site.unwrap_or_default());
847
848 AD_ENGINE.check_network_request(&request).matched
849 };
850
851 block_request
852 }
853
854 pub fn on_fetch_auth_required(&mut self, event: &EventAuthRequired) {
855 let response = if self
856 .attempted_authentications
857 .contains(event.request_id.as_ref())
858 {
859 AuthChallengeResponseResponse::CancelAuth
860 } else if self.credentials.is_some() {
861 self.attempted_authentications
862 .insert(event.request_id.clone().into());
863 AuthChallengeResponseResponse::ProvideCredentials
864 } else {
865 AuthChallengeResponseResponse::Default
866 };
867
868 let mut auth = AuthChallengeResponse::new(response);
869 if let Some(creds) = self.credentials.clone() {
870 auth.username = Some(creds.username);
871 auth.password = Some(creds.password);
872 }
873 self.push_cdp_request(ContinueWithAuthParams::new(event.request_id.clone(), auth));
874 }
875
876 pub fn set_offline_mode(&mut self, value: bool) {
877 if self.offline == value {
878 return;
879 }
880 self.offline = value;
881 if let Ok(network) = EmulateNetworkConditionsParams::builder()
882 .offline(self.offline)
883 .latency(0)
884 .download_throughput(-1.)
885 .upload_throughput(-1.)
886 .build()
887 {
888 self.push_cdp_request(network);
889 }
890 }
891
892 pub fn on_request_will_be_sent(&mut self, event: &EventRequestWillBeSent) {
894 if self.protocol_request_interception_enabled && !event.request.url.starts_with("data:") {
895 if let Some(interception_id) = self
896 .request_id_to_interception_id
897 .remove(event.request_id.as_ref())
898 {
899 self.on_request(event, Some(interception_id));
900 } else {
901 self.requests_will_be_sent
903 .insert(event.request_id.clone(), event.clone());
904 }
905 } else {
906 self.on_request(event, None);
907 }
908 }
909
910 pub fn on_request_served_from_cache(&mut self, event: &EventRequestServedFromCache) {
911 if let Some(request) = self.requests.get_mut(event.request_id.as_ref()) {
912 request.from_memory_cache = true;
913 }
914 }
915
916 pub fn on_response_received(&mut self, event: &EventResponseReceived) {
917 if let Some(mut request) = self.requests.remove(event.request_id.as_ref()) {
918 request.set_response(event.response.clone());
919 self.queued_events
920 .push_back(NetworkEvent::RequestFinished(request))
921 }
922 }
923
924 pub fn on_network_loading_finished(&mut self, event: &EventLoadingFinished) {
925 if let Some(request) = self.requests.remove(event.request_id.as_ref()) {
926 if let Some(interception_id) = request.interception_id.as_ref() {
927 self.attempted_authentications
928 .remove(interception_id.as_ref());
929 }
930 self.queued_events
931 .push_back(NetworkEvent::RequestFinished(request));
932 }
933 }
934
935 pub fn on_network_loading_failed(&mut self, event: &EventLoadingFailed) {
936 if let Some(mut request) = self.requests.remove(event.request_id.as_ref()) {
937 request.failure_text = Some(event.error_text.clone());
938 if let Some(interception_id) = request.interception_id.as_ref() {
939 self.attempted_authentications
940 .remove(interception_id.as_ref());
941 }
942 self.queued_events
943 .push_back(NetworkEvent::RequestFailed(request));
944 }
945 }
946
947 fn on_request(
948 &mut self,
949 event: &EventRequestWillBeSent,
950 interception_id: Option<InterceptionId>,
951 ) {
952 let mut redirect_chain = Vec::new();
953 let mut redirect_location = None;
954
955 if let Some(redirect_resp) = &event.redirect_response {
956 if let Some(mut request) = self.requests.remove(event.request_id.as_ref()) {
957 if is_redirect_status(redirect_resp.status) {
958 if let Some(location) = redirect_resp.headers.inner()["Location"].as_str() {
959 if redirect_resp.url != location {
960 let fixed_location = location.replace(&redirect_resp.url, "");
961
962 request.response.as_mut().map(|resp| {
963 resp.headers.0["Location"] =
964 serde_json::Value::String(fixed_location.clone());
965 });
966
967 redirect_location = Some(fixed_location);
968 }
969 }
970 }
971
972 self.handle_request_redirect(
973 &mut request,
974 if let Some(redirect_location) = redirect_location {
975 let mut redirect_resp = redirect_resp.clone();
976
977 redirect_resp.headers.0["Location"] =
978 serde_json::Value::String(redirect_location);
979
980 redirect_resp
981 } else {
982 redirect_resp.clone()
983 },
984 );
985
986 redirect_chain = std::mem::take(&mut request.redirect_chain);
987 redirect_chain.push(request);
988 }
989 }
990
991 let request = HttpRequest::new(
992 event.request_id.clone(),
993 event.frame_id.clone(),
994 interception_id,
995 self.user_request_interception_enabled,
996 redirect_chain,
997 );
998
999 self.requests.insert(event.request_id.clone(), request);
1000 self.queued_events
1001 .push_back(NetworkEvent::Request(event.request_id.clone()));
1002 }
1003
1004 fn handle_request_redirect(&mut self, request: &mut HttpRequest, response: Response) {
1005 request.set_response(response);
1006 if let Some(interception_id) = request.interception_id.as_ref() {
1007 self.attempted_authentications
1008 .remove(interception_id.as_ref());
1009 }
1010 }
1011}
1012
1013#[derive(Debug)]
1014pub enum NetworkEvent {
1015 SendCdpRequest((MethodId, serde_json::Value)),
1016 Request(RequestId),
1017 Response(RequestId),
1018 RequestFailed(HttpRequest),
1019 RequestFinished(HttpRequest),
1020}