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