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

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