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