Implement dual host configuration: bind_host and hostname

- Replace 'host' config with separate 'bind_host' and 'hostname'
- bind_host: IP/interface for server binding (default 0.0.0.0)
- hostname: Domain for URI validation (required)
- Update all parsing and validation code
- Create dist/ directory with systemd service, config, and install guide
- Add comprehensive INSTALL.md with setup instructions
This commit is contained in:
Jeena 2026-01-16 12:46:27 +00:00
parent 1665df65da
commit ea8083fe1f
7 changed files with 333 additions and 13 deletions

View file

@ -5,7 +5,8 @@ pub struct Config {
pub root: Option<String>,
pub cert: Option<String>,
pub key: Option<String>,
pub host: Option<String>,
pub bind_host: Option<String>,
pub hostname: Option<String>,
pub port: Option<u16>,
pub log_level: Option<String>,
pub max_concurrent_requests: Option<usize>,
@ -31,7 +32,8 @@ mod tests {
root = "/path/to/root"
cert = "cert.pem"
key = "key.pem"
host = "example.com"
bind_host = "0.0.0.0"
hostname = "example.com"
port = 1965
log_level = "info"
"#;
@ -41,7 +43,8 @@ mod tests {
assert_eq!(config.root, Some("/path/to/root".to_string()));
assert_eq!(config.cert, Some("cert.pem".to_string()));
assert_eq!(config.key, Some("key.pem".to_string()));
assert_eq!(config.host, Some("example.com".to_string()));
assert_eq!(config.bind_host, Some("0.0.0.0".to_string()));
assert_eq!(config.hostname, Some("example.com".to_string()));
assert_eq!(config.port, Some(1965));
assert_eq!(config.log_level, Some("info".to_string()));
assert_eq!(config.max_concurrent_requests, None); // Default

View file

@ -52,7 +52,8 @@ async fn main() {
root: None,
cert: None,
key: None,
host: None,
bind_host: None,
hostname: None,
port: None,
log_level: None,
max_concurrent_requests: None,
@ -66,7 +67,8 @@ async fn main() {
let root = config.root.expect("root is required");
let cert_path = config.cert.expect("cert is required");
let key_path = config.key.expect("key is required");
let host = config.host.unwrap_or_else(|| "0.0.0.0".to_string());
let bind_host = config.bind_host.unwrap_or_else(|| "0.0.0.0".to_string());
let hostname = config.hostname.expect("hostname is required");
let port = config.port.unwrap_or(1965);
// Validate max concurrent requests
@ -104,22 +106,22 @@ async fn main() {
let acceptor = TlsAcceptor::from(Arc::new(config));
let listener = TcpListener::bind(format!("{}:{}", host, port)).await.unwrap();
let listener = TcpListener::bind(format!("{}:{}", bind_host, port)).await.unwrap();
// Print startup information
print_startup_info(&host, port, &root, &cert_path, &key_path, Some(log_level), max_concurrent_requests);
print_startup_info(&bind_host, port, &root, &cert_path, &key_path, Some(log_level), max_concurrent_requests);
loop {
let (stream, _) = listener.accept().await.unwrap();
tracing::debug!("Accepted connection from {}", stream.peer_addr().unwrap_or_else(|_| "unknown".parse().unwrap()));
let acceptor = acceptor.clone();
let dir = root.clone();
let expected_host = "localhost".to_string(); // Override for testing
let expected_hostname = hostname.clone(); // Use configured hostname
let max_concurrent = max_concurrent_requests;
let test_delay = test_processing_delay;
tokio::spawn(async move {
if let Ok(stream) = acceptor.accept(stream).await {
if let Err(e) = server::handle_connection(stream, &dir, &expected_host, port, max_concurrent, test_delay).await {
if let Err(e) = server::handle_connection(stream, &dir, &expected_hostname, port, max_concurrent, test_delay).await {
tracing::error!("Error handling connection: {}", e);
}
}

View file

@ -6,7 +6,7 @@ pub enum PathResolutionError {
NotFound,
}
pub fn parse_gemini_url(request: &str, expected_host: &str, expected_port: u16) -> Result<String, ()> {
pub fn parse_gemini_url(request: &str, hostname: &str, expected_port: u16) -> Result<String, ()> {
if let Some(url) = request.strip_prefix("gemini://") {
let host_port_end = url.find('/').unwrap_or(url.len());
let host_port = &url[..host_port_end];
@ -21,7 +21,7 @@ pub fn parse_gemini_url(request: &str, expected_host: &str, expected_port: u16)
};
// Validate host
if host != expected_host {
if host != hostname {
return Err(()); // Hostname mismatch
}

View file

@ -40,7 +40,7 @@ pub async fn serve_file(
pub async fn handle_connection(
mut stream: TlsStream<TcpStream>,
dir: &str,
expected_host: &str,
hostname: &str,
expected_port: u16,
max_concurrent_requests: usize,
_test_processing_delay: u64,
@ -97,7 +97,7 @@ pub async fn handle_connection(
}
// Parse Gemini URL
let path = match parse_gemini_url(&request, expected_host, expected_port) {
let path = match parse_gemini_url(&request, hostname, expected_port) {
Ok(p) => p,
Err(_) => {
logger.log_error(59, "Invalid URL format");