diff options
-rw-r--r-- | Cargo.lock | 38 | ||||
-rw-r--r-- | Cargo.toml | 5 | ||||
-rw-r--r-- | README.md | 4 | ||||
-rw-r--r-- | src/args.rs | 217 | ||||
-rw-r--r-- | src/listing.rs | 42 | ||||
-rw-r--r-- | src/main.rs | 4 |
6 files changed, 144 insertions, 166 deletions
@@ -536,6 +536,14 @@ dependencies = [ ] [[package]] +name = "heck" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "hostname" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -716,6 +724,7 @@ dependencies = [ "nanoid 0.2.0 (registry+https://github.com/rust-lang/crates.io-index)", "percent-encoding 1.0.1 (registry+https://github.com/rust-lang/crates.io-index)", "simplelog 0.5.3 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", "yansi 0.5.0 (registry+https://github.com/rust-lang/crates.io-index)", ] @@ -1246,6 +1255,26 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" [[package]] +name = "structopt" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "clap 2.32.0 (registry+https://github.com/rust-lang/crates.io-index)", + "structopt-derive 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] +name = "structopt-derive" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +dependencies = [ + "heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)", + "proc-macro2 0.4.27 (registry+https://github.com/rust-lang/crates.io-index)", + "quote 0.6.11 (registry+https://github.com/rust-lang/crates.io-index)", + "syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)", +] + +[[package]] name = "syn" version = "0.15.26" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1605,6 +1634,11 @@ dependencies = [ ] [[package]] +name = "unicode-segmentation" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" + +[[package]] name = "unicode-width" version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" @@ -1816,6 +1850,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum futures 0.1.25 (registry+https://github.com/rust-lang/crates.io-index)" = "49e7653e374fe0d0c12de4250f0bdb60680b8c80eed558c5c7538eec9c89e21b" "checksum futures-cpupool 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "ab90cde24b3319636588d0c35fe03b1333857621051837ed769faefb4c2162e4" "checksum h2 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)" = "ddb2b25a33e231484694267af28fec74ac63b5ccf51ee2065a5e313b834d836e" +"checksum heck 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "20564e78d53d2bb135c343b3f47714a56af2061f1c928fdb541dc7b9fdd94205" "checksum hostname 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "21ceb46a83a85e824ef93669c8b390009623863b5c195d1ba747292c0c72f94e" "checksum htmlescape 0.3.1 (registry+https://github.com/rust-lang/crates.io-index)" = "e9025058dae765dee5070ec375f591e2ba14638c63feff74f13805a72e523163" "checksum http 0.1.15 (registry+https://github.com/rust-lang/crates.io-index)" = "1a10e5b573b9a0146545010f50772b9e8b1dd0a256564cc4307694c68832a2f5" @@ -1902,6 +1937,8 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum stable_deref_trait 1.1.1 (registry+https://github.com/rust-lang/crates.io-index)" = "dba1a27d3efae4351c8051072d619e3ade2820635c3958d826bfea39d59b54c8" "checksum string 0.1.3 (registry+https://github.com/rust-lang/crates.io-index)" = "b639411d0b9c738748b5397d5ceba08e648f4f1992231aa859af1a017f31f60b" "checksum strsim 0.7.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bb4f380125926a99e52bc279241539c018323fab05ad6368b56f93d9369ff550" +"checksum structopt 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "670ad348dc73012fcf78c71f06f9d942232cdd4c859d4b6975e27836c3efc0c3" +"checksum structopt-derive 0.2.14 (registry+https://github.com/rust-lang/crates.io-index)" = "ef98172b1a00b0bec738508d3726540edcbd186d50dfd326f2b1febbb3559f04" "checksum syn 0.15.26 (registry+https://github.com/rust-lang/crates.io-index)" = "f92e629aa1d9c827b2bb8297046c1ccffc57c99b947a680d3ccff1f136a3bee9" "checksum synstructure 0.10.1 (registry+https://github.com/rust-lang/crates.io-index)" = "73687139bf99285483c96ac0add482c3776528beac1d97d444f6e91f203a2015" "checksum term 0.5.1 (registry+https://github.com/rust-lang/crates.io-index)" = "5e6b677dd1e8214ea1ef4297f85dbcbed8e8cdddb561040cc998ca2551c37561" @@ -1932,6 +1969,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" "checksum unicase 2.2.0 (registry+https://github.com/rust-lang/crates.io-index)" = "9d3218ea14b4edcaccfa0df0a64a3792a2c32cc706f1b336e48867f9d3147f90" "checksum unicode-bidi 0.3.4 (registry+https://github.com/rust-lang/crates.io-index)" = "49f2bd0c6468a8230e1db229cff8029217cf623c767ea5d60bfbd42729ea54d5" "checksum unicode-normalization 0.1.8 (registry+https://github.com/rust-lang/crates.io-index)" = "141339a08b982d942be2ca06ff8b076563cbe223d1befd5450716790d44e2426" +"checksum unicode-segmentation 1.2.1 (registry+https://github.com/rust-lang/crates.io-index)" = "aa6024fc12ddfd1c6dbc14a80fa2324d4568849869b779f6bd37e5e4c03344d1" "checksum unicode-width 0.1.5 (registry+https://github.com/rust-lang/crates.io-index)" = "882386231c45df4700b275c7ff55b6f3698780a650026380e72dabe76fa46526" "checksum unicode-xid 0.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "fc72304796d0818e357ead4e000d19c9c174ab23dc11093ac919054d20a6a7fc" "checksum unreachable 1.0.0 (registry+https://github.com/rust-lang/crates.io-index)" = "382810877fe448991dfc7f0dd6e3ae5d58088fd0ea5e35189655f84e6814fa56" @@ -2,7 +2,7 @@ name = "miniserve" version = "0.3.0" description = "For when you really just want to serve some files over HTTP right now!" -authors = ["Sven-Hendrik Haase <svenstaro@gmail.com>"] +authors = ["Sven-Hendrik Haase <svenstaro@gmail.com>", "Boastful Squirrel <boastful.squirrel@gmail.com>"] repository = "https://github.com/svenstaro/miniserve" license = "MIT" readme = "README.md" @@ -28,4 +28,5 @@ percent-encoding = "1.0.1" htmlescape = "0.3.1" bytesize = "1.0.0" nanoid = "0.2.0" -alphanumeric-sort = "1.0.6"
\ No newline at end of file +alphanumeric-sort = "1.0.6" +structopt = "0.2.14"
\ No newline at end of file @@ -30,12 +30,12 @@ Sometimes this is just a more practical and quick way than doing things properly ### Generate random 6-hexdigit URL: - miniserve --random-route -i 192.168.0.1 /tmp + miniserve -i 192.168.0.1 --random-route /tmp # Serving path /private/tmp at http://192.168.0.1/c789b6 ### Bind to multiple interfaces: - miniserve -i 192.168.0.1 -i 10.13.37.10 -i ::1 -- /tmp/myshare + miniserve -i 192.168.0.1 -i 10.13.37.10 -i ::1 /tmp/myshare ### Sort files for easier navigation miniserve --sort=natural /tmp/myshare # (default behaviour) diff --git a/src/args.rs b/src/args.rs index ae86108..637c224 100644 --- a/src/args.rs +++ b/src/args.rs @@ -1,173 +1,122 @@ -use crate::auth; -use crate::listing; -use clap::{crate_authors, crate_description, crate_name, crate_version}; use std::net::{IpAddr, Ipv4Addr, Ipv6Addr}; use std::path::PathBuf; +use structopt::StructOpt; + +use crate::auth; +use crate::listing; /// Possible characters for random routes const ROUTE_ALPHABET: [char; 16] = [ '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'a', 'b', 'c', 'd', 'e', 'f', ]; -/// Checks wether a path is valid, i.e. it exists on the system and points to a file/directory -fn is_valid_path(path: String) -> Result<(), String> { - let path_to_check = PathBuf::from(path); - if path_to_check.is_file() || path_to_check.is_dir() { - return Ok(()); - } - Err(String::from( - "Path either doesn't exist or is not a regular file or a directory", - )) -} +#[derive(StructOpt, Debug)] +#[structopt( + name = "miniserve", + raw(global_settings = "&[structopt::clap::AppSettings::ColoredHelp]") +)] +struct CLIArgs { + /// Be verbose, includes emitting access logs + #[structopt(short = "v", long = "verbose")] + verbose: bool, -/// Checks wether a port is valid -fn is_valid_port(port: String) -> Result<(), String> { - port.parse::<u16>() - .and(Ok(())) - .or_else(|e| Err(e.to_string())) -} + /// Which path to serve + #[structopt(name = "PATH", parse(from_os_str))] + path: Option<PathBuf>, + + /// Port to use + #[structopt(short = "p", long = "port", default_value = "8080")] + port: u16, + /// Interface to listen on + #[structopt( + short = "i", + long = "if", + parse(try_from_str = "parse_interface"), + raw(number_of_values = "1") + )] + interfaces: Vec<IpAddr>, + + /// Set authentication (username:password) + #[structopt(short = "a", long = "auth", parse(try_from_str = "parse_auth"))] + auth: Option<(String, String)>, + + /// Generate a random 6-hexdigit route + #[structopt(long = "random-route")] + random_route: bool, + + /// Sort files + #[structopt( + short = "s", + long = "sort", + raw( + possible_values = "&listing::SortingMethods::variants()", + case_insensitive = "true" + ) + )] + sort_method: Option<listing::SortingMethods>, + + /// Reverse sorting + #[structopt(long = "reverse")] + reverse_sort: bool, + + /// Do not follow symbolic links + #[structopt(short = "P", long = "no-symlinks")] + no_symlinks: bool, +} /// Checks wether an interface is valid, i.e. it can be parsed into an IP address -fn is_valid_interface(interface: String) -> Result<(), String> { - interface - .parse::<IpAddr>() - .and(Ok(())) - .or_else(|e| Err(e.to_string())) +fn parse_interface(src: &str) -> Result<IpAddr, std::net::AddrParseError> { + src.parse::<IpAddr>() } /// Checks wether the auth string is valid, i.e. it follows the syntax username:password -fn is_valid_auth(auth: String) -> Result<(), String> { - auth.find(':') - .ok_or_else(|| "Correct format is username:password".to_owned()) - .map(|_| ()) +fn parse_auth(src: &str) -> Result<(String, String), String> { + match src.find(':') { + Some(_) => { + let split = src.split(':').collect::<Vec<_>>(); + Ok((split[0].to_owned(), split[1].to_owned())) + } + None => Err("Correct format is username:password".to_owned()), + } } /// Parses the command line arguments pub fn parse_args() -> crate::MiniserveConfig { - use clap::{App, AppSettings, Arg}; - - let matches = App::new(crate_name!()) - .version(crate_version!()) - .author(crate_authors!()) - .about(crate_description!()) - .global_setting(AppSettings::ColoredHelp) - .arg( - Arg::with_name("verbose") - .short("v") - .long("verbose") - .help("Be verbose, includes emitting access logs"), - ) - .arg( - Arg::with_name("PATH") - .required(false) - .validator(is_valid_path) - .help("Which path to serve"), - ) - .arg( - Arg::with_name("port") - .short("p") - .long("port") - .help("Port to use") - .validator(is_valid_port) - .required(false) - .default_value("8080") - .takes_value(true), - ) - .arg( - Arg::with_name("interfaces") - .short("i") - .long("if") - .help("Interface to listen on") - .validator(is_valid_interface) - .required(false) - .takes_value(true) - .multiple(true), - ) - .arg( - Arg::with_name("auth") - .short("a") - .long("auth") - .validator(is_valid_auth) - .help("Set authentication (username:password)") - .takes_value(true), - ) - .arg( - Arg::with_name("random-route") - .long("random-route") - .help("Generate a random 6-hexdigit route"), - ) - .arg( - Arg::with_name("sort") - .short("s") - .long("sort") - .possible_values(&["natural", "alpha", "dirsfirst"]) - .default_value("natural") - .help("Sort files"), - ) - .arg( - Arg::with_name("reverse") - .long("reverse") - .help("Reverse sorting order"), - ) - .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") { - interfaces.map(|x| x.parse().unwrap()).collect() + let args = CLIArgs::from_args(); + + let interfaces = if !args.interfaces.is_empty() { + args.interfaces } else { vec![ IpAddr::V6(Ipv6Addr::new(0, 0, 0, 0, 0, 0, 0, 0)), IpAddr::V4(Ipv4Addr::new(0, 0, 0, 0)), ] }; - let auth = if let Some(auth_split) = matches.value_of("auth").map(|x| x.splitn(2, ':')) { - let auth_vec = auth_split.collect::<Vec<&str>>(); - if auth_vec.len() == 2 { - Some(auth::BasicAuthParams { - username: auth_vec[0].to_owned(), - password: auth_vec[1].to_owned(), - }) - } else { - None - } - } else { - None + + let auth = match args.auth { + Some((username, password)) => Some(auth::BasicAuthParams { username, password }), + None => None, }; - let random_route = if matches.is_present("random-route") { + let random_route = if args.random_route { Some(nanoid::custom(6, &ROUTE_ALPHABET)) } else { None }; - let sort_method = matches - .value_of("sort") - .unwrap() - .parse::<listing::SortingMethods>() - .unwrap(); - - let reverse_sort = matches.is_present("reverse"); + let path_explicitly_chosen = args.path.is_some(); crate::MiniserveConfig { - verbose, - path: PathBuf::from(path.unwrap_or(".")), - port, + verbose: args.verbose, + path: args.path.unwrap_or_else(|| PathBuf::from(".")), + port: args.port, interfaces, auth, - path_explicitly_chosen: path.is_some(), - no_symlinks, + path_explicitly_chosen, + no_symlinks: args.no_symlinks, random_route, - sort_method, - reverse_sort, + sort_method: args.sort_method.unwrap_or(listing::SortingMethods::Natural), + reverse_sort: args.reverse_sort, } } diff --git a/src/listing.rs b/src/listing.rs index f0662ef..056c847 100644 --- a/src/listing.rs +++ b/src/listing.rs @@ -1,31 +1,34 @@ use actix_web::{fs, HttpRequest, HttpResponse, Result}; use bytesize::ByteSize; +use clap::{_clap_count_exprs, arg_enum}; 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, Copy, Debug)] -/// Available sorting methods -pub enum SortingMethods { - /// Natural sorting method +arg_enum! { + #[derive(Clone, Copy, Debug)] + /// Available sorting methods + /// + /// Natural: natural sorting method /// 1 -> 2 -> 3 -> 11 - Natural, - - /// Pure alphabetical sorting method + /// + /// Alpha: pure alphabetical sorting method /// 1 -> 11 -> 2 -> 3 - Alpha, - - /// Directories are listed first, alphabetical sorting is also applied + /// + /// DirsFirst: directories are listed first, alphabetical sorting is also applied /// 1/ -> 2/ -> 3/ -> 11 -> 12 - DirsFirst, + pub enum SortingMethods { + Natural, + Alpha, + DirsFirst, + } } #[derive(PartialEq)] -/// Possible entry types +/// Possible entry types enum EntryType { /// Entry is a directory Directory, @@ -75,19 +78,6 @@ impl Entry { } } -impl FromStr for SortingMethods { - type Err = (); - - fn from_str(s: &str) -> Result<SortingMethods, ()> { - match s { - "natural" => Ok(SortingMethods::Natural), - "alpha" => Ok(SortingMethods::Alpha), - "dirsfirst" => Ok(SortingMethods::DirsFirst), - _ => Err(()), - } - } -} - pub fn file_handler(req: &HttpRequest<crate::MiniserveConfig>) -> Result<fs::NamedFile> { let path = &req.state().path; Ok(fs::NamedFile::open(path)?) diff --git a/src/main.rs b/src/main.rs index 7c31976..378a4d0 100644 --- a/src/main.rs +++ b/src/main.rs @@ -16,11 +16,11 @@ mod listing; pub struct MiniserveConfig { /// Enable verbose mode pub verbose: bool, - + /// Path to be served by miniserve pub path: std::path::PathBuf, - /// Port on which miniserve will be listening + /// Port on which miniserve will be listening pub port: u16, /// IP address(es) on which miniserve will be available |