2020-08-02 03:13:01 +00:00
|
|
|
use crate::templates::{self, statics::StaticFile, Html, RenderRucte, ToHtml};
|
2020-08-02 02:42:44 +00:00
|
|
|
use maj::server::Handler;
|
|
|
|
use maj::{gemini, server::Request as GemRequest, StatusCode};
|
|
|
|
use std::io::Write;
|
|
|
|
use std::sync::Arc;
|
|
|
|
use url::Url;
|
2020-08-02 03:13:01 +00:00
|
|
|
use warp::{filters::path::FullPath, http::Response, path, Filter, Rejection, Reply};
|
2020-08-02 02:42:44 +00:00
|
|
|
|
|
|
|
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<crate::server::Handler>)) -> Result<impl Reply, Rejection> {
|
|
|
|
let (path, h) = args;
|
|
|
|
let u = Url::parse(&format!("gemini://{}{}", HOST, path.as_str())).unwrap();
|
|
|
|
|
|
|
|
let req = GemRequest {
|
|
|
|
url: u.clone(),
|
|
|
|
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())
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-02 03:13:01 +00:00
|
|
|
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()),
|
|
|
|
)
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
2020-08-02 02:42:44 +00:00
|
|
|
_ => 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<u8>) -> (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<u8> = Vec::new();
|
|
|
|
|
|
|
|
for node in &nodes {
|
|
|
|
match node {
|
|
|
|
Heading { level, body } => {
|
|
|
|
if *level == 1 {
|
|
|
|
title = body.to_string();
|
|
|
|
}
|
|
|
|
|
|
|
|
write!(buf, "<h{0}>{1}</h{0}>", level, body).unwrap();
|
|
|
|
}
|
|
|
|
Text(body) => write!(buf, "{}\n<br />", body).unwrap(),
|
|
|
|
Link { to, name } => write!(
|
|
|
|
buf,
|
2020-08-02 03:13:01 +00:00
|
|
|
r#"<a href="{}">{}</a><br />"#,
|
2020-08-02 02:42:44 +00:00
|
|
|
to,
|
|
|
|
name.as_ref().or(Some(&to.to_string())).unwrap()
|
|
|
|
)
|
|
|
|
.unwrap(),
|
|
|
|
Preformatted(body) => write!(buf, "<code><pre>{}</pre></code>", body).unwrap(),
|
|
|
|
ListItem(body) => write!(buf, "<li>{}</li>", body).unwrap(),
|
|
|
|
Quote(body) => write!(buf, "<blockquote>{}</blockquote>", body).unwrap(),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
(title, Html(String::from_utf8(buf).unwrap()))
|
|
|
|
}
|
|
|
|
|
|
|
|
pub fn run(h: Arc<crate::server::Handler>, port: u16) {
|
|
|
|
smol::run(async {
|
|
|
|
let h = h.clone();
|
|
|
|
let handler = warp::path::full()
|
2020-08-02 03:13:01 +00:00
|
|
|
.map(move |path: FullPath| (path, h.clone()))
|
|
|
|
.and_then(route);
|
2020-08-02 02:42:44 +00:00
|
|
|
let statics = path("static").and(path::param()).and_then(static_file);
|
2020-08-02 03:13:01 +00:00
|
|
|
let site = statics.or(handler).with(warp::log(APPLICATION_NAME));
|
2020-08-02 02:42:44 +00:00
|
|
|
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<impl Reply, Rejection> {
|
|
|
|
if let Some(data) = StaticFile::get(&name) {
|
|
|
|
Ok(Response::builder()
|
2020-08-02 03:13:01 +00:00
|
|
|
.status(warp::http::StatusCode::OK)
|
|
|
|
.header("content-type", data.mime.as_ref())
|
|
|
|
// TODO .header("expires", _far_expires)
|
|
|
|
.body(data.content))
|
2020-08-02 02:42:44 +00:00
|
|
|
} else {
|
|
|
|
println!("Static file {} not found", name);
|
|
|
|
Err(warp::reject::not_found())
|
|
|
|
}
|
|
|
|
}
|