]> git.donarmstrong.com Git - roundcube.git/blob - program/lib/imap.inc
Imported Upstream version 0.2~stable
[roundcube.git] / program / lib / imap.inc
1 <?php
2 /////////////////////////////////////////////////////////
3 //      
4 //      Iloha IMAP Library (IIL)
5 //
6 //      (C)Copyright 2002 Ryo Chijiiwa <Ryo@IlohaMail.org>
7 //
8 //      This file is part of IlohaMail. IlohaMail is free software released 
9 //      under the GPL license.  See enclosed file COPYING for details, or 
10 //      see http://www.fsf.org/copyleft/gpl.html
11 //
12 /////////////////////////////////////////////////////////
13
14 /********************************************************
15
16         FILE: include/imap.inc
17         PURPOSE:
18                 Provide alternative IMAP library that doesn't rely on the standard 
19                 C-Client based version.  This allows IlohaMail to function regardless
20                 of whether or not the PHP build it's running on has IMAP functionality
21                 built-in.
22         USEAGE:
23                 Function containing "_C_" in name require connection handler to be
24                 passed as one of the parameters.  To obtain connection handler, use
25                 iil_Connect()
26         VERSION:
27                 IlohaMail-0.9-20050415
28         CHANGES:
29                 File altered by Thomas Bruederli <roundcube@gmail.com>
30                 to fit enhanced equirements by the RoundCube Webmail:
31                 - Added list of server capabilites and check these before invoking commands
32                 - Added junk flag to iilBasicHeader
33                 - Enhanced error reporting on fsockopen()
34                 - Additional parameter for SORT command
35                 - Removed Call-time pass-by-reference because deprecated
36                 - Parse charset from content-type in iil_C_FetchHeaders()
37                 - Enhanced heaer sorting
38                 - Pass message as reference in iil_C_Append (to save memory)
39                 - Added BCC and REFERENCE to the list of headers to fetch in iil_C_FetchHeaders()
40                 - Leave messageID unchanged in iil_C_FetchHeaders()
41                 - Avoid stripslahes in iil_Connect()
42                 - Escape quotes and backslashes in iil_C_Login()
43                 - Added patch to iil_SortHeaders() by Richard Green
44                 - Removed <br> from error messages (better for logging)
45                 - Added patch to iil_C_Sort() enabling UID SORT commands
46                 - Added function iil_C_ID2UID()
47                 - Casting date parts in iil_StrToTime() to avoid mktime() warnings
48                 - Also acceppt LIST responses in iil_C_ListSubscribed()
49                 - Sanity check of $message_set in iil_C_FetchHeaders(), iil_C_FetchHeaderIndex(), iil_C_FetchThreadHeaders()
50                 - Implemented UID FETCH in iil_C_FetchHeaders()
51                 - Abort do-loop on socket errors (fgets returns false)
52                 - $ICL_SSL is not boolean anymore but contains the connection schema (ssl or tls)
53                 - Removed some debuggers (echo ...)
54                 File altered by Aleksander Machniak <alec@alec.pl>
55                 - trim(chop()) replaced by trim()
56                 - added iil_Escape() with support for " and \ in folder names
57                 - support \ character in username in iil_C_Login()
58                 - fixed iil_MultLine(): use iil_ReadBytes() instead of iil_ReadLine()
59                 - fixed iil_C_FetchStructureString() to handle many literal strings in response
60                 - removed hardcoded data size in iil_ReadLine() 
61                 - added iil_PutLine() wrapper for fputs()
62                 - code cleanup and identation fixes
63                 - removed flush() calls in iil_C_HandlePartBody() to prevent from memory leak (#1485187)
64                 - don't return "??" from iil_C_GetQuota()
65                 - RFC3501 [7.1] don't call CAPABILITY if was returned in server 
66                   optional resposne in iil_Connect(), added iil_C_GetCapability()
67                 - remove 'undisclosed-recipients' string from 'To' header
68                 - iil_C_HandlePartBody(): added 6th argument and fixed endless loop
69                 - added iil_PutLineC() 
70                 - fixed iil_C_Sort() to support very long and/or divided responses
71                 - added BYE response simple support for endless loop prevention
72                 - added 3rd argument in iil_StartsWith* functions
73                 - fix iil_C_FetchPartHeader() in some cases by use of iil_C_HandlePartBody()
74                 - allow iil_C_HandlePartBody() to fetch whole message
75                 - optimize iil_C_FetchHeaders() to use only one FETCH command
76                 - added 4th argument to iil_Connect()
77                 - allow setting rootdir and delimiter before connect
78                 - support multiquota result
79
80 ********************************************************/
81
82 /**
83  * @todo Possibly clean up more CS.
84  * @todo Try to replace most double-quotes with single-quotes.
85  * @todo Split this file into smaller files.
86  * @todo Refactor code.
87  * @todo Replace echo-debugging (make it adhere to config setting and log)
88  */
89
90 // changed path to work within roundcube webmail
91 include_once 'lib/icl_commons.inc';
92
93
94 if (!isset($IMAP_USE_HEADER_DATE) || !$IMAP_USE_HEADER_DATE) {
95     $IMAP_USE_INTERNAL_DATE = true;
96 }
97
98 /**
99  * @todo Maybe use date() to generate this.
100  */
101 $GLOBALS['IMAP_MONTHS'] = array("Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4,
102     "May" => 5, "Jun" => 6, "Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10,
103     "Nov" => 11, "Dec" => 12);
104
105 $GLOBALS['IMAP_SERVER_TZ'] = date('Z');
106
107 $GLOBALS['IMAP_FLAGS'] = array(
108     'SEEN'     => '\\Seen',
109     'DELETED'  => '\\Deleted',
110     'RECENT'   => '\\Recent',
111     'ANSWERED' => '\\Answered',
112     'DRAFT'    => '\\Draft',
113     'FLAGGED'  => '\\Flagged',
114     'FORWARDED' => '$Forwarded',
115     'MDNSENT'  => '$MDNSent');
116
117 $iil_error;
118 $iil_errornum;
119 $iil_selected;
120
121 /**
122  * @todo Change class vars to public/private
123  */
124 class iilConnection
125 {
126         var $fp;
127         var $error;
128         var $errorNum;
129         var $selected;
130         var $message;
131         var $host;
132         var $cache;
133         var $uid_cache;
134         var $do_cache;
135         var $exists;
136         var $recent;
137         var $rootdir;
138         var $delimiter;
139         var $capability = array();
140         var $permanentflags = array();
141         var $capability_readed = false;
142 }
143
144 /**
145  * @todo Change class vars to public/private
146  */
147 class iilBasicHeader
148 {
149         var $id;
150         var $uid;
151         var $subject;
152         var $from;
153         var $to;
154         var $cc;
155         var $replyto;
156         var $in_reply_to;
157         var $date;
158         var $messageID;
159         var $size;
160         var $encoding;
161         var $charset;
162         var $ctype;
163         var $flags;
164         var $timestamp;
165         var $f;
166         var $internaldate;
167         var $references;
168         var $priority;
169         var $mdn_to;
170         var $mdn_sent = false;
171         var $is_draft = false;
172         var $seen = false;
173         var $deleted = false;
174         var $recent = false;
175         var $answered = false;
176         var $forwarded = false;
177         var $junk = false;
178         var $flagged = false;
179 }
180
181 /**
182  * @todo Change class vars to public/private
183  */
184 class iilThreadHeader
185 {
186         var $id;
187         var $sbj;
188         var $irt;
189         var $mid;
190 }
191
192 function iil_xor($string, $string2) {
193         $result = '';
194         $size = strlen($string);
195         for ($i=0; $i<$size; $i++) {
196                 $result .= chr(ord($string[$i]) ^ ord($string2[$i]));
197         }
198         return $result;
199 }
200
201 function iil_PutLine($fp, $string, $endln=true) {
202 //      console('C: '. rtrim($string));
203         return fputs($fp, $string . ($endln ? "\r\n" : ''));
204 }
205
206 // iil_PutLine replacement with Command Continuation Requests (RFC3501 7.5) support
207 function iil_PutLineC($fp, $string, $endln=true) {
208         if ($endln)
209                 $string .= "\r\n";
210
211         $res = 0;
212         if ($parts = preg_split('/(\{[0-9]+\}\r\n)/m', $string, -1, PREG_SPLIT_DELIM_CAPTURE)) {
213                 for($i=0, $cnt=count($parts); $i<$cnt; $i++) {
214                         if(preg_match('/^\{[0-9]+\}\r\n$/', $parts[$i+1])) {
215                                 $res += iil_PutLine($fp, $parts[$i].$parts[$i+1], false);
216                                 $line = iil_ReadLine($fp, 1000);
217                                 $i++;
218                         }
219                         else
220                                 $res += iil_PutLine($fp, $parts[$i], false);
221                 }
222         }
223         return $res;
224 }
225
226 function iil_ReadLine($fp, $size) {
227         $line = '';
228
229         if (!$fp) {
230                 return $line;
231         }
232     
233         if (!$size) {
234                 $size = 1024;
235         }
236     
237         do {
238                 $buffer = fgets($fp, $size);
239                 if ($buffer === false) {
240                         break;
241                 }
242 //              console('S: '. chop($buffer));
243                 $line .= $buffer;
244         } while ($buffer[strlen($buffer)-1] != "\n");
245         
246         return $line;
247 }
248
249 function iil_MultLine($fp, $line) {
250         $line = chop($line);
251         if (ereg('\{[0-9]+\}$', $line)) {
252                 $out = '';
253         
254                 preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a);
255                 $bytes = $a[2][0];
256                 while (strlen($out) < $bytes) {
257                         $line = iil_ReadBytes($fp, $bytes); 
258                         $out .= $line;
259                 }
260                 $line = $a[1][0] . "\"$out\"";
261 //              console('[...] '. $out);
262         }
263         return $line;
264 }
265
266 function iil_ReadBytes($fp, $bytes) {
267         $data = '';
268         $len  = 0;
269         do {
270                 $data .= fread($fp, $bytes-$len);
271                 if ($len == strlen($data)) {
272                         break; //nothing was read -> exit to avoid apache lockups
273                 }
274                 $len = strlen($data);
275         } while ($len < $bytes);
276         
277         return $data;
278 }
279
280 function iil_ReadReply($fp) {
281         do {
282                 $line = trim(iil_ReadLine($fp, 1024));
283         } while ($line[0] == '*');
284         
285         return $line;
286 }
287
288 function iil_ParseResult($string) {
289         $a=explode(' ', $string);
290         if (count($a) > 2) {
291                 if (strcasecmp($a[1], 'OK') == 0) {
292                         return 0;
293                 } else if (strcasecmp($a[1], 'NO') == 0) {
294                         return -1;
295                 } else if (strcasecmp($a[1], 'BAD') == 0) {
296                         return -2;
297                 } else if (strcasecmp($a[1], 'BYE') == 0) {
298                         return -3;
299                 }
300         }
301         return -4;
302 }
303
304 // check if $string starts with $match
305 function iil_StartsWith($string, $match, $bye=false) {
306         $len = strlen($match);
307         if ($len == 0) {
308                 return false;
309         }
310         if (strncmp($string, $match, $len) == 0) {
311                 return true;
312         }
313         if ($bye && strncmp($string, '* BYE ', 6) == 0) {
314                 return true;
315         }
316         return false;
317 }
318
319 function iil_StartsWithI($string, $match, $bye=false) {
320         $len = strlen($match);
321         if ($len == 0) {
322                 return false;
323         }
324         if (strncasecmp($string, $match, $len) == 0) {
325                 return true;
326         }
327         if ($bye && strncmp($string, '* BYE ', 6) == 0) {
328                 return true;
329         }
330         return false;
331 }
332
333 function iil_Escape($string)
334 {
335         return strtr($string, array('"'=>'\\"', '\\' => '\\\\')); 
336 }
337
338 function iil_C_GetCapability(&$conn, $name)
339 {
340         if (in_array($name, $conn->capability)) {
341                 return true;
342         }
343         else if ($conn->capability_readed) {
344                 return false;
345         }
346
347         // get capabilities (only once) because initial 
348         // optional CAPABILITY response may differ
349         $conn->capability = array();
350
351         iil_PutLine($conn->fp, "cp01 CAPABILITY");
352         do {
353                 $line = trim(iil_ReadLine($conn->fp, 1024));
354                 $a = explode(' ', $line);
355                 if ($line[0] == '*') {
356                         while (list($k, $w) = each($a)) {
357                                 if ($w != '*' && $w != 'CAPABILITY')
358                                         $conn->capability[] = strtoupper($w);
359                         }
360                 }
361         } while ($a[0] != 'cp01');
362         
363         $conn->capability_readed = true;
364
365         if (in_array($name, $conn->capability)) {
366                 return true;
367         }
368
369         return false;
370 }
371
372 function iil_C_Authenticate(&$conn, $user, $pass, $encChallenge) {
373     
374     $ipad = '';
375     $opad = '';
376     
377     // initialize ipad, opad
378     for ($i=0;$i<64;$i++) {
379         $ipad .= chr(0x36);
380         $opad .= chr(0x5C);
381     }
382
383     // pad $pass so it's 64 bytes
384     $padLen = 64 - strlen($pass);
385     for ($i=0;$i<$padLen;$i++) {
386         $pass .= chr(0);
387     }
388     
389     // generate hash
390     $hash  = md5(iil_xor($pass,$opad) . pack("H*", md5(iil_xor($pass, $ipad) . base64_decode($encChallenge))));
391     
392     // generate reply
393     $reply = base64_encode($user . ' ' . $hash);
394     
395     // send result, get reply
396     iil_PutLine($conn->fp, $reply);
397     $line = iil_ReadLine($conn->fp, 1024);
398     
399     // process result
400     $result = iil_ParseResult($line);
401     if ($result == 0) {
402         $conn->error    .= '';
403         $conn->errorNum  = 0;
404         return $conn->fp;
405     }
406
407     if ($result == -3) fclose($conn->fp); // BYE response
408
409     $conn->error    .= 'Authentication for ' . $user . ' failed (AUTH): "';
410     $conn->error    .= htmlspecialchars($line) . '"';
411     $conn->errorNum  = $result;
412
413     return $result;
414 }
415
416 function iil_C_Login(&$conn, $user, $password) {
417
418     iil_PutLine($conn->fp, 'a001 LOGIN "'.iil_Escape($user).'" "'.iil_Escape($password).'"');
419
420     do {
421         $line = iil_ReadReply($conn->fp);
422         if ($line === false) {
423             break;
424         }
425     } while (!iil_StartsWith($line, 'a001 ', true));
426     
427     // process result
428     $result = iil_ParseResult($line);
429
430     if ($result == 0) {
431         $conn->error    .= '';
432         $conn->errorNum  = 0;
433         return $conn->fp;
434     }
435
436     fclose($conn->fp);
437     
438     $conn->error    .= 'Authentication for ' . $user . ' failed (LOGIN): "';
439     $conn->error    .= htmlspecialchars($line)."\"";
440     $conn->errorNum  = $result;
441
442     return $result;
443 }
444
445 function iil_ParseNamespace2($str, &$i, $len=0, $l) {
446         if (!$l) {
447             $str = str_replace('NIL', '()', $str);
448         }
449         if (!$len) {
450             $len = strlen($str);
451         }
452         $data      = array();
453         $in_quotes = false;
454         $elem      = 0;
455         for ($i;$i<$len;$i++) {
456                 $c = (string)$str[$i];
457                 if ($c == '(' && !$in_quotes) {
458                         $i++;
459                         $data[$elem] = iil_ParseNamespace2($str, $i, $len, $l++);
460                         $elem++;
461                 } else if ($c == ')' && !$in_quotes) {
462                         return $data;
463                 } else if ($c == '\\') {
464                         $i++;
465                         if ($in_quotes) {
466                                 $data[$elem] .= $c.$str[$i];
467                         }
468                 } else if ($c == '"') {
469                         $in_quotes = !$in_quotes;
470                         if (!$in_quotes) {
471                                 $elem++;
472                         }
473                 } else if ($in_quotes) {
474                         $data[$elem].=$c;
475                 }
476         }
477         return $data;
478 }
479
480 function iil_C_NameSpace(&$conn) {
481         global $my_prefs;
482
483         if (isset($my_prefs['rootdir']) && is_string($my_prefs['rootdir'])) {
484                 $conn->rootdir = $my_prefs['rootdir'];
485                 return true;
486         }
487         
488         if (!iil_C_GetCapability($conn, 'NAMESPACE')) {
489             return false;
490         }
491     
492         iil_PutLine($conn->fp, "ns1 NAMESPACE");
493         do {
494                 $line = iil_ReadLine($conn->fp, 1024);
495                 if (iil_StartsWith($line, '* NAMESPACE')) {
496                         $i    = 0;
497                         $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
498                 }
499         } while (!iil_StartsWith($line, 'ns1', true));
500         
501         if (!is_array($data)) {
502             return false;
503         }
504     
505         $user_space_data = $data[0];
506         if (!is_array($user_space_data)) {
507             return false;
508         }
509     
510         $first_userspace = $user_space_data[0];
511         if (count($first_userspace)!=2) {
512             return false;
513         }
514     
515         $conn->rootdir       = $first_userspace[0];
516         $conn->delimiter     = $first_userspace[1];
517         $my_prefs['rootdir'] = substr($conn->rootdir, 0, -1);
518         $my_prefs['delimiter'] = $conn->delimiter;
519         
520         return true;
521 }
522
523 function iil_Connect($host, $user, $password, $options=null) {  
524         global $iil_error, $iil_errornum;
525         global $ICL_SSL, $ICL_PORT;
526         global $IMAP_NO_CACHE;
527         global $my_prefs, $IMAP_USE_INTERNAL_DATE;
528         
529         $iil_error = '';
530         $iil_errornum = 0;
531
532         // set some imap options
533         if (is_array($options)) {
534                 foreach($options as $optkey => $optval) {
535                         if ($optkey == 'imap') {
536                                 $auth_method = $optval;
537                         } else if ($optkey == 'rootdir') {
538                                 $my_prefs['rootdir'] = $optval;
539                         } else if ($optkey == 'delimiter') {
540                                 $my_prefs['delimiter'] = $optval;
541                         }
542                 }
543         }
544
545         if (empty($auth_method))
546                 $auth_method = 'plain';
547                 
548         $message = "INITIAL: $auth_method\n";
549                 
550         $result = false;
551         
552         //initialize connection
553         $conn              = new iilConnection;
554         $conn->error       = '';
555         $conn->errorNum    = 0;
556         $conn->selected    = '';
557         $conn->user        = $user;
558         $conn->host        = $host;
559         $conn->cache       = array();
560         $conn->do_cache    = (function_exists("cache_write")&&!$IMAP_NO_CACHE);
561         $conn->cache_dirty = array();
562         
563         if ($my_prefs['sort_field'] == 'INTERNALDATE') {
564                 $IMAP_USE_INTERNAL_DATE = true;
565         } else if ($my_prefs['sort_field'] == 'DATE') {
566                 $IMAP_USE_INTERNAL_DATE = false;
567         }
568         //echo '<!-- conn sort_field: '.$my_prefs['sort_field'].' //-->';
569         
570         //check input
571         if (empty($host)) {
572                 $iil_error = "Empty host";
573                 $iil_errornum = -1;
574                 return false;
575         }
576         if (empty($user)) {
577                 $iil_error = "Empty user";
578                 $iil_errornum = -1;
579                 return false;
580         }
581         if (empty($password)) {
582                 $iil_error = "Empty password";
583                 $iil_errornum = -1;
584                 return false;
585         }
586         if (!$ICL_PORT) {
587                 $ICL_PORT = 143;
588         }
589     
590         //check for SSL
591         if ($ICL_SSL) {
592                 $host = $ICL_SSL . '://' . $host;
593         }
594         
595         //open socket connection
596         $conn->fp = fsockopen($host, $ICL_PORT, $errno, $errstr, 10);
597         if (!$conn->fp) {
598                 $iil_error = "Could not connect to $host at port $ICL_PORT: $errstr";
599                 $iil_errornum = -2;
600                 return false;
601         }
602
603         $iil_error .= "Socket connection established\r\n";
604         $line       = iil_ReadLine($conn->fp, 4096);
605
606         // RFC3501 [7.1] optional CAPABILITY response
607         if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
608                 $conn->capability = explode(' ', strtoupper($matches[1]));
609         }
610
611         $conn->message .= $line;
612
613         if (strcasecmp($auth_method, "check") == 0) {
614                 //check for supported auth methods
615                 if (iil_C_GetCapability($conn, 'AUTH=CRAM-MD5') || iil_C_GetCapability($conn, 'AUTH=CRAM_MD5')) {
616                         $auth_method = 'auth';
617                 }
618                 else {
619                         //default to plain text auth
620                         $auth_method = 'plain';
621                 }
622         }
623
624         if (strcasecmp($auth_method, 'auth') == 0) {
625                 $conn->message .= "Trying CRAM-MD5\n";
626
627                 //do CRAM-MD5 authentication
628                 iil_PutLine($conn->fp, "a000 AUTHENTICATE CRAM-MD5");
629                 $line = trim(iil_ReadLine($conn->fp, 1024));
630
631                 $conn->message .= "$line\n";
632
633                 if ($line[0] == '+') {
634                         $conn->message .= 'Got challenge: ' . htmlspecialchars($line) . "\n";
635
636                         //got a challenge string, try CRAM-5
637                         $result = iil_C_Authenticate($conn, $user, $password, substr($line,2));
638                         
639                         // stop if server sent BYE response
640                         if($result == -3) {
641                                 $iil_error = $conn->error;
642                                 $iil_errornum = $conn->errorNum;
643                                 return false;
644                         }
645                         $conn->message .= "Tried CRAM-MD5: $result \n";
646                 } else {
647                         $conn->message .='No challenge ('.htmlspecialchars($line)."), try plain\n";
648                         $auth = 'plain';
649                 }
650         }
651                 
652         if ((!$result)||(strcasecmp($auth, "plain") == 0)) {
653                 //do plain text auth
654                 $result = iil_C_Login($conn, $user, $password);
655                 $conn->message .= "Tried PLAIN: $result \n";
656         }
657                 
658         $conn->message .= $auth;
659                         
660         if (!is_int($result)) {
661                 iil_C_Namespace($conn);
662                 return $conn;
663         } else {
664                 $iil_error = $conn->error;
665                 $iil_errornum = $conn->errorNum;
666                 return false;
667         }
668 }
669
670 function iil_Close(&$conn) {
671         iil_C_WriteCache($conn);
672         if (iil_PutLine($conn->fp, "I LOGOUT")) {
673                 fgets($conn->fp, 1024);
674                 fclose($conn->fp);
675                 $conn->fp = false;
676         }
677 }
678
679 function iil_ClearCache($user, $host) {
680 }
681
682 function iil_C_WriteCache(&$conn) {
683         //echo "<!-- doing iil_C_WriteCache //-->\n";
684         if (!$conn->do_cache) return false;
685         
686         if (is_array($conn->cache)) {
687                 while (list($folder,$data)=each($conn->cache)) {
688                         if ($folder && is_array($data) && $conn->cache_dirty[$folder]) {
689                                 $key = $folder.".imap";
690                                 $result = cache_write($conn->user, $conn->host, $key, $data, true);
691                                 //echo "<!-- writing $key $data: $result //-->\n";
692                         }
693                 }
694         }
695 }
696
697 function iil_C_EnableCache(&$conn) {
698         $conn->do_cache = true;
699 }
700
701 function iil_C_DisableCache(&$conn) {
702         $conn->do_cache = false;
703 }
704
705 function iil_C_LoadCache(&$conn, $folder) {
706         if (!$conn->do_cache) {
707             return false;
708         }
709     
710         $key = $folder.'.imap';
711         if (!is_array($conn->cache[$folder])) {
712                 $conn->cache[$folder]       = cache_read($conn->user, $conn->host, $key);
713                 $conn->cache_dirty[$folder] = false;
714         }
715 }
716
717 function iil_C_ExpireCachedItems(&$conn, $folder, $message_set) {
718         
719         if (!$conn->do_cache) {
720                 return; //caching disabled
721         }
722         if (!is_array($conn->cache[$folder])) {
723                 return; //cache not initialized|empty
724         }
725         if (count($conn->cache[$folder]) == 0) {
726                 return; //cache not initialized|empty
727         }
728     
729         $uids = iil_C_FetchHeaderIndex($conn, $folder, $message_set, 'UID');
730         $num_removed = 0;
731         if (is_array($uids)) {
732                 //echo "<!-- unsetting: ".implode(",",$uids)." //-->\n";
733                 while (list($n,$uid)=each($uids)) {
734                         unset($conn->cache[$folder][$uid]);
735                         //$conn->cache[$folder][$uid] = false;
736                         //$num_removed++;
737                 }
738                 $conn->cache_dirty[$folder] = true;
739
740                 //echo '<!--'."\n";
741                 //print_r($conn->cache);
742                 //echo "\n".'//-->'."\n";
743         } else {
744                 echo "<!-- failed to get uids: $message_set //-->\n";
745         }
746         
747         /*
748         if ($num_removed>0) {
749                 $new_cache;
750                 reset($conn->cache[$folder]);
751                 while (list($uid,$item)=each($conn->cache[$folder])) {
752                         if ($item) $new_cache[$uid] = $conn->cache[$folder][$uid];
753                 }
754                 $conn->cache[$folder] = $new_cache;
755         }
756         */
757 }
758
759 function iil_ExplodeQuotedString($delimiter, $string) {
760         $quotes = explode('"', $string);
761         while ( list($key, $val) = each($quotes)) {
762                 if (($key % 2) == 1) {
763                         $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
764                 }
765         }
766         $string = implode('"', $quotes);
767         
768         $result = explode($delimiter, $string);
769         while ( list($key, $val) = each($result) ) {
770                 $result[$key] = str_replace('_!@!_', $delimiter, $result[$key]);
771         }
772     
773         return $result;
774 }
775
776 function iil_CheckForRecent($host, $user, $password, $mailbox) {
777         if (empty($mailbox)) {
778                 $mailbox = 'INBOX';
779         }
780     
781         $conn = iil_Connect($host, $user, $password, 'plain');
782         $fp   = $conn->fp;
783         if ($fp) {
784                 iil_PutLine($fp, "a002 EXAMINE \"".iil_Escape($mailbox)."\"");
785                 do {
786                         $line=chop(iil_ReadLine($fp, 300));
787                         $a=explode(' ', $line);
788                         if (($a[0] == '*') && (strcasecmp($a[2], 'RECENT') == 0)) {
789                             $result = (int) $a[1];
790                         }
791                 } while (!iil_StartsWith($a[0], 'a002', true));
792
793                 iil_PutLine($fp, "a003 LOGOUT");
794                 fclose($fp);
795         } else {
796             $result = -2;
797         }
798     
799         return $result;
800 }
801
802 function iil_C_Select(&$conn, $mailbox) {
803
804         if (empty($mailbox)) {
805                 return false;
806         }
807         if (strcmp($conn->selected, $mailbox) == 0) {
808                 return true;
809         }
810     
811         iil_C_LoadCache($conn, $mailbox);
812         
813         if (iil_PutLine($conn->fp, "sel1 SELECT \"".iil_Escape($mailbox).'"')) {
814                 do {
815                         $line = chop(iil_ReadLine($conn->fp, 300));
816                         $a = explode(' ', $line);
817                         if (count($a) == 3) {
818                                 if (strcasecmp($a[2], 'EXISTS') == 0) {
819                                         $conn->exists = (int) $a[1];
820                                 }
821                                 if (strcasecmp($a[2], 'RECENT') == 0) {
822                                         $conn->recent = (int) $a[1];
823                                 }
824                         }
825                         else if (preg_match('/\[?PERMANENTFLAGS\s+\(([^\)]+)\)\]/U', $line, $match)) {
826                                 $conn->permanentflags = explode(' ', $match[1]);
827                         }
828                 } while (!iil_StartsWith($line, 'sel1', true));
829
830                 $a = explode(' ', $line);
831
832                 if (strcasecmp($a[1], 'OK') == 0) {
833                         $conn->selected = $mailbox;
834                         return true;
835                 }
836         }
837         return false;
838 }
839
840 function iil_C_CheckForRecent(&$conn, $mailbox) {
841         if (empty($mailbox)) {
842                 $mailbox = 'INBOX';
843         }
844     
845         iil_C_Select($conn, $mailbox);
846         if ($conn->selected == $mailbox) {
847                 return $conn->recent;
848         }
849         return false;
850 }
851
852 function iil_C_CountMessages(&$conn, $mailbox, $refresh = false) {
853         if ($refresh) {
854                 $conn->selected = '';
855         }
856         
857         iil_C_Select($conn, $mailbox);
858         if ($conn->selected == $mailbox) {
859                 return $conn->exists;
860         }
861         return false;
862 }
863
864 function iil_SplitHeaderLine($string) {
865         $pos=strpos($string, ':');
866         if ($pos>0) {
867                 $res[0] = substr($string, 0, $pos);
868                 $res[1] = trim(substr($string, $pos+1));
869                 return $res;
870         }
871         return $string;
872 }
873
874 function iil_StrToTime($str) {
875         $IMAP_MONTHS    = $GLOBALS['IMAP_MONTHS'];
876         $IMAP_SERVER_TZ = $GLOBALS['IMAP_SERVER_TZ'];
877                 
878         if ($str) {
879             $time1 = strtotime($str);
880         }
881         if ($time1 && $time1 != -1) {
882             return $time1-$IMAP_SERVER_TZ;
883         }
884         //echo '<!--'.$str.'//-->';
885         
886         //replace double spaces with single space
887         $str = trim($str);
888         $str = str_replace('  ', ' ', $str);
889         
890         //strip off day of week
891         $pos = strpos($str, ' ');
892         if (!is_numeric(substr($str, 0, $pos))) {
893             $str = substr($str, $pos+1);
894         }
895         //explode, take good parts
896         $a = explode(' ', $str);
897
898         $month_str = $a[1];
899         $month     = $IMAP_MONTHS[$month_str];
900         $day       = (int)$a[0];
901         $year      = (int)$a[2];
902         $time      = $a[3];
903         $tz_str    = $a[4];
904         $tz        = substr($tz_str, 0, 3);
905         $ta        = explode(':', $time);
906         $hour      = (int)$ta[0]-(int)$tz;
907         $minute    = (int)$ta[1];
908         $second    = (int)$ta[2];
909         
910         //make UNIX timestamp
911         $time2 = mktime($hour, $minute, $second, $month, $day, $year);
912         //echo '<!--'.$time1.' '.$time2.' //-->'."\n";
913         return $time2;
914 }
915
916 function iil_C_Sort(&$conn, $mailbox, $field, $add='', $is_uid=FALSE,
917     $encoding = 'US-ASCII') {
918
919         $field = strtoupper($field);
920         if ($field == 'INTERNALDATE') {
921             $field = 'ARRIVAL';
922         }
923         
924         $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1,
925         'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1);
926         
927         if (!$fields[$field]) {
928             return false;
929         }
930
931         /*  Do "SELECT" command */
932         if (!iil_C_Select($conn, $mailbox)) {
933             return false;
934         }
935     
936         $is_uid = $is_uid ? 'UID ' : '';
937         
938         if (!empty($add)) {
939             $add = " $add";
940         }
941
942         $command  = 's ' . $is_uid . 'SORT (' . $field . ') ';
943         $command .= $encoding . ' ALL' . $add;
944         $line     = $data = '';
945         
946         if (!iil_PutLineC($conn->fp, $command)) {
947             return false;
948         }
949         do {
950                 $line = chop(iil_ReadLine($conn->fp, 1024));
951                 if (iil_StartsWith($line, '* SORT')) {
952                         $data .= ($data ? ' ' : '') . substr($line, 7);
953                 } else if (preg_match('/^[0-9 ]+$/', $line)) {
954                         $data .= $line;
955                 }
956         } while (!iil_StartsWith($line, 's ', true));
957         
958         $result_code = iil_ParseResult($line);
959         
960         if ($result_code != 0) {
961                 $conn->error = 'iil_C_Sort: ' . $line . "\n";
962                 return false;
963         }
964         
965         $out = explode(' ',$data);
966         return $out;
967 }
968
969 function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field,
970     $normalize=true) {
971         global $IMAP_USE_INTERNAL_DATE;
972         
973         $c=0;
974         $result=array();
975         $fp = $conn->fp;
976                 
977         if (empty($index_field)) {
978             $index_field = 'DATE';
979         }
980         $index_field = strtoupper($index_field);
981         
982         list($from_idx, $to_idx) = explode(':', $message_set);
983         if (empty($message_set) || (isset($to_idx)
984             && (int)$from_idx > (int)$to_idx)) {
985                 return false;
986         }
987         
988         //$fields_a['DATE'] = ($IMAP_USE_INTERNAL_DATE?6:1);
989         $fields_a['DATE']         = 1;
990         $fields_a['INTERNALDATE'] = 6;
991         $fields_a['FROM']         = 1;
992         $fields_a['REPLY-TO']     = 1;
993         $fields_a['SENDER']       = 1;
994         $fields_a['TO']           = 1;
995         $fields_a['SUBJECT']      = 1;
996         $fields_a['UID']          = 2;
997         $fields_a['SIZE']         = 2;
998         $fields_a['SEEN']         = 3;
999         $fields_a['RECENT']       = 4;
1000         $fields_a['DELETED']      = 5;
1001         
1002         $mode=$fields_a[$index_field];
1003         if (!($mode > 0)) {
1004             return false;
1005         }
1006     
1007         /*  Do "SELECT" command */
1008         if (!iil_C_Select($conn, $mailbox)) {
1009             return false;
1010         }
1011     
1012         /* FETCH date,from,subject headers */
1013         if ($mode == 1) {
1014                 $key     = 'fhi' . ($c++);
1015                 $request = $key . " FETCH $message_set (BODY.PEEK[HEADER.FIELDS ($index_field)])";
1016                 if (!iil_PutLine($fp, $request)) {
1017                     return false;
1018                 }
1019                 do {
1020                         
1021                         $line=chop(iil_ReadLine($fp, 200));
1022                         $a=explode(' ', $line);
1023                         if (($line[0] == '*') && ($a[2] == 'FETCH')
1024                             && ($line[strlen($line)-1] != ')')) {
1025                                 $id=$a[1];
1026
1027                                 $str=$line=chop(iil_ReadLine($fp, 300));
1028
1029                                 while ($line[0] != ')') {                                       //caution, this line works only in this particular case
1030                                         $line=chop(iil_ReadLine($fp, 300));
1031                                         if ($line[0] != ')') {
1032                                                 if (ord($line[0]) <= 32) {                      //continuation from previous header line
1033                                                         $str.= ' ' . trim($line);
1034                                                 }
1035                                                 if ((ord($line[0]) > 32) || (strlen($line[0]) == 0)) {
1036                                                         list($field, $string) = iil_SplitHeaderLine($str);
1037                                                         if (strcasecmp($field, 'date') == 0) {
1038                                                                 $result[$id] = iil_StrToTime($string);
1039                                                         } else {
1040                                                                 $result[$id] = str_replace('"', '', $string);
1041                                                                 if ($normalize) {
1042                                                                     $result[$id] = strtoupper($result[$id]);
1043                                                                 }
1044                                                         }
1045                                                         $str=$line;
1046                                                 }
1047                                         }
1048                                 }
1049                         }
1050                         /*
1051                         $end_pos = strlen($line)-1;
1052                         if (($line[0]=="*") && ($a[2]=="FETCH") && ($line[$end_pos]=="}")) {
1053                                 $id = $a[1];
1054                                 $pos = strrpos($line, "{")+1;
1055                                 $bytes = (int)substr($line, $pos, $end_pos-$pos);
1056                                 $received = 0;
1057                                 do {
1058                                         $line      = iil_ReadLine($fp, 0);
1059                                         $received += strlen($line);
1060                                         $line      = chop($line);
1061                                         
1062                                         if ($received>$bytes) {
1063                                                 break;
1064                                         } else if (!$line) {
1065                                                 continue;
1066                                         }
1067
1068                                         list($field, $string) = explode(': ', $line);
1069                                         
1070                                         if (strcasecmp($field, 'date') == 0) {
1071                                                 $result[$id] = iil_StrToTime($string);
1072                                         } else if ($index_field != 'DATE') {
1073                                                 $result[$id]=strtoupper(str_replace('"', '', $string));
1074                                         }
1075                                 } while ($line[0] != ')');
1076                         } else {
1077                                 //one line response, not expected so ignore                             
1078                         }
1079                         */
1080                 } while (!iil_StartsWith($line, $key, true));
1081
1082         }else if ($mode == 6) {
1083
1084                 $key     = 'fhi' . ($c++);
1085                 $request = $key . " FETCH $message_set (INTERNALDATE)";
1086                 if (!iil_PutLine($fp, $request)) {
1087                     return false;
1088                 }
1089                 do {
1090                         $line=chop(iil_ReadLine($fp, 200));
1091                         if ($line[0] == '*') {
1092                                 /*
1093                                  * original:
1094                                  * "* 10 FETCH (INTERNALDATE "31-Jul-2002 09:18:02 -0500")"
1095                                  */
1096                                 $paren_pos = strpos($line, '(');
1097                                 $foo       = substr($line, 0, $paren_pos);
1098                                 $a         = explode(' ', $foo);
1099                                 $id        = $a[1];
1100                                 
1101                                 $open_pos  = strpos($line, '"') + 1;
1102                                 $close_pos = strrpos($line, '"');
1103                                 if ($open_pos && $close_pos) {
1104                                         $len         = $close_pos - $open_pos;
1105                                         $time_str    = substr($line, $open_pos, $len);
1106                                         $result[$id] = strtotime($time_str);
1107                                 }
1108                         } else {
1109                                 $a = explode(' ', $line);
1110                         }
1111                 } while (!iil_StartsWith($a[0], $key, true));
1112         } else {
1113                 if ($mode >= 3) {
1114                     $field_name = 'FLAGS';
1115                 } else if ($index_field == 'SIZE') {
1116                     $field_name = 'RFC822.SIZE';
1117                 } else {
1118                     $field_name = $index_field;
1119                 }
1120         
1121                 /*                      FETCH uid, size, flags          */
1122                 $key     = 'fhi' .($c++);
1123                 $request = $key . " FETCH $message_set ($field_name)";
1124
1125                 if (!iil_PutLine($fp, $request)) {
1126                     return false;
1127                 }
1128                 do {
1129                         $line=chop(iil_ReadLine($fp, 200));
1130                         $a = explode(' ', $line);
1131                         if (($line[0] == '*') && ($a[2] == 'FETCH')) {
1132                                 $line = str_replace('(', '', $line);
1133                                 $line = str_replace(')', '', $line);
1134                                 $a    = explode(' ', $line);
1135                                 
1136                                 $id = $a[1];
1137
1138                                 if (isset($result[$id])) {
1139                                     continue; //if we already got the data, skip forward
1140                                 }
1141                                 if ($a[3]!=$field_name) {
1142                                         continue;  //make sure it's returning what we requested
1143                                 }
1144                 
1145                                 /*  Caution, bad assumptions, next several lines */
1146                                 if ($mode == 2) {
1147                                     $result[$id] = $a[4];
1148                                 } else {
1149                                         $haystack    = strtoupper($line);
1150                                         $result[$id] = (strpos($haystack, $index_field) > 0 ? "F" : "N");
1151                                 }
1152                         }
1153                 } while (!iil_StartsWith($line, $key, true));
1154         }
1155
1156         //check number of elements...
1157         list($start_mid, $end_mid) = explode(':', $message_set);
1158         if (is_numeric($start_mid) && is_numeric($end_mid)) {
1159                 //count how many we should have
1160                 $should_have = $end_mid - $start_mid +1;
1161                 
1162                 //if we have less, try and fill in the "gaps"
1163                 if (count($result) < $should_have) {
1164                         for ($i=$start_mid; $i<=$end_mid; $i++) {
1165                                 if (!isset($result[$i])) {
1166                                         $result[$i] = '';
1167                                 }
1168                         }
1169                 }
1170         }
1171         return $result; 
1172 }
1173
1174 function iil_CompressMessageSet($message_set) {
1175         //given a comma delimited list of independent mid's, 
1176         //compresses by grouping sequences together
1177         
1178         //if less than 255 bytes long, let's not bother
1179         if (strlen($message_set)<255) {
1180             return $message_set;
1181         }
1182     
1183         //see if it's already been compress
1184         if (strpos($message_set, ':') !== false) {
1185             return $message_set;
1186         }
1187     
1188         //separate, then sort
1189         $ids = explode(',', $message_set);
1190         sort($ids);
1191         
1192         $result = array();
1193         $start  = $prev = $ids[0];
1194
1195         foreach ($ids as $id) {
1196                 $incr = $id - $prev;
1197                 if ($incr > 1) {                        //found a gap
1198                         if ($start == $prev) {
1199                             $result[] = $prev;  //push single id
1200                         } else {
1201                             $result[] = $start . ':' . $prev;   //push sequence as start_id:end_id
1202                         }
1203                         $start = $id;                   //start of new sequence
1204                 }
1205                 $prev = $id;
1206         }
1207
1208         //handle the last sequence/id
1209         if ($start==$prev) {
1210             $result[] = $prev;
1211         } else {
1212             $result[] = $start.':'.$prev;
1213         }
1214     
1215         //return as comma separated string
1216         return implode(',', $result);
1217 }
1218
1219 function iil_C_UIDsToMIDs(&$conn, $mailbox, $uids) {
1220         if (!is_array($uids) || count($uids) == 0) {
1221             return array();
1222         }
1223         return iil_C_Search($conn, $mailbox, 'UID ' . implode(',', $uids));
1224 }
1225
1226 function iil_C_UIDToMID(&$conn, $mailbox, $uid) {
1227         $result = iil_C_UIDsToMIDs($conn, $mailbox, array($uid));
1228         if (count($result) == 1) {
1229             return $result[0];
1230         }
1231         return false;
1232 }
1233
1234 function iil_C_FetchUIDs(&$conn,$mailbox) {
1235         global $clock;
1236         
1237         $num = iil_C_CountMessages($conn, $mailbox);
1238         if ($num == 0) {
1239             return array();
1240         }
1241         $message_set = '1' . ($num>1?':' . $num:'');
1242         
1243         //if cache not enabled, just call iil_C_FetchHeaderIndex on 'UID' field
1244         if (!$conn->do_cache)
1245                 return iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
1246
1247         //otherwise, let's check cache first
1248         $key        = $mailbox.'.uids';
1249         $cache_good = true;
1250         if ($conn->uid_cache) {
1251             $data = $conn->uid_cache;
1252         } else {
1253             $data = cache_read($conn->user, $conn->host, $key);
1254         }
1255     
1256         //was anything cached at all?
1257         if ($data === false) {
1258             $cache_good = -1;
1259         }
1260     
1261         //make sure number of messages were the same
1262         if ($cache_good > 0 && $data['n'] != $num) {
1263             $cache_good = -2;
1264         }
1265     
1266         //if everything's okay so far...
1267         if ($cache_good > 0) {
1268                 //check UIDs of highest mid with current and cached
1269                 $temp = iil_C_Search($conn, $mailbox, 'UID ' . $data['d'][$num]);
1270                 if (!$temp || !is_array($temp) || $temp[0] != $num) {
1271                     $cache_good = -3;
1272                 }
1273         }
1274
1275         //if cached data's good, return it
1276         if ($cache_good > 0) {
1277                 return $data['d'];
1278         }
1279
1280         //otherwise, we need to fetch it
1281         $data      = array('n' => $num, 'd' => array());
1282         $data['d'] = iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
1283     
1284         cache_write($conn->user, $conn->host, $key, $data);
1285         $conn->uid_cache = $data;
1286         return $data['d'];
1287 }
1288
1289 function iil_SortThreadHeaders($headers, $index_a, $uids) {
1290         asort($index_a);
1291         $result = array();
1292         foreach ($index_a as $mid=>$foobar) {
1293                 $uid = $uids[$mid];
1294                 $result[$uid] = $headers[$uid];
1295         }
1296         return $result;
1297 }
1298
1299 function iil_C_FetchThreadHeaders(&$conn, $mailbox, $message_set) {
1300         global $clock;
1301         global $index_a;
1302         
1303         list($from_idx, $to_idx) = explode(':', $message_set);
1304         if (empty($message_set) || (isset($to_idx)
1305         && (int)$from_idx > (int)$to_idx)) {
1306                 return false;
1307         }
1308
1309         $result = array();
1310         $uids   = iil_C_FetchUIDs($conn, $mailbox);
1311         $debug  = false;
1312         
1313         /* Get cached records where possible */
1314         if ($conn->do_cache) {
1315                 $cached = cache_read($conn->user, $conn->host, $mailbox.'.thhd');
1316                 if ($cached && is_array($uids) && count($uids)>0) {
1317                         $needed_set = '';
1318                         foreach ($uids as $id=>$uid) {
1319                                 if ($cached[$uid]) {
1320                                         $result[$uid]     = $cached[$uid];
1321                                         $result[$uid]->id = $id;
1322                                 } else {
1323                                     $needed_set .= ($needed_set ? ',' : '') . $id;
1324                                 }
1325                         }
1326                         if ($needed_set) {
1327                             $message_set = $needed_set;
1328                         } else {
1329                             $message_set = '';
1330                         }
1331                 }
1332         }
1333         $message_set = iil_CompressMessageSet($message_set);
1334         if ($debug) {
1335             echo "Still need: ".$message_set;
1336         }
1337     
1338         /* if we're missing any, get them */
1339         if ($message_set) {
1340                 /* FETCH date,from,subject headers */
1341                 $key        = 'fh';
1342                 $fp         = $conn->fp;
1343                 $request    = $key . " FETCH $message_set ";
1344                 $request   .= "(BODY.PEEK[HEADER.FIELDS (SUBJECT MESSAGE-ID IN-REPLY-TO)])";
1345                 $mid_to_id  = array();
1346                 if (!iil_PutLine($fp, $request)) {
1347                     return false;
1348                 }
1349                 do {
1350                         $line = chop(iil_ReadLine($fp, 1024));
1351                         if ($debug) {
1352                             echo $line . "\n";
1353                         }
1354                         if (ereg('\{[0-9]+\}$', $line)) {
1355                                 $a       = explode(' ', $line);
1356                                 $new = array();
1357
1358                                 $new_thhd = new iilThreadHeader;
1359                                 $new_thhd->id = $a[1];
1360                                 do {
1361                                         $line = chop(iil_ReadLine($fp, 1024), "\r\n");
1362                                         if (iil_StartsWithI($line, 'Message-ID:')
1363                                                 || (iil_StartsWithI($line,'In-Reply-To:'))
1364                                                 || (iil_StartsWithI($line,'SUBJECT:'))) {
1365
1366                                                 $pos        = strpos($line, ':');
1367                                                 $field_name = substr($line, 0, $pos);
1368                                                 $field_val  = substr($line, $pos+1);
1369
1370                                                 $new[strtoupper($field_name)] = trim($field_val);
1371
1372                                         } else if (ereg('^[[:space:]]', $line)) {
1373                                                 $new[strtoupper($field_name)] .= trim($line);
1374                                         }
1375                                 } while ($line[0] != ')');
1376                 
1377                                 $new_thhd->sbj = $new['SUBJECT'];
1378                                 $new_thhd->mid = substr($new['MESSAGE-ID'], 1, -1);
1379                                 $new_thhd->irt = substr($new['IN-REPLY-TO'], 1, -1);
1380                                 
1381                                 $result[$uids[$new_thhd->id]] = $new_thhd;
1382                         }
1383                 } while (!iil_StartsWith($line, 'fh'));
1384         }
1385         
1386         /* sort headers */
1387         if (is_array($index_a)) {
1388                 $result = iil_SortThreadHeaders($result, $index_a, $uids);      
1389         }
1390         
1391         /* write new set to cache */
1392         if ($conn->do_cache) {
1393                 if (count($result)!=count($cached)) {
1394                         cache_write($conn->user, $conn->host, $mailbox . '.thhd', $result);
1395                 }
1396         }
1397         
1398         //echo 'iil_FetchThreadHeaders:'."\n";
1399         //print_r($result);
1400         
1401         return $result;
1402 }
1403
1404 function iil_C_BuildThreads2(&$conn, $mailbox, $message_set, &$clock) {
1405         global $index_a;
1406
1407         list($from_idx, $to_idx) = explode(':', $message_set);
1408         if (empty($message_set) || (isset($to_idx)
1409                 && (int)$from_idx > (int)$to_idx)) {
1410                 return false;
1411         }
1412     
1413         $result    = array();
1414         $roots     = array();
1415         $root_mids = array();
1416         $sub_mids  = array();
1417         $strays    = array();
1418         $messages  = array();
1419         $fp        = $conn->fp;
1420         $debug     = false;
1421         
1422         $sbj_filter_pat = '[a-zA-Z]{2,3}(\[[0-9]*\])?:([[:space:]]*)';
1423         
1424         /*  Do "SELECT" command */
1425         if (!iil_C_Select($conn, $mailbox)) {
1426             return false;
1427         }
1428     
1429         /* FETCH date,from,subject headers */
1430         $mid_to_id = array();
1431         $messages  = array();
1432         $headers   = iil_C_FetchThreadHeaders($conn, $mailbox, $message_set);
1433         if ($clock) {
1434             $clock->register('fetched headers');
1435         }
1436     
1437         if ($debug) {
1438             print_r($headers);
1439         }
1440     
1441         /* go through header records */
1442         foreach ($headers as $header) {
1443                 //$id = $header['i'];
1444                 //$new = array('id'=>$id, 'MESSAGE-ID'=>$header['m'], 
1445                 //                      'IN-REPLY-TO'=>$header['r'], 'SUBJECT'=>$header['s']);
1446                 $id  = $header->id;
1447                 $new = array('id' => $id, 'MESSAGE-ID' => $header->mid, 
1448                         'IN-REPLY-TO' => $header->irt, 'SUBJECT' => $header->sbj);
1449
1450                 /* add to message-id -> mid lookup table */
1451                 $mid_to_id[$new['MESSAGE-ID']] = $id;
1452                 
1453                 /* if no subject, use message-id */
1454                 if (empty($new['SUBJECT'])) {
1455                     $new['SUBJECT'] = $new['MESSAGE-ID'];
1456                 }
1457         
1458                 /* if subject contains 'RE:' or has in-reply-to header, it's a reply */
1459                 $sbj_pre ='';
1460                 $has_re = false;
1461                 if (eregi($sbj_filter_pat, $new['SUBJECT'])) {
1462                     $has_re = true;
1463                 }
1464                 if ($has_re||$new['IN-REPLY-TO']) {
1465                     $sbj_pre = 'RE:';
1466                 }
1467         
1468                 /* strip out 're:', 'fw:' etc */
1469                 if ($has_re) {
1470                     $sbj = ereg_replace($sbj_filter_pat, '', $new['SUBJECT']);
1471                 } else {
1472                     $sbj = $new['SUBJECT'];
1473                 }
1474                 $new['SUBJECT'] = $sbj_pre.$sbj;
1475                 
1476                 
1477                 /* if subject not a known thread-root, add to list */
1478                 if ($debug) {
1479                     echo $id . ' ' . $new['SUBJECT'] . "\t" . $new['MESSAGE-ID'] . "\n";
1480                 }
1481                 $root_id = $roots[$sbj];
1482                 
1483                 if ($root_id && ($has_re || !$root_in_root[$root_id])) {
1484                         if ($debug) {
1485                             echo "\tfound root: $root_id\n";
1486                         }
1487                         $sub_mids[$new['MESSAGE-ID']] = $root_id;
1488                         $result[$root_id][]           = $id;
1489                 } else if (!isset($roots[$sbj]) || (!$has_re && $root_in_root[$root_id])) {
1490                         /* try to use In-Reply-To header to find root 
1491                                 unless subject contains 'Re:' */
1492                         if ($has_re&&$new['IN-REPLY-TO']) {
1493                                 if ($debug) {
1494                                     echo "\tlooking: ".$new['IN-REPLY-TO']."\n";
1495                                 }
1496                                 //reply to known message?
1497                                 $temp = $sub_mids[$new['IN-REPLY-TO']];
1498                                 
1499                                 if ($temp) {
1500                                         //found it, root:=parent's root
1501                                         if ($debug) {
1502                                             echo "\tfound parent: ".$new['SUBJECT']."\n";
1503                                         }
1504                                         $result[$temp][]              = $id;
1505                                         $sub_mids[$new['MESSAGE-ID']] = $temp;
1506                                         $sbj                          = '';
1507                                 } else {
1508                                         //if we can't find referenced parent, it's a "stray"
1509                                         $strays[$id] = $new['IN-REPLY-TO'];
1510                                 }
1511                         }
1512                         
1513                         //add subject as root
1514                         if ($sbj) {
1515                                 if ($debug) {
1516                                     echo "\t added to root\n";
1517                                 }
1518                                 $roots[$sbj]                  = $id;
1519                                 $root_in_root[$id]            = !$has_re;
1520                                 $sub_mids[$new['MESSAGE-ID']] = $id;
1521                                 $result[$id]                  = array($id);
1522                         }
1523                         if ($debug) {
1524                             echo $new['MESSAGE-ID'] . "\t" . $sbj . "\n";
1525                         }
1526                 }
1527         }
1528         
1529         //now that we've gone through all the messages,
1530         //go back and try and link up the stray threads
1531         if (count($strays) > 0) {
1532                 foreach ($strays as $id=>$irt) {
1533                         $root_id = $sub_mids[$irt];
1534                         if (!$root_id || $root_id==$id) {
1535                             continue;
1536                         }
1537                         $result[$root_id] = array_merge($result[$root_id],$result[$id]);
1538                         unset($result[$id]);
1539                 }
1540         }
1541         
1542         if ($clock) {
1543             $clock->register('data prepped');
1544         }
1545     
1546         if ($debug) {
1547             print_r($roots);
1548         }
1549
1550         return $result;
1551 }
1552
1553 function iil_SortThreads(&$tree, $index, $sort_order = 'ASC') {
1554         if (!is_array($tree) || !is_array($index)) {
1555             return false;
1556         }
1557     
1558         //create an id to position lookup table
1559         $i = 0;
1560         foreach ($index as $id=>$val) {
1561                 $i++;
1562                 $index[$id] = $i;
1563         }
1564         $max = $i+1;
1565         
1566         //for each tree, set array key to position
1567         $itree = array();
1568         foreach ($tree as $id=>$node) {
1569                 if (count($tree[$id])<=1) {
1570                         //for "threads" with only one message, key is position of that message
1571                         $n         = $index[$id];
1572                         $itree[$n] = array($n=>$id);
1573                 } else {
1574                         //for "threads" with multiple messages, 
1575                         $min   = $max;
1576                         $new_a = array();
1577                         foreach ($tree[$id] as $mid) {
1578                                 $new_a[$index[$mid]] = $mid;            //create new sub-array mapping position to id
1579                                 $pos                 = $index[$mid];
1580                                 if ($pos&&$pos<$min) {
1581                                     $min = $index[$mid];        //find smallest position
1582                                 }
1583                         }
1584                         $n = $min;      //smallest position of child is thread position
1585                         
1586                         //assign smallest position to root level key
1587                         //set children array to one created above
1588                         ksort($new_a);
1589                         $itree[$n] = $new_a;
1590                 }
1591         }
1592         
1593         //sort by key, this basically sorts all threads
1594         ksort($itree);
1595         $i   = 0;
1596         $out = array();
1597         foreach ($itree as $k=>$node) {
1598                 $out[$i] = $itree[$k];
1599                 $i++;
1600         }
1601         
1602         return $out;
1603 }
1604
1605 function iil_IndexThreads(&$tree) {
1606         /* creates array mapping mid to thread id */
1607         
1608         if (!is_array($tree)) {
1609             return false;
1610         }
1611     
1612         $t_index = array();
1613         foreach ($tree as $pos=>$kids) {
1614                 foreach ($kids as $kid) $t_index[$kid] = $pos;
1615         }
1616         
1617         return $t_index;
1618 }
1619
1620 function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false)
1621 {
1622         global $IMAP_USE_INTERNAL_DATE;
1623         
1624         $result = array();
1625         $fp     = $conn->fp;
1626         
1627         list($from_idx, $to_idx) = explode(':', $message_set);
1628         if (empty($message_set) || (isset($to_idx)
1629                 && (int)$from_idx > (int)$to_idx)) {
1630                 return false;
1631         }
1632                 
1633         /*  Do "SELECT" command */
1634         if (!iil_C_Select($conn, $mailbox)) {
1635                 $conn->error = "Couldn't select $mailbox";
1636                 return false;
1637         }
1638                 
1639         /* Get cached records where possible */
1640         if ($conn->do_cache) {
1641                 $uids = iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, "UID");
1642                 if (is_array($uids) && count($conn->cache[$mailbox]>0)) {
1643                         $needed_set = '';
1644                         while (list($id,$uid)=each($uids)) {
1645                                 if ($conn->cache[$mailbox][$uid]) {
1646                                         $result[$id]     = $conn->cache[$mailbox][$uid];
1647                                         $result[$id]->id = $id;
1648                                 } else {
1649                                     $needed_set.=($needed_set ? ',': '') . $id;
1650                                 }
1651                         }
1652                         //echo "<!-- iil_C_FetchHeader\nMessage Set: $message_set\nNeeded Set:$needed_set\n//-->\n";
1653                         if ($needed_set) {
1654                                 $message_set = iil_CompressMessageSet($needed_set);
1655                         } else {
1656                                 return $result;
1657                         }
1658                 }
1659         }
1660
1661         /* FETCH uid, size, flags and headers */
1662         $key      = 'FH12';
1663         $request  = $key . ($uidfetch ? ' UID' : '') . " FETCH $message_set ";
1664         $request .= "(UID RFC822.SIZE FLAGS INTERNALDATE BODY.PEEK[HEADER.FIELDS ";
1665         $request .= "(DATE FROM TO SUBJECT REPLY-TO IN-REPLY-TO CC BCC ";
1666         $request .= "CONTENT-TRANSFER-ENCODING CONTENT-TYPE MESSAGE-ID ";
1667         $request .= "REFERENCES DISPOSITION-NOTIFICATION-TO X-PRIORITY)])";
1668
1669         if (!iil_PutLine($fp, $request)) {
1670                 return false;
1671         }
1672         do {
1673                 $line = chop(iil_ReadLine($fp, 1024));
1674                 $a    = explode(' ', $line);
1675                 if (($line[0] == '*') && ($a[2] == 'FETCH')) {
1676                         $id = $a[1];
1677             
1678                         $result[$id]            = new iilBasicHeader;
1679                         $result[$id]->id        = $id;
1680                         $result[$id]->subject   = '';
1681                         $result[$id]->messageID = 'mid:' . $id;
1682
1683                         /*
1684                             Sample reply line:
1685                             * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen)
1686                             INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODY[HEADER.FIELDS ...
1687                         */
1688                         
1689                         if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY\[HEADER/', $line, $matches)) {
1690                                 $str = $matches[1];
1691
1692                                 //swap parents with quotes, then explode
1693                                 $str = eregi_replace("[()]", "\"", $str);
1694                                 $a = iil_ExplodeQuotedString(' ', $str);
1695
1696                                 //did we get the right number of replies?
1697                                 $parts_count = count($a);
1698                                 if ($parts_count>=8) {
1699                                         for ($i=0; $i<$parts_count; $i=$i+2) {
1700                                                 if (strcasecmp($a[$i],'UID') == 0)
1701                                                         $result[$id]->uid = $a[$i+1];
1702                                                 else if (strcasecmp($a[$i],'RFC822.SIZE') == 0)
1703                                                         $result[$id]->size = $a[$i+1];
1704                                                 else if (strcasecmp($a[$i],'INTERNALDATE') == 0)
1705                                                         $time_str = $a[$i+1];
1706                                                 else if (strcasecmp($a[$i],'FLAGS') == 0)
1707                                                         $flags_str = $a[$i+1];
1708                                         }
1709
1710                                         // process flags
1711                                         $flags_str = eregi_replace('[\\\"]', '', $flags_str);
1712                                         $flags_a   = explode(' ', $flags_str);
1713                                         
1714                                         if (is_array($flags_a)) {
1715                                                 reset($flags_a);
1716                                                 while (list(,$val)=each($flags_a)) {
1717                                                         if (strcasecmp($val,'Seen') == 0) {
1718                                                             $result[$id]->seen = true;
1719                                                         } else if (strcasecmp($val, 'Deleted') == 0) {
1720                                                             $result[$id]->deleted=true;
1721                                                         } else if (strcasecmp($val, 'Recent') == 0) {
1722                                                             $result[$id]->recent = true;
1723                                                         } else if (strcasecmp($val, 'Answered') == 0) {
1724                                                             $result[$id]->answered = true;
1725                                                         } else if (strcasecmp($val, '$Forwarded') == 0) {
1726                                                             $result[$id]->forwarded = true;
1727                                                         } else if (strcasecmp($val, 'Draft') == 0) {
1728                                                             $result[$id]->is_draft = true;
1729                                                         } else if (strcasecmp($val, '$MDNSent') == 0) {
1730                                                             $result[$id]->mdn_sent = true;
1731                                                         } else if (strcasecmp($val, 'Flagged') == 0) {
1732                                                              $result[$id]->flagged = true;
1733                                                         }
1734                                                 }
1735                                                 $result[$id]->flags = $flags_a;
1736                                         }
1737
1738                                         $time_str = str_replace('"', '', $time_str);
1739                                         
1740                                         // if time is gmt...
1741                                         $time_str = str_replace('GMT','+0000',$time_str);
1742                                         
1743                                         //get timezone
1744                                         $time_str      = substr($time_str, 0, -1);
1745                                         $time_zone_str = substr($time_str, -5); // extract timezone
1746                                         $time_str      = substr($time_str, 0, -5); // remove timezone
1747                                         $time_zone     = (float)substr($time_zone_str, 1, 2); // get first two digits
1748                         
1749                                         if ($time_zone_str[3] != '0') {
1750                                                  $time_zone += 0.5;  //handle half hour offset
1751                                         }
1752                                         if ($time_zone_str[0] == '-') {
1753                                                 $time_zone = $time_zone * -1.0; //minus?
1754                                         }
1755                                         
1756                                         //calculate timestamp
1757                                         $timestamp     = strtotime($time_str); //return's server's time
1758                                         $timestamp    -= $time_zone * 3600; //compensate for tz, get GMT
1759
1760                                         $result[$id]->internaldate = $time_str;
1761                                         $result[$id]->timestamp = $timestamp;
1762                                         $result[$id]->date = $time_str;
1763                                 }
1764                         }
1765
1766                         /*
1767                                 Start parsing headers.  The problem is, some header "lines" take up multiple lines.
1768                                 So, we'll read ahead, and if the one we're reading now is a valid header, we'll
1769                                 process the previous line.  Otherwise, we'll keep adding the strings until we come
1770                                 to the next valid header line.
1771                         */
1772         
1773                         $i     = 0;
1774                         $lines = array();
1775                         do {
1776                                 $line = chop(iil_ReadLine($fp, 300), "\r\n");
1777
1778                                 if (ord($line[0])<=32) {
1779                                     $lines[$i] .= (empty($lines[$i])?'':"\n").trim($line);
1780                                 } else {
1781                                         $i++;
1782                                         $lines[$i] = trim($line);
1783                                 }
1784                                 /* 
1785                                         The preg_match below works around communigate imap, which outputs " UID <number>)".
1786                                         Without this, the while statement continues on and gets the "FH0 OK completed" message.
1787                                         If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249.  
1788                                         This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing
1789                                         If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin
1790                                         An alternative might be:
1791                                         if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break;
1792                                         however, unsure how well this would work with all imap clients.
1793                                 */
1794                                 if (preg_match("/^\s*UID [0-9]+\)$/", $line)) {
1795                                     break;
1796                                 }
1797                         // patch from "Maksim Rubis" <siburny@hotmail.com>
1798                         } while (trim($line[0]) != ')' && strncmp($line, $key, strlen($key)));
1799
1800                         if (strncmp($line, $key, strlen($key))) { 
1801                                 //process header, fill iilBasicHeader obj.
1802                                 //      initialize
1803                                 if (is_array($headers)) {
1804                                         reset($headers);
1805                                         while (list($k, $bar) = each($headers)) {
1806                                                 $headers[$k] = '';
1807                                         }
1808                                 }
1809         
1810                                 //      create array with header field:data
1811                                 while ( list($lines_key, $str) = each($lines) ) {
1812                                         list($field, $string) = iil_SplitHeaderLine($str);
1813                                         
1814                                         $field  = strtolower($field);
1815                                         $string = ereg_replace("\n[[:space:]]*"," ",$string); 
1816                                         
1817                                         switch ($field) {
1818                                         case 'date';
1819                                                 if (!$IMAP_USE_INTERNAL_DATE) {
1820                                                         $result[$id]->date = $string;
1821                                                         $result[$id]->timestamp = iil_StrToTime($string);
1822                                                 }
1823                                                 break;
1824                                         case 'from':
1825                                                 $result[$id]->from = $string;
1826                                                 break;
1827                                         case 'to':
1828                                                 $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string);
1829                                                 break;
1830                                         case 'subject':
1831                                                 $result[$id]->subject = $string;
1832                                                 break;
1833                                         case 'reply-to':
1834                                                 $result[$id]->replyto = $string;
1835                                                 break;
1836                                         case 'cc':
1837                                                 $result[$id]->cc = $string;
1838                                                 break;
1839                                         case 'bcc':
1840                                                 $result[$id]->bcc = $string;
1841                                                 break;
1842                                         case 'content-transfer-encoding':
1843                                                 $result[$id]->encoding = $string;
1844                                                 break;
1845                                         case 'content-type':
1846                                                 $ctype_parts = explode(";", $string);
1847                                                 $result[$id]->ctype = array_shift($ctype_parts);
1848                                                 foreach ($ctype_parts as $ctype_add) {
1849                                                         if (preg_match('/charset="?([a-z0-9\-\.\_]+)"?/i',
1850                                                                 $ctype_add, $regs)) {
1851                                                                 $result[$id]->charset = $regs[1];
1852                                                         }
1853                                                 }
1854                                                 break;
1855                                         case 'in-reply-to':
1856                                                 $result[$id]->in_reply_to = ereg_replace("[\n<>]", '', $string);
1857                                                 break;
1858                                         case 'references':
1859                                                 $result[$id]->references = $string;
1860                                                 break;
1861                                         case 'return-receipt-to':
1862                                         case 'disposition-notification-to':
1863                                         case 'x-confirm-reading-to':
1864                                                 $result[$id]->mdn_to = $string;
1865                                                 break;
1866                                         case 'message-id':
1867                                                 $result[$id]->messageID = $string;
1868                                                 break;
1869                                         case 'x-priority':
1870                                                 if (preg_match('/^(\d+)/', $string, $matches))
1871                                                         $result[$id]->priority = intval($matches[1]);
1872                                                 break;
1873                                         } // end switch ()
1874                                 } // end while ()
1875                 
1876                                 if ($conn->do_cache) {
1877                                         $uid = $result[$id]->uid;
1878                                         $conn->cache[$mailbox][$uid] = $result[$id];
1879                                         $conn->cache_dirty[$mailbox] = true;
1880                                 }
1881                         } else {
1882                                 $a = explode(' ', $line);
1883                         }
1884                 }
1885         } while (strcmp($a[0], $key) != 0);
1886
1887         return $result;
1888 }
1889
1890 function iil_C_FetchHeader(&$conn, $mailbox, $id, $uidfetch=false) {
1891
1892         $a  = iil_C_FetchHeaders($conn, $mailbox, $id, $uidfetch);
1893         if (is_array($a)) {
1894                 return array_shift($a);
1895         }
1896         return false;
1897 }
1898
1899 function iil_SortHeaders($a, $field, $flag) {
1900         if (empty($field)) {
1901             $field = 'uid';
1902         }
1903         $field = strtolower($field);
1904         if ($field == 'date' || $field == 'internaldate') {
1905             $field = 'timestamp';
1906         }
1907         if (empty($flag)) {
1908             $flag = 'ASC';
1909         }
1910     
1911         $flag     = strtoupper($flag);
1912         $stripArr = ($field=='subject') ? array('Re: ','Fwd: ','Fw: ','"') : array('"');
1913
1914         $c=count($a);
1915         if ($c > 0) {
1916                 /*
1917                         Strategy:
1918                         First, we'll create an "index" array.
1919                         Then, we'll use sort() on that array, 
1920                         and use that to sort the main array.
1921                 */
1922                 
1923                 // create "index" array
1924                 $index = array();
1925                 reset($a);
1926                 while (list($key, $val)=each($a)) {
1927
1928                         if ($field == 'timestamp') {
1929                                 $data = @strtotime($val->date);
1930                                 if ($data == false) {
1931                                         $data = $val->timestamp;
1932                                 }
1933                         } else {
1934                                 $data = $val->$field;
1935                                 if (is_string($data)) {
1936                                         $data=strtoupper(str_replace($stripArr, '', $data));
1937                                 }
1938                         }
1939                         $index[$key]=$data;
1940                 }
1941                 
1942                 // sort index
1943                 $i = 0;
1944                 if ($flag == 'ASC') {
1945                         asort($index);
1946                 } else {
1947                         arsort($index);
1948                 }
1949         
1950                 // form new array based on index 
1951                 $result = array();
1952                 reset($index);
1953                 while (list($key, $val)=each($index)) {
1954                         $result[$key]=$a[$key];
1955                         $i++;
1956                 }
1957         }
1958         
1959         return $result;
1960 }
1961
1962 function iil_C_Expunge(&$conn, $mailbox) {
1963
1964         if (iil_C_Select($conn, $mailbox)) {
1965                 $c = 0;
1966                 iil_PutLine($conn->fp, "exp1 EXPUNGE");
1967                 do {
1968                         $line=chop(iil_ReadLine($conn->fp, 100));
1969                         if ($line[0] == '*') {
1970                                 $c++;
1971                         }
1972                 } while (!iil_StartsWith($line, 'exp1', true));
1973                 
1974                 if (iil_ParseResult($line) == 0) {
1975                         $conn->selected = ''; //state has changed, need to reselect                     
1976                         //$conn->exists-=$c;
1977                         return $c;
1978                 }
1979                 $conn->error = $line;
1980         }
1981         
1982         return -1;
1983 }
1984
1985 function iil_C_ModFlag(&$conn, $mailbox, $messages, $flag, $mod) {
1986         if ($mod != '+' && $mod != '-') {
1987             return -1;
1988         }
1989     
1990         $fp    = $conn->fp;
1991         $flags = $GLOBALS['IMAP_FLAGS'];
1992         
1993         $flag = strtoupper($flag);
1994         $flag = $flags[$flag];
1995     
1996         if (iil_C_Select($conn, $mailbox)) {
1997                 $c = 0;
1998                 iil_PutLine($fp, "flg STORE $messages " . $mod . "FLAGS (" . $flag . ")");
1999                 do {
2000                         $line=chop(iil_ReadLine($fp, 100));
2001                         if ($line[0] == '*') {
2002                             $c++;
2003                         }
2004                 } while (!iil_StartsWith($line, 'flg', true));
2005
2006                 if (iil_ParseResult($line) == 0) {
2007                         iil_C_ExpireCachedItems($conn, $mailbox, $messages);
2008                         return $c;
2009                 }
2010                 $conn->error = $line;
2011                 return -1;
2012         }
2013         $conn->error = 'Select failed';
2014         return -1;
2015 }
2016
2017 function iil_C_Flag(&$conn, $mailbox, $messages, $flag) {
2018         return iil_C_ModFlag($conn, $mailbox, $messages, $flag, '+');
2019 }
2020
2021 function iil_C_Unflag(&$conn, $mailbox, $messages, $flag) {
2022         return iil_C_ModFlag($conn, $mailbox, $messages, $flag, '-');
2023 }
2024
2025 function iil_C_Delete(&$conn, $mailbox, $messages) {
2026         return iil_C_ModFlag($conn, $mailbox, $messages, 'DELETED', '+');
2027 }
2028
2029 function iil_C_Undelete(&$conn, $mailbox, $messages) {
2030         return iil_C_ModFlag($conn, $mailbox, $messages, 'DELETED', '-');
2031 }
2032
2033 function iil_C_Unseen(&$conn, $mailbox, $messages) {
2034         return iil_C_ModFlag($conn, $mailbox, $messages, 'SEEN', '-');
2035 }
2036
2037 function iil_C_Copy(&$conn, $messages, $from, $to) {
2038         $fp = $conn->fp;
2039
2040         if (empty($from) || empty($to)) {
2041             return -1;
2042         }
2043     
2044         if (iil_C_Select($conn, $from)) {
2045                 $c=0;
2046                 
2047                 iil_PutLine($fp, "cpy1 COPY $messages \"".iil_Escape($to)."\"");
2048                 $line=iil_ReadReply($fp);
2049                 return iil_ParseResult($line);
2050         } else {
2051                 return -1;
2052         }
2053 }
2054
2055 function iil_FormatSearchDate($month, $day, $year) {
2056         $month  = (int) $month;
2057         $months = $GLOBALS['IMAP_MONTHS'];
2058         return $day . '-' . $months[$month] . '-' . $year;
2059 }
2060
2061 function iil_C_CountUnseen(&$conn, $folder) {
2062         $index = iil_C_Search($conn, $folder, 'ALL UNSEEN');
2063         if (is_array($index)) {
2064                 if (($cnt = count($index)) && $index[0] != '') {
2065                         return $cnt;
2066                 }
2067         }
2068         return false;
2069 }
2070
2071 function iil_C_UID2ID(&$conn, $folder, $uid) {
2072         if ($uid > 0) {
2073                 $id_a = iil_C_Search($conn, $folder, "UID $uid");
2074                 if (is_array($id_a) && count($id_a) == 1) {
2075                         return $id_a[0];
2076                 }
2077         }
2078         return false;
2079 }
2080
2081 function iil_C_ID2UID(&$conn, $folder, $id) {
2082         $fp = $conn->fp;
2083         if ($id == 0) {
2084             return      -1;
2085         }
2086         $result = -1;
2087         if (iil_C_Select($conn, $folder)) {
2088                 $key = 'FUID';
2089                 if (iil_PutLine($fp, "$key FETCH $id (UID)")) {
2090                         do {
2091                                 $line=chop(iil_ReadLine($fp, 1024));
2092                                 if (eregi("^\* $id FETCH \(UID (.*)\)", $line, $r)) {
2093                                         $result = $r[1];
2094                                 }
2095                         } while (!preg_match("/^$key/", $line));
2096                 }
2097         }
2098         return $result;
2099 }
2100
2101 function iil_C_Search(&$conn, $folder, $criteria) {
2102         $fp = $conn->fp;
2103         if (iil_C_Select($conn, $folder)) {
2104                 $c = 0;
2105                 
2106                 $query = 'srch1 SEARCH ' . chop($criteria);
2107                 iil_PutLineC($fp, $query);
2108                 do {
2109                         $line=trim(iil_ReadLine($fp, 10000));
2110                         if (eregi("^\* SEARCH", $line)) {
2111                                 $str = trim(substr($line, 8));
2112                                 $messages = explode(' ', $str);
2113                         }
2114                 } while (!iil_StartsWith($line, 'srch1', true));
2115
2116                 $result_code = iil_ParseResult($line);
2117                 if ($result_code == 0) {
2118                     return $messages;
2119                 }
2120                 $conn->error = 'iil_C_Search: ' . $line . "\n";
2121                 return false;   
2122         }
2123         $conn->error = "iil_C_Search: Couldn't select \"$folder\"\n";
2124         return false;
2125 }
2126
2127 function iil_C_Move(&$conn, $messages, $from, $to) {
2128     $fp = $conn->fp;
2129
2130     if (!$from || !$to) {
2131         return -1;
2132     }
2133     $r = iil_C_Copy($conn, $messages, $from,$to);
2134     if ($r==0) {
2135         return iil_C_Delete($conn, $from, $messages);
2136     }
2137     return $r;
2138 }
2139
2140 /**
2141  * Gets the delimiter, for example:
2142  * INBOX.foo -> .
2143  * INBOX/foo -> /
2144  * INBOX\foo -> \
2145  * 
2146  * @return mixed A delimiter (string), or false. 
2147  * @param object $conn The current connection.
2148  * @see iil_Connect()
2149  */
2150 function iil_C_GetHierarchyDelimiter(&$conn) {
2151
2152         global $my_prefs;
2153         
2154         if ($conn->delimiter) {
2155                 return $conn->delimiter;
2156         }
2157         if (!empty($my_prefs['delimiter'])) {
2158             return ($conn->delimiter = $my_prefs['delimiter']);
2159         }
2160     
2161         $fp        = $conn->fp;
2162         $delimiter = false;
2163         
2164         //try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8)
2165         if (!iil_PutLine($fp, 'ghd LIST "" ""')) {
2166             return false;
2167         }
2168     
2169         do {
2170                 $line=iil_ReadLine($fp, 500);
2171                 if ($line[0] == '*') {
2172                         $line = rtrim($line);
2173                         $a=iil_ExplodeQuotedString(' ', $line);
2174                         if ($a[0] == '*') {
2175                             $delimiter = str_replace('"', '', $a[count($a)-2]);
2176                         }
2177                 }
2178         } while (!iil_StartsWith($line, 'ghd', true));
2179
2180         if (strlen($delimiter)>0) {
2181             return $delimiter;
2182         }
2183     
2184         //if that fails, try namespace extension
2185         //try to fetch namespace data
2186         iil_PutLine($conn->fp, "ns1 NAMESPACE");
2187         do {
2188                 $line = iil_ReadLine($conn->fp, 1024);
2189                 if (iil_StartsWith($line, '* NAMESPACE')) {
2190                         $i = 0;
2191                         $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
2192                 }
2193         } while (!iil_StartsWith($line, 'ns1', true));
2194                 
2195         if (!is_array($data)) {
2196             return false;
2197         }
2198     
2199         //extract user space data (opposed to global/shared space)
2200         $user_space_data = $data[0];
2201         if (!is_array($user_space_data)) {
2202             return false;
2203         }
2204     
2205         //get first element
2206         $first_userspace = $user_space_data[0];
2207         if (!is_array($first_userspace)) {
2208             return false;
2209         }
2210     
2211         //extract delimiter
2212         $delimiter = $first_userspace[1];       
2213
2214         return $delimiter;
2215 }
2216
2217 function iil_C_ListMailboxes(&$conn, $ref, $mailbox) {
2218         global $IGNORE_FOLDERS;
2219         
2220         $ignore = $IGNORE_FOLDERS[strtolower($conn->host)];
2221                 
2222         $fp = $conn->fp;
2223         
2224         if (empty($mailbox)) {
2225             $mailbox = '*';
2226         }
2227         
2228         if (empty($ref) && $conn->rootdir) {
2229             $ref = $conn->rootdir;
2230         }
2231     
2232         // send command
2233         if (!iil_PutLine($fp, "lmb LIST \"".$ref."\" \"".iil_Escape($mailbox)."\"")) {
2234             return false;
2235         }
2236     
2237         $i = 0;
2238         // get folder list
2239         do {
2240                 $line = iil_ReadLine($fp, 500);
2241                 $line = iil_MultLine($fp, $line);
2242
2243                 $a = explode(' ', $line);
2244                 if (($line[0] == '*') && ($a[1] == 'LIST')) {
2245                         $line = rtrim($line);
2246                         // split one line
2247                         $a = iil_ExplodeQuotedString(' ', $line);
2248                         // last string is folder name
2249                         $folder = trim($a[count($a)-1], '"');
2250             
2251                         if (empty($ignore) || (!empty($ignore)
2252                                 && !eregi($ignore, $folder))) {
2253                                 $folders[$i] = $folder;
2254                         }
2255             
2256                         // second from last is delimiter
2257                         $delim = trim($a[count($a)-2], '"');
2258                         // is it a container?
2259                         $i++;
2260                 }
2261         } while (!iil_StartsWith($line, 'lmb', true));
2262
2263         if (is_array($folders)) {
2264             if (!empty($ref)) {
2265                 // if rootdir was specified, make sure it's the first element
2266                 // some IMAP servers (i.e. Courier) won't return it
2267                 if ($ref[strlen($ref)-1]==$delim)
2268                     $ref = substr($ref, 0, strlen($ref)-1);
2269                 if ($folders[0]!=$ref)
2270                     array_unshift($folders, $ref);
2271             }
2272             return $folders;
2273         } else if (iil_ParseResult($line) == 0) {
2274                 return array('INBOX');
2275         } else {
2276                 $conn->error = $line;
2277                 return false;
2278         }
2279 }
2280
2281 function iil_C_ListSubscribed(&$conn, $ref, $mailbox) {
2282         global $IGNORE_FOLDERS;
2283         
2284         $ignore = $IGNORE_FOLDERS[strtolower($conn->host)];
2285         
2286         $fp = $conn->fp;
2287         if (empty($mailbox)) {
2288                 $mailbox = '*';
2289         }
2290         if (empty($ref) && $conn->rootdir) {
2291                 $ref = $conn->rootdir;
2292         }
2293         $folders = array();
2294
2295         // send command
2296         if (!iil_PutLine($fp, 'lsb LSUB "' . $ref . '" "' . iil_Escape($mailbox).'"')) {
2297                 $conn->error = "Couldn't send LSUB command\n";
2298                 return false;
2299         }
2300         
2301         $i = 0;
2302         
2303         // get folder list
2304         do {
2305                 $line = iil_ReadLine($fp, 500);
2306                 $line = iil_MultLine($fp, $line);
2307                 $a    = explode(' ', $line);
2308         
2309                 if (($line[0] == '*') && ($a[1] == 'LSUB' || $a[1] == 'LIST')) {
2310                         $line = rtrim($line);
2311             
2312                         // split one line
2313                         $a = iil_ExplodeQuotedString(' ', $line);
2314             
2315                         // last string is folder name
2316                         //$folder = UTF7DecodeString(str_replace('"', '', $a[count($a)-1]));
2317                         $folder = trim($a[count($a)-1], '"');
2318             
2319                         if ((!in_array($folder, $folders)) && (empty($ignore)
2320                                 || (!empty($ignore) && !eregi($ignore, $folder)))) {
2321                             $folders[$i] = $folder;
2322                         }
2323             
2324                         // second from last is delimiter
2325                         $delim = trim($a[count($a)-2], '"');
2326             
2327                         // is it a container?
2328                         $i++;
2329                 }
2330         } while (!iil_StartsWith($line, 'lsb', true));
2331
2332         if (is_array($folders)) {
2333             if (!empty($ref)) {
2334                 // if rootdir was specified, make sure it's the first element
2335                 // some IMAP servers (i.e. Courier) won't return it
2336                 if ($ref[strlen($ref)-1]==$delim) {
2337                     $ref = substr($ref, 0, strlen($ref)-1);
2338                 }
2339                 if ($folders[0]!=$ref) {
2340                     array_unshift($folders, $ref);
2341                 }
2342             }
2343             return $folders;
2344         }
2345         $conn->error = $line;
2346         return false;
2347 }
2348
2349 function iil_C_Subscribe(&$conn, $folder) {
2350         $fp = $conn->fp;
2351
2352         $query = 'sub1 SUBSCRIBE "' . iil_Escape($folder). '"';
2353         iil_PutLine($fp, $query);
2354
2355         $line = trim(iil_ReadLine($fp, 10000));
2356         return iil_ParseResult($line);
2357 }
2358
2359 function iil_C_UnSubscribe(&$conn, $folder) {
2360         $fp = $conn->fp;
2361
2362         $query = 'usub1 UNSUBSCRIBE "' . iil_Escape($folder) . '"';
2363         iil_PutLine($fp, $query);
2364     
2365         $line = trim(iil_ReadLine($fp, 10000));
2366         return iil_ParseResult($line);
2367 }
2368
2369 function iil_C_FetchPartHeader(&$conn, $mailbox, $id, $part) {
2370
2371         $part = empty($part) ? 'HEADER' : $part.'.MIME';
2372
2373         return iil_C_HandlePartBody($conn, $mailbox, $id, $part, 1);
2374 }
2375
2376 function iil_C_HandlePartBody(&$conn, $mailbox, $id, $part='', $mode=1, $file=NULL) {
2377         /* modes:
2378         1: return string (or write to $file pointer)
2379         2: print
2380         3: base64 and print (or write to $file pointer)
2381         */
2382         
2383         $fp     = $conn->fp;
2384         $result = false;
2385         
2386         if (iil_C_Select($conn, $mailbox)) {
2387                 $reply_key = '* ' . $id;
2388         
2389                 // format request
2390                 $key     = 'ftch' . ($c++) . ' ';
2391                 $request = $key . "FETCH $id (BODY.PEEK[$part])";
2392                 // send request
2393                 if (!iil_PutLine($fp, $request)) {
2394                     return false;
2395                 }
2396         
2397                 // receive reply line
2398                 do {
2399                         $line = chop(iil_ReadLine($fp, 1000));
2400                         $a    = explode(' ', $line);
2401                 } while ($a[2] != 'FETCH');
2402                 $len = strlen($line);
2403
2404                 // handle empty "* X FETCH ()" response
2405                 if ($line[$len-1] == ')' && $line[$len-2] != '(') {
2406                         // one line response, get everything between first and last quotes
2407                         if (substr($line, -4, 3) == 'NIL') {
2408                                 // NIL response
2409                                 $result = '';
2410                         } else {
2411                                 $from = strpos($line, '"') + 1;
2412                                 $to   = strrpos($line, '"');
2413                                 $len  = $to - $from;
2414                                 $result = substr($line, $from, $len);
2415                         }
2416             
2417                         if ($mode == 2) {
2418                                 echo $result;
2419                         } else if ($mode == 3) {
2420                                 if ($file)
2421                                         fwrite($file, base64_decode($result));
2422                                 else
2423                                         echo base64_decode($result);
2424                         }                            
2425                 } else if ($line[$len-1] == '}') {
2426                         //multi-line request, find sizes of content and receive that many bytes
2427                         $from     = strpos($line, '{') + 1;
2428                         $to       = strrpos($line, '}');
2429                         $len      = $to - $from;
2430                         $sizeStr  = substr($line, $from, $len);
2431                         $bytes    = (int)$sizeStr;
2432
2433                         while ($bytes > 0) {
2434                                 $line      = iil_ReadLine($fp, 1024);
2435                                 $len       = strlen($line);
2436                 
2437                                 if ($len > $bytes) {
2438                                         $line = substr($line, 0, $bytes);
2439                                 }
2440                                 $bytes -= strlen($line);
2441
2442                                 if ($mode == 1) {
2443                                         if ($file)
2444                                                 fwrite($file, rtrim($line, "\t\r\n\0\x0B") . "\n");
2445                                         else
2446                                                 $result .= rtrim($line, "\t\r\n\0\x0B") . "\n";
2447                                 } else if ($mode == 2) {
2448                                         echo rtrim($line, "\t\r\n\0\x0B") . "\n";
2449                                 } else if ($mode == 3) {
2450                                         if ($file)
2451                                                 fwrite($file, base64_decode($line));
2452                                         else
2453                                                 echo base64_decode($line);
2454                                 }
2455                         }
2456                 }
2457                 // read in anything up until last line
2458                 do {
2459                         $line = iil_ReadLine($fp, 1024);
2460                 } while (!iil_StartsWith($line, $key, true));
2461         
2462                 if ($mode == 3 && $file) {
2463                         return true;
2464                 }
2465         
2466                 if ($result) {
2467                         $result = rtrim($result, "\t\r\n\0\x0B");
2468                         if ($file) {
2469                                 fwrite($file, $result);
2470                                 return true;
2471                         }       
2472                         return $result; // substr($result, 0, strlen($result)-1);
2473                 }
2474                 
2475                 return false;
2476         } else {
2477                 echo 'Select failed.';
2478         }
2479     
2480         if ($mode==1) {
2481                 if ($file) {
2482                         fwrite($file, $result);
2483                         return true;
2484                 }
2485                 return $result;
2486         }
2487         
2488         return false;
2489 }
2490
2491 function iil_C_FetchPartBody(&$conn, $mailbox, $id, $part, $file=NULL) {
2492         return iil_C_HandlePartBody($conn, $mailbox, $id, $part, 1, $file);
2493 }
2494
2495 function iil_C_PrintPartBody(&$conn, $mailbox, $id, $part) {
2496         iil_C_HandlePartBody($conn, $mailbox, $id, $part, 2);
2497 }
2498
2499 function iil_C_PrintBase64Body(&$conn, $mailbox, $id, $part) {
2500         iil_C_HandlePartBody($conn, $mailbox, $id, $part, 3);
2501 }
2502
2503 function iil_C_CreateFolder(&$conn, $folder) {
2504         $fp = $conn->fp;
2505         if (iil_PutLine($fp, 'c CREATE "' . iil_Escape($folder) . '"')) {
2506                 do {
2507                         $line=iil_ReadLine($fp, 300);
2508                 } while ($line[0] != 'c');
2509         $conn->error = $line;
2510                 return (iil_ParseResult($line) == 0);
2511         }
2512         return false;
2513 }
2514
2515 function iil_C_RenameFolder(&$conn, $from, $to) {
2516         $fp = $conn->fp;
2517         if (iil_PutLine($fp, 'r RENAME "' . iil_Escape($from) . '" "' . iil_Escape($to) . '"')) {
2518                 do {
2519                         $line = iil_ReadLine($fp, 300);
2520                 } while ($line[0] != 'r');
2521                 return (iil_ParseResult($line) == 0);
2522         }
2523         return false;
2524 }
2525
2526 function iil_C_DeleteFolder(&$conn, $folder) {
2527         $fp = $conn->fp;
2528         if (iil_PutLine($fp, 'd DELETE "' . iil_Escape($folder). '"')) {
2529                 do {
2530                         $line=iil_ReadLine($fp, 300);
2531                 } while ($line[0] != 'd');
2532                 return (iil_ParseResult($line) == 0);
2533         }
2534         $conn->error = "Couldn't send command\n";
2535         return false;
2536 }
2537
2538 function iil_C_Append(&$conn, $folder, &$message) {
2539         if (!$folder) {
2540                 return false;
2541         }
2542         $fp = $conn->fp;
2543
2544         $message = str_replace("\r", '', $message);
2545         $message = str_replace("\n", "\r\n", $message);         
2546
2547         $len = strlen($message);
2548         if (!$len) {
2549                 return false;
2550         }
2551
2552         $request = 'A APPEND "' . iil_Escape($folder) .'" (\\Seen) {' . $len . '}';
2553     
2554         if (iil_PutLine($fp, $request)) {
2555                 $line=iil_ReadLine($fp, 100);           
2556                 $sent = fwrite($fp, $message."\r\n");
2557                 do {
2558                         $line=iil_ReadLine($fp, 1000);
2559                 } while ($line[0] != 'A');
2560         
2561                 $result = (iil_ParseResult($line) == 0);
2562                 if (!$result) {
2563                     $conn->error .= $line . "\n";
2564                 }
2565                 return $result;
2566         }
2567
2568         $conn->error .= "Couldn't send command \"$request\"\n";
2569         return false;
2570 }
2571
2572 function iil_C_AppendFromFile(&$conn, $folder, $path) {
2573         if (!$folder) {
2574             return false;
2575         }
2576     
2577         //open message file
2578         $in_fp = false;                         
2579         if (file_exists(realpath($path))) {
2580                 $in_fp = fopen($path, 'r');
2581         }
2582         if (!$in_fp) { 
2583                 $conn->error .= "Couldn't open $path for reading\n";
2584                 return false;
2585         }
2586         
2587         $fp  = $conn->fp;
2588         $len = filesize($path);
2589         if (!$len) {
2590                 return false;
2591         }
2592     
2593         //send APPEND command
2594         $request    = 'A APPEND "' . iil_Escape($folder) . '" (\\Seen) {' . $len . '}';
2595         $bytes_sent = 0;
2596         if (iil_PutLine($fp, $request)) {
2597                 $line = iil_ReadLine($fp, 100);
2598                                 
2599                 //send file
2600                 while (!feof($in_fp)) {
2601                         $buffer      = fgets($in_fp, 4096);
2602                         $bytes_sent += strlen($buffer);
2603                         iil_PutLine($fp, $buffer, false);
2604                 }
2605                 fclose($in_fp);
2606
2607                 iil_PutLine($fp, '');
2608
2609                 //read response
2610                 do {
2611                         $line = iil_ReadLine($fp, 1000);
2612                 } while ($line[0] != 'A');
2613                         
2614                 $result = (iil_ParseResult($line) == 0);
2615                 if (!$result) {
2616                     $conn->error .= $line . "\n";
2617                 }
2618         
2619                 return $result;
2620         }
2621         
2622         $conn->error .= "Couldn't send command \"$request\"\n";
2623         return false;
2624 }
2625
2626 function iil_C_FetchStructureString(&$conn, $folder, $id) {
2627         $fp     = $conn->fp;
2628         $result = false;
2629         
2630         if (iil_C_Select($conn, $folder)) {
2631                 $key = 'F1247';
2632
2633                 if (iil_PutLine($fp, "$key FETCH $id (BODYSTRUCTURE)")) {
2634                         do {
2635                                 $line = iil_ReadLine($fp, 5000);
2636                                 $line = iil_MultLine($fp, $line);
2637                                 list(, $index, $cmd, $rest) = explode(' ', $line);
2638                                 if ($cmd != 'FETCH' || $index == $id || preg_match("/^$key/", $line))
2639                                         $result .= $line;
2640                         } while (!preg_match("/^$key/", $line));
2641
2642                         $result = trim(substr($result, strpos($result, 'BODYSTRUCTURE')+13, -(strlen($result)-strrpos($result, $key)+1)));
2643                 }
2644         }
2645         return $result;
2646 }
2647
2648 function iil_C_PrintSource(&$conn, $folder, $id, $part) {
2649         $header = iil_C_FetchPartHeader($conn, $folder, $id, $part);
2650         //echo str_replace("\r", '', $header);
2651         echo $header;
2652         echo iil_C_PrintPartBody($conn, $folder, $id, $part);
2653 }
2654
2655 function iil_C_GetQuota(&$conn) {
2656 /*
2657  * GETQUOTAROOT "INBOX"
2658  * QUOTAROOT INBOX user/rchijiiwa1
2659  * QUOTA user/rchijiiwa1 (STORAGE 654 9765)
2660  * OK Completed
2661  */
2662         $fp         = $conn->fp;
2663         $result     = false;
2664         $quota_lines = array();
2665         
2666         // get line(s) containing quota info
2667         if (iil_PutLine($fp, 'QUOT1 GETQUOTAROOT "INBOX"')) {
2668                 do {
2669                         $line=chop(iil_ReadLine($fp, 5000));
2670                         if (iil_StartsWith($line, '* QUOTA ')) {
2671                                 $quota_lines[] = $line;
2672                         }
2673                 } while (!iil_StartsWith($line, 'QUOT1', true));
2674         }
2675         
2676         // return false if not found, parse if found
2677         $min_free = PHP_INT_MAX;
2678         foreach ($quota_lines as $key => $quota_line) {
2679                 $quota_line   = eregi_replace('[()]', '', $quota_line);
2680                 $parts        = explode(' ', $quota_line);
2681                 $storage_part = array_search('STORAGE', $parts);
2682                 
2683                 if (!$storage_part) continue;
2684         
2685                 $used   = intval($parts[$storage_part+1]);
2686                 $total  = intval($parts[$storage_part+2]);
2687                 $free   = $total - $used; 
2688         
2689                 // return lowest available space from all quotas
2690                 if ($free < $min_free) { 
2691                         $min_free = $free; 
2692                         $result['used']    = $used;
2693                         $result['total']   = $total;
2694                         $result['percent'] = min(100, round(($used/max(1,$total))*100));
2695                         $result['free']    = 100 - $result['percent'];
2696                 }
2697         }
2698         return $result;
2699 }
2700
2701 function iil_C_ClearFolder(&$conn, $folder) {
2702         $num_in_trash = iil_C_CountMessages($conn, $folder);
2703         if ($num_in_trash > 0) {
2704                 iil_C_Delete($conn, $folder, '1:' . $num_in_trash);
2705         }
2706         return (iil_C_Expunge($conn, $folder) >= 0);
2707 }
2708
2709 ?>