diff options
Diffstat (limited to 'src/args.rs')
-rw-r--r-- | src/args.rs | 146 |
1 files changed, 118 insertions, 28 deletions
diff --git a/src/args.rs b/src/args.rs index 825a4ac..f13d14f 100644 --- a/src/args.rs +++ b/src/args.rs @@ -37,9 +37,9 @@ struct CLIArgs { )] interfaces: Vec<IpAddr>, - /// Set authentication (username:password) + /// Set authentication (username:password, username:sha256:hash, or username:sha512:hash) #[structopt(short = "a", long = "auth", parse(try_from_str = "parse_auth"))] - auth: Option<(String, String)>, + auth: Option<auth::RequiredAuth>, /// Generate a random 6-hexdigit route #[structopt(long = "random-route")] @@ -76,36 +76,47 @@ fn parse_interface(src: &str) -> Result<IpAddr, std::net::AddrParseError> { } /// Checks wether the auth string is valid, i.e. it follows the syntax username:password -fn parse_auth(src: &str) -> Result<(String, String), String> { - let mut split = src.splitn(2, ':'); +fn parse_auth(src: &str) -> Result<auth::RequiredAuth, String> { + let mut split = src.splitn(3, ':'); + let errmsg = "Invalid credentials string, expected format is username:password".to_owned(); let username = match split.next() { Some(username) => username, - None => { - return Err( - "Invalid credentials string, expected format is username:password".to_owned(), - ) - } + None => return Err(errmsg), }; - let password = match split.next() { + let second_part = match split.next() { // This allows empty passwords, as the spec does not forbid it Some(password) => password, - None => { - return Err( - "Invalid credentials string, expected format is username:password".to_owned(), - ) - } + None => return Err(errmsg), }; - // To make it Windows-compatible, the password needs to be shorter than 255 characters. - // After 255 characters, Windows will truncate the value. - // As for the username, the spec does not mention a limit in length - if password.len() > 255 { - return Err("Password length cannot exceed 255 characters".to_owned()); - } + let password = if let Some(hash_hex) = split.next() { + let hash_bin = match hex::decode(hash_hex) { + Ok(hash_bin) => hash_bin, + _ => return Err("Hash string is not a valid hex code".to_owned()), + }; + + match second_part { + "sha256" => auth::RequiredAuthPassword::Sha256(hash_bin.to_owned()), + "sha512" => auth::RequiredAuthPassword::Sha512(hash_bin.to_owned()), + _ => return Err("Invalid hash method, only accept either sha256 or sha512".to_owned()), + } + } else { + // To make it Windows-compatible, the password needs to be shorter than 255 characters. + // After 255 characters, Windows will truncate the value. + // As for the username, the spec does not mention a limit in length + if second_part.len() > 255 { + return Err("Password length cannot exceed 255 characters".to_owned()); + } + + auth::RequiredAuthPassword::Plain(second_part.to_owned()) + }; - Ok((username.to_owned(), password.to_owned())) + Ok(auth::RequiredAuth { + username: username.to_owned(), + password, + }) } /// Parses the command line arguments @@ -121,11 +132,6 @@ pub fn parse_args() -> crate::MiniserveConfig { ] }; - let auth = match args.auth { - Some((username, password)) => Some(auth::BasicAuthParams { username, password }), - None => None, - }; - let random_route = if args.random_route { Some(nanoid::custom(6, &ROUTE_ALPHABET)) } else { @@ -141,7 +147,7 @@ pub fn parse_args() -> crate::MiniserveConfig { path: args.path.unwrap_or_else(|| PathBuf::from(".")), port: args.port, interfaces, - auth, + auth: args.auth, path_explicitly_chosen, no_symlinks: args.no_symlinks, random_route, @@ -150,3 +156,87 @@ pub fn parse_args() -> crate::MiniserveConfig { file_upload: args.file_upload, } } + +#[cfg(test)] +mod tests { + use super::*; + + fn create_required_auth(username: &str, password: &str, encrypt: &str) -> auth::RequiredAuth { + use auth::*; + use RequiredAuthPassword::*; + + RequiredAuth { + username: username.to_owned(), + password: match encrypt { + "plain" => Plain(password.to_owned()), + "sha256" => Sha256(hex::decode(password.to_owned()).unwrap()), + "sha512" => Sha512(hex::decode(password.to_owned()).unwrap()), + _ => panic!("Unknown encryption type"), + }, + } + } + + #[test] + fn parse_auth_plain() -> Result<(), String> { + assert_eq!( + parse_auth("username:password")?, + create_required_auth("username", "password", "plain") + ); + + Ok(()) + } + + #[test] + fn parse_auth_sha256() -> Result<(), String> { + assert_eq!( + parse_auth("username:sha256:abcd")?, + create_required_auth("username", "abcd", "sha256") + ); + + Ok(()) + } + + #[test] + fn parse_auth_sha512() -> Result<(), String> { + assert_eq!( + parse_auth("username:sha512:abcd")?, + create_required_auth("username", "abcd", "sha512") + ); + + Ok(()) + } + + #[test] + fn parse_auth_invalid_syntax() { + assert_eq!( + parse_auth("foo").unwrap_err(), + "Invalid credentials string, expected format is username:password".to_owned() + ); + } + + #[test] + fn parse_auth_invalid_hash_method() { + assert_eq!( + parse_auth("username:blahblah:abcd").unwrap_err(), + "Invalid hash method, only accept either sha256 or sha512".to_owned() + ); + } + + #[test] + fn parse_auth_invalid_hash_string() { + assert_eq!( + parse_auth("username:sha256:invalid").unwrap_err(), + "Hash string is not a valid hex code".to_owned() + ); + } + + #[test] + fn parse_auth_excessive_length() { + let auth_string = format!("username:{}", "x".repeat(256)); + + assert_eq!( + parse_auth(&*auth_string).unwrap_err(), + "Password length cannot exceed 255 characters".to_owned() + ); + } +} |