mod common; #[test] fn test_per_host_content_isolation() { let temp_dir = common::setup_test_environment(); // Create different content for each host let site1_root = temp_dir.path().join("site1"); let site2_root = temp_dir.path().join("site2"); std::fs::create_dir(&site1_root).unwrap(); std::fs::create_dir(&site2_root).unwrap(); // Create different index.gmi files for each site std::fs::write(site1_root.join("index.gmi"), "Welcome to Site 1").unwrap(); std::fs::write(site2_root.join("index.gmi"), "Welcome to Site 2").unwrap(); // Create config with two hosts let config_path = temp_dir.path().join("config.toml"); let port = 1965 + (std::process::id() % 1000) as u16; // Use dynamic port let config_content = format!( r#" bind_host = "127.0.0.1" port = {} ["site1.com"] root = "{}" cert = "{}" key = "{}" ["site2.org"] root = "{}" cert = "{}" key = "{}" "#, port, site1_root.display(), temp_dir.path().join("cert.pem").display(), temp_dir.path().join("key.pem").display(), site2_root.display(), temp_dir.path().join("cert.pem").display(), temp_dir.path().join("key.pem").display() ); std::fs::write(&config_path, config_content).unwrap(); // Start server with TLS let mut server_process = std::process::Command::new(env!("CARGO_BIN_EXE_pollux")) .arg("--config") .arg(&config_path) .spawn() .unwrap(); // Wait for server to start std::thread::sleep(std::time::Duration::from_millis(500)); // Test site1.com serves its content let response1 = make_gemini_request("127.0.0.1", port, "gemini://site1.com/"); assert!( response1.starts_with("20"), "Expected success for site1.com, got: {}", response1 ); assert!( response1.contains("Welcome to Site 1"), "Should serve site1 content, got: {}", response1 ); // Test site2.org serves its content let response2 = make_gemini_request("127.0.0.1", port, "gemini://site2.org/"); assert!( response2.starts_with("20"), "Expected success for site2.org, got: {}", response2 ); assert!( response2.contains("Welcome to Site 2"), "Should serve site2 content, got: {}", response2 ); server_process.kill().unwrap(); } #[test] fn test_per_host_path_security() { let temp_dir = common::setup_test_environment(); // Create directory structure for site1 let site1_root = temp_dir.path().join("site1"); std::fs::create_dir(&site1_root).unwrap(); std::fs::create_dir(site1_root.join("subdir")).unwrap(); std::fs::write( site1_root.join("subdir").join("secret.gmi"), "Secret content", ) .unwrap(); // Create config let config_path = temp_dir.path().join("config.toml"); let port = 1968 + (std::process::id() % 1000) as u16; let cert_path = temp_dir.path().join("cert.pem"); let key_path = temp_dir.path().join("key.pem"); let config_content = format!( r#" bind_host = "127.0.0.1" port = {} ["site1.com"] root = "{}" cert = "{}" key = "{}" "#, port, site1_root.display(), cert_path.display(), key_path.display() ); std::fs::write(&config_path, config_content).unwrap(); let mut server_process = std::process::Command::new(env!("CARGO_BIN_EXE_pollux")) .arg("--config") .arg(&config_path) .spawn() .unwrap(); std::thread::sleep(std::time::Duration::from_millis(500)); // Test path traversal attempt should be blocked let response = make_gemini_request("127.0.0.1", port, "gemini://site1.com/../../../etc/passwd"); assert!( response.starts_with("51"), "Path traversal should be blocked, got: {}", response ); // Test valid subdirectory access should work let response = make_gemini_request("127.0.0.1", port, "gemini://site1.com/subdir/secret.gmi"); assert!( response.starts_with("20"), "Valid subdirectory access should work, got: {}", response ); assert!( response.contains("Secret content"), "Should serve content from subdirectory, got: {}", response ); server_process.kill().unwrap(); } fn make_gemini_request(host: &str, port: u16, url: &str) -> String { // Use the Python client for TLS requests use std::process::Command; let output = Command::new("python3") .arg("tests/gemini_test_client.py") .arg(url) .env("GEMINI_PORT", &port.to_string()) .env("GEMINI_CONNECT_HOST", host) .output() .unwrap(); String::from_utf8(output.stdout).unwrap() }