<?php

/*
 * Simple e-mail validation, nearly without regular expressions.
 * Copyright (C) 2007 Stefan Majewsky
 * 
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 * 
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License along
 * with this program; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
 */

//Validates e-mail addresses as specified by:
//* RFC 2822 ("Internet Message Format")
//* RFC 1034/1035 ("Domain Name System") for the domain part
function isEmailAddress($val) {
	//Annotation: You should know the following ASCII codes when reading this function: " == 34, . == 46, @ == 64, \ == 92
	$val = (string) $val;                                            //ensure that the parameter is a string
	$length = strlen($val);
	if ($length < 3) return false;                                   //cannot be OK, at least three chars (local part, "@", domain part) necessary
	$isLocalPart = true;                                             //true if scanning the local part (switched by "@")
	static $localPartChars = "/[A-Za-z0-9.!#\$%&'*+\\-\/=?^_`{|}~]/";//characters allowed in the local part (if not quoted)
	$isLocalPartQuoted = (substr($val, 0, 1) == '"');                //is the local part quoted with '"' chars?
	$isEscaping = false;                                             //was the last character a backslash?
	$isClosingQuoteOccured = false;                                  //only necessary if the local part is quoted
																	 //(escaping and quoting allows all characters from ASCII 32 to 127 in the local part)
	$localPartCount = ($isLocalPartQuoted ? 1 : 0);                 //counter for characters in the local part (and the "@")
	$domainPartCount = 0;                                            //counter for characters in the domain part
	$labelLength = 0;                                                //measures the length of labels in the domain part

	for ($pos = ($isLocalPartQuoted ? 1 : 0); $pos < $length; $pos++) { //check each character for compliance
		$current = substr($val, $pos, 1);                            //get current char
		$ordinal = ord($current);                                    //get ASCII code for this char

		if ($isLocalPart) {
			//character in local part or the "@"
			$localPartCount++;
			//special treatment for escaped chars
			if ($isEscaping) {
				if (($ordinal < 32) || ($ordinal > 127)) return false;
				$isEscaping = false;
				continue;                                            //suppress further checks
			}
			//special treatment if the local part is quoted
			if ($isLocalPartQuoted) {
				if (($ordinal < 32) || ($ordinal > 127)) return false;
				if ($isClosingQuoteOccured) {                        //the last char was the closing quote (i.e. end of local part) -> this char must be "@"
					if ($ordinal == 64) {
						$isLocalPart = false;                        //it is the "@" -> the next char starts the domain part
					} else {
						return false;                                //another quote occured after the closing quote
					}
				}
				$isClosingQuoteOccured = ($ordinal == 34);           //true means: found '"' which (in this case) is the closing quote for the local part
				continue;                                            //suppress further checks
			}
			//normal treatment for local part characters
			if (!preg_match($localPartChars, $current)) {            //character not allowed in local part -> it may be the "@"
				if ($ordinal == 64) {
					$isLocalPart = false;                            //it is the "@" -> the next char starts the domain part
				} else {
					return false;                                    //illegal char occured
				}
			}
		} else {
			//character in domain part
			$domainPartCount++;
			if (($ordinal < 32) || ($ordinal > 127)) return false;
			if ($ordinal == 46) {                                    //"." occured - starting next label
				$labelLength = 0;
			} else {                                                 //next character in this label
				$labelLength++;
				if ($labelLength == 64) {                            //a label may not be longer than 63 characters
					return false;
				}
			}
		}
	}                                                                //end of character loop -> no errors occured -> check the calculated values

	return
		($isLocalPartQuoted)
			? ($localPartCount >= 4)                                 //must be at least the quotes, one character, and the '@', e.g. '"a"@'
			: ($localPartCount >= 2)                                 //must be at least one character and the '@', e.g. 'a@'
		&& ($domainPartCount >= 1)                                   //must be at least one character
		&& ($localPartCount <= 64)                                   //recommendation of RFC 2822
		&& ($domainPartCount <= 255)                                 //recommendation of RFC 2822
	;
}

?>