qrock 0.2.2

Helpers for Rocket HTTP server applications.
Documentation
pub mod snmp;

use rocket::form::{self, FromFormField, ValueField};

use eui48::MacAddress;

use validator::ValidateEmail;

#[derive(Debug)]
pub struct MAC(pub MacAddress);

#[rocket::async_trait]
impl<'v> FromFormField<'v> for MAC {
  fn from_value(field: ValueField<'v>) -> form::Result<'v, Self> {
    let s = field.value;
    match MacAddress::parse_str(s) {
      Ok(ma) => Ok(Self(ma)),
      Err(e) => {
        let error =
          form::Error::validation(format!("Unable to parse MAC address; {e}"));
        Err(error)?
      }
    }
  }
}


/// Object name validator.
///
/// See [`ObjNameRef`] for a non-owning variant.
#[derive(Debug)]
pub struct ObjName(pub String);

// Constraints:
// - Must be at least one character long
// - Must start with a letter
// - Must not contain a whitespace
// - Valid non-start characters:
//   - Letters, numbers, _, -, .
impl<'r> FromFormField<'r> for ObjName {
  fn from_value(field: ValueField<'r>) -> form::Result<'r, Self> {
    let s = field.value;
    if let Err(e) = orphanage::strx::validate_objname(s) {
      if let orphanage::Error::BadFormat(err) = e {
        Err(form::Error::validation(err))?;
      }
      Err(form::Error::validation("invalid object name format"))?;
    }
    Ok(Self(s.to_string()))
  }
}


/// Object name validator.
///
/// See [`ObjName`] for an owning variant.
#[derive(Debug)]
pub struct ObjNameRef<'r>(pub &'r str);

// Constraints:
// - Must be at least one character long
// - Must start with a letter
// - Must not contain a whitespace
// - Valid non-start characters:
//   - Letters, numbers, _, -, .
impl<'r> FromFormField<'r> for ObjNameRef<'r> {
  fn from_value(field: ValueField<'r>) -> form::Result<'r, Self> {
    let s = field.value;
    if let Err(e) = orphanage::strx::validate_objname(s) {
      if let orphanage::Error::BadFormat(err) = e {
        Err(form::Error::validation(err))?;
      }
      Err(form::Error::validation("invalid object name format"))?;
    }
    Ok(ObjNameRef(s))
  }
}


/// Optional (allow empty) pass phrase field.
#[derive(Debug)]
pub struct OptPass(pub String);

impl<'v> FromFormField<'v> for OptPass {
  fn from_value(field: ValueField<'v>) -> form::Result<'v, Self> {
    let s = field.value;
    Ok(Self(s.to_string()))
  }
}


/// Required (to not allow empty) pass phrase field.
#[derive(Debug)]
pub struct ReqPass(pub String);

impl<'v> FromFormField<'v> for ReqPass {
  fn from_value(field: ValueField<'v>) -> form::Result<'v, Self> {
    let s = field.value;
    if s.is_empty() {
      let error = form::Error::validation("Passphrase must not be empty");
      Err(error)?
    } else {
      Ok(Self(s.to_string()))
    }
  }
}


/// Required a strong passphrase.
/// Must be at least 12 characters long.
/// Must constain characters belonging to at least 3 of the character classes:
/// - uppercase letters
/// - lowercase letters
/// - numbers
/// - special characters
///
/// This makes assumption about ASCII is being used, which it possibly
/// shouldn't.
#[derive(Debug)]
pub struct ReqStrongPass(pub String);

impl PartialEq for ReqStrongPass {
  fn eq(&self, other: &Self) -> bool {
    self.0 == other.0
  }
}

impl<'v> FromFormField<'v> for ReqStrongPass {
  fn from_value(field: ValueField<'v>) -> form::Result<'v, Self> {
    let s = field.value;
    if s.len() < 12 {
      let error = form::Error::validation(
        "Passphrase too short; must be at least 12 characters long"
      );
      Err(error)?
    } else {
      let mut lowercase = false;
      let mut uppercase = false;
      let mut numeric = false;
      let mut special = false;
      let mut classes = 0;
      for c in s.chars() {
        if c.is_ascii_uppercase() {
          if !uppercase {
            classes += 1;
            uppercase = true;
          }
        } else if c.is_ascii_lowercase() {
          if !lowercase {
            classes += 1;
            lowercase = true;
          }
        } else if c.is_ascii_digit() {
          if !numeric {
            classes += 1;
            numeric = true;
          }
        } else if !special {
          classes += 1;
          special = true;
        }
        if classes == 3 {
          // break early if we've reached the requirements
          break;
        }
      }
      if classes < 3 {
        return Err(From::from(form::Error::validation(
          "Passphrase too weak; must contain at least three of the four \
           character classes: upper-case letter, lower-case letter, digit, \
           special character"
        )));
      }

      Ok(Self(s.to_string()))
    }
  }
}


#[derive(Debug)]
pub struct Email<'r>(pub &'r str);

impl<'r> FromFormField<'r> for Email<'r> {
  fn from_value(field: ValueField<'r>) -> form::Result<'r, Self> {
    let s = field.value;
    if s.validate_email() {
      Ok(Email(s))
    } else {
      Err(From::from(form::Error::validation("Invalid email format")))
    }
  }
}

// vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 :