in components/logins/src/login.rs [616:760]
fn validate_and_fixup(&self, fixup: bool) -> Result<Option<Self>> {
// XXX TODO: we've definitely got more validation and fixups to add here!
let mut maybe_fixed = None;
/// A little helper to magic a Some(self.clone()) into existence when needed.
macro_rules! get_fixed_or_throw {
($err:expr) => {
// This is a block expression returning a local variable,
// entirely so we can give it an explicit type declaration.
{
if !fixup {
return Err($err.into());
}
log::warn!("Fixing login record {:?}", $err);
let fixed: Result<&mut Self> =
Ok(maybe_fixed.get_or_insert_with(|| self.clone()));
fixed
}
};
}
if self.origin.is_empty() {
return Err(InvalidLogin::EmptyOrigin.into());
}
if self.form_action_origin.is_some() && self.http_realm.is_some() {
get_fixed_or_throw!(InvalidLogin::BothTargets)?.http_realm = None;
}
if self.form_action_origin.is_none() && self.http_realm.is_none() {
return Err(InvalidLogin::NoTarget.into());
}
let form_action_origin = self.form_action_origin.clone().unwrap_or_default();
let http_realm = maybe_fixed
.as_ref()
.unwrap_or(self)
.http_realm
.clone()
.unwrap_or_default();
let field_data = [
("form_action_origin", &form_action_origin),
("http_realm", &http_realm),
("origin", &self.origin),
("username_field", &self.username_field),
("password_field", &self.password_field),
];
for (field_name, field_value) in &field_data {
// Nuls are invalid.
if field_value.contains('\0') {
return Err(InvalidLogin::IllegalFieldValue {
field_info: format!("`{}` contains Nul", field_name),
}
.into());
}
// Newlines are invalid in Desktop for all the fields here.
if field_value.contains('\n') || field_value.contains('\r') {
return Err(InvalidLogin::IllegalFieldValue {
field_info: format!("`{}` contains newline", field_name),
}
.into());
}
}
// Desktop doesn't like fields with the below patterns
if self.username_field == "." {
return Err(InvalidLogin::IllegalFieldValue {
field_info: "`username_field` is a period".into(),
}
.into());
}
// Check we can parse the origin, then use the normalized version of it.
if let Some(fixed) = Self::validate_and_fixup_origin(&self.origin)? {
get_fixed_or_throw!(InvalidLogin::IllegalFieldValue {
field_info: "Origin is not normalized".into()
})?
.origin = fixed;
}
match &maybe_fixed.as_ref().unwrap_or(self).form_action_origin {
None => {
if !self.username_field.is_empty() {
get_fixed_or_throw!(InvalidLogin::IllegalFieldValue {
field_info: "username_field must be empty when form_action_origin is null"
.into()
})?
.username_field
.clear();
}
if !self.password_field.is_empty() {
get_fixed_or_throw!(InvalidLogin::IllegalFieldValue {
field_info: "password_field must be empty when form_action_origin is null"
.into()
})?
.password_field
.clear();
}
}
Some(href) => {
// "", ".", and "javascript:" are special cases documented at the top of this file.
if href == "." {
// A bit of a special case - if we are being asked to fixup, we replace
// "." with an empty string - but if not fixing up we don't complain.
if fixup {
maybe_fixed
.get_or_insert_with(|| self.clone())
.form_action_origin = Some("".into());
}
} else if !href.is_empty() && href != "javascript:" {
if let Some(fixed) = Self::validate_and_fixup_origin(href)? {
get_fixed_or_throw!(InvalidLogin::IllegalFieldValue {
field_info: "form_action_origin is not normalized".into()
})?
.form_action_origin = Some(fixed);
}
}
}
}
// secure fields
//
// \r\n chars are valid in desktop for some reason, so we allow them here too.
if self.username.contains('\0') {
return Err(InvalidLogin::IllegalFieldValue {
field_info: "`username` contains Nul".into(),
}
.into());
}
if self.password.is_empty() {
return Err(InvalidLogin::EmptyPassword.into());
}
if self.password.contains('\0') {
return Err(InvalidLogin::IllegalFieldValue {
field_info: "`password` contains Nul".into(),
}
.into());
}
Ok(maybe_fixed)
}