crawlkit_core/
response.rs1use std::collections::HashMap;
4
5#[derive(Debug, Clone)]
9pub struct Response {
10 pub url: String,
12 pub status: u16,
14 pub headers: HashMap<String, String>,
16 pub body: String,
18}
19
20impl Response {
21 pub fn is_success(&self) -> bool {
23 (200..300).contains(&self.status)
24 }
25
26 pub fn content_type(&self) -> Option<&str> {
28 self.headers.get("content-type").map(String::as_str)
29 }
30
31 pub fn is_html(&self) -> bool {
35 if let Some(ct) = self.content_type() {
36 let ct_lower = ct.to_ascii_lowercase();
37 if ct_lower.contains("text/html") || ct_lower.contains("application/xhtml+xml") {
38 return true;
39 }
40 }
41 let trimmed = self.body.trim_start();
42 let lower = trimmed[..trimmed.len().min(64)].to_ascii_lowercase();
43 lower.starts_with("<!doctype html") || lower.starts_with("<html")
44 }
45
46 pub fn is_bot_challenge(&self) -> bool {
52 if !self.is_html() {
53 return false;
54 }
55 let body_lower = self.body.to_ascii_lowercase();
56
57 if body_lower.contains("_pxhd")
59 || body_lower.contains("_pxuuid")
60 || body_lower.contains("px-captcha")
61 || body_lower.contains("pxcaptcha")
62 || body_lower.contains("_pxappappid")
63 || body_lower.contains("px-cloud.net")
64 || body_lower.contains("humansecurity.com")
65 {
66 return true;
67 }
68
69 if body_lower.contains("cf-challenge")
71 || body_lower.contains("challenge-platform")
72 || body_lower.contains("ray id")
73 && body_lower.contains("cloudflare")
74 && body_lower.contains("enable javascript")
75 {
76 return true;
77 }
78
79 if body_lower.contains("datadome")
81 || body_lower.contains("captcha-delivery")
82 {
83 return true;
84 }
85
86 if body_lower.contains("akamai")
88 && (body_lower.contains("bot") || body_lower.contains("challenge"))
89 {
90 return true;
91 }
92
93 if body_lower.contains("access to this page")
95 && body_lower.contains("denied")
96 && (body_lower.contains("captcha")
97 || body_lower.contains("human")
98 || body_lower.contains("press & hold")
99 || body_lower.contains("verify you are"))
100 {
101 return true;
102 }
103
104 if (body_lower.contains("<title>")
106 && (body_lower.contains("attention required")
107 || body_lower.contains("access denied")
108 || body_lower.contains("please verify")
109 || body_lower.contains("security check")
110 || body_lower.contains("robot")
111 || body_lower.contains("blocked")))
112 && body_lower.contains("challenge")
113 {
114 return true;
115 }
116
117 false
118 }
119}