pollux/tests/virtual_host_paths.rs
Jeena 0459cb6220 feat: Implement virtual hosting for multi-domain Gemini server
- Add hostname-based request routing for multiple capsules per server
- Parse virtual host configs from TOML sections ([hostname])
- Implement per-host certificate and content isolation
- Add comprehensive virtual host testing and validation
- Update docs and examples for multi-host deployments

This enables Pollux to serve multiple Gemini domains from one instance,
providing the foundation for multi-tenant Gemini hosting.
2026-01-22 02:38:09 +00:00

131 lines
No EOL
4.3 KiB
Rust

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()
}