summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorMorgana <Morgana@icolotl.com>2026-10-23 11:19:55 -0500
committerMorgana <Morgana@icolotl.com>2026-10-23 11:19:55 -0500
commitf941e853103089897127fb22b28cc2e7eb8e556f (patch)
tree741629e5caad37ebf219b184f86f2abe6ac588bd /src
Initial Commit
Diffstat (limited to 'src')
-rw-r--r--src/main.rs103
1 files changed, 103 insertions, 0 deletions
diff --git a/src/main.rs b/src/main.rs
new file mode 100644
index 0000000..c2b4509
--- /dev/null
+++ b/src/main.rs
@@ -0,0 +1,103 @@
+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<DirEntry> = 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::<Vec<String>>()
+ .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);
+}