We are not using objects here.
[squirrelmail.git] / functions / rfc822address.php
1 <?php
2 /**
3 * rfc822address.php
4 *
5 * Copyright (c) 2004-2005 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
16 /**
17 * parseRFC822Address: function for parsing RFC822 email address strings and store
18 * them in an address array
19 *
20 * @param string $address The email address string to parse
21 * @param integer $iLimit stop on $iLimit parsed addresses
22 * @public
23 * @author Marc Groot Koerkamp
24 *
25 **/
26 function parseRFC822Address($sAddress,$iLimit = 0) {
27
28 $aTokens = _getAddressTokens($sAddress);
29 $sPersonal = $sEmail = $sComment = $sGroup = '';
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);
49 $aAddr = end($aAddress);
50 if(!$aAddr || ((isset($aAddr)) && !$aAddr[SQM_ADDR_MAILBOX] && !$aAddr[SQM_ADDR_PERSONAL])) {
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
80
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 }
149 return implode($aProps['separator'],$aNewAddressList);
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
166 * @see Rfc822Header
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();
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 ?>