use crate::{Response, StatusCode}; use async_std::{ io::prelude::*, net::{TcpListener, TcpStream}, stream::StreamExt, task, }; use async_tls::TlsAcceptor; use async_trait::async_trait; use rustls::Certificate; use std::{error::Error as StdError, net::SocketAddr, sync::Arc}; use url::Url; /// A Gemini request and its associated metadata. #[allow(dead_code)] pub struct Request { pub url: Url, pub certs: Option>, } pub type Error = Box; type Result = std::result::Result; #[derive(thiserror::Error, Debug)] enum RequestParsingError { #[error("invalid scheme {0}")] InvalidScheme(String), #[error("unexpected end of request")] UnexpectedEnd, } #[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; } pub async fn serve( h: Arc, cfg: rustls::ServerConfig, host: String, port: u16, ) -> Result where { let cfg = Arc::new(cfg); let listener = TcpListener::bind(&format!("{}:{}", host, port)).await?; let mut incoming = listener.incoming(); let acceptor = Arc::new(TlsAcceptor::from(cfg.clone())); while let Some(Ok(stream)) = incoming.next().await { let h = h.clone(); let acceptor = acceptor.clone(); let addr = stream.peer_addr().unwrap(); task::spawn(handle_request(h, stream, acceptor, addr)); } Ok(()) } /// Handle a single client session (request + response). async fn handle_request( h: Arc<(dyn Handler + Send + Sync)>, stream: TcpStream, acceptor: Arc, addr: SocketAddr, ) -> Result { // Perform handshake. let mut stream = acceptor.clone().accept(stream).await?; match parse_request(&mut stream).await { Ok(url) => { let req = Request { url: url, certs: None, }; handle(h, req, &mut stream, addr).await; } Err(e) => { respond(&mut stream, "59", &["Invalid request."]).await?; return Err(e); } } Ok(()) } async fn respond(mut stream: W, status: &str, meta: &[&str]) -> Result { stream.write_all(status.as_bytes()).await?; stream.write_all(b" ").await?; for m in meta { stream.write_all(m.as_bytes()).await?; } stream.write_all(b"\r\n").await?; Ok(()) } /// Return the URL requested by the client. async fn parse_request(mut stream: R) -> Result { // Because requests are limited to 1024 bytes (plus 2 bytes for CRLF), we // can use a fixed-sized buffer on the stack, avoiding allocations and // copying, and stopping bad clients from making us use too much memory. let mut request = [0; 1026]; let mut buf = &mut request[..]; let mut len = 0; // Read until CRLF, end-of-stream, or there's no buffer space left. loop { let bytes_read = stream.read(buf).await?; len += bytes_read; if request[..len].ends_with(b"\r\n") { break; } else if bytes_read == 0 { Err(RequestParsingError::UnexpectedEnd)? } buf = &mut request[len..]; } let request = std::str::from_utf8(&request[..len - 2])?; // Handle scheme-relative URLs. let url = if request.starts_with("//") { Url::parse(&format!("gemini:{}", request))? } else { Url::parse(request)? }; // Validate the URL. TODO: Check the hostname and port. if url.scheme() != "gemini" { Err(RequestParsingError::InvalidScheme(url.scheme().to_string()))? } Ok(url) } async fn handle(h: Arc<(dyn Handler + Send + Sync)>, req: Request, stream: &mut T, addr: SocketAddr) where T: Write + 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.meta, resp.status); } Err(why) => { stream .write(format!("{} {:?}\r\n", StatusCode::PermanentFailure as u8, why).as_bytes()) .await .unwrap(); log::error!("{}: {}: {:?}", addr, u, why); } }; }