maj/src/server/mod.rs

155 lines
4.8 KiB
Rust

use crate::{Response, StatusCode};
use async_trait::async_trait;
use rustls::{Certificate, Session};
use std::{error::Error as StdError, net::SocketAddr, sync::Arc};
use tokio::io::{AsyncBufReadExt, AsyncWriteExt, BufReader};
use tokio::{net::TcpListener, stream::StreamExt};
use tokio_rustls::TlsAcceptor;
use url::Url;
/// A Gemini request and its associated metadata.
#[allow(dead_code)]
pub struct Request {
pub url: Url,
pub certs: Option<Vec<Certificate>>,
}
pub type Error = Box<dyn StdError + Sync + Send>;
#[allow(dead_code, unused_assignments, unused_mut, unused_variables)]
mod routes;
pub use routes::*;
#[async_trait]
pub trait Handler {
async fn handle(&self, r: Request) -> Result<Response, Error>;
}
pub async fn serve(
h: &(dyn Handler + Sync),
cfg: rustls::ServerConfig,
host: String,
port: u16,
) -> Result<(), Error>
where
{
let cfg = Arc::new(cfg);
let mut listener = TcpListener::bind(&format!("{}:{}", host, port)).await?;
let mut incoming = listener.incoming();
let acceptor = TlsAcceptor::from(cfg.clone());
while let Some(stream) = incoming.next().await {
let stream = stream?;
let addr = stream.peer_addr().unwrap();
let fut = async {
let acceptor = acceptor.clone();
let result = acceptor.accept(stream).await;
if result.is_err() {
return;
}
let mut stream = result.unwrap();
let mut rd = BufReader::new(&mut stream);
let mut u = String::new();
if let Err(why) = rd.read_line(&mut u).await {
log::error!("can't read request from {}: {:?}", addr, why);
let _ = stream
.write(format!("{} Invalid URL", StatusCode::BadRequest as u8).as_bytes())
.await;
return;
}
u = u.trim().to_string();
if u.len() >= 1025 {
let _ = stream
.write(format!("{} URL too long", StatusCode::BadRequest as u8).as_bytes())
.await;
return;
}
if u.starts_with("//") {
u = format!("gemini:{}", u);
}
match Url::parse(&u) {
Err(why) => {
let _ = stream
.write(
format!("{} bad URL: {:?}", StatusCode::BadRequest as u8, why)
.as_bytes(),
)
.await;
}
Ok(u) => {
if u.scheme() != "gemini" {
let _ = stream
.write(
format!(
"{} Cannot handle that kind of url",
StatusCode::ProxyRequestRefused as u8
)
.as_bytes(),
)
.await;
return;
}
if let Some(u_port) = u.port() {
if port != u_port {
let _ = stream
.write(
format!(
"{} Cannot handle that kind of url",
StatusCode::ProxyRequestRefused as u8
)
.as_bytes(),
)
.await;
return;
}
}
tokio::join!(handle(
h,
Request {
url: u.clone(),
certs: stream.get_ref().1.get_peer_certificates(),
},
&mut stream,
addr,
));
}
}
};
tokio::join!(fut);
}
Ok(())
}
async fn handle<T>(h: &(dyn Handler + Sync), req: Request, stream: &mut T, addr: SocketAddr)
where
T: AsyncWriteExt + Unpin,
{
let u = req.url.clone();
match h.handle(req).await {
Ok(resp) => {
stream
.write(format!("{} {}\r\n", resp.status as u8, resp.meta).as_bytes())
.await
.unwrap();
stream.write(&resp.body).await.unwrap();
log::info!("{}: {} {:?}", addr, u, resp.status);
}
Err(why) => {
stream
.write(format!("{} {:?}\r\n", StatusCode::PermanentFailure as u8, why).as_bytes())
.await
.unwrap();
log::error!("{}: {}: {:?}", addr, u, why);
}
};
}