From 00906cf7f561d4993de48add74d3961cb41f1982 Mon Sep 17 00:00:00 2001 From: dvkt Date: Fri, 27 Dec 2019 15:42:57 -0800 Subject: [PATCH] lean on GopherMap --- Cargo.lock | 7 ++ Cargo.toml | 3 +- src/main.rs | 16 ++-- src/server.rs | 219 +++++++++++++++++++++++++++----------------------- 4 files changed, 136 insertions(+), 109 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 265c8ee..2ffe8a7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -8,6 +8,11 @@ dependencies = [ "memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)", ] +[[package]] +name = "gophermap" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" + [[package]] name = "hermit-abi" version = "0.1.5" @@ -40,6 +45,7 @@ name = "phd" version = "0.1.0" dependencies = [ "content_inspector 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)", + "gophermap 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)", "threadpool 1.7.1 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -53,6 +59,7 @@ dependencies = [ [metadata] "checksum content_inspector 0.2.4 (registry+https://github.com/rust-lang/crates.io-index)" = "b7bda66e858c683005a53a9a60c69a4aca7eeaa45d124526e389f7aec8e62f38" +"checksum gophermap 0.1.2 (registry+https://github.com/rust-lang/crates.io-index)" = "6ec2186bfad5a5dcbc9307dbc2d2444062300a836ae91b00dd80c3b71c34af3b" "checksum hermit-abi 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "f629dc602392d3ec14bfc8a09b5e644d7ffd725102b48b81e59f90f2633621d7" "checksum libc 0.2.66 (registry+https://github.com/rust-lang/crates.io-index)" = "d515b1f41455adea1313a4a2ac8a8a477634fbae63cc6100e3aebb207ce61558" "checksum memchr 2.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "88579771288728879b57485cc7d6b07d648c9f0141eb955f8ab7f9d45394468e" diff --git a/Cargo.toml b/Cargo.toml index 1caffe5..5376b50 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -6,4 +6,5 @@ edition = "2018" [dependencies] content_inspector = "0.2.4" -threadpool = "1.7.1" \ No newline at end of file +threadpool = "1.7.1" +gophermap = "0.1.2" \ No newline at end of file diff --git a/src/main.rs b/src/main.rs index 264d73e..0be23c4 100644 --- a/src/main.rs +++ b/src/main.rs @@ -8,22 +8,22 @@ fn main() { return; } - let mut root = ".".to_string(); + let mut root = "."; let mut iter = args.iter(); - let mut host = "localhost".to_string(); - let mut port = "70".to_string(); + let mut host = "localhost"; + let mut port = "70"; while let Some(arg) = iter.next() { match arg.as_ref() { "--version" | "-v" | "-version" => return print_version(), "--help" | "-h" | "-help" => return print_help(), "--port" | "-p" | "-port" => { if let Some(p) = iter.next() { - port = p.into(); + port = p; } } "--host" | "-H" | "-host" => { if let Some(h) = iter.next() { - host = h.into(); + host = h; } } _ => { @@ -31,15 +31,13 @@ fn main() { eprintln!("unknown flag: {}", arg); process::exit(1); } else { - root = arg.clone(); + root = arg; } } } } - let addr = format!("{}:{}", host, port); - println!("-> Serving {} on {}", root, addr); - if let Err(e) = phd::server::start(&addr, &root) { + if let Err(e) = phd::server::start(host, port, root) { eprintln!("{}", e); } } diff --git a/src/server.rs b/src/server.rs index 74e8b5b..5f03f43 100644 --- a/src/server.rs +++ b/src/server.rs @@ -1,30 +1,62 @@ +use gophermap::{GopherMenu, ItemType}; use std::{ fs, io::prelude::*, io::{BufReader, Read, Write}, - net::{TcpListener, TcpStream, ToSocketAddrs}, + net::{TcpListener, TcpStream}, path::PathBuf, }; use threadpool::ThreadPool; -type Result = std::result::Result>; - +const MAX_WORKERS: usize = 10; const MAX_PEEK_SIZE: usize = 1024; -#[derive(Default)] +pub type Result = std::result::Result>; + pub struct Request { - selector: String, // client info - root: String, // server info + stream: TcpStream, + selector: String, + root: String, host: String, port: String, } +/// Starts a Gopher server at the specified root directory. +pub fn start(host: &str, port: &str, root_dir: &str) -> Result<()> { + let addr = format!("{}:{}", host, port); + let listener = TcpListener::bind(&addr)?; + println!("-> Listening at {}", addr); + let pool = ThreadPool::new(MAX_WORKERS); + for stream in listener.incoming() { + let stream = stream?; + println!("-> Connection from: {}", stream.peer_addr()?); + let mut req = Request::from( + stream, + root_dir.to_string(), + host.to_string(), + port.to_string(), + ); + pool.execute(move || { + if let Err(e) = req.serve() { + eprintln!("-> {}", e); + } + }); + } + Ok(()) +} + impl Request { - pub fn new() -> Request { - Default::default() + pub fn from(stream: TcpStream, root: String, host: String, port: String) -> Request { + Request { + stream, + root, + host, + port, + selector: String::new(), + } } - pub fn root_path_string(&self) -> Result { + pub fn root_path_as_string(&self) -> Result { Ok(fs::canonicalize(&self.root)?.to_string_lossy().to_string()) } @@ -34,114 +66,103 @@ impl Request { Ok(path) } - pub fn path_string(&self) -> Result { - let path = self.path()?; - Ok(path.to_string_lossy().to_string()) + pub fn path_as_string(&self) -> Result { + let mut path = self + .path()? + .to_string_lossy() + .to_string() + .replace(&self.root_path_as_string()?, ""); + if !path.ends_with('/') { + path.push('/'); + } + Ok(path) } -} -pub fn start(addr: impl ToSocketAddrs, root: &str) -> Result<()> { - let listener = TcpListener::bind(addr)?; - let pool = ThreadPool::new(4); - let local_addr = listener.local_addr()?; - let host = local_addr.ip(); - let port = local_addr.port(); - for stream in listener.incoming() { - let stream = stream?; - println!("-> Connection from: {}", stream.peer_addr()?); - let req = Request { - root: root.to_string(), - host: host.to_string(), - port: port.to_string(), - ..Default::default() - }; - pool.execute(|| { - if let Err(e) = client_loop(stream, req) { - eprintln!("-> {}", e); - } - }); + /// Reads from the client and responds. + fn serve(&mut self) -> Result<()> { + let reader = BufReader::new(&self.stream); + let mut lines = reader.lines(); + if let Some(Ok(line)) = lines.next() { + println!("-> Received: {:?}", line); + self.selector = line; + self.respond()?; + } + Ok(()) } - Ok(()) -} -fn client_loop(stream: TcpStream, mut req: Request) -> Result<()> { - let reader = BufReader::new(&stream); - let mut lines = reader.lines(); - if let Some(Ok(line)) = lines.next() { - println!("-> client sent: {:?}", line); - req.selector = line; - respond(stream, req)?; + /// Respond to a client's request. + fn respond(&mut self) -> Result<()> { + let md = fs::metadata(self.path()?)?; + if md.is_file() { + self.send_text() + } else if md.is_dir() { + self.send_dir() + } else { + Ok(()) + } } - Ok(()) -} -fn respond(stream: TcpStream, req: Request) -> Result<()> { - let md = fs::metadata(req.path()?)?; - if md.is_file() { - send_text(stream, req) - } else if md.is_dir() { - send_dir(stream, req) - } else { + /// Send a directory listing (menu) to the client. + fn send_dir(&mut self) -> Result<()> { + let mut dir = fs::read_dir(self.path()?)?; + let mut menu = GopherMenu::with_write(&self.stream); + let path = self.path_as_string()?; + while let Some(Ok(entry)) = dir.next() { + let mut path = path.clone(); + let file_name = entry.file_name(); + path.push_str(&file_name.to_string_lossy()); + menu.write_entry( + file_type(&entry), + &file_name.to_string_lossy(), + &path, + &self.host, + self.port.parse()?, + )?; + } + menu.end()?; Ok(()) } -} -fn send_dir(mut stream: TcpStream, req: Request) -> Result<()> { - let mut response = String::new(); - let mut dir = fs::read_dir(req.path()?)?; - let mut path = req.path_string()?.replace(&req.root_path_string()?, ""); - if !path.ends_with('/') { - path.push('/'); - } - while let Some(Ok(entry)) = dir.next() { - let file_type = file_type(&entry); - let f = entry.file_name(); - let file_name = f.to_string_lossy(); - response.push_str(&format!( - "{}{}\t{}{}\tlocalhost\t7070\r\n", - file_type, file_name, path, file_name, - )); + /// Send a text document to the client. + fn send_text(&mut self) -> Result<()> { + let path = self.path()?; + let md = fs::metadata(&path)?; + let mut f = fs::File::open(&path)?; + let mut buf = [0; 1024]; + let mut bytes = md.len(); + while bytes > 0 { + let n = f.read(&mut buf[..])?; + bytes -= n as u64; + self.stream.write_all(&buf[..n])?; + } + self.stream.write_all(b"\r\n.\r\n")?; // end gopher response + Ok(()) } - stream.write_all(response.as_bytes())?; - stream.write_all(b"\r\n.\r\n")?; // end gopher response - Ok(()) } -fn send_text(mut stream: TcpStream, req: Request) -> Result<()> { - let path = req.path()?; - let md = fs::metadata(&path)?; - let mut f = fs::File::open(&path)?; - let mut buf = [0; 1024]; - let mut bytes = md.len(); - while bytes > 0 { - let n = f.read(&mut buf[..])?; - bytes -= n as u64; - stream.write_all(&buf)?; +/// Determine the gopher type for a DirEntry on disk. +fn file_type(dir: &fs::DirEntry) -> ItemType { + let metadata = dir.metadata(); + if metadata.is_err() { + return ItemType::Error; } - stream.write_all(b"\r\n.\r\n")?; // end gopher response - Ok(()) -} + let metadata = metadata.unwrap(); -fn file_type(dir: &fs::DirEntry) -> char { - if let Ok(metadata) = dir.metadata() { - if metadata.is_file() { - if let Ok(file) = fs::File::open(&dir.path()) { - let mut buffer: Vec = vec![]; - let _ = file.take(MAX_PEEK_SIZE as u64).read_to_end(&mut buffer); - if content_inspector::inspect(&buffer).is_binary() { - '9' - } else { - '0' - } + if metadata.is_file() { + if let Ok(file) = fs::File::open(&dir.path()) { + let mut buffer: Vec = vec![]; + let _ = file.take(MAX_PEEK_SIZE as u64).read_to_end(&mut buffer); + if content_inspector::inspect(&buffer).is_binary() { + ItemType::Binary } else { - '9' + ItemType::File } - } else if metadata.is_dir() { - '1' } else { - '3' + ItemType::Binary } + } else if metadata.is_dir() { + ItemType::Directory } else { - '3' + ItemType::Error } }