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