[pve-devel] [PATCH installer v2 5/7] tui: use email regex from HTML specification for validation

Christoph Heiss c.heiss at proxmox.com
Wed Oct 2 14:27:26 CEST 2024


That regex should be a lot more accurate in what it allows - if it's
good enough for the HTML spec, it should be for us too.

Signed-off-by: Christoph Heiss <c.heiss at proxmox.com>
---
Changes v1 -> v2:
  * move `EMAIL_DEFAULT_PLACEHOLDER` constant to
    proxmox-installer-common/lib.rs

 proxmox-installer-common/Cargo.toml     |  1 +
 proxmox-installer-common/src/lib.rs     |  3 +++
 proxmox-installer-common/src/options.rs | 25 +++++++++++++++++++++++++
 proxmox-tui-installer/Cargo.toml        |  1 -
 proxmox-tui-installer/src/main.rs       | 18 +++++-------------
 proxmox-tui-installer/src/options.rs    |  3 ++-
 6 files changed, 36 insertions(+), 15 deletions(-)

diff --git a/proxmox-installer-common/Cargo.toml b/proxmox-installer-common/Cargo.toml
index 70f828a..e151b0e 100644
--- a/proxmox-installer-common/Cargo.toml
+++ b/proxmox-installer-common/Cargo.toml
@@ -8,6 +8,7 @@ exclude = [ "build", "debian" ]
 homepage = "https://www.proxmox.com"
 
 [dependencies]
+anyhow.workspace = true
 regex = "1.7"
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
diff --git a/proxmox-installer-common/src/lib.rs b/proxmox-installer-common/src/lib.rs
index 850e825..028b43c 100644
--- a/proxmox-installer-common/src/lib.rs
+++ b/proxmox-installer-common/src/lib.rs
@@ -4,3 +4,6 @@ pub mod setup;
 pub mod utils;
 
 pub const RUNTIME_DIR: &str = "/run/proxmox-installer";
+
+/// Default placeholder value for the administrator email address.
+pub const EMAIL_DEFAULT_PLACEHOLDER: &str = "mail at example.invalid";
diff --git a/proxmox-installer-common/src/options.rs b/proxmox-installer-common/src/options.rs
index 9375ded..ea06eee 100644
--- a/proxmox-installer-common/src/options.rs
+++ b/proxmox-installer-common/src/options.rs
@@ -1,5 +1,8 @@
+use anyhow::{bail, Result};
+use regex::Regex;
 use serde::Deserialize;
 use std::net::{IpAddr, Ipv4Addr};
+use std::sync::OnceLock;
 use std::{cmp, fmt};
 
 use crate::setup::{
@@ -403,6 +406,28 @@ impl NetworkOptions {
     }
 }
 
+/// Validates an email address using the regex for <input type="email" /> elements
+/// as defined in the HTML specification [0].
+/// Using that /should/ cover all possible cases that are encountered in the wild.
+///
+/// It additionally checks whether the email our default email placeholder value.
+///
+/// [0] https://html.spec.whatwg.org/multipage/input.html#valid-e-mail-address
+pub fn email_validate(email: &str) -> Result<()> {
+    static RE: OnceLock<Regex> = OnceLock::new();
+    let re = RE.get_or_init(|| {
+        Regex::new(r"^[a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*$").unwrap()
+    });
+
+    if !re.is_match(email) {
+        bail!("Email does not look like a valid address (user at domain.tld)")
+    } else if email == crate::EMAIL_DEFAULT_PLACEHOLDER {
+        bail!("Invalid (default) email address")
+    }
+
+    Ok(())
+}
+
 #[cfg(test)]
 mod tests {
     use super::*;
diff --git a/proxmox-tui-installer/Cargo.toml b/proxmox-tui-installer/Cargo.toml
index 2516468..618904c 100644
--- a/proxmox-tui-installer/Cargo.toml
+++ b/proxmox-tui-installer/Cargo.toml
@@ -11,5 +11,4 @@ homepage = "https://www.proxmox.com"
 cursive = { version = "0.21", default-features = false, features = ["crossterm-backend"] }
 serde = { version = "1.0", features = ["derive"] }
 serde_json = "1.0"
-regex = "1.7"
 proxmox-installer-common = { path = "../proxmox-installer-common" }
diff --git a/proxmox-tui-installer/src/main.rs b/proxmox-tui-installer/src/main.rs
index c36daa5..d306969 100644
--- a/proxmox-tui-installer/src/main.rs
+++ b/proxmox-tui-installer/src/main.rs
@@ -13,13 +13,11 @@ use cursive::{
     Cursive, CursiveRunnable, ScreenId, View, XY,
 };
 
-use regex::Regex;
-
 mod options;
 use options::{InstallerOptions, PasswordOptions};
 
 use proxmox_installer_common::{
-    options::{BootdiskOptions, NetworkOptions, TimezoneOptions},
+    options::{email_validate, BootdiskOptions, NetworkOptions, TimezoneOptions},
     setup::{installer_setup, LocaleInfo, ProxmoxProduct, RuntimeInfo, SetupInfo},
     utils::Fqdn,
 };
@@ -448,18 +446,12 @@ fn password_dialog(siv: &mut Cursive) -> InstallerView {
                     .get_value::<EditView, _>(2)
                     .ok_or("failed to retrieve email")?;
 
-                let email_regex =
-                    Regex::new(r"^[\w\+\-\~]+(\.[\w\+\-\~]+)*@[a-zA-Z0-9\-]+(\.[a-zA-Z0-9\-]+)*$")
-                        .unwrap();
-
                 if root_password.len() < 5 {
-                    Err("password too short, must be at least 5 characters long")
+                    Err("password too short, must be at least 5 characters long".to_owned())
                 } else if root_password != confirm_password {
-                    Err("passwords do not match")
-                } else if email == "mail at example.invalid" {
-                    Err("invalid email address")
-                } else if !email_regex.is_match(&email) {
-                    Err("Email does not look like a valid address (user at domain.tld)")
+                    Err("passwords do not match".to_owned())
+                } else if let Err(err) = email_validate(&email) {
+                    Err(err.to_string())
                 } else {
                     Ok(PasswordOptions {
                         root_password,
diff --git a/proxmox-tui-installer/src/options.rs b/proxmox-tui-installer/src/options.rs
index 19992ca..b14ecf7 100644
--- a/proxmox-tui-installer/src/options.rs
+++ b/proxmox-tui-installer/src/options.rs
@@ -5,6 +5,7 @@ use proxmox_installer_common::{
         BootdiskOptions, BtrfsRaidLevel, FsType, NetworkOptions, TimezoneOptions, ZfsRaidLevel,
     },
     setup::LocaleInfo,
+    EMAIL_DEFAULT_PLACEHOLDER,
 };
 
 pub const FS_TYPES: &[FsType] = {
@@ -33,7 +34,7 @@ pub struct PasswordOptions {
 impl Default for PasswordOptions {
     fn default() -> Self {
         Self {
-            email: "mail at example.invalid".to_string(),
+            email: EMAIL_DEFAULT_PLACEHOLDER.to_string(),
             root_password: String::new(),
         }
     }
-- 
2.46.0





More information about the pve-devel mailing list