summaryrefslogtreecommitdiff
path: root/src/main.rs
blob: c2b4509d37e3d2e5ec00d6939cfa08c6934b96e6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
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);
}