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

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