From dc5cce23e9b0869455f546d968052bc200dd0011 Mon Sep 17 00:00:00 2001 From: Benjamin Morrison Date: Tue, 20 Jun 2023 23:29:12 -0400 Subject: init --- src/handlers.rs | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 150 insertions(+) create mode 100644 src/handlers.rs (limited to 'src/handlers.rs') diff --git a/src/handlers.rs b/src/handlers.rs new file mode 100644 index 0000000..826523e --- /dev/null +++ b/src/handlers.rs @@ -0,0 +1,150 @@ +/* Copyright (C) 2023 Ben Morrison + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +use std::net::SocketAddr; +use std::str; + +use tokio::io::AsyncReadExt; +use tokio::io::AsyncWriteExt; +use tokio::net::TcpStream; +use tokio_rustls::server::TlsStream; +use url::Url; + +use crate::conf::Conf; +use crate::err::Supernova; +use crate::file; +use crate::response; + +pub async fn flush_and_kill(stream: &mut TlsStream, remote_address: SocketAddr) { + if let Err(e) = stream.flush().await { + log::error!("Could not flush writer to {}: {}", remote_address, e); + }; + if let Err(e) = stream.shutdown().await { + log::error!( + "Could not shut down connection to {}: {}", + remote_address, + e + ); + }; +} + +pub async fn entrance( + stream: &mut TlsStream, + remote_address: SocketAddr, +) -> Result { + let mut req_buf: [u8; 1024] = [0; 1024]; + + let n = match stream.read(&mut req_buf).await { + Ok(n) => n, + Err(e) => { + let msg = format!("failed to read from socket: {}", e); + return Err(Supernova::boom(&msg)); + } + }; + + let req_str = match str::from_utf8(&req_buf[..n - 1]) { + Ok(v) => v, + Err(e) => { + let msg = format!("failed to parse request as UTF-8 string: {}", e); + return Err(Supernova::boom(&msg).with_code(response::Code::BadRequest)); + } + }; + + log::info!("REQ {} :: {}", remote_address, req_str); + + if req_str.contains("../") || req_str.contains("/..") { + let msg = format!("directory traversal attempted: {}", req_str); + return Err(Supernova::boom(&msg).with_code(response::Code::BadRequest)); + }; + + let url = match Url::parse(req_str) { + Ok(v) => v, + Err(e) => { + let msg = format!("could not parse request as URL: {}", e); + return Err(Supernova::boom(&msg).with_code(response::Code::BadRequest)); + } + }; + + let url_scheme = url.scheme(); + if url_scheme != "gemini" { + let msg = format!("invalid URL scheme. refusing to proxy to: {}", url_scheme); + return Err(Supernova::boom(&msg).with_code(response::Code::ProxyRequestRefused)); + } + + Ok(url) +} + +pub async fn route( + conf: &Conf, + stream: &mut TlsStream, + remote_address: SocketAddr, + req_url: Url, +) -> Result<(), Supernova> { + let path = req_url.path(); + let root_directory = conf.root_directory(); + let root_directory_str = root_directory.display(); + + let fixed_path = if path.is_empty() { + format!("{}/{}", root_directory_str, conf.index_file_name()) + } else { + format!("{}{}", root_directory_str, path) + }; + + log::debug!( + "REQ {} :: full local request path: {}", + remote_address, + fixed_path + ); + + let (mut fd, mime) = file::get(&fixed_path).await?; + + let header = response::Code::Success.get_header(&mime); + + log::debug!( + "REQ {} :: file {} has mime {}", + remote_address, + fixed_path, + mime + ); + + let n = match stream.write(&header).await { + Ok(n) => n, + Err(e) => { + let msg = format!("could not write header to tls socket: {}", e); + return Err(Supernova::boom(&msg)); + } + }; + + let n = match tokio::io::copy(&mut fd, stream).await { + Ok(v) => v as usize + n, + Err(e) => { + let msg = format!("could not write body to tls socket: {}", e); + return Err(Supernova::boom(&msg)); + } + }; + + let bytes_written = match stream.write(response::footer_bytes()).await { + Ok(v) => v + n, + Err(e) => { + let msg = format!("could not write footer to tls socket: {}", e); + return Err(Supernova::boom(&msg)); + } + }; + + log::info!("REQ {} :: {} bytes written", remote_address, bytes_written); + + Ok(()) +} -- cgit 1.4.1