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
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
|
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; 1025];
match stream.ssl_read(&mut buffer) {
Ok(s) => {
if s == 0 || s > 1024 {
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(_) => {
println!("received invalid request url");
send_header(&mut stream, &response::bad_request());
return;
},
};
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);
// url scheme must be either "gemini://" or "//" (empty)
if url.scheme() != "gemini" && url.scheme() != "" {
send_header(&mut stream, &response::proxy_request_refused());
return;
}
// url request host must match
if url.host() != config.default_host.host() {
send_header(&mut stream, &response::proxy_request_refused());
return;
}
// TODO: also drop on incorrect port in request
let rel_path = match Path::new(url.path()).strip_prefix("/") {
Ok(path) => path,
Err(_) => {
// empty path, gemini spec says to redirect client to root
send_header(&mut stream, &response::redirect_permanent(
config.default_host.as_str(),
));
return;
}
};
let path = gen_path_index(&config.gem_root.join(rel_path));
// make sure we can't escape gem_root
if !path.starts_with(&config.gem_root) {
send_header(&mut stream, &response::bad_request());
return;
}
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;
}
}
}
}
|