use serde::{Deserialize, Serialize};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
#[repr(C)]
pub enum Profile {
#[default]
Windows,
Linux,
Macos,
}
impl Profile {
pub fn parse(s: &str) -> Option<Self> {
match s.to_lowercase().as_str() {
"windows" | "win" => Some(Profile::Windows),
"linux" => Some(Profile::Linux),
"macos" | "mac" | "darwin" => Some(Profile::Macos),
_ => None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ProxyConfig {
pub host: String,
pub port: u16,
pub username: Option<String>,
pub password: Option<String>,
}
impl ProxyConfig {
pub fn new(host: impl Into<String>, port: u16) -> Self {
Self {
host: host.into(),
port,
username: None,
password: None,
}
}
pub fn with_auth(mut self, username: impl Into<String>, password: impl Into<String>) -> Self {
self.username = Some(username.into());
self.password = Some(password.into());
self
}
pub fn to_url(&self) -> String {
format!("http://{}:{}", self.host, self.port)
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Cookie {
pub name: String,
pub value: String,
#[serde(skip_serializing_if = "Option::is_none")]
pub domain: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub path: Option<String>,
#[serde(skip_serializing_if = "Option::is_none")]
pub expires: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub http_only: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub secure: Option<bool>,
#[serde(skip_serializing_if = "Option::is_none")]
pub same_site: Option<String>,
}
impl Cookie {
pub fn new(name: impl Into<String>, value: impl Into<String>) -> Self {
Self {
name: name.into(),
value: value.into(),
domain: None,
path: None,
expires: None,
http_only: None,
secure: None,
same_site: None,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct WafSession {
pub cookies: Vec<Cookie>,
pub headers: HashMap<String, String>,
}
impl WafSession {
pub fn new(cookies: Vec<Cookie>, headers: HashMap<String, String>) -> Self {
Self { cookies, headers }
}
pub fn cookies_string(&self) -> String {
self.cookies
.iter()
.map(|c| format!("{}={}", c.name, c.value))
.collect::<Vec<_>>()
.join("; ")
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
#[serde(tag = "type", content = "data")]
pub enum ChaserResult {
Source(String),
Token(String),
WafSession(WafSession),
Error { code: i32, message: String },
}
impl ChaserResult {
pub fn source(html: String) -> Self {
ChaserResult::Source(html)
}
pub fn token(token: String) -> Self {
ChaserResult::Token(token)
}
pub fn waf_session(session: WafSession) -> Self {
ChaserResult::WafSession(session)
}
pub fn error(code: i32, message: impl Into<String>) -> Self {
ChaserResult::Error {
code,
message: message.into(),
}
}
pub fn is_success(&self) -> bool {
!matches!(self, ChaserResult::Error { .. })
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_proxy_url_without_auth() {
let proxy = ProxyConfig::new("proxy.example.com", 8080);
assert_eq!(proxy.to_url(), "http://proxy.example.com:8080");
}
#[test]
fn test_proxy_url_with_auth() {
let proxy = ProxyConfig::new("proxy.example.com", 8080).with_auth("user", "pass");
assert_eq!(proxy.to_url(), "http://user:pass@proxy.example.com:8080");
}
#[test]
fn test_waf_session_cookies_string() {
let session = WafSession::new(
vec![
Cookie::new("cf_clearance", "abc123"),
Cookie::new("session", "xyz789"),
],
HashMap::new(),
);
assert_eq!(
session.cookies_string(),
"cf_clearance=abc123; session=xyz789"
);
}
#[test]
fn test_profile_from_str() {
assert_eq!(Profile::parse("windows"), Some(Profile::Windows));
assert_eq!(Profile::parse("LINUX"), Some(Profile::Linux));
assert_eq!(Profile::parse("darwin"), Some(Profile::Macos));
assert_eq!(Profile::parse("invalid"), None);
}
}