1*3d4e3335SAndreas Gohr<?php 2*3d4e3335SAndreas Gohr 3*3d4e3335SAndreas Gohr/** 4*3d4e3335SAndreas Gohr * Class EmailAddressValidator 5*3d4e3335SAndreas Gohr * 6*3d4e3335SAndreas Gohr * @link https://github.com/aziraphale/email-address-validator 7*3d4e3335SAndreas Gohr * @link http://code.google.com/p/php-email-address-validation/ 8*3d4e3335SAndreas Gohr * @license New BSD license http://www.opensource.org/licenses/bsd-license.php 9*3d4e3335SAndreas Gohr * @example if (EmailAddressValidator::checkEmailAddress('test@example.org')) { 10*3d4e3335SAndreas Gohr * @example // Email address is technically valid 11*3d4e3335SAndreas Gohr * @example } 12*3d4e3335SAndreas Gohr */ 13*3d4e3335SAndreas Gohrclass EmailAddressValidator 14*3d4e3335SAndreas Gohr{ 15*3d4e3335SAndreas Gohr /** 16*3d4e3335SAndreas Gohr * Check email address validity 17*3d4e3335SAndreas Gohr * @param string $emailAddress Email address to be checked 18*3d4e3335SAndreas Gohr * @param bool $allowLocal allow local domains 19*3d4e3335SAndreas Gohr * @return bool Whether email is valid 20*3d4e3335SAndreas Gohr */ 21*3d4e3335SAndreas Gohr public static function checkEmailAddress($emailAddress, $allowLocal = false) 22*3d4e3335SAndreas Gohr { 23*3d4e3335SAndreas Gohr // If magic quotes is "on", email addresses with quote marks will 24*3d4e3335SAndreas Gohr // fail validation because of added escape characters. Uncommenting 25*3d4e3335SAndreas Gohr // the next three lines will allow for this issue. 26*3d4e3335SAndreas Gohr //if (get_magic_quotes_gpc()) { 27*3d4e3335SAndreas Gohr // $emailAddress = stripslashes($emailAddress); 28*3d4e3335SAndreas Gohr //} 29*3d4e3335SAndreas Gohr 30*3d4e3335SAndreas Gohr // Control characters are not allowed 31*3d4e3335SAndreas Gohr if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $emailAddress)) { 32*3d4e3335SAndreas Gohr return false; 33*3d4e3335SAndreas Gohr } 34*3d4e3335SAndreas Gohr 35*3d4e3335SAndreas Gohr // Check email length - min 3 (a@a), max 256 36*3d4e3335SAndreas Gohr if (!self::checkTextLength($emailAddress, 3, 256)) { 37*3d4e3335SAndreas Gohr return false; 38*3d4e3335SAndreas Gohr } 39*3d4e3335SAndreas Gohr 40*3d4e3335SAndreas Gohr // Split it into sections using last instance of "@" 41*3d4e3335SAndreas Gohr $atSymbol = strrpos($emailAddress, '@'); 42*3d4e3335SAndreas Gohr if ($atSymbol === false) { 43*3d4e3335SAndreas Gohr // No "@" symbol in email. 44*3d4e3335SAndreas Gohr return false; 45*3d4e3335SAndreas Gohr } 46*3d4e3335SAndreas Gohr $emailAddressParts[0] = substr($emailAddress, 0, $atSymbol); 47*3d4e3335SAndreas Gohr $emailAddressParts[1] = substr($emailAddress, $atSymbol + 1); 48*3d4e3335SAndreas Gohr 49*3d4e3335SAndreas Gohr // Count the "@" symbols. Only one is allowed, except where 50*3d4e3335SAndreas Gohr // contained in quote marks in the local part. Quickest way to 51*3d4e3335SAndreas Gohr // check this is to remove anything in quotes. We also remove 52*3d4e3335SAndreas Gohr // characters escaped with backslash, and the backslash 53*3d4e3335SAndreas Gohr // character. 54*3d4e3335SAndreas Gohr $tempAddressParts[0] = preg_replace('/\./', '', $emailAddressParts[0]); 55*3d4e3335SAndreas Gohr $tempAddressParts[0] = preg_replace('/"[^"]+"/', '', $tempAddressParts[0]); 56*3d4e3335SAndreas Gohr $tempAddressParts[1] = $emailAddressParts[1]; 57*3d4e3335SAndreas Gohr $tempAddress = $tempAddressParts[0] . $tempAddressParts[1]; 58*3d4e3335SAndreas Gohr // Then check - should be no "@" symbols. 59*3d4e3335SAndreas Gohr if (strrpos($tempAddress, '@') !== false) { 60*3d4e3335SAndreas Gohr // "@" symbol found 61*3d4e3335SAndreas Gohr return false; 62*3d4e3335SAndreas Gohr } 63*3d4e3335SAndreas Gohr 64*3d4e3335SAndreas Gohr // Check local portion 65*3d4e3335SAndreas Gohr if (!self::checkLocalPortion($emailAddressParts[0])) { 66*3d4e3335SAndreas Gohr return false; 67*3d4e3335SAndreas Gohr } 68*3d4e3335SAndreas Gohr 69*3d4e3335SAndreas Gohr // Check domain portion 70*3d4e3335SAndreas Gohr if (!self::checkDomainPortion($emailAddressParts[1], $allowLocal)) { 71*3d4e3335SAndreas Gohr return false; 72*3d4e3335SAndreas Gohr } 73*3d4e3335SAndreas Gohr 74*3d4e3335SAndreas Gohr // If we're still here, all checks above passed. Email is valid. 75*3d4e3335SAndreas Gohr return true; 76*3d4e3335SAndreas Gohr } 77*3d4e3335SAndreas Gohr 78*3d4e3335SAndreas Gohr /** 79*3d4e3335SAndreas Gohr * Checks email section before "@" symbol for validity 80*3d4e3335SAndreas Gohr * @param string $localPortion Text to be checked 81*3d4e3335SAndreas Gohr * @return bool Whether local portion is valid 82*3d4e3335SAndreas Gohr */ 83*3d4e3335SAndreas Gohr public static function checkLocalPortion($localPortion) 84*3d4e3335SAndreas Gohr { 85*3d4e3335SAndreas Gohr // Local portion can only be from 1 to 64 characters, inclusive. 86*3d4e3335SAndreas Gohr // Please note that servers are encouraged to accept longer local 87*3d4e3335SAndreas Gohr // parts than 64 characters. 88*3d4e3335SAndreas Gohr if (!self::checkTextLength($localPortion, 1, 64)) { 89*3d4e3335SAndreas Gohr return false; 90*3d4e3335SAndreas Gohr } 91*3d4e3335SAndreas Gohr // Local portion must be: 92*3d4e3335SAndreas Gohr // 1) a dot-atom (strings separated by periods) 93*3d4e3335SAndreas Gohr // 2) a quoted string 94*3d4e3335SAndreas Gohr // 3) an obsolete format string (combination of the above) 95*3d4e3335SAndreas Gohr $localPortionParts = explode('.', $localPortion); 96*3d4e3335SAndreas Gohr for ($i = 0, $max = sizeof($localPortionParts); $i < $max; $i++) { 97*3d4e3335SAndreas Gohr if (!preg_match('.^(' 98*3d4e3335SAndreas Gohr . '([A-Za-z0-9!#$%&\'*+/=?^_`{|}~-]' 99*3d4e3335SAndreas Gohr . '[A-Za-z0-9!#$%&\'*+/=?^_`{|}~-]{0,63})' 100*3d4e3335SAndreas Gohr .'|' 101*3d4e3335SAndreas Gohr . '("[^\\\"]{0,62}")' 102*3d4e3335SAndreas Gohr .')$.' 103*3d4e3335SAndreas Gohr ,$localPortionParts[$i])) { 104*3d4e3335SAndreas Gohr return false; 105*3d4e3335SAndreas Gohr } 106*3d4e3335SAndreas Gohr } 107*3d4e3335SAndreas Gohr return true; 108*3d4e3335SAndreas Gohr } 109*3d4e3335SAndreas Gohr 110*3d4e3335SAndreas Gohr /** 111*3d4e3335SAndreas Gohr * Checks email section after "@" symbol for validity 112*3d4e3335SAndreas Gohr * @param string $domainPortion Text to be checked 113*3d4e3335SAndreas Gohr * @param bool $allowLocal allow local domains? 114*3d4e3335SAndreas Gohr * @return bool Whether domain portion is valid 115*3d4e3335SAndreas Gohr */ 116*3d4e3335SAndreas Gohr public static function checkDomainPortion($domainPortion, $allowLocal = false) 117*3d4e3335SAndreas Gohr { 118*3d4e3335SAndreas Gohr // Total domain can only be from 1 to 255 characters, inclusive 119*3d4e3335SAndreas Gohr if (!self::checkTextLength($domainPortion, 1, 255)) { 120*3d4e3335SAndreas Gohr return false; 121*3d4e3335SAndreas Gohr } 122*3d4e3335SAndreas Gohr 123*3d4e3335SAndreas Gohr // some IPv4/v6 regexps borrowed from Feyd 124*3d4e3335SAndreas Gohr // see: http://forums.devnetwork.net/viewtopic.php?f=38&t=53479 125*3d4e3335SAndreas Gohr $dec_octet = '(?:25[0-5]|2[0-4]\d|1\d\d|[1-9]\d|[0-9])'; 126*3d4e3335SAndreas Gohr $hex_digit = '[A-Fa-f0-9]'; 127*3d4e3335SAndreas Gohr $h16 = "{$hex_digit}{1,4}"; 128*3d4e3335SAndreas Gohr $IPv4Address = "$dec_octet\\.$dec_octet\\.$dec_octet\\.$dec_octet"; 129*3d4e3335SAndreas Gohr $ls32 = "(?:$h16:$h16|$IPv4Address)"; 130*3d4e3335SAndreas Gohr $IPv6Address = 131*3d4e3335SAndreas Gohr "(?:(?:{$IPv4Address})|(?:" . 132*3d4e3335SAndreas Gohr "(?:$h16:){6}$ls32" . 133*3d4e3335SAndreas Gohr "|::(?:$h16:){5}$ls32" . 134*3d4e3335SAndreas Gohr "|(?:$h16)?::(?:$h16:){4}$ls32" . 135*3d4e3335SAndreas Gohr "|(?:(?:$h16:){0,1}$h16)?::(?:$h16:){3}$ls32" . 136*3d4e3335SAndreas Gohr "|(?:(?:$h16:){0,2}$h16)?::(?:$h16:){2}$ls32" . 137*3d4e3335SAndreas Gohr "|(?:(?:$h16:){0,3}$h16)?::(?:$h16:){1}$ls32" . 138*3d4e3335SAndreas Gohr "|(?:(?:$h16:){0,4}$h16)?::$ls32" . 139*3d4e3335SAndreas Gohr "|(?:(?:$h16:){0,5}$h16)?::$h16" . 140*3d4e3335SAndreas Gohr "|(?:(?:$h16:){0,6}$h16)?::" . 141*3d4e3335SAndreas Gohr ")(?:\\/(?:12[0-8]|1[0-1][0-9]|[1-9][0-9]|[0-9]))?)"; 142*3d4e3335SAndreas Gohr 143*3d4e3335SAndreas Gohr if (preg_match("/^($IPv4Address|\\[$IPv4Address\\]|\\[$IPv6Address\\])$/", 144*3d4e3335SAndreas Gohr $domainPortion)){ 145*3d4e3335SAndreas Gohr return true; 146*3d4e3335SAndreas Gohr } else { 147*3d4e3335SAndreas Gohr $domainPortionParts = explode('.', $domainPortion); 148*3d4e3335SAndreas Gohr if (!$allowLocal && sizeof($domainPortionParts) < 2) { 149*3d4e3335SAndreas Gohr return false; // Not enough parts to domain 150*3d4e3335SAndreas Gohr } 151*3d4e3335SAndreas Gohr for ($i = 0, $max = sizeof($domainPortionParts); $i < $max; $i++) { 152*3d4e3335SAndreas Gohr // Each portion must be between 1 and 63 characters, inclusive 153*3d4e3335SAndreas Gohr if (!self::checkTextLength($domainPortionParts[$i], 1, 63)) { 154*3d4e3335SAndreas Gohr return false; 155*3d4e3335SAndreas Gohr } 156*3d4e3335SAndreas Gohr if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|' 157*3d4e3335SAndreas Gohr .'([A-Za-z0-9]+))$/', $domainPortionParts[$i])) { 158*3d4e3335SAndreas Gohr return false; 159*3d4e3335SAndreas Gohr } 160*3d4e3335SAndreas Gohr if ($i == $max - 1) { // TLD cannot be only numbers 161*3d4e3335SAndreas Gohr if (strlen(preg_replace('/[0-9]/', '', $domainPortionParts[$i])) <= 0) { 162*3d4e3335SAndreas Gohr return false; 163*3d4e3335SAndreas Gohr } 164*3d4e3335SAndreas Gohr } 165*3d4e3335SAndreas Gohr } 166*3d4e3335SAndreas Gohr } 167*3d4e3335SAndreas Gohr return true; 168*3d4e3335SAndreas Gohr } 169*3d4e3335SAndreas Gohr 170*3d4e3335SAndreas Gohr /** 171*3d4e3335SAndreas Gohr * Check given text length is between defined bounds 172*3d4e3335SAndreas Gohr * @param string $text Text to be checked 173*3d4e3335SAndreas Gohr * @param int $minimum Minimum acceptable length 174*3d4e3335SAndreas Gohr * @param int $maximum Maximum acceptable length 175*3d4e3335SAndreas Gohr * @return bool Whether string is within bounds (inclusive) 176*3d4e3335SAndreas Gohr */ 177*3d4e3335SAndreas Gohr protected static function checkTextLength($text, $minimum, $maximum) 178*3d4e3335SAndreas Gohr { 179*3d4e3335SAndreas Gohr // Minimum and maximum are both inclusive 180*3d4e3335SAndreas Gohr $textLength = strlen($text); 181*3d4e3335SAndreas Gohr return ($textLength >= $minimum && $textLength <= $maximum); 182*3d4e3335SAndreas Gohr } 183*3d4e3335SAndreas Gohr} 184