2020-11-19 07:02:11 +00:00
|
|
|
#[cfg(feature="serve_dir")]
|
|
|
|
use std::path::{Path, PathBuf};
|
2020-10-31 19:53:03 +00:00
|
|
|
use anyhow::*;
|
2020-11-19 07:02:11 +00:00
|
|
|
#[cfg(feature="serve_dir")]
|
2020-10-31 19:53:03 +00:00
|
|
|
use tokio::{
|
|
|
|
fs::{self, File},
|
|
|
|
io,
|
|
|
|
};
|
2020-11-19 07:02:11 +00:00
|
|
|
#[cfg(feature="serve_dir")]
|
2020-11-19 16:29:11 +00:00
|
|
|
use crate::types::{Document, document::HeadingLevel::*};
|
2020-11-24 04:30:59 +00:00
|
|
|
#[cfg(feature="serve_dir")]
|
2020-11-19 16:29:11 +00:00
|
|
|
use crate::types::Response;
|
2020-11-20 04:51:25 +00:00
|
|
|
use std::future::Future;
|
2020-11-19 18:29:17 +00:00
|
|
|
use tokio::time;
|
2020-10-31 19:53:03 +00:00
|
|
|
|
2020-11-19 07:02:11 +00:00
|
|
|
#[cfg(feature="serve_dir")]
|
2020-12-01 21:36:29 +00:00
|
|
|
pub async fn serve_file<P: AsRef<Path>>(path: P, mime: &str) -> Response {
|
2020-10-31 19:53:03 +00:00
|
|
|
let path = path.as_ref();
|
|
|
|
|
|
|
|
let file = match File::open(path).await {
|
|
|
|
Ok(file) => file,
|
|
|
|
Err(err) => match err.kind() {
|
2020-11-23 14:39:45 +00:00
|
|
|
std::io::ErrorKind::PermissionDenied => {
|
|
|
|
warn!("Asked to serve {}, but permission denied by OS", path.display());
|
2020-12-01 21:36:29 +00:00
|
|
|
return Response::not_found();
|
2020-11-23 14:39:45 +00:00
|
|
|
},
|
|
|
|
_ => return warn_unexpected(err, path, line!()),
|
2020-10-31 19:53:03 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-12-01 21:36:29 +00:00
|
|
|
Response::success(mime, file)
|
2020-10-31 19:53:03 +00:00
|
|
|
}
|
|
|
|
|
2020-11-19 07:02:11 +00:00
|
|
|
#[cfg(feature="serve_dir")]
|
2020-12-01 21:36:29 +00:00
|
|
|
pub async fn serve_dir<D: AsRef<Path>, P: AsRef<Path>>(dir: D, virtual_path: &[P]) -> Response {
|
2020-10-31 19:53:03 +00:00
|
|
|
debug!("Dir: {}", dir.as_ref().display());
|
2020-11-23 14:39:45 +00:00
|
|
|
let dir = dir.as_ref();
|
|
|
|
let dir = match dir.canonicalize() {
|
|
|
|
Ok(dir) => dir,
|
|
|
|
Err(e) => {
|
|
|
|
match e.kind() {
|
|
|
|
std::io::ErrorKind::NotFound => {
|
|
|
|
warn!("Path {} not found. Check your configuration.", dir.display());
|
2020-12-01 21:36:29 +00:00
|
|
|
return Response::temporary_failure("Server incorrectly configured")
|
2020-11-23 14:39:45 +00:00
|
|
|
},
|
|
|
|
std::io::ErrorKind::PermissionDenied => {
|
|
|
|
warn!("Permission denied for {}. Check that the server has access.", dir.display());
|
2020-12-01 21:36:29 +00:00
|
|
|
return Response::temporary_failure("Server incorrectly configured")
|
2020-11-23 14:39:45 +00:00
|
|
|
},
|
|
|
|
_ => return warn_unexpected(e, dir, line!()),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
2020-10-31 19:53:03 +00:00
|
|
|
let mut path = dir.to_path_buf();
|
|
|
|
|
|
|
|
for segment in virtual_path {
|
|
|
|
path.push(segment);
|
|
|
|
}
|
|
|
|
|
2020-11-23 14:39:45 +00:00
|
|
|
let path = match path.canonicalize() {
|
|
|
|
Ok(dir) => dir,
|
|
|
|
Err(e) => {
|
|
|
|
match e.kind() {
|
2020-12-01 21:36:29 +00:00
|
|
|
std::io::ErrorKind::NotFound => return Response::not_found(),
|
2020-11-23 14:39:45 +00:00
|
|
|
std::io::ErrorKind::PermissionDenied => {
|
|
|
|
// Runs when asked to serve a file in a restricted dir
|
|
|
|
// i.e. not /noaccess, but /noaccess/file
|
|
|
|
warn!("Asked to serve {}, but permission denied by OS", path.display());
|
2020-12-01 21:36:29 +00:00
|
|
|
return Response::not_found();
|
2020-11-23 14:39:45 +00:00
|
|
|
},
|
|
|
|
_ => return warn_unexpected(e, path.as_ref(), line!()),
|
|
|
|
}
|
|
|
|
},
|
|
|
|
};
|
2020-10-31 19:53:03 +00:00
|
|
|
|
|
|
|
if !path.starts_with(&dir) {
|
2020-12-01 21:36:29 +00:00
|
|
|
return Response::not_found();
|
2020-10-31 19:53:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if !path.is_dir() {
|
|
|
|
let mime = guess_mime_from_path(&path);
|
2020-12-01 02:18:37 +00:00
|
|
|
return serve_file(path, mime).await;
|
2020-10-31 19:53:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
serve_dir_listing(path, virtual_path).await
|
|
|
|
}
|
|
|
|
|
2020-11-19 07:02:11 +00:00
|
|
|
#[cfg(feature="serve_dir")]
|
2020-12-01 21:36:29 +00:00
|
|
|
async fn serve_dir_listing<P: AsRef<Path>, B: AsRef<Path>>(path: P, virtual_path: &[B]) -> Response {
|
2020-11-23 14:39:45 +00:00
|
|
|
let mut dir = match fs::read_dir(path.as_ref()).await {
|
2020-10-31 19:53:03 +00:00
|
|
|
Ok(dir) => dir,
|
|
|
|
Err(err) => match err.kind() {
|
2020-12-01 21:36:29 +00:00
|
|
|
io::ErrorKind::NotFound => return Response::not_found(),
|
2020-11-23 14:39:45 +00:00
|
|
|
std::io::ErrorKind::PermissionDenied => {
|
|
|
|
warn!("Asked to serve {}, but permission denied by OS", path.as_ref().display());
|
2020-12-01 21:36:29 +00:00
|
|
|
return Response::not_found();
|
2020-11-23 14:39:45 +00:00
|
|
|
},
|
|
|
|
_ => return warn_unexpected(err, path.as_ref(), line!()),
|
2020-10-31 19:53:03 +00:00
|
|
|
}
|
|
|
|
};
|
|
|
|
|
2020-11-19 07:02:11 +00:00
|
|
|
let breadcrumbs: PathBuf = virtual_path.iter().collect();
|
2020-11-14 08:55:21 +00:00
|
|
|
let mut document = Document::new();
|
|
|
|
|
2020-11-19 07:02:11 +00:00
|
|
|
document.add_heading(H1, format!("Index of /{}", breadcrumbs.display()));
|
2020-11-14 08:55:21 +00:00
|
|
|
document.add_blank_line();
|
2020-10-31 19:53:03 +00:00
|
|
|
|
|
|
|
if virtual_path.get(0).map(<_>::as_ref) != Some(Path::new("")) {
|
2020-11-14 08:55:21 +00:00
|
|
|
document.add_link("..", "📁 ../");
|
2020-10-31 19:53:03 +00:00
|
|
|
}
|
|
|
|
|
2020-12-01 21:36:29 +00:00
|
|
|
while let Some(entry) = dir.next_entry().await.expect("Failed to list directory") {
|
2020-10-31 19:53:03 +00:00
|
|
|
let file_name = entry.file_name();
|
|
|
|
let file_name = file_name.to_string_lossy();
|
2020-12-01 21:36:29 +00:00
|
|
|
let is_dir = entry.file_type().await.unwrap().is_dir();
|
2020-11-14 08:55:21 +00:00
|
|
|
let trailing_slash = if is_dir { "/" } else { "" };
|
|
|
|
let uri = format!("./{}{}", file_name, trailing_slash);
|
2020-10-31 19:53:03 +00:00
|
|
|
|
2020-11-14 08:55:21 +00:00
|
|
|
document.add_link(uri.as_str(), format!("{icon} {name}{trailing_slash}",
|
2020-10-31 19:53:03 +00:00
|
|
|
icon = if is_dir { '📁' } else { '📄' },
|
|
|
|
name = file_name,
|
2020-11-14 08:55:21 +00:00
|
|
|
trailing_slash = trailing_slash
|
|
|
|
));
|
2020-10-31 19:53:03 +00:00
|
|
|
}
|
|
|
|
|
2020-12-01 21:36:29 +00:00
|
|
|
document.into()
|
2020-10-31 19:53:03 +00:00
|
|
|
}
|
|
|
|
|
2020-11-19 07:02:11 +00:00
|
|
|
#[cfg(feature="serve_dir")]
|
2020-12-01 02:18:37 +00:00
|
|
|
pub fn guess_mime_from_path<P: AsRef<Path>>(path: P) -> &'static str {
|
2020-10-31 19:53:03 +00:00
|
|
|
let path = path.as_ref();
|
|
|
|
let extension = path.extension().and_then(|s| s.to_str());
|
2020-11-18 21:58:23 +00:00
|
|
|
let extension = match extension {
|
|
|
|
Some(extension) => extension,
|
2020-12-01 02:18:37 +00:00
|
|
|
None => return "application/octet-stream"
|
2020-10-31 19:53:03 +00:00
|
|
|
};
|
2020-11-15 06:01:38 +00:00
|
|
|
|
2020-11-18 21:58:23 +00:00
|
|
|
if let "gemini" | "gmi" = extension {
|
2020-12-01 02:18:37 +00:00
|
|
|
return "text/gemini";
|
2020-11-18 21:58:23 +00:00
|
|
|
}
|
|
|
|
|
2020-12-01 02:18:37 +00:00
|
|
|
mime_guess::from_ext(extension).first_raw().unwrap_or("application/octet-stream")
|
2020-10-31 19:53:03 +00:00
|
|
|
}
|
2020-11-14 21:46:29 +00:00
|
|
|
|
2020-11-23 14:39:45 +00:00
|
|
|
#[cfg(feature="serve_dir")]
|
|
|
|
/// Print a warning to the log asking to file an issue and respond with "Unexpected Error"
|
2020-12-01 21:36:29 +00:00
|
|
|
pub (crate) fn warn_unexpected(err: impl std::fmt::Debug, path: &Path, line: u32) -> Response {
|
2020-11-23 14:39:45 +00:00
|
|
|
warn!(
|
|
|
|
concat!(
|
|
|
|
"Unexpected error serving path {} at util.rs:{}, please report to ",
|
|
|
|
env!("CARGO_PKG_REPOSITORY"),
|
|
|
|
"/issues: {:?}",
|
|
|
|
),
|
|
|
|
path.display(),
|
|
|
|
line,
|
|
|
|
err
|
|
|
|
);
|
2020-12-01 21:36:29 +00:00
|
|
|
Response::temporary_failure("Unexpected error")
|
2020-11-23 14:39:45 +00:00
|
|
|
}
|
|
|
|
|
2020-11-14 21:46:29 +00:00
|
|
|
/// A convenience trait alias for `AsRef<T> + Into<T::Owned>`,
|
|
|
|
/// most commonly used to accept `&str` or `String`:
|
|
|
|
///
|
|
|
|
/// `Cowy<str>` ⇔ `AsRef<str> + Into<String>`
|
|
|
|
pub trait Cowy<T>
|
|
|
|
where
|
|
|
|
Self: AsRef<T> + Into<T::Owned>,
|
|
|
|
T: ToOwned + ?Sized,
|
|
|
|
{}
|
|
|
|
|
|
|
|
impl<C, T> Cowy<T> for C
|
|
|
|
where
|
|
|
|
C: AsRef<T> + Into<T::Owned>,
|
|
|
|
T: ToOwned + ?Sized,
|
|
|
|
{}
|