1: <?php
2:
3: /**
4: * This file contains the mail validator class.
5: *
6: * @package Core
7: * @subpackage Validation
8: * @author Murat Purc <murat@purc.de>
9: * @copyright four for business AG <www.4fb.de>
10: * @license http://www.contenido.org/license/LIZENZ.txt
11: * @link http://www.4fb.de
12: * @link http://www.contenido.org
13: */
14:
15: defined('CON_FRAMEWORK') || die('Illegal call: Missing framework initialization - request aborted.');
16:
17: /**
18: * E-Mail validation.
19: *
20: * Supports following options:
21: * <pre>
22: * - disallow_tld (array) Optional, list of top level domains to disallow
23: * - disallow_host (array) Optional, list of hosts to disallow
24: * - mx_check (bool) Optional, flag to check DNS records for MX type
25: * </pre>
26: *
27: * Usage:
28: * <pre>
29: * $validator = cValidatorFactory::getInstance('email');
30: * if ($validator->isValid('user@contenido.org')) {
31: * // email is valid
32: * } else {
33: * $errors = $validator->getErrors();
34: * foreach ($errors as $pos => $errItem) {
35: * echo $errItem->code . ": " . $errItem->message . "\n";
36: * }
37: * }
38: * </pre>
39: *
40: * @package Core
41: * @subpackage Validation
42: */
43: class cValidatorEmail extends cValidatorAbstract {
44:
45: /**
46: * Flag about existing flter_var function
47: * @var bool
48: */
49: protected static $_filterVarExists;
50:
51: /**
52: * Constructor to create an instance of this class.
53: *
54: * Sets some predefined options.
55: */
56: public function __construct() {
57: // Some default options to exclude tld or host
58: // RFC 2606 filter (<http://tools.ietf.org/html/rfc2606>)
59: $this->setOption('disallow_tld', array('.test', '.example', '.invalid', '.localhost'));
60: $this->setOption('disallow_host', array('example.com', 'example.org', 'example.net'));
61: }
62:
63: /**
64: * Filter variable function exists setter
65: * @param bool $exists
66: */
67: public static function setFilterVarExists($exists) {
68: self::$_filterVarExists = (bool) $exists;
69: }
70:
71: /**
72: * Unsets filter variable function state
73: */
74: public static function resetFilterVarExists() {
75: unset(self::$_filterVarExists);
76: }
77:
78: /**
79: *
80: * @see cValidatorAbstract::_isValid()
81: * @param mixed $value
82: * @return bool
83: */
84: protected function _isValid($value) {
85: if (!is_string($value) || empty($value)) {
86: $this->addError('Invalid or empty value', 1);
87: return false;
88: }
89:
90: $host = cString::getPartOfString($value, cString::findFirstPos($value, '@') + 1);
91: $tld = cString::findLastOccurrence($value, '.');
92:
93: // do RFC 2606 or user defined filter if configured
94: if ($this->getOption('disallow_tld')) {
95: if (in_array($tld, $this->getOption('disallow_tld'))) {
96: $this->addError('Disallowed top level domain', 2);
97: return false;
98: }
99: }
100: if ($this->getOption('disallow_host')) {
101: if (in_array($host, $this->getOption('disallow_host'))) {
102: $this->addError('Disallowed host name', 3);
103: return false;
104: }
105: }
106:
107: if (!isset(self::$_filterVarExists)) {
108: self::setFilterVarExists(function_exists('filter_var'));
109: }
110:
111: // Use native filter_var function, if exists
112: if (self::$_filterVarExists) {
113: $isValid = (bool) filter_var($value, FILTER_VALIDATE_EMAIL);
114: } else {
115: // Fallback for not existing filter_var function
116: // Taken over from PHP trunk ext/filter/logical_filters.c
117: /*
118: * The regex below is based on a regex by Michael Rushton.
119: * However, it is not identical. I changed it to only consider routeable
120: * addresses as valid. Michael's regex considers a@b a valid address
121: * which conflicts with section 2.3.5 of RFC 5321 which states that:
122: *
123: * Only resolvable, fully-qualified domain names (FQDNs) are permitted
124: * when domain names are used in SMTP. In other words, names that can
125: * be resolved to MX RRs or address (i.e., A or AAAA) RRs (as discussed
126: * in Section 5) are permitted, as are CNAME RRs whose targets can be
127: * resolved, in turn, to MX or address RRs. Local nicknames or
128: * unqualified names MUST NOT be used.
129: *
130: * This regex does not handle comments and folding whitespace. While
131: * this is technically valid in an email address, these parts aren't
132: * actually part of the address itself.
133: *
134: * Michael's regex carries this copyright:
135: *
136: * Copyright � Michael Rushton 2009-10
137: * http://squiloople.com/
138: * Feel free to use and redistribute this code. But please keep this copyright notice.
139: *
140: */
141: $regexp = "/^(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){255,})(?!(?:(?:\\x22?\\x5C[\\x00-\\x7E]\\x22?)|(?:\\x22?[^\\x5C\\x22]\\x22?)){65,}@)(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22))(?:\\.(?:(?:[\\x21\\x23-\\x27\\x2A\\x2B\\x2D\\x2F-\\x39\\x3D\\x3F\\x5E-\\x7E]+)|(?:\\x22(?:[\\x01-\\x08\\x0B\\x0C\\x0E-\\x1F\\x21\\x23-\\x5B\\x5D-\\x7F]|(?:\\x5C[\\x00-\\x7F]))*\\x22)))*@(?:(?:(?!.*[^.]{64,})(?:(?:(?:xn--)?[a-z0-9]+(?:-[a-z0-9]+)*\\.){1,126}){1,}(?:(?:[a-z][a-z0-9]*)|(?:(?:xn--)[a-z0-9]+))(?:-[a-z0-9]+)*)|(?:\\[(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){7})|(?:(?!(?:.*[a-f0-9][:\\]]){7,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,5})?)))|(?:(?:IPv6:(?:(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){5}:)|(?:(?!(?:.*[a-f0-9]:){5,})(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3})?::(?:[a-f0-9]{1,4}(?::[a-f0-9]{1,4}){0,3}:)?)))?(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))(?:\\.(?:(?:25[0-5])|(?:2[0-4][0-9])|(?:1[0-9]{2})|(?:[1-9]?[0-9]))){3}))\\]))$/iD";
142: $isValid = (bool) preg_match($regexp, $value);
143: }
144: if (!$isValid) {
145: $this->addError('Invalid email', 4);
146: return false;
147: }
148:
149:
150: // Do DNS check for MX type
151: if ($this->getOption('mx_check') && function_exists('checkdnsrr')) {
152: $isValid = $this->_checkMx($host);
153: if (!$isValid) {
154: $this->addError('MX check failed', 5);
155: return false;
156: }
157: }
158:
159: return true;
160: }
161:
162: /**
163: * Check DNS Records for MX type.
164: *
165: * @param string $host
166: * Host name
167: * @return bool
168: */
169: private function _checkMx($host) {
170: return checkdnsrr($host, 'MX');
171: }
172:
173: }
174: