Updated spec list, UTF-8 support (via is_uchar).

This commit is contained in:
Simon Johnston
2020-03-24 08:46:45 -07:00
parent 5dd80bcc70
commit 6f3aa4e7e6
2 changed files with 145 additions and 19 deletions
+9 -3
View File
@@ -26,10 +26,16 @@ assert_eq!(
## Specifications ## Specifications
1. RFC 1123: [_Requirements for Internet Hosts -- Application and Support_](https://tools.ietf.org/html/rfc1123),
IETF,Oct 1989.
1. RFC 3629: [_UTF-8, a transformation format of ISO 10646_](https://tools.ietf.org/html/rfc3629), 1. RFC 3629: [_UTF-8, a transformation format of ISO 10646_](https://tools.ietf.org/html/rfc3629),
IETF, Nov 2003. IETF, Nov 2003.
1. RFC 3696: [_Application Techniques for Checking and Transformation of 1. RFC 3696: [_Application Techniques for Checking and Transformation of
Names_](https://tools.ietf.org/html/rfc3696), IETF, Feb 2004. Names_](https://tools.ietf.org/html/rfc3696), IETF, Feb 2004.
1. RFC 4291 [_IP Version 6 Addressing Architecture_](https://tools.ietf.org/html/rfc4291),
IETF, Feb 2006.
1. RFC 5234: [_Augmented BNF for Syntax Specifications: ABNF_](https://tools.ietf.org/html/rfc5234),
IETF, Jan 2008.
1. RFC 5321: [_Simple Mail Transfer Protocol_](https://tools.ietf.org/html/rfc5321), 1. RFC 5321: [_Simple Mail Transfer Protocol_](https://tools.ietf.org/html/rfc5321),
IETF, Oct 2008. IETF, Oct 2008.
1. RFC 5322: [_Internet Message Format_](https://tools.ietf.org/html/rfc5322), I 1. RFC 5322: [_Internet Message Format_](https://tools.ietf.org/html/rfc5322), I
@@ -38,9 +44,9 @@ assert_eq!(
Framework_](https://tools.ietf.org/html/rfc5890), IETF, Aug 2010. Framework_](https://tools.ietf.org/html/rfc5890), IETF, Aug 2010.
1. RFC 6531: [_SMTP Extension for Internationalized Email_](https://tools.ietf.org/html/rfc6531), 1. RFC 6531: [_SMTP Extension for Internationalized Email_](https://tools.ietf.org/html/rfc6531),
IETF, Feb 2012 IETF, Feb 2012
1. RFC 5234: [_Augmented BNF for Syntax Specifications: ABNF_](https://tools.ietf.org/html/rfc5234), 1. RFC 6532: [_Internationalized Email Headers_](https://tools.ietf.org/html/rfc6532),
IETF, Jan 2008. IETF, Feb 2012.
## Changes ## Changes
**Version 0.1.0** **Version 0.1.0**
+136 -16
View File
@@ -43,10 +43,16 @@ assert_eq!(
# Specifications # Specifications
1. RFC 1123: [_Requirements for Internet Hosts -- Application and Support_](https://tools.ietf.org/html/rfc1123),
IETF,Oct 1989.
1. RFC 3629: [_UTF-8, a transformation format of ISO 10646_](https://tools.ietf.org/html/rfc3629), 1. RFC 3629: [_UTF-8, a transformation format of ISO 10646_](https://tools.ietf.org/html/rfc3629),
IETF, Nov 2003. IETF, Nov 2003.
1. RFC 3696: [_Application Techniques for Checking and Transformation of 1. RFC 3696: [_Application Techniques for Checking and Transformation of
Names_](https://tools.ietf.org/html/rfc3696), IETF, Feb 2004. Names_](https://tools.ietf.org/html/rfc3696), IETF, Feb 2004.
1. RFC 4291 [_IP Version 6 Addressing Architecture_](https://tools.ietf.org/html/rfc4291),
IETF, Feb 2006.
1. RFC 5234: [_Augmented BNF for Syntax Specifications: ABNF_](https://tools.ietf.org/html/rfc5234),
IETF, Jan 2008.
1. RFC 5321: [_Simple Mail Transfer Protocol_](https://tools.ietf.org/html/rfc5321), 1. RFC 5321: [_Simple Mail Transfer Protocol_](https://tools.ietf.org/html/rfc5321),
IETF, Oct 2008. IETF, Oct 2008.
1. RFC 5322: [_Internet Message Format_](https://tools.ietf.org/html/rfc5322), I 1. RFC 5322: [_Internet Message Format_](https://tools.ietf.org/html/rfc5322), I
@@ -55,8 +61,8 @@ assert_eq!(
Framework_](https://tools.ietf.org/html/rfc5890), IETF, Aug 2010. Framework_](https://tools.ietf.org/html/rfc5890), IETF, Aug 2010.
1. RFC 6531: [_SMTP Extension for Internationalized Email_](https://tools.ietf.org/html/rfc6531), 1. RFC 6531: [_SMTP Extension for Internationalized Email_](https://tools.ietf.org/html/rfc6531),
IETF, Feb 2012 IETF, Feb 2012
1. RFC 5234: [_Augmented BNF for Syntax Specifications: ABNF_](https://tools.ietf.org/html/rfc5234), 1. RFC 6532: [_Internationalized Email Headers_](https://tools.ietf.org/html/rfc6532),
IETF, Jan 2008. IETF, Feb 2012.
From RFC 5322: §3.2.1. [Quoted characters](https://tools.ietf.org/html/rfc5322#section-3.2.1): From RFC 5322: §3.2.1. [Quoted characters](https://tools.ietf.org/html/rfc5322#section-3.2.1):
@@ -146,15 +152,51 @@ dtext = %d33-90 / ; Printable US-ASCII
RFC 3696, §3. [Restrictions on email addresses](https://tools.ietf.org/html/rfc3696#section-3) RFC 3696, §3. [Restrictions on email addresses](https://tools.ietf.org/html/rfc3696#section-3)
describes in detail the quoting of characters in an address. describes in detail the quoting of characters in an address.
## Unicode
RFC 6531, §3.3. [Extended Mailbox Address Syntax](https://tools.ietf.org/html/rfc6531#section-3.3) RFC 6531, §3.3. [Extended Mailbox Address Syntax](https://tools.ietf.org/html/rfc6531#section-3.3)
extends the rules above for non-ASCII character sets. extends the rules above for non-ASCII character sets.
```ebnf
sub-domain =/ U-label
; extend the definition of sub-domain in RFC 5321, Section 4.1.2
atext =/ UTF8-non-ascii
; extend the implicit definition of atext in
; RFC 5321, Section 4.1.2, which ultimately points to
; the actual definition in RFC 5322, Section 3.2.3
qtextSMTP =/ UTF8-non-ascii
; extend the definition of qtextSMTP in RFC 5321, Section 4.1.2
esmtp-value =/ UTF8-non-ascii
; extend the definition of esmtp-value in RFC 5321, Section 4.1.2
```
RFC 6532: §3.1 [UTF-8 Syntax and Normalization](https://tools.ietf.org/html/rfc6532#section-3.1),
and §3.2 [Syntax Extensions to RFC 5322](https://tools.ietf.org/html/rfc6532#section-3.2) extend
the syntax above with:
```ebnf ```ebnf
UTF8-non-ascii = UTF8-2 / UTF8-3 / UTF8-4 UTF8-non-ascii = UTF8-2 / UTF8-3 / UTF8-4
...
VCHAR =/ UTF8-non-ascii
ctext =/ UTF8-non-ascii
atext =/ UTF8-non-ascii
qtext =/ UTF8-non-ascii
text =/ UTF8-non-ascii
; note that this upgrades the body to UTF-8
dtext =/ UTF8-non-ascii
``` ```
This refers to RFC 6529 §4. [Syntax of UTF-8 Byte Sequences](https://tools.ietf.org/html/rfc3629#section-4): These in turn refer to RFC 6529 §4. [Syntax of UTF-8 Byte Sequences](https://tools.ietf.org/html/rfc3629#section-4):
> A UTF-8 string is a sequence of octets representing a sequence of UCS > A UTF-8 string is a sequence of octets representing a sequence of UCS
> characters. An octet sequence is valid UTF-8 only if it matches the > characters. An octet sequence is valid UTF-8 only if it matches the
@@ -173,7 +215,6 @@ This refers to RFC 6529 §4. [Syntax of UTF-8 Byte Sequences](https://tools.ietf
UTF8-tail = %x80-BF UTF8-tail = %x80-BF
``` ```
Comments in addresses are discussed in RFC 5322 Appendix A.5. [White Space, Comments, and Other Comments in addresses are discussed in RFC 5322 Appendix A.5. [White Space, Comments, and Other
Oddities](https://tools.ietf.org/html/rfc5322#appendix-A.5). Oddities](https://tools.ietf.org/html/rfc5322#appendix-A.5).
@@ -243,7 +284,8 @@ pub struct EmailAddress(String);
// ------------------------------------------------------------------------------------------------ // ------------------------------------------------------------------------------------------------
const LOCAL_PART_MAX_LENGTH: usize = 64; const LOCAL_PART_MAX_LENGTH: usize = 64;
const DOMAIN_MAX_LENGTH: usize = 255; const DOMAIN_MAX_LENGTH: usize = 254; // see: https://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690
const SUB_DOMAIN_MAX_LENGTH: usize = 63;
#[allow(dead_code)] #[allow(dead_code)]
const CR: char = '\r'; const CR: char = '\r';
@@ -265,6 +307,8 @@ const RPAREN: char = ')';
const LT: char = '<'; const LT: char = '<';
const GT: char = '>'; const GT: char = '>';
const UTF8_START: char = '\u{0080}';
const MAILTO_URI_PREFIX: &str = "mailto:"; const MAILTO_URI_PREFIX: &str = "mailto:";
impl Display for Error { impl Display for Error {
@@ -289,7 +333,7 @@ impl Display for Error {
Error::DomainInvalidSeparator => { Error::DomainInvalidSeparator => {
write!(f, "Invalid placement of the domain separator '{:?}", DOT) write!(f, "Invalid placement of the domain separator '{:?}", DOT)
} }
Error::InvalidIPAddress => write!(f, "Invalid IP Address specified for doamin."), Error::InvalidIPAddress => write!(f, "Invalid IP Address specified for domain."),
Error::UnbalancedQuotes => write!(f, "Quotes around the local-part are unbalanced."), Error::UnbalancedQuotes => write!(f, "Quotes around the local-part are unbalanced."),
Error::InvalidComment => write!(f, "A comment was badly formed."), Error::InvalidComment => write!(f, "A comment was badly formed."),
} }
@@ -390,14 +434,15 @@ fn parse_local_part(part: &str) -> Result<(), Error> {
} }
fn parse_quoted_local_part(part: &str) -> Result<(), Error> { fn parse_quoted_local_part(part: &str) -> Result<(), Error> {
if part.is_ascii() && is_qcontent(part) { if is_qcontent(part) {
return Ok(()); return Ok(());
} else {
} }
Error::InvalidCharacter.into() Error::InvalidCharacter.into()
} }
fn parse_unquoted_local_part(part: &str) -> Result<(), Error> { fn parse_unquoted_local_part(part: &str) -> Result<(), Error> {
if part.is_ascii() && is_dot_atom_text(part) { if is_dot_atom_text(part) {
return Ok(()); return Ok(());
} }
Error::InvalidCharacter.into() Error::InvalidCharacter.into()
@@ -416,14 +461,15 @@ fn parse_domain(part: &str) -> Result<(), Error> {
} }
fn parse_text_domain(part: &str) -> Result<(), Error> { fn parse_text_domain(part: &str) -> Result<(), Error> {
if part.is_ascii() && is_dot_atom_text(part) { // TODO: check sub-domain-len
if is_dot_atom_text(part) {
return Ok(()); return Ok(());
} }
Error::InvalidCharacter.into() Error::InvalidCharacter.into()
} }
fn parse_literal_domain(part: &str) -> Result<(), Error> { fn parse_literal_domain(part: &str) -> Result<(), Error> {
if part.is_ascii() && part.chars().all(is_dtext_char) { if part.chars().all(is_dtext_char) {
return Ok(()); return Ok(());
} }
Error::InvalidCharacter.into() Error::InvalidCharacter.into()
@@ -452,6 +498,28 @@ fn is_atext(c: char) -> bool {
|| c == '|' || c == '|'
|| c == '}' || c == '}'
|| c == '~' || c == '~'
|| is_uchar(c)
}
#[allow(dead_code)]
fn is_special(c: char) -> bool {
c == '('
|| c == ')'
|| c == '<'
|| c == '>'
|| c == '['
|| c == ']'
|| c == ':'
|| c == ';'
|| c == '@'
|| c == '\\'
|| c == ','
|| c == '.'
|| c == DQUOTE
}
fn is_uchar(c: char) -> bool {
c >= UTF8_START
} }
fn is_atom(s: &str) -> bool { fn is_atom(s: &str) -> bool {
@@ -470,13 +538,8 @@ fn is_wsp(c: char) -> bool {
c == SP || c == HTAB c == SP || c == HTAB
} }
#[allow(dead_code)]
fn is_ctext_char(c: char) -> bool {
(c >= '\x21' && c == '\x27') || (c >= '\x2A' && c <= '\x5B') || (c >= '\x5D' && c <= '\x7E')
}
fn is_qtext_char(c: char) -> bool { fn is_qtext_char(c: char) -> bool {
c == '\x21' || (c >= '\x23' && c <= '\x5B') || (c >= '\x5D' && c <= '\x7E') c == '\x21' || (c >= '\x23' && c <= '\x5B') || (c >= '\x5D' && c <= '\x7E') || is_uchar(c)
} }
fn is_qcontent(s: &str) -> bool { fn is_qcontent(s: &str) -> bool {
@@ -500,6 +563,11 @@ fn is_dtext_char(c: char) -> bool {
(c >= '\x21' && c <= '\x5A') || (c >= '\x5E' && c <= '\x7E') (c >= '\x21' && c <= '\x5A') || (c >= '\x5E' && c <= '\x7E')
} }
#[allow(dead_code)]
fn is_ctext_char(c: char) -> bool {
(c >= '\x21' && c == '\x27') || (c >= '\x2A' && c <= '\x5B') || (c >= '\x5D' && c <= '\x7E')
}
#[allow(dead_code)] #[allow(dead_code)]
fn is_ctext(s: &str) -> bool { fn is_ctext(s: &str) -> bool {
s.chars().all(is_ctext_char) s.chars().all(is_ctext_char)
@@ -605,6 +673,58 @@ mod tests {
assert!(EmailAddress::is_valid("jsmith@[IPv6:2001:db8::1]")); assert!(EmailAddress::is_valid("jsmith@[IPv6:2001:db8::1]"));
} }
#[test]
fn test_good_examples_from_wikipedia_17() {
assert!(EmailAddress::is_valid(
"user+mailbox/department=shipping@example.com"
));
}
#[test]
fn test_good_examples_from_wikipedia_18() {
assert!(EmailAddress::is_valid("!#$%&'*+-/=?^_`.{|}~@example.com"));
}
#[test]
fn test_good_examples_from_wikipedia_19() {
assert!(EmailAddress::is_valid("\"Abc@def\"@example.com"));
}
#[test]
fn test_good_examples_from_wikipedia_20() {
assert!(EmailAddress::is_valid("\"Joe.\\\\Blow\"@example.com"));
}
#[test]
fn test_good_examples_from_wikipedia_21() {
assert!(EmailAddress::is_valid("用户@例子.广告"));
}
#[test]
fn test_good_examples_from_wikipedia_22() {
assert!(EmailAddress::is_valid("अजय@डाटा.भारत"));
}
#[test]
fn test_good_examples_from_wikipedia_23() {
assert!(EmailAddress::is_valid("квіточка@пошта.укр"));
}
#[test]
fn test_good_examples_from_wikipedia_24() {
assert!(EmailAddress::is_valid("θσερ@εχαμπλε.ψομ"));
}
#[test]
fn test_good_examples_from_wikipedia_25() {
assert!(EmailAddress::is_valid("Dörte@Sörensen.example.com"));
}
#[test]
fn test_good_examples_from_wikipedia_26() {
assert!(EmailAddress::is_valid("коля@пример.рф"));
}
#[test] #[test]
fn test_bad_examples_from_wikipedia_00() { fn test_bad_examples_from_wikipedia_00() {
// (no @ character) // (no @ character)