File Coverage

blib/lib/Email/IsEmail.pm
Criterion Covered Total %
statement 631 736 85.7
branch 134 180 74.4
condition 39 108 36.1
subroutine 92 92 100.0
pod 1 1 100.0
total 897 1117 80.3


line stmt bran cond sub pod time code
1             package Email::IsEmail;
2              
3 3     3   137511 use v5.10;
  3         7  
4 3     3   10 use strict qw(subs vars);
  3         4  
  3         100  
5             *{'Email::IsEmail'} = \&IsEmail; # add short alias Email::IsEmail
6 3     3   9 use strict 'refs';
  3         6  
  3         75  
7 3     3   11 use warnings;
  3         4  
  3         67  
8              
9 3     3   9 use Scalar::Util qw(looks_like_number);
  3         3  
  3         286  
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.8
24              
25             =cut
26              
27             $VERSION = '3.04.8';
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         198  
59 3     3   10 use constant DNSWARN => 7;
  3         4  
  3         98  
60 3     3   9 use constant RFC5321 => 15;
  3         3  
  3         96  
61 3     3   8 use constant CFWS => 31;
  3         3  
  3         100  
62 3     3   20 use constant DEPREC => 63;
  3         3  
  3         95  
63 3     3   7 use constant RFC5322 => 127;
  3         3  
  3         107  
64 3     3   10 use constant ERR => 255;
  3         3  
  3         95  
65              
66             # Diagnoses
67             # Address is valid
68 3     3   11 use constant VALID => 0;
  3         8  
  3         100  
69             # Address is valid but a DNS check was not successful
70 3     3   17 use constant DNSWARN_NO_MX_RECORD => 5;
  3         4  
  3         115  
71 3     3   9 use constant DNSWARN_NO_RECORD => 6;
  3         2  
  3         91  
72             # Address is valid for SMTP but has unusual elements
73 3     3   9 use constant RFC5321_TLD => 9;
  3         3  
  3         88  
74 3     3   9 use constant RFC5321_TLDNUMERIC => 10;
  3         3  
  3         87  
75 3     3   8 use constant RFC5321_QUOTEDSTRING => 11;
  3         6  
  3         88  
76 3     3   8 use constant RFC5321_ADDRESSLITERAL => 12;
  3         3  
  3         147  
77 3     3   10 use constant RFC5321_IPV6DEPRECATED => 13;
  3         2  
  3         100  
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         9  
  3         95  
80 3     3   8 use constant CFWS_FWS => 18;
  3         2  
  3         92  
81             # Address contains deprecated elements but may still be valid in restricted contexts
82 3     3   7 use constant DEPREC_LOCALPART => 33;
  3         2  
  3         90  
83 3     3   9 use constant DEPREC_FWS => 34;
  3         2  
  3         85  
84 3     3   8 use constant DEPREC_QTEXT => 35;
  3         3  
  3         92  
85 3     3   9 use constant DEPREC_QP => 36;
  3         2  
  3         99  
86 3     3   8 use constant DEPREC_COMMENT => 37;
  3         3  
  3         93  
87 3     3   11 use constant DEPREC_CTEXT => 38;
  3         3  
  3         124  
88 3     3   9 use constant DEPREC_CFWS_NEAR_AT => 49;
  3         7  
  3         106  
89             # The address is only valid according to the broad definition of RFC 5322. It is otherwise invalid.
90 3     3   8 use constant RFC5322_DOMAIN => 65;
  3         11  
  3         119  
91 3     3   9 use constant RFC5322_TOOLONG => 66;
  3         23  
  3         91  
92 3     3   8 use constant RFC5322_LOCAL_TOOLONG => 67;
  3         6  
  3         94  
93 3     3   9 use constant RFC5322_DOMAIN_TOOLONG => 68;
  3         3  
  3         94  
94 3     3   8 use constant RFC5322_LABEL_TOOLONG => 69;
  3         3  
  3         87  
95 3     3   9 use constant RFC5322_DOMAINLITERAL => 70;
  3         14  
  3         90  
96 3     3   10 use constant RFC5322_DOMLIT_OBSDTEXT => 71;
  3         2  
  3         87  
97 3     3   9 use constant RFC5322_IPV6_GRPCOUNT => 72;
  3         2  
  3         100  
98 3     3   9 use constant RFC5322_IPV6_2X2XCOLON => 73;
  3         3  
  3         99  
