rfc822address.php 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427
  1. <?php
  2. /**
  3. * rfc822address.php
  4. *
  5. * Copyright (c) 2004-2005 The SquirrelMail Project Team
  6. * Licensed under the GNU GPL. For full terms see the file COPYING.
  7. *
  8. * Contains rfc822 email address function parsing functions.
  9. *
  10. *
  11. * @version $Id$
  12. * @package squirrelmail
  13. */
  14. /**
  15. * Undocumented defines
  16. */
  17. if (!defined('SQM_ADDR_PERSONAL')) define('SQM_ADDR_PERSONAL', 0);
  18. if (!defined('SQM_ADDR_ADLL')) define('SQM_ADDR_ADL', 1);
  19. if (!defined('SQM_ADDR_MAILBOX')) define('SQM_ADDR_MAILBOX', 2);
  20. if (!defined('SQM_ADDR_HOST')) define('SQM_ADDR_HOST', 3);
  21. /**
  22. * parseRFC822Address: function for parsing RFC822 email address strings and store
  23. * them in an address array
  24. *
  25. * @param string $address The email address string to parse
  26. * @param array $aProps associative array with properties
  27. * @public
  28. * @author Marc Groot Koerkamp
  29. *
  30. **/
  31. function parseRFC822Address($sAddress,$aProps) {
  32. // $aPropsDefault = array (
  33. // 'domain' => '', //
  34. // 'limit' => 0, // limits returned addresses
  35. // 'abooklookup' => false); // callback function for addressbook lookup
  36. //
  37. // $aProps = is_array($aProps) ? array_merge($aPropsDefault,$aProps) : $aPropsDefault;
  38. // $cbLookup = $aProps['abooklookup'];
  39. // $sDomain = $aProps['domain'];
  40. $iLimit = $aProps['limit'];
  41. $aTokens = _getAddressTokens($sAddress);
  42. $sEmail = $sGroup = '';
  43. $aStack = $aComment = $aAddress = array();
  44. foreach ($aTokens as $sToken) {
  45. if ($iLimit && $iLimit == count($aAddress)) {
  46. return $aAddress;
  47. }
  48. $cChar = $sToken{0};
  49. switch ($cChar)
  50. {
  51. case '=':
  52. case '"':
  53. case ' ':
  54. $aStack[] = $sToken;
  55. break;
  56. case '(':
  57. $aComment[] = substr($sToken,1,-1);
  58. break;
  59. case ';':
  60. if ($sGroup) {
  61. $aAddress[] = _createAddressElement($aStack,$aComment,$sEmail);
  62. $oAddr = end($aAddress);
  63. if(!$oAddr || ((isset($oAddr)) && !$oAddr->mailbox && !$oAddr->personal)) {
  64. $sEmail = $sGroup . ':;';
  65. }
  66. $aAddress[] = _createAddressElement($aStack,$aComment,$sEmail);
  67. $sGroup = '';
  68. $aStack = $aComment = array();
  69. break;
  70. }
  71. case ',':
  72. $aAddress[] = _createAddressElement($aStack,$aComment,$sEmail);
  73. break;
  74. case ':':
  75. $sGroup = trim(implode(' ',$aStack));
  76. $sGroup = preg_replace('/\s+/',' ',$sGroup);
  77. $aStack = array();
  78. break;
  79. case '<':
  80. $sEmail = trim(substr($sToken,1,-1));
  81. break;
  82. case '>':
  83. /* skip */
  84. break;
  85. default: $aStack[] = $sToken; break;
  86. }
  87. }
  88. /* now do the action again for the last address */
  89. $aAddress[] = _createAddressElement($aStack,$aComment,$sEmail);
  90. return $aAddress;
  91. }
  92. /**
  93. * Do the address array to string translation
  94. *
  95. * @param array $aAddressList list with email address arrays
  96. * @param array $aProps associative array with properties
  97. * @return string
  98. * @public
  99. * @see parseRFC822Address
  100. * @author Marc Groot Koerkamp
  101. *
  102. **/
  103. function getAddressString($aAddressList,$aProps) {
  104. $aPropsDefault = array (
  105. 'separator' => ',', // address separator
  106. 'limit' => 0, // limits returned addresses
  107. 'personal' => true, // show persnal part
  108. 'email' => true, // show email part
  109. 'best' => false, // show personal if available
  110. 'encode' => false, // encode the personal part
  111. 'unique' => false, // make email addresses unique.
  112. 'exclude' => array() // array with exclude addresses
  113. // format of address: mailbox@host
  114. );
  115. $aProps = is_array($aProps) ? array_merge($aPropsDefault,$aProps) : $aPropsDefault;
  116. $aNewAddressList = array();
  117. $aEmailUnique = array();
  118. foreach ($aAddressList as $aAddr) {
  119. if ($aProps['limit'] && count($aNewAddressList) == $aProps['limit']) {
  120. break;
  121. }
  122. $sPersonal = (isset($aAddr[SQM_ADDR_PERSONAL])) ? $aAddr[SQM_ADDR_PERSONAL] : '';
  123. $sMailbox = (isset($aAddr[SQM_ADDR_MAILBOX])) ? $aAddr[SQM_ADDR_MAILBOX] : '';
  124. $sHost = (isset($aAddr[SQM_ADDR_HOST])) ? $aAddr[SQM_ADDR_HOST] : '';
  125. $sEmail = ($sHost) ? "$sMailbox@$sHost": $sMailbox;
  126. if (in_array($sEmail,$aProps['exclude'],true)) {
  127. continue;
  128. }
  129. if ($aProps['unique']) {
  130. if (in_array($sEmail,$aEmailUnique,true)) {
  131. continue;
  132. } else {
  133. $aEmailUnique[] = $sEmail;
  134. }
  135. }
  136. $s = '';
  137. if ($aProps['best']) {
  138. $s .= ($sPersonal) ? $sPersonal : $sEmail;
  139. } else {
  140. if ($aProps['personal'] && $sPersonal) {
  141. if ($aProps['encode']) {
  142. $sPersonal = encodeHeader($sPersonal);
  143. }
  144. $s .= $sPersonal;
  145. }
  146. if ($aProps['email'] && $sEmail) {
  147. $s.= ($s) ? ' <'.$sEmail.'>': '<'.$sEmail.'>';
  148. }
  149. }
  150. if ($s) {
  151. $aNewAddressList[] = $s;
  152. }
  153. }
  154. return explode($aProps['seperator'],$aNewAddressList);
  155. }
  156. /**
  157. * Do after address parsing handling. This is used by compose.php and should
  158. * be moved to compose.php.
  159. * The AddressStructure objetc is now obsolete and dependent parts of that will
  160. * be adapted so that it can make use of this function
  161. * After that we can remove the parseAddress method from the Rfc822Header class completely
  162. * so we achieved 1 single instance of parseAddress instead of two like we have now.
  163. *
  164. * @param array $aAddressList list with email address arrays
  165. * @param array $aProps associative array with properties
  166. * @return string
  167. * @public
  168. * @see parseRFC822Address
  169. * $see Rfc822Header
  170. * @author Marc Groot Koerkamp
  171. *
  172. **/
  173. function processAddressArray($aAddresses,$aProps) {
  174. $aPropsDefault = array (
  175. 'domain' => '',
  176. 'limit' => 0,
  177. 'abooklookup' => false);
  178. $aProps = is_array($aProps) ? array_merge($aPropsDefault,$aProps) : $aPropsDefault;
  179. $aProcessedAddress = array();
  180. foreach ($aAddresses as $aEntry) {
  181. /*
  182. * if the emailaddress does not contain the domainpart it can concern
  183. * an alias or local (in the same domain as the user is) email
  184. * address. In that case we try to look it up in the addressbook or add
  185. * the local domain part
  186. */
  187. if (!$aEntry[SQM_ADDR_HOST]) {
  188. if ($cbLookup) {
  189. $aAddr = call_user_func_array($cbLookup,array($aEntry[SQM_ADDR_MAILBOX]));
  190. if (isset($aAddr['email'])) {
  191. /*
  192. * if the returned email address concerns multiple email
  193. * addresses we have to process those as well
  194. */
  195. if (strpos($aAddr['email'],',')) { /* multiple addresses */
  196. /* add the parsed addresses to the processed address array */
  197. $aProcessedAddress = array_merge($aProcessedAddress,parseAddress($aAddr['email']));
  198. /* skip to next address, all processing is done */
  199. continue;
  200. } else { /* single address */
  201. $iPosAt = strpos($aAddr['email'], '@');
  202. $aEntry[SQM_ADDR_MAILBOX] = substr($aAddr['email'], 0, $iPosAt);
  203. $aEntry[SQM_ADDR_HOST] = substr($aAddr['email'], $iPosAt+1);
  204. if (isset($aAddr['name'])) {
  205. $aEntry[SQM_ADDR_PERSONAL] = $aAddr['name'];
  206. } else {
  207. $aEntry[SQM_ADDR_PERSONAL] = encodeHeader($sPersonal);
  208. }
  209. }
  210. }
  211. }
  212. /*
  213. * append the domain
  214. *
  215. */
  216. if (!$aEntry[SQM_ADDR_MAILBOX]) {
  217. $aEntry[SQM_ADDR_MAILBOX] = trim($sEmail);
  218. }
  219. if ($sDomain && !$aEntry[SQM_ADDR_HOST]) {
  220. $aEntry[SQM_ADDR_HOST] = $sDomain;
  221. }
  222. }
  223. if ($aEntry[SQM_ADDR_MAILBOX]) {
  224. $aProcessedAddress[] = $aEntry;
  225. }
  226. }
  227. return $aProcessedAddress;
  228. }
  229. /**
  230. * Internal function for creating an address array
  231. *
  232. * @param array $aStack
  233. * @param array $aComment
  234. * @param string $sEmail
  235. * @return array $aAddr array with personal (0), adl(1), mailbox(2) and host(3) info
  236. * @private
  237. * @author Marc Groot Koerkamp
  238. *
  239. **/
  240. function _createAddressElement(&$aStack,&$aComment,&$sEmail) {
  241. if (!$sEmail) {
  242. while (count($aStack) && !$sEmail) {
  243. $sEmail = trim(array_pop($aStack));
  244. }
  245. }
  246. if (count($aStack)) {
  247. $sPersonal = trim(implode('',$aStack));
  248. } else {
  249. $sPersonal = '';
  250. }
  251. if (!$sPersonal && count($aComment)) {
  252. $sComment = trim(implode(' ',$aComment));
  253. $sPersonal .= $sComment;
  254. }
  255. $aAddr = array();
  256. // if ($sPersonal && substr($sPersonal,0,2) == '=?') {
  257. // $aAddr[SQM_ADDR_PERSONAL] = encodeHeader($sPersonal);
  258. // } else {
  259. $aAddr[SQM_ADDR_PERSONAL] = $sPersonal;
  260. // }
  261. $iPosAt = strpos($sEmail,'@');
  262. if ($iPosAt) {
  263. $aAddr[SQM_ADDR_MAILBOX] = substr($sEmail, 0, $iPosAt);
  264. $aAddr[SQM_ADDR_HOST] = substr($sEmail, $iPosAt+1);
  265. } else {
  266. $aAddr[SQM_ADDR_MAILBOX] = $sEmail;
  267. $aAddr[SQM_ADDR_HOST] = false;
  268. }
  269. $sEmail = '';
  270. $aStack = $aComment = array();
  271. return $aAddr;
  272. }
  273. /**
  274. * Tokenizer function for parsing the RFC822 email address string
  275. *
  276. * @param string $address The email address string to parse
  277. * @return array $aTokens
  278. * @private
  279. * @author Marc Groot Koerkamp
  280. *
  281. **/
  282. function _getAddressTokens($address) {
  283. $aTokens = array();
  284. $aSpecials = array('(' ,'<' ,',' ,';' ,':');
  285. $aReplace = array(' (',' <',' ,',' ;',' :');
  286. $address = str_replace($aSpecials,$aReplace,$address);
  287. $iCnt = strlen($address);
  288. $i = 0;
  289. while ($i < $iCnt) {
  290. $cChar = $address{$i};
  291. switch($cChar)
  292. {
  293. case '<':
  294. $iEnd = strpos($address,'>',$i+1);
  295. if (!$iEnd) {
  296. $sToken = substr($address,$i);
  297. $i = $iCnt;
  298. } else {
  299. $sToken = substr($address,$i,$iEnd - $i +1);
  300. $i = $iEnd;
  301. }
  302. $sToken = str_replace($aReplace, $aSpecials,$sToken);
  303. if ($sToken) $aTokens[] = $sToken;
  304. break;
  305. case '"':
  306. $iEnd = strpos($address,$cChar,$i+1);
  307. if ($iEnd) {
  308. // skip escaped quotes
  309. $prev_char = $address{$iEnd-1};
  310. while ($prev_char === '\\' && substr($address,$iEnd-2,2) !== '\\\\') {
  311. $iEnd = strpos($address,$cChar,$iEnd+1);
  312. if ($iEnd) {
  313. $prev_char = $address{$iEnd-1};
  314. } else {
  315. $prev_char = false;
  316. }
  317. }
  318. }
  319. if (!$iEnd) {
  320. $sToken = substr($address,$i);
  321. $i = $iCnt;
  322. } else {
  323. // also remove the surrounding quotes
  324. $sToken = substr($address,$i+1,$iEnd - $i -1);
  325. $i = $iEnd;
  326. }
  327. $sToken = str_replace($aReplace, $aSpecials,$sToken);
  328. if ($sToken) $aTokens[] = $sToken;
  329. break;
  330. case '(':
  331. array_pop($aTokens); //remove inserted space
  332. $iEnd = strpos($address,')',$i);
  333. if (!$iEnd) {
  334. $sToken = substr($address,$i);
  335. $i = $iCnt;
  336. } else {
  337. $iDepth = 1;
  338. $iComment = $i;
  339. while (($iDepth > 0) && (++$iComment < $iCnt)) {
  340. $cCharComment = $address{$iComment};
  341. switch($cCharComment) {
  342. case '\\':
  343. ++$iComment;
  344. break;
  345. case '(':
  346. ++$iDepth;
  347. break;
  348. case ')':
  349. --$iDepth;
  350. break;
  351. default:
  352. break;
  353. }
  354. }
  355. if ($iDepth == 0) {
  356. $sToken = substr($address,$i,$iComment - $i +1);
  357. $i = $iComment;
  358. } else {
  359. $sToken = substr($address,$i,$iEnd - $i + 1);
  360. $i = $iEnd;
  361. }
  362. }
  363. // check the next token in case comments appear in the middle of email addresses
  364. $prevToken = end($aTokens);
  365. if (!in_array($prevToken,$aSpecials,true)) {
  366. if ($i+1<strlen($address) && !in_array($address{$i+1},$aSpecials,true)) {
  367. $iEnd = strpos($address,' ',$i+1);
  368. if ($iEnd) {
  369. $sNextToken = trim(substr($address,$i+1,$iEnd - $i -1));
  370. $i = $iEnd-1;
  371. } else {
  372. $sNextToken = trim(substr($address,$i+1));
  373. $i = $iCnt;
  374. }
  375. // remove the token
  376. array_pop($aTokens);
  377. // create token and add it again
  378. $sNewToken = $prevToken . $sNextToken;
  379. if($sNewToken) $aTokens[] = $sNewToken;
  380. }
  381. }
  382. $sToken = str_replace($aReplace, $aSpecials,$sToken);
  383. if ($sToken) $aTokens[] = $sToken;
  384. break;
  385. case ',':
  386. case ':':
  387. case ';':
  388. case ' ':
  389. $aTokens[] = $cChar;
  390. break;
  391. default:
  392. $iEnd = strpos($address,' ',$i+1);
  393. if ($iEnd) {
  394. $sToken = trim(substr($address,$i,$iEnd - $i));
  395. $i = $iEnd-1;
  396. } else {
  397. $sToken = trim(substr($address,$i));
  398. $i = $iCnt;
  399. }
  400. if ($sToken) $aTokens[] = $sToken;
  401. }
  402. ++$i;
  403. }
  404. return $aTokens;
  405. }
  406. ?>