file serving
continuous-integration/drone/push Build is passing Details

This commit is contained in:
Cadey Ratio 2020-07-31 12:16:15 -04:00
parent 0a100b081c
commit e84375a572
5 changed files with 113 additions and 31 deletions

View File

@ -1,6 +1,6 @@
[package] [package]
name = "maj" name = "maj"
version = "0.4.2" version = "0.5.0"
authors = ["Christine Dodrill <me@christine.website>"] authors = ["Christine Dodrill <me@christine.website>"]
edition = "2018" edition = "2018"
license = "0BSD" license = "0BSD"
@ -10,22 +10,23 @@ repository = "https://tulpa.dev/cadey/maj"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
[dependencies] [dependencies]
async-std = { version = "1.6", optional = true }
async-tls = { default-features = false, optional = true, version = "0" }
async-trait = { version = "0", optional = true } async-trait = { version = "0", optional = true }
log = "0.4"
mime_guess = "2.0"
num = "0.2" num = "0.2"
num-derive = "0.3" num-derive = "0.3"
num-traits = "0.2" num-traits = "0.2"
rustls = { version = "0.18", optional = true, features = ["dangerous_configuration"] }
tokio-rustls = { version = "0.14", features = ["dangerous_configuration"], optional = true }
webpki = { version = "0.21.0", optional = true }
webpki-roots = { version = "0.20", optional = true }
tokio = { version = "0.2", features = ["full"], optional = true }
async-tls = { default-features = false, optional = true, version = "0" }
async-std = { version = "1.6", optional = true }
log = "0.4"
url = "2"
thiserror = "1"
structopt = "0.3"
once_cell = "1.4" once_cell = "1.4"
rustls = { version = "0.18", optional = true, features = ["dangerous_configuration"] }
structopt = "0.3"
thiserror = "1"
tokio-rustls = { version = "0.14", features = ["dangerous_configuration"], optional = true }
tokio = { version = "0.2", features = ["full"], optional = true }
url = "2"
webpki-roots = { version = "0.20", optional = true }
webpki = { version = "0.21.0", optional = true }
[dev-dependencies] [dev-dependencies]
pretty_env_logger = "0.4" pretty_env_logger = "0.4"

View File

