handle errors

This commit is contained in:
yggverse 2025-02-22 00:33:33 +02:00
parent 08c437920c
commit 04a276116d
2 changed files with 121 additions and 77 deletions

View file

@ -13,7 +13,6 @@ use std::{
thread, thread,
time::{SystemTime, UNIX_EPOCH}, time::{SystemTime, UNIX_EPOCH},
}; };
use titanite::response::success::Default;
fn main() -> Result<()> { fn main() -> Result<()> {
let argument = Arc::new(Argument::parse()); let argument = Arc::new(Argument::parse());
@ -50,95 +49,134 @@ fn main() -> Result<()> {
} }
fn handle(argument: Arc<Argument>, peer: SocketAddr, mut stream: TlsStream<TcpStream>) { fn handle(argument: Arc<Argument>, peer: SocketAddr, mut stream: TlsStream<TcpStream>) {
use titanite::{request::Titan, response::*}; use titanite::*;
println!("[{}] [info] [{peer}] New connection", now()); println!("[{}] [info] [{peer}] New connection", now());
// read header bytes // read header bytes
let mut input = vec![0; 1024]; let mut input = vec![0; 1024];
match stream.read(&mut input) { match stream.read(&mut input) {
Ok(0) => todo!("Canceled"), Ok(0) => println!("[{}] [warning] [{peer}] Connection closed by peer", now()),
Ok(l) => { Ok(l) => match request::Titan::from_bytes(&input[..l]) {
match Titan::from_bytes(&input[..l]) { Ok(titan) => {
Ok(titan) => { // init memory pool
// init memory pool let mut data: Vec<u8> = Vec::with_capacity(titan.size);
let mut data: Vec<u8> = Vec::with_capacity(titan.size); loop {
loop { // read data bytes
// read data bytes let mut input = vec![0; argument.chunk];
let mut input = vec![0; argument.chunk]; match stream.read(&mut input) {
match stream.read(&mut input) { Ok(0) => {
Ok(0) => { println!("[{}] [warning] [{peer}] Connection closed by peer", now())
todo!("Canceled") }
} Ok(l) => {
Ok(l) => { data.extend(&input[..l]);
data.extend(&input[..l]);
let total = data.len(); // calculate once
if argument.size.is_some_and(|limit| total > limit) { let total = data.len();
todo!("Allowed max length limit reached")
} // validate server-side limits
if titan.size >= total { if argument.size.is_some_and(|limit| total > limit) {
if titan.size > total { const MESSAGE: &str = "Allowed max length limit reached";
println!( return send(
"[{}] [warning] [{peer}] Data size mismatch header declaration", &response::failure::permanent::BadRequest {
now() message: Some(MESSAGE.to_string()),
);
return;
} }
// success .into_bytes(),
match storage::Item::create(&argument.directory) { stream,
Ok(mut tmp) => match tmp.file.write(titan.data) { |result| match result {
Ok(_) => match &stream.write_all( Ok(()) => {
&Success::Default(Default { println!("[{}] [warning] [{peer}] {MESSAGE}", now())
mime: "text/gemini".to_string(), }
}) Err(e) => println!("[{}] [error] [{peer}] {e}", now()),
.into_bytes(), },
) { );
// @TODO detect/validate/cache mime based on data received }
Ok(()) => match tmp.commit() {
Ok(path) => match stream.flush() { // all expected data received
// Close connection gracefully if titan.size >= total {
// https://geminiprotocol.net/docs/protocol-specification.gmi#closing-connections // validate client-side limits
Ok(()) => match stream.shutdown() { if titan.size > total {
Ok(()) => println!( const MESSAGE: &str = "Data size mismatch header declaration";
"[{}] [info] [{peer}] Data saved to {path}", return send(
now() &response::failure::permanent::BadRequest {
), message: Some(MESSAGE.to_string()),
Err(e) => println!("[{}] [warning] [{peer}] {e}", now()) }
}, .into_bytes(),
Err(e) => println!("[{}] [warning] [{peer}] {e}", now()) stream,
} |result| match result {
Err(e) => println!("[{}] [warning] [{peer}] {e}", now()) Ok(()) => {
}, println!("[{}] [warning] [{peer}] {MESSAGE}", now())
Err(e) => { }
println!("[{}] [error] [{peer}] {e}", now()); Err(e) => println!("[{}] [error] [{peer}] {e}", now()),
if let Err(e) = tmp.delete() { },
println!("[{}] [error] [{peer}] {e}", now()) );
} }
// @TODO detect/validate/cache mime based on data received
// success
match storage::Item::create(&argument.directory) {
Ok(mut tmp) => match tmp.file.write(titan.data) {
Ok(_) => match tmp.commit() {
Ok(pmt) => send(
&response::redirect::Permanent {
target: pmt.path.to_str().unwrap().to_owned(),
} }
}, .into_bytes(),
Err(e) => { 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)) => {
println!("[{}] [error] [{peer}] {e}", now()); println!("[{}] [error] [{peer}] {e}", now());
if let Err(e) = tmp.delete() { if let Err(e) = tmp.delete() {
println!("[{}] [error] [{peer}] {e}", now()) println!("[{}] [error] [{peer}] {e}", now());
} }
} }
}, },
Err(e) => println!("[{}] [error] [{peer}] {e}", now()), Err(e) => {
} println!("[{}] [error] [{peer}] {e}", now());
break; if let Err(e) = tmp.delete() {
println!("[{}] [error] [{peer}] {e}", now());
}
}
},
Err(e) => println!("[{}] [error] [{peer}] {e}", now()),
} }
break;
} }
Err(e) => todo!("{e}"),
} }
Err(e) => todo!("{e}"),
} }
} }
Err(e) => todo!("{e}"),
} }
} Err(e) => todo!("{e}"),
},
Err(e) => todo!("{e}"), Err(e) => todo!("{e}"),
} }
} }
fn send(data: &[u8], mut stream: TlsStream<TcpStream>, callback: impl FnOnce(Result<()>)) {
callback((|| {
stream.write_all(data)?;
stream.flush()?;
// Close connection gracefully
// https://geminiprotocol.net/docs/protocol-specification.gmi#closing-connections
stream.shutdown()?;
Ok(())
})());
}
fn now() -> u128 { fn now() -> u128 {
SystemTime::now() SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)

View file

@ -1,7 +1,8 @@
use anyhow::{bail, Result}; use anyhow::{anyhow, Error, Result};
use std::{ use std::{
fs::{create_dir_all, remove_file, rename, File}, fs::{create_dir_all, remove_file, rename, File},
path::{PathBuf, MAIN_SEPARATOR}, path::{PathBuf, MAIN_SEPARATOR},
str::FromStr,
thread, thread,
time::{Duration, SystemTime, UNIX_EPOCH}, time::{Duration, SystemTime, UNIX_EPOCH},
}; };
@ -62,21 +63,26 @@ impl Item {
// Actions // Actions
/// Take object processed, commit its changes /// Commit changes, return permanent Self
pub fn commit(self) -> Result<String> { pub fn commit(mut self) -> Result<Self, (Self, Error)> {
match self.path.to_str() { match self.path.to_str() {
Some(old) => match old.strip_suffix(TMP_SUFFIX) { Some(old) => match old.strip_suffix(TMP_SUFFIX) {
Some(new) => { Some(new) => match rename(old, new) {
rename(old, new)?; Ok(()) => {
Ok(new.to_string()) self.path = match PathBuf::from_str(new) {
} Ok(path) => path,
None => bail!("Invalid TMP suffix"), // | panic Err(e) => return Err((self, anyhow!(e))),
};
Ok(self)
}
Err(e) => Err((self, anyhow!(e))),
},
None => Err((self, anyhow!("Unexpected suffix"))),
}, },
None => bail!("Invalid Item path"), // | panic None => Err((self, anyhow!("Unexpected file path"))),
} }
} }
/// Cleanup object relationships
pub fn delete(self) -> Result<()> { pub fn delete(self) -> Result<()> {
Ok(remove_file(self.path)?) Ok(remove_file(self.path)?)
} }