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   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