forked from pyca/cryptography
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
verification: add RFC822Name (pyca#10487)
* verification: add RFC822Name Signed-off-by: William Woodruff <[email protected]> * verification: clippy Signed-off-by: William Woodruff <[email protected]> * verification: clippage Signed-off-by: William Woodruff <[email protected]> * verification: feedback Signed-off-by: William Woodruff <[email protected]> --------- Signed-off-by: William Woodruff <[email protected]>
- Loading branch information
Showing
1 changed file
with
108 additions
and
1 deletion.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,6 +5,11 @@ | |
use std::net::IpAddr; | ||
use std::str::FromStr; | ||
|
||
use asn1::IA5String; | ||
|
||
// RFC 2822 3.2.4 | ||
static ATEXT_CHARS: &str = "!#$%&'*+-/=?^_`{|}~"; | ||
|
||
/// A `DNSName` is an `asn1::IA5String` with additional invariant preservations | ||
/// per [RFC 5280 4.2.1.6], which in turn uses the preferred name syntax defined | ||
/// in [RFC 1034 3.5] and amended in [RFC 1123 2.1]. | ||
|
@@ -298,9 +303,54 @@ impl IPConstraint { | |
} | ||
} | ||
|
||
/// An `RFC822Name` represents an email address, as defined in [RFC 822 6.1] | ||
/// and as amended by [RFC 2821 4.1.2]. In particular, it represents the `Mailbox` | ||
/// rule from RFC 2821's grammar. | ||
/// | ||
/// This type does not currently support the quoted local-part form; email | ||
/// addresses that use this form will be rejected. | ||
/// | ||
/// [RFC 822 6.1]: https://datatracker.ietf.org/doc/html/rfc822#section-6.1 | ||
/// [RFC 2821 4.1.2]: https://datatracker.ietf.org/doc/html/rfc2821#section-4.1.2 | ||
pub struct RFC822Name<'a> { | ||
pub mailbox: IA5String<'a>, | ||
pub domain: DNSName<'a>, | ||
} | ||
|
||
impl<'a> RFC822Name<'a> { | ||
pub fn new(value: &'a str) -> Option<Self> { | ||
// Mailbox = Local-part "@" Domain | ||
// Both must be present. | ||
let (local_part, domain) = value.split_once('@')?; | ||
let local_part = IA5String::new(local_part)?; | ||
|
||
// Local-part = Dot-string / Quoted-string | ||
// NOTE(ww): We do not support the Quoted-string form, for now. | ||
// | ||
// Dot-string: Atom *("." Atom) | ||
// Atom = 1*atext | ||
// | ||
// NOTE(ww): `atext`'s production is in RFC 2822 3.2.4. | ||
for component in local_part.as_str().split('.') { | ||
if component.is_empty() | ||
|| !component | ||
.chars() | ||
.all(|c| c.is_ascii_alphanumeric() || ATEXT_CHARS.contains(c)) | ||
{ | ||
return None; | ||
} | ||
} | ||
|
||
Some(Self { | ||
mailbox: local_part, | ||
domain: DNSName::new(domain)?, | ||
}) | ||
} | ||
} | ||
|
||
#[cfg(test)] | ||
mod tests { | ||
use crate::types::{DNSConstraint, DNSName, DNSPattern, IPAddress, IPConstraint}; | ||
use crate::types::{DNSConstraint, DNSName, DNSPattern, IPAddress, IPConstraint, RFC822Name}; | ||
|
||
#[test] | ||
fn test_dnsname_debug_trait() { | ||
|
@@ -587,4 +637,61 @@ mod tests { | |
assert!(!ipv6_128.matches(&IPAddress::from_str("2600::ff00:dede").unwrap())); | ||
assert!(!ipv6_128.matches(&IPAddress::from_str("2600:db8::ff00:0").unwrap())); | ||
} | ||
|
||
#[test] | ||
fn test_rfc822name() { | ||
for bad_case in &[ | ||
"", | ||
// Missing local-part. | ||
"@example.com", | ||
" @example.com", | ||
" @example.com", | ||
// Missing domain cases. | ||
"foo", | ||
"foo@", | ||
"foo@ ", | ||
"foo@ ", | ||
// Invalid domains. | ||
"foo@!!!", | ||
"foo@white space", | ||
"foo@🙈", | ||
// Invalid local part (empty mailbox sections). | ||
"[email protected]", | ||
"[email protected]", | ||
"[email protected]", | ||
"[email protected]", | ||
"[email protected]", | ||
// Invalid local part (@ in mailbox). | ||
"lol@[email protected]", | ||
"lol\\@[email protected]", | ||
"[email protected]@example.com", | ||
"@@example.com", | ||
// Invalid local part (invalid characters). | ||
"lol\"[email protected]", | ||
"lol;[email protected]", | ||
"🙈@example.com", | ||
// Intentionally unsupported quoted local parts. | ||
"\"validbutunsupported\"@example.com", | ||
] { | ||
assert!(RFC822Name::new(bad_case).is_none()); | ||
} | ||
|
||
// Each good case is (address, (mailbox, domain)). | ||
for (address, (mailbox, domain)) in &[ | ||
// Normal mailboxes. | ||
("[email protected]", ("foo", "example.com")), | ||
("[email protected]", ("foo.bar", "example.com")), | ||
("[email protected]", ("foo.bar.baz", "example.com")), | ||
("[email protected]", ("1.2.3.4.5", "example.com")), | ||
// Mailboxes with special but valid characters. | ||
("{legal}@example.com", ("{legal}", "example.com")), | ||
("{&*.legal}@example.com", ("{&*.legal}", "example.com")), | ||
("``````````@example.com", ("``````````", "example.com")), | ||
("[email protected]", ("hello?", "sub.example.com")), | ||
] { | ||
let parsed = RFC822Name::new(&address).unwrap(); | ||
assert_eq!(&parsed.mailbox.as_str(), mailbox); | ||
assert_eq!(&parsed.domain.as_str(), domain); | ||
} | ||
} | ||
} |