use titanite library for backend

This commit is contained in:
yggverse 2025-02-21 22:18:07 +02:00
parent a4518ba93d
commit 08c437920c
6 changed files with 89 additions and 147 deletions

View file

@ -1,11 +1,9 @@
mod argument;
mod header;
mod storage;
use anyhow::Result;
use argument::Argument;
use clap::Parser;
use header::Header;
use native_tls::{Identity, TlsAcceptor, TlsStream};
use std::{
fs::File,
@ -15,6 +13,7 @@ use std::{
thread,
time::{SystemTime, UNIX_EPOCH},
};
use titanite::response::success::Default;
fn main() -> Result<()> {
let argument = Arc::new(Argument::parse());
@ -51,106 +50,92 @@ fn main() -> Result<()> {
}
fn handle(argument: Arc<Argument>, peer: SocketAddr, mut stream: TlsStream<TcpStream>) {
use titanite::{request::Titan, response::*};
println!("[{}] [info] [{peer}] New connection", now());
match Header::for_stream(&mut stream) {
Ok(header) => {
// do not trust header values, but check it to continue
if argument.size.is_some_and(|s| header.size > s) {
println!("[{}] [error] [{peer}] Max size limit reached", now());
return;
}
// make sure mime type whitelisted
if argument
.mime
.as_ref()
.is_some_and(|m| header.mime.is_some_and(|h| m.contains(&h)))
{
println!("[{}] [error] [{peer}] MIME type not allowed", now());
return;
}
// begin data handle
let mut total = 0;
let mut buffer = vec![0]; // @TODO optional chunk size
match storage::Item::create(&argument.directory) {
Ok(mut item) => {
// read header bytes
let mut input = vec![0; 1024];
match stream.read(&mut input) {
Ok(0) => todo!("Canceled"),
Ok(l) => {
match Titan::from_bytes(&input[..l]) {
Ok(titan) => {
// init memory pool
let mut data: Vec<u8> = Vec::with_capacity(titan.size);
loop {
match stream.read(&mut buffer) {
// read data bytes
let mut input = vec![0; argument.chunk];
match stream.read(&mut input) {
Ok(0) => {
item.delete().unwrap();
println!(
"[{}] [warning] [{peer}] Connection closed with rollback",
now()
);
break;
todo!("Canceled")
}
Ok(n) => {
total += n; // validate real data size
if argument.size.is_some_and(|s| s > total) {
item.delete().unwrap();
println!("[{}] [error] [{peer}] Max size limit reached", now());
break;
Ok(l) => {
data.extend(&input[..l]);
let total = data.len();
if argument.size.is_some_and(|limit| total > limit) {
todo!("Allowed max length limit reached")
}
if total > header.size {
item.delete().unwrap();
println!(
"[{}] [error] [{peer}] Content size larger than declared in headers", now()
);
break;
}
if let Err(e) = item.file.write(&buffer) {
item.delete().unwrap();
println!("[{}] [error] [{peer}] Failed to write file from stream: {e}", now());
break;
}
// transfer completed
if total == header.size {
match &stream.write_all(
// https://geminiprotocol.net/docs/protocol-specification.gmi#status-31-permanent-redirection
format!(
"31 {}\r\n",
argument
.redirect
.clone()
.unwrap_or(format!("gemini://{}", argument.bind))
)
.as_bytes(),
) {
Ok(_) => {
item.commit().unwrap(); // @TODO detect/cache mime based on content type received
println!("[{}] [info] [{peer}] Success", now());
// @TODO close connection gracefully
// https://geminiprotocol.net/docs/protocol-specification.gmi#closing-connections
match stream.flush() {
Ok(_) => println!("[{}] [info] [{peer}] Connection closed by server.", now()),
Err(e) => println!("[{}] [error] [{peer}] {e}", now())
};
}
Err(e) => {
item.delete().unwrap();
println!(
"[{}] [error] [{peer}] Failed write to stream: {e}",
now()
)
}
if titan.size >= total {
if titan.size > total {
println!(
"[{}] [warning] [{peer}] Data size mismatch header declaration",
now()
);
return;
}
// success
match storage::Item::create(&argument.directory) {
Ok(mut tmp) => match tmp.file.write(titan.data) {
Ok(_) => match &stream.write_all(
&Success::Default(Default {
mime: "text/gemini".to_string(),
})
.into_bytes(),
) {
// @TODO detect/validate/cache mime based on data received
Ok(()) => match tmp.commit() {
Ok(path) => match stream.flush() {
// Close connection gracefully
// https://geminiprotocol.net/docs/protocol-specification.gmi#closing-connections
Ok(()) => match stream.shutdown() {
Ok(()) => println!(
"[{}] [info] [{peer}] Data saved to {path}",
now()
),
Err(e) => println!("[{}] [warning] [{peer}] {e}", now())
},
Err(e) => println!("[{}] [warning] [{peer}] {e}", now())
}
Err(e) => println!("[{}] [warning] [{peer}] {e}", now())
},
Err(e) => {
println!("[{}] [error] [{peer}] {e}", now());
if let Err(e) = tmp.delete() {
println!("[{}] [error] [{peer}] {e}", now())
}
}
},
Err(e) => {
println!("[{}] [error] [{peer}] {e}", now());
if let Err(e) = tmp.delete() {
println!("[{}] [error] [{peer}] {e}", now())
}
}
},
Err(e) => println!("[{}] [error] [{peer}] {e}", now()),
}
break;
}
}
Err(e) => {
item.delete().unwrap();
println!(
"[{}] [error] [{peer}] Failed read from stream: {e}",
now()
);
break;
}
Err(e) => todo!("{e}"),
}
}
}
Err(e) => println!("[{}] [error] [{peer}] Could not create Item: {e}", now()),
Err(e) => todo!("{e}"),
}
}
Err(e) => println!("[{}] [error] [{peer}] Header error: {e}", now()),
Err(e) => todo!("{e}"),
}
}