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

Last change on this file since 1691 was 1691, checked in by alec, 5 years ago
  • Added option 'quota_zero_as_unlimited' (#1484604)
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 66.4 KB
Line 
1<?php
2/////////////////////////////////////////////////////////
3//     
4//      Iloha IMAP Library (IIL)
5//
6//      (C)Copyright 2002 Ryo Chijiiwa <Ryo@IlohaMail.org>
7//
8//      This file is part of IlohaMail. IlohaMail is free software released
9//      under the GPL license.  See enclosed file COPYING for details, or
10//      see http://www.fsf.org/copyleft/gpl.html
11//
12/////////////////////////////////////////////////////////
13
14/********************************************************
15
16        FILE: include/imap.inc
17        PURPOSE:
18                Provide alternative IMAP library that doesn't rely on the standard
19                C-Client based version.  This allows IlohaMail to function regardless
20                of whether or not the PHP build it's running on has IMAP functionality
21                built-in.
22        USEAGE:
23                Function containing "_C_" in name require connection handler to be
24                passed as one of the parameters.  To obtain connection handler, use
25                iil_Connect()
26        VERSION:
27                IlohaMail-0.9-20050415
28        CHANGES:
29                File altered by Thomas Bruederli <roundcube@gmail.com>
30                to fit enhanced equirements by the RoundCube Webmail:
31                - Added list of server capabilites and check these before invoking commands
32                - Added junk flag to iilBasicHeader
33                - Enhanced error reporting on fsockopen()
34                - Additional parameter for SORT command
35                - Removed Call-time pass-by-reference because deprecated
36                - Parse charset from content-type in iil_C_FetchHeaders()
37                - Enhanced heaer sorting
38                - Pass message as reference in iil_C_Append (to save memory)
39                - Added BCC and REFERENCE to the list of headers to fetch in iil_C_FetchHeaders()
40                - Leave messageID unchanged in iil_C_FetchHeaders()
41                - Avoid stripslahes in iil_Connect()
42                - Escape quotes and backslashes in iil_C_Login()
43                - Added patch to iil_SortHeaders() by Richard Green
44                - Removed <br> from error messages (better for logging)
45                - Added patch to iil_C_Sort() enabling UID SORT commands
46                - Added function iil_C_ID2UID()
47                - Casting date parts in iil_StrToTime() to avoid mktime() warnings
48                - Also acceppt LIST responses in iil_C_ListSubscribed()
49                - Sanity check of $message_set in iil_C_FetchHeaders(), iil_C_FetchHeaderIndex(), iil_C_FetchThreadHeaders()
50                - Implemented UID FETCH in iil_C_FetchHeaders()
51                - Abort do-loop on socket errors (fgets returns false)
52                - $ICL_SSL is not boolean anymore but contains the connection schema (ssl or tls)
53                - Removed some debuggers (echo ...)
54                File altered by Aleksander Machniak <alec@alec.pl>
55                - 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();
1229        $uids   = iil_C_FetchUIDs($conn, $mailbox);
1230        $debug  = false;
1231       
1232        /* Get cached records where possible */
1233        if ($conn->do_cache) {
1234                $cached = cache_read($conn->user, $conn->host, $mailbox.'.thhd');
1235                if ($cached && is_array($uids) && count($uids)>0) {
1236                        $needed_set = '';
1237                        foreach ($uids as $id=>$uid) {
1238                                if ($cached[$uid]) {
1239                                        $result[$uid]     = $cached[$uid];
1240                                        $result[$uid]->id = $id;
1241                                } else {
1242                                    $needed_set .= ($needed_set ? ',' : '') . $id;
1243                                }
1244                        }
1245                        if ($needed_set) {
1246                            $message_set = $needed_set;
1247                        } else {
1248                            $message_set = '';
1249                        }
1250                }
1251        }
1252        $message_set = iil_CompressMessageSet($message_set);
1253        if ($debug) {
1254            echo "Still need: ".$message_set;
1255        }
1256   
1257        /* if we're missing any, get them */
1258        if ($message_set) {
1259                /* FETCH date,from,subject headers */
1260                $key        = 'fh';
1261                $fp         = $conn->fp;
1262                $request    = $key . " FETCH $message_set ";
1263                $request   .= "(BODY.PEEK[HEADER.FIELDS (SUBJECT MESSAGE-ID IN-REPLY-TO)])";
1264                $mid_to_id  = array();
1265                if (!iil_PutLine($fp, $request)) {
1266                    return false;
1267                }
1268                do {
1269                        $line = chop(iil_ReadLine($fp, 1024));
1270                        if ($debug) {
1271                            echo $line . "\n";
1272                        }
1273                        if (ereg('\{[0-9]+\}$', $line)) {
1274                                $a       = explode(' ', $line);
1275                                $new = array();
1276
1277                                $new_thhd = new iilThreadHeader;
1278                                $new_thhd->id = $a[1];
1279                                do {
1280                                        $line = chop(iil_ReadLine($fp, 1024), "\r\n");
1281                                        if (iil_StartsWithI($line, 'Message-ID:')
1282                                                || (iil_StartsWithI($line,'In-Reply-To:'))
1283                                                || (iil_StartsWithI($line,'SUBJECT:'))) {
1284
1285                                                $pos        = strpos($line, ':');
1286                                                $field_name = substr($line, 0, $pos);
1287                                                $field_val  = substr($line, $pos+1);
1288
1289                                                $new[strtoupper($field_name)] = trim($field_val);
1290
1291                                        } else if (ereg('^[[:space:]]', $line)) {
1292                                                $new[strtoupper($field_name)] .= trim($line);
1293                                        }
1294                                } while ($line[0] != ')');
1295               
1296                                $new_thhd->sbj = $new['SUBJECT'];
1297                                $new_thhd->mid = substr($new['MESSAGE-ID'], 1, -1);
1298                                $new_thhd->irt = substr($new['IN-REPLY-TO'], 1, -1);
1299                               
1300                                $result[$uids[$new_thhd->id]] = $new_thhd;
1301                        }
1302                } while (!iil_StartsWith($line, 'fh'));
1303        }
1304       
1305        /* sort headers */
1306        if (is_array($index_a)) {
1307                $result = iil_SortThreadHeaders($result, $index_a, $uids);     
1308        }
1309       
1310        /* write new set to cache */
1311        if ($conn->do_cache) {
1312                if (count($result)!=count($cached)) {
1313                        cache_write($conn->user, $conn->host, $mailbox . '.thhd', $result);
1314                }
1315        }
1316       
1317        //echo 'iil_FetchThreadHeaders:'."\n";
1318        //print_r($result);
1319       
1320        return $result;
1321}
1322
1323function iil_C_BuildThreads2(&$conn, $mailbox, $message_set, &$clock) {
1324        global $index_a;
1325
1326        list($from_idx, $to_idx) = explode(':', $message_set);
1327        if (empty($message_set) || (isset($to_idx)
1328                && (int)$from_idx > (int)$to_idx)) {
1329                return false;
1330        }
1331   
1332        $result    = array();
1333        $roots     = array();
1334        $root_mids = array();
1335        $sub_mids  = array();
1336        $strays    = array();
1337        $messages  = array();
1338        $fp        = $conn->fp;
1339        $debug     = false;
1340       
1341        $sbj_filter_pat = '[a-zA-Z]{2,3}(\[[0-9]*\])?:([[:space:]]*)';
1342       
1343        /*  Do "SELECT" command */
1344        if (!iil_C_Select($conn, $mailbox)) {
1345            return false;
1346        }
1347   
1348        /* FETCH date,from,subject headers */
1349        $mid_to_id = array();
1350        $messages  = array();
1351        $headers   = iil_C_FetchThreadHeaders($conn, $mailbox, $message_set);
1352        if ($clock) {
1353            $clock->register('fetched headers');
1354        }
1355   
1356        if ($debug) {
1357            print_r($headers);
1358        }
1359   
1360        /* go through header records */
1361        foreach ($headers as $header) {
1362                //$id = $header['i'];
1363                //$new = array('id'=>$id, 'MESSAGE-ID'=>$header['m'],
1364                //                      'IN-REPLY-TO'=>$header['r'], 'SUBJECT'=>$header['s']);
1365                $id  = $header->id;
1366                $new = array('id' => $id, 'MESSAGE-ID' => $header->mid,
1367                        'IN-REPLY-TO' => $header->irt, 'SUBJECT' => $header->sbj);
1368
1369                /* add to message-id -> mid lookup table */
1370                $mid_to_id[$new['MESSAGE-ID']] = $id;
1371               
1372                /* if no subject, use message-id */
1373                if (empty($new['SUBJECT'])) {
1374                    $new['SUBJECT'] = $new['MESSAGE-ID'];
1375                }
1376       
1377                /* if subject contains 'RE:' or has in-reply-to header, it's a reply */
1378                $sbj_pre ='';
1379                $has_re = false;
1380                if (eregi($sbj_filter_pat, $new['SUBJECT'])) {
1381                    $has_re = true;
1382                }
1383                if ($has_re||$new['IN-REPLY-TO']) {
1384                    $sbj_pre = 'RE:';
1385                }
1386       
1387                /* strip out 're:', 'fw:' etc */
1388                if ($has_re) {
1389                    $sbj = ereg_replace($sbj_filter_pat, '', $new['SUBJECT']);
1390                } else {
1391                    $sbj = $new['SUBJECT'];
1392                }
1393                $new['SUBJECT'] = $sbj_pre.$sbj;
1394               
1395               
1396                /* if subject not a known thread-root, add to list */
1397                if ($debug) {
1398                    echo $id . ' ' . $new['SUBJECT'] . "\t" . $new['MESSAGE-ID'] . "\n";
1399                }
1400                $root_id = $roots[$sbj];
1401               
1402                if ($root_id && ($has_re || !$root_in_root[$root_id])) {
1403                        if ($debug) {
1404                            echo "\tfound root: $root_id\n";
1405                        }
1406                        $sub_mids[$new['MESSAGE-ID']] = $root_id;
1407                        $result[$root_id][]           = $id;
1408                } else if (!isset($roots[$sbj]) || (!$has_re && $root_in_root[$root_id])) {
1409                        /* try to use In-Reply-To header to find root
1410                                unless subject contains 'Re:' */
1411                        if ($has_re&&$new['IN-REPLY-TO']) {
1412                                if ($debug) {
1413                                    echo "\tlooking: ".$new['IN-REPLY-TO']."\n";
1414                                }
1415                                //reply to known message?
1416                                $temp = $sub_mids[$new['IN-REPLY-TO']];
1417                               
1418                                if ($temp) {
1419                                        //found it, root:=parent's root
1420                                        if ($debug) {
1421                                            echo "\tfound parent: ".$new['SUBJECT']."\n";
1422                                        }
1423                                        $result[$temp][]              = $id;
1424                                        $sub_mids[$new['MESSAGE-ID']] = $temp;
1425                                        $sbj                          = '';
1426                                } else {
1427                                        //if we can't find referenced parent, it's a "stray"
1428                                        $strays[$id] = $new['IN-REPLY-TO'];
1429                                }
1430                        }
1431                       
1432                        //add subject as root
1433                        if ($sbj) {
1434                                if ($debug) {
1435                                    echo "\t added to root\n";
1436                                }
1437                                $roots[$sbj]                  = $id;
1438                                $root_in_root[$id]            = !$has_re;
1439                                $sub_mids[$new['MESSAGE-ID']] = $id;
1440                                $result[$id]                  = array($id);
1441                        }
1442                        if ($debug) {
1443                            echo $new['MESSAGE-ID'] . "\t" . $sbj . "\n";
1444                        }
1445                }
1446        }
1447       
1448        //now that we've gone through all the messages,
1449        //go back and try and link up the stray threads
1450        if (count($strays) > 0) {
1451                foreach ($strays as $id=>$irt) {
1452                        $root_id = $sub_mids[$irt];
1453                        if (!$root_id || $root_id==$id) {
1454                            continue;
1455                        }
1456                        $result[$root_id] = array_merge($result[$root_id],$result[$id]);
1457                        unset($result[$id]);
1458                }
1459        }
1460       
1461        if ($clock) {
1462            $clock->register('data prepped');
1463        }
1464   
1465        if ($debug) {
1466            print_r($roots);
1467        }
1468
1469        return $result;
1470}
1471
1472function iil_SortThreads(&$tree, $index, $sort_order = 'ASC') {
1473        if (!is_array($tree) || !is_array($index)) {
1474            return false;
1475        }
1476   
1477        //create an id to position lookup table
1478        $i = 0;
1479        foreach ($index as $id=>$val) {
1480                $i++;
1481                $index[$id] = $i;
1482        }
1483        $max = $i+1;
1484       
1485        //for each tree, set array key to position
1486        $itree = array();
1487        foreach ($tree as $id=>$node) {
1488                if (count($tree[$id])<=1) {
1489                        //for "threads" with only one message, key is position of that message
1490                        $n         = $index[$id];
1491                        $itree[$n] = array($n=>$id);
1492                } else {
1493                        //for "threads" with multiple messages,
1494                        $min   = $max;
1495                        $new_a = array();
1496                        foreach ($tree[$id] as $mid) {
1497                                $new_a[$index[$mid]] = $mid;            //create new sub-array mapping position to id
1498                                $pos                 = $index[$mid];
1499                                if ($pos&&$pos<$min) {
1500                                    $min = $index[$mid];        //find smallest position
1501                                }
1502                        }
1503                        $n = $min;      //smallest position of child is thread position
1504                       
1505                        //assign smallest position to root level key
1506                        //set children array to one created above
1507                        ksort($new_a);
1508                        $itree[$n] = $new_a;
1509                }
1510        }
1511       
1512        //sort by key, this basically sorts all threads
1513        ksort($itree);
1514        $i   = 0;
1515        $out = array();
1516        foreach ($itree as $k=>$node) {
1517                $out[$i] = $itree[$k];
1518                $i++;
1519        }
1520       
1521        return $out;
1522}
1523
1524function iil_IndexThreads(&$tree) {
1525        /* creates array mapping mid to thread id */
1526       
1527        if (!is_array($tree)) {
1528            return false;
1529        }
1530   
1531        $t_index = array();
1532        foreach ($tree as $pos=>$kids) {
1533                foreach ($kids as $kid) $t_index[$kid] = $pos;
1534        }
1535       
1536        return $t_index;
1537}
1538
1539function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false)
1540{
1541        global $IMAP_USE_INTERNAL_DATE;
1542       
1543        $c      = 0;
1544        $result = array();
1545        $fp     = $conn->fp;
1546       
1547        list($from_idx, $to_idx) = explode(':', $message_set);
1548        if (empty($message_set) || (isset($to_idx)
1549                && (int)$from_idx > (int)$to_idx)) {
1550                return false;
1551        }
1552               
1553        /*  Do "SELECT" command */
1554        if (!iil_C_Select($conn, $mailbox)) {
1555                $conn->error = "Couldn't select $mailbox";
1556                return false;
1557        }
1558               
1559        /* Get cached records where possible */
1560        if ($conn->do_cache) {
1561                $uids = iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, "UID");
1562                if (is_array($uids) && count($conn->cache[$mailbox]>0)) {
1563                        $needed_set = '';
1564                        while (list($id,$uid)=each($uids)) {
1565                                if ($conn->cache[$mailbox][$uid]) {
1566                                        $result[$id]     = $conn->cache[$mailbox][$uid];
1567                                        $result[$id]->id = $id;
1568                                } else {
1569                                    $needed_set.=($needed_set ? ',': '') . $id;
1570                                }
1571                        }
1572                        //echo "<!-- iil_C_FetchHeader\nMessage Set: $message_set\nNeeded Set:$needed_set\n//-->\n";
1573                        if ($needed_set) {
1574                                $message_set = iil_CompressMessageSet($needed_set);
1575                        } else {
1576                                return $result;
1577                        }
1578                }
1579        }
1580
1581        /* FETCH date,from,subject headers */
1582        $key      = 'fh' . ($c++);
1583        $prefix   = $uidfetch?' UID':'';
1584        $request  = $key . $prefix;
1585        $request .= " FETCH $message_set (BODY.PEEK[HEADER.FIELDS ";
1586        $request .= "(DATE FROM TO SUBJECT REPLY-TO IN-REPLY-TO CC BCC ";
1587        $request .= "CONTENT-TRANSFER-ENCODING CONTENT-TYPE MESSAGE-ID ";
1588        $request .= "REFERENCES DISPOSITION-NOTIFICATION-TO X-PRIORITY)])";
1589
1590        if (!iil_PutLine($fp, $request)) {
1591                return false;
1592        }
1593        do {
1594                $line = chop(iil_ReadLine($fp, 200));
1595                $a    = explode(' ', $line);
1596                if (($line[0] == '*') && ($a[2] == 'FETCH')) {
1597                        $id = $a[1];
1598           
1599                        $result[$id]            = new iilBasicHeader;
1600                        $result[$id]->id        = $id;
1601                        $result[$id]->subject   = '';
1602                        $result[$id]->messageID = 'mid:' . $id;
1603
1604                        /*
1605                                Start parsing headers.  The problem is, some header "lines" take up multiple lines.
1606                                So, we'll read ahead, and if the one we're reading now is a valid header, we'll
1607                                process the previous line.  Otherwise, we'll keep adding the strings until we come
1608                                to the next valid header line.
1609                        */
1610                        $i     = 0;
1611                        $lines = array();
1612                        do {
1613                                $line = chop(iil_ReadLine($fp, 300), "\r\n");
1614                                if (ord($line[0])<=32) {
1615                                    $lines[$i] .= (empty($lines[$i])?'':"\n").trim($line);
1616                                } else {
1617                                        $i++;
1618                                        $lines[$i] = trim($line);
1619                                }
1620                                /*
1621                                        The preg_match below works around communigate imap, which outputs " UID <number>)".
1622                                        Without this, the while statement continues on and gets the "fh0 OK completed" message.
1623                                        If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249. 
1624                                        This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing
1625                                        If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin
1626                                        An alternative might be:
1627                                        if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break;
1628                                        however, unsure how well this would work with all imap clients.
1629                                */
1630                                if (preg_match("/^\s*UID [0-9]+\)$/", $line)) {
1631                                    break;
1632                                }
1633                        // patch from "Maksim Rubis" <siburny@hotmail.com>
1634                        } while (trim($line[0]) != ')' && strncmp($line, $key, strlen($key)));
1635                       
1636                        if (strncmp($line, $key, strlen($key))) {
1637                                //process header, fill iilBasicHeader obj.
1638                                //      initialize
1639                                if (is_array($headers)) {
1640                                        reset($headers);
1641                                        while (list($k, $bar) = each($headers)) {
1642                                                $headers[$k] = '';
1643                                        }
1644                                }
1645       
1646                                //      create array with header field:data
1647                                while ( list($lines_key, $str) = each($lines) ) {
1648                                        list($field, $string) = iil_SplitHeaderLine($str);
1649                                       
1650                                        $field  = strtolower($field);
1651                                        $string = ereg_replace("\n[[:space:]]*"," ",$string);
1652                                       
1653                                        switch ($field) {
1654                                        case 'date';
1655                                                $result[$id]->date = $string;
1656                                                $result[$id]->timestamp = iil_StrToTime($string);
1657                                                break;
1658                                        case 'from':
1659                                                $result[$id]->from = $string;
1660                                                break;
1661                                        case 'to':
1662                                                $result[$id]->to = $string;
1663                                                break;
1664                                        case 'subject':
1665                                                $result[$id]->subject = $string;
1666                                                break;
1667                                        case 'reply-to':
1668                                                $result[$id]->replyto = $string;
1669                                                break;
1670                                        case 'cc':
1671                                                $result[$id]->cc = $string;
1672                                                break;
1673                                        case 'bcc':
1674                                                $result[$id]->bcc = $string;
1675                                                break;
1676                                        case 'content-transfer-encoding':
1677                                                $result[$id]->encoding = $string;
1678                                                break;
1679                                        case 'content-type':
1680                                                $ctype_parts = explode(";", $string);
1681                                                $result[$id]->ctype = array_shift($ctype_parts);
1682                                                foreach ($ctype_parts as $ctype_add) {
1683                                                        if (preg_match('/charset="?([a-z0-9\-\.\_]+)"?/i',
1684                                                                $ctype_add, $regs)) {
1685                                                                $result[$id]->charset = $regs[1];
1686                                                        }
1687                                                }
1688                                                break;
1689                                        case 'in-reply-to':
1690                                                $result[$id]->in_reply_to = ereg_replace("[\n<>]", '', $string);
1691                                                break;
1692                                        case 'references':
1693                                                $result[$id]->references = $string;
1694                                                break;
1695                                        case 'return-receipt-to':
1696                                        case 'disposition-notification-to':
1697                                        case 'x-confirm-reading-to':
1698                                                $result[$id]->mdn_to = $string;
1699                                                break;
1700                                        case 'message-id':
1701                                                $result[$id]->messageID = $string;
1702                                                break;
1703                                        case 'x-priority':
1704                                                if (preg_match('/^(\d+)/', $string, $matches))
1705                                                        $result[$id]->priority = intval($matches[1]);
1706                                                break;
1707                                        } // end switch ()
1708                                } // end while ()
1709                        } else {
1710                                $a = explode(' ', $line);
1711                        }
1712                }
1713        } while (strcmp($a[0], $key) != 0);
1714
1715        /*
1716                FETCH uid, size, flags
1717                Sample reply line: "* 3 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen \Deleted))"
1718        */
1719        $command_key = 'fh' . ($c++);
1720        $request     = $command_key . $prefix;
1721        $request    .= " FETCH $message_set (UID RFC822.SIZE FLAGS INTERNALDATE)";
1722       
1723        if (!iil_PutLine($fp, $request)) {
1724            return false;
1725        }
1726        do {
1727                $line = chop(iil_ReadLine($fp, 200));
1728                //$a = explode(' ', $line);
1729                //if (($line[0]=="*") && ($a[2]=="FETCH")) {
1730                if ($line[0] == '*') {
1731                        //echo "<!-- $line //-->\n";
1732                        //get outter most parens
1733                        $open_pos = strpos($line, "(") + 1;
1734                        $close_pos = strrpos($line, ")");
1735                        if ($open_pos && $close_pos) {
1736                                //extract ID from pre-paren
1737                                $pre_str = substr($line, 0, $open_pos);
1738                                $pre_a = explode(' ', $line);
1739                                $id = $pre_a[1];
1740                               
1741                                //get data
1742                                $len = $close_pos - $open_pos;
1743                                $str = substr($line, $open_pos, $len);
1744                               
1745                                //swap parents with quotes, then explode
1746                                $str = eregi_replace("[()]", "\"", $str);
1747                                $a = iil_ExplodeQuotedString(' ', $str);
1748                               
1749                                //did we get the right number of replies?
1750                                $parts_count = count($a);
1751                                if ($parts_count>=8) {
1752                                        for ($i=0;$i<$parts_count;$i=$i+2) {
1753                                                if (strcasecmp($a[$i],"UID") == 0) $result[$id]->uid=$a[$i+1];
1754                                                else if (strcasecmp($a[$i],"RFC822.SIZE") == 0) $result[$id]->size=$a[$i+1];
1755                                                else if (strcasecmp($a[$i],"INTERNALDATE") == 0) $time_str = $a[$i+1];
1756                                                else if (strcasecmp($a[$i],"FLAGS") == 0) $flags_str = $a[$i+1];
1757                                        }
1758
1759                                        // process flags
1760                                        $flags_str = eregi_replace('[\\\"]', '', $flags_str);
1761                                        $flags_a   = explode(' ', $flags_str);
1762                                       
1763                                        if (is_array($flags_a)) {
1764                                                reset($flags_a);
1765                                                while (list($key,$val)=each($flags_a)) {
1766                                                        if (strcasecmp($val,'Seen') == 0) {
1767                                                            $result[$id]->seen = true;
1768                                                        } else if (strcasecmp($val, 'Deleted') == 0) {
1769                                                            $result[$id]->deleted=true;
1770                                                        } else if (strcasecmp($val, 'Recent') == 0) {
1771                                                            $result[$id]->recent = true;
1772                                                        } else if (strcasecmp($val, 'Answered') == 0) {
1773                                                            $result[$id]->answered = true;
1774                                                        } else if (strcasecmp($val, '$Forwarded') == 0) {
1775                                                            $result[$id]->forwarded = true;
1776                                                        } else if (strcasecmp($val, 'Draft') == 0) {
1777                                                            $result[$id]->is_draft = true;
1778                                                        } else if (strcasecmp($val, '$MDNSent') == 0) {
1779                                                            $result[$id]->mdn_sent = true;
1780                                                        } else if (strcasecmp($val, 'Flagged') == 0) {
1781                                                             $result[$id]->flagged = true;
1782                                                        }
1783                                                }
1784                                                $result[$id]->flags = $flags_a;
1785                                        }
1786                       
1787                                        // if time is gmt...   
1788                                        $time_str = str_replace('GMT','+0000',$time_str);
1789                                       
1790                                        //get timezone
1791                                        $time_str      = substr($time_str, 0, -1);
1792                                        $time_zone_str = substr($time_str, -5); //extract timezone
1793                                        $time_str      = substr($time_str, 1, -6); //remove quotes
1794                                        $time_zone     = (float)substr($time_zone_str, 1, 2); //get first two digits
1795                                        if ($time_zone_str[3] != '0') {
1796                                            $time_zone += 0.5;  //handle half hour offset
1797                                        }
1798                                        if ($time_zone_str[0] == '-') {
1799                                                $time_zone = $time_zone * -1.0; //minus?
1800                                        }
1801                                        $result[$id]->internaldate = $time_str;
1802                                       
1803                                        if ($IMAP_USE_INTERNAL_DATE || empty($result[$id]->date)) {
1804                                                //calculate timestamp
1805                                                $timestamp     = strtotime($time_str); //return's server's time
1806                                                $na_timestamp  = $timestamp;
1807                                                $timestamp    -= $time_zone * 3600; //compensate for tz, get GMT
1808                                               
1809                                                $result[$id]->timestamp = $timestamp;
1810                                                $result[$id]->date = $time_str;
1811                                        }
1812                                               
1813                                        if ($conn->do_cache) {
1814                                                $uid = $result[$id]->uid;
1815                                                $conn->cache[$mailbox][$uid] = $result[$id];
1816                                                $conn->cache_dirty[$mailbox] = true;
1817                                        }
1818                                        //echo "<!-- ID: $id : $time_str -- local: $na_timestamp (".date("F j, Y, g:i a", $na_timestamp).") tz: $time_zone -- GMT: ".$timestamp." (".date("F j, Y, g:i a", $timestamp).")  //-->\n";
1819                                } else {
1820                                        //echo "<!-- ERROR: $id : $str //-->\n";
1821                                }
1822                        }
1823                }
1824        } while (strpos($line, $command_key) === false);
1825               
1826        return $result;
1827}
1828
1829function iil_C_FetchHeader(&$conn, $mailbox, $id, $uidfetch=false) {
1830        $fp = $conn->fp;
1831        $a  = iil_C_FetchHeaders($conn, $mailbox, $id, $uidfetch);
1832        if (is_array($a)) {
1833                return array_shift($a);
1834        }
1835        return false;
1836}
1837
1838function iil_SortHeaders($a, $field, $flag) {
1839        if (empty($field)) {
1840            $field = 'uid';
1841        }
1842        $field = strtolower($field);
1843        if ($field == 'date' || $field == 'internaldate') {
1844            $field = 'timestamp';
1845        }
1846        if (empty($flag)) {
1847            $flag = 'ASC';
1848        }
1849   
1850        $flag     = strtoupper($flag);
1851        $stripArr = ($field=='subject') ? array('Re: ','Fwd: ','Fw: ','"') : array('"');
1852
1853        $c=count($a);
1854        if ($c > 0) {
1855                /*
1856                        Strategy:
1857                        First, we'll create an "index" array.
1858                        Then, we'll use sort() on that array,
1859                        and use that to sort the main array.
1860                */
1861               
1862                // create "index" array
1863                $index = array();
1864                reset($a);
1865                while (list($key, $val)=each($a)) {
1866
1867                        if ($field == 'timestamp') {
1868                                $data = @strtotime($val->date);
1869                                if ($data == false) {
1870                                        $data = $val->timestamp;
1871                                }
1872                        } else {
1873                                $data = $val->$field;
1874                                if (is_string($data)) {
1875                                        $data=strtoupper(str_replace($stripArr, '', $data));
1876                                }
1877                        }
1878                        $index[$key]=$data;
1879                }
1880               
1881                // sort index
1882                $i = 0;
1883                if ($flag == 'ASC') {
1884                        asort($index);
1885                } else {
1886                        arsort($index);
1887                }
1888       
1889                // form new array based on index
1890                $result = array();
1891                reset($index);
1892                while (list($key, $val)=each($index)) {
1893                        $result[$key]=$a[$key];
1894                        $i++;
1895                }
1896        }
1897       
1898        return $result;
1899}
1900
1901function iil_C_Expunge(&$conn, $mailbox) {
1902
1903        if (iil_C_Select($conn, $mailbox)) {
1904                $c = 0;
1905                iil_PutLine($conn->fp, "exp1 EXPUNGE");
1906                do {
1907                        $line=chop(iil_ReadLine($conn->fp, 100));
1908                        if ($line[0] == '*') {
1909                                $c++;
1910                        }
1911                } while (!iil_StartsWith($line, 'exp1'));
1912               
1913                if (iil_ParseResult($line) == 0) {
1914                        $conn->selected = ''; //state has changed, need to reselect                     
1915                        //$conn->exists-=$c;
1916                        return $c;
1917                }
1918                $conn->error = $line;
1919        }
1920       
1921        return -1;
1922}
1923
1924function iil_C_ModFlag(&$conn, $mailbox, $messages, $flag, $mod) {
1925        if ($mod != '+' && $mod != '-') {
1926            return -1;
1927        }
1928   
1929        $fp    = $conn->fp;
1930        $flags = $GLOBALS['IMAP_FLAGS'];
1931       
1932        $flag = strtoupper($flag);
1933        $flag = $flags[$flag];
1934   
1935        if (iil_C_Select($conn, $mailbox)) {
1936                $c = 0;
1937                iil_PutLine($fp, "flg STORE $messages " . $mod . "FLAGS (" . $flag . ")");
1938                do {
1939                        $line=chop(iil_ReadLine($fp, 100));
1940                        if ($line[0] == '*') {
1941                            $c++;
1942                        }
1943                } while (!iil_StartsWith($line, 'flg'));
1944
1945                if (iil_ParseResult($line) == 0) {
1946                        iil_C_ExpireCachedItems($conn, $mailbox, $messages);
1947                        return $c;
1948                }
1949                $conn->error = $line;
1950                return -1;
1951        }
1952        $conn->error = 'Select failed';
1953        return -1;
1954}
1955
1956function iil_C_Flag(&$conn, $mailbox, $messages, $flag) {
1957        return iil_C_ModFlag($conn, $mailbox, $messages, $flag, '+');
1958}
1959
1960function iil_C_Unflag(&$conn, $mailbox, $messages, $flag) {
1961        return iil_C_ModFlag($conn, $mailbox, $messages, $flag, '-');
1962}
1963
1964function iil_C_Delete(&$conn, $mailbox, $messages) {
1965        return iil_C_ModFlag($conn, $mailbox, $messages, 'DELETED', '+');
1966}
1967
1968function iil_C_Undelete(&$conn, $mailbox, $messages) {
1969        return iil_C_ModFlag($conn, $mailbox, $messages, 'DELETED', '-');
1970}
1971
1972function iil_C_Unseen(&$conn, $mailbox, $messages) {
1973        return iil_C_ModFlag($conn, $mailbox, $messages, 'SEEN', '-');
1974}
1975
1976function iil_C_Copy(&$conn, $messages, $from, $to) {
1977        $fp = $conn->fp;
1978
1979        if (empty($from) || empty($to)) {
1980            return -1;
1981        }
1982   
1983        if (iil_C_Select($conn, $from)) {
1984                $c=0;
1985               
1986                iil_PutLine($fp, "cpy1 COPY $messages \"".iil_Escape($to)."\"");
1987                $line=iil_ReadReply($fp);
1988                return iil_ParseResult($line);
1989        } else {
1990                return -1;
1991        }
1992}
1993
1994function iil_FormatSearchDate($month, $day, $year) {
1995        $month  = (int) $month;
1996        $months = $GLOBALS['IMAP_MONTHS'];
1997        return $day . '-' . $months[$month] . '-' . $year;
1998}
1999
2000function iil_C_CountUnseen(&$conn, $folder) {
2001        $index = iil_C_Search($conn, $folder, 'ALL UNSEEN');
2002        if (is_array($index)) {
2003                $str = implode(',', $index);
2004                if (empty($str)) {
2005                    return false;
2006                }
2007                return count($index);
2008        }
2009        return false;
2010}
2011
2012function iil_C_UID2ID(&$conn, $folder, $uid) {
2013        if ($uid > 0) {
2014                $id_a = iil_C_Search($conn, $folder, "UID $uid");
2015                if (is_array($id_a)) {
2016                        $count = count($id_a);
2017                        if ($count > 1) {
2018                            return false;
2019                        }
2020                        return $id_a[0];
2021                }
2022        }
2023        return false;
2024}
2025
2026function iil_C_ID2UID(&$conn, $folder, $id) {
2027        $fp = $conn->fp;
2028        if ($id == 0) {
2029            return      -1;
2030        }
2031        $result = -1;
2032        if (iil_C_Select($conn, $folder)) {
2033                $key = 'FUID';
2034                if (iil_PutLine($fp, "$key FETCH $id (UID)")) {
2035                        do {
2036                                $line=chop(iil_ReadLine($fp, 1024));
2037                                if (eregi("^\* $id FETCH \(UID (.*)\)", $line, $r)) {
2038                                        $result = $r[1];
2039                                }
2040                        } while (!preg_match("/^$key/", $line));
2041                }
2042        }
2043        return $result;
2044}
2045
2046function iil_C_Search(&$conn, $folder, $criteria) {
2047        $fp = $conn->fp;
2048        if (iil_C_Select($conn, $folder)) {
2049                $c = 0;
2050               
2051                $query = 'srch1 SEARCH ' . chop($criteria);
2052                iil_PutLine($fp, $query);
2053                do {
2054                        $line=trim(iil_ReadLine($fp, 10000));
2055                        if (eregi("^\* SEARCH", $line)) {
2056                                $str = trim(substr($line, 8));
2057                                $messages = explode(' ', $str);
2058                        }
2059                } while (!iil_StartsWith($line, 'srch1'));
2060               
2061                $result_code = iil_ParseResult($line);
2062                if ($result_code == 0) {
2063                    return $messages;
2064                }
2065                $conn->error = 'iil_C_Search: ' . $line . "\n";
2066                return false;   
2067        }
2068        $conn->error = "iil_C_Search: Couldn't select \"$folder\"\n";
2069        return false;
2070}
2071
2072function iil_C_Move(&$conn, $messages, $from, $to) {
2073    $fp = $conn->fp;
2074
2075    if (!$from || !$to) {
2076        return -1;
2077    }
2078    $r = iil_C_Copy($conn, $messages, $from,$to);
2079    if ($r==0) {
2080        return iil_C_Delete($conn, $from, $messages);
2081    }
2082    return $r;
2083}
2084
2085/**
2086 * Gets the delimiter, for example:
2087 * INBOX.foo -> .
2088 * INBOX/foo -> /
2089 * INBOX\foo -> \
2090 *
2091 * @return mixed A delimiter (string), or false.
2092 * @param object $conn The current connection.
2093 * @see iil_Connect()
2094 */
2095function iil_C_GetHierarchyDelimiter(&$conn) {
2096        if ($conn->delimiter) {
2097        return $conn->delimiter;
2098        }
2099   
2100        $fp        = $conn->fp;
2101        $delimiter = false;
2102       
2103        //try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8)
2104        if (!iil_PutLine($fp, 'ghd LIST "" ""')) {
2105            return false;
2106        }
2107   
2108        do {
2109                $line=iil_ReadLine($fp, 500);
2110                if ($line[0] == '*') {
2111                        $line = rtrim($line);
2112                        $a=iil_ExplodeQuotedString(' ', $line);
2113                        if ($a[0] == '*') {
2114                            $delimiter = str_replace('"', '', $a[count($a)-2]);
2115                        }
2116                }
2117        } while (!iil_StartsWith($line, 'ghd'));
2118
2119        if (strlen($delimiter)>0) {
2120            return $delimiter;
2121        }
2122   
2123        //if that fails, try namespace extension
2124        //try to fetch namespace data
2125        iil_PutLine($conn->fp, "ns1 NAMESPACE");
2126        do {
2127                $line = iil_ReadLine($conn->fp, 1024);
2128                if (iil_StartsWith($line, '* NAMESPACE')) {
2129                        $i = 0;
2130                        $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
2131                }
2132        } while (!iil_StartsWith($line, 'ns1'));
2133               
2134        if (!is_array($data)) {
2135            return false;
2136        }
2137   
2138        //extract user space data (opposed to global/shared space)
2139        $user_space_data = $data[0];
2140        if (!is_array($user_space_data)) {
2141            return false;
2142        }
2143   
2144        //get first element
2145        $first_userspace = $user_space_data[0];
2146        if (!is_array($first_userspace)) {
2147            return false;
2148        }
2149   
2150        //extract delimiter
2151        $delimiter = $first_userspace[1];       
2152
2153        return $delimiter;
2154}
2155
2156function iil_C_ListMailboxes(&$conn, $ref, $mailbox) {
2157        global $IGNORE_FOLDERS;
2158       
2159        $ignore = $IGNORE_FOLDERS[strtolower($conn->host)];
2160               
2161        $fp = $conn->fp;
2162       
2163        if (empty($mailbox)) {
2164            $mailbox = '*';
2165        }
2166       
2167        if (empty($ref) && $conn->rootdir) {
2168            $ref = $conn->rootdir;
2169        }
2170   
2171        // send command
2172        if (!iil_PutLine($fp, "lmb LIST \"".$ref."\" \"".iil_Escape($mailbox)."\"")) {
2173            return false;
2174        }
2175   
2176        $i = 0;
2177        // get folder list
2178        do {
2179                $line = iil_ReadLine($fp, 500);
2180                $line = iil_MultLine($fp, $line);
2181
2182                $a = explode(' ', $line);
2183                if (($line[0] == '*') && ($a[1] == 'LIST')) {
2184                        $line = rtrim($line);
2185                        // split one line
2186                        $a = iil_ExplodeQuotedString(' ', $line);
2187                        // last string is folder name
2188                        $folder = trim($a[count($a)-1], '"');
2189           
2190                        if (empty($ignore) || (!empty($ignore)
2191                                && !eregi($ignore, $folder))) {
2192                                $folders[$i] = $folder;
2193                        }
2194           
2195                        // second from last is delimiter
2196                        $delim = trim($a[count($a)-2], '"');
2197                        // is it a container?
2198                        $i++;
2199                }
2200        } while (!iil_StartsWith($line, 'lmb'));
2201
2202        if (is_array($folders)) {
2203            if (!empty($ref)) {
2204                // if rootdir was specified, make sure it's the first element
2205                // some IMAP servers (i.e. Courier) won't return it
2206                if ($ref[strlen($ref)-1]==$delim)
2207                    $ref = substr($ref, 0, strlen($ref)-1);
2208                if ($folders[0]!=$ref)
2209                    array_unshift($folders, $ref);
2210            }
2211            return $folders;
2212        } else if (iil_ParseResult($line) == 0) {
2213                return array('INBOX');
2214        } else {
2215                $conn->error = $line;
2216                return false;
2217        }
2218}
2219
2220function iil_C_ListSubscribed(&$conn, $ref, $mailbox) {
2221        global $IGNORE_FOLDERS;
2222       
2223        $ignore = $IGNORE_FOLDERS[strtolower($conn->host)];
2224       
2225        $fp = $conn->fp;
2226        if (empty($mailbox)) {
2227                $mailbox = '*';
2228        }
2229        if (empty($ref) && $conn->rootdir) {
2230                $ref = $conn->rootdir;
2231        }
2232        $folders = array();
2233
2234        // send command
2235        if (!iil_PutLine($fp, 'lsb LSUB "' . $ref . '" "' . iil_Escape($mailbox).'"')) {
2236                $conn->error = "Couldn't send LSUB command\n";
2237                return false;
2238        }
2239       
2240        $i = 0;
2241       
2242        // get folder list
2243        do {
2244                $line = iil_ReadLine($fp, 500);
2245                $line = iil_MultLine($fp, $line);
2246                $a    = explode(' ', $line);
2247       
2248                if (($line[0] == '*') && ($a[1] == 'LSUB' || $a[1] == 'LIST')) {
2249                        $line = rtrim($line);
2250           
2251                        // split one line
2252                        $a = iil_ExplodeQuotedString(' ', $line);
2253           
2254                        // last string is folder name
2255                        //$folder = UTF7DecodeString(str_replace('"', '', $a[count($a)-1]));
2256                        $folder = trim($a[count($a)-1], '"');
2257           
2258                        if ((!in_array($folder, $folders)) && (empty($ignore)
2259                                || (!empty($ignore) && !eregi($ignore, $folder)))) {
2260                            $folders[$i] = $folder;
2261                        }
2262           
2263                        // second from last is delimiter
2264                        $delim = trim($a[count($a)-2], '"');
2265           
2266                        // is it a container?
2267                        $i++;
2268                }
2269        } while (!iil_StartsWith($line, 'lsb'));
2270
2271        if (is_array($folders)) {
2272            if (!empty($ref)) {
2273                // if rootdir was specified, make sure it's the first element
2274                // some IMAP servers (i.e. Courier) won't return it
2275                if ($ref[strlen($ref)-1]==$delim) {
2276                    $ref = substr($ref, 0, strlen($ref)-1);
2277                }
2278                if ($folders[0]!=$ref) {
2279                    array_unshift($folders, $ref);
2280                }
2281            }
2282            return $folders;
2283        }
2284        $conn->error = $line;
2285        return false;
2286}
2287
2288function iil_C_Subscribe(&$conn, $folder) {
2289        $fp = $conn->fp;
2290
2291        $query = 'sub1 SUBSCRIBE "' . iil_Escape($folder). '"';
2292        iil_PutLine($fp, $query);
2293
2294        $line = trim(iil_ReadLine($fp, 10000));
2295        return iil_ParseResult($line);
2296}
2297
2298function iil_C_UnSubscribe(&$conn, $folder) {
2299        $fp = $conn->fp;
2300
2301        $query = 'usub1 UNSUBSCRIBE "' . iil_Escape($folder) . '"';
2302        iil_PutLine($fp, $query);
2303   
2304        $line = trim(iil_ReadLine($fp, 10000));
2305        return iil_ParseResult($line);
2306}
2307
2308function iil_C_FetchPartHeader(&$conn, $mailbox, $id, $part) {
2309        $fp     = $conn->fp;
2310        $result = false;
2311        if (($part == 0) || (empty($part))) {
2312            $part = 'HEADER';
2313        } else {
2314            $part .= '.MIME';
2315        }
2316   
2317        if (iil_C_Select($conn, $mailbox)) {
2318                $key     = 'fh' . ($c++);
2319                $request = $key . " FETCH $id (BODY.PEEK[$part])";
2320                if (!iil_PutLine($fp, $request)) return false;
2321                do {
2322                        $line = chop(iil_ReadLine($fp, 200));
2323                        $a    = explode(' ', $line);
2324                        if (($line[0] == '*') && ($a[2] == 'FETCH')
2325                && ($line[strlen($line)-1] != ')')) {
2326                                $line=iil_ReadLine($fp, 300);
2327                                while (trim($line) != ')') {
2328                                        $result .= $line;
2329                                        $line=iil_ReadLine($fp, 300);
2330                                }
2331                        }
2332                } while (strcmp($a[0], $key) != 0);
2333        }
2334       
2335        return $result;
2336}
2337
2338function iil_C_HandlePartBody(&$conn, $mailbox, $id, $part, $mode) {
2339        /* modes:
2340        1: return string
2341        2: print
2342        3: base64 and print
2343        */
2344       
2345        $fp     = $conn->fp;
2346        $result = false;
2347        if (($part == 0) || empty($part)) {
2348            $part = 'TEXT';
2349        }
2350   
2351        if (iil_C_Select($conn, $mailbox)) {
2352                $reply_key = '* ' . $id;
2353       
2354                // format request
2355                $key     = 'ftch' . ($c++) . ' ';
2356                $request = $key . "FETCH $id (BODY.PEEK[$part])";
2357                // send request
2358                if (!iil_PutLine($fp, $request)) {
2359                    return false;
2360                }
2361       
2362                // receive reply line
2363                do {
2364                        $line = chop(iil_ReadLine($fp, 1000));
2365                        $a    = explode(' ', $line);
2366                } while ($a[2] != 'FETCH');
2367                $len = strlen($line);
2368   
2369                if ($line[$len-1] == ')') {
2370                        // one line response, get everything between first and last quotes
2371                        if (substr($line, -4, 3) == 'NIL') {
2372                                // NIL response
2373                                $result = '';
2374                        } else {
2375                                $from = strpos($line, '"') + 1;
2376                                $to   = strrpos($line, '"');
2377                                $len  = $to - $from;
2378                                $result = substr($line, $from, $len);
2379                        }
2380           
2381                        if ($mode == 2) {
2382                                echo $result;
2383                        } else if ($mode == 3) {
2384                                echo base64_decode($result);
2385                        }
2386                } else if ($line[$len-1] == '}') {
2387                        //multi-line request, find sizes of content and receive that many bytes
2388                        $from     = strpos($line, '{') + 1;
2389                        $to       = strrpos($line, '}');
2390                        $len      = $to - $from;
2391                        $sizeStr  = substr($line, $from, $len);
2392                        $bytes    = (int)$sizeStr;
2393                        $received = 0;
2394
2395                        while ($received < $bytes) {
2396                                $remaining = $bytes - $received;
2397                                $line      = iil_ReadLine($fp, 1024);
2398                                $len       = strlen($line);
2399               
2400                                if ($len > $remaining) {
2401                                        $line = substr($line, 0, $remaining);
2402                                }
2403                                $received += strlen($line);
2404                                if ($mode == 1) {
2405                                        $result .= rtrim($line, "\t\r\n\0\x0B") . "\n";
2406                                } else if ($mode == 2) {
2407                                        echo rtrim($line, "\t\r\n\0\x0B") . "\n";
2408                                } else if ($mode == 3) {
2409                                        echo base64_decode($line);
2410                                }
2411                        }
2412                }
2413                // read in anything up until 'til last line
2414                do {
2415                        $line = iil_ReadLine($fp, 1024);
2416                } while (!iil_StartsWith($line, $key));
2417       
2418                if ($result) {
2419                        $result = rtrim($result, "\t\r\n\0\x0B");
2420                        return $result; // substr($result, 0, strlen($result)-1);
2421                }
2422               
2423                return false;
2424        } else {
2425                echo 'Select failed.';
2426        }
2427   
2428        if ($mode==1) {
2429                return $result;
2430        }
2431        return $received;
2432}
2433
2434function iil_C_FetchPartBody(&$conn, $mailbox, $id, $part) {
2435        return iil_C_HandlePartBody($conn, $mailbox, $id, $part, 1);
2436}
2437
2438function iil_C_PrintPartBody(&$conn, $mailbox, $id, $part) {
2439        iil_C_HandlePartBody($conn, $mailbox, $id, $part, 2);
2440}
2441
2442function iil_C_PrintBase64Body(&$conn, $mailbox, $id, $part) {
2443        iil_C_HandlePartBody($conn, $mailbox, $id, $part, 3);
2444}
2445
2446function iil_C_CreateFolder(&$conn, $folder) {
2447        $fp = $conn->fp;
2448        if (iil_PutLine($fp, 'c CREATE "' . iil_Escape($folder) . '"')) {
2449                do {
2450                        $line=iil_ReadLine($fp, 300);
2451                } while ($line[0] != 'c');
2452        $conn->error = $line;
2453                return (iil_ParseResult($line) == 0);
2454        }
2455        return false;
2456}
2457
2458function iil_C_RenameFolder(&$conn, $from, $to) {
2459        $fp = $conn->fp;
2460        if (iil_PutLine($fp, 'r RENAME "' . iil_Escape($from) . '" "' . iil_Escape($to) . '"')) {
2461                do {
2462                        $line = iil_ReadLine($fp, 300);
2463                } while ($line[0] != 'r');
2464                return (iil_ParseResult($line) == 0);
2465        }
2466        return false;
2467}
2468
2469function iil_C_DeleteFolder(&$conn, $folder) {
2470        $fp = $conn->fp;
2471        if (iil_PutLine($fp, 'd DELETE "' . iil_Escape($folder). '"')) {
2472                do {
2473                        $line=iil_ReadLine($fp, 300);
2474                } while ($line[0] != 'd');
2475                return (iil_ParseResult($line) == 0);
2476        }
2477        $conn->error = "Couldn't send command\n";
2478        return false;
2479}
2480
2481function iil_C_Append(&$conn, $folder, &$message) {
2482        if (!$folder) {
2483                return false;
2484        }
2485        $fp = $conn->fp;
2486
2487        $message = str_replace("\r", '', $message);
2488        $message = str_replace("\n", "\r\n", $message);         
2489
2490        $len = strlen($message);
2491        if (!$len) {
2492                return false;
2493        }
2494
2495        $request = 'A APPEND "' . iil_Escape($folder) .'" (\\Seen) {' . $len . '}';
2496   
2497        if (iil_PutLine($fp, $request)) {
2498                $line=iil_ReadLine($fp, 100);           
2499                $sent = fwrite($fp, $message."\r\n");
2500                do {
2501                        $line=iil_ReadLine($fp, 1000);
2502                } while ($line[0] != 'A');
2503       
2504                $result = (iil_ParseResult($line) == 0);
2505                if (!$result) {
2506                    $conn->error .= $line . "\n";
2507                }
2508                return $result;
2509        }
2510
2511        $conn->error .= "Couldn't send command \"$request\"\n";
2512        return false;
2513}
2514
2515function iil_C_AppendFromFile(&$conn, $folder, $path) {
2516        if (!$folder) {
2517            return false;
2518        }
2519   
2520        //open message file
2521        $in_fp = false;                         
2522        if (file_exists(realpath($path))) {
2523                $in_fp = fopen($path, 'r');
2524        }
2525        if (!$in_fp) {
2526                $conn->error .= "Couldn't open $path for reading\n";
2527                return false;
2528        }
2529       
2530        $fp  = $conn->fp;
2531        $len = filesize($path);
2532        if (!$len) {
2533                return false;
2534        }
2535   
2536        //send APPEND command
2537        $request    = 'A APPEND "' . iil_Escape($folder) . '" (\\Seen) {' . $len . '}';
2538        $bytes_sent = 0;
2539        if (iil_PutLine($fp, $request)) {
2540                $line = iil_ReadLine($fp, 100);
2541                               
2542                //send file
2543                while (!feof($in_fp)) {
2544                        $buffer      = fgets($in_fp, 4096);
2545                        $bytes_sent += strlen($buffer);
2546                        iil_PutLine($fp, $buffer, false);
2547                }
2548                fclose($in_fp);
2549
2550                iil_PutLine($fp, '');
2551
2552                //read response
2553                do {
2554                        $line = iil_ReadLine($fp, 1000);
2555                } while ($line[0] != 'A');
2556                       
2557                $result = (iil_ParseResult($line) == 0);
2558                if (!$result) {
2559                    $conn->error .= $line . "\n";
2560                }
2561       
2562                return $result;
2563        }
2564       
2565        $conn->error .= "Couldn't send command \"$request\"\n";
2566        return false;
2567}
2568
2569function iil_C_FetchStructureString(&$conn, $folder, $id) {
2570        $fp     = $conn->fp;
2571        $result = false;
2572       
2573        if (iil_C_Select($conn, $folder)) {
2574                $key = 'F1247';
2575
2576                if (iil_PutLine($fp, "$key FETCH $id (BODYSTRUCTURE)")) {
2577                        do {
2578                                $line = iil_ReadLine($fp, 5000);
2579                                $line = iil_MultLine($fp, $line);
2580                                list(, $index, $cmd, $rest) = explode(' ', $line);
2581                                if ($cmd != 'FETCH' || $index == $id || preg_match("/^$key/", $line))
2582                                        $result .= $line;
2583                        } while (!preg_match("/^$key/", $line));
2584
2585                        $result = trim(substr($result, strpos($result, 'BODYSTRUCTURE')+13, -(strlen($result)-strrpos($result, $key)+1)));
2586                }
2587        }
2588        return $result;
2589}
2590
2591function iil_C_PrintSource(&$conn, $folder, $id, $part) {
2592        $header = iil_C_FetchPartHeader($conn, $folder, $id, $part);
2593        //echo str_replace("\r", '', $header);
2594        echo $header;
2595        echo iil_C_PrintPartBody($conn, $folder, $id, $part);
2596}
2597
2598function iil_C_GetQuota(&$conn) {
2599/*
2600 * GETQUOTAROOT "INBOX"
2601 * QUOTAROOT INBOX user/rchijiiwa1
2602 * QUOTA user/rchijiiwa1 (STORAGE 654 9765)
2603 b OK Completed
2604 */
2605        $fp         = $conn->fp;
2606        $result     = false;
2607        $quota_line = '';
2608       
2609        //get line containing quota info
2610        if (iil_PutLine($fp, 'QUOT1 GETQUOTAROOT "INBOX"')) {
2611                do {
2612                        $line=chop(iil_ReadLine($fp, 5000));
2613                        if (iil_StartsWith($line, '* QUOTA ')) {
2614                                $quota_line = $line;
2615                        }
2616                } while (!iil_StartsWith($line, 'QUOT1'));
2617        }
2618       
2619        //return false if not found, parse if found
2620        if (!empty($quota_line)) {
2621                $quota_line   = eregi_replace('[()]', '', $quota_line);
2622                $parts        = explode(' ', $quota_line);
2623                $storage_part = array_search('STORAGE', $parts);
2624                if ($storage_part > 0) {
2625                        $result['used']    = intval($parts[$storage_part+1]);
2626                        $result['total']   = intval($parts[$storage_part+2]);
2627                        $result['percent'] = min(100, round(($result['used']/max(1,$result['total']))*100));
2628                        $result['free']    = 100 - $result['percent'];
2629                }
2630        }
2631        return $result;
2632}
2633
2634function iil_C_ClearFolder(&$conn, $folder) {
2635        $num_in_trash = iil_C_CountMessages($conn, $folder);
2636        if ($num_in_trash > 0) {
2637                iil_C_Delete($conn, $folder, '1:' . $num_in_trash);
2638        }
2639        return (iil_C_Expunge($conn, $folder) >= 0);
2640}
2641
2642?>
Note: See TracBrowser for help on using the repository browser.