mirror of
https://github.com/YGGverse/titanit.git
synced 2026-03-31 17:15:30 +00:00
separate handlers
This commit is contained in:
parent
69b0186d01
commit
71d75bc22f
1 changed files with 208 additions and 207 deletions
415
src/main.rs
415
src/main.rs
|
|
@ -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)?;
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue