You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

223 lines
6.4 KiB

extern crate openssl;
/// Types representing the gemini specification
pub mod gemini;
pub mod error;
mod io;
mod logger;
use std::io::Read;
use std::net::{TcpListener, TcpStream};
use std::path::PathBuf;
use std::sync::Arc;
pub use error::{GempressError, GempressResult};
use openssl::ssl::{SslMethod, SslAcceptor, SslStream, SslFiletype};
/// Configuration for a Gempress server.
#[derive(Clone, Debug)]
pub struct Config {
// Path to the identity file
certPath: PathBuf,
keyPath: PathBuf,
}
impl Config {
/// Create a new Gempress config by loading a TLS certificate at the given file path.
///
/// To generate a self-signed certificate, you can execute the following commands (substitute
/// `localhost` with your hostname, if applicable):
///
/// ```sh
/// openssl req -x509 -newkey rsa:4096 -keyout key.pem -out cert.pem -days 365 -nodes -subj '/CN=localhost'
/// openssl pkcs12 -export -out identity.pfx -inkey key.pem -in cert.pem
///
/// rm key.pem cert.pem # Cleanup
/// ```
///
///
/// # Examples
///
/// ```
/// let config = gempress::Config::from_identity(PathBuf::from("identity.pfx"), "password".into());
/// let mut app = Gempress::new(config);
/// ```
pub fn new(certPath: PathBuf, keyPath: PathBuf) -> Self {
Self {
certPath,
keyPath,
}
}
}
/// A function handler
///
/// # Examples
///
/// ```
/// use gempress::*;
/// use gempress::gemini;
/// use std::path::PathBuf;
///
/// let config = gempress::Config::from_identity(PathBuf::from("identity.pfx"), "password".into());
/// let mut app = Gempress::new(config);
///
/// // Define a function handler
/// fn index_handler(req: Box<gemini::Request>, mut res: Box<gemini::Response>) ->
/// GempressResult<()> {
/// res.send("Hello from index route!".as_bytes())?;
/// Ok(())
/// }
///
/// // Apply function handler to path
/// app.on("/foo", &index_handler);
///
/// app.listen(1965, || {
/// println!("Listening on port 1965");
/// })
/// .unwrap();
/// ```
pub type Handler = dyn Fn(Box<gemini::Request>, Box<gemini::Response>) -> error::GempressResult<()>;
struct Layer {
handler: Box<Handler>,
path: String,
}
impl std::fmt::Debug for Layer {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
f.debug_struct("Layer").field("path", &self.path).finish()
}
}
/// A Gempress server
pub struct Gempress {
pub config: Config,
stack: Vec<Layer>,
}
impl Gempress {
/// Create a new Gempress server with the given config
///
/// # Examples
///
/// ```
/// let config = gempress::Config::from_identity(PathBuf::from("identity.pfx"), "password".into());
/// let mut app = Gempress::new(config);
/// ```
pub fn new(config: Config) -> Self {
Gempress {
config,
stack: Vec::new(),
}
}
/// Registers a new route handler to a given path
///
/// # Examples
///
/// ```
/// use gempress::gemini;
/// use gempress::*;
///
/// let config = gempress::Config::from_identity(PathBuf::from("identity.pfx"), "password".into());
/// let mut app = Gempress::new(config);
///
/// fn index_handler(req: Box<gemini::Request>, mut res: Box<gemini::Response>) ->
/// GempressResult<()> {
/// res.send("Hello from index route!".as_bytes())?;
/// Ok(())
/// }
///
/// fn foo_handler(req: Box<gemini::Request>, mut res: Box<gemini::Response>) ->
/// GempressResult<()> {
/// res.send("This is the /foo route".as_bytes());
/// Ok(())
/// }
///
/// app.on("/", &index_handler);
/// app.on("/foo", &foo_handler);
///
/// app.listen(1965, || {
/// println!("Listening on port 1965");
/// })
/// .unwrap();
/// ```
pub fn on(&mut self, path: &str, handler: &'static Handler) {
let layer = Layer {
path: path.to_string(),
handler: Box::new(handler.to_owned()),
};
self.stack.push(layer);
}
/// Bind the server to a network port, then execute the callback
pub fn listen<F: Fn()>(self, port: u16, callback: F) -> GempressResult<()> {
let mut acceptor = SslAcceptor::mozilla_intermediate(SslMethod::tls()).unwrap();
acceptor.set_private_key_file(&self.config.keyPath, SslFiletype::PEM).unwrap();
acceptor.set_certificate_chain_file(&self.config.certPath).unwrap();
acceptor.check_private_key().unwrap();
let acceptor = Arc::new(acceptor.build());
let listener = TcpListener::bind(format!("0.0.0.0:{}", port)).map_err(GempressError::BindFailed)?;
logger::info(format!("Listening on port {}", port));
for stream in listener.incoming() {
match stream {
Ok(stream) => {
let acceptor = acceptor.clone();
match acceptor.accept(stream) {
Ok(stream) => {
if let Err(e) = self.handle_client(stream) {
logger::error(format!("Can't handle client: {}", e));
}
}
Err(e) => {
logger::error(format!("Can't handle stream: {}", e));
}
};
}
Err(err) => logger::error(err),
}
}
(callback)();
Ok(())
}
fn handle_client(&self, mut stream: SslStream<TcpStream>) -> GempressResult<()> {
let mut buffer = [0; 1024];
stream
.read(&mut buffer)
.map_err(GempressError::StreamReadFailed)?;
let raw_request = String::from_utf8(buffer.to_vec())?;
let mut request = gemini::Request::parse(&raw_request)?;
request.certificate = None; // TODO
let mut response = gemini::Response::new(stream);
response.status(gemini::StatusCode::Success)?;
let maybe_layer = self
.stack
.iter()
.find(|&l| l.path == request.url.path());
match maybe_layer {
Some(layer) => { (layer.handler)(Box::new(request), Box::new(response))?; },
None => { response.status(gemini::StatusCode::NotFound)?.send("Not found".as_bytes())?; },
};
Ok(())
}
}