99 3     3   8 use constant RFC5322_IPV6_BADCHAR => 74;
  3         3  
  3         101  
100 3     3   9 use constant RFC5322_IPV6_MAXGRPS => 75;
  3         3  
  3         98  
101 3     3   11 use constant RFC5322_IPV6_COLONSTRT => 76;
  3         3  
  3         94  
102 3     3   10 use constant RFC5322_IPV6_COLONEND => 77;
  3         2  
  3         97  
103             # Address is invalid for any purpose
104 3     3   9 use constant ERR_EXPECTING_DTEXT => 129;
  3         4  
  3         84  
105 3     3   9 use constant ERR_NOLOCALPART => 130;
  3         3  
  3         90  
106 3     3   8 use constant ERR_NODOMAIN => 131;
  3         2  
  3         99  
107 3     3   9 use constant ERR_CONSECUTIVEDOTS => 132;
  3         3  
  3         95  
108 3     3   10 use constant ERR_ATEXT_AFTER_CFWS => 133;
  3         2  
  3         87  
109 3     3   7 use constant ERR_ATEXT_AFTER_QS => 134;
  3         3  
  3         85  
110 3     3   8 use constant ERR_ATEXT_AFTER_DOMLIT => 135;
  3         10  
  3         128  
111 3     3   9 use constant ERR_EXPECTING_QPAIR => 136;
  3         2  
  3         99  
112 3     3   9 use constant ERR_EXPECTING_ATEXT => 137;
  3         8  
  3         90  
113 3     3   8 use constant ERR_EXPECTING_QTEXT => 138;
  3         3  
  3         92  
114 3     3   8 use constant ERR_EXPECTING_CTEXT => 139;
  3         3  
  3         105  
115 3     3   9 use constant ERR_BACKSLASHEND => 140;
  3         1  
  3         111  
116 3     3   10 use constant ERR_DOT_START => 141;
  3         2  
  3         95  
117 3     3   9 use constant ERR_DOT_END => 142;
  3         6  
  3         83  
118 3     3   8 use constant ERR_DOMAINHYPHENSTART => 143;
  3         3  
  3         177  
119 3     3   10 use constant ERR_DOMAINHYPHENEND => 144;
  3         2  
  3         118  
120 3     3   9 use constant ERR_UNCLOSEDQUOTEDSTR => 145;
  3         3  
  3         91  
121 3     3   7 use constant ERR_UNCLOSEDCOMMENT => 146;
  3         3  
  3         92  
122 3     3   9 use constant ERR_UNCLOSEDDOMLIT => 147;
  3         3  
  3         92  
123 3     3   8 use constant ERR_FWS_CRLF_X2 => 148;
  3         3  
  3         104  
124 3     3   9 use constant ERR_FWS_CRLF_END => 149;
  3         3  
  3         100  
125 3     3   10 use constant ERR_CR_NO_LF => 150;
  3         2  
  3         87  
126             # diagnostic constants end
127              
128             # function control
129 3     3   8 use constant THRESHOLD => 16;
  3         3  
  3         94  
130              
131             # Email parts
132 3     3   9 use constant COMPONENT_LOCALPART => 0;
  3         1  
  3         88  
133 3     3   10 use constant COMPONENT_DOMAIN => 1;
  3         4  
  3         106  
134 3     3   12 use constant COMPONENT_LITERAL => 2;
  3         10  
  3         90  
135 3     3   8 use constant CONTEXT_COMMENT => 3;
  3         2  
  3         98  
136 3     3   11 use constant CONTEXT_FWS => 4;
  3         3  
  3         118  
137 3     3   10 use constant CONTEXT_QUOTEDSTRING => 5;
  3         2  
  3         91  
138 3     3   8 use constant CONTEXT_QUOTEDPAIR => 6;
  3         4  
  3         100  
139              
140             # Miscellaneous string constants
141 3     3   9 use constant STRING_AT => '@';
  3         2  
  3         96  
142 3     3   44 use constant STRING_BACKSLASH => '\\';
  3         5  
  3         90  
143 3     3   9 use constant STRING_DOT => '.';
  3         3  
  3         95  
144 3     3   10 use constant STRING_DQUOTE => '"';
  3         1  
  3         85  
145 3     3   8 use constant STRING_OPENPARENTHESIS => '(';
  3         3  
  3         101  
