1use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5
6#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
8#[serde(rename_all = "lowercase")]
9#[repr(C)]
10pub enum Profile {
11 #[default]
13 Windows,
14 Linux,
16 Macos,
18}
19
20impl Profile {
21 pub fn parse(s: &str) -> Option<Self> {
23 match s.to_lowercase().as_str() {
24 "windows" | "win" => Some(Profile::Windows),
25 "linux" => Some(Profile::Linux),
26 "macos" | "mac" | "darwin" => Some(Profile::Macos),
27 _ => None,
28 }
29 }
30}
31
32#[derive(Debug, Clone, Serialize, Deserialize)]
34pub struct ProxyConfig {
35 pub host: String,
37 pub port: u16,
39 pub username: Option<String>,
41 pub password: Option<String>,
43}
44
45impl ProxyConfig {
46 pub fn new(host: impl Into<String>, port: u16) -> Self {
48 Self {
49 host: host.into(),
50 port,
51 username: None,
52 password: None,
53 }
54 }
55
56 pub fn with_auth(mut self, username: impl Into<String>, password: impl Into<String>) -> Self {
58 self.username = Some(username.into());
59 self.password = Some(password.into());
60 self
61 }
62
63 pub fn to_url(&self) -> String {
66 format!("http://{}:{}", self.host, self.port)
67 }
68}
69
70#[derive(Debug, Clone, Serialize, Deserialize)]
72pub struct Cookie {
73 pub name: String,
75 pub value: String,
77 #[serde(skip_serializing_if = "Option::is_none")]
79 pub domain: Option<String>,
80 #[serde(skip_serializing_if = "Option::is_none")]
82 pub path: Option<String>,
83 #[serde(skip_serializing_if = "Option::is_none")]
85 pub expires: Option<f64>,
86 #[serde(skip_serializing_if = "Option::is_none")]
88 pub http_only: Option<bool>,
89 #[serde(skip_serializing_if = "Option::is_none")]
91 pub secure: Option<bool>,
92 #[serde(skip_serializing_if = "Option::is_none")]
94 pub same_site: Option<String>,
95}
96
97impl Cookie {
98 pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
100 Self {
101 name: name.into(),
102 value: value.into(),
103 domain: None,
104 path: None,
105 expires: None,
106 http_only: None,
107 secure: None,
108 same_site: None,
109 }
110 }
111}
112
113#[derive(Debug, Clone, Serialize, Deserialize)]
115pub struct WafSession {
116 pub cookies: Vec<Cookie>,
118 pub headers: HashMap<String, String>,
120}
121
122impl WafSession {
123 pub fn new(cookies: Vec<Cookie>, headers: HashMap<String, String>) -> Self {
125 Self { cookies, headers }
126 }
127
128 pub fn cookies_string(&self) -> String {
130 self.cookies
131 .iter()
132 .map(|c| format!("{}={}", c.name, c.value))
133 .collect::<Vec<_>>()
134 .join("; ")
135 }
136}
137
138#[derive(Debug, Clone, Serialize, Deserialize)]
140#[serde(tag = "type", content = "data")]
141pub enum ChaserResult {
142 Source(String),
144 Token(String),
146 WafSession(WafSession),
148 Error { code: i32, message: String },
150}
151
152impl ChaserResult {
153 pub fn source(html: String) -> Self {
155 ChaserResult::Source(html)
156 }
157
158 pub fn token(token: String) -> Self {
160 ChaserResult::Token(token)
161 }
162
163 pub fn waf_session(session: WafSession) -> Self {
165 ChaserResult::WafSession(session)
166 }
167
168 pub fn error(code: i32, message: impl Into<String>) -> Self {
170 ChaserResult::Error {
171 code,
172 message: message.into(),
173 }
174 }
175
176 pub fn is_success(&self) -> bool {
178 !matches!(self, ChaserResult::Error { .. })
179 }
180}
181
182#[cfg(test)]
183mod tests {
184 use super::*;
185
186 #[test]
187 fn test_proxy_url_without_auth() {
188 let proxy = ProxyConfig::new("proxy.example.com", 8080);
189 assert_eq!(proxy.to_url(), "http://proxy.example.com:8080");
190 }
191
192 #[test]
193 fn test_proxy_url_with_auth() {
194 let proxy = ProxyConfig::new("proxy.example.com", 8080).with_auth("user", "pass");
195 assert_eq!(proxy.to_url(), "http://user:pass@proxy.example.com:8080");
196 }
197
198 #[test]
199 fn test_waf_session_cookies_string() {
200 let session = WafSession::new(
201 vec![
202 Cookie::new("cf_clearance", "abc123"),
203 Cookie::new("session", "xyz789"),
204 ],
205 HashMap::new(),
206 );
207 assert_eq!(
208 session.cookies_string(),
209 "cf_clearance=abc123; session=xyz789"
210 );
211 }
212
213 #[test]
214 fn test_profile_from_str() {
215 assert_eq!(Profile::parse("windows"), Some(Profile::Windows));
216 assert_eq!(Profile::parse("LINUX"), Some(Profile::Linux));
217 assert_eq!(Profile::parse("darwin"), Some(Profile::Macos));
218 assert_eq!(Profile::parse("invalid"), None);
219 }
220}