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

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