source: subversion/trunk/roundcubemail/program/lib/imap.inc @ 1713

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