82465805 |
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) { |
324ac3c5 |
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; |
82465805 |
38 | |
324ac3c5 |
39 | // $cbLookup = $aProps['abooklookup']; |
40 | // $sDomain = $aProps['domain']; |
82465805 |
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 | ?> |