diff --git a/src/main.rs b/src/main.rs index 9e80ecb..6880cab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,7 +3,7 @@ mod storage; use anyhow::Result; use argument::Argument; -use clap::Parser; + use native_tls::{Identity, TlsAcceptor, TlsStream}; use std::{ fs::File, @@ -15,6 +15,8 @@ use std::{ }; fn main() -> Result<()> { + use clap::Parser; + let argument = Arc::new(Argument::parse()); // https://geminiprotocol.net/docs/protocol-specification.gmi#the-use-of-tls @@ -42,7 +44,7 @@ fn main() -> Result<()> { move || handle(argument, peer, &mut stream) }); } - Err(e) => println!("[{}] [error] Failed to accept connection: {e}", now()), + Err(e) => println!("[{}] [error] {e}", now()), } } Ok(()) @@ -51,220 +53,66 @@ fn main() -> Result<()> { fn handle(argument: Arc, peer: SocketAddr, stream: &mut TlsStream) { use titanite::*; println!("[{}] [info] [{peer}] New connection", now()); - // validate totals - let mut total = 0; - // read header bytes - let mut input = vec![0; 1024]; + let mut input = vec![0; titanite::HEADER_MAX_LEN]; match stream.read(&mut input) { Ok(0) => println!("[{}] [warning] [{peer}] Connection closed by peer", now()), - Ok(l) => { - match Request::from_bytes(&input[..l]) { - Ok(request) => { - match request { - Request::Gemini(gemini) => { - match storage::Item::from_url(&gemini.url.as_str(), &argument.directory) - { - Ok(item) => send( - &response::success::Default { - mime: "text/gemini".to_string(), - //data: item.file.read(vec![1000]), - } - .into_bytes(), - stream, - |result| match result { - Ok(()) => println!( - "[{}] [info] [{peer}] Request: {}", - now(), - gemini.url - ), - Err(e) => println!("[{}] [error] [{peer}] {e}", now()), - }, - ), - Err(e) => send( - &response::failure::permanent::NotFound { - message: Some("Not found".to_string()), - } - .into_bytes(), - stream, - |result| match result { - Ok(()) => println!("[{}] [error] [{peer}] {e}", now()), - Err(e) => println!("[{}] [error] [{peer}] {e}", now()), - }, - ), - } - } - Request::Titan(titan) => match storage::Item::create(&argument.directory) { - Ok(mut tmp) => loop { - let mut input = vec![0; argument.chunk]; - match stream.read(&mut input) { - Ok(0) => println!( - "[{}] [warning] [{peer}] Connection closed by peer", - now() - ), - Ok(l) => match tmp.file.write(&input[..l]) { - Ok(s) => { - total += s; - if s != l { - todo!() - } - // validate server-side limits - if argument.size.is_some_and(|limit| total > limit) { - const MESSAGE: &str = - "Allowed max length limit reached"; - return send( - &response::failure::permanent::BadRequest { - message: Some(MESSAGE.to_string()), - } - .into_bytes(), - stream, - |result| match result { - Ok(()) => println!( - "[{}] [warning] [{peer}] {MESSAGE}", - now() - ), - Err(e) => { - println!( - "[{}] [error] [{peer}] {e}", - now() - ) - } - }, - ); - } - // all expected data received - if titan.size >= total { - // validate client-side limits - if titan.size > total { - const MESSAGE: &str = - "Data size mismatch header declaration"; - return send( - &response::failure::permanent::BadRequest { - message: Some(MESSAGE.to_string()), - } - .into_bytes(), - stream, - |result| match result { - Ok(()) => { - println!( - "[{}] [warning] [{peer}] {MESSAGE}", - now() - ) - } - Err(e) => println!( - "[{}] [error] [{peer}] {e}", - now() - ), - }, - ); - } - // @TODO detect/validate/cache mime based on data received - // success - match tmp.commit() { - Ok(pmt) => send( - &response::redirect::Permanent { - target: match argument.redirect { - Some(ref target) => format!( - "{}/{}", - target.trim_end_matches("/"), - pmt.to_uri(&argument.directory) - ), - None => format!( - "gemini://{}/{}", - argument.bind, - pmt.to_uri(&argument.directory) - ), - }, - } - .into_bytes(), - stream, - |result| { - match result { - Ok(()) => println!( - "[{}] [info] [{peer}] Data saved to {}", - now(), - pmt.path.to_string_lossy() - ), - Err(e) => println!( - "[{}] [warning] [{peer}] {e}", - now() - ), - } - }, - ), - Err((tmp, e)) => send( - &response::failure::temporary::General { - message: Some( - "Internal server error".to_string(), - ), - } - .into_bytes(), - stream, - |result| { - match result { - Ok(()) => println!( - "[{}] [error] [{peer}] {e}", - now() - ), - Err(e) => println!( - "[{}] [error] [{peer}] {e}", - now() - ), - }; - if let Err(e) = tmp.delete() { - println!( - "[{}] [error] [{peer}] {e}", - now() - ); - } - }, - ), - } - break; - } - } - Err(e) => todo!(), - }, - Err(e) => send( - &response::failure::temporary::General { - message: Some("Internal server error".to_string()), - } - .into_bytes(), - stream, - |result| match result { - Ok(()) => { - println!("[{}] [error] [{peer}] {e}", now()) - } - Err(e) => { - println!("[{}] [error] [{peer}] {e}", now()) - } - }, - ), - } - }, - - Err(e) => todo!(), - }, - } + Ok(s) => match Request::from_bytes(&input[..s]) { + Ok(request) => match request { + Request::Gemini(this) => handle_gemini(this, argument, peer, stream), + Request::Titan(this) => handle_titan(this, argument, peer, stream), + }, + Err(e) => send( + &response::failure::temporary::General { + message: Some("Internal server error".to_string()), } - Err(e) => send( - &response::failure::temporary::General { - message: Some("Internal server error".to_string()), - } - .into_bytes(), - stream, - |result| match result { - Ok(()) => println!("[{}] [error] [{peer}] {e}", now()), - Err(e) => println!("[{}] [error] [{peer}] {e}", now()), - }, - ), - } - } + .into_bytes(), + stream, + |result| match result { + Ok(()) => println!("[{}] [warning] [{peer}] {e}", now()), + Err(e) => println!("[{}] [error] [{peer}] {e}", now()), + }, + ), + }, Err(e) => send( &response::failure::temporary::General { message: Some("Internal server error".to_string()), } .into_bytes(), stream, + |result| match result { + Ok(()) => println!("[{}] [warning] [{peer}] {e}", now()), + Err(e) => println!("[{}] [error] [{peer}] {e}", now()), + }, + ), + } +} + +fn handle_gemini( + gemini: titanite::request::Gemini, + argument: Arc, + peer: SocketAddr, + stream: &mut TlsStream, +) { + use titanite::*; + match storage::Item::from_url(&gemini.url.as_str(), &argument.directory) { + Ok(item) => send( + &response::success::Default { + mime: "text/gemini".to_string(), + //data: item.file.read(vec![1000]), + } + .into_bytes(), + stream, + |result| match result { + Ok(()) => println!("[{}] [info] [{peer}] Request: {}", now(), gemini.url), + Err(e) => println!("[{}] [error] [{peer}] {e}", now()), + }, + ), + Err(e) => send( + &response::failure::permanent::NotFound { + message: Some("Not found".to_string()), + } + .into_bytes(), + stream, |result| match result { Ok(()) => println!("[{}] [error] [{peer}] {e}", now()), Err(e) => println!("[{}] [error] [{peer}] {e}", now()), @@ -273,6 +121,159 @@ fn handle(argument: Arc, peer: SocketAddr, stream: &mut TlsStream, + peer: SocketAddr, + stream: &mut TlsStream, +) { + use titanite::*; + let mut total = 0; + match storage::Item::create(&argument.directory) { + Ok(mut tmp) => loop { + let mut input = vec![0; argument.chunk]; + match stream.read(&mut input) { + Ok(0) => { + println!("[{}] [warning] [{peer}] Connection closed by peer", now()); + if let Err(e) = tmp.delete() { + println!("[{}] [error] [{peer}] {e}", now()); + } + break; + } + Ok(l) => match tmp.file.write(&input[..l]) { + Ok(s) => { + total += s; + if s != l { + if let Err(e) = tmp.delete() { + println!("[{}] [error] [{peer}] {e}", now()); + } + const MESSAGE: &str = "File sizes mismatch"; + return send( + &response::failure::temporary::General { + message: Some(MESSAGE.to_string()), + } + .into_bytes(), + stream, + |result| match result { + Ok(()) => println!("[{}] [error] [{peer}] {MESSAGE}", now()), + Err(e) => { + println!("[{}] [error] [{peer}] {e}", now()) + } + }, + ); + } + // validate server-side limits + if argument.size.is_some_and(|limit| total > limit) { + if let Err(e) = tmp.delete() { + println!("[{}] [error] [{peer}] {e}", now()); + } + const MESSAGE: &str = "Allowed max length limit reached"; + return send( + &response::failure::permanent::BadRequest { + message: Some(MESSAGE.to_string()), + } + .into_bytes(), + stream, + |result| match result { + Ok(()) => println!("[{}] [warning] [{peer}] {MESSAGE}", now()), + Err(e) => { + println!("[{}] [error] [{peer}] {e}", now()) + } + }, + ); + } + // all expected data received + if titan.size >= total { + // validate client-side limits + if titan.size > total { + if let Err(e) = tmp.delete() { + println!("[{}] [error] [{peer}] {e}", now()); + } + const MESSAGE: &str = "Data size mismatch header declaration"; + return send( + &response::failure::permanent::BadRequest { + message: Some(MESSAGE.to_string()), + } + .into_bytes(), + stream, + |result| match result { + Ok(()) => { + println!("[{}] [warning] [{peer}] {MESSAGE}", now()) + } + Err(e) => println!("[{}] [error] [{peer}] {e}", now()), + }, + ); + } + // @TODO detect/validate/cache mime based on data received + // success + match tmp.commit() { + Ok(pmt) => send( + &response::redirect::Permanent { + target: match argument.redirect { + Some(ref target) => format!( + "{}/{}", + target.trim_end_matches("/"), + pmt.to_uri(&argument.directory) + ), + None => format!( + "gemini://{}/{}", + argument.bind, + pmt.to_uri(&argument.directory) + ), + }, + } + .into_bytes(), + stream, + |result| match result { + Ok(()) => println!( + "[{}] [info] [{peer}] Data saved to {}", + now(), + pmt.path.to_string_lossy() + ), + Err(e) => println!("[{}] [warning] [{peer}] {e}", now()), + }, + ), + Err((tmp, e)) => send( + &response::failure::temporary::General { + message: Some("Internal server error".to_string()), + } + .into_bytes(), + stream, + |result| { + match result { + Ok(()) => { + println!("[{}] [warning] [{peer}] {e}", now()) + } + Err(e) => println!("[{}] [error] [{peer}] {e}", now()), + }; + if let Err(e) = tmp.delete() { + println!("[{}] [error] [{peer}] {e}", now()); + } + }, + ), + } + break; + } + } + Err(e) => todo!(), + }, + Err(e) => send( + &response::failure::temporary::General { + message: Some("Internal server error".to_string()), + } + .into_bytes(), + stream, + |result| match result { + Ok(()) => println!("[{}] [warning] [{peer}] {e}", now()), + Err(e) => println!("[{}] [error] [{peer}] {e}", now()), + }, + ), + } + }, + Err(e) => todo!(), + } +} + fn send(data: &[u8], stream: &mut TlsStream, callback: impl FnOnce(Result<()>)) { callback((|| { stream.write_all(data)?;