dufs/src/args.rs

240 lines
7.7 KiB
Rust
Raw Normal View History

2022-05-26 11:17:55 +03:00
use clap::crate_description;
use clap::{Arg, ArgMatches};
2022-06-02 06:06:41 +03:00
use rustls::{Certificate, PrivateKey};
2022-06-06 05:52:12 +03:00
use std::net::{IpAddr, SocketAddr};
2022-05-26 11:17:55 +03:00
use std::path::{Path, PathBuf};
2022-06-02 06:06:41 +03:00
use std::{env, fs, io};
2022-05-26 11:17:55 +03:00
use crate::auth::parse_auth;
2022-05-26 11:17:55 +03:00
use crate::BoxResult;
const ABOUT: &str = concat!("\n", crate_description!()); // Add extra newline.
fn app() -> clap::Command<'static> {
clap::command!()
.about(ABOUT)
.arg(
Arg::new("address")
.short('b')
.long("bind")
2022-06-03 06:18:46 +03:00
.default_value("0.0.0.0")
.help("Specify bind address")
.value_name("address"),
)
.arg(
Arg::new("port")
.short('p')
.long("port")
.default_value("5000")
.help("Specify port to listen on")
.value_name("port"),
)
.arg(
Arg::new("path")
.default_value(".")
.allow_invalid_utf8(true)
2022-05-31 15:53:14 +03:00
.help("Path to a root directory for serving files"),
)
2022-06-02 03:32:31 +03:00
.arg(
Arg::new("path-prefix")
.long("path-prefix")
.value_name("path")
.help("Specify an url path prefix"),
)
.arg(
Arg::new("allow-all")
.short('A')
.long("allow-all")
.help("Allow all operations"),
)
.arg(
Arg::new("allow-upload")
.long("allow-upload")
2022-06-02 14:32:19 +03:00
.help("Allow upload files/folders"),
)
.arg(
Arg::new("allow-delete")
2022-05-31 17:38:00 +03:00
.long("allow-delete")
2022-06-02 14:32:19 +03:00
.help("Allow delete files/folders"),
)
2022-05-31 15:53:14 +03:00
.arg(
Arg::new("allow-symlink")
2022-05-31 17:38:00 +03:00
.long("allow-symlink")
2022-06-02 14:32:19 +03:00
.help("Allow symlink to files/folders outside root directory"),
2022-05-31 15:53:14 +03:00
)
.arg(
Arg::new("render-index")
.long("render-index")
2022-06-02 12:06:22 +03:00
.help("Render index.html when requesting a directory"),
)
.arg(
Arg::new("render-spa")
.long("render-spa")
2022-06-02 12:06:22 +03:00
.help("Render for single-page application"),
)
.arg(
Arg::new("auth")
.short('a')
2022-06-02 14:32:19 +03:00
.display_order(1)
.long("auth")
.help("Use HTTP authentication")
.value_name("user:pass"),
)
2022-05-30 07:40:57 +03:00
.arg(
Arg::new("no-auth-access")
2022-06-02 14:32:19 +03:00
.display_order(1)
.long("no-auth-access")
.help("Not required auth when access static files"),
2022-05-30 07:40:57 +03:00
)
2022-05-29 12:33:21 +03:00
.arg(
Arg::new("cors")
.long("cors")
.help("Enable CORS, sets `Access-Control-Allow-Origin: *`"),
)
2022-06-02 06:06:41 +03:00
.arg(
Arg::new("tls-cert")
.long("tls-cert")
.value_name("path")
.help("Path to an SSL/TLS certificate to serve with HTTPS"),
)
.arg(
Arg::new("tls-key")
.long("tls-key")
.value_name("path")
.help("Path to the SSL/TLS certificate's private key"),
)
2022-05-26 11:17:55 +03:00
}
pub fn matches() -> ArgMatches {
app().get_matches()
}
#[derive(Debug, Clone, Eq, PartialEq)]
pub struct Args {
2022-06-06 05:52:12 +03:00
pub addr: SocketAddr,
2022-05-26 11:17:55 +03:00
pub path: PathBuf,
pub path_prefix: String,
pub uri_prefix: String,
pub auth: Option<(String, String)>,
pub no_auth_access: bool,
pub allow_upload: bool,
pub allow_delete: bool,
2022-05-31 15:53:14 +03:00
pub allow_symlink: bool,
pub render_index: bool,
pub render_spa: bool,
2022-05-29 12:33:21 +03:00
pub cors: bool,
2022-06-02 06:06:41 +03:00
pub tls: Option<(Vec<Certificate>, PrivateKey)>,
2022-05-26 11:17:55 +03:00
}
impl Args {
/// Parse command-line arguments.
///
/// If a parsing error ocurred, exit the process and print out informative
/// error message to user.
pub fn parse(matches: ArgMatches) -> BoxResult<Args> {
2022-06-06 05:52:12 +03:00
let ip = matches.value_of("address").unwrap_or_default();
2022-05-26 11:17:55 +03:00
let port = matches.value_of_t::<u16>("port")?;
2022-06-06 05:52:12 +03:00
let addr = to_addr(ip, port)?;
let path = Args::parse_path(matches.value_of_os("path").unwrap_or_default())?;
2022-06-04 07:51:56 +03:00
let path_prefix = matches
.value_of("path-prefix")
.map(|v| v.trim_matches('/').to_owned())
.unwrap_or_default();
let uri_prefix = if path_prefix.is_empty() {
"/".to_owned()
} else {
format!("/{}/", &path_prefix)
};
2022-05-29 12:33:21 +03:00
let cors = matches.is_present("cors");
let auth = match matches.value_of("auth") {
Some(auth) => Some(parse_auth(auth)?),
None => None,
};
let no_auth_access = matches.is_present("no-auth-access");
let allow_upload = matches.is_present("allow-all") || matches.is_present("allow-upload");
let allow_delete = matches.is_present("allow-all") || matches.is_present("allow-delete");
2022-05-31 15:53:14 +03:00
let allow_symlink = matches.is_present("allow-all") || matches.is_present("allow-symlink");
let render_index = matches.is_present("render-index");
let render_spa = matches.is_present("render-spa");
2022-06-02 06:06:41 +03:00
let tls = match (matches.value_of("tls-cert"), matches.value_of("tls-key")) {
(Some(certs_file), Some(key_file)) => {
let certs = load_certs(certs_file)?;
let key = load_private_key(key_file)?;
Some((certs, key))
}
_ => None,
};
2022-05-26 11:17:55 +03:00
Ok(Args {
2022-06-06 05:52:12 +03:00
addr,
2022-05-26 11:17:55 +03:00
path,
2022-06-02 03:32:31 +03:00
path_prefix,
uri_prefix,
2022-05-26 13:06:52 +03:00
auth,
no_auth_access,
2022-05-29 12:33:21 +03:00
cors,
allow_delete,
allow_upload,
2022-05-31 15:53:14 +03:00
allow_symlink,
render_index,
render_spa,
2022-06-02 06:06:41 +03:00
tls,
2022-05-26 11:17:55 +03:00
})
}
/// Parse path.
fn parse_path<P: AsRef<Path>>(path: P) -> BoxResult<PathBuf> {
let path = path.as_ref();
if !path.exists() {
2022-06-03 06:18:46 +03:00
return Err(format!("Path `{}` doesn't exist", path.display()).into());
2022-05-26 11:17:55 +03:00
}
env::current_dir()
.and_then(|mut p| {
p.push(path); // If path is absolute, it replaces the current path.
2022-05-31 17:38:00 +03:00
std::fs::canonicalize(p)
2022-05-26 11:17:55 +03:00
})
2022-06-03 06:18:46 +03:00
.map_err(|err| format!("Failed to access path `{}`: {}", path.display(), err,).into())
2022-05-26 11:17:55 +03:00
}
2022-06-06 05:52:12 +03:00
}
2022-05-26 11:17:55 +03:00
2022-06-06 05:52:12 +03:00
fn to_addr(ip: &str, port: u16) -> BoxResult<SocketAddr> {
let ip: IpAddr = ip.parse()?;
Ok(SocketAddr::new(ip, port))
2022-05-26 11:17:55 +03:00
}
2022-06-02 06:06:41 +03:00
// Load public certificate from file.
2022-06-06 05:52:12 +03:00
fn load_certs(filename: &str) -> BoxResult<Vec<Certificate>> {
2022-06-02 06:06:41 +03:00
// Open certificate file.
2022-06-12 03:43:50 +03:00
let certfile = fs::File::open(&filename)
.map_err(|e| format!("Failed to access `{}`, {}", &filename, e))?;
2022-06-02 06:06:41 +03:00
let mut reader = io::BufReader::new(certfile);
// Load and return certificate.
let certs = rustls_pemfile::certs(&mut reader).map_err(|_| "Failed to load certificate")?;
if certs.is_empty() {
return Err("No supported certificate in file".into());
2022-06-02 06:06:41 +03:00
}
Ok(certs.into_iter().map(Certificate).collect())
}
// Load private key from file.
2022-06-06 05:52:12 +03:00
fn load_private_key(filename: &str) -> BoxResult<PrivateKey> {
2022-06-02 06:06:41 +03:00
// Open keyfile.
2022-06-12 03:43:50 +03:00
let keyfile = fs::File::open(&filename)
.map_err(|e| format!("Failed to access `{}`, {}", &filename, e))?;
2022-06-02 06:06:41 +03:00
let mut reader = io::BufReader::new(keyfile);
// Load and return a single private key.
let keys = rustls_pemfile::read_all(&mut reader)
.map_err(|e| format!("There was a problem with reading private key: {:?}", e))?
.into_iter()
.find_map(|item| match item {
rustls_pemfile::Item::RSAKey(key) | rustls_pemfile::Item::PKCS8Key(key) => Some(key),
_ => None,
})
.ok_or("No supported private key in file")?;
2022-06-02 06:06:41 +03:00
Ok(PrivateKey(keys))
2022-06-02 06:06:41 +03:00
}