diff options
author | Sven-Hendrik Haase <svenstaro@gmail.com> | 2018-11-19 22:01:27 +0000 |
---|---|---|
committer | GitHub <noreply@github.com> | 2018-11-19 22:01:27 +0000 |
commit | c800ef4e535692a956e7e6d629d0d7a6b4586e52 (patch) | |
tree | d27f91083c56c43522fa41abaf95a23eb8139eef /src | |
parent | Bump Cargo.lock (diff) | |
parent | Fix double "symbolic" typo (diff) | |
download | miniserve-c800ef4e535692a956e7e6d629d0d7a6b4586e52.tar.gz miniserve-c800ef4e535692a956e7e6d629d0d7a6b4586e52.zip |
Merge pull request #19 from pebbleKite/skip_symlinks
add option to ignore symlinks
Diffstat (limited to 'src')
-rw-r--r-- | src/main.rs | 89 |
1 files changed, 86 insertions, 3 deletions
diff --git a/src/main.rs b/src/main.rs index 29bb468..5806cab 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,6 +1,8 @@ extern crate actix; extern crate actix_web; extern crate base64; +extern crate htmlescape; +extern crate percent_encoding; extern crate simplelog; extern crate yansi; #[macro_use] @@ -9,10 +11,13 @@ extern crate clap; use actix_web::http::header; use actix_web::middleware::{Middleware, Response}; use actix_web::{fs, middleware, server, App, HttpMessage, HttpRequest, HttpResponse, Result}; +use htmlescape::encode_minimal as escape_html_entity; +use percent_encoding::{utf8_percent_encode, DEFAULT_ENCODE_SET}; use simplelog::{Config, LevelFilter, TermLogger}; +use std::fmt::Write as FmtWrite; use std::io::{self, Write}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr, SocketAddr, ToSocketAddrs}; -use std::path::PathBuf; +use std::path::{Path, PathBuf}; use std::thread; use std::time::Duration; use yansi::{Color, Paint}; @@ -36,6 +41,7 @@ pub struct MiniserveConfig { interfaces: Vec<IpAddr>, auth: Option<BasicAuthParams>, path_explicitly_chosen: bool, + no_symlinks: bool, } /// Decode a HTTP basic auth string into a tuple of username and password. @@ -85,7 +91,7 @@ fn is_valid_auth(auth: String) -> Result<(), String> { } pub fn parse_args() -> MiniserveConfig { - use clap::{App, Arg, AppSettings}; + use clap::{App, AppSettings, Arg}; let matches = App::new(crate_name!()) .version(crate_version!()) @@ -127,9 +133,15 @@ pub fn parse_args() -> MiniserveConfig { .validator(is_valid_auth) .help("Set authentication (username:password)") .takes_value(true), + ).arg( + Arg::with_name("no-symlinks") + .short("P") + .long("no-symlinks") + .help("Do not follow symbolic links"), ).get_matches(); let verbose = matches.is_present("verbose"); + let no_symlinks = matches.is_present("no-symlinks"); let path = matches.value_of("PATH"); let port = matches.value_of("port").unwrap().parse().unwrap(); let interfaces = if let Some(interfaces) = matches.values_of("interfaces") { @@ -161,6 +173,7 @@ pub fn parse_args() -> MiniserveConfig { interfaces, auth, path_explicitly_chosen: path.is_some(), + no_symlinks, } } @@ -172,13 +185,17 @@ fn file_handler(req: &HttpRequest<MiniserveConfig>) -> Result<fs::NamedFile> { fn configure_app(app: App<MiniserveConfig>) -> App<MiniserveConfig> { let s = { let path = &app.state().path; + let no_symlinks = app.state().no_symlinks; if path.is_file() { None } else { Some( fs::StaticFiles::new(path) .expect("Couldn't create path") - .show_files_listing(), + .show_files_listing() + .files_listing_renderer(move |dir, req| { + directory_listing(dir, req, no_symlinks) + }), ) } }; @@ -235,6 +252,19 @@ fn main() { } let miniserve_config = parse_args(); + if miniserve_config.no_symlinks && miniserve_config + .path + .symlink_metadata() + .expect("Can't get file metadata") + .file_type() + .is_symlink() + { + println!( + "{error} The no-symlinks option cannot be used with a symlink path", + error = Paint::red("error:").bold(), + ); + return; + } if miniserve_config.verbose { let _ = TermLogger::init(LevelFilter::Info, Config::default()); @@ -324,3 +354,56 @@ fn main() { let _ = sys.run(); } + +// ↓ Adapted from https://docs.rs/actix-web/0.7.13/src/actix_web/fs.rs.html#564 +fn directory_listing<S>( + dir: &fs::Directory, + req: &HttpRequest<S>, + skip_symlinks: bool, +) -> Result<HttpResponse, io::Error> { + let index_of = format!("Index of {}", req.path()); + let mut body = String::new(); + let base = Path::new(req.path()); + + 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() { + let _ = write!(body, "<li><a href=\"{}\">{}/</a></li>", file_url, file_name); + } else { + let _ = write!(body, "<li><a href=\"{}\">{}</a></li>", file_url, file_name); + } + } else { + continue; + } + } + } + + let html = format!( + "<html>\ + <head><title>{}</title></head>\ + <body><h1>{}</h1>\ + <ul>\ + {}\ + </ul></body>\n</html>", + index_of, index_of, body + ); + Ok(HttpResponse::Ok() + .content_type("text/html; charset=utf-8") + .body(html)) +} |