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

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