use std::fs::{self, DirEntry}; use std::io::Write; use std::net::TcpStream; use std::path::{Path, PathBuf}; use std::sync::Arc; use std::{io::Read, net::TcpListener}; use rustls::ServerConnection; use rustls::{ ServerConfig, Stream, pki_types::{CertificateDer, PrivateKeyDer, pem::PemObject}, }; fn main() { println!("Starting MorGemTS Server..."); let root: PathBuf = fs::canonicalize("content").unwrap(); let certs = CertificateDer::pem_file_iter("cert/morgemrs.crt") .unwrap() .map(|cert| cert.unwrap()) .collect(); let private_key = PrivateKeyDer::from_pem_file("cert/morgemrs.key").unwrap(); let config = Arc::new( ServerConfig::builder() .with_no_client_auth() .with_single_cert(certs, private_key) .unwrap(), ); let listener = TcpListener::bind("127.0.0.1:1965").unwrap(); for mut tcp_stream in listener.incoming().flatten() { let mut conn = ServerConnection::new(config.clone()).unwrap(); let mut tls_stream = Stream::new(&mut conn, &mut tcp_stream); let mut buffer = [0; 1027]; // 1024 URI bytes + 2 CRLF bytes + 1 overflow byte let Ok(n) = tls_stream.read(&mut buffer[..]) else { let _ = writeln!(tls_stream, "59\r\n"); continue; }; if n == 1027 { let _ = writeln!(tls_stream, "59\r\n"); continue; } let Ok(raw_uri) = String::from_utf8(buffer.to_vec()) else { let _ = writeln!(tls_stream, "59\r\n"); continue; }; let Some(mut uri) = raw_uri .trim_end_matches(|c: char| c == char::from(0) || c.is_whitespace()) .strip_prefix("gemini://localhost") else { let _ = writeln!(tls_stream, "53 Proxy Not Supported\r\n"); continue; }; uri = uri.trim_matches('/'); let Ok(path) = root.join(uri).canonicalize() else { let _ = writeln!(tls_stream, "51 Not Found\r\n"); continue; }; if !path.starts_with(&root) { let _ = writeln!(tls_stream, "51 Not Found\rpath\n"); continue; } if path.is_dir() { serve_dir(&path, uri, &mut tls_stream); } else if path.is_file() { serve_file(&path, &mut tls_stream); } else { let _ = writeln!(tls_stream, "51 Not Found\r\n"); } } } fn serve_dir(dir: &Path, uri: &str, tls_stream: &mut Stream<'_, ServerConnection, TcpStream>) { let index_path = dir.join("index.gem"); if index_path.is_file() { serve_file(&index_path, tls_stream); return; } let Ok(read_dir) = dir.read_dir() else { let _ = writeln!(tls_stream, "50 Server Error\r\n"); return; }; let contents: Vec = read_dir.flatten().collect(); let content_string = contents .iter() .flat_map(|f| f.file_name().into_string()) .map(|s| { if uri.is_empty() { format!("=> /{}", s) } else { format!("=> /{}/{} ", uri, s) } }) .collect::>() .join("\r\n"); let _ = writeln!(tls_stream, "20 text/gemini\r\n{}", content_string); } fn serve_file(file: &Path, tls_stream: &mut Stream<'_, ServerConnection, TcpStream>) { let Ok(contents) = fs::read_to_string(file) else { let _ = writeln!(tls_stream, "50 Server Error\r\n"); return; }; let _ = writeln!(tls_stream, "20 text/gemini\r\n{}", contents); }