separate handlers

This commit is contained in:
yggverse 2025-02-22 07:41:25 +02:00
parent 69b0186d01
commit 71d75bc22f

View file

@ -3,7 +3,7 @@ mod storage;
use anyhow::Result; use anyhow::Result;
use argument::Argument; use argument::Argument;
use clap::Parser;
use native_tls::{Identity, TlsAcceptor, TlsStream}; use native_tls::{Identity, TlsAcceptor, TlsStream};
use std::{ use std::{
fs::File, fs::File,
@ -15,6 +15,8 @@ use std::{
}; };
fn main() -> Result<()> { fn main() -> Result<()> {
use clap::Parser;
let argument = Arc::new(Argument::parse()); let argument = Arc::new(Argument::parse());
// https://geminiprotocol.net/docs/protocol-specification.gmi#the-use-of-tls // https://geminiprotocol.net/docs/protocol-specification.gmi#the-use-of-tls
@ -42,7 +44,7 @@ fn main() -> Result<()> {
move || handle(argument, peer, &mut stream) move || handle(argument, peer, &mut stream)
}); });
} }
Err(e) => println!("[{}] [error] Failed to accept connection: {e}", now()), Err(e) => println!("[{}] [error] {e}", now()),
} }
} }
Ok(()) Ok(())
@ -51,220 +53,66 @@ fn main() -> Result<()> {
fn handle(argument: Arc<Argument>, peer: SocketAddr, stream: &mut TlsStream<TcpStream>) { fn handle(argument: Arc<Argument>, peer: SocketAddr, stream: &mut TlsStream<TcpStream>) {
use titanite::*; use titanite::*;
println!("[{}] [info] [{peer}] New connection", now()); println!("[{}] [info] [{peer}] New connection", now());
// validate totals let mut input = vec![0; titanite::HEADER_MAX_LEN];
let mut total = 0;
// read header bytes
let mut input = vec![0; 1024];
match stream.read(&mut input) { match stream.read(&mut input) {
Ok(0) => println!("[{}] [warning] [{peer}] Connection closed by peer", now()), Ok(0) => println!("[{}] [warning] [{peer}] Connection closed by peer", now()),
Ok(l) => { Ok(s) => match Request::from_bytes(&input[..s]) {
match Request::from_bytes(&input[..l]) { Ok(request) => match request {
Ok(request) => { Request::Gemini(this) => handle_gemini(this, argument, peer, stream),
match request { Request::Titan(this) => handle_titan(this, argument, peer, stream),
Request::Gemini(gemini) => { },
match storage::Item::from_url(&gemini.url.as_str(), &argument.directory) Err(e) => send(
{ &response::failure::temporary::General {
Ok(item) => send( message: Some("Internal server error".to_string()),
&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!(),
},
}
} }
Err(e) => send( .into_bytes(),
&response::failure::temporary::General { stream,
message: Some("Internal server error".to_string()), |result| match result {
} Ok(()) => println!("[{}] [warning] [{peer}] {e}", now()),
.into_bytes(), Err(e) => println!("[{}] [error] [{peer}] {e}", now()),
stream, },
|result| match result { ),
Ok(()) => println!("[{}] [error] [{peer}] {e}", now()), },
Err(e) => println!("[{}] [error] [{peer}] {e}", now()),
},
),
}
}
Err(e) => send( Err(e) => send(
&response::failure::temporary::General { &response::failure::temporary::General {
message: Some("Internal server error".to_string()), message: Some("Internal server error".to_string()),
} }
.into_bytes(), .into_bytes(),
stream, 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<Argument>,
peer: SocketAddr,
stream: &mut TlsStream<TcpStream>,
) {
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 { |result| match result {
Ok(()) => println!("[{}] [error] [{peer}] {e}", now()), Ok(()) => println!("[{}] [error] [{peer}] {e}", now()),
Err(e) => println!("[{}] [error] [{peer}] {e}", now()), Err(e) => println!("[{}] [error] [{peer}] {e}", now()),
@ -273,6 +121,159 @@ fn handle(argument: Arc<Argument>, peer: SocketAddr, stream: &mut TlsStream<TcpS
} }
} }
fn handle_titan(
titan: titanite::request::Titan,
argument: Arc<Argument>,
peer: SocketAddr,
stream: &mut TlsStream<TcpStream>,
) {
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<TcpStream>, callback: impl FnOnce(Result<()>)) { fn send(data: &[u8], stream: &mut TlsStream<TcpStream>, callback: impl FnOnce(Result<()>)) {
callback((|| { callback((|| {
stream.write_all(data)?; stream.write_all(data)?;