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

Revision 1691, 66.4 kB (checked in by alec, 12 hours ago)

- Added option 'quota_zero_as_unlimited' (#1484604)

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