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, 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) { let mut buffer = [0; 1024]; match stream.ssl_read(&mut buffer) { Ok(s) => { if s == 0 || s > 1025 { 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(line: &[u8], stream: &mut BufWriter) -> Result<(), Error> { stream.write(line)?; stream.write(b"\r\n")?; Ok(()) } fn handle_response(config: &ServerConfig, url: Url, mut stream: &mut SslStream) { 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; } } } }