moving functions to separate file, adding configuration files
[squirrelmail.git] / functions / rfc822address.php
CommitLineData
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
d1532827 15/**
16 * Undocumented defines
17 */
82465805 18if (!defined('SQM_ADDR_PERSONAL')) define('SQM_ADDR_PERSONAL', 0);
19if (!defined('SQM_ADDR_ADLL')) define('SQM_ADDR_ADL', 1);
20if (!defined('SQM_ADDR_MAILBOX')) define('SQM_ADDR_MAILBOX', 2);
21if (!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
34function 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);
47 $sPersonal = $sEmail = $sComment = $sGroup = '';
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 **/
109function 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 **/
187function 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
258function _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
303function _getAddressTokens($address) {
304 $aTokens = array();
305 $aAddress = array();
306 $aSpecials = array('(' ,'<' ,',' ,';' ,':');
307 $aReplace = array(' (',' <',' ,',' ;',' :');
308 $address = str_replace($aSpecials,$aReplace,$address);
309 $iCnt = strlen($address);
310 $i = 0;
311 while ($i < $iCnt) {
312 $cChar = $address{$i};
313 switch($cChar)
314 {
315 case '<':
316 $iEnd = strpos($address,'>',$i+1);
317 if (!$iEnd) {
318 $sToken = substr($address,$i);
319 $i = $iCnt;
320 } else {
321 $sToken = substr($address,$i,$iEnd - $i +1);
322 $i = $iEnd;
323 }
324 $sToken = str_replace($aReplace, $aSpecials,$sToken);
325 if ($sToken) $aTokens[] = $sToken;
326 break;
327 case '"':
328 $iEnd = strpos($address,$cChar,$i+1);
329 if ($iEnd) {
330 // skip escaped quotes
331 $prev_char = $address{$iEnd-1};
332 while ($prev_char === '\\' && substr($address,$iEnd-2,2) !== '\\\\') {
333 $iEnd = strpos($address,$cChar,$iEnd+1);
334 if ($iEnd) {
335 $prev_char = $address{$iEnd-1};
336 } else {
337 $prev_char = false;
338 }
339 }
340 }
341 if (!$iEnd) {
342 $sToken = substr($address,$i);
343 $i = $iCnt;
344 } else {
345 // also remove the surrounding quotes
346 $sToken = substr($address,$i+1,$iEnd - $i -1);
347 $i = $iEnd;
348 }
349 $sToken = str_replace($aReplace, $aSpecials,$sToken);
350 if ($sToken) $aTokens[] = $sToken;
351 break;
352 case '(':
353 array_pop($aTokens); //remove inserted space
354 $iEnd = strpos($address,')',$i);
355 if (!$iEnd) {
356 $sToken = substr($address,$i);
357 $i = $iCnt;
358 } else {
359 $iDepth = 1;
360 $iComment = $i;
361 while (($iDepth > 0) && (++$iComment < $iCnt)) {
362 $cCharComment = $address{$iComment};
363 switch($cCharComment) {
364 case '\\':
365 ++$iComment;
366 break;
367 case '(':
368 ++$iDepth;
369 break;
370 case ')':
371 --$iDepth;
372 break;
373 default:
374 break;
375 }
376 }
377 if ($iDepth == 0) {
378 $sToken = substr($address,$i,$iComment - $i +1);
379 $i = $iComment;
380 } else {
381 $sToken = substr($address,$i,$iEnd - $i + 1);
382 $i = $iEnd;
383 }
384 }
385 // check the next token in case comments appear in the middle of email addresses
386 $prevToken = end($aTokens);
387 if (!in_array($prevToken,$aSpecials,true)) {
388 if ($i+1<strlen($address) && !in_array($address{$i+1},$aSpecials,true)) {
389 $iEnd = strpos($address,' ',$i+1);
390 if ($iEnd) {
391 $sNextToken = trim(substr($address,$i+1,$iEnd - $i -1));
392 $i = $iEnd-1;
393 } else {
394 $sNextToken = trim(substr($address,$i+1));
395 $i = $iCnt;
396 }
397 // remove the token
398 array_pop($aTokens);
399 // create token and add it again
400 $sNewToken = $prevToken . $sNextToken;
401 if($sNewToken) $aTokens[] = $sNewToken;
402 }
403 }
404 $sToken = str_replace($aReplace, $aSpecials,$sToken);
405 if ($sToken) $aTokens[] = $sToken;
406 break;
407 case ',':
408 case ':':
409 case ';':
410 case ' ':
411 $aTokens[] = $cChar;
412 break;
413 default:
414 $iEnd = strpos($address,' ',$i+1);
415 if ($iEnd) {
416 $sToken = trim(substr($address,$i,$iEnd - $i));
417 $i = $iEnd-1;
418 } else {
419 $sToken = trim(substr($address,$i));
420 $i = $iCnt;
421 }
422 if ($sToken) $aTokens[] = $sToken;
423 }
424 ++$i;
425 }
426 return $aTokens;
427}
428?>