146 3     3   26 use constant STRING_CLOSEPARENTHESIS => ')';
  3         3  
  3         94  
147 3     3   9 use constant STRING_OPENSQBRACKET => '[';
  3         3  
  3         111  
148 3     3   9 use constant STRING_CLOSESQBRACKET => ']';
  3         3  
  3         99  
149 3     3   9 use constant STRING_HYPHEN => '-';
  3         2  
  3         83  
150 3     3   8 use constant STRING_COLON => ':';
  3         3  
  3         93  
151 3     3   8 use constant STRING_DOUBLECOLON => '::';
  3         3  
  3         86  
152 3     3   8 use constant STRING_SP => ' ';
  3         3  
  3         105  
153 3     3   8 use constant STRING_HTAB => "\t";
  3         2  
  3         115  
154 3     3   9 use constant STRING_CR => "\r";
  3         3  
  3         121  
155 3     3   15 use constant STRING_LF => "\n";
  3         3  
  3         121  
156 3     3   10 use constant STRING_IPV6TAG => 'IPv6:';
  3         2  
  3         120  
157             # US-ASCII visible characters not valid for atext (http://tools.ietf.org/html/rfc5322#section-3.2.3)
158 3     3   8 use constant STRING_SPECIALS => '()<>[]:;@\\,."';
  3         3  
  3         10435  
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 154739 my ( $email, $checkDNS, $errorlevel, $parsedata ) = @_;
198              
199 81   100     204 $checkDNS //= 0;
200 81   100     145 $errorlevel //= -1;
201 81   50     222 $parsedata //= {};
202              
203 81 100       132 return !1
204             unless $email;
205              
206 80         58 my ( $threshold, $diagnose );
207              
208 80 100       112 if ( $errorlevel < 0 ) {
209 30         25 $threshold = Email::IsEmail::VALID;
210 30         24 $diagnose = 0;
211             }
212             else {
213 50         43 $diagnose = 1;
214 50         47 $threshold = int $errorlevel;
215             }
216              
217 80         83 my $return_status = [Email::IsEmail::VALID];
218              
219              
220             # Parse the address into components, character by character
221 80         61 my $raw_length = length $email;
222 80         72 my $context = Email::IsEmail::COMPONENT_LOCALPART; # Where we are
223 80         85 my $context_stack = [$context]; # Where we have been
224 80         63 my $context_prior = Email::IsEmail::COMPONENT_LOCALPART; # Where we just came from
225 80         57 my $token = ''; # The current character
226 80         56 my $token_prior = ''; # The previous character
227 80         105 $parsedata->{Email::IsEmail::COMPONENT_LOCALPART} = ''; # For the components of the address
228 80         72 $parsedata->{Email::IsEmail::COMPONENT_DOMAIN} = '';
229              
230 80         150 my $atomlist = {
231             Email::IsEmail::COMPONENT_LOCALPART => [''],
232             Email::IsEmail::COMPONENT_DOMAIN => [''],
233             }; # For the dot-atom elements of the address
234 80         60 my $element_count = 0;
235 80         51 my $element_len = 0;
236 80         62 my $hyphen_flag = 0; # Hyphen cannot occur at the end of a subdomain
237 80         46 my $end_or_die = 0; # CFWS can only appear at the end of the element
238 80         53 my $crlf_count = 0;
239              
240 80         134 for ( my $i = 0; $i < $raw_length; $i++ ) {
241 2119         1875 $token = substr $email, $i, 1;
242 2119         1494 given($context) {
243             #-------------------------------------------------------------
244             # local-part
245             #-------------------------------------------------------------
246 2119         1547 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         394 given($token) {
264             # Comment
265 612         516 when (Email::IsEmail::STRING_OPENPARENTHESIS) {
266 5 100       7 if ( $element_len == 0 ) {
267             # Comments are OK at the beginning of an element
268 4 100       5 push @{$return_status}, ( $element_count == 0 ) ? Email::IsEmail::CFWS_COMMENT : Email::IsEmail::DEPREC_COMMENT;
  4         8  
269             }
270             else {
271 1         1 push @{$return_status}, Email::IsEmail::CFWS_COMMENT;
  1         2  
272 1         1 $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         6  
276 5         6 $context = Email::IsEmail::CONTEXT_COMMENT;
277             }
278             # Next dot-atom element
279 607         405 when (Email::IsEmail::STRING_DOT) {
280 12 100       20 if ( $element_len == 0 ) {
281             # Another dot, already?
282 2 100       1 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       15 if ($end_or_die) {
288 2         1 push @{$return_status}, Email::IsEmail::DEPREC_LOCALPART;
  2         4  
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         7 $element_len = 0;
294 12         11 $element_count++;
295 12         10 $parsedata->{Email::IsEmail::COMPONENT_LOCALPART} .= $token;
296 12         21 $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] = '';
297             }
298             # Quoted string
299 595         379 when (Email::IsEmail::STRING_DQUOTE) {
300 21 100       29 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       13 push @{$return_status}, ( $element_count == 0 ) ? Email::IsEmail::RFC5321_QUOTEDSTRING : Email::IsEmail::DEPREC_LOCALPART;
  17         38  
304              
305 17         21 $parsedata->{Email::IsEmail::COMPONENT_LOCALPART} .= $token;
306 17         18 $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] .= $token;
307 17         14 $element_len++;
308 17         15 $end_or_die = 1; # Quoted string must be the entire element
309 17         10 push @{$context_stack}, $context;
  17         19  
310 17         28 $context = Email::IsEmail::CONTEXT_QUOTEDSTRING;
311             }
312             else {
313 4         3 push @{$return_status}, Email::IsEmail::ERR_EXPECTING_ATEXT; # Fatal error
  4         8  
314             }
315             }
316             # Folding White Space
317             when ([ Email::IsEmail::STRING_CR,
318             Email::IsEmail::STRING_SP,
319 574         739 Email::IsEmail::STRING_HTAB, ]) {
320 5 0 0     10 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       1 push @{$return_status}, ( $element_count == 0 ) ? Email::IsEmail::CFWS_FWS : Email::IsEmail::DEPREC_FWS;
  3         7  
328             }
329             else {
330 2         1 $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         7  
334 5         3 $context = Email::IsEmail::CONTEXT_FWS;
335 5         9 $token_prior = $token;
336             }
337             # @
338 569         508 when (Email::IsEmail::STRING_AT) {
339             # At this point we should have a valid local-part
340 66 50       45 if ( scalar @{$context_stack} != 1 ) {
  66         111  
341 0         0 die('Unexpected item on context stack');
342             }
343              
344 66 100 66     321 if ( $parsedata->{Email::IsEmail::COMPONENT_LOCALPART} eq '' ) {
    100          
    100          
    100          
345 1         1 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         1 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         57 $context = Email::IsEmail::COMPONENT_DOMAIN; # Where we are
373 66         67 $context_stack = [$context]; # Where we have been
374 66         68 $element_count = 0;
375 66         55 $element_len = 0;
376 66         89 $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       337 if ($end_or_die) {
  503         521  
393             # We have encountered atext where it is no longer valid
394 2         2 given ($context_prior) {
395             when ([ Email::IsEmail::CONTEXT_COMMENT,
396 2         3 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         2  
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         348 $context_prior = $context;
408 501         331 my $ord = ord $token;
409              
410 501 100 33     2685 if ( ( $ord < 33 ) or ( $ord > 126 ) or ( $ord == 10 ) or
      33        
      66        
411             ( index( Email::IsEmail::STRING_SPECIALS, $token ) != -1 ) ) {
412 1         2 push @{$return_status}, Email::IsEmail::ERR_EXPECTING_ATEXT; # Fatal error
  1         2  
413             }
414 501         438 $parsedata->{Email::IsEmail::COMPONENT_LOCALPART} .= $token;
415 501         366 $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] .= $token;
416 501         672 $element_len++;
417             }
418             }
419             }
420             }
421             #-------------------------------------------------------------
422             # Domain
423             #-------------------------------------------------------------
424 1507         997 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         567 given($token) {
466             # Comment
467 832         673 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       1 push @{$return_status}, ( $element_count == 0 ) ? Email::IsEmail::DEPREC_CFWS_NEAR_AT : Email::IsEmail::DEPREC_COMMENT;
  1         3  
473             }
474             else {
475 1         2 push @{$return_status}, Email::IsEmail::CFWS_COMMENT;
  1         1  
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         3  
480 2         3 $context = Email::IsEmail::CONTEXT_COMMENT;
481             }
482             # Next dot-atom element
483 830         542 when (Email::IsEmail::STRING_DOT) {
484 47 100       83 if ( $element_len == 0 ) {
    100          
485             # Another dot, already?
486 2 100       1 push @{$return_status}, ( $element_count == 0 ) ? Email::IsEmail::ERR_DOT_START : Email::IsEmail::ERR_CONSECUTIVEDOTS; # Fatal error
  2         5  
487             }
488             elsif ($hyphen_flag) {
489             # Previous subdomain ended in a hyphen
490 1         1 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       56 if ( $element_len > 63 ) {
506 1         2 push @{$return_status}, Email::IsEmail::RFC5322_LABEL_TOOLONG;
  1         2  
507             }
508             }
509              
510 47         35 $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         31 $element_len = 0;
512 47         29 $element_count++;
513 47         59 $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count] = '';
514 47         79 $parsedata->{Email::IsEmail::COMPONENT_DOMAIN} .= $token;
515             }
516             # Domain literal
517 783         540 when (Email::IsEmail::STRING_OPENSQBRACKET) {
518 16 50       25 if ( $parsedata->{Email::IsEmail::COMPONENT_DOMAIN} eq '' ) {
519 16         15 $end_or_die = 1; # Domain literal must be the only component
520 16         12 $element_len++;
521 16         13 push @{$context_stack}, $context;
  16         26  
522 16         11 $context = Email::IsEmail::COMPONENT_LITERAL;
523 16         16 $parsedata->{Email::IsEmail::COMPONENT_DOMAIN} .= $token;
524 16         17 $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count] .= $token;
525 16         28 $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         1013 Email::IsEmail::STRING_HTAB ]) {
535 3 0 0     7 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       5 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         3  
544             }
545             else {
546 2         2 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         2 push @{$context_stack}, $context;
  3         4  
551 3         4 $context = Email::IsEmail::CONTEXT_FWS;
552 3         4 $token_prior = $token;
553             }
554             # atext
555 764         653 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       887 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         2 Email::IsEmail::CONTEXT_FWS ]) {
583 0         0 push @{$return_status}, Email::IsEmail::ERR_ATEXT_AFTER_CFWS;
  0         0  
584             }
585 1         2 when (Email::IsEmail::COMPONENT_LITERAL) {
586 1         0 push @{$return_status}, Email::IsEmail::ERR_ATEXT_AFTER_DOMLIT;
  1         2  
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         519 my $ord = ord $token;
595 764         469 $hyphen_flag = 0; # Assume this token isn't a hyphen unless we discover it is
596              
597 764 50 33     7039 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       12 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         2  
605             }
606              
607 5         4 $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         2  
611             }
612              
613 764         656 $parsedata->{Email::IsEmail::COMPONENT_DOMAIN} .= $token;
614 764         564 $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count] .= $token;
615 764         924 $element_len++;
616             }
617             }
618             }
619             #-------------------------------------------------------------
620             # Domain literal
621             #-------------------------------------------------------------
622 675         434 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         247 given($token) {
632             # End of domain literal
633 367         262 when (Email::IsEmail::STRING_CLOSESQBRACKET) {
634 13 100       16 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         8 my $max_groups = 8;
683 12         12 my $matchesIP = ();
684 12         8 my $index = -1;
685 12         12 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       51 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         35  
689 5         8 $index = index( $addressliteral, $matchesIP->[0] );
690 5 100       17 if ( $index > 0 ) {
691 3         6 $addressliteral = substr( $addressliteral, 0x0, $index ) . '0:0'; # Convert IPv4 part to IPv6 format for further testing
692             }
693             }
694              
695 12 100       32 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         2 push @{$return_status}, Email::IsEmail::RFC5322_DOMAINLITERAL;
  1         2  
701             }
702             else {
703 9         11 my $IPv6 = substr $addressliteral, 5;
704 9         74 $matchesIP = [ split Email::IsEmail::STRING_COLON, $IPv6 ]; # Revision 2.7: Daniel Marschall's new IPv6 testing strategy
705 9         11 my $groupCount = scalar @{$matchesIP};
  9         10  
706 9         13 my $index = index $IPv6, Email::IsEmail::STRING_DOUBLECOLON;
707              
708 9 100       12 if ( $index == -1 ) {
709             # We need exactly the right number of groups
710 4 100       6 if ( $groupCount != $max_groups ) {
711 3         2 push @{$return_status}, Email::IsEmail::RFC5322_IPV6_GRPCOUNT;
  3         4  
712             }
713             }
714             else {
715 5 100       13 if ( -1 != index( $IPv6, Email::IsEmail::STRING_DOUBLECOLON, $index + 1 ) ) {
716 1         7 push @{$return_status}, Email::IsEmail::RFC5322_IPV6_2X2XCOLON;
  1         1  
717             }
718             else {
719 4 100 66     17 if ( ( $index == 0 ) or ( $index == ( length($IPv6) - 2 ) ) ) {
720 1         1 $max_groups++; # RFC 4291 allows :: at the start or end of an address with 7 other groups in addition
721             }
722              
723 4 100       14 if ( $groupCount > $max_groups ) {
    50          
724 1         2 push @{$return_status}, Email::IsEmail::RFC5322_IPV6_MAXGRPS;
  1         1  
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     43 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         2  
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         2  
740             }
741 44         81 elsif ( scalar(grep { !/^[0-9A-Fa-f]{0,4}$/ } @{$matchesIP}) != 0 ) {
  7         8  
742 1         1 push @{$return_status}, Email::IsEmail::RFC5322_IPV6_BADCHAR; # Check for unmatched characters
  1         3  
743             }
744             else {
745 6         5 push @{$return_status}, Email::IsEmail::RFC5321_ADDRESSLITERAL;
  6         11  
746             }
747             }
748             }
749             else {
750 1         1 push @{$return_status}, Email::IsEmail::RFC5322_DOMAINLITERAL;
  1         2  
751             }
752              
753 13         17 $parsedata->{Email::IsEmail::COMPONENT_DOMAIN} .= $token;
754 13         15 $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count] .= $token;
755 13         8 $element_len++;
756 13         12 $context_prior = $context;
757 13         8 $context = pop @{$context_stack};
  13         27  