@ -1,6 +1,7 @@
{ pkgs ? import <nixpkgs> { } }:
let let
moz_overlay = import (builtins.fetchTarball
"https://github.com/mozilla/nixpkgs-mozilla/archive/master.tar.gz");
pkgs = import <nixpkgs> { overlays = [ moz_overlay ]; };
nur = import (builtins.fetchTarball nur = import (builtins.fetchTarball
"https://github.com/nix-community/NUR/archive/master.tar.gz") { "https://github.com/nix-community/NUR/archive/master.tar.gz") {
inherit pkgs; inherit pkgs;
@ -9,11 +10,7 @@ let
texlive.combine { inherit (texlive) scheme-medium bitter titlesec; }; texlive.combine { inherit (texlive) scheme-medium bitter titlesec; };
in pkgs.mkShell { in pkgs.mkShell {
buildInputs = with pkgs; [ buildInputs = with pkgs; [
rustc pkgs.latest.rustChannels.stable.rust
cargo
rls
rustfmt
cargo-watch
pkg-config pkg-config
ncurses ncurses

View File

@ -11,6 +11,14 @@ pub struct Response {
} }
impl Response { impl Response {
pub fn with_body(meta: String, body: Vec<u8>) -> Response {
Response {
status: StatusCode::Success,
meta: meta,
body: body,
}
}
pub fn gemini(body: Vec<u8>) -> Response { pub fn gemini(body: Vec<u8>) -> Response {
Response { Response {
status: StatusCode::Success, status: StatusCode::Success,

61
src/server/files.rs Normal file
View File

@ -0,0 +1,61 @@
/// A simple handler for disk based files. Will optionally chop off a prefix.
use super::{Handler as MajHandler, Request, Result};
use crate::Response;
use async_trait::async_trait;
use std::ffi::OsStr;
pub struct Handler {
chop_off: String,
base_dir: String,
}
impl Handler {
/// Serves static files from an OS directory with a given prefix chopped off.
pub fn new(chop_off: String, base_dir: String) -> Self {
Handler {
chop_off: chop_off,
base_dir: base_dir,
}
}
fn chop(&self, path: String) -> Option<String> {
if path.starts_with(&self.chop_off) {
path.strip_prefix(&self.chop_off).map(|val| val.to_string())
} else {
Some(path)
}
}
}
#[async_trait]
impl MajHandler for Handler {
async fn handle(&self, r: Request) -> Result<Response> {
let mut path = std::path::PathBuf::from(&self.base_dir);
let mut url = r.url.clone();
url.set_path(&self.chop(r.url.path().to_string()).unwrap());
if let Some(segments) = r.url.path_segments() {
path.extend(segments);
}
if async_std::fs::metadata(&path).await?.is_dir() {
if url.as_str().ends_with('/') {
path.push("index.gmi");
} else {
// Send a redirect when the URL for a directory has no trailing slash.
return Ok(Response::perm_redirect(format!("{}/", url)));
}
}
let mut file = async_std::fs::File::open(&path).await?;
let mut buf: Vec<u8> = Vec::new();
async_std::io::copy(&mut file, &mut buf).await?;
// Send header.
if path.extension() == Some(OsStr::new("gmi")) {
return Ok(Response::gemini(buf));
}
let mime = mime_guess::from_path(&path).first_or_octet_stream();
Ok(Response::with_body(mime.essence_str().to_string(), buf))
}
}

View File

@ -34,6 +34,8 @@ enum RequestParsingError {
mod routes; mod routes;
pub use routes::*; pub use routes::*;
pub mod files;
#[async_trait] #[async_trait]
pub trait Handler { pub trait Handler {
async fn handle(&self, r: Request) -> Result<Response>; async fn handle(&self, r: Request) -> Result<Response>;
@ -78,13 +80,23 @@ async fn handle_request(
Ok(url) => { Ok(url) => {
if let Some(u_port) = url.port() { if let Some(u_port) = url.port() {
if port != u_port { if port != u_port {
let _ = respond(&mut stream, "53", &["Cannot proxy"]).await; let _ = write_header(
&mut stream,
StatusCode::ProxyRequestRefused,
"Cannot proxy to that URL",
)
.await;
return Ok(()); return Ok(());
} }
} }
if url.scheme() != "gemini" { if url.scheme() != "gemini" {
let _ = respond(&mut stream, "53", &["Cannot proxy outside geminispace"]).await; let _ = write_header(
&mut stream,
StatusCode::ProxyRequestRefused,
"Cannot proxy to that URL",
)
.await;
Err(RequestParsingError::InvalidScheme(url.scheme().to_string()))? Err(RequestParsingError::InvalidScheme(url.scheme().to_string()))?
} }
@ -95,20 +107,19 @@ async fn handle_request(
handle(h, req, &mut stream, addr).await; handle(h, req, &mut stream, addr).await;
} }
Err(e) => { Err(e) => {
respond(&mut stream, "59", &["Invalid request."]).await?; let _ = write_header(&mut stream, StatusCode::BadRequest, "Invalid request.").await;
log::error!("error from {}: {:?}", addr, e); log::error!("error from {}: {:?}", addr, e);
} }
} }
Ok(()) Ok(())
} }
async fn respond<W: Write + Unpin>(mut stream: W, status: &str, meta: &[&str]) -> Result { pub async fn write_header<W: Write + Unpin>(
stream.write_all(status.as_bytes()).await?; mut stream: W,
stream.write_all(b" ").await?; status: StatusCode,
for m in meta { meta: &str,
stream.write_all(m.as_bytes()).await?; ) -> Result {
} stream.write(format!("{} {}\r\n", status as u8, meta).as_bytes()).await?;
stream.write_all(b"\r\n").await?;
Ok(()) Ok(())
} }
@ -145,8 +156,12 @@ async fn parse_request<R: Read + Unpin>(mut stream: R) -> Result<Url> {
Ok(url) Ok(url)
} }
async fn handle<T>(h: Arc<(dyn Handler + Send + Sync)>, req: Request, stream: &mut T, addr: SocketAddr) async fn handle<T>(
where h: Arc<(dyn Handler + Send + Sync)>,
req: Request,
stream: &mut T,
addr: SocketAddr,
) where
T: Write + Unpin, T: Write + Unpin,
{ {
let u = req.url.clone(); let u = req.url.clone();