summaryrefslogtreecommitdiff
path: root/src/server/handler.rs
blob: f32a0158dd66080fea223b82aa8c9cb597370531 (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
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
use crate::mime;
use crate::server::response;
use crate::server::ServerConfig;
use openssl::ssl::SslStream;
use std::fs::File;
use std::io::{copy, BufRead, BufReader, BufWriter, Error, Write};
use std::net::TcpStream;
use std::path::{Path, PathBuf};
use url::Url;

fn send_header(stream: &mut SslStream<TcpStream>, header: &response::Header) {
    match stream.ssl_write(&header.to_vec()) {
        Ok(_s) => {
            return;
        }
        Err(_e) => {
            return;
        }
    };
}

pub fn handle_request(config: &ServerConfig, mut stream: SslStream<TcpStream>) {
    let mut buffer = [0; 1026];
    match stream.ssl_read(&mut buffer) {
        Ok(s) => {
            if s == 0 {
                println!("received empty request buffer");
                send_header(&mut stream, &response::bad_request());
                return;
            }
        }
        Err(_) => {
            println!("received broken request");
            send_header(&mut stream, &response::bad_request());
            return;
        }
    };

    let request = match String::from_utf8(buffer.to_vec()) {
        Ok(request) => request,
        Err(_) => {
            println!("received empty request string");
            send_header(&mut stream, &response::bad_request());
            return;
        }
    };

    let location = match Url::parse(&request) {
        Ok(url) => url,
        Err(_) => config.default_host.join(&request).unwrap(),
    };

    handle_response(config, location, &mut stream);
}

fn gen_path_index(path: &Path) -> PathBuf {
    match path.is_dir() {
        true => path.join("index.gmi"),
        false => PathBuf::from(path),
    }
}

fn write_line<T: Write>(line: &[u8], stream: &mut BufWriter<T>) -> Result<(), Error> {
    stream.write(line)?;
    stream.write(b"\r\n")?;
    Ok(())
}

fn handle_response(config: &ServerConfig, url: Url, mut stream: &mut SslStream<TcpStream>) {
    println!("responding for: {}", url);

    if url.scheme() != "gemini" {
        send_header(&mut stream, &response::permanent_failure());
        return;
    }

    if url.host() != config.default_host.host() {
        send_header(&mut stream, &response::proxy_request_refused());
        return;
    }

    let rel_path = match Path::new(url.path()).strip_prefix("/") {
        Ok(path) => path,
        Err(_) => {
            Path::new("")
        }
    };

    let path = gen_path_index(&config.gem_root.join(rel_path));

    let file = match File::open(&path) {
        Ok(file) => file,
        Err(_) => {
            println!("not found: {:?}", path);
            send_header(&mut stream, &response::not_found());
            return;
        }
    };
    println!("sending file: {:?}", path);

    let mime_type = match path.extension() {
        Some(ext) => mime::get_mime_type(ext),
        None => mime::default_mime_type(),
    };
    let header = response::Header::new(response::Status::Success, mime_type);
    send_header(&mut stream, &header);

    let mut buf_file = BufReader::new(file);
    let mut buf_stream = BufWriter::new(stream);

    if mime_type.starts_with("text/") {
        // We make sure to send all text/* files with correct CL/RF line endings
        for line in buf_file.lines() {
            match write_line(line.unwrap().as_bytes(), &mut buf_stream) {
                Ok(_) => {}
                Err(e) => {
                    println!("error while sending text file: {:?}\n  {}", path, e);
                    return;
                }
            }
        }
    } else {
        // Any other MIME type can just be copied as-is
        match copy(&mut buf_file, &mut buf_stream) {
            Ok(_) => {}
            Err(e) => {
                println!("error while sending file: {:?}\n  {}", path, e);
                return;
            }
        }
    }
}