feat: support file:// URL scheme for local HTML testing
Allow loading local HTML/CSS/JS files via the file:// protocol, enabling Obscura to be used for testing static sites without running a local HTTP server. Changes: - Allow "file" scheme in validate_url() and validate_fetch_url() - Add fetch_file_url() to read local files and return synthetic HTTP responses with appropriate content-type headers - Skip private IP / localhost checks for file:// URLs Closes #16
This commit is contained in:
1 parent
b00a1f20e7
commit
21136ca819
2 files changed
+51
-4
No files matched your search
@@ -551,13 +551,17 @@ fn glob_match(pattern: &str, url: &str) -> bool {
|
|||||||
|
|
||||||
fn validate_fetch_url(url: &url::Url) -> Result<(), String> {
|
fn validate_fetch_url(url: &url::Url) -> Result<(), String> {
|
||||||
let scheme = url.scheme();
|
let scheme = url.scheme();
|
||||||
if scheme != "http" && scheme != "https" {
|
if scheme != "http" && scheme != "https" && scheme != "file" {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"Forbidden URL scheme '{}' - only http and https are allowed",
|
"Forbidden URL scheme '{}' - only http, https, and file are allowed",
|
||||||
scheme
|
scheme
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if scheme == "file" {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(host) = url.host() {
|
if let Some(host) = url.host() {
|
||||||
match host {
|
match host {
|
||||||
url::Host::Ipv4(ip) => {
|
url::Host::Ipv4(ip) => {
|
||||||
|
|||||||
@@ -66,13 +66,17 @@ pub type ResponseCallback = Arc<dyn Fn(&RequestInfo, &Response) + Send + Sync>;
|
|||||||
|
|
||||||
fn validate_url(url: &Url) -> Result<(), ObscuraNetError> {
|
fn validate_url(url: &Url) -> Result<(), ObscuraNetError> {
|
||||||
let scheme = url.scheme();
|
let scheme = url.scheme();
|
||||||
if scheme != "http" && scheme != "https" {
|
if scheme != "http" && scheme != "https" && scheme != "file" {
|
||||||
return Err(ObscuraNetError::Network(format!(
|
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
|
scheme
|
||||||
)));
|
)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if scheme == "file" {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(host) = url.host() {
|
if let Some(host) = url.host() {
|
||||||
match host {
|
match host {
|
||||||
url::Host::Ipv4(ip) => {
|
url::Host::Ipv4(ip) => {
|
||||||
@@ -115,6 +119,41 @@ fn validate_url(url: &Url) -> Result<(), ObscuraNetError> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async fn fetch_file_url(url: &Url) -> Result<Response, ObscuraNetError> {
|
||||||
|
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 {
|
pub struct ObscuraHttpClient {
|
||||||
client: tokio::sync::OnceCell<Client>,
|
client: tokio::sync::OnceCell<Client>,
|
||||||
proxy_url: Option<String>,
|
proxy_url: Option<String>,
|
||||||
@@ -190,6 +229,10 @@ impl ObscuraHttpClient {
|
|||||||
) -> Result<Response, ObscuraNetError> {
|
) -> Result<Response, ObscuraNetError> {
|
||||||
validate_url(url)?;
|
validate_url(url)?;
|
||||||
|
|
||||||
|
if url.scheme() == "file" {
|
||||||
|
return fetch_file_url(url).await;
|
||||||
|
}
|
||||||
|
|
||||||
let mut method = initial_method;
|
let mut method = initial_method;
|
||||||
let mut body = initial_body;
|
let mut body = initial_body;
|
||||||
if self.block_trackers {
|
if self.block_trackers {
|
||||||
|
|||||||
Reference in new issue
Block a user