use url::Url;
use crate::{Credentials, Error, Result};
pub fn credentials_from_url(url: &Url) -> Result<Option<Credentials>> {
if url.username().is_empty() {
return Ok(None);
}
let username = decode(url.username())?;
let password = match url.password() {
Some(p) => decode(p)?,
None => String::new(),
};
Ok(Some(Credentials::new(username, password)))
}
pub fn strip_userinfo(url: &Url) -> Result<Url> {
let mut clean = url.clone();
clean
.set_username("")
.map_err(|()| Error::UserinfoNotRemovable("username"))?;
clean
.set_password(None)
.map_err(|()| Error::UserinfoNotRemovable("password"))?;
Ok(clean)
}
fn decode(s: &str) -> Result<String> {
percent_encoding::percent_decode_str(s)
.decode_utf8()
.map(|c| c.into_owned())
.map_err(|e| Error::InvalidUserinfo(e.to_string()))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn decodes_percent_encoded_userinfo() {
let url = Url::parse("rtsp://us%40er:p%3Aw@host/s").unwrap();
assert_eq!(
credentials_from_url(&url).unwrap().unwrap(),
Credentials::new("us@er", "p:w")
);
}
#[test]
fn no_username_is_none() {
let url = Url::parse("http://host/s").unwrap();
assert!(credentials_from_url(&url).unwrap().is_none());
}
#[test]
fn missing_password_is_empty() {
let url = Url::parse("http://user@host/s").unwrap();
assert_eq!(
credentials_from_url(&url).unwrap().unwrap(),
Credentials::new("user", "")
);
}
#[test]
fn invalid_utf8_is_rejected_without_echoing_the_secret() {
let url = Url::parse("http://user:%FFsecret@host/s").unwrap();
let err = credentials_from_url(&url).unwrap_err();
assert!(matches!(err, Error::InvalidUserinfo(_)));
assert!(!err.to_string().contains("secret"));
}
#[test]
fn strip_removes_userinfo() {
let url = Url::parse("http://user:secret@host/s").unwrap();
assert_eq!(strip_userinfo(&url).unwrap().as_str(), "http://host/s");
}
#[test]
fn strip_rejects_a_cannot_be_a_base_url() {
let url = Url::parse("mailto:user@host").unwrap();
assert!(matches!(
strip_userinfo(&url),
Err(Error::UserinfoNotRemovable("username"))
));
}
}