use url_preview::{CacheStrategy, Fetcher, FetcherConfig, PreviewError, PreviewService, PreviewServiceConfig, UrlValidationConfig};
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
println!("=== URL Preview Security Validation Example ===\n");
println!("1. Testing default security configuration:");
test_default_security().await?;
println!("\n2. Testing domain whitelist:");
test_domain_whitelist().await?;
println!("\n3. Testing domain blacklist:");
test_domain_blacklist().await?;
println!("\n4. Testing private IP and localhost blocking:");
test_private_ip_blocking().await?;
println!("\n5. Testing HTTPS-only configuration:");
test_https_only().await?;
Ok(())
}
async fn test_default_security() -> Result<(), Box<dyn std::error::Error>> {
let service = PreviewService::new();
let test_urls = vec![
("http://localhost", "LocalhostBlocked"),
("http://127.0.0.1", "LocalhostBlocked"),
("http://192.168.1.1", "PrivateIpBlocked"),
("http://10.0.0.1", "PrivateIpBlocked"),
("file:///etc/passwd", "InvalidUrlScheme"),
("ftp://example.com", "InvalidUrlScheme"),
];
for (url, _expected_error) in test_urls {
match service.generate_preview(url).await {
Err(e) => println!(" ✓ {} blocked as expected: {}", url, e),
Ok(_) => println!(" ✗ {} should have been blocked!", url),
}
}
match service.generate_preview("https://www.rust-lang.org").await {
Ok(preview) => println!(
" ✓ https://www.rust-lang.org allowed: {}",
preview.title.unwrap_or_else(|| "No title".to_string())
),
Err(e) => println!(" ✗ Public HTTPS URL should be allowed: {}", e),
}
Ok(())
}
async fn test_domain_whitelist() -> Result<(), Box<dyn std::error::Error>> {
let mut url_validation = UrlValidationConfig::default();
url_validation
.allowed_domains
.insert("rust-lang.org".to_string());
url_validation
.allowed_domains
.insert("github.com".to_string());
let fetcher_config = FetcherConfig {
url_validation,
..Default::default()
};
let custom_fetcher = Fetcher::with_config(fetcher_config);
let service_config = PreviewServiceConfig::new(1000)
.with_cache_strategy(CacheStrategy::UseCache)
.with_default_fetcher(custom_fetcher);
let service = PreviewService::new_with_config(service_config);
println!(" Testing allowed domains:");
match service.generate_preview("https://www.rust-lang.org").await {
Ok(_) => println!(" ✓ rust-lang.org allowed"),
Err(e) => println!(" ✗ rust-lang.org should be allowed: {}", e),
}
match service
.generate_preview("https://github.com/rust-lang")
.await
{
Ok(_) => println!(" ✓ github.com allowed"),
Err(e) => println!(" ✗ github.com should be allowed: {}", e),
}
match service.generate_preview("https://example.com").await {
Err(PreviewError::DomainNotAllowed(domain)) => {
println!(" ✓ {} blocked (not in whitelist)", domain)
}
_ => println!(" ✗ example.com should be blocked"),
}
Ok(())
}
async fn test_domain_blacklist() -> Result<(), Box<dyn std::error::Error>> {
let mut url_validation = UrlValidationConfig::default();
url_validation
.blocked_domains
.insert("evil.com".to_string());
url_validation
.blocked_domains
.insert("malicious.site".to_string());
let fetcher_config = FetcherConfig {
url_validation,
..Default::default()
};
let custom_fetcher = Fetcher::with_config(fetcher_config);
let service_config = PreviewServiceConfig::new(1000)
.with_cache_strategy(CacheStrategy::UseCache)
.with_default_fetcher(custom_fetcher);
let service = PreviewService::new_with_config(service_config);
println!(" Testing blocked domains:");
match service.generate_preview("https://evil.com").await {
Err(PreviewError::DomainBlocked(domain)) => {
println!(" ✓ {} blocked", domain)
}
_ => println!(" ✗ evil.com should be blocked"),
}
match service.generate_preview("https://sub.evil.com").await {
Err(PreviewError::DomainBlocked(domain)) => {
println!(" ✓ {} blocked (subdomain)", domain)
}
_ => println!(" ✗ sub.evil.com should be blocked"),
}
match service.generate_preview("https://www.rust-lang.org").await {
Ok(_) => println!(" ✓ rust-lang.org allowed"),
Err(e) => println!(" ✗ rust-lang.org should be allowed: {}", e),
}
Ok(())
}
async fn test_private_ip_blocking() -> Result<(), Box<dyn std::error::Error>> {
let service = PreviewService::new();
let private_ips = vec![
"http://10.0.0.1",
"http://172.16.0.1",
"http://192.168.1.1",
"http://169.254.1.1",
"http://127.0.0.1",
"http://localhost",
"http://[::1]",
"http://[fe80::1]",
"http://[fc00::1]",
];
for ip_url in private_ips {
match service.generate_preview(ip_url).await {
Err(PreviewError::PrivateIpBlocked(ip)) => {
println!(" ✓ {} blocked (private IP: {})", ip_url, ip)
}
Err(PreviewError::LocalhostBlocked) => {
println!(" ✓ {} blocked (localhost)", ip_url)
}
_ => println!(" ✗ {} should be blocked!", ip_url),
}
}
match service.generate_preview("http://8.8.8.8").await {
Ok(_) | Err(_) => println!(" ✓ Public IP 8.8.8.8 not blocked due to IP restriction"),
}
Ok(())
}
async fn test_https_only() -> Result<(), Box<dyn std::error::Error>> {
let mut url_validation = UrlValidationConfig::default();
url_validation.allowed_schemes.clear();
url_validation.allowed_schemes.insert("https".to_string());
let fetcher_config = FetcherConfig {
url_validation,
..Default::default()
};
let custom_fetcher = Fetcher::with_config(fetcher_config);
let service_config = PreviewServiceConfig::new(1000)
.with_cache_strategy(CacheStrategy::UseCache)
.with_default_fetcher(custom_fetcher);
let service = PreviewService::new_with_config(service_config);
match service.generate_preview("http://example.com").await {
Err(PreviewError::InvalidUrlScheme(scheme)) => {
println!(" ✓ HTTP blocked in HTTPS-only mode: {}", scheme)
}
_ => println!(" ✗ HTTP should be blocked in HTTPS-only mode"),
}
match service.generate_preview("https://www.rust-lang.org").await {
Ok(_) => println!(" ✓ HTTPS allowed"),
Err(e) => println!(" ✗ HTTPS should be allowed: {}", e),
}
Ok(())
}