From 066535edaefa2c1f34531d8be5ad280d914a9edd Mon Sep 17 00:00:00 2001 From: boasting-squirrel Date: Tue, 12 Feb 2019 20:20:57 +0100 Subject: Split project into multiple files --- src/listing.rs | 245 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 245 insertions(+) create mode 100644 src/listing.rs (limited to 'src/listing.rs') diff --git a/src/listing.rs b/src/listing.rs new file mode 100644 index 0000000..142018a --- /dev/null +++ b/src/listing.rs @@ -0,0 +1,245 @@ +use actix_web::{fs, HttpRequest, HttpResponse, Result}; +use bytesize::ByteSize; +use htmlescape::encode_minimal as escape_html_entity; +use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; +use std::cmp::Ordering; +use std::fmt::Write as FmtWrite; +use std::io; +use std::path::Path; +use std::str::FromStr; + +#[derive(Clone, Debug)] +pub enum SortingMethods { + Natural, + Alpha, + DirsFirst, +} + +#[derive(PartialEq)] +enum EntryType { + Directory, + File, +} + +impl PartialOrd for EntryType { + fn partial_cmp(&self, other: &EntryType) -> Option { + match (self, other) { + (EntryType::Directory, EntryType::File) => Some(Ordering::Less), + (EntryType::File, EntryType::Directory) => Some(Ordering::Greater), + _ => Some(Ordering::Equal), + } + } +} + +struct Entry { + name: String, + entry_type: EntryType, + link: String, + size: Option, +} + +impl Entry { + fn new( + name: String, + entry_type: EntryType, + link: String, + size: Option, + ) -> Self { + Entry { + name, + entry_type, + link, + size, + } + } +} + +impl FromStr for SortingMethods { + type Err = (); + + fn from_str(s: &str) -> Result { + match s { + "natural" => Ok(SortingMethods::Natural), + "alpha" => Ok(SortingMethods::Alpha), + "dirsfirst" => Ok(SortingMethods::DirsFirst), + _ => Err(()), + } + } +} + +// ↓ Adapted from https://docs.rs/actix-web/0.7.13/src/actix_web/fs.rs.html#564 +pub fn directory_listing( + dir: &fs::Directory, + req: &HttpRequest, + skip_symlinks: bool, + random_route: Option, + sort_method: SortingMethods, + reverse_sort: bool, +) -> Result { + let index_of = format!("Index of {}", req.path()); + let mut body = String::new(); + let base = Path::new(req.path()); + let random_route = format!("/{}", random_route.unwrap_or_default()); + + if let Some(parent) = base.parent() { + if req.path() != random_route { + let _ = write!( + body, + "..", + parent.display() + ); + } + } + + let mut entries: Vec = Vec::new(); + + for entry in dir.path.read_dir()? { + if dir.is_visible(&entry) { + let entry = entry.unwrap(); + let p = match entry.path().strip_prefix(&dir.path) { + Ok(p) => base.join(p), + Err(_) => continue, + }; + // show file url as relative to static path + let file_url = + utf8_percent_encode(&p.to_string_lossy(), DEFAULT_ENCODE_SET).to_string(); + // " -- " & -- & ' -- ' < -- < > -- > + let file_name = escape_html_entity(&entry.file_name().to_string_lossy()); + + // if file is a directory, add '/' to the end of the name + if let Ok(metadata) = entry.metadata() { + if skip_symlinks && metadata.file_type().is_symlink() { + continue; + } + if metadata.is_dir() { + entries.push(Entry::new(file_name, EntryType::Directory, file_url, None)); + } else { + entries.push(Entry::new( + file_name, + EntryType::File, + file_url, + Some(ByteSize::b(metadata.len())), + )); + } + } else { + continue; + } + } + } + + match sort_method { + SortingMethods::Natural => entries + .sort_by(|e1, e2| alphanumeric_sort::compare_str(e1.name.clone(), e2.name.clone())), + SortingMethods::Alpha => { + entries.sort_by(|e1, e2| e1.entry_type.partial_cmp(&e2.entry_type).unwrap()); + entries.sort_by_key(|e| e.name.clone()) + } + SortingMethods::DirsFirst => { + entries.sort_by_key(|e| e.name.clone()); + entries.sort_by(|e1, e2| e1.entry_type.partial_cmp(&e2.entry_type).unwrap()); + } + }; + + if reverse_sort { + entries.reverse(); + } + + for entry in entries { + match entry.entry_type { + EntryType::Directory => { + let _ = write!( + body, + "{}/", + entry.link, entry.name + ); + } + EntryType::File => { + let _ = write!( + body, + "{}{}", + entry.link, + entry.name, + entry.size.unwrap() + ); + } + } + } + + let html = format!( + "\ + \ + {}\ + \ + \ +

{}

\ + \ + \ + \ + {}\ +
NameSize
\n", + index_of, index_of, body + ); + Ok(HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(html)) +} -- cgit v1.2.3