Search page should remember last move-target too
[squirrelmail.git] / functions / rfc822address.php
1 <?php
2
3 /**
4 * rfc822address.php
5 *
6 * Contains rfc822 email address function parsing functions.
7 *
8 * @copyright 2004-2020 The SquirrelMail Project Team
9 * @license http://opensource.org/licenses/gpl-license.php GNU Public License
10 * @version $Id$
11 * @package squirrelmail
12 */
13
14
15 /**
16 * parseRFC822Address: function for parsing RFC822 email address strings and store
17 * them in an address array
18 *
19 * @param string $address The email address string to parse
20 * @param integer $iLimit stop on $iLimit parsed addresses
21 * @public
22 * @author Marc Groot Koerkamp
23 *
24 **/
25 function parseRFC822Address($sAddress,$iLimit = 0) {
26
27 $aTokens = _getAddressTokens($sAddress);
28 $sPersonal = $sEmail = $sComment = $sGroup = '';
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);
48 $aAddr = end($aAddress);
49 if(!$aAddr || ((isset($aAddr)) && !$aAddr[SQM_ADDR_MAILBOX] && !$aAddr[SQM_ADDR_PERSONAL])) {
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
79
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 }
148 return implode($aProps['separator'],$aNewAddressList);
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
165 * @see Rfc822Header
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();
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 }