50 lines
1.6 KiB
Swift
50 lines
1.6 KiB
Swift
import Foundation
|
|
|
|
/// A validator reducing a submitted email address to its canonical form.
|
|
///
|
|
/// Trims surrounding whitespace, lowercases the address (so one mailbox cannot register once per spelling), caps it at the 254 bytes an address can
|
|
/// be, and checks its shape: something before the `@`, and a domain with a dot. Control characters are rejected separately: the shape only excludes
|
|
/// whitespace, which would let non-whitespace controls (such as `NUL`) into the stored address.
|
|
public struct NormalizeEmail: Sendable {
|
|
|
|
// MARK: Initializers
|
|
|
|
/// Creates an email normalization method.
|
|
public init() {}
|
|
|
|
// MARK: Functions
|
|
|
|
/// Validates and normalizes a submitted email address.
|
|
/// - Parameter email: the submitted email address, if any.
|
|
/// - Returns: the normalized address, or `nil` when the submission is missing or invalid.
|
|
public func callAsFunction(
|
|
_ email: String?
|
|
) -> String? {
|
|
guard
|
|
let email = email?
|
|
.trimmingCharacters(in: .whitespacesAndNewlines)
|
|
.lowercased(),
|
|
email.utf8.count <= Constant.Length.maxEmail,
|
|
!email.unicodeScalars.contains(where: {
|
|
$0.properties.generalCategory == .control
|
|
}),
|
|
email.wholeMatch(of: /[^\s@]+@[^\s@]+\.[^\s@]+/) != nil
|
|
else {
|
|
return nil
|
|
}
|
|
|
|
return email
|
|
}
|
|
|
|
}
|
|
|
|
// MARK: - Constants
|
|
|
|
private enum Constant {
|
|
/// A namespace for the length constants.
|
|
enum Length {
|
|
/// The longest an email address can be, in bytes, per RFC 5321's path limit.
|
|
static let maxEmail = 254
|
|
}
|
|
}
|