Functions/GenXdev.Helpers/Email.cs

// ################################################################################
// Part of PowerShell module : GenXdev.Helpers
// Original cmdlet filename : Email.cs
// Original author : René Vaessen / GenXdev
// Version : 2.1.2025
// ################################################################################
// Copyright (c) René Vaessen / GenXdev
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ################################################################################
 
 
 
using System.Text;
using System.Text.RegularExpressions;
 
namespace GenXdev.Helpers
{
    /// <summary>
    /// <para type="synopsis">
    /// Provides static utility methods for email address validation and manipulation.
    /// </para>
    ///
    /// <para type="description">
    /// The Email class contains helper methods for working with email addresses,
    /// including validation, escaping/unescaping for ESMTP parameters, address comparison,
    /// and extracting domain or account names from email addresses.
    /// </para>
    /// </summary>
    public static class Email
    {
        /// <summary>
        /// Validates whether the provided string is a valid email address format.
        /// </summary>
        /// <param name="email">The email address string to validate.</param>
        /// <returns>True if the email address matches the expected format, otherwise false.</returns>
        public static bool IsValidEmailAddress(string email)
        {
            // Define the regular expression pattern for email validation
            string pattern = @"^(?!\.)(""([^""\r\\]|\\[""\r\\])*""|"
              + @"([-a-z0-9!#$%&'*+/=?^_`{|}~]|(?<!\.)\.)*)(?<!\.)"
              + @"@[a-z0-9][\w\.-]*[a-z0-9]\.[a-z][a-z\.]*[a-z]$";
 
            // Create regex object with case-insensitive matching
            Regex regex = new Regex(pattern, RegexOptions.IgnoreCase);
 
            // Return whether the email matches the pattern
            return regex.IsMatch(email);
        }
 
        /// <summary>
        /// Escapes a value for use in ESMTP MAIL FROM or RCPT TO parameters.
        /// </summary>
        /// <param name="value">The value to escape.</param>
        /// <returns>The escaped value.</returns>
        public static string EscapeESMTPMailOrRcptParamValue(string value)
        {
            // Call the ref version to perform the escaping
            EscapeESMTPMailOrRcptParamValue(ref value);
 
            // Return the modified value
            return value;
        }
 
        /// <summary>
        /// Unescapes a value that was previously escaped for ESMTP parameters.
        /// </summary>
        /// <param name="value">The value to unescape.</param>
        /// <returns>The unescaped value.</returns>
        public static string UnescapeESMTPMailOrRcptParamValue(string value)
        {
            // Call the ref version to perform the unescaping
            UnescapeESMTPMailOrRcptParamValue(ref value);
 
            // Return the modified value
            return value;
        }
 
        /// <summary>
        /// Escapes special characters in a value for ESMTP MAIL FROM or RCPT TO parameters.
        /// Modifies the input string in place.
        /// </summary>
        /// <param name="value">The value to escape, modified by reference.</param>
        public static void EscapeESMTPMailOrRcptParamValue(ref string value)
        {
            // Create a string builder from the input value
            var sb = new StringBuilder(value);
 
            // Iterate backwards through the string to handle replacements
            for (var i = sb.Length - 1; i >= 0; i--)
            {
                // Get the current character
                char c = sb[i];
 
                // Check if character needs escaping (control chars, non-ASCII, +, or =)
                if ((c < '!') || (c > '~') || (c == '+') || (c == '='))
                {
                    // Remove the character and insert its hex-encoded version
                    sb.Remove(i, 1);
                    sb.Insert(i, "+" + Uri.HexEscape(c).Substring(1, 2));
                }
            }
 
            // Update the original value with the escaped string
            value = sb.ToString();
        }
 
        /// <summary>
        /// Unescapes special characters in a value that was escaped for ESMTP parameters.
        /// Modifies the input string in place.
        /// </summary>
        /// <param name="value">The value to unescape, modified by reference.</param>
        public static void UnescapeESMTPMailOrRcptParamValue(ref string value)
        {
            // Create a string builder from the input value
            var sb = new StringBuilder(value);
 
            // Initialize index for iteration
            var i = 0;
 
            // Iterate through the string to find and replace escape sequences
            while (i < sb.Length)
            {
                // Get the current character
                char c = sb[i];
 
                // Check if this is an escape sequence start (+)
                if (c == '+')
                {
                    // Remove the + character
                    sb.Remove(i, 1);
 
                    // Extract the next two characters as hex
                    int i2 = 0;
                    sb.Insert(i, Uri.HexUnescape("%" + sb.Remove(i, 2).ToString(), ref i2));
                }
 
                // Move to the next character
                i++;
            }
 
            // Update the original value with the unescaped string
            value = sb.ToString();
        }
 
        /// <summary>
        /// Compares two email addresses for equality, ignoring case and whitespace.
        /// </summary>
        /// <param name="address1">The first email address to compare.</param>
        /// <param name="address2">The second email address to compare.</param>
        /// <returns>True if the addresses are equal, otherwise false.</returns>
        public static bool EmailAddressesAreEqual(string address1, string address2)
        {
            // Trim whitespace and convert to lowercase for comparison
            return address1.Trim().ToLowerInvariant().Equals(address2.Trim().ToLowerInvariant());
        }
 
        /// <summary>
        /// Checks if a given email address exists in an array of email addresses.
        /// </summary>
        /// <param name="emailAddressList">The array of email addresses to search.</param>
        /// <param name="emailAddress">The email address to find.</param>
        /// <returns>True if the email address is found in the list, otherwise false.</returns>
        public static bool ContainsEmailAddress(string[] emailAddressList, string emailAddress)
        {
            // Iterate through each address in the list
            foreach (var address in emailAddressList)
            {
                // Check if current address matches the target
                if (EmailAddressesAreEqual(address, emailAddress))
                    return true;
            }
 
            // Return false if no match found
            return false;
        }
 
        /// <summary>
        /// Extracts the domain name from an email address.
        /// </summary>
        /// <param name="emailAddress">The email address to parse.</param>
        /// <returns>The domain name part of the email address, or "anonymous" if no domain, or the original string if no @ found.</returns>
        public static string GetDomainNameFromEmailAddress(string emailAddress)
        {
            // Check if input is null or whitespace
            if (String.IsNullOrWhiteSpace(emailAddress))
                return String.Empty;
 
            // Remove leading/trailing whitespace
            emailAddress = emailAddress.Trim();
 
            // Find the last @ symbol
            int idx = emailAddress.LastIndexOf("@");
 
            // If @ is at the end, return "anonymous"
            if ((idx >= 0) && (idx == emailAddress.Length - 1))
                return "anonymous";
 
            // If no @ found, return the whole string
            if (idx < 0)
                return emailAddress;
 
            // Return the domain part after @
            return emailAddress.Substring(idx + 1).ToLowerInvariant().Trim();
        }
 
        /// <summary>
        /// Extracts the account name (local part) from an email address.
        /// </summary>
        /// <param name="emailAddress">The email address to parse.</param>
        /// <returns>The account name part before the @ symbol, or the original string if no @ found.</returns>
        public static string GetAccountNameFromEmailAddress(string emailAddress)
        {
            // Check if input is null or whitespace
            if (String.IsNullOrWhiteSpace(emailAddress))
                return String.Empty;
 
            // Remove leading/trailing whitespace
            emailAddress = emailAddress.Trim();
 
            // Find the last @ symbol
            int idx = emailAddress.LastIndexOf("@");
 
            // If no @ found, return the whole string
            if (idx < 0)
                return emailAddress;
 
            // Return the account part before @
            return emailAddress.Substring(0, idx).Trim();
        }
    }
}