This project does not seem to handle request data directly as such no vulnerable execution paths were found.
include
, or for example
via PHP's auto-loading mechanism.
These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more
1 | <?php |
||
2 | /** |
||
3 | * |
||
4 | * This file is part of Aura for PHP. |
||
5 | * |
||
6 | * @license http://opensource.org/licenses/bsd-license.php BSD |
||
7 | * |
||
8 | */ |
||
9 | namespace Aura\Filter\Rule\Validate; |
||
10 | |||
11 | use Aura\Filter\Exception; |
||
12 | |||
13 | /** |
||
14 | * |
||
15 | * Check that an email address conforms to RFCs 5321, 5322 and others. |
||
16 | * |
||
17 | * As of Version 3.0, we are now distinguishing clearly between a Mailbox |
||
18 | * as defined by RFC 5321 and an addr-spec as defined by RFC 5322. Depending |
||
19 | * on the context, either can be regarded as a valid email address. The |
||
20 | * RFC 5321 Mailbox specification is more restrictive (comments, white space |
||
21 | * and obsolete forms are not allowed). |
||
22 | * |
||
23 | * Read the following RFCs to understand the constraints: |
||
24 | * |
||
25 | * - <http://tools.ietf.org/html/rfc5321> |
||
26 | * - <http://tools.ietf.org/html/rfc5322> |
||
27 | * - <http://tools.ietf.org/html/rfc4291#section-2.2> |
||
28 | * - <http://tools.ietf.org/html/rfc1123#section-2.1> |
||
29 | * - <http://tools.ietf.org/html/rfc3696> (guidance only) |
||
30 | * |
||
31 | * Copyright © 2008-2011, Dominic Sayers |
||
32 | * Test schema documentation Copyright © 2011, Daniel Marschall |
||
33 | * All rights reserved. |
||
34 | * |
||
35 | * --- |
||
36 | * |
||
37 | * N.B.: Refactored by Paul M. Jones, from Dominic Sayers' is_email() function |
||
38 | * to methods and properties. Errors and omissions should be presumed to be a |
||
39 | * result of the refactoring, not of the original function. |
||
40 | * |
||
41 | * Further, this validation rule converts IDNs to ASCII, which is not required |
||
42 | * per se by any of the email RFCs. |
||
43 | * |
||
44 | * --- |
||
45 | * |
||
46 | * @package Aura.Filter |
||
47 | * |
||
48 | * @author Dominic Sayers <[email protected]> |
||
49 | * |
||
50 | * @author Paul M. Jones <[email protected]> |
||
51 | * |
||
52 | * @copyright 2008-2011 Dominic Sayers |
||
53 | * |
||
54 | * @copyright 2015 Paul M. Jones |
||
55 | * |
||
56 | * @license http://www.opensource.org/licenses/bsd-license.php BSD License |
||
57 | * |
||
58 | * @link http://www.dominicsayers.com/isemail |
||
59 | * |
||
60 | * @version 3.04.1 - Changed my link to http://isemail.info throughout |
||
61 | * |
||
62 | */ |
||
63 | class Email |
||
64 | { |
||
65 | /*:diagnostic constants start:*/ |
||
66 | |||
67 | // Categories |
||
68 | const VALID_CATEGORY = 1; |
||
69 | const DNSWARN = 7; |
||
70 | const RFC5321 = 15; |
||
71 | const CFWS = 31; |
||
72 | const DEPREC = 63; |
||
73 | const RFC5322 = 127; |
||
74 | const ERR = 255; |
||
75 | |||
76 | // Diagnoses |
||
77 | // Address is valid |
||
78 | const VALID = 0; |
||
79 | // Address is valid but a DNS check was not successful |
||
80 | const DNSWARN_NO_MX_RECORD = 5; |
||
81 | const DNSWARN_NO_RECORD = 6; |
||
82 | // Address is valid for SMTP but has unusual elements |
||
83 | const RFC5321_TLD = 9; |
||
84 | const RFC5321_TLDNUMERIC = 10; |
||
85 | const RFC5321_QUOTEDSTRING = 11; |
||
86 | const RFC5321_ADDRESSLITERAL = 12; |
||
87 | const RFC5321_IPV6DEPRECATED = 13; |
||
88 | // Address is valid within the message but cannot be used unmodified for the envelope |
||
89 | const CFWS_COMMENT = 17; |
||
90 | const CFWS_FWS = 18; |
||
91 | // Address contains deprecated elements but may still be valid in restricted contexts |
||
92 | const DEPREC_LOCALPART = 33; |
||
93 | const DEPREC_FWS = 34; |
||
94 | const DEPREC_QTEXT = 35; |
||
95 | const DEPREC_QP = 36; |
||
96 | const DEPREC_COMMENT = 37; |
||
97 | const DEPREC_CTEXT = 38; |
||
98 | const DEPREC_CFWS_NEAR_AT = 49; |
||
99 | // The address is only valid according to the broad definition of RFC 5322. |
||
100 | // It is otherwise invalid. |
||
101 | const RFC5322_DOMAIN = 65; |
||
102 | const RFC5322_TOOLONG = 66; |
||
103 | const RFC5322_LOCAL_TOOLONG = 67; |
||
104 | const RFC5322_DOMAIN_TOOLONG = 68; |
||
105 | const RFC5322_LABEL_TOOLONG = 69; |
||
106 | const RFC5322_DOMAINLITERAL = 70; |
||
107 | const RFC5322_DOMLIT_OBSDTEXT = 71; |
||
108 | const RFC5322_IPV6_GRPCOUNT = 72; |
||
109 | const RFC5322_IPV6_2X2XCOLON = 73; |
||
110 | const RFC5322_IPV6_BADCHAR = 74; |
||
111 | const RFC5322_IPV6_MAXGRPS = 75; |
||
112 | const RFC5322_IPV6_COLONSTRT = 76; |
||
113 | const RFC5322_IPV6_COLONEND = 77; |
||
114 | // Address is invalid for any purpose |
||
115 | const ERR_EXPECTING_DTEXT = 129; |
||
116 | const ERR_NOLOCALPART = 130; |
||
117 | const ERR_NODOMAIN = 131; |
||
118 | const ERR_CONSECUTIVEDOTS = 132; |
||
119 | const ERR_ATEXT_AFTER_CFWS = 133; |
||
120 | const ERR_ATEXT_AFTER_QS = 134; |
||
121 | const ERR_ATEXT_AFTER_DOMLIT = 135; |
||
122 | const ERR_EXPECTING_QPAIR = 136; |
||
123 | const ERR_EXPECTING_ATEXT = 137; |
||
124 | const ERR_EXPECTING_QTEXT = 138; |
||
125 | const ERR_EXPECTING_CTEXT = 139; |
||
126 | const ERR_BACKSLASHEND = 140; |
||
127 | const ERR_DOT_START = 141; |
||
128 | const ERR_DOT_END = 142; |
||
129 | const ERR_DOMAINHYPHENSTART = 143; |
||
130 | const ERR_DOMAINHYPHENEND = 144; |
||
131 | const ERR_UNCLOSEDQUOTEDSTR = 145; |
||
132 | const ERR_UNCLOSEDCOMMENT = 146; |
||
133 | const ERR_UNCLOSEDDOMLIT = 147; |
||
134 | const ERR_FWS_CRLF_X2 = 148; |
||
135 | const ERR_FWS_CRLF_END = 149; |
||
136 | const ERR_CR_NO_LF = 150; |
||
137 | /*:diagnostic constants end:*/ |
||
138 | |||
139 | // function control |
||
140 | const THRESHOLD = 16; |
||
141 | |||
142 | // Email parts |
||
143 | const COMPONENT_LOCALPART = 0; |
||
144 | const COMPONENT_DOMAIN = 1; |
||
145 | const COMPONENT_LITERAL = 2; |
||
146 | const CONTEXT_COMMENT = 3; |
||
147 | const CONTEXT_FWS = 4; |
||
148 | const CONTEXT_QUOTEDSTRING = 5; |
||
149 | const CONTEXT_QUOTEDPAIR = 6; |
||
150 | |||
151 | // Miscellaneous string constants |
||
152 | const STRING_AT = '@'; |
||
153 | const STRING_BACKSLASH = '\\'; |
||
154 | const STRING_DOT = '.'; |
||
155 | const STRING_DQUOTE = '"'; |
||
156 | const STRING_OPENPARENTHESIS = '('; |
||
157 | const STRING_CLOSEPARENTHESIS = ')'; |
||
158 | const STRING_OPENSQBRACKET = '['; |
||
159 | const STRING_CLOSESQBRACKET = ']'; |
||
160 | const STRING_HYPHEN = '-'; |
||
161 | const STRING_COLON = ':'; |
||
162 | const STRING_DOUBLECOLON = '::'; |
||
163 | const STRING_SP = ' '; |
||
164 | const STRING_HTAB = "\t"; |
||
165 | const STRING_CR = "\r"; |
||
166 | const STRING_LF = "\n"; |
||
167 | const STRING_IPV6TAG = 'IPv6:'; |
||
168 | |||
169 | // US-ASCII visible characters not valid for atext |
||
170 | // <http://tools.ietf.org/html/rfc5322#section-3.2.3> |
||
171 | const STRING_SPECIALS = '()<>[]:;@\\,."'; |
||
172 | |||
173 | /** |
||
174 | * |
||
175 | * The email address being checked. |
||
176 | * |
||
177 | * @var string |
||
178 | * |
||
179 | */ |
||
180 | protected $email; |
||
181 | |||
182 | /** |
||
183 | * |
||
184 | * Check DNS as part of validation? |
||
185 | * |
||
186 | * @var bool |
||
187 | * |
||
188 | */ |
||
189 | protected $checkDns; |
||
190 | |||
191 | /** |
||
192 | * |
||
193 | * The validation threshold level. |
||
194 | * |
||
195 | * @var int |
||
196 | * |
||
197 | */ |
||
198 | protected $threshold; |
||
199 | |||
200 | /** |
||
201 | * |
||
202 | * Diagnose errors? |
||
203 | * |
||
204 | * @var bool |
||
205 | * |
||
206 | */ |
||
207 | protected $diagnose; |
||
208 | |||
209 | /** |
||
210 | * |
||
211 | * Has DNS been checked? |
||
212 | * |
||
213 | * @var bool |
||
214 | * |
||
215 | */ |
||
216 | protected $dnsChecked; |
||
217 | |||
218 | /** |
||
219 | * |
||
220 | * The return status. |
||
221 | * |
||
222 | * @var int |
||
223 | * |
||
224 | */ |
||
225 | protected $returnStatus; |
||
226 | |||
227 | /** |
||
228 | * |
||
229 | * The length of the email address being checked. |
||
230 | * |
||
231 | * @var int |
||
232 | * |
||
233 | */ |
||
234 | protected $rawLength; |
||
235 | |||
236 | /** |
||
237 | * |
||
238 | * The current parser context. |
||
239 | * |
||
240 | * @var int |
||
241 | * |
||
242 | */ |
||
243 | protected $context; |
||
244 | |||
245 | /** |
||
246 | * |
||
247 | * Parser context stack. |
||
248 | * |
||
249 | * @var array |
||
250 | * |
||
251 | */ |
||
252 | protected $contextStack; |
||
253 | |||
254 | /** |
||
255 | * |
||
256 | * The prior parser context. |
||
257 | * |
||
258 | * @var int |
||
259 | * |
||
260 | */ |
||
261 | protected $contextPrior; |
||
262 | |||
263 | /** |
||
264 | * |
||
265 | * The current token being parsed. |
||
266 | * |
||
267 | * @var string |
||
268 | * |
||
269 | */ |
||
270 | protected $token; |
||
271 | |||
272 | /** |
||
273 | * |
||
274 | * The previous token being parsed. |
||
275 | * |
||
276 | * @var string |
||
277 | * |
||
278 | */ |
||
279 | protected $tokenPrior; |
||
280 | |||
281 | /** |
||
282 | * |
||
283 | * The components of the address. |
||
284 | * |
||
285 | * @var array |
||
286 | * |
||
287 | */ |
||
288 | protected $parseData; |
||
289 | |||
290 | /** |
||
291 | * |
||
292 | * The dot-atom elements of the address. |
||
293 | * |
||
294 | * @var array |
||
295 | * |
||
296 | */ |
||
297 | protected $atomList; |
||
298 | |||
299 | /** |
||
300 | * |
||
301 | * Element count. |
||
302 | * |
||
303 | * @var int |
||
304 | * |
||
305 | */ |
||
306 | protected $elementCount; |
||
307 | |||
308 | /** |
||
309 | * |
||
310 | * Element length. |
||
311 | * |
||
312 | * @var int |
||
313 | * |
||
314 | */ |
||
315 | protected $elementLen; |
||
316 | |||
317 | /** |
||
318 | * |
||
319 | * Is a hyphen allowed? |
||
320 | * |
||
321 | * @var bool |
||
322 | * |
||
323 | */ |
||
324 | protected $hyphenFlag; |
||
325 | |||
326 | /** |
||
327 | * |
||
328 | * CFWS can only appear at the end of the element |
||
329 | * |
||
330 | * @var bool |
||
331 | * |
||
332 | */ |
||
333 | protected $endOrDie; |
||
334 | |||
335 | /** |
||
336 | * |
||
337 | * Current position in the email string. |
||
338 | * |
||
339 | * @var int |
||
340 | * |
||
341 | */ |
||
342 | protected $pos; |
||
343 | |||
344 | /** |
||
345 | * |
||
346 | * Count of CRLF occurrences. |
||
347 | * |
||
348 | * @var null|int |
||
349 | * |
||
350 | */ |
||
351 | protected $crlfCount; |
||
352 | |||
353 | /** |
||
354 | * |
||
355 | * The final status of email validation. |
||
356 | * |
||
357 | * @var int |
||
358 | * |
||
359 | */ |
||
360 | protected $finalStatus; |
||
361 | |||
362 | /** |
||
363 | * |
||
364 | * Validates that the value is an email address. |
||
365 | * |
||
366 | * @param object $subject The subject to be filtered. |
||
367 | * |
||
368 | * @param string $field The subject field name. |
||
369 | * |
||
370 | * @return bool True if valid, false if not. |
||
371 | * |
||
372 | */ |
||
373 | 165 | public function __invoke($subject, $field) |
|
374 | { |
||
375 | 165 | $email = $subject->$field; |
|
376 | 165 | if ($this->intl()) { |
|
377 | 165 | $email = $this->idnToAscii($email); |
|
378 | } |
||
379 | 165 | return $this->isEmail($email); |
|
380 | } |
||
381 | |||
382 | /** |
||
383 | * |
||
384 | * Is the intl extension loaded? |
||
385 | * |
||
386 | * @return bool |
||
387 | * |
||
388 | */ |
||
389 | 165 | protected function intl() |
|
390 | { |
||
391 | 165 | return extension_loaded('intl'); |
|
392 | } |
||
393 | |||
394 | /** |
||
395 | * |
||
396 | * Converts an international domain in the email address to ASCII. |
||
397 | * |
||
398 | * @param string $email The email address to check. |
||
399 | * |
||
400 | * @return string The email with the IDN converted to ASCII (if possible). |
||
401 | * |
||
402 | */ |
||
403 | 165 | protected function idnToAscii($email) |
|
404 | { |
||
405 | 165 | $parts = explode('@', $email); |
|
406 | 165 | $domain = array_pop($parts); |
|
407 | 165 | if (! $parts) { |
|
0 ignored issues
–
show
|
|||
408 | // no parts remaining, so no @ symbol, so not valid to begin with |
||
409 | 4 | return $email; |
|
410 | } |
||
411 | |||
412 | // put the parts back together, with the domain part converted to ascii |
||
413 | 161 | return implode('@', $parts) . '@' . idn_to_ascii($domain); |
|
414 | } |
||
415 | |||
416 | /** |
||
417 | * |
||
418 | * Checks that an email address conforms to RFCs 5321, 5322 and others, |
||
419 | * allowing for international domain names when the intl extension is |
||
420 | * loaded. |
||
421 | * |
||
422 | * @param string $email The email address to check. |
||
423 | * |
||
424 | * @param bool $checkDns Make a DNS check for MX records? |
||
425 | * |
||
426 | * @param mixed $errorlevel Determines the boundary between valid and |
||
427 | * invalid addresses. Status codes above this number will be returned as- |
||
428 | * is, status codes below will be returned as Email::VALID. Thus the |
||
429 | * calling program can simply look for Email::VALID if it is only |
||
430 | * interested in whether an address is valid or not. The errorlevel will |
||
431 | * determine how "picky" is_email() is about the address. If omitted or |
||
432 | * passed as false then isEmail() will return true or false rather than |
||
433 | * an integer error or warning. N.B.: Note the difference between |
||
434 | * $errorlevel = false and $errorlevel = 0. |
||
435 | * |
||
436 | */ |
||
437 | 165 | protected function isEmail($email, $checkDns = false, $errorlevel = false) |
|
438 | { |
||
439 | 165 | $this->reset($email, $checkDns, $errorlevel); |
|
440 | 165 | $this->parse(); |
|
441 | 165 | $this->checkDns(); |
|
442 | 165 | $this->checkTld(); |
|
443 | 165 | $this->finalStatus(); |
|
444 | 165 | return ($this->diagnose) |
|
445 | ? $this->finalStatus |
||
446 | 165 | : ($this->finalStatus < Email::THRESHOLD); |
|
447 | } |
||
448 | |||
449 | /** |
||
450 | * |
||
451 | * Resets the validation rule for a new email address. |
||
452 | * |
||
453 | * @param string $email The email address to check. |
||
454 | * |
||
455 | * @param bool $checkDns Make a DNS check for MX records? |
||
456 | * |
||
457 | * @param mixed $errorlevel Determines the boundary between valid and |
||
458 | * invalid addresses. |
||
459 | * |
||
460 | * @return null |
||
461 | * |
||
462 | */ |
||
463 | 165 | protected function reset($email, $checkDns, $errorlevel) |
|
464 | { |
||
465 | 165 | $this->email = $email; |
|
466 | |||
467 | 165 | $this->checkDns = $checkDns; |
|
468 | 165 | $this->dnsChecked = false; |
|
469 | |||
470 | 165 | $this->setThresholdDiagnose($errorlevel); |
|
471 | |||
472 | 165 | $this->returnStatus = array(Email::VALID); |
|
0 ignored issues
–
show
It seems like
array(\Aura\Filter\Rule\Validate\Email::VALID) of type array<integer,?> is incompatible with the declared type integer of property $returnStatus .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..
Loading history...
|
|||
473 | 165 | $this->rawLength = strlen($this->email); |
|
474 | |||
475 | // Where we are |
||
476 | 165 | $this->context = Email::COMPONENT_LOCALPART; |
|
477 | |||
478 | // Where we have been |
||
479 | 165 | $this->contextStack = array($this->context); |
|
480 | |||
481 | // Where we just came from |
||
482 | 165 | $this->contextPrior = Email::COMPONENT_LOCALPART; |
|
483 | |||
484 | // The current character |
||
485 | 165 | $this->token = ''; |
|
486 | |||
487 | // The previous character |
||
488 | 165 | $this->tokenPrior = ''; |
|
489 | |||
490 | // For the components of the address |
||
491 | 165 | $this->parseData = array( |
|
492 | Email::COMPONENT_LOCALPART => '', |
||
493 | Email::COMPONENT_DOMAIN => '' |
||
494 | ); |
||
495 | |||
496 | // For the dot-atom elements of the address |
||
497 | 165 | $this->atomList = array( |
|
498 | Email::COMPONENT_LOCALPART => array(''), |
||
499 | Email::COMPONENT_DOMAIN => array('') |
||
500 | ); |
||
501 | |||
502 | 165 | $this->elementCount = 0; |
|
503 | 165 | $this->elementLen = 0; |
|
504 | |||
505 | // Hyphen cannot occur at the end of a subdomain |
||
506 | 165 | $this->hyphenFlag = false; |
|
507 | |||
508 | // CFWS can only appear at the end of the element |
||
509 | 165 | $this->endOrDie = false; |
|
510 | |||
511 | 165 | $this->finalStatus = null; |
|
512 | |||
513 | 165 | $this->crlfCount = null; |
|
514 | 165 | } |
|
515 | |||
516 | /** |
||
517 | * |
||
518 | * Sets the $threshold and $diagnose properties. |
||
519 | * |
||
520 | * @param mixed $errorlevel Determines the boundary between valid and |
||
521 | * invalid addresses. |
||
522 | * |
||
523 | * @return null |
||
524 | * |
||
525 | */ |
||
526 | 165 | protected function setThresholdDiagnose($errorlevel) |
|
527 | { |
||
528 | 165 | if (is_bool($errorlevel)) { |
|
529 | 165 | $this->threshold = Email::VALID; |
|
530 | 165 | $this->diagnose = (bool) $errorlevel; |
|
531 | 165 | return; |
|
532 | } |
||
533 | |||
534 | $this->diagnose = true; |
||
535 | |||
536 | switch ((int) $errorlevel) { |
||
537 | case E_WARNING: |
||
538 | // For backward compatibility |
||
539 | $this->threshold = Email::THRESHOLD; |
||
540 | break; |
||
541 | case E_ERROR: |
||
542 | // For backward compatibility |
||
543 | $this->threshold = Email::VALID; |
||
544 | break; |
||
545 | default: |
||
546 | $this->threshold = (int) $errorlevel; |
||
547 | } |
||
548 | } |
||
549 | |||
550 | /** |
||
551 | * |
||
552 | * Parse the address into components, character by character. |
||
553 | * |
||
554 | * @return null |
||
555 | * |
||
556 | */ |
||
557 | 165 | protected function parse() |
|
558 | { |
||
559 | 165 | for ($this->pos = 0; $this->pos < $this->rawLength; $this->pos++) { |
|
560 | 164 | $this->token = $this->email[$this->pos]; |
|
561 | 164 | $this->parseContext(); |
|
562 | 164 | if ((int) max($this->returnStatus) > Email::RFC5322) { |
|
563 | // No point going on if we've got a fatal error |
||
564 | 42 | break; |
|
565 | } |
||
566 | } |
||
567 | 165 | $this->parseFinal(); |
|
568 | 165 | } |
|
569 | |||
570 | /** |
||
571 | * |
||
572 | * Parse for the current context. |
||
573 | * |
||
574 | * @return null |
||
575 | * |
||
576 | */ |
||
577 | 164 | protected function parseContext() |
|
578 | { |
||
579 | 164 | switch ($this->context) { |
|
580 | 164 | case Email::COMPONENT_LOCALPART: |
|
581 | 164 | $this->parseComponentLocalPart(); |
|
582 | 164 | break; |
|
583 | 143 | case Email::COMPONENT_DOMAIN: |
|
584 | 119 | $this->parseComponentDomain(); |
|
585 | 119 | break; |
|
586 | 102 | case Email::COMPONENT_LITERAL: |
|
587 | 36 | $this->parseComponentLiteral(); |
|
588 | 36 | break; |
|
589 | 73 | case Email::CONTEXT_QUOTEDSTRING: |
|
590 | 27 | $this->parseContextQuotedString(); |
|
591 | 27 | break; |
|
592 | 58 | case Email::CONTEXT_QUOTEDPAIR: |
|
593 | 18 | $this->parseContextQuotedPair(); |
|
594 | 18 | break; |
|
595 | 42 | case Email::CONTEXT_COMMENT: |
|
596 | 20 | $this->parseContextComment(); |
|
597 | 20 | break; |
|
598 | 23 | case Email::CONTEXT_FWS: |
|
599 | 23 | $this->parseContextFws(); |
|
600 | 23 | break; |
|
601 | default: |
||
602 | throw new Exception("Unknown context: {$this->context}"); |
||
603 | } |
||
604 | 164 | } |
|
605 | |||
606 | /** |
||
607 | * |
||
608 | * Parse for the local part component. |
||
609 | * |
||
610 | * @return null |
||
611 | * |
||
612 | */ |
||
613 | 164 | protected function parseComponentLocalPart() |
|
614 | { |
||
615 | // http://tools.ietf.org/html/rfc5322#section-3.4.1 |
||
616 | // local-part = dot-atom / quoted-string / obs-local-part |
||
617 | // |
||
618 | // dot-atom = [CFWS] dot-atom-text [CFWS] |
||
619 | // |
||
620 | // dot-atom-text = 1*atext *("." 1*atext) |
||
621 | // |
||
622 | // quoted-string = [CFWS] |
||
623 | // DQUOTE *([FWS] qcontent) [FWS] DQUOTE |
||
624 | // [CFWS] |
||
625 | // |
||
626 | // obs-local-part = word *("." word) |
||
627 | // |
||
628 | // word = atom / quoted-string |
||
629 | // |
||
630 | // atom = [CFWS] 1*atext [CFWS] |
||
631 | 164 | switch ($this->token) { |
|
632 | |||
633 | // Comment |
||
634 | 164 | case Email::STRING_OPENPARENTHESIS: |
|
635 | 13 | if ($this->elementLen === 0) { |
|
636 | // Comments are OK at the beginning of an element |
||
637 | 12 | $this->returnStatus[] = ($this->elementCount === 0) |
|
638 | 11 | ? Email::CFWS_COMMENT |
|
639 | 12 | : Email::DEPREC_COMMENT; |
|
640 | } else { |
||
641 | // We can't start a comment in the middle of an element, so this better be the end |
||
642 | 1 | $this->returnStatus[] = Email::CFWS_COMMENT; |
|
643 | 1 | $this->endOrDie = true; |
|
644 | } |
||
645 | |||
646 | 13 | $this->contextStack[] = $this->context; |
|
647 | 13 | $this->context = Email::CONTEXT_COMMENT; |
|
648 | 13 | break; |
|
649 | |||
650 | // Next dot-atom element |
||
651 | 159 | case Email::STRING_DOT: |
|
652 | 9 | if ($this->elementLen === 0) { |
|
653 | // Another dot, already? |
||
654 | // Fatal error |
||
655 | 2 | $this->returnStatus[] = ($this->elementCount === 0) |
|
656 | 1 | ? Email::ERR_DOT_START |
|
657 | 2 | : Email::ERR_CONSECUTIVEDOTS; |
|
658 | } else { |
||
659 | // The entire local-part can be a quoted string for RFC 5321 |
||
660 | // If it's just one atom that is quoted then it's an RFC 5322 obsolete form |
||
661 | 8 | if ($this->endOrDie) { |
|
662 | 3 | $this->returnStatus[] = Email::DEPREC_LOCALPART; |
|
663 | } |
||
664 | } |
||
665 | |||
666 | // CFWS & quoted strings are OK again now we're at the beginning of an element (although they are obsolete forms) |
||
667 | 9 | $this->endOrDie = false; |
|
668 | 9 | $this->elementLen = 0; |
|
669 | 9 | $this->elementCount++; |
|
670 | 9 | $this->parseData[Email::COMPONENT_LOCALPART] .= $this->token; |
|
671 | 9 | $this->atomList[Email::COMPONENT_LOCALPART][$this->elementCount] = ''; |
|
672 | |||
673 | 9 | break; |
|
674 | |||
675 | // Quoted string |
||
676 | 158 | case Email::STRING_DQUOTE: |
|
677 | 29 | if ($this->elementLen === 0) { |
|
678 | // The entire local-part can be a quoted string for RFC 5321 |
||
679 | // If it's just one atom that is quoted then it's an RFC 5322 obsolete form |
||
680 | 27 | $this->returnStatus[] = ($this->elementCount === 0) |
|
681 | 27 | ? Email::RFC5321_QUOTEDSTRING |
|
682 | 1 | : Email::DEPREC_LOCALPART; |
|
683 | |||
684 | 27 | $this->parseData[Email::COMPONENT_LOCALPART] .= $this->token; |
|
685 | 27 | $this->atomList[Email::COMPONENT_LOCALPART][$this->elementCount] .= $this->token; |
|
686 | 27 | $this->elementLen++; |
|
687 | // Quoted string must be the entire element |
||
688 | 27 | $this->endOrDie = true; |
|
689 | 27 | $this->contextStack[] = $this->context; |
|
690 | 27 | $this->context = Email::CONTEXT_QUOTEDSTRING; |
|
691 | } else { |
||
692 | // Fatal error |
||
693 | 4 | $this->returnStatus[] = Email::ERR_EXPECTING_ATEXT; |
|
694 | } |
||
695 | |||
696 | 29 | break; |
|
697 | |||
698 | // Folding White Space |
||
699 | 147 | case Email::STRING_CR: |
|
700 | 144 | case Email::STRING_SP: |
|
701 | 140 | case Email::STRING_HTAB: |
|
702 | 13 | if (($this->token === Email::STRING_CR) && ((++$this->pos === $this->rawLength) || ($this->email[$this->pos] !== Email::STRING_LF))) { |
|
703 | // Fatal error |
||
704 | 1 | $this->returnStatus[] = Email::ERR_CR_NO_LF; |
|
705 | 1 | break; |
|
706 | } |
||
707 | |||
708 | 12 | if ($this->elementLen === 0) { |
|
709 | 12 | $this->returnStatus[] = ($this->elementCount === 0) ? Email::CFWS_FWS : Email::DEPREC_FWS; |
|
710 | } else { |
||
711 | // We can't start FWS in the middle of an element, so this better be the end |
||
712 | 2 | $this->endOrDie = true; |
|
713 | } |
||
714 | |||
715 | 12 | $this->contextStack[] = $this->context; |
|
716 | 12 | $this->context = Email::CONTEXT_FWS; |
|
717 | 12 | $this->tokenPrior = $this->token; |
|
718 | |||
719 | 12 | break; |
|
720 | |||
721 | // @ |
||
722 | 140 | case Email::STRING_AT: |
|
723 | // At this point we should have a valid local-part |
||
724 | 129 | if (count($this->contextStack) !== 1) { |
|
725 | throw new Exception('Unexpected item on context stack'); |
||
726 | } |
||
727 | |||
728 | 129 | if ($this->parseData[Email::COMPONENT_LOCALPART] === '') { |
|
729 | // Fatal error |
||
730 | 3 | $this->returnStatus[] = Email::ERR_NOLOCALPART; |
|
731 | 126 | } elseif ($this->elementLen === 0) { |
|
732 | // Fatal error |
||
733 | 1 | $this->returnStatus[] = Email::ERR_DOT_END; |
|
734 | 125 | } elseif (strlen($this->parseData[Email::COMPONENT_LOCALPART]) > 64) { |
|
735 | // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.1 |
||
736 | // The maximum total length of a user name or other local-part is 64 |
||
737 | // octets. |
||
738 | 3 | $this->returnStatus[] = Email::RFC5322_LOCAL_TOOLONG; |
|
739 | 122 | } elseif (($this->contextPrior === Email::CONTEXT_COMMENT) || ($this->contextPrior === Email::CONTEXT_FWS)) { |
|
740 | // http://tools.ietf.org/html/rfc5322#section-3.4.1 |
||
741 | // Comments and folding white space |
||
742 | // SHOULD NOT be used around the "@" in the addr-spec. |
||
743 | // |
||
744 | // http://tools.ietf.org/html/rfc2119 |
||
745 | // 4. SHOULD NOT This phrase, or the phrase "NOT RECOMMENDED" mean that |
||
746 | // there may exist valid reasons in particular circumstances when the |
||
747 | // particular behavior is acceptable or even useful, but the full |
||
748 | // implications should be understood and the case carefully weighed |
||
749 | // before implementing any behavior described with this label. |
||
750 | 1 | $this->returnStatus[] = Email::DEPREC_CFWS_NEAR_AT; |
|
751 | } |
||
752 | |||
753 | // Clear everything down for the domain parsing |
||
754 | 129 | $this->context = Email::COMPONENT_DOMAIN; // Where we are |
|
755 | 129 | $this->contextStack = array($this->context); // Where we have been |
|
756 | 129 | $this->elementCount = 0; |
|
757 | 129 | $this->elementLen = 0; |
|
758 | 129 | $this->endOrDie = false; // CFWS can only appear at the end of the element |
|
759 | |||
760 | 129 | break; |
|
761 | |||
762 | // atext |
||
763 | default: |
||
764 | // http://tools.ietf.org/html/rfc5322#section-3.2.3 |
||
765 | // atext = ALPHA / DIGIT / ; Printable US-ASCII |
||
766 | // "!" / "#" / ; characters not including |
||
767 | // "$" / "%" / ; specials. Used for atoms. |
||
768 | // "&" / "'" / |
||
769 | // "*" / "+" / |
||
770 | // "-" / "/" / |
||
771 | // " = " / "?" / |
||
772 | // "^" / "_" / |
||
773 | // "`" / "{" / |
||
774 | // "|" / "}" / |
||
775 | // "~" |
||
776 | 123 | if ($this->endOrDie) { |
|
777 | // We have encountered atext where it is no longer valid |
||
778 | 2 | switch ($this->contextPrior) { |
|
779 | 2 | case Email::CONTEXT_COMMENT: |
|
780 | 1 | case Email::CONTEXT_FWS: |
|
781 | 1 | $this->returnStatus[] = Email::ERR_ATEXT_AFTER_CFWS; |
|
782 | 1 | break; |
|
783 | 1 | case Email::CONTEXT_QUOTEDSTRING: |
|
784 | 1 | $this->returnStatus[] = Email::ERR_ATEXT_AFTER_QS; |
|
785 | 1 | break; |
|
786 | default: |
||
787 | 2 | throw new Exception("More atext found where none is allowed, but unrecognised prior context: {$this->contextPrior}"); |
|
788 | } |
||
789 | } else { |
||
790 | 122 | $this->contextPrior = $this->context; |
|
791 | 122 | $ord = ord($this->token); |
|
792 | |||
793 | 122 | if (($ord < 33) || ($ord > 126) || ($ord === 10) || (!is_bool(strpos(Email::STRING_SPECIALS, $this->token)))) { |
|
794 | // Fatal error |
||
795 | 4 | $this->returnStatus[] = Email::ERR_EXPECTING_ATEXT; |
|
796 | } |
||
797 | |||
798 | 122 | $this->parseData[Email::COMPONENT_LOCALPART] .= $this->token; |
|
799 | 122 | $this->atomList[Email::COMPONENT_LOCALPART][$this->elementCount] .= $this->token; |
|
800 | 122 | $this->elementLen++; |
|
801 | } |
||
802 | } |
||
803 | 164 | } |
|
804 | |||
805 | /** |
||
806 | * |
||
807 | * Parse for the domain component. |
||
808 | * |
||
809 | * @return null |
||
810 | * |
||
811 | */ |
||
812 | 119 | protected function parseComponentDomain() |
|
813 | { |
||
814 | // http://tools.ietf.org/html/rfc5322#section-3.4.1 |
||
815 | // domain = dot-atom / domain-literal / obs-domain |
||
816 | // |
||
817 | // dot-atom = [CFWS] dot-atom-text [CFWS] |
||
818 | // |
||
819 | // dot-atom-text = 1*atext *("." 1*atext) |
||
820 | // |
||
821 | // domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS] |
||
822 | // |
||
823 | // dtext = %d33-90 / ; Printable US-ASCII |
||
824 | // %d94-126 / ; characters not including |
||
825 | // obs-dtext ; "[", "]", or "\" |
||
826 | // |
||
827 | // obs-domain = atom *("." atom) |
||
828 | // |
||
829 | // atom = [CFWS] 1*atext [CFWS] |
||
830 | |||
831 | // http://tools.ietf.org/html/rfc5321#section-4.1.2 |
||
832 | // Mailbox = Local-part "@" ( Domain / address-literal ) |
||
833 | // |
||
834 | // Domain = sub-domain *("." sub-domain) |
||
835 | // |
||
836 | // address-literal = "[" ( IPv4-address-literal / |
||
837 | // IPv6-address-literal / |
||
838 | // General-address-literal ) "]" |
||
839 | // ; See Section 4.1.3 |
||
840 | |||
841 | // http://tools.ietf.org/html/rfc5322#section-3.4.1 |
||
842 | // Note: A liberal syntax for the domain portion of addr-spec is |
||
843 | // given here. However, the domain portion contains addressing |
||
844 | // information specified by and used in other protocols (e.g., |
||
845 | // [RFC1034], [RFC1035], [RFC1123], [RFC5321]). It is therefore |
||
846 | // incumbent upon implementations to conform to the syntax of |
||
847 | // addresses for the context in which they are used. |
||
848 | // is_email() author's note: it's not clear how to interpret this in |
||
849 | // the context of a general email address validator. The conclusion I |
||
850 | // have reached is this: "addressing information" must comply with |
||
851 | // RFC 5321 (and in turn RFC 1035), anything that is "semantically |
||
852 | // invisible" must comply only with RFC 5322. |
||
853 | 119 | switch ($this->token) { |
|
854 | |||
855 | // Comment |
||
856 | 119 | case Email::STRING_OPENPARENTHESIS: |
|
857 | 7 | if ($this->elementLen === 0) { |
|
858 | // Comments at the start of the domain are deprecated in the text |
||
859 | // Comments at the start of a subdomain are obs-domain |
||
860 | // (http://tools.ietf.org/html/rfc5322#section-3.4.1) |
||
861 | 3 | $this->returnStatus[] = ($this->elementCount === 0) ? Email::DEPREC_CFWS_NEAR_AT : Email::DEPREC_COMMENT; |
|
862 | } else { |
||
863 | 4 | $this->returnStatus[] = Email::CFWS_COMMENT; |
|
864 | // We can't start a comment in the middle of an element, so this better be the end |
||
865 | 4 | $this->endOrDie = true; |
|
866 | } |
||
867 | |||
868 | 7 | $this->contextStack[] = $this->context; |
|
869 | 7 | $this->context = Email::CONTEXT_COMMENT; |
|
870 | 7 | break; |
|
871 | |||
872 | // Next dot-atom element |
||
873 | 118 | case Email::STRING_DOT: |
|
874 | 76 | if ($this->elementLen === 0) { |
|
875 | // Another dot, already? |
||
876 | // Fatal error |
||
877 | $this->returnStatus[] = ($this->elementCount === 0) ? Email::ERR_DOT_START : Email::ERR_CONSECUTIVEDOTS; |
||
878 | 76 | } elseif ($this->hyphenFlag) { |
|
879 | // Previous subdomain ended in a hyphen |
||
880 | 1 | $this->returnStatus[] = Email::ERR_DOMAINHYPHENEND; |
|
881 | } else { |
||
882 | // Fatal error |
||
883 | // |
||
884 | // Nowhere in RFC 5321 does it say explicitly that the |
||
885 | // domain part of a Mailbox must be a valid domain according |
||
886 | // to the DNS standards set out in RFC 1035, but this *is* |
||
887 | // implied in several places. For instance, wherever the idea |
||
888 | // of host routing is discussed the RFC says that the domain |
||
889 | // must be looked up in the DNS. This would be nonsense unless |
||
890 | // the domain was designed to be a valid DNS domain. Hence we |
||
891 | // must conclude that the RFC 1035 restriction on label length |
||
892 | // also applies to RFC 5321 domains. |
||
893 | // |
||
894 | // http://tools.ietf.org/html/rfc1035#section-2.3.4 |
||
895 | // labels 63 octets or less |
||
896 | 75 | if ($this->elementLen > 63) { |
|
897 | $this->returnStatus[] = Email::RFC5322_LABEL_TOOLONG; |
||
898 | } |
||
899 | } |
||
900 | |||
901 | // CFWS is OK again now we're at the beginning of an element (although it may be obsolete CFWS) |
||
902 | 76 | $this->endOrDie = false; |
|
903 | 76 | $this->elementLen = 0; |
|
904 | 76 | $this->elementCount++; |
|
905 | 76 | $this->atomList[Email::COMPONENT_DOMAIN][$this->elementCount] = ''; |
|
906 | 76 | $this->parseData[Email::COMPONENT_DOMAIN] .= $this->token; |
|
907 | |||
908 | 76 | break; |
|
909 | |||
910 | // Domain literal |
||
911 | 118 | case Email::STRING_OPENSQBRACKET: |
|
912 | 37 | if ($this->parseData[Email::COMPONENT_DOMAIN] === '') { |
|
913 | // Domain literal must be the only component |
||
914 | 36 | $this->endOrDie = true; |
|
915 | 36 | $this->elementLen++; |
|
916 | 36 | $this->contextStack[] = $this->context; |
|
917 | 36 | $this->context = Email::COMPONENT_LITERAL; |
|
918 | 36 | $this->parseData[Email::COMPONENT_DOMAIN] .= $this->token; |
|
919 | 36 | $this->atomList[Email::COMPONENT_DOMAIN][$this->elementCount] .= $this->token; |
|
920 | 36 | $this->parseData[Email::COMPONENT_LITERAL] = ''; |
|
921 | } else { |
||
922 | // Fatal error |
||
923 | 1 | $this->returnStatus[] = Email::ERR_EXPECTING_ATEXT; |
|
924 | } |
||
925 | |||
926 | 37 | break; |
|
927 | |||
928 | // Folding White Space |
||
929 | 84 | case Email::STRING_CR: |
|
930 | 84 | case Email::STRING_SP: |
|
931 | 83 | case Email::STRING_HTAB: |
|
932 | 13 | if (($this->token === Email::STRING_CR) && ((++$this->pos === $this->rawLength) || ($this->email[$this->pos] !== Email::STRING_LF))) { |
|
933 | // Fatal error |
||
934 | 1 | $this->returnStatus[] = Email::ERR_CR_NO_LF; |
|
935 | 1 | break; |
|
936 | } |
||
937 | |||
938 | 12 | if ($this->elementLen === 0) { |
|
939 | 1 | $this->returnStatus[] = ($this->elementCount === 0) ? Email::DEPREC_CFWS_NEAR_AT : Email::DEPREC_FWS; |
|
940 | } else { |
||
941 | 12 | $this->returnStatus[] = Email::CFWS_FWS; |
|
942 | // We can't start FWS in the middle of an element, so this better be the end |
||
943 | 12 | $this->endOrDie = true; |
|
944 | } |
||
945 | |||
946 | 12 | $this->contextStack[] = $this->context; |
|
947 | 12 | $this->context = Email::CONTEXT_FWS; |
|
948 | 12 | $this->tokenPrior = $this->token; |
|
949 | 12 | break; |
|
950 | |||
951 | // atext |
||
952 | default: |
||
953 | // RFC 5322 allows any atext... |
||
954 | // http://tools.ietf.org/html/rfc5322#section-3.2.3 |
||
955 | // atext = ALPHA / DIGIT / ; Printable US-ASCII |
||
956 | // "!" / "#" / ; characters not including |
||
957 | // "$" / "%" / ; specials. Used for atoms. |
||
958 | // "&" / "'" / |
||
959 | // "*" / "+" / |
||
960 | // "-" / "/" / |
||
961 | // " = " / "?" / |
||
962 | // "^" / "_" / |
||
963 | // "`" / "{" / |
||
964 | // "|" / "}" / |
||
965 | // "~" |
||
966 | |||
967 | // But RFC 5321 only allows letter-digit-hyphen to comply with DNS rules (RFCs 1034 & 1123) |
||
968 | // http://tools.ietf.org/html/rfc5321#section-4.1.2 |
||
969 | // sub-domain = Let-dig [Ldh-str] |
||
970 | // |
||
971 | // Let-dig = ALPHA / DIGIT |
||
972 | // |
||
973 | // Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig |
||
974 | // |
||
975 | 83 | if ($this->endOrDie) { |
|
976 | // We have encountered atext where it is no longer valid |
||
977 | 1 | switch ($this->contextPrior) { |
|
978 | 1 | case Email::CONTEXT_COMMENT: |
|
979 | 1 | case Email::CONTEXT_FWS: |
|
980 | $this->returnStatus[] = Email::ERR_ATEXT_AFTER_CFWS; |
||
981 | break; |
||
982 | 1 | case Email::COMPONENT_LITERAL: |
|
983 | 1 | $this->returnStatus[] = Email::ERR_ATEXT_AFTER_DOMLIT; |
|
984 | 1 | break; |
|
985 | default: |
||
986 | throw new Exception("More atext found where none is allowed, but unrecognised prior context: {$this->contextPrior}"); |
||
987 | } |
||
988 | } |
||
989 | |||
990 | 83 | $ord = ord($this->token); |
|
991 | |||
992 | // Assume this token isn't a hyphen unless we discover it is |
||
993 | 83 | $this->hyphenFlag = false; |
|
994 | |||
995 | 83 | if (($ord < 33) || ($ord > 126) || (!is_bool(strpos(Email::STRING_SPECIALS, $this->token)))) { |
|
996 | // Fatal error |
||
997 | 3 | $this->returnStatus[] = Email::ERR_EXPECTING_ATEXT; |
|
998 | 81 | } elseif ($this->token === Email::STRING_HYPHEN) { |
|
999 | 9 | if ($this->elementLen === 0) { |
|
1000 | // Hyphens can't be at the beginning of a subdomain |
||
1001 | // Fatal error |
||
1002 | 1 | $this->returnStatus[] = Email::ERR_DOMAINHYPHENSTART; |
|
1003 | } |
||
1004 | 9 | $this->hyphenFlag = true; |
|
1005 | 79 | } elseif (!(($ord > 47 && $ord < 58) || ($ord > 64 && $ord < 91) || ($ord > 96 && $ord < 123))) { |
|
1006 | // Not an RFC 5321 subdomain, but still OK by RFC 5322 |
||
1007 | 1 | $this->returnStatus[] = Email::RFC5322_DOMAIN; |
|
1008 | } |
||
1009 | |||
1010 | 83 | $this->parseData[Email::COMPONENT_DOMAIN] .= $this->token; |
|
1011 | 83 | $this->atomList[Email::COMPONENT_DOMAIN][$this->elementCount] .= $this->token; |
|
1012 | 83 | $this->elementLen++; |
|
1013 | } |
||
1014 | 119 | } |
|
1015 | |||
1016 | /** |
||
1017 | * |
||
1018 | * Parse for a literal component. |
||
1019 | * |
||
1020 | * @return null |
||
1021 | * |
||
1022 | */ |
||
1023 | 36 | protected function parseComponentLiteral() |
|
1024 | { |
||
1025 | // http://tools.ietf.org/html/rfc5322#section-3.4.1 |
||
1026 | // domain-literal = [CFWS] "[" *([FWS] dtext) [FWS] "]" [CFWS] |
||
1027 | // |
||
1028 | // dtext = %d33-90 / ; Printable US-ASCII |
||
1029 | // %d94-126 / ; characters not including |
||
1030 | // obs-dtext ; "[", "]", or "\" |
||
1031 | // |
||
1032 | // obs-dtext = obs-NO-WS-CTL / quoted-pair |
||
1033 | 36 | switch ($this->token) { |
|
1034 | |||
1035 | // End of domain literal |
||
1036 | 36 | case Email::STRING_CLOSESQBRACKET: |
|
1037 | 32 | if ((int) max($this->returnStatus) < Email::DEPREC) { |
|
1038 | // Could be a valid RFC 5321 address literal, so let's check |
||
1039 | |||
1040 | // http://tools.ietf.org/html/rfc5321#section-4.1.2 |
||
1041 | // address-literal = "[" ( IPv4-address-literal / |
||
1042 | // IPv6-address-literal / |
||
1043 | // General-address-literal ) "]" |
||
1044 | // ; See Section 4.1.3 |
||
1045 | // |
||
1046 | // http://tools.ietf.org/html/rfc5321#section-4.1.3 |
||
1047 | // IPv4-address-literal = Snum 3("." Snum) |
||
1048 | // |
||
1049 | // IPv6-address-literal = "IPv6:" IPv6-addr |
||
1050 | // |
||
1051 | // General-address-literal = Standardized-tag ":" 1*dcontent |
||
1052 | // |
||
1053 | // Standardized-tag = Ldh-str |
||
1054 | // ; Standardized-tag MUST be specified in a |
||
1055 | // ; Standards-Track RFC and registered with IANA |
||
1056 | // |
||
1057 | // dcontent = %d33-90 / ; Printable US-ASCII |
||
1058 | // %d94-126 ; excl. "[", "\", "]" |
||
1059 | // |
||
1060 | // Snum = 1*3DIGIT |
||
1061 | // ; representing a decimal integer |
||
1062 | // ; value in the range 0 through 255 |
||
1063 | // |
||
1064 | // IPv6-addr = IPv6-full / IPv6-comp / IPv6v4-full / IPv6v4-comp |
||
1065 | // |
||
1066 | // IPv6-hex = 1*4HEXDIG |
||
1067 | // |
||
1068 | // IPv6-full = IPv6-hex 7(":" IPv6-hex) |
||
1069 | // |
||
1070 | // IPv6-comp = [IPv6-hex *5(":" IPv6-hex)] "::" |
||
1071 | // [IPv6-hex *5(":" IPv6-hex)] |
||
1072 | // ; The "::" represents at least 2 16-bit groups of |
||
1073 | // ; zeros. No more than 6 groups in addition to the |
||
1074 | // ; "::" may be present. |
||
1075 | // |
||
1076 | // IPv6v4-full = IPv6-hex 5(":" IPv6-hex) ":" IPv4-address-literal |
||
1077 | // |
||
1078 | // IPv6v4-comp = [IPv6-hex *3(":" IPv6-hex)] "::" |
||
1079 | // [IPv6-hex *3(":" IPv6-hex) ":"] |
||
1080 | // IPv4-address-literal |
||
1081 | // ; The "::" represents at least 2 16-bit groups of |
||
1082 | // ; zeros. No more than 4 groups in addition to the |
||
1083 | // ; "::" and IPv4-address-literal may be present. |
||
1084 | // |
||
1085 | // is_email() author's note: We can't use ip2long() to validate |
||
1086 | // IPv4 addresses because it accepts abbreviated addresses |
||
1087 | // (xxx.xxx.xxx), expanding the last group to complete the address. |
||
1088 | // filter_var() validates IPv6 address inconsistently (up to PHP 5.3.3 |
||
1089 | // at least) -- see http://bugs.php.net/bug.php?id = 53236 for example |
||
1090 | 29 | $max_groups = 8; |
|
1091 | 29 | $matchesIP = array(); |
|
1092 | 29 | $index = false; |
|
1093 | 29 | $addressliteral = $this->parseData[Email::COMPONENT_LITERAL]; |
|
1094 | |||
1095 | // Extract IPv4 part from the end of the address-literal (if there is one) |
||
1096 | 29 | if (preg_match('/\\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]?)$/', $addressliteral, $matchesIP) > 0) { |
|
1097 | 10 | $index = strrpos($addressliteral, $matchesIP[0]); |
|
1098 | 10 | if ($index !== 0) { |
|
1099 | // Convert IPv4 part to IPv6 format for further testing |
||
1100 | 8 | $addressliteral = substr($addressliteral, 0, $index) . '0:0'; |
|
1101 | } |
||
1102 | } |
||
1103 | |||
1104 | 29 | if ($index === 0) { |
|
1105 | // Nothing there except a valid IPv4 address, so... |
||
1106 | 2 | $this->returnStatus[] = Email::RFC5321_ADDRESSLITERAL; |
|
1107 | 27 | } elseif (strncasecmp($addressliteral, Email::STRING_IPV6TAG, 5) !== 0) { |
|
1108 | 8 | $this->returnStatus[] = Email::RFC5322_DOMAINLITERAL; |
|
1109 | } else { |
||
1110 | 19 | $IPv6 = substr($addressliteral, 5); |
|
1111 | // Revision 2.7: Daniel Marschall's new IPv6 testing strategy |
||
1112 | 19 | $matchesIP = explode(Email::STRING_COLON, $IPv6); |
|
1113 | 19 | $groupCount = count($matchesIP); |
|
1114 | 19 | $index = strpos($IPv6, Email::STRING_DOUBLECOLON); |
|
1115 | |||
1116 | 19 | if ($index === false) { |
|
1117 | // We need exactly the right number of groups |
||
1118 | 9 | if ($groupCount !== $max_groups) { |
|
1119 | 9 | $this->returnStatus[] = Email::RFC5322_IPV6_GRPCOUNT; |
|
1120 | } |
||
1121 | } else { |
||
1122 | 10 | if ($index !== strrpos($IPv6, Email::STRING_DOUBLECOLON)) { |
|
1123 | 2 | $this->returnStatus[] = Email::RFC5322_IPV6_2X2XCOLON; |
|
1124 | } else { |
||
1125 | 8 | if ($index === 0 || $index === (strlen($IPv6) - 2)) { |
|
1126 | // RFC 4291 allows :: at the start or end of an address with 7 other groups in addition |
||
1127 | 2 | $max_groups++; |
|
1128 | } |
||
1129 | |||
1130 | 8 | if ($groupCount > $max_groups) { |
|
1131 | 2 | $this->returnStatus[] = Email::RFC5322_IPV6_MAXGRPS; |
|
1132 | 6 | } elseif ($groupCount === $max_groups) { |
|
1133 | // Eliding a single "::" |
||
1134 | 1 | $this->returnStatus[] = Email::RFC5321_IPV6DEPRECATED; |
|
1135 | } |
||
1136 | } |
||
1137 | } |
||
1138 | |||
1139 | // Revision 2.7: Daniel Marschall's new IPv6 testing strategy |
||
1140 | 19 | if ((substr($IPv6, 0, 1) === Email::STRING_COLON) && (substr($IPv6, 1, 1) !== Email::STRING_COLON)) { |
|
1141 | // Address starts with a single colon |
||
1142 | 2 | $this->returnStatus[] = Email::RFC5322_IPV6_COLONSTRT; |
|
1143 | 17 | } elseif ((substr($IPv6, -1) === Email::STRING_COLON) && (substr($IPv6, -2, 1) !== Email::STRING_COLON)) { |
|
1144 | // Address ends with a single colon |
||
1145 | 1 | $this->returnStatus[] = Email::RFC5322_IPV6_COLONEND; |
|
1146 | 16 | } elseif (count(preg_grep('/^[0-9A-Fa-f]{0,4}$/', $matchesIP, PREG_GREP_INVERT)) !== 0) { |
|
1147 | // Check for unmatched characters |
||
1148 | 1 | $this->returnStatus[] = Email::RFC5322_IPV6_BADCHAR; |
|
1149 | } else { |
||
1150 | 29 | $this->returnStatus[] = Email::RFC5321_ADDRESSLITERAL; |
|
1151 | } |
||
1152 | } |
||
1153 | } else { |
||
1154 | 3 | $this->returnStatus[] = Email::RFC5322_DOMAINLITERAL; |
|
1155 | } |
||
1156 | |||
1157 | 32 | $this->parseData[Email::COMPONENT_DOMAIN] .= $this->token; |
|
1158 | 32 | $this->atomList[Email::COMPONENT_DOMAIN][$this->elementCount] .= $this->token; |
|
1159 | 32 | $this->elementLen++; |
|
1160 | 32 | $this->contextPrior = $this->context; |
|
1161 | 32 | $this->context = (int) array_pop($this->contextStack); |
|
1162 | 32 | break; |
|
1163 | |||
1164 | 36 | case Email::STRING_BACKSLASH: |
|
1165 | 5 | $this->returnStatus[] = Email::RFC5322_DOMLIT_OBSDTEXT; |
|
1166 | 5 | $this->contextStack[] = $this->context; |
|
1167 | 5 | $this->context = Email::CONTEXT_QUOTEDPAIR; |
|
1168 | 5 | break; |
|
1169 | |||
1170 | // Folding White Space |
||
1171 | 36 | case Email::STRING_CR: |
|
1172 | 36 | case Email::STRING_SP: |
|
1173 | 36 | case Email::STRING_HTAB: |
|
1174 | 1 | if (($this->token === Email::STRING_CR) && ((++$this->pos === $this->rawLength) || ($this->email[$this->pos] !== Email::STRING_LF))) { |
|
1175 | // Fatal error |
||
1176 | $this->returnStatus[] = Email::ERR_CR_NO_LF; |
||
1177 | break; |
||
1178 | } |
||
1179 | |||
1180 | 1 | $this->returnStatus[] = Email::CFWS_FWS; |
|
1181 | |||
1182 | 1 | $this->contextStack[] = $this->context; |
|
1183 | 1 | $this->context = Email::CONTEXT_FWS; |
|
1184 | 1 | $this->tokenPrior = $this->token; |
|
1185 | 1 | break; |
|
1186 | |||
1187 | // dtext |
||
1188 | default: |
||
1189 | // http://tools.ietf.org/html/rfc5322#section-3.4.1 |
||
1190 | // dtext = %d33-90 / ; Printable US-ASCII |
||
1191 | // %d94-126 / ; characters not including |
||
1192 | // obs-dtext ; "[", "]", or "\" |
||
1193 | // |
||
1194 | // obs-dtext = obs-NO-WS-CTL / quoted-pair |
||
1195 | // |
||
1196 | // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control |
||
1197 | // %d11 / ; characters that do not |
||
1198 | // %d12 / ; include the carriage |
||
1199 | // %d14-31 / ; return, line feed, and |
||
1200 | // %d127 ; white space characters |
||
1201 | 36 | $ord = ord($this->token); |
|
1202 | |||
1203 | // CR, LF, SP & HTAB have already been parsed above |
||
1204 | 36 | if (($ord > 127) || ($ord === 0) || ($this->token === Email::STRING_OPENSQBRACKET)) { |
|
1205 | 1 | $this->returnStatus[] = Email::ERR_EXPECTING_DTEXT; // Fatal error |
|
1206 | 1 | break; |
|
1207 | 36 | } elseif (($ord < 33) || ($ord === 127)) { |
|
1208 | $this->returnStatus[] = Email::RFC5322_DOMLIT_OBSDTEXT; |
||
1209 | } |
||
1210 | |||
1211 | 36 | $this->parseData[Email::COMPONENT_LITERAL] .= $this->token; |
|
1212 | 36 | $this->parseData[Email::COMPONENT_DOMAIN] .= $this->token; |
|
1213 | 36 | $this->atomList[Email::COMPONENT_DOMAIN][$this->elementCount] .= $this->token; |
|
1214 | 36 | $this->elementLen++; |
|
1215 | } |
||
1216 | 36 | } |
|
1217 | |||
1218 | /** |
||
1219 | * |
||
1220 | * Parse for a quoted-string context. |
||
1221 | * |
||
1222 | * @return null |
||
1223 | * |
||
1224 | */ |
||
1225 | 27 | protected function parseContextQuotedString() |
|
1226 | { |
||
1227 | // http://tools.ietf.org/html/rfc5322#section-3.2.4 |
||
1228 | // quoted-string = [CFWS] |
||
1229 | // DQUOTE *([FWS] qcontent) [FWS] DQUOTE |
||
1230 | // [CFWS] |
||
1231 | // |
||
1232 | // qcontent = qtext / quoted-pair |
||
1233 | 27 | switch ($this->token) { |
|
1234 | |||
1235 | // Quoted pair |
||
1236 | 27 | case Email::STRING_BACKSLASH: |
|
1237 | 12 | $this->contextStack[] = $this->context; |
|
1238 | 12 | $this->context = Email::CONTEXT_QUOTEDPAIR; |
|
1239 | 12 | break; |
|
1240 | |||
1241 | // Folding White Space |
||
1242 | // Inside a quoted string, spaces are allowed as regular characters. |
||
1243 | // It's only FWS if we include HTAB or CRLF |
||
1244 | 27 | case Email::STRING_CR: |
|
1245 | 26 | case Email::STRING_HTAB: |
|
1246 | 1 | if (($this->token === Email::STRING_CR) && ((++$this->pos === $this->rawLength) || ($this->email[$this->pos] !== Email::STRING_LF))) { |
|
1247 | // Fatal error |
||
1248 | 1 | $this->returnStatus[] = Email::ERR_CR_NO_LF; |
|
1249 | 1 | break; |
|
1250 | } |
||
1251 | |||
1252 | // http://tools.ietf.org/html/rfc5322#section-3.2.2 |
||
1253 | // Runs of FWS, comment, or CFWS that occur between lexical tokens in a |
||
1254 | // structured header field are semantically interpreted as a single |
||
1255 | // space character. |
||
1256 | |||
1257 | // http://tools.ietf.org/html/rfc5322#section-3.2.4 |
||
1258 | // the CRLF in any FWS/CFWS that appears within the quoted-string [is] |
||
1259 | // semantically "invisible" and therefore not part of the quoted-string |
||
1260 | $this->parseData[Email::COMPONENT_LOCALPART] .= Email::STRING_SP; |
||
1261 | $this->atomList[Email::COMPONENT_LOCALPART][$this->elementCount] .= Email::STRING_SP; |
||
1262 | $this->elementLen++; |
||
1263 | |||
1264 | $this->returnStatus[] = Email::CFWS_FWS; |
||
1265 | $this->contextStack[] = $this->context; |
||
1266 | $this->context = Email::CONTEXT_FWS; |
||
1267 | $this->tokenPrior = $this->token; |
||
1268 | break; |
||
1269 | |||
1270 | // End of quoted string |
||
1271 | 26 | case Email::STRING_DQUOTE: |
|
1272 | 18 | $this->parseData[Email::COMPONENT_LOCALPART] .= $this->token; |
|
1273 | 18 | $this->atomList[Email::COMPONENT_LOCALPART][$this->elementCount] .= $this->token; |
|
1274 | 18 | $this->elementLen++; |
|
1275 | 18 | $this->contextPrior = $this->context; |
|
1276 | 18 | $this->context = (int) array_pop($this->contextStack); |
|
1277 | 18 | break; |
|
1278 | |||
1279 | // qtext |
||
1280 | default: |
||
1281 | // http://tools.ietf.org/html/rfc5322#section-3.2.4 |
||
1282 | // qtext = %d33 / ; Printable US-ASCII |
||
1283 | // %d35-91 / ; characters not including |
||
1284 | // %d93-126 / ; "\" or the quote character |
||
1285 | // obs-qtext |
||
1286 | // |
||
1287 | // obs-qtext = obs-NO-WS-CTL |
||
1288 | // |
||
1289 | // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control |
||
1290 | // %d11 / ; characters that do not |
||
1291 | // %d12 / ; include the carriage |
||
1292 | // %d14-31 / ; return, line feed, and |
||
1293 | // %d127 ; white space characters |
||
1294 | 18 | $ord = ord($this->token); |
|
1295 | |||
1296 | 18 | if (($ord > 127) || ($ord === 0) || ($ord === 10)) { |
|
1297 | // Fatal error |
||
1298 | 2 | $this->returnStatus[] = Email::ERR_EXPECTING_QTEXT; |
|
1299 | 17 | } elseif (($ord < 32) || ($ord === 127)) { |
|
1300 | 2 | $this->returnStatus[] = Email::DEPREC_QTEXT; |
|
1301 | } |
||
1302 | |||
1303 | 18 | $this->parseData[Email::COMPONENT_LOCALPART] .= $this->token; |
|
1304 | 18 | $this->atomList[Email::COMPONENT_LOCALPART][$this->elementCount] .= $this->token; |
|
1305 | 18 | $this->elementLen++; |
|
1306 | } |
||
1307 | |||
1308 | // http://tools.ietf.org/html/rfc5322#section-3.4.1 |
||
1309 | // If the string can be represented as a dot-atom (that is, it contains |
||
1310 | // no characters other than atext characters or "." surrounded by atext |
||
1311 | // characters), then the dot-atom form SHOULD be used and the quoted- |
||
1312 | // string form SHOULD NOT be used. |
||
1313 | // |
||
1314 | // TODO |
||
1315 | // |
||
1316 | 27 | } |
|
1317 | |||
1318 | /** |
||
1319 | * |
||
1320 | * Parse for a quoted-pair context. |
||
1321 | * |
||
1322 | * @return null |
||
1323 | * |
||
1324 | */ |
||
1325 | 18 | protected function parseContextQuotedPair() |
|
1326 | { |
||
1327 | // http://tools.ietf.org/html/rfc5322#section-3.2.1 |
||
1328 | // quoted-pair = ("\" (VCHAR / WSP)) / obs-qp |
||
1329 | // |
||
1330 | // VCHAR = %d33-126 ; visible (printing) characters |
||
1331 | // WSP = SP / HTAB ; white space |
||
1332 | // |
||
1333 | // obs-qp = "\" (%d0 / obs-NO-WS-CTL / LF / CR) |
||
1334 | // |
||
1335 | // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control |
||
1336 | // %d11 / ; characters that do not |
||
1337 | // %d12 / ; include the carriage |
||
1338 | // %d14-31 / ; return, line feed, and |
||
1339 | // %d127 ; white space characters |
||
1340 | // |
||
1341 | // i.e. obs-qp = "\" (%d0-8, %d10-31 / %d127) |
||
1342 | 18 | $ord = ord($this->token); |
|
1343 | |||
1344 | 18 | if ($ord > 127) { |
|
1345 | 2 | $this->returnStatus[] = Email::ERR_EXPECTING_QPAIR; |
|
1346 | 16 | } elseif ((($ord < 31) && ($ord !== 9)) || ($ord === 127)) { |
|
1347 | // SP & HTAB are allowed |
||
1348 | // Fatal error |
||
1349 | 4 | $this->returnStatus[] = Email::DEPREC_QP; |
|
1350 | } |
||
1351 | |||
1352 | // At this point we know where this qpair occurred so |
||
1353 | // we could check to see if the character actually |
||
1354 | // needed to be quoted at all. |
||
1355 | // http://tools.ietf.org/html/rfc5321#section-4.1.2 |
||
1356 | // the sending system SHOULD transmit the |
||
1357 | // form that uses the minimum quoting possible. |
||
1358 | // |
||
1359 | // TODO: check whether the character needs to be quoted (escaped) in this context |
||
1360 | // |
||
1361 | 18 | $this->contextPrior = $this->context; |
|
1362 | 18 | $this->context = (int) array_pop($this->contextStack); // End of qpair |
|
1363 | 18 | $this->token = Email::STRING_BACKSLASH . $this->token; |
|
1364 | |||
1365 | 18 | switch ($this->context) { |
|
1366 | 18 | case Email::CONTEXT_COMMENT: |
|
1367 | 2 | break; |
|
1368 | 16 | case Email::CONTEXT_QUOTEDSTRING: |
|
1369 | 12 | $this->parseData[Email::COMPONENT_LOCALPART] .= $this->token; |
|
1370 | 12 | $this->atomList[Email::COMPONENT_LOCALPART][$this->elementCount] .= $this->token; |
|
1371 | // The maximum sizes specified by RFC 5321 are octet counts, so we must include the backslash |
||
1372 | 12 | $this->elementLen += 2; |
|
1373 | 12 | break; |
|
1374 | 4 | case Email::COMPONENT_LITERAL: |
|
1375 | 4 | $this->parseData[Email::COMPONENT_DOMAIN] .= $this->token; |
|
1376 | 4 | $this->atomList[Email::COMPONENT_DOMAIN][$this->elementCount] .= $this->token; |
|
1377 | // The maximum sizes specified by RFC 5321 are octet counts, so we must include the backslash |
||
1378 | 4 | $this->elementLen += 2; |
|
1379 | 4 | break; |
|
1380 | default: |
||
1381 | throw new Exception("Quoted pair logic invoked in an invalid context: {$this->context}"); |
||
1382 | } |
||
1383 | 18 | } |
|
1384 | |||
1385 | /** |
||
1386 | * |
||
1387 | * Parse for a comment context. |
||
1388 | * |
||
1389 | * @return null |
||
1390 | * |
||
1391 | */ |
||
1392 | 20 | protected function parseContextComment() |
|
1393 | { |
||
1394 | // http://tools.ietf.org/html/rfc5322#section-3.2.2 |
||
1395 | // comment = "(" *([FWS] ccontent) [FWS] ")" |
||
1396 | // |
||
1397 | // ccontent = ctext / quoted-pair / comment |
||
1398 | 20 | switch ($this->token) { |
|
1399 | |||
1400 | // Nested comment |
||
1401 | 20 | case Email::STRING_OPENPARENTHESIS: |
|
1402 | // Nested comments are OK |
||
1403 | 2 | $this->contextStack[] = $this->context; |
|
1404 | 2 | $this->context = Email::CONTEXT_COMMENT; |
|
1405 | 2 | break; |
|
1406 | |||
1407 | // End of comment |
||
1408 | 20 | case Email::STRING_CLOSEPARENTHESIS: |
|
1409 | 12 | $this->contextPrior = $this->context; |
|
1410 | 12 | $this->context = (int) array_pop($this->contextStack); |
|
1411 | |||
1412 | // http://tools.ietf.org/html/rfc5322#section-3.2.2 |
||
1413 | // Runs of FWS, comment, or CFWS that occur between lexical tokens in a |
||
1414 | // structured header field are semantically interpreted as a single |
||
1415 | // space character. |
||
1416 | // |
||
1417 | // is_email() author's note: This *cannot* mean that we must add a |
||
1418 | // space to the address wherever CFWS appears. This would result in |
||
1419 | // any addr-spec that had CFWS outside a quoted string being invalid |
||
1420 | // for RFC 5321. |
||
1421 | // |
||
1422 | // if (($this->context === Email::COMPONENT_LOCALPART) || ($this->context === Email::COMPONENT_DOMAIN)) { |
||
1423 | // $this->parseData[$this->context] .= Email::STRING_SP; |
||
1424 | // $this->atomList[$this->context][$this->elementCount] .= Email::STRING_SP; |
||
1425 | // $this->elementLen++; |
||
1426 | // } |
||
1427 | |||
1428 | 12 | break; |
|
1429 | |||
1430 | // Quoted pair |
||
1431 | 20 | case Email::STRING_BACKSLASH: |
|
1432 | 3 | $this->contextStack[] = $this->context; |
|
1433 | 3 | $this->context = Email::CONTEXT_QUOTEDPAIR; |
|
1434 | 3 | break; |
|
1435 | |||
1436 | // Folding White Space |
||
1437 | 20 | case Email::STRING_CR: |
|
1438 | 18 | case Email::STRING_SP: |
|
1439 | 18 | case Email::STRING_HTAB: |
|
1440 | 2 | if (($this->token === Email::STRING_CR) && ((++$this->pos === $this->rawLength) || ($this->email[$this->pos] !== Email::STRING_LF))) { |
|
1441 | // Fatal error |
||
1442 | 2 | $this->returnStatus[] = Email::ERR_CR_NO_LF; |
|
1443 | 2 | break; |
|
1444 | } |
||
1445 | |||
1446 | $this->returnStatus[] = Email::CFWS_FWS; |
||
1447 | |||
1448 | $this->contextStack[] = $this->context; |
||
1449 | $this->context = Email::CONTEXT_FWS; |
||
1450 | $this->tokenPrior = $this->token; |
||
1451 | break; |
||
1452 | |||
1453 | // ctext |
||
1454 | default: |
||
1455 | // http://tools.ietf.org/html/rfc5322#section-3.2.3 |
||
1456 | // ctext = %d33-39 / ; Printable US-ASCII |
||
1457 | // %d42-91 / ; characters not including |
||
1458 | // %d93-126 / ; "(", ")", or "\" |
||
1459 | // obs-ctext |
||
1460 | // |
||
1461 | // obs-ctext = obs-NO-WS-CTL |
||
1462 | // |
||
1463 | // obs-NO-WS-CTL = %d1-8 / ; US-ASCII control |
||
1464 | // %d11 / ; characters that do not |
||
1465 | // %d12 / ; include the carriage |
||
1466 | // %d14-31 / ; return, line feed, and |
||
1467 | // %d127 ; white space characters |
||
1468 | 18 | $ord = ord($this->token); |
|
1469 | |||
1470 | 18 | if (($ord > 127) || ($ord === 0) || ($ord === 10)) { |
|
1471 | 1 | $this->returnStatus[] = Email::ERR_EXPECTING_CTEXT; // Fatal error |
|
1472 | 1 | break; |
|
1473 | 17 | } elseif (($ord < 32) || ($ord === 127)) { |
|
1474 | 2 | $this->returnStatus[] = Email::DEPREC_CTEXT; |
|
1475 | } |
||
1476 | } |
||
1477 | 20 | } |
|
1478 | |||
1479 | /** |
||
1480 | * |
||
1481 | * Parse for a folding-white-space context. |
||
1482 | * |
||
1483 | * @return null |
||
1484 | * |
||
1485 | */ |
||
1486 | 23 | protected function parseContextFws() |
|
1487 | { |
||
1488 | // http://tools.ietf.org/html/rfc5322#section-3.2.2 |
||
1489 | // FWS = ([*WSP CRLF] 1*WSP) / obs-FWS |
||
1490 | // ; Folding white space |
||
1491 | |||
1492 | // But note the erratum: |
||
1493 | // http://www.rfc-editor.org/errata_search.php?rfc = 5322&eid = 1908: |
||
1494 | // In the obsolete syntax, any amount of folding white space MAY be |
||
1495 | // inserted where the obs-FWS rule is allowed. This creates the |
||
1496 | // possibility of having two consecutive "folds" in a line, and |
||
1497 | // therefore the possibility that a line which makes up a folded header |
||
1498 | // field could be composed entirely of white space. |
||
1499 | // |
||
1500 | // obs-FWS = 1*([CRLF] WSP) |
||
1501 | 23 | if ($this->tokenPrior === Email::STRING_CR) { |
|
1502 | 16 | if ($this->token === Email::STRING_CR) { |
|
1503 | // Fatal error |
||
1504 | 4 | $this->returnStatus[] = Email::ERR_FWS_CRLF_X2; |
|
1505 | 4 | return; |
|
1506 | } |
||
1507 | |||
1508 | 12 | if (isset($this->crlfCount)) { |
|
1509 | 4 | if (++$this->crlfCount > 1) { |
|
1510 | 4 | $this->returnStatus[] = Email::DEPREC_FWS; |
|
1511 | } // Multiple folds = obsolete FWS |
||
1512 | } else { |
||
1513 | 12 | $this->crlfCount = 1; |
|
1514 | } |
||
1515 | } |
||
1516 | |||
1517 | 23 | switch ($this->token) { |
|
1518 | 23 | case Email::STRING_CR: |
|
1519 | 14 | if ((++$this->pos === $this->rawLength) || ($this->email[$this->pos] !== Email::STRING_LF)) { |
|
1520 | // Fatal error |
||
1521 | $this->returnStatus[] = Email::ERR_CR_NO_LF; |
||
1522 | } |
||
1523 | 14 | break; |
|
1524 | |||
1525 | 18 | case Email::STRING_SP: |
|
1526 | 13 | case Email::STRING_HTAB: |
|
1527 | 10 | break; |
|
1528 | |||
1529 | default: |
||
1530 | 13 | if ($this->tokenPrior === Email::STRING_CR) { |
|
1531 | // Fatal error |
||
1532 | 4 | $this->returnStatus[] = Email::ERR_FWS_CRLF_END; |
|
1533 | 4 | break; |
|
1534 | } |
||
1535 | |||
1536 | 9 | if (isset($this->crlfCount)) { |
|
1537 | 3 | unset($this->crlfCount); |
|
1538 | } |
||
1539 | |||
1540 | 9 | $this->contextPrior = $this->context; |
|
1541 | 9 | $this->context = (int) array_pop($this->contextStack); // End of FWS |
|
1542 | |||
1543 | // http://tools.ietf.org/html/rfc5322#section-3.2.2 |
||
1544 | // Runs of FWS, comment, or CFWS that occur between lexical tokens in a |
||
1545 | // structured header field are semantically interpreted as a single |
||
1546 | // space character. |
||
1547 | // |
||
1548 | // is_email() author's note: This *cannot* mean that we must add a |
||
1549 | // space to the address wherever CFWS appears. This would result in |
||
1550 | // any addr-spec that had CFWS outside a quoted string being invalid |
||
1551 | // for RFC 5321. |
||
1552 | // |
||
1553 | // if (($this->context === Email::COMPONENT_LOCALPART) || ($this->context === Email::COMPONENT_DOMAIN)) { |
||
1554 | // $this->parseData[$this->context] .= Email::STRING_SP; |
||
1555 | // $this->atomList[$this->context][$this->elementCount] .= Email::STRING_SP; |
||
1556 | // $this->elementLen++; |
||
1557 | // } |
||
1558 | |||
1559 | 9 | $this->pos--; // Look at this token again in the parent context |
|
1560 | } |
||
1561 | |||
1562 | 23 | $this->tokenPrior = $this->token; |
|
1563 | 23 | } |
|
1564 | |||
1565 | /** |
||
1566 | * |
||
1567 | * Final wrap-up parsing. |
||
1568 | * |
||
1569 | * @return null |
||
1570 | * |
||
1571 | */ |
||
1572 | 165 | protected function parseFinal() |
|
1573 | { |
||
1574 | // Some simple final tests |
||
1575 | 165 | if ((int) max($this->returnStatus) < Email::RFC5322) { |
|
1576 | 123 | if ($this->context === Email::CONTEXT_QUOTEDSTRING) { |
|
1577 | // Fatal error |
||
1578 | 4 | $this->returnStatus[] = Email::ERR_UNCLOSEDQUOTEDSTR; |
|
1579 | 119 | } elseif ($this->context === Email::CONTEXT_QUOTEDPAIR) { |
|
1580 | // Fatal error |
||
1581 | 2 | $this->returnStatus[] = Email::ERR_BACKSLASHEND; |
|
1582 | 117 | } elseif ($this->context === Email::CONTEXT_COMMENT) { |
|
1583 | // Fatal error |
||
1584 | 5 | $this->returnStatus[] = Email::ERR_UNCLOSEDCOMMENT; |
|
1585 | 112 | } elseif ($this->context === Email::COMPONENT_LITERAL) { |
|
1586 | // Fatal error |
||
1587 | 2 | $this->returnStatus[] = Email::ERR_UNCLOSEDDOMLIT; |
|
1588 | 110 | } elseif ($this->token === Email::STRING_CR) { |
|
1589 | // Fatal error |
||
1590 | 4 | $this->returnStatus[] = Email::ERR_FWS_CRLF_END; |
|
1591 | 106 | } elseif ($this->parseData[Email::COMPONENT_DOMAIN] === '') { |
|
1592 | // Fatal error |
||
1593 | 9 | $this->returnStatus[] = Email::ERR_NODOMAIN; |
|
1594 | 97 | } elseif ($this->elementLen === 0) { |
|
1595 | // Fatal error |
||
1596 | 1 | $this->returnStatus[] = Email::ERR_DOT_END; |
|
1597 | 96 | } elseif ($this->hyphenFlag) { |
|
1598 | // Fatal error |
||
1599 | 1 | $this->returnStatus[] = Email::ERR_DOMAINHYPHENEND; |
|
1600 | 95 | } elseif (strlen($this->parseData[Email::COMPONENT_DOMAIN]) > 255) { |
|
1601 | // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.2 |
||
1602 | // The maximum total length of a domain name or number is 255 octets. |
||
1603 | $this->returnStatus[] = Email::RFC5322_DOMAIN_TOOLONG; |
||
1604 | 95 | } elseif (strlen($this->parseData[Email::COMPONENT_LOCALPART] . Email::STRING_AT . $this->parseData[Email::COMPONENT_DOMAIN]) > 254) { |
|
1605 | // http://tools.ietf.org/html/rfc5321#section-4.1.2 |
||
1606 | // Forward-path = Path |
||
1607 | // |
||
1608 | // Path = "<" [ A-d-l ":" ] Mailbox ">" |
||
1609 | // |
||
1610 | // http://tools.ietf.org/html/rfc5321#section-4.5.3.1.3 |
||
1611 | // The maximum total length of a reverse-path or forward-path is 256 |
||
1612 | // octets (including the punctuation and element separators). |
||
1613 | // |
||
1614 | // Thus, even without (obsolete) routing information, the Mailbox can |
||
1615 | // only be 254 characters long. This is confirmed by this verified |
||
1616 | // erratum to RFC 3696: |
||
1617 | // |
||
1618 | // http://www.rfc-editor.org/errata_search.php?rfc = 3696&eid = 1690 |
||
1619 | // However, there is a restriction in RFC 2821 on the length of an |
||
1620 | // address in MAIL and RCPT commands of 254 characters. Since addresses |
||
1621 | // that do not fit in those fields are not normally useful, the upper |
||
1622 | // limit on address lengths should normally be considered to be 254. |
||
1623 | 2 | $this->returnStatus[] = Email::RFC5322_TOOLONG; |
|
1624 | 93 | } elseif ($this->elementLen > 63) { |
|
1625 | // http://tools.ietf.org/html/rfc1035#section-2.3.4 |
||
1626 | // labels 63 octets or less |
||
1627 | $this->returnStatus[] = Email::RFC5322_LABEL_TOOLONG; |
||
1628 | } |
||
1629 | } |
||
1630 | 165 | } |
|
1631 | |||
1632 | /** |
||
1633 | * |
||
1634 | * Make a DNS check on the MX record, if requested. |
||
1635 | * |
||
1636 | * @return null |
||
1637 | * |
||
1638 | */ |
||
1639 | 165 | protected function checkDns() |
|
1640 | { |
||
1641 | // Check DNS? |
||
1642 | 165 | if ($this->checkDns && ((int) max($this->returnStatus) < Email::DNSWARN) && function_exists('dns_get_record')) { |
|
1643 | // http://tools.ietf.org/html/rfc5321#section-2.3.5 |
||
1644 | // Names that can |
||
1645 | // be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed |
||
1646 | // in Section 5) are permitted, as are CNAME RRs whose targets can be |
||
1647 | // resolved, in turn, to MX or address RRs. |
||
1648 | // |
||
1649 | // http://tools.ietf.org/html/rfc5321#section-5.1 |
||
1650 | // The lookup first attempts to locate an MX record associated with the |
||
1651 | // name. If a CNAME record is found, the resulting name is processed as |
||
1652 | // if it were the initial name. ... If an empty list of MXs is returned, |
||
1653 | // the address is treated as if it was associated with an implicit MX |
||
1654 | // RR, with a preference of 0, pointing to that host. |
||
1655 | // |
||
1656 | // is_email() author's note: We will regard the existence of a CNAME to be |
||
1657 | // sufficient evidence of the domain's existence. For performance reasons |
||
1658 | // we will not repeat the DNS lookup for the CNAME's target, but we will |
||
1659 | // raise a warning because we didn't immediately find an MX record. |
||
1660 | if ($this->elementCount === 0) { |
||
1661 | // Checking TLD DNS seems to work only if you explicitly check from the root |
||
1662 | $this->parseData[Email::COMPONENT_DOMAIN] .= '.'; |
||
1663 | } |
||
1664 | |||
1665 | $result = @dns_get_record($this->parseData[Email::COMPONENT_DOMAIN], DNS_MX); // Not using checkdnsrr because of a suspected bug in PHP 5.3 (http://bugs.php.net/bug.php?id = 51844) |
||
1666 | |||
1667 | if ((is_bool($result) && !(bool) $result)) { |
||
1668 | // Domain can't be found in DNS |
||
1669 | $this->returnStatus[] = Email::DNSWARN_NO_RECORD; |
||
1670 | } else { |
||
1671 | if (count($result) === 0) { |
||
1672 | // MX-record for domain can't be found |
||
1673 | $this->returnStatus[] = Email::DNSWARN_NO_MX_RECORD; |
||
1674 | $result = @dns_get_record($this->parseData[Email::COMPONENT_DOMAIN], DNS_A + DNS_CNAME); |
||
1675 | if (count($result) === 0) { |
||
1676 | // No usable records for the domain can be found |
||
1677 | $this->returnStatus[] = Email::DNSWARN_NO_RECORD; |
||
1678 | } |
||
1679 | } else { |
||
1680 | $this->dnsChecked = true; |
||
1681 | } |
||
1682 | } |
||
1683 | } |
||
1684 | 165 | } |
|
1685 | |||
1686 | /** |
||
1687 | * |
||
1688 | * Check the top-level domain of the address. |
||
1689 | * |
||
1690 | * @return null |
||
1691 | * |
||
1692 | */ |
||
1693 | 165 | protected function checkTld() |
|
1694 | { |
||
1695 | // Check for TLD addresses |
||
1696 | // ----------------------- |
||
1697 | // TLD addresses are specifically allowed in RFC 5321 but they are |
||
1698 | // unusual to say the least. We will allocate a separate |
||
1699 | // status to these addresses on the basis that they are more likely |
||
1700 | // to be typos than genuine addresses (unless we've already |
||
1701 | // established that the domain does have an MX record) |
||
1702 | // |
||
1703 | // http://tools.ietf.org/html/rfc5321#section-2.3.5 |
||
1704 | // In the case |
||
1705 | // of a top-level domain used by itself in an email address, a single |
||
1706 | // string is used without any dots. This makes the requirement, |
||
1707 | // described in more detail below, that only fully-qualified domain |
||
1708 | // names appear in SMTP transactions on the public Internet, |
||
1709 | // particularly important where top-level domains are involved. |
||
1710 | // |
||
1711 | // TLD format |
||
1712 | // ---------- |
||
1713 | // The format of TLDs has changed a number of times. The standards |
||
1714 | // used by IANA have been largely ignored by ICANN, leading to |
||
1715 | // confusion over the standards being followed. These are not defined |
||
1716 | // anywhere, except as a general component of a DNS host name (a label). |
||
1717 | // However, this could potentially lead to 123.123.123.123 being a |
||
1718 | // valid DNS name (rather than an IP address) and thereby creating |
||
1719 | // an ambiguity. The most authoritative statement on TLD formats that |
||
1720 | // the author can find is in a (rejected!) erratum to RFC 1123 |
||
1721 | // submitted by John Klensin, the author of RFC 5321: |
||
1722 | // |
||
1723 | // http://www.rfc-editor.org/errata_search.php?rfc = 1123&eid = 1353 |
||
1724 | // However, a valid host name can never have the dotted-decimal |
||
1725 | // form #.#.#.#, since this change does not permit the highest-level |
||
1726 | // component label to start with a digit even if it is not all-numeric. |
||
1727 | 165 | if (!$this->dnsChecked && ((int) max($this->returnStatus) < Email::DNSWARN)) { |
|
1728 | 26 | if ($this->elementCount === 0) { |
|
1729 | 2 | $this->returnStatus[] = Email::RFC5321_TLD; |
|
1730 | } |
||
1731 | |||
1732 | 26 | if (is_numeric($this->atomList[Email::COMPONENT_DOMAIN][$this->elementCount][0])) { |
|
1733 | 2 | $this->returnStatus[] = Email::RFC5321_TLDNUMERIC; |
|
1734 | } |
||
1735 | } |
||
1736 | 165 | } |
|
1737 | |||
1738 | /** |
||
1739 | * |
||
1740 | * Sets the final status and return status. |
||
1741 | * |
||
1742 | * @return null |
||
1743 | * |
||
1744 | */ |
||
1745 | 165 | protected function finalStatus() |
|
1746 | { |
||
1747 | 165 | $this->returnStatus = array_unique($this->returnStatus); |
|
0 ignored issues
–
show
It seems like
array_unique($this->returnStatus) of type array is incompatible with the declared type integer of property $returnStatus .
Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property. Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..
Loading history...
|
|||
1748 | 165 | $this->finalStatus = (int) max($this->returnStatus); |
|
1749 | |||
1750 | 165 | if (count($this->returnStatus) !== 1) { |
|
1751 | // remove redundant Email::VALID |
||
1752 | 143 | array_shift($this->returnStatus); |
|
1753 | } |
||
1754 | |||
1755 | 165 | $this->parseData['status'] = $this->returnStatus; |
|
1756 | |||
1757 | 165 | if ($this->finalStatus < $this->threshold) { |
|
1758 | $this->finalStatus = Email::VALID; |
||
1759 | } |
||
1760 | 165 | } |
|
1761 | } |
||
1762 |
This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.
Consider making the comparison explicit by using
empty(..)
or! empty(...)
instead.