firefox_webdriver/browser/network.rs
1//! Network interception types.
2//!
3//! Types for request/response interception callbacks.
4//!
5//! # Request Interception
6//!
7//! ```ignore
8//! use firefox_webdriver::RequestAction;
9//!
10//! let intercept_id = tab.intercept_request(|req| {
11//! if req.url.contains("ads") {
12//! RequestAction::block()
13//! } else {
14//! RequestAction::allow()
15//! }
16//! }).await?;
17//! ```
18//!
19//! # Response Interception
20//!
21//! ```ignore
22//! use firefox_webdriver::BodyAction;
23//!
24//! let intercept_id = tab.intercept_response_body(|res| {
25//! if res.url.contains("config.json") {
26//! BodyAction::modify_body(r#"{"modified": true}"#)
27//! } else {
28//! BodyAction::allow()
29//! }
30//! }).await?;
31//! ```
32
33// ============================================================================
34// Imports
35// ============================================================================
36
37use std::collections::HashMap;
38
39// ============================================================================
40// InterceptedRequest
41// ============================================================================
42
43/// Data about an intercepted network request.
44#[derive(Debug, Clone)]
45pub struct InterceptedRequest {
46 /// Unique request ID.
47 pub request_id: String,
48
49 /// Request URL.
50 pub url: String,
51
52 /// HTTP method (GET, POST, etc.).
53 pub method: String,
54
55 /// Request headers.
56 pub headers: HashMap<String, String>,
57
58 /// Resource type (document, script, xhr, etc.).
59 pub resource_type: String,
60
61 /// Tab ID where request originated.
62 pub tab_id: u32,
63
64 /// Frame ID where request originated.
65 pub frame_id: u64,
66
67 /// Request body (if available).
68 pub body: Option<RequestBody>,
69}
70
71// ============================================================================
72// RequestBody
73// ============================================================================
74
75/// Request body data.
76#[derive(Debug, Clone)]
77pub enum RequestBody {
78 /// Form data (application/x-www-form-urlencoded or multipart/form-data).
79 FormData(HashMap<String, Vec<String>>),
80
81 /// Raw bytes (base64 encoded).
82 Raw(Vec<u8>),
83
84 /// Error reading body.
85 Error(String),
86}
87
88// ============================================================================
89// RequestAction
90// ============================================================================
91
92/// Action to take for an intercepted request.
93#[derive(Debug, Clone)]
94pub enum RequestAction {
95 /// Allow the request to proceed.
96 Allow,
97
98 /// Block/cancel the request.
99 Block,
100
101 /// Redirect to a different URL.
102 Redirect(String),
103}
104
105// ============================================================================
106// RequestAction - Constructors
107// ============================================================================
108
109impl RequestAction {
110 /// Creates an Allow action.
111 #[inline]
112 #[must_use]
113 pub fn allow() -> Self {
114 Self::Allow
115 }
116
117 /// Creates a Block action.
118 #[inline]
119 #[must_use]
120 pub fn block() -> Self {
121 Self::Block
122 }
123
124 /// Creates a Redirect action.
125 #[inline]
126 #[must_use]
127 pub fn redirect(url: impl Into<String>) -> Self {
128 Self::Redirect(url.into())
129 }
130}
131
132// ============================================================================
133// InterceptedRequestBody
134// ============================================================================
135
136/// Data about an intercepted request body (read-only, cannot be modified).
137///
138/// Browser limitation: request body cannot be modified, only inspected.
139#[derive(Debug, Clone)]
140pub struct InterceptedRequestBody {
141 /// Unique request ID.
142 pub request_id: String,
143
144 /// Request URL.
145 pub url: String,
146
147 /// HTTP method.
148 pub method: String,
149
150 /// Resource type (document, script, xhr, etc.).
151 pub resource_type: String,
152
153 /// Tab ID.
154 pub tab_id: u32,
155
156 /// Frame ID.
157 pub frame_id: u64,
158
159 /// Request body (if available).
160 pub body: Option<RequestBody>,
161}
162
163// ============================================================================
164// InterceptedRequestHeaders
165// ============================================================================
166
167/// Data about intercepted request headers.
168#[derive(Debug, Clone)]
169pub struct InterceptedRequestHeaders {
170 /// Unique request ID.
171 pub request_id: String,
172
173 /// Request URL.
174 pub url: String,
175
176 /// HTTP method.
177 pub method: String,
178
179 /// Request headers.
180 pub headers: HashMap<String, String>,
181
182 /// Tab ID.
183 pub tab_id: u32,
184
185 /// Frame ID.
186 pub frame_id: u64,
187}
188
189// ============================================================================
190// HeadersAction
191// ============================================================================
192
193/// Action to take for intercepted headers.
194#[derive(Debug, Clone)]
195pub enum HeadersAction {
196 /// Allow headers to proceed unchanged.
197 Allow,
198
199 /// Modify headers with optional status code override.
200 Modify {
201 /// Headers to set/replace.
202 headers: HashMap<String, String>,
203 /// Optional HTTP status code override.
204 status_code: Option<u16>,
205 },
206
207 /// Block/cancel the request.
208 Block,
209}
210
211// ============================================================================
212// HeadersAction - Constructors
213// ============================================================================
214
215impl HeadersAction {
216 /// Creates an Allow action.
217 #[inline]
218 #[must_use]
219 pub fn allow() -> Self {
220 Self::Allow
221 }
222
223 /// Creates a Block action.
224 #[inline]
225 #[must_use]
226 pub fn block() -> Self {
227 Self::Block
228 }
229
230 /// Creates a Modify action with headers only (no status code change).
231 #[inline]
232 #[must_use]
233 pub fn modify_headers(headers: HashMap<String, String>) -> Self {
234 Self::Modify {
235 headers,
236 status_code: None,
237 }
238 }
239
240 /// Creates a Modify action with headers and status code override.
241 #[inline]
242 #[must_use]
243 pub fn modify_headers_with_status(
244 headers: HashMap<String, String>,
245 status_code: u16,
246 ) -> Self {
247 Self::Modify {
248 headers,
249 status_code: Some(status_code),
250 }
251 }
252}
253
254// ============================================================================
255// InterceptedResponse
256// ============================================================================
257
258/// Data about an intercepted network response.
259#[derive(Debug, Clone)]
260pub struct InterceptedResponse {
261 /// Unique request ID.
262 pub request_id: String,
263
264 /// Request URL.
265 pub url: String,
266
267 /// HTTP status code.
268 pub status: u16,
269
270 /// HTTP status text.
271 pub status_text: String,
272
273 /// Response headers.
274 pub headers: HashMap<String, String>,
275
276 /// Tab ID where request originated.
277 pub tab_id: u32,
278
279 /// Frame ID where request originated.
280 pub frame_id: u64,
281}
282
283// ============================================================================
284// ResponseAction
285// ============================================================================
286
287/// Action to take for intercepted response headers.
288#[derive(Debug, Clone)]
289pub enum ResponseAction {
290 /// Allow the response to proceed unchanged.
291 Allow,
292
293 /// Block/cancel the response.
294 Block,
295
296 /// Modify response body content.
297 Modify {
298 /// Replacement body content.
299 body: String,
300 },
301
302 /// Fulfill the response with fully synthetic data (complete response mocking).
303 Fulfill {
304 /// HTTP status code.
305 status: u16,
306 /// Response headers.
307 headers: HashMap<String, String>,
308 /// Response body.
309 body: String,
310 },
311}
312
313// ============================================================================
314// ResponseAction - Constructors
315// ============================================================================
316
317impl ResponseAction {
318 /// Creates an Allow action.
319 #[inline]
320 #[must_use]
321 pub fn allow() -> Self {
322 Self::Allow
323 }
324
325 /// Creates a Block action.
326 #[inline]
327 #[must_use]
328 pub fn block() -> Self {
329 Self::Block
330 }
331
332 /// Creates a Modify action that replaces the response body.
333 #[inline]
334 #[must_use]
335 pub fn modify(body: impl Into<String>) -> Self {
336 Self::Modify { body: body.into() }
337 }
338
339 /// Creates a Fulfill action for complete response mocking.
340 #[inline]
341 #[must_use]
342 pub fn fulfill(
343 status: u16,
344 headers: HashMap<String, String>,
345 body: impl Into<String>,
346 ) -> Self {
347 Self::Fulfill {
348 status,
349 headers,
350 body: body.into(),
351 }
352 }
353}
354
355// ============================================================================
356// InterceptedResponseBody
357// ============================================================================
358
359/// Data about an intercepted response body.
360#[derive(Debug, Clone)]
361pub struct InterceptedResponseBody {
362 /// Unique request ID.
363 pub request_id: String,
364
365 /// Request URL.
366 pub url: String,
367
368 /// HTTP status code.
369 pub status: u16,
370
371 /// Content-Type of the response.
372 pub content_type: String,
373
374 /// Response body data (text or binary).
375 pub body: ResponseBodyData,
376
377 /// Tab ID.
378 pub tab_id: u32,
379
380 /// Frame ID.
381 pub frame_id: u64,
382
383 /// Content length.
384 pub content_length: usize,
385}
386
387// ============================================================================
388// ResponseBodyData
389// ============================================================================
390
391/// Response body content, supporting both text and binary data.
392#[derive(Debug, Clone)]
393pub enum ResponseBodyData {
394 /// Text body content (UTF-8 string).
395 Text(String),
396
397 /// Binary body content (base64-decoded from extension).
398 Binary(Vec<u8>),
399}
400
401impl ResponseBodyData {
402 /// Returns the body as a string reference if it is text.
403 #[inline]
404 #[must_use]
405 pub fn as_text(&self) -> Option<&str> {
406 match self {
407 Self::Text(s) => Some(s),
408 Self::Binary(_) => None,
409 }
410 }
411
412 /// Returns the body as a byte slice if it is binary.
413 #[inline]
414 #[must_use]
415 pub fn as_bytes(&self) -> Option<&[u8]> {
416 match self {
417 Self::Text(_) => None,
418 Self::Binary(b) => Some(b),
419 }
420 }
421
422 /// Returns true if the body is text.
423 #[inline]
424 #[must_use]
425 pub fn is_text(&self) -> bool {
426 matches!(self, Self::Text(_))
427 }
428
429 /// Returns true if the body is binary.
430 #[inline]
431 #[must_use]
432 pub fn is_binary(&self) -> bool {
433 matches!(self, Self::Binary(_))
434 }
435}
436
437// ============================================================================
438// BodyAction
439// ============================================================================
440
441/// Action to take for intercepted response body.
442#[derive(Debug, Clone)]
443pub enum BodyAction {
444 /// Allow body to proceed unchanged.
445 Allow,
446
447 /// Modify body content.
448 ModifyBody(String),
449}
450
451// ============================================================================
452// BodyAction - Constructors
453// ============================================================================
454
455impl BodyAction {
456 /// Creates an Allow action.
457 #[inline]
458 #[must_use]
459 pub fn allow() -> Self {
460 Self::Allow
461 }
462
463 /// Creates a ModifyBody action.
464 #[inline]
465 #[must_use]
466 pub fn modify_body(body: impl Into<String>) -> Self {
467 Self::ModifyBody(body.into())
468 }
469}
470
471// ============================================================================
472// Tests
473// ============================================================================
474
475#[cfg(test)]
476mod tests {
477 use super::{BodyAction, HeadersAction, RequestAction, ResponseAction, ResponseBodyData};
478
479 use std::collections::HashMap;
480
481 #[test]
482 fn test_request_action_allow() {
483 let action = RequestAction::allow();
484 assert!(matches!(action, RequestAction::Allow));
485 }
486
487 #[test]
488 fn test_request_action_block() {
489 let action = RequestAction::block();
490 assert!(matches!(action, RequestAction::Block));
491 }
492
493 #[test]
494 fn test_request_action_redirect() {
495 let action = RequestAction::redirect("https://example.com");
496 if let RequestAction::Redirect(url) = action {
497 assert_eq!(url, "https://example.com");
498 } else {
499 panic!("Expected Redirect action");
500 }
501 }
502
503 #[test]
504 fn test_headers_action_allow() {
505 let action = HeadersAction::allow();
506 assert!(matches!(action, HeadersAction::Allow));
507 }
508
509 #[test]
510 fn test_headers_action_block() {
511 let action = HeadersAction::block();
512 assert!(matches!(action, HeadersAction::Block));
513 }
514
515 #[test]
516 fn test_headers_action_modify() {
517 let mut headers = HashMap::new();
518 headers.insert("X-Custom".to_string(), "value".to_string());
519
520 let action = HeadersAction::modify_headers(headers);
521 if let HeadersAction::Modify {
522 headers: h,
523 status_code,
524 } = action
525 {
526 assert_eq!(h.get("X-Custom"), Some(&"value".to_string()));
527 assert!(status_code.is_none());
528 } else {
529 panic!("Expected Modify action");
530 }
531 }
532
533 #[test]
534 fn test_headers_action_modify_with_status() {
535 let mut headers = HashMap::new();
536 headers.insert("X-Custom".to_string(), "value".to_string());
537
538 let action = HeadersAction::modify_headers_with_status(headers, 404);
539 if let HeadersAction::Modify {
540 headers: h,
541 status_code,
542 } = action
543 {
544 assert_eq!(h.get("X-Custom"), Some(&"value".to_string()));
545 assert_eq!(status_code, Some(404));
546 } else {
547 panic!("Expected Modify action with status");
548 }
549 }
550
551 #[test]
552 fn test_response_action_allow() {
553 let action = ResponseAction::allow();
554 assert!(matches!(action, ResponseAction::Allow));
555 }
556
557 #[test]
558 fn test_response_action_block() {
559 let action = ResponseAction::block();
560 assert!(matches!(action, ResponseAction::Block));
561 }
562
563 #[test]
564 fn test_response_action_modify() {
565 let action = ResponseAction::modify("new body");
566 if let ResponseAction::Modify { body } = action {
567 assert_eq!(body, "new body");
568 } else {
569 panic!("Expected Modify action");
570 }
571 }
572
573 #[test]
574 fn test_response_action_fulfill() {
575 let mut headers = HashMap::new();
576 headers.insert("Content-Type".to_string(), "application/json".to_string());
577
578 let action = ResponseAction::fulfill(200, headers, r#"{"ok": true}"#);
579 if let ResponseAction::Fulfill {
580 status,
581 headers: h,
582 body,
583 } = action
584 {
585 assert_eq!(status, 200);
586 assert_eq!(
587 h.get("Content-Type"),
588 Some(&"application/json".to_string())
589 );
590 assert_eq!(body, r#"{"ok": true}"#);
591 } else {
592 panic!("Expected Fulfill action");
593 }
594 }
595
596 #[test]
597 fn test_body_action_allow() {
598 let action = BodyAction::allow();
599 assert!(matches!(action, BodyAction::Allow));
600 }
601
602 #[test]
603 fn test_body_action_modify() {
604 let action = BodyAction::modify_body("new body");
605 if let BodyAction::ModifyBody(body) = action {
606 assert_eq!(body, "new body");
607 } else {
608 panic!("Expected ModifyBody action");
609 }
610 }
611
612 #[test]
613 fn test_response_body_data_text() {
614 let data = ResponseBodyData::Text("hello".to_string());
615 assert!(data.is_text());
616 assert!(!data.is_binary());
617 assert_eq!(data.as_text(), Some("hello"));
618 assert!(data.as_bytes().is_none());
619 }
620
621 #[test]
622 fn test_response_body_data_binary() {
623 let data = ResponseBodyData::Binary(vec![0x89, 0x50, 0x4E, 0x47]);
624 assert!(!data.is_text());
625 assert!(data.is_binary());
626 assert!(data.as_text().is_none());
627 assert_eq!(data.as_bytes(), Some(&[0x89, 0x50, 0x4E, 0x47][..]));
628 }
629}