428 lines
No EOL
15 KiB
PHP
428 lines
No EOL
15 KiB
PHP
<?php
|
|
/**
|
|
* rfc822address.php
|
|
*
|
|
* Copyright (c) 2004 The SquirrelMail Project Team
|
|
* Licensed under the GNU GPL. For full terms see the file COPYING.
|
|
*
|
|
* Contains rfc822 email address function parsing functions.
|
|
*
|
|
*
|
|
* @version $Id$
|
|
* @package squirrelmail
|
|
*/
|
|
|
|
/**
|
|
* Undocumented defines
|
|
*/
|
|
if (!defined('SQM_ADDR_PERSONAL')) define('SQM_ADDR_PERSONAL', 0);
|
|
if (!defined('SQM_ADDR_ADLL')) define('SQM_ADDR_ADL', 1);
|
|
if (!defined('SQM_ADDR_MAILBOX')) define('SQM_ADDR_MAILBOX', 2);
|
|
if (!defined('SQM_ADDR_HOST')) define('SQM_ADDR_HOST', 3);
|
|
|
|
/**
|
|
* parseRFC822Address: function for parsing RFC822 email address strings and store
|
|
* them in an address array
|
|
*
|
|
* @param string $address The email address string to parse
|
|
* @param array $aProps associative array with properties
|
|
* @public
|
|
* @author Marc Groot Koerkamp
|
|
*
|
|
**/
|
|
|
|
function parseRFC822Address($sAddress,$aProps) {
|
|
// $aPropsDefault = array (
|
|
// 'domain' => '', //
|
|
// 'limit' => 0, // limits returned addresses
|
|
// 'abooklookup' => false); // callback function for addressbook lookup
|
|
//
|
|
// $aProps = is_array($aProps) ? array_merge($aPropsDefault,$aProps) : $aPropsDefault;
|
|
|
|
// $cbLookup = $aProps['abooklookup'];
|
|
// $sDomain = $aProps['domain'];
|
|
$iLimit = $aProps['limit'];
|
|
|
|
$aTokens = _getAddressTokens($sAddress);
|
|
$sPersonal = $sEmail = $sComment = $sGroup = '';
|
|
$aStack = $aComment = $aAddress = array();
|
|
foreach ($aTokens as $sToken) {
|
|
if ($iLimit && $iLimit == count($aAddress)) {
|
|
return $aAddress;
|
|
}
|
|
$cChar = $sToken{0};
|
|
switch ($cChar)
|
|
{
|
|
case '=':
|
|
case '"':
|
|
case ' ':
|
|
$aStack[] = $sToken;
|
|
break;
|
|
case '(':
|
|
$aComment[] = substr($sToken,1,-1);
|
|
break;
|
|
case ';':
|
|
if ($sGroup) {
|
|
$aAddress[] = _createAddressElement($aStack,$aComment,$sEmail);
|
|
$oAddr = end($aAddress);
|
|
if(!$oAddr || ((isset($oAddr)) && !$oAddr->mailbox && !$oAddr->personal)) {
|
|
$sEmail = $sGroup . ':;';
|
|
}
|
|
$aAddress[] = _createAddressElement($aStack,$aComment,$sEmail);
|
|
$sGroup = '';
|
|
$aStack = $aComment = array();
|
|
break;
|
|
}
|
|
case ',':
|
|
$aAddress[] = _createAddressElement($aStack,$aComment,$sEmail);
|
|
break;
|
|
case ':':
|
|
$sGroup = trim(implode(' ',$aStack));
|
|
$sGroup = preg_replace('/\s+/',' ',$sGroup);
|
|
$aStack = array();
|
|
break;
|
|
case '<':
|
|
$sEmail = trim(substr($sToken,1,-1));
|
|
break;
|
|
case '>':
|
|
/* skip */
|
|
break;
|
|
default: $aStack[] = $sToken; break;
|
|
}
|
|
}
|
|
/* now do the action again for the last address */
|
|
$aAddress[] = _createAddressElement($aStack,$aComment,$sEmail);
|
|
return $aAddress;
|
|
}
|
|
|
|
/**
|
|
* Do the address array to string translation
|
|
*
|
|
* @param array $aAddressList list with email address arrays
|
|
* @param array $aProps associative array with properties
|
|
* @return string
|
|
* @public
|
|
* @see parseRFC822Address
|
|
* @author Marc Groot Koerkamp
|
|
*
|
|
**/
|
|
function getAddressString($aAddressList,$aProps) {
|
|
$aPropsDefault = array (
|
|
'separator' => ',', // address separator
|
|
'limit' => 0, // limits returned addresses
|
|
'personal' => true, // show persnal part
|
|
'email' => true, // show email part
|
|
'best' => false, // show personal if available
|
|
'encode' => false, // encode the personal part
|
|
'unique' => false, // make email addresses unique.
|
|
'exclude' => array() // array with exclude addresses
|
|
// format of address: mailbox@host
|
|
);
|
|
|
|
$aProps = is_array($aProps) ? array_merge($aPropsDefault,$aProps) : $aPropsDefault;
|
|
|
|
$aNewAddressList = array();
|
|
$aEmailUnique = array();
|
|
foreach ($aAddressList as $aAddr) {
|
|
if ($aProps['limit'] && count($aNewAddressList) == $aProps['limit']) {
|
|
break;
|
|
}
|
|
$sPersonal = (isset($aAddr[SQM_ADDR_PERSONAL])) ? $aAddr[SQM_ADDR_PERSONAL] : '';
|
|
$sMailbox = (isset($aAddr[SQM_ADDR_MAILBOX])) ? $aAddr[SQM_ADDR_MAILBOX] : '';
|
|
$sHost = (isset($aAddr[SQM_ADDR_HOST])) ? $aAddr[SQM_ADDR_HOST] : '';
|
|
|
|
$sEmail = ($sHost) ? "$sMailbox@$sHost": $sMailbox;
|
|
|
|
if (in_array($sEmail,$aProps['exclude'],true)) {
|
|
continue;
|
|
}
|
|
|
|
if ($aProps['unique']) {
|
|
if (in_array($sEmail,$aEmailUnique,true)) {
|
|
continue;
|
|
} else {
|
|
$aEmailUnique[] = $sEmail;
|
|
}
|
|
}
|
|
|
|
$s = '';
|
|
if ($aProps['best']) {
|
|
$s .= ($sPersonal) ? $sPersonal : $sEmail;
|
|
} else {
|
|
if ($aProps['personal'] && $sPersonal) {
|
|
if ($aProps['encode']) {
|
|
$sPersonal = encodeHeader($sPersonal);
|
|
}
|
|
$s .= $sPersonal;
|
|
}
|
|
if ($aProps['email'] && $sEmail) {
|
|
$s.= ($s) ? ' <'.$sEmail.'>': '<'.$sEmail.'>';
|
|
}
|
|
}
|
|
if ($s) {
|
|
$aNewAddressList[] = $s;
|
|
}
|
|
}
|
|
return explode($aProps['seperator'],$aNewAddressList);
|
|
}
|
|
|
|
|
|
/**
|
|
* Do after address parsing handling. This is used by compose.php and should
|
|
* be moved to compose.php.
|
|
* The AddressStructure objetc is now obsolete and dependent parts of that will
|
|
* be adapted so that it can make use of this function
|
|
* After that we can remove the parseAddress method from the Rfc822Header class completely
|
|
* so we achieved 1 single instance of parseAddress instead of two like we have now.
|
|
*
|
|
* @param array $aAddressList list with email address arrays
|
|
* @param array $aProps associative array with properties
|
|
* @return string
|
|
* @public
|
|
* @see parseRFC822Address
|
|
* $see Rfc822Header
|
|
* @author Marc Groot Koerkamp
|
|
*
|
|
**/
|
|
function processAddressArray($aAddresses,$aProps) {
|
|
$aPropsDefault = array (
|
|
'domain' => '',
|
|
'limit' => 0,
|
|
'abooklookup' => false);
|
|
|
|
$aProps = is_array($aProps) ? array_merge($aPropsDefault,$aProps) : $aPropsDefault;
|
|
$aProcessedAddress = array();
|
|
|
|
foreach ($aAddresses as $aEntry) {
|
|
/*
|
|
* if the emailaddress does not contain the domainpart it can concern
|
|
* an alias or local (in the same domain as the user is) email
|
|
* address. In that case we try to look it up in the addressbook or add
|
|
* the local domain part
|
|
*/
|
|
if (!$aEntry[SQM_ADDR_HOST]) {
|
|
if ($cbLookup) {
|
|
$aAddr = call_user_func_array($cbLookup,array($aEntry[SQM_ADDR_MAILBOX]));
|
|
if (isset($aAddr['email'])) {
|
|
/*
|
|
* if the returned email address concerns multiple email
|
|
* addresses we have to process those as well
|
|
*/
|
|
if (strpos($aAddr['email'],',')) { /* multiple addresses */
|
|
/* add the parsed addresses to the processed address array */
|
|
$aProcessedAddress = array_merge($aProcessedAddress,parseAddress($aAddr['email']));
|
|
/* skip to next address, all processing is done */
|
|
continue;
|
|
} else { /* single address */
|
|
$iPosAt = strpos($aAddr['email'], '@');
|
|
$aEntry[SQM_ADDR_MAILBOX] = substr($aAddr['email'], 0, $iPosAt);
|
|
$aEntry[SQM_ADDR_HOST] = substr($aAddr['email'], $iPosAt+1);
|
|
if (isset($aAddr['name'])) {
|
|
$aEntry[SQM_ADDR_PERSONAL] = $aAddr['name'];
|
|
} else {
|
|
$aEntry[SQM_ADDR_PERSONAL] = encodeHeader($sPersonal);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
/*
|
|
* append the domain
|
|
*
|
|
*/
|
|
if (!$aEntry[SQM_ADDR_MAILBOX]) {
|
|
$aEntry[SQM_ADDR_MAILBOX] = trim($sEmail);
|
|
}
|
|
if ($sDomain && !$aEntry[SQM_ADDR_HOST]) {
|
|
$aEntry[SQM_ADDR_HOST] = $sDomain;
|
|
}
|
|
}
|
|
if ($aEntry[SQM_ADDR_MAILBOX]) {
|
|
$aProcessedAddress[] = $aEntry;
|
|
}
|
|
}
|
|
return $aProcessedAddress;
|
|
}
|
|
|
|
/**
|
|
* Internal function for creating an address array
|
|
*
|
|
* @param array $aStack
|
|
* @param array $aComment
|
|
* @param string $sEmail
|
|
* @return array $aAddr array with personal (0), adl(1), mailbox(2) and host(3) info
|
|
* @private
|
|
* @author Marc Groot Koerkamp
|
|
*
|
|
**/
|
|
|
|
function _createAddressElement(&$aStack,&$aComment,&$sEmail) {
|
|
if (!$sEmail) {
|
|
while (count($aStack) && !$sEmail) {
|
|
$sEmail = trim(array_pop($aStack));
|
|
}
|
|
}
|
|
if (count($aStack)) {
|
|
$sPersonal = trim(implode('',$aStack));
|
|
} else {
|
|
$sPersonal = '';
|
|
}
|
|
if (!$sPersonal && count($aComment)) {
|
|
$sComment = trim(implode(' ',$aComment));
|
|
$sPersonal .= $sComment;
|
|
}
|
|
$aAddr = array();
|
|
// if ($sPersonal && substr($sPersonal,0,2) == '=?') {
|
|
// $aAddr[SQM_ADDR_PERSONAL] = encodeHeader($sPersonal);
|
|
// } else {
|
|
$aAddr[SQM_ADDR_PERSONAL] = $sPersonal;
|
|
// }
|
|
|
|
$iPosAt = strpos($sEmail,'@');
|
|
if ($iPosAt) {
|
|
$aAddr[SQM_ADDR_MAILBOX] = substr($sEmail, 0, $iPosAt);
|
|
$aAddr[SQM_ADDR_HOST] = substr($sEmail, $iPosAt+1);
|
|
} else {
|
|
$aAddr[SQM_ADDR_MAILBOX] = $sEmail;
|
|
$aAddr[SQM_ADDR_HOST] = false;
|
|
}
|
|
$sEmail = '';
|
|
$aStack = $aComment = array();
|
|
return $aAddr;
|
|
}
|
|
|
|
/**
|
|
* Tokenizer function for parsing the RFC822 email address string
|
|
*
|
|
* @param string $address The email address string to parse
|
|
* @return array $aTokens
|
|
* @private
|
|
* @author Marc Groot Koerkamp
|
|
*
|
|
**/
|
|
|
|
function _getAddressTokens($address) {
|
|
$aTokens = array();
|
|
$aAddress = array();
|
|
$aSpecials = array('(' ,'<' ,',' ,';' ,':');
|
|
$aReplace = array(' (',' <',' ,',' ;',' :');
|
|
$address = str_replace($aSpecials,$aReplace,$address);
|
|
$iCnt = strlen($address);
|
|
$i = 0;
|
|
while ($i < $iCnt) {
|
|
$cChar = $address{$i};
|
|
switch($cChar)
|
|
{
|
|
case '<':
|
|
$iEnd = strpos($address,'>',$i+1);
|
|
if (!$iEnd) {
|
|
$sToken = substr($address,$i);
|
|
$i = $iCnt;
|
|
} else {
|
|
$sToken = substr($address,$i,$iEnd - $i +1);
|
|
$i = $iEnd;
|
|
}
|
|
$sToken = str_replace($aReplace, $aSpecials,$sToken);
|
|
if ($sToken) $aTokens[] = $sToken;
|
|
break;
|
|
case '"':
|
|
$iEnd = strpos($address,$cChar,$i+1);
|
|
if ($iEnd) {
|
|
// skip escaped quotes
|
|
$prev_char = $address{$iEnd-1};
|
|
while ($prev_char === '\\' && substr($address,$iEnd-2,2) !== '\\\\') {
|
|
$iEnd = strpos($address,$cChar,$iEnd+1);
|
|
if ($iEnd) {
|
|
$prev_char = $address{$iEnd-1};
|
|
} else {
|
|
$prev_char = false;
|
|
}
|
|
}
|
|
}
|
|
if (!$iEnd) {
|
|
$sToken = substr($address,$i);
|
|
$i = $iCnt;
|
|
} else {
|
|
// also remove the surrounding quotes
|
|
$sToken = substr($address,$i+1,$iEnd - $i -1);
|
|
$i = $iEnd;
|
|
}
|
|
$sToken = str_replace($aReplace, $aSpecials,$sToken);
|
|
if ($sToken) $aTokens[] = $sToken;
|
|
break;
|
|
case '(':
|
|
array_pop($aTokens); //remove inserted space
|
|
$iEnd = strpos($address,')',$i);
|
|
if (!$iEnd) {
|
|
$sToken = substr($address,$i);
|
|
$i = $iCnt;
|
|
} else {
|
|
$iDepth = 1;
|
|
$iComment = $i;
|
|
while (($iDepth > 0) && (++$iComment < $iCnt)) {
|
|
$cCharComment = $address{$iComment};
|
|
switch($cCharComment) {
|
|
case '\\':
|
|
++$iComment;
|
|
break;
|
|
case '(':
|
|
++$iDepth;
|
|
break;
|
|
case ')':
|
|
--$iDepth;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
if ($iDepth == 0) {
|
|
$sToken = substr($address,$i,$iComment - $i +1);
|
|
$i = $iComment;
|
|
} else {
|
|
$sToken = substr($address,$i,$iEnd - $i + 1);
|
|
$i = $iEnd;
|
|
}
|
|
}
|
|
// check the next token in case comments appear in the middle of email addresses
|
|
$prevToken = end($aTokens);
|
|
if (!in_array($prevToken,$aSpecials,true)) {
|
|
if ($i+1<strlen($address) && !in_array($address{$i+1},$aSpecials,true)) {
|
|
$iEnd = strpos($address,' ',$i+1);
|
|
if ($iEnd) {
|
|
$sNextToken = trim(substr($address,$i+1,$iEnd - $i -1));
|
|
$i = $iEnd-1;
|
|
} else {
|
|
$sNextToken = trim(substr($address,$i+1));
|
|
$i = $iCnt;
|
|
}
|
|
// remove the token
|
|
array_pop($aTokens);
|
|
// create token and add it again
|
|
$sNewToken = $prevToken . $sNextToken;
|
|
if($sNewToken) $aTokens[] = $sNewToken;
|
|
}
|
|
}
|
|
$sToken = str_replace($aReplace, $aSpecials,$sToken);
|
|
if ($sToken) $aTokens[] = $sToken;
|
|
break;
|
|
case ',':
|
|
case ':':
|
|
case ';':
|
|
case ' ':
|
|
$aTokens[] = $cChar;
|
|
break;
|
|
default:
|
|
$iEnd = strpos($address,' ',$i+1);
|
|
if ($iEnd) {
|
|
$sToken = trim(substr($address,$i,$iEnd - $i));
|
|
$i = $iEnd-1;
|
|
} else {
|
|
$sToken = trim(substr($address,$i));
|
|
$i = $iCnt;
|
|
}
|
|
if ($sToken) $aTokens[] = $sToken;
|
|
}
|
|
++$i;
|
|
}
|
|
return $aTokens;
|
|
}
|
|
?>
|