mirror of
https://github.com/zhom/donutbrowser.git
synced 2026-04-30 15:48:19 +02:00
fix: properly match proxy timezone
This commit is contained in:
Generated
+29
@@ -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"
|
||||
|
||||
@@ -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"] }
|
||||
|
||||
@@ -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;
|
||||
|
||||
|
||||
@@ -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(),
|
||||
|
||||
Reference in New Issue
Block a user