fix: properly match proxy timezone

This commit is contained in:
zhom
2026-03-10 01:59:58 +04:00
parent f4eecf24cc
commit b3d279046b
4 changed files with 164 additions and 12 deletions
+29
View File
@@ -984,6 +984,16 @@ dependencies = [
"windows-link 0.2.1",
]
[[package]]
name = "chrono-tz"
version = "0.10.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a6139a8597ed92cf816dfb33f5dd6cf0bb93a6adc938f11039f371bc5bcd26c3"
dependencies = [
"chrono",
"phf 0.12.1",
]
[[package]]
name = "cipher"
version = "0.4.4"
@@ -1600,6 +1610,7 @@ dependencies = [
"boringtun",
"bzip2",
"chrono",
"chrono-tz",
"clap",
"core-foundation 0.10.1",
"crossbeam-channel",
@@ -4354,6 +4365,15 @@ dependencies = [
"phf_shared 0.11.3",
]
[[package]]
name = "phf"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "913273894cec178f401a31ec4b656318d95473527be05c0752cc41cdc32be8b7"
dependencies = [
"phf_shared 0.12.1",
]
[[package]]
name = "phf_codegen"
version = "0.8.0"
@@ -4458,6 +4478,15 @@ dependencies = [
"siphasher 1.0.2",
]
[[package]]
name = "phf_shared"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "06005508882fb681fd97892ecff4b7fd0fee13ef1aa569f8695dae7ab9099981"
dependencies = [
"siphasher 1.0.2",
]
[[package]]
name = "pico-args"
version = "0.5.0"
+1
View File
@@ -72,6 +72,7 @@ mime_guess = "2"
once_cell = "1"
urlencoding = "2.1"
chrono = { version = "0.4", features = ["serde"] }
chrono-tz = "0.10"
axum = { version = "0.8.8", features = ["ws"] }
tower = "0.5"
tower-http = { version = "0.6", features = ["cors"] }
+22 -2
View File
@@ -425,8 +425,28 @@ impl CamoufoxConfigBuilder {
/// Build the complete Camoufox launch configuration with async geolocation support.
/// This method should be used when geoip option is set to Auto.
pub async fn build_async(self) -> Result<CamoufoxLaunchConfig, ConfigError> {
// Get proxy URL for IP detection if set
let proxy_url = self.proxy.as_ref().map(|p| p.server.clone());
// Get full proxy URL (with credentials) for IP detection
let proxy_url = self.proxy.as_ref().map(|p| {
if let (Some(user), Some(pass)) = (&p.username, &p.password) {
// Reconstruct URL with credentials: scheme://user:pass@host:port
if let Ok(mut parsed) = url::Url::parse(&p.server) {
let _ = parsed.set_username(user);
let _ = parsed.set_password(Some(pass));
parsed.to_string()
} else {
p.server.clone()
}
} else if let Some(user) = &p.username {
if let Ok(mut parsed) = url::Url::parse(&p.server) {
let _ = parsed.set_username(user);
parsed.to_string()
} else {
p.server.clone()
}
} else {
p.server.clone()
}
});
let geoip_option = self.geoip.clone();
let block_webrtc = self.block_webrtc;
+112 -10
View File
@@ -342,18 +342,69 @@ impl WayfernManager {
// Normalize the fingerprint: convert JSON string fields to proper types
let mut normalized = Self::normalize_fingerprint(fp);
// Add default timezone/geolocation if not present
// Wayfern's Bayesian network generator doesn't include these fields,
// so we need to add sensible defaults
if let Some(obj) = normalized.as_object_mut() {
if !obj.contains_key("timezone") {
obj.insert("timezone".to_string(), json!("America/New_York"));
// Apply geolocation based on proxy IP or geoip config
let geoip_option = config.geoip.as_ref();
let should_geolocate = match geoip_option {
Some(serde_json::Value::Bool(false)) => false,
_ => true, // Default to auto-detect
};
if should_geolocate {
let geo_result = async {
let ip = match geoip_option {
Some(serde_json::Value::String(ip_str)) => ip_str.clone(),
_ => {
// Auto-detect IP, optionally through proxy
crate::ip_utils::fetch_public_ip(config.proxy.as_deref())
.await
.map_err(|e| format!("Failed to fetch public IP: {e}"))?
}
};
crate::camoufox::geolocation::get_geolocation(&ip)
.map_err(|e| format!("Failed to get geolocation for IP {ip}: {e}"))
}
if !obj.contains_key("timezoneOffset") {
obj.insert("timezoneOffset".to_string(), json!(300)); // EST = UTC-5 = 300 minutes
.await;
match geo_result {
Ok(geo) => {
if let Some(obj) = normalized.as_object_mut() {
obj.insert("timezone".to_string(), json!(geo.timezone));
// Calculate timezone offset from IANA timezone name
if let Ok(tz) = geo.timezone.parse::<chrono_tz::Tz>() {
use chrono::Offset;
let now = chrono::Utc::now().with_timezone(&tz);
let offset_seconds = now.offset().fix().local_minus_utc();
let offset_minutes = -(offset_seconds / 60);
obj.insert("timezoneOffset".to_string(), json!(offset_minutes));
}
obj.insert("latitude".to_string(), json!(geo.latitude));
obj.insert("longitude".to_string(), json!(geo.longitude));
let locale_str = geo.locale.as_string();
obj.insert("language".to_string(), json!(&locale_str));
obj.insert(
"languages".to_string(),
json!([&locale_str, &geo.locale.language]),
);
}
log::info!(
"Applied geolocation to Wayfern fingerprint: {} ({})",
geo.locale.as_string(),
geo.timezone
);
}
Err(e) => {
log::warn!("Geolocation failed, using defaults: {e}");
if let Some(obj) = normalized.as_object_mut() {
if !obj.contains_key("timezone") {
obj.insert("timezone".to_string(), json!("America/New_York"));
}
if !obj.contains_key("timezoneOffset") {
obj.insert("timezoneOffset".to_string(), json!(300));
}
}
}
}
// Note: latitude/longitude are intentionally not set by default
// as they reveal precise location. Users should set these manually if needed.
}
normalized
@@ -567,6 +618,38 @@ impl WayfernManager {
log::warn!("No fingerprint found in config, browser will use default fingerprint");
}
// Set geolocation override via CDP so navigator.geolocation.getCurrentPosition() matches
if let Some(fingerprint_json) = &config.fingerprint {
if let Ok(fp) = serde_json::from_str::<serde_json::Value>(fingerprint_json) {
let fp_obj = if fp.get("fingerprint").is_some() {
fp.get("fingerprint").unwrap()
} else {
&fp
};
if let (Some(lat), Some(lng)) = (
fp_obj.get("latitude").and_then(|v| v.as_f64()),
fp_obj.get("longitude").and_then(|v| v.as_f64()),
) {
let accuracy = fp_obj
.get("accuracy")
.and_then(|v| v.as_f64())
.unwrap_or(100.0);
if let Some(target) = page_targets.first() {
if let Some(ws_url) = &target.websocket_debugger_url {
let _ = self
.send_cdp_command(
ws_url,
"Emulation.setGeolocationOverride",
json!({ "latitude": lat, "longitude": lng, "accuracy": accuracy }),
)
.await;
log::info!("Set geolocation override: lat={lat}, lng={lng}");
}
}
}
}
}
// Navigate to URL via CDP - fingerprint will be applied at navigation commit time
if let Some(url) = url {
log::info!("Navigating to URL via CDP: {}", url);
@@ -583,6 +666,25 @@ impl WayfernManager {
}
}
// Close the debugging port to prevent localhost port-scan detection.
// Reopen on a random high port after 5s so we can still manage the browser.
let reopen_port = port; // Reopen on same port for find_wayfern_by_profile recovery
if let Some(target) = page_targets.first() {
if let Some(ws_url) = &target.websocket_debugger_url {
match self
.send_cdp_command(
ws_url,
"Wayfern.closeDebuggingPort",
json!({ "reopenPort": reopen_port, "reopenDelayMs": 30000 }),
)
.await
{
Ok(_) => log::info!("Closed debugging port, will reopen on {reopen_port} after 30s"),
Err(e) => log::warn!("Failed to close debugging port: {e}"),
}
}
}
let id = uuid::Uuid::new_v4().to_string();
let instance = WayfernInstance {
id: id.clone(),