diff --git a/crates/obscura-js/src/ops.rs b/crates/obscura-js/src/ops.rs index 4edabc1..9d5c5f5 100644 --- a/crates/obscura-js/src/ops.rs +++ b/crates/obscura-js/src/ops.rs @@ -551,13 +551,17 @@ fn glob_match(pattern: &str, url: &str) -> bool { fn validate_fetch_url(url: &url::Url) -> Result<(), String> { let scheme = url.scheme(); - if scheme != "http" && scheme != "https" { + if scheme != "http" && scheme != "https" && scheme != "file" { return Err(format!( - "Forbidden URL scheme '{}' - only http and https are allowed", + "Forbidden URL scheme '{}' - only http, https, and file are allowed", scheme )); } + if scheme == "file" { + return Ok(()); + } + if let Some(host) = url.host() { match host { url::Host::Ipv4(ip) => { diff --git a/crates/obscura-net/src/client.rs b/crates/obscura-net/src/client.rs index 85479c9..ac7fd24 100644 --- a/crates/obscura-net/src/client.rs +++ b/crates/obscura-net/src/client.rs @@ -66,13 +66,17 @@ pub type ResponseCallback = Arc; fn validate_url(url: &Url) -> Result<(), ObscuraNetError> { let scheme = url.scheme(); - if scheme != "http" && scheme != "https" { + if scheme != "http" && scheme != "https" && scheme != "file" { return Err(ObscuraNetError::Network(format!( - "Forbidden URL scheme '{}' - only http and https are allowed", + "Forbidden URL scheme '{}' - only http, https, and file are allowed", scheme ))); } + if scheme == "file" { + return Ok(()); + } + if let Some(host) = url.host() { match host { url::Host::Ipv4(ip) => { @@ -115,6 +119,41 @@ fn validate_url(url: &Url) -> Result<(), ObscuraNetError> { Ok(()) } +async fn fetch_file_url(url: &Url) -> Result { + let path = url + .to_file_path() + .map_err(|_| ObscuraNetError::Network("Invalid file URL".to_string()))?; + let body = tokio::fs::read(&path) + .await + .map_err(|e| ObscuraNetError::Network(format!("Failed to read file: {}", e)))?; + + let mut headers = HashMap::new(); + if let Some(ext) = path.extension().and_then(|e| e.to_str()) { + let ct = match ext.to_lowercase().as_str() { + "html" | "htm" => "text/html", + "css" => "text/css", + "js" | "mjs" => "application/javascript", + "json" => "application/json", + "png" => "image/png", + "jpg" | "jpeg" => "image/jpeg", + "gif" => "image/gif", + "svg" => "image/svg+xml", + "webp" => "image/webp", + "ico" => "image/x-icon", + _ => "application/octet-stream", + }; + headers.insert("content-type".to_string(), ct.to_string()); + } + + Ok(Response { + url: url.clone(), + status: 200, + headers, + body, + redirected_from: Vec::new(), + }) +} + pub struct ObscuraHttpClient { client: tokio::sync::OnceCell, proxy_url: Option, @@ -190,6 +229,10 @@ impl ObscuraHttpClient { ) -> Result { validate_url(url)?; + if url.scheme() == "file" { + return fetch_file_url(url).await; + } + let mut method = initial_method; let mut body = initial_body; if self.block_trackers {