758             }
759 354         236 when (Email::IsEmail::STRING_BACKSLASH) {
760 2         2 push @{$return_status}, Email::IsEmail::RFC5322_DOMLIT_OBSDTEXT;
  2         3  
761 2         2 push @{$context_stack}, $context;
  2         2  
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         455 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         290 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         231 my $ord = ord $token;
795              
796             # CR, LF, SP & HTAB have already been parsed above
797 352 100 33     1914 if ( ( $ord > 127 ) or ( $ord == 0 ) or
    50 66        
      33        
798             ( $token eq Email::IsEmail::STRING_OPENSQBRACKET ) ) {
799 1         2 push @{$return_status}, Email::IsEmail::ERR_EXPECTING_DTEXT; # Fatal error
  1         2  
800 1         1 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         305 $parsedata->{Email::IsEmail::COMPONENT_LITERAL} .= $token;
807 351         220 $parsedata->{Email::IsEmail::COMPONENT_DOMAIN} .= $token;
808 351         269 $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count] .= $token;
809 351         394 $element_len++;
810             }
811             }
812             }
813             #-------------------------------------------------------------
814             # Quoted string
815             #-------------------------------------------------------------
816 308         207 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         138 given($token) {
824             # Quoted pair
825 205         147 when (Email::IsEmail::STRING_BACKSLASH) {
826 10         7 push @{$context_stack}, $context;
  10         11  
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         238 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         178 when (Email::IsEmail::STRING_DQUOTE) {
860 15         11 $parsedata->{Email::IsEmail::COMPONENT_LOCALPART} .= $token;
861 15         14 $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] .= $token;
862 15         12 $element_len++;
863 15         11 $context_prior = $context;
864 15         8 $context = pop @{$context_stack};
  15         30  
865             }
866             # qtext
867 180         116 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         124 my $ord = ord $token;
882              
883 180 50 33     1018 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         1 push @{$return_status}, Email::IsEmail::DEPREC_QTEXT;
  1         2  
888             }
889              
890 180         158 $parsedata->{Email::IsEmail::COMPONENT_LOCALPART} .= $token;
891 180         129 $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] .= $token;
892 180         227 $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         69 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         11 my $ord = ord $token;
924              
925 13 50 33     57 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         10 $context_prior = $context;
940 13         11 $context = pop @{$context_stack}; # End of qpair
  13         13  
