💾 Archived View for alchemi.dev › en › projects › kochab › files › src › util.rs captured on 2023-11-04 at 11:24:57.
⬅️ Previous capture (2022-07-16)
-=-=-=-=-=-=-
#[cfg(feature="serve_dir")] use std::path::{Path, PathBuf}; use anyhow::*; #[cfg(feature="serve_dir")] use tokio::{ fs::{self, File}, io, }; #[cfg(feature="serve_dir")] use crate::types::{Document, document::HeadingLevel::*}; #[cfg(feature="serve_dir")] use crate::types::Response; use std::future::Future; use tokio::time; #[cfg(feature="serve_dir")] pub async fn serve_file<P: AsRef<Path>>(path: P, mime: &str) -> Response { let path = path.as_ref(); let file = match File::open(path).await { Ok(file) => file, Err(err) => match err.kind() { std::io::ErrorKind::PermissionDenied => { warn!("Asked to serve {}, but permission denied by OS", path.display()); return Response::not_found(); }, _ => return warn_unexpected(err, path, line!()), } }; Response::success(mime, file) } #[cfg(feature="serve_dir")] pub async fn serve_dir<D: AsRef<Path>, P: AsRef<Path>>(dir: D, virtual_path: &[P]) -> Response { debug!("Dir: {}", dir.as_ref().display()); 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()); return Response::temporary_failure("Server incorrectly configured") }, std::io::ErrorKind::PermissionDenied => { warn!("Permission denied for {}. Check that the server has access.", dir.display()); return Response::temporary_failure("Server incorrectly configured") }, _ => return warn_unexpected(e, dir, line!()), } }, }; let mut path = dir.to_path_buf(); for segment in virtual_path { path.push(segment); } let path = match path.canonicalize() { Ok(dir) => dir, Err(e) => { match e.kind() { std::io::ErrorKind::NotFound => return Response::not_found(), 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()); return Response::not_found(); }, _ => return warn_unexpected(e, path.as_ref(), line!()), } }, }; if !path.starts_with(&dir) { return Response::not_found(); } if !path.is_dir() { let mime = guess_mime_from_path(&path); return serve_file(path, mime).await; } serve_dir_listing(path, virtual_path).await } #[cfg(feature="serve_dir")] async fn serve_dir_listing<P: AsRef<Path>, B: AsRef<Path>>(path: P, virtual_path: &[B]) -> Response { let mut dir = match fs::read_dir(path.as_ref()).await { Ok(dir) => dir, Err(err) => match err.kind() { io::ErrorKind::NotFound => return Response::not_found(), std::io::ErrorKind::PermissionDenied => { warn!("Asked to serve {}, but permission denied by OS", path.as_ref().display()); return Response::not_found(); }, _ => return warn_unexpected(err, path.as_ref(), line!()), } }; let breadcrumbs: PathBuf = virtual_path.iter().collect(); let mut document = Document::new(); document.add_heading(H1, format!("Index of /{}", breadcrumbs.display())); document.add_blank_line(); if virtual_path.get(0).map(<_>::as_ref) != Some(Path::new("")) { document.add_link("..", "📁 ../"); } while let Some(entry) = dir.next_entry().await.expect("Failed to list directory") { let file_name = entry.file_name(); let file_name = file_name.to_string_lossy(); let is_dir = entry.file_type().await.unwrap().is_dir(); let trailing_slash = if is_dir { "/" } else { "" }; let uri = format!("./{}{}", file_name, trailing_slash); document.add_link(uri.as_str(), format!("{icon} {name}{trailing_slash}", icon = if is_dir { '📁' } else { '📄' }, name = file_name, trailing_slash = trailing_slash )); } document.into() } #[cfg(feature="serve_dir")] pub fn guess_mime_from_path<P: AsRef<Path>>(path: P) -> &'static str { let path = path.as_ref(); let extension = path.extension().and_then(|s| s.to_str()); let extension = match extension { Some(extension) => extension, None => return "application/octet-stream" }; if let "gemini" | "gmi" = extension { return "text/gemini"; } mime_guess::from_ext(extension).first_raw().unwrap_or("application/octet-stream") } #[cfg(feature="serve_dir")] /// Print a warning to the log asking to file an issue and respond with "Unexpected Error" pub (crate) fn warn_unexpected(err: impl std::fmt::Debug, path: &Path, line: u32) -> Response { warn!( concat!( "Unexpected error serving path {} at util.rs:{}, please report to ", env!("CARGO_PKG_REPOSITORY"), "/issues: {:?}", ), path.display(), line, err ); Response::temporary_failure("Unexpected error") } /// 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, {} pub(crate) async fn opt_timeout<T>(duration: Option<time::Duration>, future: impl Future<Output = T>) -> Result<T, time::error::Elapsed> { match duration { Some(duration) => time::timeout(duration, future).await, None => Ok(future.await), } }