root/trunk/roundcubemail/program/lib/imap.inc

Revision 1580, 66.0 kB (checked in by alec, 5 days ago)

- Added flag column on messages list (#1484623)

  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
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                - RFC3501 [7.1] don't call CAPABILITY if was returned in server
56                  optional resposne in iil_Connect()
57                - trim(chop()) replaced by trim()
58                - added iil_Escape() with support for " and \ in folder names
59                - support \ character in username in iil_C_Login()
60                - fixed iil_MultLine(): use iil_ReadBytes() instead of iil_ReadLine()
61                - fixed iil_C_FetchStructureString() to handle many literal strings in response
62                - removed hardcoded data size in iil_ReadLine()
63                - added iil_PutLine() wrapper for fputs()
64                - code cleanup and identation fixes
65
66********************************************************/
67
68/**
69 * @todo Possibly clean up more CS.
70 * @todo Try to replace most double-quotes with single-quotes.
71 * @todo Split this file into smaller files.
72 * @todo Refactor code.
73 * @todo Replace echo-debugging (make it adhere to config setting and log)
74 */
75
76// changed path to work within roundcube webmail
77include_once 'lib/icl_commons.inc';
78
79
80if (!isset($IMAP_USE_HEADER_DATE) || !$IMAP_USE_HEADER_DATE) {
81    $IMAP_USE_INTERNAL_DATE = true;
82}
83
84/**
85 * @todo Maybe use date() to generate this.
86 */
87$GLOBALS['IMAP_MONTHS'] = array("Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4,
88    "May" => 5, "Jun" => 6, "Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10,
89    "Nov" => 11, "Dec" => 12);
90
91$GLOBALS['IMAP_SERVER_TZ'] = date('Z');
92
93$iil_error;
94$iil_errornum;
95$iil_selected;
96
97/**
98 * @todo Change class vars to public/private
99 */
100class iilConnection
101{
102        var $fp;
103        var $error;
104        var $errorNum;
105        var $selected;
106        var $message;
107        var $host;
108        var $cache;
109        var $uid_cache;
110        var $do_cache;
111        var $exists;
112        var $recent;
113        var $rootdir;
114        var $delimiter;
115        var $capability = array();
116}
117
118/**
119 * @todo Change class vars to public/private
120 */
121class iilBasicHeader
122{
123        var $id;
124        var $uid;
125        var $subject;
126        var $from;
127        var $to;
128        var $cc;
129        var $replyto;
130        var $in_reply_to;
131        var $date;
132        var $messageID;
133        var $size;
134        var $encoding;
135        var $charset;
136        var $ctype;
137        var $flags;
138        var $timestamp;
139        var $f;
140        var $internaldate;
141        var $references;
142        var $priority;
143        var $mdn_to;
144        var $mdn_sent = false;
145        var $is_reply = false;
146        var $seen = false;
147        var $deleted = false;
148        var $recent = false;
149        var $answered = false;
150        var $forwarded = false;
151        var $junk = false;
152        var $flagged = false;
153}
154
155/**
156 * @todo Change class vars to public/private
157 */
158class iilThreadHeader
159{
160        var $id;
161        var $sbj;
162        var $irt;
163        var $mid;
164}
165
166function iil_xor($string, $string2) {
167        $result = '';
168        $size = strlen($string);
169        for ($i=0; $i<$size; $i++) {
170                $result .= chr(ord($string[$i]) ^ ord($string2[$i]));
171        }
172        return $result;
173}
174
175function iil_PutLine($fp, $string, $endln=true) {
176//      console('C: '. $string);
177        return fputs($fp, $string . ($endln ? "\r\n" : ''));
178}
179
180function iil_ReadLine($fp, $size) {
181        $line = '';
182
183        if (!$fp) {
184                return $line;
185        }
186   
187        if (!$size) {
188                $size = 1024;
189        }
190   
191        do {
192                $buffer = fgets($fp, $size);
193                if ($buffer === false) {
194                        break;
195                }
196//              console('S: '. chop($buffer));
197                $line .= $buffer;
198        } while ($buffer[strlen($buffer)-1] != "\n");
199       
200        return $line;
201}
202
203function iil_MultLine($fp, $line) {
204        $line = chop($line);
205        if (ereg('\{[0-9]+\}$', $line)) {
206                $out = '';
207       
208                preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a);
209                $bytes = $a[2][0];
210                while (strlen($out) < $bytes) {
211                        $line = iil_ReadBytes($fp, $bytes);
212                        $out .= $line;
213                }
214                $line = $a[1][0] . "\"$out\"";
215//              console('[...] '. $out);
216        }
217        return $line;
218}
219
220function iil_ReadBytes($fp, $bytes) {
221        $data = '';
222        $len  = 0;
223        do {
224                $data .= fread($fp, $bytes-$len);
225                if ($len == strlen($data)) {
226                        break; //nothing was read -> exit to avoid apache lockups
227                }
228                $len = strlen($data);
229        } while ($len < $bytes);
230       
231        return $data;
232}
233
234function iil_ReadReply($fp) {
235        do {
236                $line = trim(iil_ReadLine($fp, 1024));
237        } while ($line[0] == '*');
238       
239        return $line;
240}
241
242function iil_ParseResult($string) {
243        $a=explode(' ', $string);
244        if (count($a) > 2) {
245                if (strcasecmp($a[1], 'OK') == 0) {
246                        return 0;
247                } else if (strcasecmp($a[1], 'NO') == 0) {
248                        return -1;
249                } else if (strcasecmp($a[1], 'BAD') == 0) {
250                        return -2;
251                }
252        }
253        return -3;
254}
255
256// check if $string starts with $match
257function iil_StartsWith($string, $match) {
258        $len = strlen($match);
259        if ($len == 0) {
260                return false;
261        }
262        if (strncmp($string, $match, $len) == 0) {
263                return true;
264        }
265        return false;
266}
267
268function iil_StartsWithI($string, $match) {
269        $len = strlen($match);
270        if ($len == 0) {
271                return false;
272        }
273        if (strncasecmp($string, $match, $len) == 0) {
274                return true;
275        }
276        return false;
277}
278
279function iil_Escape($string)
280{
281        return strtr($string, array('"'=>'\\"', '\\' => '\\\\'));
282}
283
284function iil_C_Authenticate(&$conn, $user, $pass, $encChallenge) {
285   
286    $ipad = '';
287    $opad = '';
288   
289    // initialize ipad, opad
290    for ($i=0;$i<64;$i++) {
291        $ipad .= chr(0x36);
292        $opad .= chr(0x5C);
293    }
294
295    // pad $pass so it's 64 bytes
296    $padLen = 64 - strlen($pass);
297    for ($i=0;$i<$padLen;$i++) {
298        $pass .= chr(0);
299    }
300   
301    // generate hash
302    $hash  = md5(iil_xor($pass,$opad) . pack("H*", md5(iil_xor($pass, $ipad) . base64_decode($encChallenge))));
303   
304    // generate reply
305    $reply = base64_encode($user . ' ' . $hash);
306   
307    // send result, get reply
308    iil_PutLine($conn->fp, $reply);
309    $line = iil_ReadLine($conn->fp, 1024);
310   
311    // process result
312    if (iil_ParseResult($line) == 0) {
313        $conn->error    .= '';
314        $conn->errorNum  = 0;
315        return $conn->fp;
316    }
317    $conn->error    .= 'Authentication for ' . $user . ' failed (AUTH): "';
318    $conn->error    .= htmlspecialchars($line) . '"';
319    $conn->errorNum  = -2;
320    return false;
321}
322
323function iil_C_Login(&$conn, $user, $password) {
324
325    iil_PutLine($conn->fp, 'a001 LOGIN "'.iil_Escape($user).'" "'.iil_Escape($password).'"');
326
327    do {
328        $line = iil_ReadReply($conn->fp);
329        if ($line === false) {
330            break;
331        }
332    } while (!iil_StartsWith($line, "a001 "));
333    $a = explode(' ', $line);
334    if (strcmp($a[1], 'OK') == 0) {
335        $result          = $conn->fp;
336        $conn->error    .= '';
337        $conn->errorNum  = 0;
338        return $result;
339    }
340    $result = false;
341    fclose($conn->fp);
342   
343    $conn->error    .= 'Authentication for ' . $user . ' failed (LOGIN): "';
344    $conn->error    .= htmlspecialchars($line)."\"";
345    $conn->errorNum  = -2;
346
347    return $result;
348}
349
350function iil_ParseNamespace2($str, &$i, $len=0, $l) {
351        if (!$l) {
352            $str = str_replace('NIL', '()', $str);
353        }
354        if (!$len) {
355            $len = strlen($str);
356        }
357        $data      = array();
358        $in_quotes = false;
359        $elem      = 0;
360        for ($i;$i<$len;$i++) {
361                $c = (string)$str[$i];
362                if ($c == '(' && !$in_quotes) {
363                        $i++;
364                        $data[$elem] = iil_ParseNamespace2($str, $i, $len, $l++);
365                        $elem++;
366                } else if ($c == ')' && !$in_quotes) {
367                        return $data;
368                } else if ($c == '\\') {
369                        $i++;
370                        if ($in_quotes) {
371                                $data[$elem] .= $c.$str[$i];
372                        }
373                } else if ($c == '"') {
374                        $in_quotes = !$in_quotes;
375                        if (!$in_quotes) {
376                                $elem++;
377                        }
378                } else if ($in_quotes) {
379                        $data[$elem].=$c;
380                }
381        }
382        return $data;
383}
384
385function iil_C_NameSpace(&$conn) {
386        global $my_prefs;
387       
388        if (!in_array('NAMESPACE', $conn->capability)) {
389            return false;
390        }
391   
392        if ($my_prefs["rootdir"]) {
393            return true;
394        }
395   
396        iil_PutLine($conn->fp, "ns1 NAMESPACE");
397        do {
398                $line = iil_ReadLine($conn->fp, 1024);
399                if (iil_StartsWith($line, '* NAMESPACE')) {
400                        $i    = 0;
401                        $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
402                }
403        } while (!iil_StartsWith($line, "ns1"));
404       
405        if (!is_array($data)) {
406            return false;
407        }
408   
409        $user_space_data = $data[0];
410        if (!is_array($user_space_data)) {
411            return false;
412        }
413   
414        $first_userspace = $user_space_data[0];
415        if (count($first_userspace)!=2) {
416            return false;
417        }
418   
419        $conn->rootdir       = $first_userspace[0];
420        $conn->delimiter     = $first_userspace[1];
421        $my_prefs["rootdir"] = substr($conn->rootdir, 0, -1);
422       
423        return true;
424}
425
426function iil_Connect($host, $user, $password) {
427        global $iil_error, $iil_errornum;
428        global $ICL_SSL, $ICL_PORT;
429        global $IMAP_NO_CACHE;
430        global $my_prefs, $IMAP_USE_INTERNAL_DATE;
431       
432        $iil_error = '';
433        $iil_errornum = 0;
434       
435        //strip slashes
436        // $user = stripslashes($user);
437        // $password = stripslashes($password);
438       
439        //set auth method
440        $auth_method = 'plain';
441        if (func_num_args() >= 4) {
442                $auth_array = func_get_arg(3);
443                if (is_array($auth_array)) {
444                        $auth_method = $auth_array['imap'];
445                }
446                if (empty($auth_method)) {
447                        $auth_method = "plain";
448                }
449        }
450        $message = "INITIAL: $auth_method\n";
451               
452        $result = false;
453       
454        //initialize connection
455        $conn              = new iilConnection;
456        $conn->error       = '';
457        $conn->errorNum    = 0;
458        $conn->selected    = '';
459        $conn->user        = $user;
460        $conn->host        = $host;
461        $conn->cache       = array();
462        $conn->do_cache    = (function_exists("cache_write")&&!$IMAP_NO_CACHE);
463        $conn->cache_dirty = array();
464       
465        if ($my_prefs['sort_field'] == 'INTERNALDATE') {
466                $IMAP_USE_INTERNAL_DATE = true;
467        } else if ($my_prefs['sort_field'] == 'DATE') {
468                $IMAP_USE_INTERNAL_DATE = false;
469        }
470        //echo '<!-- conn sort_field: '.$my_prefs['sort_field'].' //-->';
471       
472        //check input
473        if (empty($host)) {
474                $iil_error .= "Invalid host\n";
475        }
476        if (empty($user)) {
477                $iil_error .= "Invalid user\n";
478        }
479        if (empty($password)) {
480                $iil_error .= "Invalid password\n";
481        }
482        if (!empty($iil_error)) {
483                return false;
484        }
485        if (!$ICL_PORT) {
486                $ICL_PORT = 143;
487        }
488   
489        //check for SSL
490        if ($ICL_SSL) {
491                $host = $ICL_SSL . '://' . $host;
492        }
493       
494        //open socket connection
495        $conn->fp = fsockopen($host, $ICL_PORT, $errno, $errstr, 10);
496        if (!$conn->fp) {
497                $iil_error = "Could not connect to $host at port $ICL_PORT: $errstr";
498                $iil_errornum = -1;
499                return false;
500        }
501
502        $iil_error .= "Socket connection established\r\n";
503        $line       = iil_ReadLine($conn->fp, 1024);
504
505        // RFC3501 [7.1] optional CAPABILITY response
506        // commented out, because it's not working always as should
507//      if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
508//              $conn->capability = explode(' ', $matches[1]);
509//      } else {
510                iil_PutLine($conn->fp, "cp01 CAPABILITY");
511                do {
512                        $line = trim(iil_ReadLine($conn->fp, 1024));
513
514                        $conn->message .= "$line\n";
515
516                        $a = explode(' ', $line);
517                        if ($line[0] == '*') {
518                                while (list($k, $w) = each($a)) {
519                                        if ($w != '*' && $w != 'CAPABILITY')
520                                        $conn->capability[] = $w;
521                                }
522                        }
523                } while ($a[0] != 'cp01');
524//      }
525
526        if (strcasecmp($auth_method, "check") == 0) {
527                //check for supported auth methods
528               
529                //default to plain text auth
530                $auth_method = 'plain';
531                       
532                //check for CRAM-MD5
533                foreach ($conn->capability as $c)
534                        if (strcasecmp($c, 'AUTH=CRAM_MD5') == 0 ||
535                                strcasecmp($c, 'AUTH=CRAM-MD5') == 0) {
536                                $auth_method = 'auth';
537                                break;
538                        }
539        }
540
541        if (strcasecmp($auth_method, 'auth') == 0) {
542                $conn->message .= "Trying CRAM-MD5\n";
543
544                //do CRAM-MD5 authentication
545                iil_PutLine($conn->fp, "a000 AUTHENTICATE CRAM-MD5");
546                $line = trim(iil_ReadLine($conn->fp, 1024));
547
548                $conn->message .= "$line\n";
549
550                if ($line[0] == '+') {
551                        $conn->message .= 'Got challenge: ' . htmlspecialchars($line) . "\n";
552
553                        //got a challenge string, try CRAM-5
554                        $result = iil_C_Authenticate($conn, $user, $password, substr($line,2));
555           
556                        $conn->message .= "Tried CRAM-MD5: $result \n";
557                } else {
558                        $conn->message .='No challenge ('.htmlspecialchars($line)."), try plain\n";
559                        $auth = 'plain';
560                }
561        }
562               
563        if ((!$result)||(strcasecmp($auth, "plain") == 0)) {
564                //do plain text auth
565                $result = iil_C_Login($conn, $user, $password);
566                $conn->message.="Tried PLAIN: $result \n";
567        }
568               
569        $conn->message .= $auth;
570                       
571        if ($result) {
572                iil_C_Namespace($conn);
573                return $conn;
574        } else {
575                $iil_error = $conn->error;
576                $iil_errornum = $conn->errorNum;
577                return false;
578        }
579}
580
581function iil_Close(&$conn) {
582        iil_C_WriteCache($conn);
583        if (iil_PutLine($conn->fp, "I LOGOUT")) {
584                fgets($conn->fp, 1024);
585                fclose($conn->fp);
586                $conn->fp = false;
587        }
588}
589
590function iil_ClearCache($user, $host) {
591}
592
593function iil_C_WriteCache(&$conn) {
594        //echo "<!-- doing iil_C_WriteCache //-->\n";
595        if (!$conn->do_cache) return false;
596       
597        if (is_array($conn->cache)) {
598                while (list($folder,$data)=each($conn->cache)) {
599                        if ($folder && is_array($data) && $conn->cache_dirty[$folder]) {
600                                $key = $folder.".imap";
601                                $result = cache_write($conn->user, $conn->host, $key, $data, true);
602                                //echo "<!-- writing $key $data: $result //-->\n";
603                        }
604                }
605        }
606}
607
608function iil_C_EnableCache(&$conn) {
609        $conn->do_cache = true;
610}
611
612function iil_C_DisableCache(&$conn) {
613        $conn->do_cache = false;
614}
615
616function iil_C_LoadCache(&$conn, $folder) {
617        if (!$conn->do_cache) {
618            return false;
619        }
620   
621        $key = $folder.'.imap';
622        if (!is_array($conn->cache[$folder])) {
623                $conn->cache[$folder]       = cache_read($conn->user, $conn->host, $key);
624                $conn->cache_dirty[$folder] = false;
625        }
626}
627
628function iil_C_ExpireCachedItems(&$conn, $folder, $message_set) {
629       
630        if (!$conn->do_cache) {
631                return; //caching disabled
632        }
633        if (!is_array($conn->cache[$folder])) {
634                return; //cache not initialized|empty
635        }
636        if (count($conn->cache[$folder]) == 0) {
637                return; //cache not initialized|empty
638        }
639   
640        $uids = iil_C_FetchHeaderIndex($conn, $folder, $message_set, 'UID');
641        $num_removed = 0;
642        if (is_array($uids)) {
643                //echo "<!-- unsetting: ".implode(",",$uids)." //-->\n";
644                while (list($n,$uid)=each($uids)) {
645                        unset($conn->cache[$folder][$uid]);
646                        //$conn->cache[$folder][$uid] = false;
647                        //$num_removed++;
648                }
649                $conn->cache_dirty[$folder] = true;
650
651                //echo '<!--'."\n";
652                //print_r($conn->cache);
653                //echo "\n".'//-->'."\n";
654        } else {
655                echo "<!-- failed to get uids: $message_set //-->\n";
656        }
657       
658        /*
659        if ($num_removed>0) {
660                $new_cache;
661                reset($conn->cache[$folder]);
662                while (list($uid,$item)=each($conn->cache[$folder])) {
663                        if ($item) $new_cache[$uid] = $conn->cache[$folder][$uid];
664                }
665                $conn->cache[$folder] = $new_cache;
666        }
667        */
668}
669
670function iil_ExplodeQuotedString($delimiter, $string) {
671        $quotes=explode('"', $string);
672        while ( list($key, $val) = each($quotes)) {
673                if (($key % 2) == 1) {
674                        $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
675                }
676        }
677        $string=implode('"', $quotes);
678       
679        $result=explode($delimiter, $string);
680        while ( list($key, $val) = each($result) ) {
681                $result[$key] = str_replace('_!@!_', $delimiter, $result[$key]);
682        }
683   
684        return $result;
685}
686
687function iil_CheckForRecent($host, $user, $password, $mailbox) {
688        if (empty($mailbox)) {
689                $mailbox = 'INBOX';
690        }
691   
692        $conn = iil_Connect($host, $user, $password, 'plain');
693        $fp   = $conn->fp;
694        if ($fp) {
695                iil_PutLine($fp, "a002 EXAMINE \"".iil_Escape($mailbox)."\"");
696                do {
697                        $line=chop(iil_ReadLine($fp, 300));
698                        $a=explode(' ', $line);
699                        if (($a[0] == '*') && (strcasecmp($a[2], 'RECENT') == 0)) {
700                            $result = (int) $a[1];
701            }
702                } while (!iil_StartsWith($a[0], 'a002'));
703
704                iil_PutLine($fp, "a003 LOGOUT");
705                fclose($fp);
706        } else {
707            $result = -2;
708        }
709   
710        return $result;
711}
712
713function iil_C_Select(&$conn, $mailbox) {
714
715        if (empty($mailbox)) {
716                return false;
717        }
718        if (strcmp($conn->selected, $mailbox) == 0) {
719                return true;
720        }
721   
722        iil_C_LoadCache($conn, $mailbox);
723       
724        if (iil_PutLine($conn->fp, "sel1 SELECT \"".iil_Escape($mailbox).'"')) {
725                do {
726                        $line = chop(iil_ReadLine($conn->fp, 300));
727                        $a = explode(' ', $line);
728                        if (count($a) == 3) {
729                                if (strcasecmp($a[2], 'EXISTS') == 0) {
730                                        $conn->exists = (int) $a[1];
731                                }
732                                if (strcasecmp($a[2], 'RECENT') == 0) {
733                                        $conn->recent = (int) $a[1];
734                                }
735                        }
736                } while (!iil_StartsWith($line, 'sel1'));
737
738                $a = explode(' ', $line);
739
740                if (strcasecmp($a[1], 'OK') == 0) {
741                        $conn->selected = $mailbox;
742                        return true;
743                }
744        }
745        return false;
746}
747
748function iil_C_CheckForRecent(&$conn, $mailbox) {
749        if (empty($mailbox)) {
750                $mailbox = 'INBOX';
751        }
752   
753        iil_C_Select($conn, $mailbox);
754        if ($conn->selected == $mailbox) {
755                return $conn->recent;
756        }
757        return false;
758}
759
760function iil_C_CountMessages(&$conn, $mailbox, $refresh = false) {
761        if ($refresh) {
762                $conn->selected= '';
763        }
764       
765        iil_C_Select($conn, $mailbox);
766        if ($conn->selected == $mailbox) {
767                return $conn->exists;
768        }
769        return false;
770}
771
772function iil_SplitHeaderLine($string) {
773        $pos=strpos($string, ':');
774        if ($pos>0) {
775                $res[0] = substr($string, 0, $pos);
776                $res[1] = trim(substr($string, $pos+1));
777                return $res;
778        }
779        return $string;
780}
781
782function iil_StrToTime($str) {
783        $IMAP_MONTHS    = $GLOBALS['IMAP_MONTHS'];
784        $IMAP_SERVER_TZ = $GLOBALS['IMAP_SERVER_TR'];
785               
786        if ($str) {
787            $time1 = strtotime($str);
788        }
789        if ($time1 && $time1 != -1) {
790            return $time1-$IMAP_SERVER_TZ;
791        }
792        //echo '<!--'.$str.'//-->';
793       
794        //replace double spaces with single space
795        $str = trim($str);
796        $str = str_replace('  ', ' ', $str);
797       
798        //strip off day of week
799        $pos = strpos($str, ' ');
800        if (!is_numeric(substr($str, 0, $pos))) {
801            $str = substr($str, $pos+1);
802        }
803        //explode, take good parts
804        $a = explode(' ', $str);
805
806        $month_str = $a[1];
807        $month     = $IMAP_MONTHS[$month_str];
808        $day       = (int)$a[0];
809        $year      = (int)$a[2];
810        $time      = $a[3];
811        $tz_str    = $a[4];
812        $tz        = substr($tz_str, 0, 3);
813        $ta        = explode(':', $time);
814        $hour      = (int)$ta[0]-(int)$tz;
815        $minute    = (int)$ta[1];
816        $second    = (int)$ta[2];
817       
818        //make UNIX timestamp
819        $time2 = mktime($hour, $minute, $second, $month, $day, $year);
820        //echo '<!--'.$time1.' '.$time2.' //-->'."\n";
821        return $time2;
822}
823
824function iil_C_Sort(&$conn, $mailbox, $field, $add='', $is_uid=FALSE,
825    $encoding = 'US-ASCII') {
826        /*  Do "SELECT" command */
827        if (!iil_C_Select($conn, $mailbox)) {
828            return false;
829        }
830        $field = strtoupper($field);
831        if ($field == 'INTERNALDATE') {
832            $field = 'ARRIVAL';
833        }
834       
835        $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1,
836        'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1);
837       
838        if (!$fields[$field]) {
839            return false;
840        }
841   
842        $is_uid = $is_uid ? 'UID ' : '';
843       
844        if (!empty($add)) {
845            $add = " $add";
846        }
847
848        $fp       = $conn->fp;
849        $command  = 's ' . $is_uid . 'SORT (' . $field . ') ';
850        $command .= $encoding . ' ALL' . $add;
851        $line     = $data = '';
852       
853        if (!iil_PutLine($fp, $command)) {
854            return false;
855        }
856        do {
857                $line = chop(iil_ReadLine($fp, 1024));
858                if (iil_StartsWith($line, '* SORT')) {
859                        $data .= ($data?' ':'') . substr($line, 7);
860                }
861        } while ($line[0]!='s');
862       
863        if (empty($data)) {
864                $conn->error = $line;
865                return false;
866        }
867       
868        $out = explode(' ',$data);
869        return $out;
870}
871
872function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field,
873    $normalize=true) {
874        global $IMAP_USE_INTERNAL_DATE;
875       
876        $c=0;
877        $result=array();
878        $fp = $conn->fp;
879               
880        if (empty($index_field)) {
881            $index_field = 'DATE';
882        }
883        $index_field = strtoupper($index_field);
884       
885        list($from_idx, $to_idx) = explode(':', $message_set);
886        if (empty($message_set) || (isset($to_idx)
887            && (int)$from_idx > (int)$to_idx)) {
888                return false;
889        }
890       
891        //$fields_a['DATE'] = ($IMAP_USE_INTERNAL_DATE?6:1);
892        $fields_a['DATE']         = 1;
893        $fields_a['INTERNALDATE'] = 6;
894        $fields_a['FROM']         = 1;
895        $fields_a['REPLY-TO']     = 1;
896        $fields_a['SENDER']       = 1;
897        $fields_a['TO']           = 1;
898        $fields_a['SUBJECT']      = 1;
899        $fields_a['UID']          = 2;
900        $fields_a['SIZE']         = 2;
901        $fields_a['SEEN']         = 3;
902        $fields_a['RECENT']       = 4;
903        $fields_a['DELETED']      = 5;
904       
905        $mode=$fields_a[$index_field];
906        if (!($mode > 0)) {
907            return false;
908        }
909   
910        /*  Do "SELECT" command */
911        if (!iil_C_Select($conn, $mailbox)) {
912            return false;
913        }
914   
915        /* FETCH date,from,subject headers */
916        if ($mode == 1) {
917                $key     = 'fhi' . ($c++);
918                $request = $key . " FETCH $message_set (BODY.PEEK[HEADER.FIELDS ($index_field)])";
919                if (!iil_PutLine($fp, $request)) {
920                    return false;
921                }
922                do {
923                       
924                        $line=chop(iil_ReadLine($fp, 200));
925                        $a=explode(' ', $line);
926                        if (($line[0] == '*') && ($a[2] == 'FETCH')
927                            && ($line[strlen($line)-1] != ')')) {
928                                $id=$a[1];
929
930                                $str=$line=chop(iil_ReadLine($fp, 300));
931
932                                while ($line[0] != ')') {                                       //caution, this line works only in this particular case
933                                        $line=chop(iil_ReadLine($fp, 300));
934                                        if ($line[0] != ')') {
935                                                if (ord($line[0]) <= 32) {                      //continuation from previous header line
936                                                        $str.= ' ' . trim($line);
937                                                }
938                                                if ((ord($line[0]) > 32) || (strlen($line[0]) == 0)) {
939                                                        list($field, $string) = iil_SplitHeaderLine($str);
940                                                        if (strcasecmp($field, 'date') == 0) {
941                                                                $result[$id] = iil_StrToTime($string);
942                                                        } else {
943                                                                $result[$id] = str_replace('"', '', $string);
944                                                                if ($normalize) {
945                                                                    $result[$id] = strtoupper($result[$id]);
946                                                                }
947                                                        }
948                                                        $str=$line;
949                                                }
950                                        }
951                                }
952                        }
953                        /*
954                        $end_pos = strlen($line)-1;
955                        if (($line[0]=="*") && ($a[2]=="FETCH") && ($line[$end_pos]=="}")) {
956                                $id = $a[1];
957                                $pos = strrpos($line, "{")+1;
958                                $bytes = (int)substr($line, $pos, $end_pos-$pos);
959                                $received = 0;
960                                do {
961                                        $line      = iil_ReadLine($fp, 0);
962                                        $received += strlen($line);
963                                        $line      = chop($line);
964                                       
965                                        if ($received>$bytes) {
966                                                break;
967                                        } else if (!$line) {
968                                                continue;
969                                        }
970
971                                        list($field, $string) = explode(': ', $line);
972                                       
973                                        if (strcasecmp($field, 'date') == 0) {
974                                                $result[$id] = iil_StrToTime($string);
975                                        } else if ($index_field != 'DATE') {
976                                                $result[$id]=strtoupper(str_replace('"', '', $string));
977                                        }
978                                } while ($line[0] != ')');
979                        } else {
980                                //one line response, not expected so ignore                             
981                        }
982                        */
983                } while (!iil_StartsWith($line, $key));
984
985        }else if ($mode == 6) {
986
987                $key     = 'fhi' . ($c++);
988                $request = $key . " FETCH $message_set (INTERNALDATE)";
989                if (!iil_PutLine($fp, $request)) {
990                    return false;
991                }
992                do {
993                        $line=chop(iil_ReadLine($fp, 200));
994                        if ($line[0] == '*') {
995                                /*
996                                 * original:
997                                 * "* 10 FETCH (INTERNALDATE "31-Jul-2002 09:18:02 -0500")"
998                                 */
999                                $paren_pos = strpos($line, '(');
1000                                $foo       = substr($line, 0, $paren_pos);
1001                                $a         = explode(' ', $foo);
1002                                $id        = $a[1];
1003                               
1004                                $open_pos  = strpos($line, '"') + 1;
1005                                $close_pos = strrpos($line, '"');
1006                                if ($open_pos && $close_pos) {
1007                                        $len         = $close_pos - $open_pos;
1008                                        $time_str    = substr($line, $open_pos, $len);
1009                                        $result[$id] = strtotime($time_str);
1010                                }
1011                        } else {
1012                                $a = explode(' ', $line);
1013                        }
1014                } while (!iil_StartsWith($a[0], $key));
1015        } else {
1016                if ($mode >= 3) {
1017                    $field_name = 'FLAGS';
1018                } else if ($index_field == 'SIZE') {
1019                    $field_name = 'RFC822.SIZE';
1020                } else {
1021                    $field_name = $index_field;
1022                }
1023       
1024                /*                      FETCH uid, size, flags          */
1025                $key     = 'fhi' .($c++);
1026                $request = $key . " FETCH $message_set ($field_name)";
1027
1028                if (!iil_PutLine($fp, $request)) {
1029                    return false;
1030                }
1031                do {
1032                        $line=chop(iil_ReadLine($fp, 200));
1033                        $a = explode(' ', $line);
1034                        if (($line[0] == '*') && ($a[2] == 'FETCH')) {
1035                                $line = str_replace('(', '', $line);
1036                                $line = str_replace(')', '', $line);
1037                                $a    = explode(' ', $line);
1038                               
1039                                $id = $a[1];
1040
1041                                if (isset($result[$id])) {
1042                                    continue; //if we already got the data, skip forward
1043                                }
1044                                if ($a[3]!=$field_name) {
1045                                        continue;  //make sure it's returning what we requested
1046                                }
1047               
1048                                /*  Caution, bad assumptions, next several lines */
1049                                if ($mode == 2) {
1050                                    $result[$id] = $a[4];
1051                                } else {
1052                                        $haystack    = strtoupper($line);
1053                                        $result[$id] = (strpos($haystack, $index_field) > 0 ? "F" : "N");
1054                                }
1055                        }
1056                } while (!iil_StartsWith($line, $key));
1057        }
1058
1059        //check number of elements...
1060        list($start_mid, $end_mid) = explode(':', $message_set);
1061        if (is_numeric($start_mid) && is_numeric($end_mid)) {
1062                //count how many we should have
1063                $should_have = $end_mid - $start_mid +1;
1064               
1065                //if we have less, try and fill in the "gaps"
1066                if (count($result) < $should_have) {
1067                        for ($i=$start_mid; $i<=$end_mid; $i++) {
1068                                if (!isset($result[$i])) {
1069                                        $result[$i] = '';
1070                                }
1071                        }
1072                }
1073        }
1074        return $result;
1075}
1076
1077function iil_CompressMessageSet($message_set) {
1078        //given a comma delimited list of independent mid's,
1079        //compresses by grouping sequences together
1080       
1081        //if less than 255 bytes long, let's not bother
1082        if (strlen($message_set)<255) {
1083            return $message_set;
1084        }
1085   
1086        //see if it's already been compress
1087        if (strpos($message_set, ':') !== false) {
1088            return $message_set;
1089        }
1090   
1091        //separate, then sort
1092        $ids = explode(',', $message_set);
1093        sort($ids);
1094       
1095        $result = array();
1096        $start  = $prev = $ids[0];
1097
1098        foreach ($ids as $id) {
1099                $incr = $id - $prev;
1100                if ($incr > 1) {                        //found a gap
1101                        if ($start == $prev) {
1102                            $result[] = $prev;  //push single id
1103                        } else {
1104                            $result[] = $start . ':' . $prev;   //push sequence as start_id:end_id
1105                        }
1106                        $start = $id;                   //start of new sequence
1107                }
1108                $prev = $id;
1109        }
1110
1111        //handle the last sequence/id
1112        if ($start==$prev) {
1113            $result[] = $prev;
1114        } else {
1115            $result[] = $start.':'.$prev;
1116        }
1117   
1118        //return as comma separated string
1119        return implode(',', $result);
1120}
1121
1122function iil_C_UIDsToMIDs(&$conn, $mailbox, $uids) {
1123        if (!is_array($uids) || count($uids) == 0) {
1124            return array();
1125        }
1126        return iil_C_Search($conn, $mailbox, 'UID ' . implode(',', $uids));
1127}
1128
1129function iil_C_UIDToMID(&$conn, $mailbox, $uid) {
1130        $result = iil_C_UIDsToMIDs($conn, $mailbox, array($uid));
1131        if (count($result) == 1) {
1132            return $result[0];
1133        }
1134        return false;
1135}
1136
1137function iil_C_FetchUIDs(&$conn,$mailbox) {
1138        global $clock;
1139       
1140        $num = iil_C_CountMessages($conn, $mailbox);
1141        if ($num == 0) {
1142            return array();
1143        }
1144        $message_set = '1' . ($num>1?':' . $num:'');
1145       
1146        //if cache not enabled, just call iil_C_FetchHeaderIndex on 'UID' field
1147        if (!$conn->do_cache)
1148                return iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
1149
1150        //otherwise, let's check cache first
1151        $key        = $mailbox.'.uids';
1152        $cache_good = true;
1153        if ($conn->uid_cache) {
1154            $data = $conn->uid_cache;
1155        } else {
1156            $data = cache_read($conn->user, $conn->host, $key);
1157        }
1158   
1159        //was anything cached at all?
1160        if ($data === false) {
1161            $cache_good = -1;
1162        }
1163   
1164        //make sure number of messages were the same
1165        if ($cache_good > 0 && $data['n'] != $num) {
1166            $cache_good = -2;
1167        }
1168   
1169        //if everything's okay so far...
1170        if ($cache_good > 0) {
1171                //check UIDs of highest mid with current and cached
1172                $temp = iil_C_Search($conn, $mailbox, 'UID ' . $data['d'][$num]);
1173                if (!$temp || !is_array($temp) || $temp[0] != $num) {
1174                    $cache_good = -3;
1175                }
1176        }
1177
1178        //if cached data's good, return it
1179        if ($cache_good > 0) {
1180                return $data['d'];
1181        }
1182
1183        //otherwise, we need to fetch it
1184        $data      = array('n' => $num, 'd' => array());
1185        $data['d'] = iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
1186   
1187        cache_write($conn->user, $conn->host, $key, $data);
1188        $conn->uid_cache = $data;
1189        return $data['d'];
1190}
1191
1192function iil_SortThreadHeaders($headers, $index_a, $uids) {
1193        asort($index_a);
1194        $result = array();
1195        foreach ($index_a as $mid=>$foobar) {
1196                $uid = $uids[$mid];
1197                $result[$uid] = $headers[$uid];
1198        }
1199        return $result;
1200}
1201
1202function iil_C_FetchThreadHeaders(&$conn, $mailbox, $message_set) {
1203        global $clock;
1204        global $index_a;
1205       
1206        list($from_idx, $to_idx) = explode(':', $message_set);
1207        if (empty($message_set) || (isset($to_idx)
1208        && (int)$from_idx > (int)$to_idx)) {
1209                return false;
1210        }
1211
1212        $result = array();
1213        $uids   = iil_C_FetchUIDs($conn, $mailbox);
1214        $debug  = false;
1215       
1216        /* Get cached records where possible */
1217        if ($conn->do_cache) {
1218                $cached = cache_read($conn->user, $conn->host, $mailbox.'.thhd');
1219                if ($cached && is_array($uids) && count($uids)>0) {
1220                        $needed_set = '';
1221                        foreach ($uids as $id=>$uid) {
1222                                if ($cached[$uid]) {
1223                                        $result[$uid]     = $cached[$uid];
1224                                        $result[$uid]->id = $id;
1225                                } else {
1226                                    $needed_set .= ($needed_set ? ',' : '') . $id;
1227                                }
1228                        }
1229                        if ($needed_set) {
1230                            $message_set = $needed_set;