1use crate::cdp::session::CdpSession;
8use crate::cdp::CdpEvent;
9use crate::error::Result;
10use crate::network::NetworkStore;
11use crate::request::Request;
12use crate::types::Headers;
13use base64::Engine;
14use parking_lot::Mutex;
15use serde_json::{json, Value};
16use std::future::Future;
17use std::pin::Pin;
18use std::sync::atomic::{AtomicBool, Ordering};
19use std::sync::{Arc, Weak};
20use tokio::sync::broadcast;
21
22pub(crate) type RouteHandler =
24 Arc<dyn Fn(Route) -> Pin<Box<dyn Future<Output = ()> + Send>> + Send + Sync>;
25
26#[derive(Clone)]
28pub(crate) struct RouteEntry {
29 pub pattern: String,
30 pub handler: RouteHandler,
31}
32
33pub(crate) async fn route_listener(
35 mut rx: broadcast::Receiver<CdpEvent>,
36 session: Arc<CdpSession>,
37 store: Arc<NetworkStore>,
38 handlers: Arc<Mutex<Vec<RouteEntry>>>,
39) {
40 loop {
41 match rx.recv().await {
42 Ok(ev) if ev.method == "Fetch.requestPaused" => {
43 let url = ev
44 .params
45 .get("request")
46 .and_then(|r| r.get("url"))
47 .and_then(|v| v.as_str())
48 .unwrap_or("")
49 .to_string();
50 let fetch_id = ev
51 .params
52 .get("requestId")
53 .and_then(|v| v.as_str())
54 .unwrap_or("")
55 .to_string();
56 let handler = {
57 let hs = handlers.lock();
58 hs.iter()
59 .find(|e| pattern_matches(&e.pattern, &url))
60 .map(|e| Arc::clone(&e.handler))
61 };
62 match (handler, request_from_paused(&ev.params, Arc::downgrade(&store))) {
63 (Some(h), Some(req)) => {
64 let route = Route::new(req, fetch_id, Arc::clone(&session));
65 tokio::spawn(async move {
66 (h)(route).await;
67 });
68 }
69 _ => {
70 let _ = session
72 .send("Fetch.continueRequest", json!({ "requestId": fetch_id }))
73 .await;
74 }
75 }
76 }
77 Ok(_) => {}
78 Err(broadcast::error::RecvError::Closed) => break,
79 Err(broadcast::error::RecvError::Lagged(_)) => continue,
80 }
81 }
82}
83
84pub(crate) fn pattern_matches(pattern: &str, url: &str) -> bool {
86 if pattern == url {
87 return true;
88 }
89 if pattern.len() >= 2 && pattern.starts_with('*') && pattern.ends_with('*') {
90 return url.contains(&pattern[1..pattern.len() - 1]);
91 }
92 if let Some(rest) = pattern.strip_prefix('*') {
93 return url.ends_with(rest);
94 }
95 if let Some(rest) = pattern.strip_suffix('*') {
96 return url.starts_with(rest);
97 }
98 url.contains(pattern)
99}
100
101#[derive(Debug, Clone, Default)]
103#[non_exhaustive]
104pub struct RouteContinueOptions {
105 pub url: Option<String>,
106 pub method: Option<String>,
107 pub headers: Option<Headers>,
108 pub post_data: Option<String>,
109}
110
111#[derive(Debug, Clone, Default)]
113#[non_exhaustive]
114pub struct RouteFulfillOptions {
115 pub status: Option<u16>,
116 pub headers: Option<Headers>,
117 pub content_type: Option<String>,
118 pub body: Option<String>,
119}
120
121impl RouteFulfillOptions {
122 pub fn new() -> Self {
123 Self::default()
124 }
125 pub fn status(mut self, v: u16) -> Self {
126 self.status = Some(v);
127 self
128 }
129 pub fn body(mut self, v: impl Into<String>) -> Self {
130 self.body = Some(v.into());
131 self
132 }
133 pub fn content_type(mut self, v: impl Into<String>) -> Self {
134 self.content_type = Some(v.into());
135 self
136 }
137}
138
139#[derive(Clone)]
141pub struct Route {
142 inner: Arc<RouteInner>,
143}
144
145struct RouteInner {
146 request: Request,
147 fetch_request_id: String,
148 session: Arc<CdpSession>,
149 handled: AtomicBool,
150}
151
152impl Route {
153 pub(crate) fn new(
154 request: Request,
155 fetch_request_id: String,
156 session: Arc<CdpSession>,
157 ) -> Self {
158 Self {
159 inner: Arc::new(RouteInner {
160 request,
161 fetch_request_id,
162 session,
163 handled: AtomicBool::new(false),
164 }),
165 }
166 }
167
168 pub fn request(&self) -> Request {
170 self.inner.request.clone()
171 }
172
173 fn claim(&self) -> bool {
174 !self.inner.handled.swap(true, Ordering::SeqCst)
175 }
176
177 pub async fn continue_(&self, options: Option<RouteContinueOptions>) -> Result<()> {
179 if !self.claim() {
180 return Ok(());
181 }
182 let opts = options.unwrap_or_default();
183 let mut params = json!({ "requestId": self.inner.fetch_request_id });
184 if let Some(u) = opts.url {
185 params["url"] = json!(u);
186 }
187 if let Some(m) = opts.method {
188 params["method"] = json!(m);
189 }
190 if let Some(h) = opts.headers {
191 params["headers"] = json!(headers_to_array(&h));
192 }
193 if let Some(p) = opts.post_data {
194 params["postData"] = json!(base64::engine::general_purpose::STANDARD.encode(p));
195 }
196 self.inner
197 .session
198 .send("Fetch.continueRequest", params)
199 .await
200 .map(|_: Value| ())
201 }
202
203 pub async fn fallback(&self) -> Result<()> {
205 self.continue_(None).await
206 }
207
208 pub async fn fulfill(&self, options: RouteFulfillOptions) -> Result<()> {
210 if !self.claim() {
211 return Ok(());
212 }
213 let status = options.status.unwrap_or(200);
214 let mut headers = options.headers.unwrap_or_default();
215 if let Some(ct) = options.content_type {
216 headers.entry("content-type".into()).or_insert(ct);
217 }
218 let body_b64 = options
219 .body
220 .map(|b| base64::engine::general_purpose::STANDARD.encode(b))
221 .unwrap_or_default();
222 self.inner
223 .session
224 .send(
225 "Fetch.fulfillRequest",
226 json!({
227 "requestId": self.inner.fetch_request_id,
228 "responseCode": status,
229 "responseHeaders": headers_to_array(&headers),
230 "body": body_b64,
231 }),
232 )
233 .await
234 .map(|_: Value| ())
235 }
236
237 pub async fn abort(&self, error_code: Option<&str>) -> Result<()> {
239 if !self.claim() {
240 return Ok(());
241 }
242 let reason = error_code.unwrap_or("Failed");
243 self.inner
244 .session
245 .send(
246 "Fetch.failRequest",
247 json!({ "requestId": self.inner.fetch_request_id, "errorReason": reason }),
248 )
249 .await
250 .map(|_: Value| ())
251 }
252}
253
254pub(crate) fn request_from_paused(params: &Value, store: Weak<NetworkStore>) -> Option<Request> {
256 let req = params.get("request")?;
257 let request_id = params.get("requestId").and_then(|v| v.as_str())?.to_string();
258 let url = req.get("url").and_then(|v| v.as_str())?.to_string();
259 let method = req
260 .get("method")
261 .and_then(|v| v.as_str())
262 .unwrap_or("GET")
263 .to_string();
264 let headers = headers_from_object(req.get("headers"));
265 let resource_type = params
266 .get("resourceType")
267 .and_then(|v| v.as_str())
268 .unwrap_or("Other")
269 .to_string();
270 Some(Request::new(
271 url,
272 method,
273 headers,
274 resource_type.clone(),
275 request_id,
276 resource_type == "Document",
277 store,
278 ))
279}
280
281fn headers_to_array(h: &Headers) -> Vec<Value> {
282 h.iter().map(|(k, v)| json!({ "name": k, "value": v })).collect()
283}
284
285fn headers_from_object(v: Option<&Value>) -> Headers {
286 let mut out = Headers::new();
287 if let Some(obj) = v.and_then(|v| v.as_object()) {
288 for (k, val) in obj {
289 if let Some(s) = val.as_str() {
290 out.insert(k.to_lowercase(), s.to_string());
291 }
292 }
293 }
294 out
295}