| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | package Email::IsEmail; | 
| 2 |  |  |  |  |  |  |  | 
| 3 | 3 |  |  | 3 |  | 142861 | use v5.10; | 
|  | 3 |  |  |  |  | 8 |  | 
| 4 | 3 |  |  | 3 |  | 9 | use strict qw(subs vars); | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 102 |  | 
| 5 |  |  |  |  |  |  | *{'Email::IsEmail'} = \&IsEmail;  # add short alias Email::IsEmail | 
| 6 | 3 |  |  | 3 |  | 8 | use strict 'refs'; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 61 |  | 
| 7 | 3 |  |  | 3 |  | 10 | use warnings; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 66 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 3 |  |  | 3 |  | 10 | use Scalar::Util qw(looks_like_number); | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 285 |  | 
| 10 |  |  |  |  |  |  |  | 
| 11 |  |  |  |  |  |  | our ( @ISA, @EXPORT_OK, %EXPORT_TAGS, $VERSION ); | 
| 12 |  |  |  |  |  |  |  | 
| 13 |  |  |  |  |  |  | @ISA = qw(Exporter); | 
| 14 |  |  |  |  |  |  | @EXPORT_OK = qw(IsEmail); | 
| 15 |  |  |  |  |  |  | %EXPORT_TAGS = ( all => [ @EXPORT_OK ] ) ; | 
| 16 |  |  |  |  |  |  |  | 
| 17 |  |  |  |  |  |  | =head1 NAME | 
| 18 |  |  |  |  |  |  |  | 
| 19 |  |  |  |  |  |  | Email::IsEmail - Checks an email address against the following RFCs: 3696, 1123, 4291, 5321, 5322 | 
| 20 |  |  |  |  |  |  |  | 
| 21 |  |  |  |  |  |  | =head1 VERSION | 
| 22 |  |  |  |  |  |  |  | 
| 23 |  |  |  |  |  |  | Version 3.04.6 | 
| 24 |  |  |  |  |  |  |  | 
| 25 |  |  |  |  |  |  | =cut | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | $VERSION = '3.04.6'; | 
| 28 |  |  |  |  |  |  |  | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | Checks an email address against the following RFCs: 3696, 1123, 4291, 5321, 5322 | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | Example usage: | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | use Email::IsEmail qw/IsEmail/; | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | my $valid = Email::IsEmail('test@example.org'); | 
| 39 |  |  |  |  |  |  | ... | 
| 40 |  |  |  |  |  |  | my $checkDNS    = 0;   # do not check DNS (default) | 
| 41 |  |  |  |  |  |  | my $error_level = -1;  # use dafault error threshold: Email::IsEmail::THRESHOLD | 
| 42 |  |  |  |  |  |  | my %parse_data  = ();  # collect E-mail components | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | $valid = IsEmail( 'test@[127.0.0.1]', $checkDNS, $error_level, \%parse_data ); | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | print "Local-part: ",     $parse_data{Email::IsEmail::COMPONENT_LOCALPART}, "\n"; | 
| 47 |  |  |  |  |  |  | print "Domain: ",         $parse_data{Email::IsEmail::COMPONENT_DOMAIN}, "\n"; | 
| 48 |  |  |  |  |  |  | # only for IPv4/IPv6 addresses: | 
| 49 |  |  |  |  |  |  | print "Domain literal: ", $parse_data{Email::IsEmail::COMPONENT_LITERAL}, "\n"; | 
| 50 |  |  |  |  |  |  |  | 
| 51 |  |  |  |  |  |  | =cut | 
| 52 |  |  |  |  |  |  |  | 
| 53 |  |  |  |  |  |  | =head1 FUNCTIONS | 
| 54 |  |  |  |  |  |  |  | 
| 55 |  |  |  |  |  |  | =cut | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | # Categories | 
| 58 | 3 |  |  | 3 |  | 10 | use constant VALID_CATEGORY => 1; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 201 |  | 
| 59 | 3 |  |  | 3 |  | 10 | use constant DNSWARN => 7; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 100 |  | 
| 60 | 3 |  |  | 3 |  | 9 | use constant RFC5321 => 15; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 98 |  | 
| 61 | 3 |  |  | 3 |  | 9 | use constant CFWS => 31; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 99 |  | 
| 62 | 3 |  |  | 3 |  | 24 | use constant DEPREC => 63; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 96 |  | 
| 63 | 3 |  |  | 3 |  | 8 | use constant RFC5322 => 127; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 109 |  | 
| 64 | 3 |  |  | 3 |  | 9 | use constant ERR => 255; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 98 |  | 
| 65 |  |  |  |  |  |  |  | 
| 66 |  |  |  |  |  |  | # Diagnoses | 
| 67 |  |  |  |  |  |  | # Address is valid | 
| 68 | 3 |  |  | 3 |  | 11 | use constant VALID => 0; | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 94 |  | 
| 69 |  |  |  |  |  |  | # Address is valid but a DNS check was not successful | 
| 70 | 3 |  |  | 3 |  | 19 | use constant DNSWARN_NO_MX_RECORD => 5; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 106 |  | 
| 71 | 3 |  |  | 3 |  | 9 | use constant DNSWARN_NO_RECORD => 6; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 88 |  | 
| 72 |  |  |  |  |  |  | # Address is valid for SMTP but has unusual elements | 
| 73 | 3 |  |  | 3 |  | 9 | use constant RFC5321_TLD => 9; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 99 |  | 
| 74 | 3 |  |  | 3 |  | 8 | use constant RFC5321_TLDNUMERIC => 10; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 86 |  | 
| 75 | 3 |  |  | 3 |  | 9 | use constant RFC5321_QUOTEDSTRING => 11; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 91 |  | 
| 76 | 3 |  |  | 3 |  | 8 | use constant RFC5321_ADDRESSLITERAL => 12; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 91 |  | 
| 77 | 3 |  |  | 3 |  | 9 | use constant RFC5321_IPV6DEPRECATED => 13; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 95 |  | 
| 78 |  |  |  |  |  |  | # Address is valid within the message but cannot be used unmodified for the envelope | 
| 79 | 3 |  |  | 3 |  | 9 | use constant CFWS_COMMENT => 17; | 
|  | 3 |  |  |  |  | 8 |  | 
|  | 3 |  |  |  |  | 94 |  | 
| 80 | 3 |  |  | 3 |  | 9 | use constant CFWS_FWS => 18; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 90 |  | 
| 81 |  |  |  |  |  |  | # Address contains deprecated elements but may still be valid in restricted contexts | 
| 82 | 3 |  |  | 3 |  | 8 | use constant DEPREC_LOCALPART => 33; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 88 |  | 
| 83 | 3 |  |  | 3 |  | 8 | use constant DEPREC_FWS => 34; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 84 |  | 
| 84 | 3 |  |  | 3 |  | 9 | use constant DEPREC_QTEXT => 35; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 93 |  | 
| 85 | 3 |  |  | 3 |  | 8 | use constant DEPREC_QP => 36; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 97 |  | 
| 86 | 3 |  |  | 3 |  | 10 | use constant DEPREC_COMMENT => 37; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 88 |  | 
| 87 | 3 |  |  | 3 |  | 8 | use constant DEPREC_CTEXT => 38; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 90 |  | 
| 88 | 3 |  |  | 3 |  | 8 | use constant DEPREC_CFWS_NEAR_AT => 49; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 93 |  | 
| 89 |  |  |  |  |  |  | # The address is only valid according to the broad definition of RFC 5322. It is otherwise invalid. | 
| 90 | 3 |  |  | 3 |  | 9 | use constant RFC5322_DOMAIN => 65; | 
|  | 3 |  |  |  |  | 7 |  | 
|  | 3 |  |  |  |  | 104 |  | 
| 91 | 3 |  |  | 3 |  | 8 | use constant RFC5322_TOOLONG => 66; | 
|  | 3 |  |  |  |  | 21 |  | 
|  | 3 |  |  |  |  | 88 |  | 
| 92 | 3 |  |  | 3 |  | 9 | use constant RFC5322_LOCAL_TOOLONG => 67; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 94 |  | 
| 93 | 3 |  |  | 3 |  | 8 | use constant RFC5322_DOMAIN_TOOLONG => 68; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 91 |  | 
| 94 | 3 |  |  | 3 |  | 9 | use constant RFC5322_LABEL_TOOLONG => 69; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 87 |  | 
| 95 | 3 |  |  | 3 |  | 9 | use constant RFC5322_DOMAINLITERAL => 70; | 
|  | 3 |  |  |  |  | 11 |  | 
|  | 3 |  |  |  |  | 88 |  | 
| 96 | 3 |  |  | 3 |  | 10 | use constant RFC5322_DOMLIT_OBSDTEXT => 71; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 93 |  | 
| 97 | 3 |  |  | 3 |  | 11 | use constant RFC5322_IPV6_GRPCOUNT => 72; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 128 |  | 
| 98 | 3 |  |  | 3 |  | 10 | use constant RFC5322_IPV6_2X2XCOLON => 73; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 105 |  | 
| 99 | 3 |  |  | 3 |  | 9 | use constant RFC5322_IPV6_BADCHAR => 74; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 97 |  | 
| 100 | 3 |  |  | 3 |  | 9 | use constant RFC5322_IPV6_MAXGRPS => 75; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 91 |  | 
| 101 | 3 |  |  | 3 |  | 9 | use constant RFC5322_IPV6_COLONSTRT => 76; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 84 |  | 
| 102 | 3 |  |  | 3 |  | 6 | use constant RFC5322_IPV6_COLONEND => 77; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 94 |  | 
| 103 |  |  |  |  |  |  | # Address is invalid for any purpose | 
| 104 | 3 |  |  | 3 |  | 9 | use constant ERR_EXPECTING_DTEXT => 129; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 85 |  | 
| 105 | 3 |  |  | 3 |  | 8 | use constant ERR_NOLOCALPART => 130; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 86 |  | 
| 106 | 3 |  |  | 3 |  | 8 | use constant ERR_NODOMAIN => 131; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 99 |  | 
| 107 | 3 |  |  | 3 |  | 9 | use constant ERR_CONSECUTIVEDOTS => 132; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 98 |  | 
| 108 | 3 |  |  | 3 |  | 9 | use constant ERR_ATEXT_AFTER_CFWS => 133; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 85 |  | 
| 109 | 3 |  |  | 3 |  | 8 | use constant ERR_ATEXT_AFTER_QS => 134; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 84 |  | 
| 110 | 3 |  |  | 3 |  | 7 | use constant ERR_ATEXT_AFTER_DOMLIT => 135; | 
|  | 3 |  |  |  |  | 9 |  | 
|  | 3 |  |  |  |  | 109 |  | 
| 111 | 3 |  |  | 3 |  | 10 | use constant ERR_EXPECTING_QPAIR => 136; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 107 |  | 
| 112 | 3 |  |  | 3 |  | 8 | use constant ERR_EXPECTING_ATEXT => 137; | 
|  | 3 |  |  |  |  | 11 |  | 
|  | 3 |  |  |  |  | 90 |  | 
| 113 | 3 |  |  | 3 |  | 9 | use constant ERR_EXPECTING_QTEXT => 138; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 93 |  | 
| 114 | 3 |  |  | 3 |  | 8 | use constant ERR_EXPECTING_CTEXT => 139; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 100 |  | 
| 115 | 3 |  |  | 3 |  | 10 | use constant ERR_BACKSLASHEND => 140; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 93 |  | 
| 116 | 3 |  |  | 3 |  | 7 | use constant ERR_DOT_START => 141; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 83 |  | 
| 117 | 3 |  |  | 3 |  | 8 | use constant ERR_DOT_END => 142; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 370 |  | 
| 118 | 3 |  |  | 3 |  | 10 | use constant ERR_DOMAINHYPHENSTART => 143; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 111 |  | 
| 119 | 3 |  |  | 3 |  | 9 | use constant ERR_DOMAINHYPHENEND => 144; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 142 |  | 
| 120 | 3 |  |  | 3 |  | 11 | use constant ERR_UNCLOSEDQUOTEDSTR => 145; | 
|  | 3 |  |  |  |  | 4 |  | 
|  | 3 |  |  |  |  | 105 |  | 
| 121 | 3 |  |  | 3 |  | 9 | use constant ERR_UNCLOSEDCOMMENT => 146; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 95 |  | 
| 122 | 3 |  |  | 3 |  | 9 | use constant ERR_UNCLOSEDDOMLIT => 147; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 91 |  | 
| 123 | 3 |  |  | 3 |  | 9 | use constant ERR_FWS_CRLF_X2 => 148; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 106 |  | 
| 124 | 3 |  |  | 3 |  | 10 | use constant ERR_FWS_CRLF_END => 149; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 94 |  | 
| 125 | 3 |  |  | 3 |  | 9 | use constant ERR_CR_NO_LF => 150; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 86 |  | 
| 126 |  |  |  |  |  |  | # diagnostic constants end | 
| 127 |  |  |  |  |  |  |  | 
| 128 |  |  |  |  |  |  | # function control | 
| 129 | 3 |  |  | 3 |  | 8 | use constant THRESHOLD => 16; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 94 |  | 
| 130 |  |  |  |  |  |  |  | 
| 131 |  |  |  |  |  |  | # Email parts | 
| 132 | 3 |  |  | 3 |  | 7 | use constant COMPONENT_LOCALPART => 0; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 85 |  | 
| 133 | 3 |  |  | 3 |  | 9 | use constant COMPONENT_DOMAIN => 1; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 134 |  | 
| 134 | 3 |  |  | 3 |  | 13 | use constant COMPONENT_LITERAL => 2; | 
|  | 3 |  |  |  |  | 6 |  | 
|  | 3 |  |  |  |  | 93 |  | 
| 135 | 3 |  |  | 3 |  | 7 | use constant CONTEXT_COMMENT => 3; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 98 |  | 
| 136 | 3 |  |  | 3 |  | 12 | use constant CONTEXT_FWS => 4; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 95 |  | 
| 137 | 3 |  |  | 3 |  | 9 | use constant CONTEXT_QUOTEDSTRING => 5; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 85 |  | 
| 138 | 3 |  |  | 3 |  | 9 | use constant CONTEXT_QUOTEDPAIR => 6; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 100 |  | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | # Miscellaneous string constants | 
| 141 | 3 |  |  | 3 |  | 10 | use constant STRING_AT => '@'; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 88 |  | 
| 142 | 3 |  |  | 3 |  | 46 | use constant STRING_BACKSLASH => '\\'; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 90 |  | 
| 143 | 3 |  |  | 3 |  | 9 | use constant STRING_DOT => '.'; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 99 |  | 
| 144 | 3 |  |  | 3 |  | 8 | use constant STRING_DQUOTE => '"'; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 85 |  | 
| 145 | 3 |  |  | 3 |  | 8 | use constant STRING_OPENPARENTHESIS => '('; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 102 |  | 
| 146 | 3 |  |  | 3 |  | 29 | use constant STRING_CLOSEPARENTHESIS => ')'; | 
|  | 3 |  |  |  |  | 1 |  | 
|  | 3 |  |  |  |  | 95 |  | 
| 147 | 3 |  |  | 3 |  | 8 | use constant STRING_OPENSQBRACKET => '['; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 111 |  | 
| 148 | 3 |  |  | 3 |  | 8 | use constant STRING_CLOSESQBRACKET => ']'; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 97 |  | 
| 149 | 3 |  |  | 3 |  | 8 | use constant STRING_HYPHEN => '-'; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 82 |  | 
| 150 | 3 |  |  | 3 |  | 9 | use constant STRING_COLON => ':'; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 89 |  | 
| 151 | 3 |  |  | 3 |  | 9 | use constant STRING_DOUBLECOLON => '::'; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 87 |  | 
| 152 | 3 |  |  | 3 |  | 8 | use constant STRING_SP => ' '; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 105 |  | 
| 153 | 3 |  |  | 3 |  | 9 | use constant STRING_HTAB => "\t"; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 126 |  | 
| 154 | 3 |  |  | 3 |  | 8 | use constant STRING_CR => "\r"; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 130 |  | 
| 155 | 3 |  |  | 3 |  | 23 | use constant STRING_LF => "\n"; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 144 |  | 
| 156 | 3 |  |  | 3 |  | 10 | use constant STRING_IPV6TAG => 'IPv6:'; | 
|  | 3 |  |  |  |  | 2 |  | 
|  | 3 |  |  |  |  | 113 |  | 
| 157 |  |  |  |  |  |  | # US-ASCII visible characters not valid for atext (http://tools.ietf.org/html/rfc5322#section-3.2.3) | 
| 158 | 3 |  |  | 3 |  | 9 | use constant STRING_SPECIALS => '()<>[]:;@\\,."'; | 
|  | 3 |  |  |  |  | 3 |  | 
|  | 3 |  |  |  |  | 10995 |  | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  |  | 
| 161 |  |  |  |  |  |  | =over 4 | 
| 162 |  |  |  |  |  |  |  | 
| 163 |  |  |  |  |  |  | =item B<IsEmail> | 
| 164 |  |  |  |  |  |  |  | 
| 165 |  |  |  |  |  |  | my $valid = Email::IsEmail( $email, $checkDNS, $errorlevel, $parsedata ); | 
| 166 |  |  |  |  |  |  |  | 
| 167 |  |  |  |  |  |  | Check that an email address conforms to RFCs 5321, 5322 and others | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | As of Version 3.0, we are now distinguishing clearly between a Mailbox | 
| 170 |  |  |  |  |  |  | as defined by RFC 5321 and an addr-spec as defined by RFC 5322. Depending | 
| 171 |  |  |  |  |  |  | on the context, either can be regarded as a valid email address. The | 
| 172 |  |  |  |  |  |  | RFC 5321 Mailbox specification is more restrictive (comments, white space | 
| 173 |  |  |  |  |  |  | and obsolete forms are not allowed) | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | @param string   $email          The email address to check | 
| 176 |  |  |  |  |  |  | @param boolean  $checkDNS       If true then a DNS check for MX records will be made | 
| 177 |  |  |  |  |  |  | @param int      $errorlevel     Determines the boundary between valid and invalid addresses. | 
| 178 |  |  |  |  |  |  | Status codes above this number will be returned as-is, | 
| 179 |  |  |  |  |  |  | status codes below will be returned as Email::IsEmail::VALID. Thus the | 
| 180 |  |  |  |  |  |  | calling program can simply look for Email::IsEmail::VALID if it is | 
| 181 |  |  |  |  |  |  | only interested in whether an address is valid or not. The | 
| 182 |  |  |  |  |  |  | errorlevel will determine how "picky" Email::IsEmail() is about | 
| 183 |  |  |  |  |  |  | the address. | 
| 184 |  |  |  |  |  |  |  | 
| 185 |  |  |  |  |  |  | If omitted or passed as -1 then Email::IsEmail() will return | 
| 186 |  |  |  |  |  |  | true or false rather than an integer error or warning. | 
| 187 |  |  |  |  |  |  |  | 
| 188 |  |  |  |  |  |  | NB Note the difference between $errorlevel = -1 and | 
| 189 |  |  |  |  |  |  | $errorlevel = 0 | 
| 190 |  |  |  |  |  |  | @param hashref  $parsedata      If passed, returns the parsed address components | 
| 191 |  |  |  |  |  |  |  | 
| 192 |  |  |  |  |  |  | =back | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | =cut | 
| 195 |  |  |  |  |  |  |  | 
| 196 |  |  |  |  |  |  | sub IsEmail { | 
| 197 | 81 |  |  | 81 | 1 | 161325 | my ( $email, $checkDNS, $errorlevel, $parsedata ) = @_; | 
| 198 |  |  |  |  |  |  |  | 
| 199 | 81 |  | 100 |  |  | 202 | $checkDNS   //= 0; | 
| 200 | 81 |  | 100 |  |  | 140 | $errorlevel //= -1; | 
| 201 | 81 |  | 50 |  |  | 231 | $parsedata  //= {}; | 
| 202 |  |  |  |  |  |  |  | 
| 203 | 81 | 100 |  |  |  | 140 | return !1 | 
| 204 |  |  |  |  |  |  | unless $email; | 
| 205 |  |  |  |  |  |  |  | 
| 206 | 80 |  |  |  |  | 69 | my ( $threshold, $diagnose ); | 
| 207 |  |  |  |  |  |  |  | 
| 208 | 80 | 100 |  |  |  | 122 | if ( $errorlevel < 0 ) { | 
| 209 | 30 |  |  |  |  | 29 | $threshold = Email::IsEmail::VALID; | 
| 210 | 30 |  |  |  |  | 31 | $diagnose  = 0; | 
| 211 |  |  |  |  |  |  | } | 
| 212 |  |  |  |  |  |  | else { | 
| 213 | 50 |  |  |  |  | 40 | $diagnose  = 1; | 
| 214 | 50 |  |  |  |  | 49 | $threshold = int $errorlevel; | 
| 215 |  |  |  |  |  |  | } | 
| 216 |  |  |  |  |  |  |  | 
| 217 | 80 |  |  |  |  | 87 | my $return_status = [Email::IsEmail::VALID]; | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  |  | 
| 220 |  |  |  |  |  |  | # Parse the address into components, character by character | 
| 221 | 80 |  |  |  |  | 90 | my $raw_length    = length $email; | 
| 222 | 80 |  |  |  |  | 70 | my $context       = Email::IsEmail::COMPONENT_LOCALPART;  # Where we are | 
| 223 | 80 |  |  |  |  | 83 | my $context_stack = [$context];  # Where we have been | 
| 224 | 80 |  |  |  |  | 64 | my $context_prior = Email::IsEmail::COMPONENT_LOCALPART;  # Where we just came from | 
| 225 | 80 |  |  |  |  | 65 | my $token         = '';  # The current character | 
| 226 | 80 |  |  |  |  | 57 | my $token_prior   = '';  # The previous character | 
| 227 | 80 |  |  |  |  | 117 | $parsedata->{Email::IsEmail::COMPONENT_LOCALPART} = '';  # For the components of the address | 
| 228 | 80 |  |  |  |  | 73 | $parsedata->{Email::IsEmail::COMPONENT_DOMAIN}    = ''; | 
| 229 |  |  |  |  |  |  |  | 
| 230 | 80 |  |  |  |  | 152 | my $atomlist      = { | 
| 231 |  |  |  |  |  |  | Email::IsEmail::COMPONENT_LOCALPART => [''], | 
| 232 |  |  |  |  |  |  | Email::IsEmail::COMPONENT_DOMAIN    => [''], | 
| 233 |  |  |  |  |  |  | };  # For the dot-atom elements of the address | 
| 234 | 80 |  |  |  |  | 65 | my $element_count = 0; | 
| 235 | 80 |  |  |  |  | 52 | my $element_len   = 0; | 
| 236 | 80 |  |  |  |  | 51 | my $hyphen_flag   = 0;  # Hyphen cannot occur at the end of a subdomain | 
| 237 | 80 |  |  |  |  | 50 | my $end_or_die    = 0;  # CFWS can only appear at the end of the element | 
| 238 | 80 |  |  |  |  | 50 | my $crlf_count    = 0; | 
| 239 |  |  |  |  |  |  |  | 
| 240 | 80 |  |  |  |  | 144 | for ( my $i = 0; $i < $raw_length; $i++ ) { | 
| 241 | 2119 |  |  |  |  | 1904 | $token = substr $email, $i, 1; | 
| 242 | 2119 |  |  |  |  | 1443 | given($context) { | 
| 243 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 244 |  |  |  |  |  |  | # local-part | 
| 245 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 246 | 2119 |  |  |  |  | 1700 | when (Email::IsEmail::COMPONENT_LOCALPART) { | 
| 247 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.4.1 | 
| 248 |  |  |  |  |  |  | #   local-part      =   dot-atom / quoted-string / obs-local-part | 
| 249 |  |  |  |  |  |  | # | 
| 250 |  |  |  |  |  |  | #   dot-atom        =   [CFWS] dot-atom-text [CFWS] | 
| 251 |  |  |  |  |  |  | # | 
| 252 |  |  |  |  |  |  | #   dot-atom-text   =   1*atext *("." 1*atext) | 
| 253 |  |  |  |  |  |  | # | 
| 254 |  |  |  |  |  |  | #   quoted-string   =   [CFWS] | 
| 255 |  |  |  |  |  |  | #                       DQUOTE *([FWS] qcontent) [FWS] DQUOTE | 
| 256 |  |  |  |  |  |  | #                       [CFWS] | 
| 257 |  |  |  |  |  |  | # | 
| 258 |  |  |  |  |  |  | #   obs-local-part  =   word *("." word) | 
| 259 |  |  |  |  |  |  | # | 
| 260 |  |  |  |  |  |  | #   word            =   atom / quoted-string | 
| 261 |  |  |  |  |  |  | # | 
| 262 |  |  |  |  |  |  | #   atom            =   [CFWS] 1*atext [CFWS] | 
| 263 | 612 |  |  |  |  | 409 | given($token) { | 
| 264 |  |  |  |  |  |  | # Comment | 
| 265 | 612 |  |  |  |  | 570 | when (Email::IsEmail::STRING_OPENPARENTHESIS) { | 
| 266 | 5 | 100 |  |  |  | 10 | if ( $element_len == 0 ) { | 
| 267 |  |  |  |  |  |  | # Comments are OK at the beginning of an element | 
| 268 | 4 | 100 |  |  |  | 2 | push @{$return_status}, ( $element_count == 0 ) ? Email::IsEmail::CFWS_COMMENT : Email::IsEmail::DEPREC_COMMENT; | 
|  | 4 |  |  |  |  | 9 |  | 
| 269 |  |  |  |  |  |  | } | 
| 270 |  |  |  |  |  |  | else { | 
| 271 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::CFWS_COMMENT; | 
|  | 1 |  |  |  |  | 3 |  | 
| 272 | 1 |  |  |  |  | 2 | $end_or_die = 1;  # We can't start a comment in the middle of an element, so this better be the end | 
| 273 |  |  |  |  |  |  | } | 
| 274 |  |  |  |  |  |  |  | 
| 275 | 5 |  |  |  |  | 5 | push @{$context_stack}, $context; | 
|  | 5 |  |  |  |  | 7 |  | 
| 276 | 5 |  |  |  |  | 8 | $context = Email::IsEmail::CONTEXT_COMMENT; | 
| 277 |  |  |  |  |  |  | } | 
| 278 |  |  |  |  |  |  | # Next dot-atom element | 
| 279 | 607 |  |  |  |  | 454 | when (Email::IsEmail::STRING_DOT) { | 
| 280 | 12 | 100 |  |  |  | 16 | if ( $element_len == 0 ) { | 
| 281 |  |  |  |  |  |  | # Another dot, already? | 
| 282 | 2 | 100 |  |  |  | 2 | push @{$return_status}, ( $element_count == 0 ) ? Email::IsEmail::ERR_DOT_START : Email::IsEmail::ERR_CONSECUTIVEDOTS;  # Fatal error | 
|  | 2 |  |  |  |  | 5 |  | 
| 283 |  |  |  |  |  |  | } | 
| 284 |  |  |  |  |  |  | else { | 
| 285 |  |  |  |  |  |  | # The entire local-part can be a quoted string for RFC 5321 | 
| 286 |  |  |  |  |  |  | # If it's just one atom that is quoted then it's an RFC 5322 obsolete form | 
| 287 | 10 | 100 |  |  |  | 21 | if ($end_or_die) { | 
| 288 | 2 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::DEPREC_LOCALPART; | 
|  | 2 |  |  |  |  | 3 |  | 
| 289 |  |  |  |  |  |  | } | 
| 290 |  |  |  |  |  |  | } | 
| 291 |  |  |  |  |  |  |  | 
| 292 | 12 |  |  |  |  | 10 | $end_or_die  = 0;  # CFWS & quoted strings are OK again now we're at the beginning of an element (although they are obsolete forms) | 
| 293 | 12 |  |  |  |  | 10 | $element_len = 0; | 
| 294 | 12 |  |  |  |  | 12 | $element_count++; | 
| 295 | 12 |  |  |  |  | 10 | $parsedata->{Email::IsEmail::COMPONENT_LOCALPART}               .= $token; | 
| 296 | 12 |  |  |  |  | 23 | $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] = ''; | 
| 297 |  |  |  |  |  |  | } | 
| 298 |  |  |  |  |  |  | # Quoted string | 
| 299 | 595 |  |  |  |  | 417 | when (Email::IsEmail::STRING_DQUOTE) { | 
| 300 | 21 | 100 |  |  |  | 30 | if ( $element_len == 0 ) { | 
| 301 |  |  |  |  |  |  | # The entire local-part can be a quoted string for RFC 5321 | 
| 302 |  |  |  |  |  |  | # If it's just one atom that is quoted then it's an RFC 5322 obsolete form | 
| 303 | 17 | 100 |  |  |  | 18 | push @{$return_status}, ( $element_count == 0 ) ? Email::IsEmail::RFC5321_QUOTEDSTRING : Email::IsEmail::DEPREC_LOCALPART; | 
|  | 17 |  |  |  |  | 40 |  | 
| 304 |  |  |  |  |  |  |  | 
| 305 | 17 |  |  |  |  | 26 | $parsedata->{Email::IsEmail::COMPONENT_LOCALPART}                .= $token; | 
| 306 | 17 |  |  |  |  | 18 | $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] .= $token; | 
| 307 | 17 |  |  |  |  | 17 | $element_len++; | 
| 308 | 17 |  |  |  |  | 18 | $end_or_die = 1;  # Quoted string must be the entire element | 
| 309 | 17 |  |  |  |  | 10 | push @{$context_stack}, $context; | 
|  | 17 |  |  |  |  | 22 |  | 
| 310 | 17 |  |  |  |  | 31 | $context = Email::IsEmail::CONTEXT_QUOTEDSTRING; | 
| 311 |  |  |  |  |  |  | } | 
| 312 |  |  |  |  |  |  | else { | 
| 313 | 4 |  |  |  |  | 3 | push @{$return_status}, Email::IsEmail::ERR_EXPECTING_ATEXT;  # Fatal error | 
|  | 4 |  |  |  |  | 10 |  | 
| 314 |  |  |  |  |  |  | } | 
| 315 |  |  |  |  |  |  | } | 
| 316 |  |  |  |  |  |  | # Folding White Space | 
| 317 |  |  |  |  |  |  | when ([ Email::IsEmail::STRING_CR, | 
| 318 |  |  |  |  |  |  | Email::IsEmail::STRING_SP, | 
| 319 | 574 |  |  |  |  | 801 | Email::IsEmail::STRING_HTAB, ]) { | 
| 320 | 5 | 0 | 0 |  |  | 9 | if ( ( $token eq Email::IsEmail::STRING_CR ) and | 
|  |  |  | 33 |  |  |  |  | 
| 321 |  |  |  |  |  |  | ( ( ++$i == $raw_length ) or | 
| 322 |  |  |  |  |  |  | ( substr( $email, $i, 1 ) ne Email::IsEmail::STRING_LF ) ) ) { | 
| 323 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_CR_NO_LF; | 
|  | 0 |  |  |  |  | 0 |  | 
| 324 | 0 |  |  |  |  | 0 | break; | 
| 325 |  |  |  |  |  |  | }  # Fatal error | 
| 326 | 5 | 100 |  |  |  | 7 | if ( $element_len == 0 ) { | 
| 327 | 3 | 100 |  |  |  | 3 | push @{$return_status}, ( $element_count == 0 ) ? Email::IsEmail::CFWS_FWS : Email::IsEmail::DEPREC_FWS; | 
|  | 3 |  |  |  |  | 8 |  | 
| 328 |  |  |  |  |  |  | } | 
| 329 |  |  |  |  |  |  | else { | 
| 330 | 2 |  |  |  |  | 2 | $end_or_die = 1;  # We can't start FWS in the middle of an element, so this better be the end | 
| 331 |  |  |  |  |  |  | } | 
| 332 |  |  |  |  |  |  |  | 
| 333 | 5 |  |  |  |  | 5 | push @{$context_stack}, $context; | 
|  | 5 |  |  |  |  | 5 |  | 
| 334 | 5 |  |  |  |  | 6 | $context     = Email::IsEmail::CONTEXT_FWS; | 
| 335 | 5 |  |  |  |  | 7 | $token_prior = $token; | 
| 336 |  |  |  |  |  |  | } | 
| 337 |  |  |  |  |  |  | # @ | 
| 338 | 569 |  |  |  |  | 539 | when (Email::IsEmail::STRING_AT) { | 
| 339 |  |  |  |  |  |  | # At this point we should have a valid local-part | 
| 340 | 66 | 50 |  |  |  | 51 | if ( scalar @{$context_stack} != 1 ) { | 
|  | 66 |  |  |  |  | 111 |  | 
| 341 | 0 |  |  |  |  | 0 | die('Unexpected item on context stack'); | 
| 342 |  |  |  |  |  |  | } | 
| 343 |  |  |  |  |  |  |  | 
| 344 | 66 | 100 | 66 |  |  | 324 | if ( $parsedata->{Email::IsEmail::COMPONENT_LOCALPART} eq '' ) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 345 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::ERR_NOLOCALPART;  # Fatal error | 
|  | 1 |  |  |  |  | 2 |  | 
| 346 |  |  |  |  |  |  | } | 
| 347 |  |  |  |  |  |  | elsif ( $element_len == 0 ) { | 
| 348 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::ERR_DOT_END;  # Fatal error | 
|  | 1 |  |  |  |  | 1 |  | 
| 349 |  |  |  |  |  |  | } | 
| 350 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1 | 
| 351 |  |  |  |  |  |  | #   The maximum total length of a user name or other local-part is 64 | 
| 352 |  |  |  |  |  |  | #   octets. | 
| 353 |  |  |  |  |  |  | elsif ( length($parsedata->{Email::IsEmail::COMPONENT_LOCALPART}) > 64 ) { | 
| 354 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::RFC5322_LOCAL_TOOLONG; | 
|  | 1 |  |  |  |  | 2 |  | 
| 355 |  |  |  |  |  |  | } | 
| 356 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.4.1 | 
| 357 |  |  |  |  |  |  | #   Comments and folding white space | 
| 358 |  |  |  |  |  |  | #   SHOULD NOT be used around the "@" in the addr-spec. | 
| 359 |  |  |  |  |  |  | # | 
| 360 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc2119 | 
| 361 |  |  |  |  |  |  | # 4. SHOULD NOT   This phrase, or the phrase "NOT RECOMMENDED" mean that | 
| 362 |  |  |  |  |  |  | #    there may exist valid reasons in particular circumstances when the | 
| 363 |  |  |  |  |  |  | #    particular behavior is acceptable or even useful, but the full | 
| 364 |  |  |  |  |  |  | #    implications should be understood and the case carefully weighed | 
| 365 |  |  |  |  |  |  | #    before implementing any behavior described with this label. | 
| 366 |  |  |  |  |  |  | elsif ( ( $context_prior == Email::IsEmail::CONTEXT_COMMENT ) or | 
| 367 |  |  |  |  |  |  | ( $context_prior == Email::IsEmail::CONTEXT_FWS ) ) { | 
| 368 | 1 |  |  |  |  | 1 | push @{$return_status}, Email::IsEmail::DEPREC_CFWS_NEAR_AT; | 
|  | 1 |  |  |  |  | 2 |  | 
| 369 |  |  |  |  |  |  | } | 
| 370 |  |  |  |  |  |  |  | 
| 371 |  |  |  |  |  |  | # Clear everything down for the domain parsing | 
| 372 | 66 |  |  |  |  | 50 | $context       = Email::IsEmail::COMPONENT_DOMAIN;  # Where we are | 
| 373 | 66 |  |  |  |  | 80 | $context_stack = [$context];  # Where we have been | 
| 374 | 66 |  |  |  |  | 68 | $element_count = 0; | 
| 375 | 66 |  |  |  |  | 46 | $element_len   = 0; | 
| 376 | 66 |  |  |  |  | 95 | $end_or_die    = 0;  # CFWS can only appear at the end of the element | 
| 377 |  |  |  |  |  |  | } | 
| 378 |  |  |  |  |  |  | # atext | 
| 379 |  |  |  |  |  |  | default: { | 
| 380 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.2.3 | 
| 381 |  |  |  |  |  |  | #    atext           =   ALPHA / DIGIT /    ; Printable US-ASCII | 
| 382 |  |  |  |  |  |  | #                        "!" / "#" /        ;  characters not including | 
| 383 |  |  |  |  |  |  | #                        "$" / "%" /        ;  specials.  Used for atoms. | 
| 384 |  |  |  |  |  |  | #                        "&" / "'" / | 
| 385 |  |  |  |  |  |  | #                        "*" / "+" / | 
| 386 |  |  |  |  |  |  | #                        "-" / "/" / | 
| 387 |  |  |  |  |  |  | #                        "=" / "?" / | 
| 388 |  |  |  |  |  |  | #                        "^" / "_" / | 
| 389 |  |  |  |  |  |  | #                        "`" / "{" / | 
| 390 |  |  |  |  |  |  | #                        "|" / "}" / | 
| 391 |  |  |  |  |  |  | #                        "~" | 
| 392 | 503 | 100 |  |  |  | 326 | if ($end_or_die) { | 
|  | 503 |  |  |  |  | 521 |  | 
| 393 |  |  |  |  |  |  | # We have encountered atext where it is no longer valid | 
| 394 | 2 |  |  |  |  | 3 | given ($context_prior) { | 
| 395 |  |  |  |  |  |  | when ([ Email::IsEmail::CONTEXT_COMMENT, | 
| 396 | 2 |  |  |  |  | 5 | Email::IsEmail::CONTEXT_FWS, ]) { | 
| 397 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::ERR_ATEXT_AFTER_CFWS; | 
|  | 1 |  |  |  |  | 2 |  | 
| 398 |  |  |  |  |  |  | } | 
| 399 | 1 |  |  |  |  | 2 | when (Email::IsEmail::CONTEXT_QUOTEDSTRING) { | 
| 400 | 1 |  |  |  |  | 1 | push @{$return_status}, Email::IsEmail::ERR_ATEXT_AFTER_QS; | 
|  | 1 |  |  |  |  | 3 |  | 
| 401 |  |  |  |  |  |  | } | 
| 402 |  |  |  |  |  |  | default: { | 
| 403 | 0 |  |  |  |  | 0 | die ("More atext found where none is allowed, but unrecognised prior context: $context_prior"); | 
|  | 0 |  |  |  |  | 0 |  | 
| 404 |  |  |  |  |  |  | } | 
| 405 |  |  |  |  |  |  | } | 
| 406 |  |  |  |  |  |  | } else { | 
| 407 | 501 |  |  |  |  | 366 | $context_prior = $context; | 
| 408 | 501 |  |  |  |  | 365 | my $ord        = ord $token; | 
| 409 |  |  |  |  |  |  |  | 
| 410 | 501 | 100 | 33 |  |  | 2701 | if ( ( $ord < 33 ) or ( $ord > 126 ) or ( $ord == 10 ) or | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
| 411 |  |  |  |  |  |  | ( index( Email::IsEmail::STRING_SPECIALS, $token ) != -1 ) ) { | 
| 412 | 1 |  |  |  |  | 1 | push @{$return_status}, Email::IsEmail::ERR_EXPECTING_ATEXT;  # Fatal error | 
|  | 1 |  |  |  |  | 8 |  | 
| 413 |  |  |  |  |  |  | } | 
| 414 | 501 |  |  |  |  | 464 | $parsedata->{Email::IsEmail::COMPONENT_LOCALPART}                .= $token; | 
| 415 | 501 |  |  |  |  | 426 | $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] .= $token; | 
| 416 | 501 |  |  |  |  | 722 | $element_len++; | 
| 417 |  |  |  |  |  |  | } | 
| 418 |  |  |  |  |  |  | } | 
| 419 |  |  |  |  |  |  | } | 
| 420 |  |  |  |  |  |  | } | 
| 421 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 422 |  |  |  |  |  |  | # Domain | 
| 423 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 424 | 1507 |  |  |  |  | 1049 | when (Email::IsEmail::COMPONENT_DOMAIN) { | 
| 425 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.4.1 | 
| 426 |  |  |  |  |  |  | #   domain          =   dot-atom / domain-literal / obs-domain | 
| 427 |  |  |  |  |  |  | # | 
| 428 |  |  |  |  |  |  | #   dot-atom        =   [CFWS] dot-atom-text [CFWS] | 
| 429 |  |  |  |  |  |  | # | 
| 430 |  |  |  |  |  |  | #   dot-atom-text   =   1*atext *("." 1*atext) | 
| 431 |  |  |  |  |  |  | # | 
| 432 |  |  |  |  |  |  | #   domain-literal  =   [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS] | 
| 433 |  |  |  |  |  |  | # | 
| 434 |  |  |  |  |  |  | #   dtext           =   %d33-90 /          ; Printable US-ASCII | 
| 435 |  |  |  |  |  |  | #                       %d94-126 /         ;  characters not including | 
| 436 |  |  |  |  |  |  | #                       obs-dtext          ;  "[", "]", or "\" | 
| 437 |  |  |  |  |  |  | # | 
| 438 |  |  |  |  |  |  | #   obs-domain      =   atom *("." atom) | 
| 439 |  |  |  |  |  |  | # | 
| 440 |  |  |  |  |  |  | #   atom            =   [CFWS] 1*atext [CFWS] | 
| 441 |  |  |  |  |  |  |  | 
| 442 |  |  |  |  |  |  |  | 
| 443 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5321#section-4.1.2 | 
| 444 |  |  |  |  |  |  | #   Mailbox        = Local-part "@" ( Domain / address-literal ) | 
| 445 |  |  |  |  |  |  | # | 
| 446 |  |  |  |  |  |  | #   Domain         = sub-domain *("." sub-domain) | 
| 447 |  |  |  |  |  |  | # | 
| 448 |  |  |  |  |  |  | #   address-literal  = "[" ( IPv4-address-literal / | 
| 449 |  |  |  |  |  |  | #                    IPv6-address-literal / | 
| 450 |  |  |  |  |  |  | #                    General-address-literal ) "]" | 
| 451 |  |  |  |  |  |  | #                    ; See Section 4.1.3 | 
| 452 |  |  |  |  |  |  |  | 
| 453 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.4.1 | 
| 454 |  |  |  |  |  |  | #      Note: A liberal syntax for the domain portion of addr-spec is | 
| 455 |  |  |  |  |  |  | #      given here.  However, the domain portion contains addressing | 
| 456 |  |  |  |  |  |  | #      information specified by and used in other protocols (e.g., | 
| 457 |  |  |  |  |  |  | #      [RFC1034], [RFC1035], [RFC1123], [RFC5321]).  It is therefore | 
| 458 |  |  |  |  |  |  | #      incumbent upon implementations to conform to the syntax of | 
| 459 |  |  |  |  |  |  | #      addresses for the context in which they are used. | 
| 460 |  |  |  |  |  |  | # Email::IsEmail() author's note: it's not clear how to interpret this in | 
| 461 |  |  |  |  |  |  | # the context of a general email address validator. The conclusion I | 
| 462 |  |  |  |  |  |  | # have reached is this: "addressing information" must comply with | 
| 463 |  |  |  |  |  |  | # RFC 5321 (and in turn RFC 1035), anything that is "semantically | 
| 464 |  |  |  |  |  |  | # invisible" must comply only with RFC 5322. | 
| 465 | 832 |  |  |  |  | 557 | given($token) { | 
| 466 |  |  |  |  |  |  | # Comment | 
| 467 | 832 |  |  |  |  | 717 | when (Email::IsEmail::STRING_OPENPARENTHESIS) { | 
| 468 | 2 | 100 |  |  |  | 4 | if ( $element_len == 0 ) { | 
| 469 |  |  |  |  |  |  | # Comments at the start of the domain are deprecated in the text | 
| 470 |  |  |  |  |  |  | # Comments at the start of a subdomain are obs-domain | 
| 471 |  |  |  |  |  |  | # (http://tools.ietf.org/html/rfc5322#section-3.4.1) | 
| 472 | 1 | 50 |  |  |  | 2 | push @{$return_status}, ( $element_count == 0 ) ? Email::IsEmail::DEPREC_CFWS_NEAR_AT : Email::IsEmail::DEPREC_COMMENT; | 
|  | 1 |  |  |  |  | 3 |  | 
| 473 |  |  |  |  |  |  | } | 
| 474 |  |  |  |  |  |  | else { | 
| 475 | 1 |  |  |  |  | 1 | push @{$return_status}, Email::IsEmail::CFWS_COMMENT; | 
|  | 1 |  |  |  |  | 3 |  | 
| 476 | 1 |  |  |  |  | 2 | $end_or_die = 1;  # We can't start a comment in the middle of an element, so this better be the end | 
| 477 |  |  |  |  |  |  | } | 
| 478 |  |  |  |  |  |  |  | 
| 479 | 2 |  |  |  |  | 1 | push @{$context_stack}, $context; | 
|  | 2 |  |  |  |  | 4 |  | 
| 480 | 2 |  |  |  |  | 9 | $context = Email::IsEmail::CONTEXT_COMMENT; | 
| 481 |  |  |  |  |  |  | } | 
| 482 |  |  |  |  |  |  | # Next dot-atom element | 
| 483 | 830 |  |  |  |  | 565 | when (Email::IsEmail::STRING_DOT) { | 
| 484 | 47 | 100 |  |  |  | 98 | if ( $element_len == 0 ) { | 
|  |  | 100 |  |  |  |  |  | 
| 485 |  |  |  |  |  |  | # Another dot, already? | 
| 486 | 2 | 100 |  |  |  | 3 | push @{$return_status}, ( $element_count == 0 ) ? Email::IsEmail::ERR_DOT_START : Email::IsEmail::ERR_CONSECUTIVEDOTS;  # Fatal error | 
|  | 2 |  |  |  |  | 18 |  | 
| 487 |  |  |  |  |  |  | } | 
| 488 |  |  |  |  |  |  | elsif ($hyphen_flag) { | 
| 489 |  |  |  |  |  |  | # Previous subdomain ended in a hyphen | 
| 490 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::ERR_DOMAINHYPHENEND;  # Fatal error | 
|  | 1 |  |  |  |  | 2 |  | 
| 491 |  |  |  |  |  |  | } | 
| 492 |  |  |  |  |  |  | else { | 
| 493 |  |  |  |  |  |  | # Nowhere in RFC 5321 does it say explicitly that the | 
| 494 |  |  |  |  |  |  | # domain part of a Mailbox must be a valid domain according | 
| 495 |  |  |  |  |  |  | # to the DNS standards set out in RFC 1035, but this *is* | 
| 496 |  |  |  |  |  |  | # implied in several places. For instance, wherever the idea | 
| 497 |  |  |  |  |  |  | # of host routing is discussed the RFC says that the domain | 
| 498 |  |  |  |  |  |  | # must be looked up in the DNS. This would be nonsense unless | 
| 499 |  |  |  |  |  |  | # the domain was designed to be a valid DNS domain. Hence we | 
| 500 |  |  |  |  |  |  | # must conclude that the RFC 1035 restriction on label length | 
| 501 |  |  |  |  |  |  | # also applies to RFC 5321 domains. | 
| 502 |  |  |  |  |  |  | # | 
| 503 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc1035#section-2.3.4 | 
| 504 |  |  |  |  |  |  | # labels          63 octets or less | 
| 505 | 44 | 100 |  |  |  | 60 | if ( $element_len > 63 ) { | 
| 506 | 1 |  |  |  |  | 1 | push @{$return_status}, Email::IsEmail::RFC5322_LABEL_TOOLONG; | 
|  | 1 |  |  |  |  | 2 |  | 
| 507 |  |  |  |  |  |  | } | 
| 508 |  |  |  |  |  |  | } | 
| 509 |  |  |  |  |  |  |  | 
| 510 | 47 |  |  |  |  | 36 | $end_or_die  = 0;  # CFWS is OK again now we're at the beginning of an element (although it may be obsolete CFWS) | 
| 511 | 47 |  |  |  |  | 37 | $element_len = 0; | 
| 512 | 47 |  |  |  |  | 23 | $element_count++; | 
| 513 | 47 |  |  |  |  | 64 | $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count] = ''; | 
| 514 | 47 |  |  |  |  | 80 | $parsedata->{Email::IsEmail::COMPONENT_DOMAIN}               .= $token; | 
| 515 |  |  |  |  |  |  | } | 
| 516 |  |  |  |  |  |  | # Domain literal | 
| 517 | 783 |  |  |  |  | 506 | when (Email::IsEmail::STRING_OPENSQBRACKET) { | 
| 518 | 16 | 50 |  |  |  | 28 | if ( $parsedata->{Email::IsEmail::COMPONENT_DOMAIN} eq '' ) { | 
| 519 | 16 |  |  |  |  | 15 | $end_or_die = 1;  # Domain literal must be the only component | 
| 520 | 16 |  |  |  |  | 13 | $element_len++; | 
| 521 | 16 |  |  |  |  | 10 | push @{$context_stack}, $context; | 
|  | 16 |  |  |  |  | 30 |  | 
| 522 | 16 |  |  |  |  | 16 | $context = Email::IsEmail::COMPONENT_LITERAL; | 
| 523 | 16 |  |  |  |  | 18 | $parsedata->{Email::IsEmail::COMPONENT_DOMAIN}                .= $token; | 
| 524 | 16 |  |  |  |  | 16 | $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count] .= $token; | 
| 525 | 16 |  |  |  |  | 35 | $parsedata->{Email::IsEmail::COMPONENT_LITERAL}                = ''; | 
| 526 |  |  |  |  |  |  | } | 
| 527 |  |  |  |  |  |  | else { | 
| 528 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_EXPECTING_ATEXT;  # Fatal error | 
|  | 0 |  |  |  |  | 0 |  | 
| 529 |  |  |  |  |  |  | } | 
| 530 |  |  |  |  |  |  | } | 
| 531 |  |  |  |  |  |  | # Folding White Space | 
| 532 |  |  |  |  |  |  | when ([ Email::IsEmail::STRING_CR, | 
| 533 |  |  |  |  |  |  | Email::IsEmail::STRING_SP, | 
| 534 | 767 |  |  |  |  | 1085 | Email::IsEmail::STRING_HTAB ]) { | 
| 535 | 3 | 0 | 0 |  |  | 6 | if ( ( $token eq Email::IsEmail::STRING_CR ) and | 
|  |  |  | 33 |  |  |  |  | 
| 536 |  |  |  |  |  |  | ( ( ++$i == $raw_length ) or | 
| 537 |  |  |  |  |  |  | ( substr( $email, $i, 1 ) ne Email::IsEmail::STRING_LF ) ) ) { | 
| 538 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_CR_NO_LF; | 
|  | 0 |  |  |  |  | 0 |  | 
| 539 | 0 |  |  |  |  | 0 | break; | 
| 540 |  |  |  |  |  |  | }  # Fatal error | 
| 541 |  |  |  |  |  |  |  | 
| 542 | 3 | 100 |  |  |  | 7 | if ( $element_len == 0 ) { | 
| 543 | 1 | 50 |  |  |  | 1 | push @{$return_status}, ( $element_count == 0 ) ? Email::IsEmail::DEPREC_CFWS_NEAR_AT : Email::IsEmail::DEPREC_FWS; | 
|  | 1 |  |  |  |  | 4 |  | 
| 544 |  |  |  |  |  |  | } | 
| 545 |  |  |  |  |  |  | else { | 
| 546 | 2 |  |  |  |  | 1 | push @{$return_status}, Email::IsEmail::CFWS_FWS; | 
|  | 2 |  |  |  |  | 3 |  | 
| 547 | 2 |  |  |  |  | 2 | $end_or_die = 1;  # We can't start FWS in the middle of an element, so this better be the end | 
| 548 |  |  |  |  |  |  | } | 
| 549 |  |  |  |  |  |  |  | 
| 550 | 3 |  |  |  |  | 3 | push @{$context_stack}, $context; | 
|  | 3 |  |  |  |  | 4 |  | 
| 551 | 3 |  |  |  |  | 2 | $context     = Email::IsEmail::CONTEXT_FWS; | 
| 552 | 3 |  |  |  |  | 5 | $token_prior = $token; | 
| 553 |  |  |  |  |  |  | } | 
| 554 |  |  |  |  |  |  | # atext | 
| 555 | 764 |  |  |  |  | 689 | default { | 
| 556 |  |  |  |  |  |  | # RFC 5322 allows any atext... | 
| 557 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.2.3 | 
| 558 |  |  |  |  |  |  | #    atext           =   ALPHA / DIGIT /    ; Printable US-ASCII | 
| 559 |  |  |  |  |  |  | #                        "!" / "#" /        ;  characters not including | 
| 560 |  |  |  |  |  |  | #                        "$" / "%" /        ;  specials.  Used for atoms. | 
| 561 |  |  |  |  |  |  | #                        "&" / "'" / | 
| 562 |  |  |  |  |  |  | #                        "*" / "+" / | 
| 563 |  |  |  |  |  |  | #                        "-" / "/" / | 
| 564 |  |  |  |  |  |  | #                        "=" / "?" / | 
| 565 |  |  |  |  |  |  | #                        "^" / "_" / | 
| 566 |  |  |  |  |  |  | #                        "`" / "{" / | 
| 567 |  |  |  |  |  |  | #                        "|" / "}" / | 
| 568 |  |  |  |  |  |  | #                        "~" | 
| 569 |  |  |  |  |  |  |  | 
| 570 |  |  |  |  |  |  | # But RFC 5321 only allows letter-digit-hyphen to comply with DNS rules (RFCs 1034 & 1123) | 
| 571 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5321#section-4.1.2 | 
| 572 |  |  |  |  |  |  | #   sub-domain     = Let-dig [Ldh-str] | 
| 573 |  |  |  |  |  |  | # | 
| 574 |  |  |  |  |  |  | #   Let-dig        = ALPHA / DIGIT | 
| 575 |  |  |  |  |  |  | # | 
| 576 |  |  |  |  |  |  | #   Ldh-str        = *( ALPHA / DIGIT / "-" ) Let-dig | 
| 577 |  |  |  |  |  |  | # | 
| 578 | 764 | 100 |  |  |  | 886 | if ($end_or_die) { | 
| 579 |  |  |  |  |  |  | # We have encountered atext where it is no longer valid | 
| 580 | 1 |  |  |  |  | 2 | given($context_prior) { | 
| 581 |  |  |  |  |  |  | when ([ Email::IsEmail::CONTEXT_COMMENT, | 
| 582 | 1 |  |  |  |  | 3 | Email::IsEmail::CONTEXT_FWS ]) { | 
| 583 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_ATEXT_AFTER_CFWS; | 
|  | 0 |  |  |  |  | 0 |  | 
| 584 |  |  |  |  |  |  | } | 
| 585 | 1 |  |  |  |  | 14 | when (Email::IsEmail::COMPONENT_LITERAL) { | 
| 586 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::ERR_ATEXT_AFTER_DOMLIT; | 
|  | 1 |  |  |  |  | 3 |  | 
| 587 |  |  |  |  |  |  | } | 
| 588 | 0 |  |  |  |  | 0 | default { | 
| 589 | 0 |  |  |  |  | 0 | die ("More atext found where none is allowed, but unrecognised prior context: $context_prior"); | 
| 590 |  |  |  |  |  |  | } | 
| 591 |  |  |  |  |  |  | } | 
| 592 |  |  |  |  |  |  | } | 
| 593 |  |  |  |  |  |  |  | 
| 594 | 764 |  |  |  |  | 538 | my $ord      = ord $token; | 
| 595 | 764 |  |  |  |  | 495 | $hyphen_flag = 0;  # Assume this token isn't a hyphen unless we discover it is | 
| 596 |  |  |  |  |  |  |  | 
| 597 | 764 | 50 | 33 |  |  | 7207 | if ( ( $ord < 33 ) or ( $ord > 126 ) or | 
|  |  | 100 | 33 |  |  |  |  | 
|  |  | 100 | 33 |  |  |  |  | 
| 598 |  |  |  |  |  |  | ( index( Email::IsEmail::STRING_SPECIALS, $token ) ) != -1 ) { | 
| 599 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_EXPECTING_ATEXT;  # Fatal error | 
|  | 0 |  |  |  |  | 0 |  | 
| 600 |  |  |  |  |  |  | } | 
| 601 |  |  |  |  |  |  | elsif ( $token eq Email::IsEmail::STRING_HYPHEN ) { | 
| 602 | 5 | 100 |  |  |  | 11 | if ( $element_len == 0 ) { | 
| 603 |  |  |  |  |  |  | # Hyphens can't be at the beginning of a subdomain | 
| 604 | 1 |  |  |  |  | 1 | push @{$return_status}, Email::IsEmail::ERR_DOMAINHYPHENSTART;  # Fatal error | 
|  | 1 |  |  |  |  | 3 |  | 
| 605 |  |  |  |  |  |  | } | 
| 606 |  |  |  |  |  |  |  | 
| 607 | 5 |  |  |  |  | 6 | $hyphen_flag = 1; | 
| 608 |  |  |  |  |  |  | } elsif ( !( ( $ord > 47 and $ord < 58 ) or ( $ord > 64 and $ord < 91 ) or ( $ord > 96 and $ord < 123 ) ) ) { | 
| 609 |  |  |  |  |  |  | # Not an RFC 5321 subdomain, but still OK by RFC 5322 | 
| 610 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::RFC5322_DOMAIN; | 
|  | 1 |  |  |  |  | 1 |  | 
| 611 |  |  |  |  |  |  | } | 
| 612 |  |  |  |  |  |  |  | 
| 613 | 764 |  |  |  |  | 680 | $parsedata->{Email::IsEmail::COMPONENT_DOMAIN}                .= $token; | 
| 614 | 764 |  |  |  |  | 608 | $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count] .= $token; | 
| 615 | 764 |  |  |  |  | 946 | $element_len++; | 
| 616 |  |  |  |  |  |  | } | 
| 617 |  |  |  |  |  |  | } | 
| 618 |  |  |  |  |  |  | } | 
| 619 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 620 |  |  |  |  |  |  | # Domain literal | 
| 621 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 622 | 675 |  |  |  |  | 479 | when (Email::IsEmail::COMPONENT_LITERAL) { | 
| 623 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.4.1 | 
| 624 |  |  |  |  |  |  | #   domain-literal  =   [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS] | 
| 625 |  |  |  |  |  |  | # | 
| 626 |  |  |  |  |  |  | #   dtext           =   %d33-90 /          ; Printable US-ASCII | 
| 627 |  |  |  |  |  |  | #                       %d94-126 /         ;  characters not including | 
| 628 |  |  |  |  |  |  | #                       obs-dtext          ;  "[", "]", or "\" | 
| 629 |  |  |  |  |  |  | # | 
| 630 |  |  |  |  |  |  | #   obs-dtext       =   obs-NO-WS-CTL / quoted-pair | 
| 631 | 367 |  |  |  |  | 281 | given($token) { | 
| 632 |  |  |  |  |  |  | # End of domain literal | 
| 633 | 367 |  |  |  |  | 308 | when (Email::IsEmail::STRING_CLOSESQBRACKET) { | 
| 634 | 13 | 100 |  |  |  | 19 | if ( Email::IsEmail::_max($return_status) < Email::IsEmail::DEPREC ) { | 
| 635 |  |  |  |  |  |  | # Could be a valid RFC 5321 address literal, so let's check | 
| 636 |  |  |  |  |  |  |  | 
| 637 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5321#section-4.1.2 | 
| 638 |  |  |  |  |  |  | #   address-literal  = "[" ( IPv4-address-literal / | 
| 639 |  |  |  |  |  |  | #                    IPv6-address-literal / | 
| 640 |  |  |  |  |  |  | #                    General-address-literal ) "]" | 
| 641 |  |  |  |  |  |  | #                    ; See Section 4.1.3 | 
| 642 |  |  |  |  |  |  | # | 
| 643 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5321#section-4.1.3 | 
| 644 |  |  |  |  |  |  | #   IPv4-address-literal  = Snum 3("."  Snum) | 
| 645 |  |  |  |  |  |  | # | 
| 646 |  |  |  |  |  |  | #   IPv6-address-literal  = "IPv6:" IPv6-addr | 
| 647 |  |  |  |  |  |  | # | 
| 648 |  |  |  |  |  |  | #   General-address-literal  = Standardized-tag ":" 1*dcontent | 
| 649 |  |  |  |  |  |  | # | 
| 650 |  |  |  |  |  |  | #   Standardized-tag  = Ldh-str | 
| 651 |  |  |  |  |  |  | #                     ; Standardized-tag MUST be specified in a | 
| 652 |  |  |  |  |  |  | #                     ; Standards-Track RFC and registered with IANA | 
| 653 |  |  |  |  |  |  | # | 
| 654 |  |  |  |  |  |  | #   dcontent       = %d33-90 / ; Printable US-ASCII | 
| 655 |  |  |  |  |  |  | #                  %d94-126 ; excl. "[", "\", "]" | 
| 656 |  |  |  |  |  |  | # | 
| 657 |  |  |  |  |  |  | #   Snum           = 1*3DIGIT | 
| 658 |  |  |  |  |  |  | #                  ; representing a decimal integer | 
| 659 |  |  |  |  |  |  | #                  ; value in the range 0 through 255 | 
| 660 |  |  |  |  |  |  | # | 
| 661 |  |  |  |  |  |  | #   IPv6-addr      = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp | 
| 662 |  |  |  |  |  |  | # | 
| 663 |  |  |  |  |  |  | #   IPv6-hex       = 1*4HEXDIG | 
| 664 |  |  |  |  |  |  | # | 
| 665 |  |  |  |  |  |  | #   IPv6-full      = IPv6-hex 7(":" IPv6-hex) | 
| 666 |  |  |  |  |  |  | # | 
| 667 |  |  |  |  |  |  | #   IPv6-comp      = [IPv6-hex *5(":" IPv6-hex)] "::" | 
| 668 |  |  |  |  |  |  | #                  [IPv6-hex *5(":" IPv6-hex)] | 
| 669 |  |  |  |  |  |  | #                  ; The "::" represents at least 2 16-bit groups of | 
| 670 |  |  |  |  |  |  | #                  ; zeros.  No more than 6 groups in addition to the | 
| 671 |  |  |  |  |  |  | #                  ; "::" may be present. | 
| 672 |  |  |  |  |  |  | # | 
| 673 |  |  |  |  |  |  | #   IPv6v4-full    = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal | 
| 674 |  |  |  |  |  |  | # | 
| 675 |  |  |  |  |  |  | #   IPv6v4-comp    = [IPv6-hex *3(":" IPv6-hex)] "::" | 
| 676 |  |  |  |  |  |  | #                  [IPv6-hex *3(":" IPv6-hex) ":"] | 
| 677 |  |  |  |  |  |  | #                  IPv4-address-literal | 
| 678 |  |  |  |  |  |  | #                  ; The "::" represents at least 2 16-bit groups of | 
| 679 |  |  |  |  |  |  | #                  ; zeros.  No more than 4 groups in addition to the | 
| 680 |  |  |  |  |  |  | #                  ; "::" and IPv4-address-literal may be present. | 
| 681 |  |  |  |  |  |  | # | 
| 682 | 12 |  |  |  |  | 10 | my $max_groups     = 8; | 
| 683 | 12 |  |  |  |  | 13 | my $matchesIP      = (); | 
| 684 | 12 |  |  |  |  | 11 | my $index          = -1; | 
| 685 | 12 |  |  |  |  | 17 | my $addressliteral = $parsedata->{Email::IsEmail::COMPONENT_LITERAL}; | 
| 686 |  |  |  |  |  |  |  | 
| 687 |  |  |  |  |  |  | # Extract IPv4 part from the end of the address-literal (if there is one) | 
| 688 | 12 | 100 |  |  |  | 70 | if ( @{$matchesIP} = $addressliteral =~ /\b((?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?))$/ ) { | 
|  | 12 |  |  |  |  | 41 |  | 
| 689 | 5 |  |  |  |  | 11 | $index = index( $addressliteral, $matchesIP->[0] ); | 
| 690 | 5 | 100 |  |  |  | 25 | if ( $index > 0 ) { | 
| 691 | 3 |  |  |  |  | 9 | $addressliteral = substr( $addressliteral, 0x0, $index ) . '0:0';  # Convert IPv4 part to IPv6 format for further testing | 
| 692 |  |  |  |  |  |  | } | 
| 693 |  |  |  |  |  |  | } | 
| 694 |  |  |  |  |  |  |  | 
| 695 | 12 | 100 |  |  |  | 43 | if ( $index == 0 ) { | 
|  |  | 100 |  |  |  |  |  | 
| 696 |  |  |  |  |  |  | # Nothing there except a valid IPv4 address, so... | 
| 697 | 2 |  |  |  |  | 3 | push @{$return_status}, Email::IsEmail::RFC5321_ADDRESSLITERAL; | 
|  | 2 |  |  |  |  | 4 |  | 
| 698 |  |  |  |  |  |  | } | 
| 699 |  |  |  |  |  |  | elsif ( substr( $addressliteral, 0x0, length(Email::IsEmail::STRING_IPV6TAG) ) ne Email::IsEmail::STRING_IPV6TAG ) { | 
| 700 | 1 |  |  |  |  | 1 | push @{$return_status}, Email::IsEmail::RFC5322_DOMAINLITERAL; | 
|  | 1 |  |  |  |  | 4 |  | 
| 701 |  |  |  |  |  |  | } | 
| 702 |  |  |  |  |  |  | else { | 
| 703 | 9 |  |  |  |  | 13 | my $IPv6 = substr $addressliteral, 5; | 
| 704 | 9 |  |  |  |  | 91 | $matchesIP     = [ split Email::IsEmail::STRING_COLON, $IPv6 ];  # Revision 2.7: Daniel Marschall's new IPv6 testing strategy | 
| 705 | 9 |  |  |  |  | 12 | my $groupCount = scalar @{$matchesIP}; | 
|  | 9 |  |  |  |  | 13 |  | 
| 706 | 9 |  |  |  |  | 14 | my $index      = index $IPv6, Email::IsEmail::STRING_DOUBLECOLON; | 
| 707 |  |  |  |  |  |  |  | 
| 708 | 9 | 100 |  |  |  | 15 | if ( $index == -1 ) { | 
| 709 |  |  |  |  |  |  | # We need exactly the right number of groups | 
| 710 | 4 | 100 |  |  |  | 10 | if ( $groupCount != $max_groups ) { | 
| 711 | 3 |  |  |  |  | 3 | push @{$return_status}, Email::IsEmail::RFC5322_IPV6_GRPCOUNT; | 
|  | 3 |  |  |  |  | 5 |  | 
| 712 |  |  |  |  |  |  | } | 
| 713 |  |  |  |  |  |  | } | 
| 714 |  |  |  |  |  |  | else { | 
| 715 | 5 | 100 |  |  |  | 16 | if ( -1 != index( $IPv6, Email::IsEmail::STRING_DOUBLECOLON, $index + 1 ) ) { | 
| 716 | 1 |  |  |  |  | 8 | push @{$return_status}, Email::IsEmail::RFC5322_IPV6_2X2XCOLON; | 
|  | 1 |  |  |  |  | 3 |  | 
| 717 |  |  |  |  |  |  | } | 
| 718 |  |  |  |  |  |  | else { | 
| 719 | 4 | 100 | 66 |  |  | 23 | if ( ( $index == 0 ) or ( $index == ( length($IPv6) - 2 ) ) ) { | 
| 720 | 1 |  |  |  |  | 3 | $max_groups++;  # RFC 4291 allows :: at the start or end of an address with 7 other groups in addition | 
| 721 |  |  |  |  |  |  | } | 
| 722 |  |  |  |  |  |  |  | 
| 723 | 4 | 100 |  |  |  | 17 | if ( $groupCount > $max_groups ) { | 
|  |  | 50 |  |  |  |  |  | 
| 724 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::RFC5322_IPV6_MAXGRPS; | 
|  | 1 |  |  |  |  | 4 |  | 
| 725 |  |  |  |  |  |  | } | 
| 726 |  |  |  |  |  |  | elsif ( $groupCount == $max_groups ) { | 
| 727 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::RFC5321_IPV6DEPRECATED;  # Eliding a single "::" | 
|  | 0 |  |  |  |  | 0 |  | 
| 728 |  |  |  |  |  |  | } | 
| 729 |  |  |  |  |  |  | } | 
| 730 |  |  |  |  |  |  | } | 
| 731 |  |  |  |  |  |  |  | 
| 732 |  |  |  |  |  |  | # Revision 2.7: Daniel Marschall's new IPv6 testing strategy | 
| 733 | 9 | 100 | 100 |  |  | 49 | if ( ( substr( $IPv6, 0x0, 1 ) eq Email::IsEmail::STRING_COLON ) and | 
|  |  | 100 | 66 |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
| 734 |  |  |  |  |  |  | ( substr( $IPv6, 1,  1 ) ne Email::IsEmail::STRING_COLON ) ) { | 
| 735 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::RFC5322_IPV6_COLONSTRT;  # Address starts with a single colon | 
|  | 1 |  |  |  |  | 4 |  | 
| 736 |  |  |  |  |  |  | } | 
| 737 |  |  |  |  |  |  | elsif ( ( substr( $IPv6, -1 ) eq Email::IsEmail::STRING_COLON) and | 
| 738 |  |  |  |  |  |  | ( substr( $IPv6, -2, 1 ) ne Email::IsEmail::STRING_COLON ) ) { | 
| 739 | 1 |  |  |  |  | 1 | push @{$return_status}, Email::IsEmail::RFC5322_IPV6_COLONEND;  # Address ends with a single colon | 
|  | 1 |  |  |  |  | 3 |  | 
| 740 |  |  |  |  |  |  | } | 
| 741 | 44 |  |  |  |  | 124 | elsif ( scalar(grep { !/^[0-9A-Fa-f]{0,4}$/ } @{$matchesIP}) != 0 ) { | 
|  | 7 |  |  |  |  | 11 |  | 
| 742 | 1 |  |  |  |  | 6 | push @{$return_status}, Email::IsEmail::RFC5322_IPV6_BADCHAR;  # Check for unmatched characters | 
|  | 1 |  |  |  |  | 3 |  | 
| 743 |  |  |  |  |  |  | } | 
| 744 |  |  |  |  |  |  | else { | 
| 745 | 6 |  |  |  |  | 7 | push @{$return_status}, Email::IsEmail::RFC5321_ADDRESSLITERAL; | 
|  | 6 |  |  |  |  | 17 |  | 
| 746 |  |  |  |  |  |  | } | 
| 747 |  |  |  |  |  |  | } | 
| 748 |  |  |  |  |  |  | } | 
| 749 |  |  |  |  |  |  | else { | 
| 750 | 1 |  |  |  |  | 3 | push @{$return_status}, Email::IsEmail::RFC5322_DOMAINLITERAL; | 
|  | 1 |  |  |  |  | 3 |  | 
| 751 |  |  |  |  |  |  | } | 
| 752 |  |  |  |  |  |  |  | 
| 753 | 13 |  |  |  |  | 21 | $parsedata->{Email::IsEmail::COMPONENT_DOMAIN}                .= $token; | 
| 754 | 13 |  |  |  |  | 13 | $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count] .= $token; | 
| 755 | 13 |  |  |  |  | 13 | $element_len++; | 
| 756 | 13 |  |  |  |  | 14 | $context_prior = $context; | 
| 757 | 13 |  |  |  |  | 9 | $context       = pop @{$context_stack}; | 
|  | 13 |  |  |  |  | 33 |  | 
| 758 |  |  |  |  |  |  | } | 
| 759 | 354 |  |  |  |  | 257 | when (Email::IsEmail::STRING_BACKSLASH) { | 
| 760 | 2 |  |  |  |  | 1 | push @{$return_status}, Email::IsEmail::RFC5322_DOMLIT_OBSDTEXT; | 
|  | 2 |  |  |  |  | 4 |  | 
| 761 | 2 |  |  |  |  | 2 | push @{$context_stack}, $context; | 
|  | 2 |  |  |  |  | 3 |  | 
| 762 | 2 |  |  |  |  | 3 | $context = Email::IsEmail::CONTEXT_QUOTEDPAIR; | 
| 763 |  |  |  |  |  |  | } | 
| 764 |  |  |  |  |  |  | # Folding White Space | 
| 765 |  |  |  |  |  |  | when ([ Email::IsEmail::STRING_CR, | 
| 766 |  |  |  |  |  |  | Email::IsEmail::STRING_SP, | 
| 767 | 352 |  |  |  |  | 525 | Email::IsEmail::STRING_HTAB, ]) { | 
| 768 | 0 | 0 | 0 |  |  | 0 | if ( ( $token eq Email::IsEmail::STRING_CR ) and | 
|  |  |  | 0 |  |  |  |  | 
| 769 |  |  |  |  |  |  | ( ( ++$i == $raw_length ) or | 
| 770 |  |  |  |  |  |  | ( substr( $email, $i, 1 ) ne Email::IsEmail::STRING_LF ) ) ) { | 
| 771 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_CR_NO_LF; | 
|  | 0 |  |  |  |  | 0 |  | 
| 772 | 0 |  |  |  |  | 0 | break; | 
| 773 |  |  |  |  |  |  | }  # Fatal error | 
| 774 |  |  |  |  |  |  |  | 
| 775 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::CFWS_FWS; | 
|  | 0 |  |  |  |  | 0 |  | 
| 776 | 0 |  |  |  |  | 0 | push @{$context_stack}, $context; | 
|  | 0 |  |  |  |  | 0 |  | 
| 777 | 0 |  |  |  |  | 0 | $context     = Email::IsEmail::CONTEXT_FWS; | 
| 778 | 0 |  |  |  |  | 0 | $token_prior = $token; | 
| 779 |  |  |  |  |  |  | } | 
| 780 |  |  |  |  |  |  | # dtext | 
| 781 | 352 |  |  |  |  | 322 | default { | 
| 782 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.4.1 | 
| 783 |  |  |  |  |  |  | #   dtext           =   %d33-90 /          ; Printable US-ASCII | 
| 784 |  |  |  |  |  |  | #                       %d94-126 /         ;  characters not including | 
| 785 |  |  |  |  |  |  | #                       obs-dtext          ;  "[", "]", or "\" | 
| 786 |  |  |  |  |  |  | # | 
| 787 |  |  |  |  |  |  | #   obs-dtext       =   obs-NO-WS-CTL / quoted-pair | 
| 788 |  |  |  |  |  |  | # | 
| 789 |  |  |  |  |  |  | #   obs-NO-WS-CTL   =   %d1-8 /            ; US-ASCII control | 
| 790 |  |  |  |  |  |  | #                       %d11 /             ;  characters that do not | 
| 791 |  |  |  |  |  |  | #                       %d12 /             ;  include the carriage | 
| 792 |  |  |  |  |  |  | #                       %d14-31 /          ;  return, line feed, and | 
| 793 |  |  |  |  |  |  | #                       %d127              ;  white space characters | 
| 794 | 352 |  |  |  |  | 252 | my $ord = ord $token; | 
| 795 |  |  |  |  |  |  |  | 
| 796 |  |  |  |  |  |  | # CR, LF, SP & HTAB have already been parsed above | 
| 797 | 352 | 100 | 33 |  |  | 1962 | if ( ( $ord > 127 ) or ( $ord == 0 ) or | 
|  |  | 50 | 66 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 798 |  |  |  |  |  |  | ( $token eq Email::IsEmail::STRING_OPENSQBRACKET ) ) { | 
| 799 | 1 |  |  |  |  | 4 | push @{$return_status}, Email::IsEmail::ERR_EXPECTING_DTEXT;  # Fatal error | 
|  | 1 |  |  |  |  | 3 |  | 
| 800 | 1 |  |  |  |  | 3 | break; | 
| 801 |  |  |  |  |  |  | } | 
| 802 |  |  |  |  |  |  | elsif ( ( $ord < 33 ) or ( $ord == 127 ) ) { | 
| 803 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::RFC5322_DOMLIT_OBSDTEXT; | 
|  | 0 |  |  |  |  | 0 |  | 
| 804 |  |  |  |  |  |  | } | 
| 805 |  |  |  |  |  |  |  | 
| 806 | 351 |  |  |  |  | 334 | $parsedata->{Email::IsEmail::COMPONENT_LITERAL}               .= $token; | 
| 807 | 351 |  |  |  |  | 241 | $parsedata->{Email::IsEmail::COMPONENT_DOMAIN}                .= $token; | 
| 808 | 351 |  |  |  |  | 287 | $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count] .= $token; | 
| 809 | 351 |  |  |  |  | 452 | $element_len++; | 
| 810 |  |  |  |  |  |  | } | 
| 811 |  |  |  |  |  |  | } | 
| 812 |  |  |  |  |  |  | } | 
| 813 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 814 |  |  |  |  |  |  | # Quoted string | 
| 815 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 816 | 308 |  |  |  |  | 214 | when (Email::IsEmail::CONTEXT_QUOTEDSTRING) { | 
| 817 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.2.4 | 
| 818 |  |  |  |  |  |  | #   quoted-string   =   [CFWS] | 
| 819 |  |  |  |  |  |  | #                       DQUOTE *([FWS] qcontent) [FWS] DQUOTE | 
| 820 |  |  |  |  |  |  | #                       [CFWS] | 
| 821 |  |  |  |  |  |  | # | 
| 822 |  |  |  |  |  |  | #   qcontent        =   qtext / quoted-pair | 
| 823 | 205 |  |  |  |  | 161 | given($token) { | 
| 824 |  |  |  |  |  |  | # Quoted pair | 
| 825 | 205 |  |  |  |  | 166 | when (Email::IsEmail::STRING_BACKSLASH) { | 
| 826 | 10 |  |  |  |  | 9 | push @{$context_stack}, $context; | 
|  | 10 |  |  |  |  | 13 |  | 
| 827 | 10 |  |  |  |  | 14 | $context = Email::IsEmail::CONTEXT_QUOTEDPAIR; | 
| 828 |  |  |  |  |  |  | } | 
| 829 |  |  |  |  |  |  | # Folding White Space | 
| 830 |  |  |  |  |  |  | # Inside a quoted string, spaces are allowed as regular characters. | 
| 831 |  |  |  |  |  |  | # It's only FWS if we include HTAB or CRLF | 
| 832 |  |  |  |  |  |  | when ([ Email::IsEmail::STRING_CR, | 
| 833 | 195 |  |  |  |  | 257 | Email::IsEmail::STRING_HTAB, ]) { | 
| 834 | 0 | 0 | 0 |  |  | 0 | if ( ( $token eq Email::IsEmail::STRING_CR ) and | 
|  |  |  | 0 |  |  |  |  | 
| 835 |  |  |  |  |  |  | ( ( ++$i == $raw_length ) or | 
| 836 |  |  |  |  |  |  | ( substr( $email, $i, 1 ) ne Email::IsEmail::STRING_LF ) ) ) { | 
| 837 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_CR_NO_LF; | 
|  | 0 |  |  |  |  | 0 |  | 
| 838 | 0 |  |  |  |  | 0 | break; | 
| 839 |  |  |  |  |  |  | }  # Fatal error | 
| 840 |  |  |  |  |  |  |  | 
| 841 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.2.2 | 
| 842 |  |  |  |  |  |  | #   Runs of FWS, comment, or CFWS that occur between lexical tokens in a | 
| 843 |  |  |  |  |  |  | #   structured header field are semantically interpreted as a single | 
| 844 |  |  |  |  |  |  | #   space character. | 
| 845 |  |  |  |  |  |  |  | 
| 846 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.2.4 | 
| 847 |  |  |  |  |  |  | #   the CRLF in any FWS/CFWS that appears within the quoted-string [is] | 
| 848 |  |  |  |  |  |  | #   semantically "invisible" and therefore not part of the quoted-string | 
| 849 | 0 |  |  |  |  | 0 | $parsedata->{Email::IsEmail::COMPONENT_LOCALPART}                .= Email::IsEmail::STRING_SP; | 
| 850 | 0 |  |  |  |  | 0 | $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] .= Email::IsEmail::STRING_SP; | 
| 851 | 0 |  |  |  |  | 0 | $element_len++; | 
| 852 |  |  |  |  |  |  |  | 
| 853 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::CFWS_FWS; | 
|  | 0 |  |  |  |  | 0 |  | 
| 854 | 0 |  |  |  |  | 0 | push @{$context_stack}, $context; | 
|  | 0 |  |  |  |  | 0 |  | 
| 855 | 0 |  |  |  |  | 0 | $context     = Email::IsEmail::CONTEXT_FWS; | 
| 856 | 0 |  |  |  |  | 0 | $token_prior = $token; | 
| 857 |  |  |  |  |  |  | } | 
| 858 |  |  |  |  |  |  | # End of quoted string | 
| 859 | 195 |  |  |  |  | 176 | when (Email::IsEmail::STRING_DQUOTE) { | 
| 860 | 15 |  |  |  |  | 14 | $parsedata->{Email::IsEmail::COMPONENT_LOCALPART}                .= $token; | 
| 861 | 15 |  |  |  |  | 16 | $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] .= $token; | 
| 862 | 15 |  |  |  |  | 11 | $element_len++; | 
| 863 | 15 |  |  |  |  | 11 | $context_prior = $context; | 
| 864 | 15 |  |  |  |  | 13 | $context       = pop @{$context_stack}; | 
|  | 15 |  |  |  |  | 28 |  | 
| 865 |  |  |  |  |  |  | } | 
| 866 |  |  |  |  |  |  | # qtext | 
| 867 | 180 |  |  |  |  | 132 | default { | 
| 868 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.2.4 | 
| 869 |  |  |  |  |  |  | #   qtext           =   %d33 /             ; Printable US-ASCII | 
| 870 |  |  |  |  |  |  | #                       %d35-91 /          ;  characters not including | 
| 871 |  |  |  |  |  |  | #                       %d93-126 /         ;  "\" or the quote character | 
| 872 |  |  |  |  |  |  | #                       obs-qtext | 
| 873 |  |  |  |  |  |  | # | 
| 874 |  |  |  |  |  |  | #   obs-qtext       =   obs-NO-WS-CTL | 
| 875 |  |  |  |  |  |  | # | 
| 876 |  |  |  |  |  |  | #   obs-NO-WS-CTL   =   %d1-8 /            ; US-ASCII control | 
| 877 |  |  |  |  |  |  | #                       %d11 /             ;  characters that do not | 
| 878 |  |  |  |  |  |  | #                       %d12 /             ;  include the carriage | 
| 879 |  |  |  |  |  |  | #                       %d14-31 /          ;  return, line feed, and | 
| 880 |  |  |  |  |  |  | #                       %d127              ;  white space characters | 
| 881 | 180 |  |  |  |  | 140 | my $ord = ord $token; | 
| 882 |  |  |  |  |  |  |  | 
| 883 | 180 | 50 | 33 |  |  | 1009 | if ( ( $ord > 127 ) or ( $ord == 0 ) or ( $ord == 10 ) ) { | 
|  |  | 100 | 33 |  |  |  |  | 
|  |  |  | 66 |  |  |  |  | 
| 884 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_EXPECTING_QTEXT;  # Fatal error | 
|  | 0 |  |  |  |  | 0 |  | 
| 885 |  |  |  |  |  |  | } | 
| 886 |  |  |  |  |  |  | elsif ( ( $ord < 32 ) or ( $ord == 127 ) ) { | 
| 887 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::DEPREC_QTEXT; | 
|  | 1 |  |  |  |  | 2 |  | 
| 888 |  |  |  |  |  |  | } | 
| 889 |  |  |  |  |  |  |  | 
| 890 | 180 |  |  |  |  | 166 | $parsedata->{Email::IsEmail::COMPONENT_LOCALPART}                .= $token; | 
| 891 | 180 |  |  |  |  | 140 | $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] .= $token; | 
| 892 | 180 |  |  |  |  | 213 | $element_len++; | 
| 893 |  |  |  |  |  |  | } | 
| 894 |  |  |  |  |  |  | } | 
| 895 |  |  |  |  |  |  |  | 
| 896 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.4.1 | 
| 897 |  |  |  |  |  |  | #   If the | 
| 898 |  |  |  |  |  |  | #   string can be represented as a dot-atom (that is, it contains no | 
| 899 |  |  |  |  |  |  | #   characters other than atext characters or "." surrounded by atext | 
| 900 |  |  |  |  |  |  | #   characters), then the dot-atom form SHOULD be used and the quoted- | 
| 901 |  |  |  |  |  |  | #   string form SHOULD NOT be used. | 
| 902 |  |  |  |  |  |  | # TODO | 
| 903 |  |  |  |  |  |  | } | 
| 904 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 905 |  |  |  |  |  |  | # Quoted pair | 
| 906 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 907 | 103 |  |  |  |  | 70 | when (Email::IsEmail::CONTEXT_QUOTEDPAIR) { | 
| 908 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.2.1 | 
| 909 |  |  |  |  |  |  | #   quoted-pair     =   ("\" (VCHAR / WSP)) / obs-qp | 
| 910 |  |  |  |  |  |  | # | 
| 911 |  |  |  |  |  |  | #   VCHAR           =  %d33-126            ; visible (printing) characters | 
| 912 |  |  |  |  |  |  | #   WSP             =  SP / HTAB           ; white space | 
| 913 |  |  |  |  |  |  | # | 
| 914 |  |  |  |  |  |  | #   obs-qp          =   "\" (%d0 / obs-NO-WS-CTL / LF / CR) | 
| 915 |  |  |  |  |  |  | # | 
| 916 |  |  |  |  |  |  | #   obs-NO-WS-CTL   =   %d1-8 /            ; US-ASCII control | 
| 917 |  |  |  |  |  |  | #                       %d11 /             ;  characters that do not | 
| 918 |  |  |  |  |  |  | #                       %d12 /             ;  include the carriage | 
| 919 |  |  |  |  |  |  | #                       %d14-31 /          ;  return, line feed, and | 
| 920 |  |  |  |  |  |  | #                       %d127              ;  white space characters | 
| 921 |  |  |  |  |  |  | # | 
| 922 |  |  |  |  |  |  | # i.e. obs-qp       =  "\" (%d0-8, %d10-31 / %d127) | 
| 923 | 13 |  |  |  |  | 14 | my $ord = ord $token; | 
| 924 |  |  |  |  |  |  |  | 
| 925 | 13 | 50 | 33 |  |  | 63 | if ( $ord > 127 ) { | 
|  |  | 50 | 33 |  |  |  |  | 
| 926 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_EXPECTING_QPAIR;  # Fatal error | 
|  | 0 |  |  |  |  | 0 |  | 
| 927 |  |  |  |  |  |  | } | 
| 928 |  |  |  |  |  |  | elsif ( ( ( $ord < 31 ) and ( $ord != 9 ) ) or ( $ord == 127 ) ) {  # SP & HTAB are allowed | 
| 929 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::DEPREC_QP; | 
|  | 0 |  |  |  |  | 0 |  | 
| 930 |  |  |  |  |  |  | } | 
| 931 |  |  |  |  |  |  |  | 
| 932 |  |  |  |  |  |  | # At this point we know where this qpair occurred so | 
| 933 |  |  |  |  |  |  | # we could check to see if the character actually | 
| 934 |  |  |  |  |  |  | # needed to be quoted at all. | 
| 935 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5321#section-4.1.2 | 
| 936 |  |  |  |  |  |  | #   the sending system SHOULD transmit the | 
| 937 |  |  |  |  |  |  | #   form that uses the minimum quoting possible. | 
| 938 |  |  |  |  |  |  | # TODO: check whether the character needs to be quoted (escaped) in this context | 
| 939 | 13 |  |  |  |  | 11 | $context_prior = $context; | 
| 940 | 13 |  |  |  |  | 9 | $context       = pop @{$context_stack};  # End of qpair | 
|  | 13 |  |  |  |  | 15 |  | 
| 941 | 13 |  |  |  |  | 16 | $token         = Email::IsEmail::STRING_BACKSLASH . $token; | 
| 942 |  |  |  |  |  |  |  | 
| 943 | 13 |  |  |  |  | 15 | given($context) { | 
| 944 | 13 |  |  |  |  | 14 | when (Email::IsEmail::CONTEXT_COMMENT) {} | 
| 945 | 12 |  |  |  |  | 12 | when (Email::IsEmail::CONTEXT_QUOTEDSTRING) { | 
| 946 | 10 |  |  |  |  | 11 | $parsedata->{Email::IsEmail::COMPONENT_LOCALPART}                .= $token; | 
| 947 | 10 |  |  |  |  | 7 | $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] .= $token; | 
| 948 | 10 |  |  |  |  | 17 | $element_len += 2;  # The maximum sizes specified by RFC 5321 are octet counts, so we must include the backslash | 
| 949 |  |  |  |  |  |  | } | 
| 950 | 2 |  |  |  |  | 2 | when (Email::IsEmail::COMPONENT_LITERAL) { | 
| 951 | 2 |  |  |  |  | 2 | $parsedata->{Email::IsEmail::COMPONENT_DOMAIN}                .= $token; | 
| 952 | 2 |  |  |  |  | 3 | $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count] .= $token; | 
| 953 | 2 |  |  |  |  | 3 | $element_len += 2;  # The maximum sizes specified by RFC 5321 are octet counts, so we must include the backslash | 
| 954 |  |  |  |  |  |  | } | 
| 955 | 0 |  |  |  |  | 0 | default { | 
| 956 | 0 |  |  |  |  | 0 | die("Quoted pair logic invoked in an invalid context: $context"); | 
| 957 |  |  |  |  |  |  | } | 
| 958 |  |  |  |  |  |  | } | 
| 959 |  |  |  |  |  |  | } | 
| 960 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 961 |  |  |  |  |  |  | # Comment | 
| 962 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 963 | 90 |  |  |  |  | 65 | when (Email::IsEmail::CONTEXT_COMMENT) { | 
| 964 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.2.2 | 
| 965 |  |  |  |  |  |  | #   comment         =   "(" *([FWS] ccontent) [FWS] ")" | 
| 966 |  |  |  |  |  |  | # | 
| 967 |  |  |  |  |  |  | #   ccontent        =   ctext / quoted-pair / comment | 
| 968 | 83 |  |  |  |  | 59 | given($token) { | 
| 969 |  |  |  |  |  |  | # Nested comment | 
| 970 | 83 |  |  |  |  | 59 | when (Email::IsEmail::STRING_OPENPARENTHESIS) { | 
| 971 |  |  |  |  |  |  | # Nested comments are OK | 
| 972 | 1 |  |  |  |  | 1 | push @{$context_stack}, $context; | 
|  | 1 |  |  |  |  | 2 |  | 
| 973 | 1 |  |  |  |  | 2 | $context = Email::IsEmail::CONTEXT_COMMENT; | 
| 974 |  |  |  |  |  |  | } | 
| 975 |  |  |  |  |  |  | # End of comment | 
| 976 | 82 |  |  |  |  | 63 | when (Email::IsEmail::STRING_CLOSEPARENTHESIS) { | 
| 977 | 5 |  |  |  |  | 4 | $context_prior = $context; | 
| 978 | 5 |  |  |  |  | 4 | $context       = pop @{$context_stack}; | 
|  | 5 |  |  |  |  | 10 |  | 
| 979 |  |  |  |  |  |  |  | 
| 980 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.2.2 | 
| 981 |  |  |  |  |  |  | #   Runs of FWS, comment, or CFWS that occur between lexical tokens in a | 
| 982 |  |  |  |  |  |  | #   structured header field are semantically interpreted as a single | 
| 983 |  |  |  |  |  |  | #   space character. | 
| 984 |  |  |  |  |  |  | # | 
| 985 |  |  |  |  |  |  | # Email::IsEmail() author's note: This *cannot* mean that we must add a | 
| 986 |  |  |  |  |  |  | # space to the address wherever CFWS appears. This would result in | 
| 987 |  |  |  |  |  |  | # any addr-spec that had CFWS outside a quoted string being invalid | 
| 988 |  |  |  |  |  |  | # for RFC 5321. | 
| 989 |  |  |  |  |  |  | #                        if ( ( $context == Email::IsEmail::COMPONENT_LOCALPART ) or | 
| 990 |  |  |  |  |  |  | #                             ( $context == Email::IsEmail::COMPONENT_DOMAIN ) ) { | 
| 991 |  |  |  |  |  |  | #                            $parsedata->{$context} .= Email::IsEmail::STRING_SP; | 
| 992 |  |  |  |  |  |  | #                            $atomlist->{$context}[$element_count] .= Email::IsEmail::STRING_SP; | 
| 993 |  |  |  |  |  |  | #                            $element_len++; | 
| 994 |  |  |  |  |  |  | #                        } | 
| 995 |  |  |  |  |  |  | } | 
| 996 |  |  |  |  |  |  | # Quoted pair | 
| 997 | 77 |  |  |  |  | 48 | when (Email::IsEmail::STRING_BACKSLASH) { | 
| 998 | 2 |  |  |  |  | 2 | push @{$context_stack}, $context; | 
|  | 2 |  |  |  |  | 3 |  | 
| 999 | 2 |  |  |  |  | 3 | $context = Email::IsEmail::CONTEXT_QUOTEDPAIR; | 
| 1000 |  |  |  |  |  |  | } | 
| 1001 |  |  |  |  |  |  | # Folding White Space | 
| 1002 |  |  |  |  |  |  | when ([ Email::IsEmail::STRING_CR, | 
| 1003 |  |  |  |  |  |  | Email::IsEmail::STRING_SP, | 
| 1004 | 75 |  |  |  |  | 102 | Email::IsEmail::STRING_HTAB ]) { | 
| 1005 | 0 | 0 | 0 |  |  | 0 | if ( ( $token eq Email::IsEmail::STRING_CR ) and | 
|  |  |  | 0 |  |  |  |  | 
| 1006 |  |  |  |  |  |  | ( ( ++$i == $raw_length ) or | 
| 1007 |  |  |  |  |  |  | ( substr( $email, $i, 1 ) ne Email::IsEmail::STRING_LF ) ) ) { | 
| 1008 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_CR_NO_LF; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1009 | 0 |  |  |  |  | 0 | break; | 
| 1010 |  |  |  |  |  |  | }  # Fatal error | 
| 1011 |  |  |  |  |  |  |  | 
| 1012 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::CFWS_FWS; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1013 |  |  |  |  |  |  |  | 
| 1014 | 0 |  |  |  |  | 0 | push @{$context_stack}, $context; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1015 | 0 |  |  |  |  | 0 | $context     = Email::IsEmail::CONTEXT_FWS; | 
| 1016 | 0 |  |  |  |  | 0 | $token_prior = $token; | 
| 1017 |  |  |  |  |  |  | } | 
| 1018 |  |  |  |  |  |  | # ctext | 
| 1019 | 75 |  |  |  |  | 63 | default { | 
| 1020 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.2.3 | 
| 1021 |  |  |  |  |  |  | #   ctext           =   %d33-39 /          ; Printable US-ASCII | 
| 1022 |  |  |  |  |  |  | #                       %d42-91 /          ;  characters not including | 
| 1023 |  |  |  |  |  |  | #                       %d93-126 /         ;  "(", ")", or "\" | 
| 1024 |  |  |  |  |  |  | #                       obs-ctext | 
| 1025 |  |  |  |  |  |  | # | 
| 1026 |  |  |  |  |  |  | #   obs-ctext       =   obs-NO-WS-CTL | 
| 1027 |  |  |  |  |  |  | # | 
| 1028 |  |  |  |  |  |  | #   obs-NO-WS-CTL   =   %d1-8 /            ; US-ASCII control | 
| 1029 |  |  |  |  |  |  | #                       %d11 /             ;  characters that do not | 
| 1030 |  |  |  |  |  |  | #                       %d12 /             ;  include the carriage | 
| 1031 |  |  |  |  |  |  | #                       %d14-31 /          ;  return, line feed, and | 
| 1032 |  |  |  |  |  |  | #                       %d127              ;  white space characters | 
| 1033 | 75 |  |  |  |  | 56 | my $ord = ord $token; | 
| 1034 |  |  |  |  |  |  |  | 
| 1035 | 75 | 50 | 33 |  |  | 509 | if ( ( $ord > 127 ) or ( $ord == 0 ) or ( $ord == 10 ) ) { | 
|  |  | 50 | 33 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 1036 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_EXPECTING_CTEXT;  # Fatal error | 
|  | 0 |  |  |  |  | 0 |  | 
| 1037 | 0 |  |  |  |  | 0 | break; | 
| 1038 |  |  |  |  |  |  | } | 
| 1039 |  |  |  |  |  |  | elsif ( ( $ord < 32 ) or ( $ord == 127 ) ) { | 
| 1040 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::DEPREC_CTEXT; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1041 |  |  |  |  |  |  | } | 
| 1042 |  |  |  |  |  |  | } | 
| 1043 |  |  |  |  |  |  | } | 
| 1044 |  |  |  |  |  |  | } | 
| 1045 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 1046 |  |  |  |  |  |  | # Folding White Space | 
| 1047 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 1048 | 7 |  |  |  |  | 8 | when (Email::IsEmail::CONTEXT_FWS) { | 
| 1049 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.2.2 | 
| 1050 |  |  |  |  |  |  | #   FWS             =   ([*WSP CRLF] 1*WSP) /  obs-FWS | 
| 1051 |  |  |  |  |  |  | #                                          ; Folding white space | 
| 1052 |  |  |  |  |  |  |  | 
| 1053 |  |  |  |  |  |  | # But note the erratum: | 
| 1054 |  |  |  |  |  |  | # http://www.rfc-editor.org/errata_search.php?rfc=5322&eid=1908: | 
| 1055 |  |  |  |  |  |  | #   In the obsolete syntax, any amount of folding white space MAY be | 
| 1056 |  |  |  |  |  |  | #   inserted where the obs-FWS rule is allowed.  This creates the | 
| 1057 |  |  |  |  |  |  | #   possibility of having two consecutive "folds" in a line, and | 
| 1058 |  |  |  |  |  |  | #   therefore the possibility that a line which makes up a folded header | 
| 1059 |  |  |  |  |  |  | #   field could be composed entirely of white space. | 
| 1060 |  |  |  |  |  |  | # | 
| 1061 |  |  |  |  |  |  | #   obs-FWS         =   1*([CRLF] WSP) | 
| 1062 | 7 | 50 |  |  |  | 12 | if ( $token_prior eq Email::IsEmail::STRING_CR ) { | 
| 1063 | 0 | 0 |  |  |  | 0 | if ( $token eq Email::IsEmail::STRING_CR ) { | 
| 1064 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_FWS_CRLF_X2;  # Fatal error | 
|  | 0 |  |  |  |  | 0 |  | 
| 1065 | 0 |  |  |  |  | 0 | break; | 
| 1066 |  |  |  |  |  |  | } | 
| 1067 |  |  |  |  |  |  |  | 
| 1068 | 0 | 0 |  |  |  | 0 | if ( ++$crlf_count > 1 ) { | 
| 1069 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::DEPREC_FWS;  # Multiple folds = obsolete FWS | 
|  | 0 |  |  |  |  | 0 |  | 
| 1070 |  |  |  |  |  |  | } | 
| 1071 |  |  |  |  |  |  | } | 
| 1072 |  |  |  |  |  |  |  | 
| 1073 | 7 |  |  |  |  | 6 | given($token) { | 
| 1074 | 7 |  |  |  |  | 7 | when (Email::IsEmail::STRING_CR) { | 
| 1075 | 0 | 0 | 0 |  |  | 0 | if ( ( ++$i == $raw_length ) or | 
| 1076 |  |  |  |  |  |  | ( substr( $email, $i, 1 ) ne Email::IsEmail::STRING_LF ) ) { | 
| 1077 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_CR_NO_LF;  # Fatal error | 
|  | 0 |  |  |  |  | 0 |  | 
| 1078 |  |  |  |  |  |  | } | 
| 1079 |  |  |  |  |  |  | } | 
| 1080 |  |  |  |  |  |  | when ([ Email::IsEmail::STRING_SP, | 
| 1081 | 7 |  |  |  |  | 12 | Email::IsEmail::STRING_HTAB, ]) { | 
| 1082 |  |  |  |  |  |  | } | 
| 1083 | 7 |  |  |  |  | 5 | default { | 
| 1084 | 7 | 50 |  |  |  | 11 | if ( $token_prior eq Email::IsEmail::STRING_CR ) { | 
| 1085 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_FWS_CRLF_END;  # Fatal error | 
|  | 0 |  |  |  |  | 0 |  | 
| 1086 | 0 |  |  |  |  | 0 | break; | 
| 1087 |  |  |  |  |  |  | } | 
| 1088 |  |  |  |  |  |  |  | 
| 1089 | 7 |  |  |  |  | 3 | $crlf_count    = 0; | 
| 1090 | 7 |  |  |  |  | 8 | $context_prior = $context; | 
| 1091 | 7 |  |  |  |  | 6 | $context       = pop @{$context_stack};  # End of FWS | 
|  | 7 |  |  |  |  | 7 |  | 
| 1092 |  |  |  |  |  |  |  | 
| 1093 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5322#section-3.2.2 | 
| 1094 |  |  |  |  |  |  | #   Runs of FWS, comment, or CFWS that occur between lexical tokens in a | 
| 1095 |  |  |  |  |  |  | #   structured header field are semantically interpreted as a single | 
| 1096 |  |  |  |  |  |  | #   space character. | 
| 1097 |  |  |  |  |  |  | # | 
| 1098 |  |  |  |  |  |  | # Email::IsEmail() author's note: This *cannot* mean that we must add a | 
| 1099 |  |  |  |  |  |  | # space to the address wherever CFWS appears. This would result in | 
| 1100 |  |  |  |  |  |  | # any addr-spec that had CFWS outside a quoted string being invalid | 
| 1101 |  |  |  |  |  |  | # for RFC 5321. | 
| 1102 |  |  |  |  |  |  | #                        if ( ( $context == Email::IsEmail::COMPONENT_LOCALPART ) or | 
| 1103 |  |  |  |  |  |  | #                             ( $context == Email::IsEmail::COMPONENT_DOMAIN ) ) { | 
| 1104 |  |  |  |  |  |  | #                            $parsedata->{$context} .= Email::IsEmail::STRING_SP; | 
| 1105 |  |  |  |  |  |  | #                            $atomlist->{$context}[$element_count] .= Email::IsEmail::STRING_SP; | 
| 1106 |  |  |  |  |  |  | #                            $element_len++; | 
| 1107 |  |  |  |  |  |  | #                        } | 
| 1108 | 7 |  |  |  |  | 9 | $i--;  # Look at this token again in the parent context | 
| 1109 |  |  |  |  |  |  | } | 
| 1110 |  |  |  |  |  |  | } | 
| 1111 |  |  |  |  |  |  |  | 
| 1112 | 7 |  |  |  |  | 8 | $token_prior = $token; | 
| 1113 |  |  |  |  |  |  | } | 
| 1114 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 1115 |  |  |  |  |  |  | # A context we aren't expecting | 
| 1116 |  |  |  |  |  |  | #------------------------------------------------------------- | 
| 1117 |  |  |  |  |  |  | default: { | 
| 1118 | 0 |  |  |  |  | 0 | die("Unknown context: $context"); | 
|  | 0 |  |  |  |  | 0 |  | 
| 1119 |  |  |  |  |  |  | } | 
| 1120 |  |  |  |  |  |  | } | 
| 1121 |  |  |  |  |  |  |  | 
| 1122 | 2119 | 100 |  |  |  | 2166 | if ( Email::IsEmail::_max($return_status) > Email::IsEmail::RFC5322 ) { | 
| 1123 | 17 |  |  |  |  | 22 | last;  # No point going on if we've got a fatal error | 
| 1124 |  |  |  |  |  |  | } | 
| 1125 |  |  |  |  |  |  | } | 
| 1126 |  |  |  |  |  |  |  | 
| 1127 |  |  |  |  |  |  | # Some simple final tests | 
| 1128 | 80 | 100 |  |  |  | 93 | if ( Email::IsEmail::_max($return_status) < Email::IsEmail::RFC5322 ) { | 
| 1129 | 63 | 100 |  |  |  | 408 | if ( $context == Email::IsEmail::CONTEXT_QUOTEDSTRING ) { | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
|  |  | 100 |  |  |  |  |  | 
|  |  | 50 |  |  |  |  |  | 
| 1130 | 2 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::ERR_UNCLOSEDQUOTEDSTR;  # Fatal error | 
|  | 2 |  |  |  |  | 3 |  | 
| 1131 |  |  |  |  |  |  | } | 
| 1132 |  |  |  |  |  |  | elsif ( $context == Email::IsEmail::CONTEXT_QUOTEDPAIR ) { | 
| 1133 | 1 |  |  |  |  | 1 | push @{$return_status}, Email::IsEmail::ERR_BACKSLASHEND;  # Fatal error | 
|  | 1 |  |  |  |  | 2 |  | 
| 1134 |  |  |  |  |  |  | } | 
| 1135 |  |  |  |  |  |  | elsif ( $context == Email::IsEmail::CONTEXT_COMMENT ) { | 
| 1136 | 2 |  |  |  |  | 3 | push @{$return_status}, Email::IsEmail::ERR_UNCLOSEDCOMMENT;  # Fatal error | 
|  | 2 |  |  |  |  | 2 |  | 
| 1137 |  |  |  |  |  |  | } | 
| 1138 |  |  |  |  |  |  | elsif ( $context == Email::IsEmail::COMPONENT_LITERAL ) { | 
| 1139 | 2 |  |  |  |  | 3 | push @{$return_status}, Email::IsEmail::ERR_UNCLOSEDDOMLIT;  # Fatal error | 
|  | 2 |  |  |  |  | 3 |  | 
| 1140 |  |  |  |  |  |  | } | 
| 1141 |  |  |  |  |  |  | elsif ( $token eq Email::IsEmail::STRING_CR ) { | 
| 1142 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::ERR_FWS_CRLF_END;  # Fatal error | 
|  | 0 |  |  |  |  | 0 |  | 
| 1143 |  |  |  |  |  |  | } | 
| 1144 |  |  |  |  |  |  | elsif ( $parsedata->{Email::IsEmail::COMPONENT_DOMAIN} eq '' ) { | 
| 1145 | 2 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::ERR_NODOMAIN;  # Fatal error | 
|  | 2 |  |  |  |  | 7 |  | 
| 1146 |  |  |  |  |  |  | } | 
| 1147 |  |  |  |  |  |  | elsif ( $element_len == 0 ) { | 
| 1148 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::ERR_DOT_END;  # Fatal error | 
|  | 1 |  |  |  |  | 3 |  | 
| 1149 |  |  |  |  |  |  | } | 
| 1150 |  |  |  |  |  |  | elsif ( $hyphen_flag ) { | 
| 1151 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::ERR_DOMAINHYPHENEND;  # Fatal error | 
|  | 1 |  |  |  |  | 3 |  | 
| 1152 |  |  |  |  |  |  | } | 
| 1153 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5321#section-4.5.3.1.2 | 
| 1154 |  |  |  |  |  |  | #   The maximum total length of a domain name or number is 255 octets. | 
| 1155 |  |  |  |  |  |  | elsif ( length($parsedata->{Email::IsEmail::COMPONENT_DOMAIN}) > 255 ) { | 
| 1156 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::RFC5322_DOMAIN_TOOLONG; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1157 |  |  |  |  |  |  | } | 
| 1158 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5321#section-4.1.2 | 
| 1159 |  |  |  |  |  |  | #   Forward-path   = Path | 
| 1160 |  |  |  |  |  |  | # | 
| 1161 |  |  |  |  |  |  | #   Path           = "<" [ A-d-l ":" ] Mailbox ">" | 
| 1162 |  |  |  |  |  |  | # | 
| 1163 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5321#section-4.5.3.1.3 | 
| 1164 |  |  |  |  |  |  | #   The maximum total length of a reverse-path or forward-path is 256 | 
| 1165 |  |  |  |  |  |  | #   octets (including the punctuation and element separators). | 
| 1166 |  |  |  |  |  |  | # | 
| 1167 |  |  |  |  |  |  | # Thus, even without (obsolete) routing information, the Mailbox can | 
| 1168 |  |  |  |  |  |  | # only be 254 characters long. This is confirmed by this verified | 
| 1169 |  |  |  |  |  |  | # erratum to RFC 3696: | 
| 1170 |  |  |  |  |  |  | # | 
| 1171 |  |  |  |  |  |  | # http://www.rfc-editor.org/errata_search.php?rfc=3696&eid=1690 | 
| 1172 |  |  |  |  |  |  | #   However, there is a restriction in RFC 2821 on the length of an | 
| 1173 |  |  |  |  |  |  | #   address in MAIL and RCPT commands of 254 characters.  Since addresses | 
| 1174 |  |  |  |  |  |  | #   that do not fit in those fields are not normally useful, the upper | 
| 1175 |  |  |  |  |  |  | #   limit on address lengths should normally be considered to be 254. | 
| 1176 |  |  |  |  |  |  | elsif ( length( $parsedata->{Email::IsEmail::COMPONENT_LOCALPART} . | 
| 1177 |  |  |  |  |  |  | Email::IsEmail::STRING_AT . | 
| 1178 |  |  |  |  |  |  | $parsedata->{Email::IsEmail::COMPONENT_DOMAIN} ) > 254 ) { | 
| 1179 | 1 |  |  |  |  | 2 | push @{$return_status}, Email::IsEmail::RFC5322_TOOLONG; | 
|  | 1 |  |  |  |  | 2 |  | 
| 1180 |  |  |  |  |  |  | } | 
| 1181 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc1035#section-2.3.4 | 
| 1182 |  |  |  |  |  |  | # labels          63 octets or less | 
| 1183 |  |  |  |  |  |  | elsif ( $element_len > 63 ) { | 
| 1184 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::RFC5322_LABEL_TOOLONG; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1185 |  |  |  |  |  |  | } | 
| 1186 |  |  |  |  |  |  | } | 
| 1187 |  |  |  |  |  |  |  | 
| 1188 |  |  |  |  |  |  | # Check DNS? | 
| 1189 | 80 |  |  |  |  | 66 | my $dns_checked = 0; | 
| 1190 |  |  |  |  |  |  |  | 
| 1191 | 80 | 50 | 33 |  |  | 159 | if ( $checkDNS and ( Email::IsEmail::_max($return_status) < Email::IsEmail::DNSWARN ) ) { | 
| 1192 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5321#section-2.3.5 | 
| 1193 |  |  |  |  |  |  | #   Names that can | 
| 1194 |  |  |  |  |  |  | #   be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed | 
| 1195 |  |  |  |  |  |  | #   in Section 5) are permitted, as are CNAME RRs whose targets can be | 
| 1196 |  |  |  |  |  |  | #   resolved, in turn, to MX or address RRs. | 
| 1197 |  |  |  |  |  |  | # | 
| 1198 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5321#section-5.1 | 
| 1199 |  |  |  |  |  |  | #   The lookup first attempts to locate an MX record associated with the | 
| 1200 |  |  |  |  |  |  | #   name.  If a CNAME record is found, the resulting name is processed as | 
| 1201 |  |  |  |  |  |  | #   if it were the initial name. ... If an empty list of MXs is returned, | 
| 1202 |  |  |  |  |  |  | #   the address is treated as if it was associated with an implicit MX | 
| 1203 |  |  |  |  |  |  | #   RR, with a preference of 0, pointing to that host. | 
| 1204 |  |  |  |  |  |  | # | 
| 1205 |  |  |  |  |  |  | # Email::IsEmail() author's note: We will regard the existence of a CNAME to be | 
| 1206 |  |  |  |  |  |  | # sufficient evidence of the domain's existence. For performance reasons | 
| 1207 |  |  |  |  |  |  | # we will not repeat the DNS lookup for the CNAME's target, but we will | 
| 1208 |  |  |  |  |  |  | # raise a warning because we didn't immediately find an MX record. | 
| 1209 | 0 |  |  |  |  | 0 | eval { require Net::DNS } | 
| 1210 | 0 | 0 |  |  |  | 0 | unless $INC{'Net/DNS.pm'}; | 
| 1211 | 0 | 0 |  |  |  | 0 | if ( $INC{'Net/DNS.pm'} ) { | 
| 1212 | 0 |  |  |  |  | 0 | my $domain = $parsedata->{Email::IsEmail::COMPONENT_DOMAIN}; | 
| 1213 | 0 | 0 |  |  |  | 0 | if ( $element_count == 0 ) { | 
| 1214 | 0 |  |  |  |  | 0 | $domain .= '.';  # Checking TLD DNS seems to work only if you explicitly check from the root | 
| 1215 |  |  |  |  |  |  | } | 
| 1216 |  |  |  |  |  |  |  | 
| 1217 | 0 |  |  |  |  | 0 | my @domains = Net::DNS::rr( $domain, 'MX' ); | 
| 1218 |  |  |  |  |  |  |  | 
| 1219 | 0 | 0 |  |  |  | 0 | if ( scalar @domains == 0 ) { | 
| 1220 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::DNSWARN_NO_MX_RECORD;  # MX-record for domain can't be found | 
|  | 0 |  |  |  |  | 0 |  | 
| 1221 |  |  |  |  |  |  |  | 
| 1222 |  |  |  |  |  |  | # TODO: check also AAAA and CNAME | 
| 1223 | 0 |  |  |  |  | 0 | @domains = Net::DNS::rr( $domain, 'A' ); | 
| 1224 |  |  |  |  |  |  |  | 
| 1225 | 0 | 0 |  |  |  | 0 | if ( scalar @domains == 0 ) { | 
| 1226 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::DNSWARN_NO_RECORD;  # No usable records for the domain can be found | 
|  | 0 |  |  |  |  | 0 |  | 
| 1227 |  |  |  |  |  |  | } | 
| 1228 |  |  |  |  |  |  | } | 
| 1229 |  |  |  |  |  |  | } | 
| 1230 |  |  |  |  |  |  | } | 
| 1231 |  |  |  |  |  |  |  | 
| 1232 |  |  |  |  |  |  | # Check for TLD addresses | 
| 1233 |  |  |  |  |  |  | # ----------------------- | 
| 1234 |  |  |  |  |  |  | # TLD addresses are specifically allowed in RFC 5321 but they are | 
| 1235 |  |  |  |  |  |  | # unusual to say the least. We will allocate a separate | 
| 1236 |  |  |  |  |  |  | # status to these addresses on the basis that they are more likely | 
| 1237 |  |  |  |  |  |  | # to be typos than genuine addresses (unless we've already | 
| 1238 |  |  |  |  |  |  | # established that the domain does have an MX record) | 
| 1239 |  |  |  |  |  |  | # | 
| 1240 |  |  |  |  |  |  | # http://tools.ietf.org/html/rfc5321#section-2.3.5 | 
| 1241 |  |  |  |  |  |  | #   In the case | 
| 1242 |  |  |  |  |  |  | #   of a top-level domain used by itself in an email address, a single | 
| 1243 |  |  |  |  |  |  | #   string is used without any dots.  This makes the requirement, | 
| 1244 |  |  |  |  |  |  | #   described in more detail below, that only fully-qualified domain | 
| 1245 |  |  |  |  |  |  | #   names appear in SMTP transactions on the public Internet, | 
| 1246 |  |  |  |  |  |  | #   particularly important where top-level domains are involved. | 
| 1247 |  |  |  |  |  |  | # | 
| 1248 |  |  |  |  |  |  | # TLD format | 
| 1249 |  |  |  |  |  |  | # ---------- | 
| 1250 |  |  |  |  |  |  | # The format of TLDs has changed a number of times. The standards | 
| 1251 |  |  |  |  |  |  | # used by IANA have been largely ignored by ICANN, leading to | 
| 1252 |  |  |  |  |  |  | # confusion over the standards being followed. These are not defined | 
| 1253 |  |  |  |  |  |  | # anywhere, except as a general component of a DNS host name (a label). | 
| 1254 |  |  |  |  |  |  | # However, this could potentially lead to 123.123.123.123 being a | 
| 1255 |  |  |  |  |  |  | # valid DNS name (rather than an IP address) and thereby creating | 
| 1256 |  |  |  |  |  |  | # an ambiguity. The most authoritative statement on TLD formats that | 
| 1257 |  |  |  |  |  |  | # the author can find is in a (rejected!) erratum to RFC 1123 | 
| 1258 |  |  |  |  |  |  | # submitted by John Klensin, the author of RFC 5321: | 
| 1259 |  |  |  |  |  |  | # | 
| 1260 |  |  |  |  |  |  | # http://www.rfc-editor.org/errata_search.php?rfc=1123&eid=1353 | 
| 1261 |  |  |  |  |  |  | #   However, a valid host name can never have the dotted-decimal | 
| 1262 |  |  |  |  |  |  | #   form #.#.#.#, since this change does not permit the highest-level | 
| 1263 |  |  |  |  |  |  | #   component label to start with a digit even if it is not all-numeric. | 
| 1264 | 80 | 100 | 66 |  |  | 162 | if ( !$dns_checked and ( Email::IsEmail::_max($return_status) < Email::IsEmail::DNSWARN ) ) { | 
| 1265 | 17 | 100 |  |  |  | 23 | if ( $element_count == 0 ) { | 
| 1266 | 4 |  |  |  |  | 5 | push @{$return_status}, Email::IsEmail::RFC5321_TLD; | 
|  | 4 |  |  |  |  | 6 |  | 
| 1267 |  |  |  |  |  |  | } | 
| 1268 |  |  |  |  |  |  |  | 
| 1269 | 17 | 50 |  |  |  | 75 | if (looks_like_number(substr( $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count], 0x0, 1 ))) { | 
| 1270 | 0 |  |  |  |  | 0 | push @{$return_status}, Email::IsEmail::RFC5321_TLDNUMERIC; | 
|  | 0 |  |  |  |  | 0 |  | 
| 1271 |  |  |  |  |  |  | } | 
| 1272 |  |  |  |  |  |  | } | 
| 1273 |  |  |  |  |  |  |  | 
| 1274 | 80 |  |  |  |  | 119 | $return_status   = Email::IsEmail::_unique($return_status); | 
| 1275 | 80 |  |  |  |  | 119 | my $final_status = Email::IsEmail::_max($return_status); | 
| 1276 |  |  |  |  |  |  |  | 
| 1277 | 80 | 100 |  |  |  | 63 | if ( scalar @{$return_status} != 1 ) { | 
|  | 80 |  |  |  |  | 126 |  | 
| 1278 | 67 |  |  |  |  | 46 | shift @{$return_status};  # remove redundant Email::IsEmail::VALID | 
|  | 67 |  |  |  |  | 69 |  | 
| 1279 |  |  |  |  |  |  | } | 
| 1280 |  |  |  |  |  |  |  | 
| 1281 | 80 |  |  |  |  | 97 | $parsedata->{'status'} = $return_status; | 
| 1282 |  |  |  |  |  |  |  | 
| 1283 | 80 | 50 |  |  |  | 106 | if ( $final_status < $threshold ) { | 
| 1284 | 0 |  |  |  |  | 0 | $final_status = Email::IsEmail::VALID; | 
| 1285 |  |  |  |  |  |  | } | 
| 1286 |  |  |  |  |  |  |  | 
| 1287 | 80 | 100 |  |  |  | 491 | return ($diagnose) ? $final_status : ( $final_status < Email::IsEmail::THRESHOLD ); | 
| 1288 |  |  |  |  |  |  | } | 
| 1289 |  |  |  |  |  |  |  | 
| 1290 |  |  |  |  |  |  | sub _max { | 
| 1291 | 2372 |  |  | 2372 |  | 1712 | my ( $array_ref ) = @_; | 
| 1292 |  |  |  |  |  |  |  | 
| 1293 | 2372 |  |  |  |  | 1573 | my $res = VALID; | 
| 1294 |  |  |  |  |  |  |  | 
| 1295 | 2372 |  |  |  |  | 1472 | foreach my $val ( @{$array_ref} ) { | 
|  | 2372 |  |  |  |  | 2794 |  | 
| 1296 | 3330 | 100 |  |  |  | 4533 | if ( $val > $res ) { | 
| 1297 | 912 |  |  |  |  | 892 | $res = $val; | 
| 1298 |  |  |  |  |  |  | } | 
| 1299 |  |  |  |  |  |  | } | 
| 1300 |  |  |  |  |  |  |  | 
| 1301 | 2372 |  |  |  |  | 5398 | return $res; | 
| 1302 |  |  |  |  |  |  | } | 
| 1303 |  |  |  |  |  |  |  | 
| 1304 |  |  |  |  |  |  |  | 
| 1305 |  |  |  |  |  |  | sub _unique { | 
| 1306 | 80 |  |  | 80 |  | 65 | my ( $array_ref ) = @_; | 
| 1307 |  |  |  |  |  |  |  | 
| 1308 | 80 |  |  |  |  | 64 | my %seen; | 
| 1309 |  |  |  |  |  |  |  | 
| 1310 | 80 |  |  |  |  | 60 | return [ grep !$seen{$_}++, @{$array_ref} ]; | 
|  | 80 |  |  |  |  | 398 |  | 
| 1311 |  |  |  |  |  |  | } | 
| 1312 |  |  |  |  |  |  |  | 
| 1313 |  |  |  |  |  |  |  | 
| 1314 |  |  |  |  |  |  | =head1 AUTHOR | 
| 1315 |  |  |  |  |  |  |  | 
| 1316 |  |  |  |  |  |  | Original PHP version Dominic Sayers C<< <dominic@sayers.cc> >> | 
| 1317 |  |  |  |  |  |  |  | 
| 1318 |  |  |  |  |  |  | Perl port Leandr Khaliullov, C<< <leandr at cpan.org> >> | 
| 1319 |  |  |  |  |  |  |  | 
| 1320 |  |  |  |  |  |  | =encoding utf8 | 
| 1321 |  |  |  |  |  |  |  | 
| 1322 |  |  |  |  |  |  | =head1 COPYRIGHT | 
| 1323 |  |  |  |  |  |  |  | 
| 1324 |  |  |  |  |  |  | Copyright © 2008-2011, Dominic Sayers. | 
| 1325 |  |  |  |  |  |  |  | 
| 1326 |  |  |  |  |  |  | Copyright 2016 Leandr Khaliullov. | 
| 1327 |  |  |  |  |  |  |  | 
| 1328 |  |  |  |  |  |  |  | 
| 1329 |  |  |  |  |  |  | All rights reserved. | 
| 1330 |  |  |  |  |  |  |  | 
| 1331 |  |  |  |  |  |  | =head1 BUGS | 
| 1332 |  |  |  |  |  |  |  | 
| 1333 |  |  |  |  |  |  | Please report any bugs or feature requests to C<bug-email-isemail at rt.cpan.org>, or through | 
| 1334 |  |  |  |  |  |  | the web interface at L<http://rt.cpan.org/NoAuth/ReportBug.html?Queue=Email-IsEmail>.  I will be notified, and then you'll | 
| 1335 |  |  |  |  |  |  | automatically be notified of progress on your bug as I make changes. | 
| 1336 |  |  |  |  |  |  |  | 
| 1337 |  |  |  |  |  |  |  | 
| 1338 |  |  |  |  |  |  | =head1 SUPPORT | 
| 1339 |  |  |  |  |  |  |  | 
| 1340 |  |  |  |  |  |  | You can find documentation for this module with the perldoc command. | 
| 1341 |  |  |  |  |  |  |  | 
| 1342 |  |  |  |  |  |  | perldoc Email::IsEmail | 
| 1343 |  |  |  |  |  |  |  | 
| 1344 |  |  |  |  |  |  |  | 
| 1345 |  |  |  |  |  |  | You can also look for information at: | 
| 1346 |  |  |  |  |  |  |  | 
| 1347 |  |  |  |  |  |  | =over 4 | 
| 1348 |  |  |  |  |  |  |  | 
| 1349 |  |  |  |  |  |  | =item * RT: CPAN's request tracker (report bugs here) | 
| 1350 |  |  |  |  |  |  |  | 
| 1351 |  |  |  |  |  |  | L<http://rt.cpan.org/NoAuth/Bugs.html?Dist=Email-IsEmail> | 
| 1352 |  |  |  |  |  |  |  | 
| 1353 |  |  |  |  |  |  | =item * AnnoCPAN: Annotated CPAN documentation | 
| 1354 |  |  |  |  |  |  |  | 
| 1355 |  |  |  |  |  |  | L<http://annocpan.org/dist/Email-IsEmail> | 
| 1356 |  |  |  |  |  |  |  | 
| 1357 |  |  |  |  |  |  | =item * CPAN Ratings | 
| 1358 |  |  |  |  |  |  |  | 
| 1359 |  |  |  |  |  |  | L<http://cpanratings.perl.org/d/Email-IsEmail> | 
| 1360 |  |  |  |  |  |  |  | 
| 1361 |  |  |  |  |  |  | =item * Search CPAN | 
| 1362 |  |  |  |  |  |  |  | 
| 1363 |  |  |  |  |  |  | L<http://search.cpan.org/dist/Email-IsEmail/> | 
| 1364 |  |  |  |  |  |  |  | 
| 1365 |  |  |  |  |  |  | =back | 
| 1366 |  |  |  |  |  |  |  | 
| 1367 |  |  |  |  |  |  |  | 
| 1368 |  |  |  |  |  |  | =head1 ACKNOWLEDGEMENTS | 
| 1369 |  |  |  |  |  |  |  | 
| 1370 |  |  |  |  |  |  | - Dominic Sayers (original PHP version of is_email) | 
| 1371 |  |  |  |  |  |  | - Daniel Marschall (test schemas) | 
| 1372 |  |  |  |  |  |  | - Umberto Salsi (PHPLint) | 
| 1373 |  |  |  |  |  |  |  | 
| 1374 |  |  |  |  |  |  | =head1 LICENSE | 
| 1375 |  |  |  |  |  |  |  | 
| 1376 |  |  |  |  |  |  | This program is released under the following license: BSD | 
| 1377 |  |  |  |  |  |  |  | 
| 1378 |  |  |  |  |  |  | See F<http://www.opensource.org/licenses/bsd-license.php> | 
| 1379 |  |  |  |  |  |  |  | 
| 1380 |  |  |  |  |  |  | Redistribution and use in source and binary forms, with or without modification, | 
| 1381 |  |  |  |  |  |  | are permitted provided that the following conditions are met: | 
| 1382 |  |  |  |  |  |  |  | 
| 1383 |  |  |  |  |  |  | - Redistributions of source code must retain the above copyright notice, | 
| 1384 |  |  |  |  |  |  | this list of conditions and the following disclaimer. | 
| 1385 |  |  |  |  |  |  | - Redistributions in binary form must reproduce the above copyright notice, | 
| 1386 |  |  |  |  |  |  | this list of conditions and the following disclaimer in the documentation | 
| 1387 |  |  |  |  |  |  | and/or other materials provided with the distribution. | 
| 1388 |  |  |  |  |  |  | - Neither the name of Dominic Sayers nor the names of its contributors may be | 
| 1389 |  |  |  |  |  |  | used to endorse or promote products derived from this software without | 
| 1390 |  |  |  |  |  |  | specific prior written permission. | 
| 1391 |  |  |  |  |  |  |  | 
| 1392 |  |  |  |  |  |  | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND | 
| 1393 |  |  |  |  |  |  | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED | 
| 1394 |  |  |  |  |  |  | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE | 
| 1395 |  |  |  |  |  |  | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR | 
| 1396 |  |  |  |  |  |  | ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES | 
| 1397 |  |  |  |  |  |  | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; | 
| 1398 |  |  |  |  |  |  | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON | 
| 1399 |  |  |  |  |  |  | ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT | 
| 1400 |  |  |  |  |  |  | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS | 
| 1401 |  |  |  |  |  |  | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | 
| 1402 |  |  |  |  |  |  |  | 
| 1403 |  |  |  |  |  |  | =cut | 
| 1404 |  |  |  |  |  |  |  | 
| 1405 |  |  |  |  |  |  | 1; # End of Email::IsEmail |