fn validate_and_fixup()

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)
    }