rfc822address.php 14 KB

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