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

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