use crate::templates::{self, statics::StaticFile, Html, RenderRucte, ToHtml}; use maj::server::Handler; use maj::{gemini, server::Request as GemRequest, StatusCode}; use std::io::Write; use std::sync::Arc; use url::Url; use warp::{filters::path::FullPath, http::Response, path, Filter, Rejection, Reply}; const HOST: &'static str = "cetacean.club"; // XXX(cadey): HACK const APPLICATION_NAME: &str = concat!(env!("CARGO_PKG_NAME"), "/", env!("CARGO_PKG_VERSION")); async fn route(args: (FullPath, Arc)) -> Result { let (path, h) = args; let u = Url::parse(&format!("gemini://{}{}", HOST, path.as_str())).unwrap(); let req = GemRequest { url: u.clone(), addr: "127.0.0.1:8080".parse().unwrap(), certs: None, }; let resp = h.clone().handle(req).await.unwrap(); match resp.status { StatusCode::Success => { if resp.meta.starts_with("text/gemini") { let (title, body) = gemtext_to_html(resp.body); Response::builder().html(|o| templates::page_html(o, title, body)) } else { Response::builder() .status(warp::http::StatusCode::INTERNAL_SERVER_ERROR) .html(|o| { templates::error_html(o, u.to_string(), "cannot proxy this yet".to_string()) }) } } StatusCode::PermanentRedirect => { let uu = Url::parse(&resp.meta).expect("url parsing to work"); log::info!("uu: {}", uu.to_string()); Response::builder() .status(warp::http::StatusCode::PERMANENT_REDIRECT) .header("Location", uu.path()) .html(|o| { templates::error_html( o, u.to_string(), format!("forwarding you to {}", uu.path()), ) }) } _ => Response::builder() .status(warp::http::StatusCode::INTERNAL_SERVER_ERROR) .html(|o| templates::error_html(o, u.to_string(), resp.meta)), } } fn gemtext_to_html(inp: Vec) -> (String, impl ToHtml) { use gemini::Node::*; let mut title: String = "Unknown Title".to_string(); let inp = std::str::from_utf8(&inp).unwrap(); let nodes = gemini::parse(inp); let mut buf: Vec = Vec::new(); for node in &nodes { match node { Heading { level, body } => { if *level == 1 { title = body.to_string(); } write!(buf, "{1}", level, body).unwrap(); } Text(body) => write!(buf, "{}\n
", body).unwrap(), Link { to, name } => write!( buf, r#"{}
"#, to, name.as_ref().or(Some(&to.to_string())).unwrap() ) .unwrap(), Preformatted(body) => write!(buf, "
{}
", body).unwrap(), ListItem(body) => write!(buf, "
  • {}
  • ", body).unwrap(), Quote(body) => write!(buf, "
    {}
    ", body).unwrap(), } } (title, Html(String::from_utf8(buf).unwrap())) } pub fn run(h: Arc, port: u16) { smol::run(async { let h = h.clone(); let handler = warp::path::full() .map(move |path: FullPath| (path, h.clone())) .and_then(route); let statics = path("static").and(path::param()).and_then(static_file); let site = statics.or(handler).with(warp::log(APPLICATION_NAME)); warp::serve(site).run(([0, 0, 0, 0], port)).await; }); } /// Handler for static files. /// Create a response from the file data with a correct content type /// and a far expires header (or a 404 if the file does not exist). async fn static_file(name: String) -> Result { if let Some(data) = StaticFile::get(&name) { Ok(Response::builder() .status(warp::http::StatusCode::OK) .header("content-type", data.mime.as_ref()) // TODO .header("expires", _far_expires) .body(data.content)) } else { println!("Static file {} not found", name); Err(warp::reject::not_found()) } }