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