1use chromiumoxide_cdp::cdp::browser_protocol::fetch::{
2 self, AuthChallengeResponse, AuthChallengeResponseResponse, ContinueRequestParams,
3 ContinueWithAuthParams, DisableParams, EventAuthRequired, EventRequestPaused, RequestPattern,
4};
5#[allow(deprecated)]
6use chromiumoxide_cdp::cdp::browser_protocol::network::{
7 EmulateNetworkConditionsParams, EventLoadingFailed, EventLoadingFinished,
8 EventRequestServedFromCache, EventRequestWillBeSent, EventResponseReceived, Headers,
9 InterceptionId, RequestId, Response, SetCacheDisabledParams, SetExtraHttpHeadersParams,
10};
11use chromiumoxide_cdp::cdp::browser_protocol::{
12 network::EnableParams, security::SetIgnoreCertificateErrorsParams,
13};
14use chromiumoxide_types::{Command, Method, MethodId};
15
16use crate::auth::Credentials;
17use crate::cmd::CommandChain;
18use crate::handler::http::HttpRequest;
19use std::collections::{HashMap, HashSet, VecDeque};
20use std::time::Duration;
21
22#[derive(Debug)]
23pub struct NetworkManager {
24 queued_events: VecDeque<NetworkEvent>,
25 ignore_httpserrors: bool,
26 requests: HashMap<RequestId, HttpRequest>,
27 requests_will_be_sent: HashMap<RequestId, EventRequestWillBeSent>,
29 extra_headers: HashMap<String, String>,
30 request_id_to_interception_id: HashMap<RequestId, InterceptionId>,
31 user_cache_disabled: bool,
32 attempted_authentications: HashSet<RequestId>,
33 credentials: Option<Credentials>,
34 user_request_interception_enabled: bool,
35 protocol_request_interception_enabled: bool,
36 offline: bool,
37 request_timeout: Duration,
38}
39
40impl NetworkManager {
41 pub fn new(ignore_httpserrors: bool, request_timeout: Duration) -> Self {
42 Self {
43 queued_events: Default::default(),
44 ignore_httpserrors,
45 requests: Default::default(),
46 requests_will_be_sent: Default::default(),
47 extra_headers: Default::default(),
48 request_id_to_interception_id: Default::default(),
49 user_cache_disabled: false,
50 attempted_authentications: Default::default(),
51 credentials: None,
52 user_request_interception_enabled: false,
53 protocol_request_interception_enabled: false,
54 offline: false,
55 request_timeout,
56 }
57 }
58
59 pub fn init_commands(&self) -> CommandChain {
60 let enable = EnableParams::default();
61 let cmds = if self.ignore_httpserrors {
62 let ignore = SetIgnoreCertificateErrorsParams::new(true);
63 vec![
64 (enable.identifier(), serde_json::to_value(enable).unwrap()),
65 (ignore.identifier(), serde_json::to_value(ignore).unwrap()),
66 ]
67 } else {
68 vec![(enable.identifier(), serde_json::to_value(enable).unwrap())]
69 };
70 CommandChain::new(cmds, self.request_timeout)
71 }
72
73 fn push_cdp_request<T: Command>(&mut self, cmd: T) {
74 let method = cmd.identifier();
75 let params = serde_json::to_value(cmd).expect("Command should not panic");
76 self.queued_events
77 .push_back(NetworkEvent::SendCdpRequest((method, params)));
78 }
79
80 pub fn poll(&mut self) -> Option<NetworkEvent> {
82 self.queued_events.pop_front()
83 }
84
85 pub fn extra_headers(&self) -> &HashMap<String, String> {
86 &self.extra_headers
87 }
88
89 pub fn set_extra_headers(&mut self, headers: HashMap<String, String>) {
90 self.extra_headers = headers;
91 let headers = serde_json::to_value(self.extra_headers.clone()).unwrap();
92 self.push_cdp_request(SetExtraHttpHeadersParams::new(Headers::new(headers)));
93 }
94
95 pub fn set_request_interception(&mut self, enabled: bool) {
96 self.user_request_interception_enabled = enabled;
97 self.update_protocol_request_interception();
98 }
99
100 pub fn set_cache_enabled(&mut self, enabled: bool) {
101 self.user_cache_disabled = !enabled;
102 self.update_protocol_cache_disabled();
103 }
104
105 pub fn update_protocol_cache_disabled(&mut self) {
106 self.push_cdp_request(SetCacheDisabledParams::new(
107 self.user_cache_disabled || self.protocol_request_interception_enabled,
108 ));
109 }
110
111 pub fn authenticate(&mut self, credentials: Credentials) {
112 self.credentials = Some(credentials);
113 self.update_protocol_request_interception()
114 }
115
116 fn update_protocol_request_interception(&mut self) {
117 let enabled = self.user_request_interception_enabled || self.credentials.is_some();
118 if enabled == self.protocol_request_interception_enabled {
119 return;
120 }
121 self.update_protocol_cache_disabled();
122 self.protocol_request_interception_enabled = enabled;
123 if enabled {
124 self.push_cdp_request(
125 fetch::EnableParams::builder()
126 .handle_auth_requests(true)
127 .pattern(RequestPattern::builder().url_pattern("*").build())
128 .build(),
129 )
130 } else {
131 self.push_cdp_request(DisableParams::default())
132 }
133 }
134
135 pub fn on_fetch_request_paused(&mut self, event: &EventRequestPaused) {
136 if !self.user_request_interception_enabled && self.protocol_request_interception_enabled {
137 self.push_cdp_request(ContinueRequestParams::new(event.request_id.clone()))
138 }
139 if let Some(network_id) = event.network_id.as_ref() {
140 if let Some(request_will_be_sent) =
141 self.requests_will_be_sent.remove(network_id.as_ref())
142 {
143 self.on_request(&request_will_be_sent, Some(event.request_id.clone().into()));
144 } else {
145 self.request_id_to_interception_id
146 .insert(network_id.clone(), event.request_id.clone().into());
147 }
148 }
149 }
150
151 pub fn on_fetch_auth_required(&mut self, event: &EventAuthRequired) {
152 let response = if self
153 .attempted_authentications
154 .contains(event.request_id.as_ref())
155 {
156 AuthChallengeResponseResponse::CancelAuth
157 } else if self.credentials.is_some() {
158 self.attempted_authentications
159 .insert(event.request_id.clone().into());
160 AuthChallengeResponseResponse::ProvideCredentials
161 } else {
162 AuthChallengeResponseResponse::Default
163 };
164
165 let mut auth = AuthChallengeResponse::new(response);
166 if let Some(creds) = self.credentials.clone() {
167 auth.username = Some(creds.username);
168 auth.password = Some(creds.password);
169 }
170 self.push_cdp_request(ContinueWithAuthParams::new(event.request_id.clone(), auth));
171 }
172
173 pub fn set_offline_mode(&mut self, value: bool) {
174 if self.offline == value {
175 return;
176 }
177 self.offline = value;
178 self.push_cdp_request(
179 #[allow(deprecated)]
182 EmulateNetworkConditionsParams::builder()
183 .offline(self.offline)
184 .latency(0)
185 .download_throughput(-1.)
186 .upload_throughput(-1.)
187 .build()
188 .unwrap(),
189 );
190 }
191
192 pub fn on_request_will_be_sent(&mut self, event: &EventRequestWillBeSent) {
194 if self.protocol_request_interception_enabled && !event.request.url.starts_with("data:") {
195 if let Some(interception_id) = self
196 .request_id_to_interception_id
197 .remove(event.request_id.as_ref())
198 {
199 self.on_request(event, Some(interception_id));
200 } else {
201 self.requests_will_be_sent
203 .insert(event.request_id.clone(), event.clone());
204 }
205 } else {
206 self.on_request(event, None);
207 }
208 }
209
210 pub fn on_request_served_from_cache(&mut self, event: &EventRequestServedFromCache) {
211 if let Some(request) = self.requests.get_mut(event.request_id.as_ref()) {
212 request.from_memory_cache = true;
213 }
214 }
215
216 pub fn on_response_received(&mut self, event: &EventResponseReceived) {
217 if let Some(mut request) = self.requests.remove(event.request_id.as_ref()) {
218 request.set_response(event.response.clone());
219 self.queued_events
220 .push_back(NetworkEvent::RequestFinished(request))
221 }
222 }
223
224 pub fn on_network_loading_finished(&mut self, event: &EventLoadingFinished) {
225 if let Some(request) = self.requests.remove(event.request_id.as_ref()) {
226 if let Some(interception_id) = request.interception_id.as_ref() {
227 self.attempted_authentications
228 .remove(interception_id.as_ref());
229 }
230 self.queued_events
231 .push_back(NetworkEvent::RequestFinished(request));
232 }
233 }
234
235 pub fn on_network_loading_failed(&mut self, event: &EventLoadingFailed) {
236 if let Some(mut request) = self.requests.remove(event.request_id.as_ref()) {
237 request.failure_text = Some(event.error_text.clone());
238 if let Some(interception_id) = request.interception_id.as_ref() {
239 self.attempted_authentications
240 .remove(interception_id.as_ref());
241 }
242 self.queued_events
243 .push_back(NetworkEvent::RequestFailed(request));
244 }
245 }
246
247 fn on_request(
248 &mut self,
249 event: &EventRequestWillBeSent,
250 interception_id: Option<InterceptionId>,
251 ) {
252 let mut redirect_chain = Vec::new();
253 if let Some(redirect_resp) = event.redirect_response.as_ref() {
254 if let Some(mut request) = self.requests.remove(event.request_id.as_ref()) {
255 self.handle_request_redirect(&mut request, redirect_resp.clone());
256 redirect_chain = std::mem::take(&mut request.redirect_chain);
257 redirect_chain.push(request);
258 }
259 }
260 let request = HttpRequest::new(
261 event.request_id.clone(),
262 event.frame_id.clone(),
263 interception_id,
264 self.user_request_interception_enabled,
265 redirect_chain,
266 );
267
268 self.requests.insert(event.request_id.clone(), request);
269 self.queued_events
270 .push_back(NetworkEvent::Request(event.request_id.clone()));
271 }
272
273 fn handle_request_redirect(&mut self, request: &mut HttpRequest, response: Response) {
274 request.set_response(response);
275 if let Some(interception_id) = request.interception_id.as_ref() {
276 self.attempted_authentications
277 .remove(interception_id.as_ref());
278 }
279 }
280}
281
282#[derive(Debug)]
283pub enum NetworkEvent {
284 SendCdpRequest((MethodId, serde_json::Value)),
285 Request(RequestId),
286 Response(RequestId),
287 RequestFailed(HttpRequest),
288 RequestFinished(HttpRequest),
289}