941 13         15 $token = Email::IsEmail::STRING_BACKSLASH . $token;
942              
943 13         14 given($context) {
944 13         11 when (Email::IsEmail::CONTEXT_COMMENT) {}
945 12         13 when (Email::IsEmail::CONTEXT_QUOTEDSTRING) {
946 10         7 $parsedata->{Email::IsEmail::COMPONENT_LOCALPART} .= $token;
947 10         11 $atomlist->{Email::IsEmail::COMPONENT_LOCALPART}[$element_count] .= $token;
948 10         16 $element_len += 2; # The maximum sizes specified by RFC 5321 are octet counts, so we must include the backslash
949             }
950 2         3 when (Email::IsEmail::COMPONENT_LITERAL) {
951 2         2 $parsedata->{Email::IsEmail::COMPONENT_DOMAIN} .= $token;
952 2         1 $atomlist->{Email::IsEmail::COMPONENT_DOMAIN}[$element_count] .= $token;
953 2         4 $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         68 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         55 given($token) {
969             # Nested comment
970 83         52 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         3 $context_prior = $context;
978 5         3 $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         51 when (Email::IsEmail::STRING_BACKSLASH) {
998 2         3 push @{$context_stack}, $context;
  2         2  
999 2         4 $context = Email::IsEmail::CONTEXT_QUOTEDPAIR;
1000             }
1001             # Folding White Space
1002             when ([ Email::IsEmail::STRING_CR,
1003             Email::IsEmail::STRING_SP,
1004 75         97 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         60 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         47 my $ord = ord $token;
1034              
1035 75 50 33     495 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         7 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       10 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         7 given($token) {
1074 7         5 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         10 Email::IsEmail::STRING_HTAB, ]) {
1082             }
1083 7         8 default {
1084 7 50       10 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         6 $crlf_count = 0;
1090 7         5 $context_prior = $context;
1091 7         5 $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       2075 if ( Email::IsEmail::_max($return_status) > Email::IsEmail::RFC5322 ) {
1123 17         20 last; # No point going on if we've got a fatal error
1124             }
1125             }
1126              
1127             # Some simple final tests
1128 80 100       86 if ( Email::IsEmail::_max($return_status) < Email::IsEmail::RFC5322 ) {
1129 63 100       373 if ( $context == Email::IsEmail::CONTEXT_QUOTEDSTRING ) {
    100          
    100          
    100          
    50          
    100          
    100          
    100          
    50          
    100          
    50          
1130 2         1 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         2 push @{$return_status}, Email::IsEmail::ERR_UNCLOSEDCOMMENT; # Fatal error
  2         4  
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         5  
1146             }
1147             elsif ( $element_len == 0 ) {
1148 1         1 push @{$return_status}, Email::IsEmail::ERR_DOT_END; # Fatal error
  1         2  
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         72 my $dns_checked = 0;
1190              
1191 80 50 33     165 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     150 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         7  
1267             }
1268              
1269 17 50       86 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         99 $return_status = Email::IsEmail::_unique($return_status);
1275 80         112 my $final_status = Email::IsEmail::_max($return_status);
1276              
1277 80 100       60 if ( scalar @{$return_status} != 1 ) {
  80         126  
1278 67         39 shift @{$return_status}; # remove redundant Email::IsEmail::VALID
  67         60  
1279             }
1280              
1281 80         86 $parsedata->{'status'} = $return_status;
1282              
1283 80 50       105 if ( $final_status < $threshold ) {
1284 0         0 $final_status = Email::IsEmail::VALID;
1285             }
1286              
1287 80 100       428 return ($diagnose) ? $final_status : ( $final_status < Email::IsEmail::THRESHOLD );
1288             }
1289              
1290             sub _max {
1291 2372     2372   1665 my ( $array_ref ) = @_;
1292              
1293 2372         1432 my $res = VALID;
1294              
1295 2372         1455 foreach my $val ( @{$array_ref} ) {
  2372         2298  
1296 3330 100       4369 if ( $val > $res ) {
1297 912         818 $res = $val;
1298             }
1299             }
1300              
1301 2372         5154 return $res;
1302             }
1303              
1304              
1305             sub _unique {
1306 80     80   64 my ( $array_ref ) = @_;
1307              
1308 80         61 my %seen;
1309              
1310 80         59 return [ grep !$seen{$_}++, @{$array_ref} ];
  80         352  
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