use async_std::task; use maj::{ gemini::Builder, route, seg, server::{Error, Handler as MajHandler, Request}, split, Response, }; use percent_encoding::percent_decode_str; use rustls::internal::pemfile::{certs, rsa_private_keys}; use rustls::{ AllowAnyAnonymousOrAuthenticatedClient, Certificate, PrivateKey, RootCertStore, ServerConfig, }; use std::fs::File; use std::io::{self, BufReader}; use std::path::{Path, PathBuf}; use std::sync::Arc; use structopt::StructOpt; #[derive(StructOpt, Debug)] struct Options { /// host to listen on #[structopt(short = "H", long, env = "HOST", default_value = "0.0.0.0")] host: String, /// port to listen on #[structopt(short = "p", long, env = "PORT", default_value = "1965")] port: u16, /// cert file #[structopt(short = "c", long = "cert", env = "CERT_FILE")] cert: PathBuf, /// key file #[structopt(short = "k", long = "key", env = "KEY_FILE")] key: PathBuf, /// static path #[structopt(short = "s", long, env = "STATIC_PATH", default_value = "./static")] static_path: PathBuf, /// server hostname #[structopt( long = "hostname", env = "SERVER_HOSTNAME", default_value = "maj.kahless.cetacean.club" )] hostname: String, } fn load_certs(path: &Path) -> io::Result> { certs(&mut BufReader::new(File::open(path)?)) .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid cert")) } fn load_keys(path: &Path) -> io::Result> { rsa_private_keys(&mut BufReader::new(File::open(path)?)) .map_err(|_| io::Error::new(io::ErrorKind::InvalidInput, "invalid key")) } fn main() -> Result<(), maj::server::Error> { env_logger::init(); let opts = Options::from_args(); let certs = load_certs(&opts.cert)?; let mut keys = load_keys(&opts.key)?; log::info!( "serving gemini://{} on {}:{}", opts.hostname, opts.host, opts.port ); let mut config = ServerConfig::new(AllowAnyAnonymousOrAuthenticatedClient::new( RootCertStore::empty(), )); config .set_single_cert(certs, keys.remove(0)) .map_err(|err| io::Error::new(io::ErrorKind::InvalidInput, err))?; task::block_on(maj::server::serve( Arc::new(Handler { hostname: opts.hostname, files: maj::server::files::Handler::new(opts.static_path), }), config, opts.host, opts.port, ))?; Ok(()) } struct Handler { hostname: String, files: maj::server::files::Handler, } async fn index() -> Result { let msg = include_bytes!("index.gmi"); Ok(Response::gemini(msg.to_vec())) } async fn majc() -> Result { let msg = include_bytes!("majc.gmi"); Ok(Response::gemini(msg.to_vec())) } async fn need_cert(req: Request) -> Result { match req.certs { None => Ok(Response::need_cert("test")), Some(certs) => Ok(Response::render( Builder::new() .heading(1, "Cert test") .text(format!("{:?}", certs)) .build(), )), } } async fn input(req: Request) -> Result { match req.url.query() { None => Ok(Response::input("test")), Some(q) => Ok({ use maj::gemini::Node::{self, *}; let result = vec![ Heading { level: 1, body: "Input test".to_string(), }, Node::blank(), Text("You gave me:".to_string()), Preformatted(format!("{}", percent_decode_str(q).decode_utf8()?)), ]; Response::render(result) }), } } async fn user(name: String) -> Result { Ok(Response::render({ use maj::gemini::Node::{self, *}; vec![ Heading { level: 1, body: format!("{}'s user page", name), }, Node::blank(), Text(format!("this is a test page for {}", name)), ] })) } #[async_trait::async_trait] impl MajHandler for Handler { async fn handle(&self, req: Request) -> Result { if req.url.has_host() && req.url.host_str().unwrap().to_string() != self.hostname { return Ok(Response::no_proxy()); } if req.url.path() == "" { return Ok(Response::perm_redirect(format!( "gemini://{}/", self.hostname ))); } route!(req.url.path(), { (/) => index().await; (/"cert") => need_cert(req).await; (/"input") => input(req).await; (/"majc") => majc().await; (/"~"/[name: String][/rest..]) => user(name).await; }); self.files.handle(req).await } }