Skip to main content

chaser_oxide/handler/
network.rs

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    // TODO put event in an Arc?
28    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    /// The next event to handle
81    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            // This event was recently deprecated, so we continue to use it for now
180            // if some users are on older versions of chromium.
181            #[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    /// Request interception doesn't happen for data URLs with Network Service.
193    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                // TODO remove the clone for event
202                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}