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