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

Last change on this file since 2563 was 2563, checked in by alec, 4 years ago
  • Fixed problem with double quote at the end of folder name (#1485884)
  • Property svn:eol-style set to native
  • Property svn:keywords set to Author Date Id Revision
File size: 67.7 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                - handling connection startup response
85                - added UID EXPUNGE support
86                - fixed problem with double quote at the end of folder name in LIST and LSUB
87
88********************************************************/
89
90/**
91 * @todo Possibly clean up more CS.
92 * @todo Try to replace most double-quotes with single-quotes.
93 * @todo Split this file into smaller files.
94 * @todo Refactor code.
95 * @todo Replace echo-debugging (make it adhere to config setting and log)
96 */
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=1024) {
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        stream_set_timeout($conn->fp, 10);
621        $line = stream_get_line($conn->fp, 8192, "\r\n");
622
623        // Connected to wrong port or connection error?
624        if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) {
625                if ($line)
626                        $iil_error = "Wrong startup greeting ($host:$ICL_PORT): $line";
627                else
628                        $iil_error = "Empty startup greeting ($host:$ICL_PORT)";
629                $iil_errornum = -2;
630                return false;
631        }
632       
633        // RFC3501 [7.1] optional CAPABILITY response
634        if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
635                $conn->capability = explode(' ', strtoupper($matches[1]));
636        }
637
638        $conn->message .= $line;
639
640        // TLS connection
641        if ($ICL_SSL == 'tls' && iil_C_GetCapability($conn, 'STARTTLS')) {
642                if (version_compare(PHP_VERSION, '5.1.0', '>=')) {
643                        iil_PutLine($conn->fp, 'stls000 STARTTLS');
644
645                        $line = iil_ReadLine($conn->fp, 4096);
646                        if (!iil_StartsWith($line, 'stls000 OK')) {
647                                $iil_error = "Server responded to STARTTLS with: $line";
648                                $iil_errornum = -2;
649                                return false;
650                        }
651
652                        if (!stream_socket_enable_crypto($conn->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
653                                $iil_error = "Unable to negotiate TLS";
654                                $iil_errornum = -2;
655                                return false;
656                        }
657                       
658                        // Now we're authenticated, capabilities need to be reread
659                        iil_C_ClearCapability($conn);
660                }
661        }
662
663        if (strcasecmp($auth_method, "check") == 0) {
664                //check for supported auth methods
665                if (iil_C_GetCapability($conn, 'AUTH=CRAM-MD5') || iil_C_GetCapability($conn, 'AUTH=CRAM_MD5')) {
666                        $auth_method = 'auth';
667                }
668                else {
669                        //default to plain text auth
670                        $auth_method = 'plain';
671                }
672        }
673
674        if (strcasecmp($auth_method, 'auth') == 0) {
675                $conn->message .= "Trying CRAM-MD5\n";
676
677                //do CRAM-MD5 authentication
678                iil_PutLine($conn->fp, "a000 AUTHENTICATE CRAM-MD5");
679                $line = trim(iil_ReadLine($conn->fp, 1024));
680
681                $conn->message .= "$line\n";
682
683                if ($line[0] == '+') {
684                        $conn->message .= 'Got challenge: ' . htmlspecialchars($line) . "\n";
685
686                        //got a challenge string, try CRAM-5
687                        $result = iil_C_Authenticate($conn, $user, $password, substr($line,2));
688                       
689                        // stop if server sent BYE response
690                        if($result == -3) {
691                                $iil_error = $conn->error;
692                                $iil_errornum = $conn->errorNum;
693                                return false;
694                        }
695                        $conn->message .= "Tried CRAM-MD5: $result \n";
696                } else {
697                        $conn->message .='No challenge ('.htmlspecialchars($line)."), try plain\n";
698                        $auth = 'plain';
699                }
700        }
701               
702        if ((!$result)||(strcasecmp($auth, "plain") == 0)) {
703                //do plain text auth
704                $result = iil_C_Login($conn, $user, $password);
705                $conn->message .= "Tried PLAIN: $result \n";
706        }
707               
708        $conn->message .= $auth;
709                       
710        if (!is_int($result)) {
711                iil_C_Namespace($conn);
712                return $conn;
713        } else {
714                $iil_error = $conn->error;
715                $iil_errornum = $conn->errorNum;
716                return false;
717        }
718}
719
720function iil_Close(&$conn) {
721        if (iil_PutLine($conn->fp, "I LOGOUT")) {
722                fgets($conn->fp, 1024);
723                fclose($conn->fp);
724                $conn->fp = false;
725        }
726}
727
728function iil_ExplodeQuotedString($delimiter, $string) {
729        $quotes = explode('"', $string);
730        while ( list($key, $val) = each($quotes)) {
731                if (($key % 2) == 1) {
732                        $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
733                }
734        }
735        $string = implode('"', $quotes);
736       
737        $result = explode($delimiter, $string);
738        while ( list($key, $val) = each($result) ) {
739                $result[$key] = str_replace('_!@!_', $delimiter, $result[$key]);
740        }
741   
742        return $result;
743}
744
745function iil_CheckForRecent($host, $user, $password, $mailbox) {
746        if (empty($mailbox)) {
747                $mailbox = 'INBOX';
748        }
749   
750        $conn = iil_Connect($host, $user, $password, 'plain');
751        $fp   = $conn->fp;
752        if ($fp) {
753                iil_PutLine($fp, "a002 EXAMINE \"".iil_Escape($mailbox)."\"");
754                do {
755                        $line=chop(iil_ReadLine($fp, 300));
756                        $a=explode(' ', $line);
757                        if (($a[0] == '*') && (strcasecmp($a[2], 'RECENT') == 0)) {
758                            $result = (int) $a[1];
759                        }
760                } while (!iil_StartsWith($a[0], 'a002', true));
761
762                iil_PutLine($fp, "a003 LOGOUT");
763                fclose($fp);
764        } else {
765            $result = -2;
766        }
767   
768        return $result;
769}
770
771function iil_C_Select(&$conn, $mailbox) {
772
773        if (empty($mailbox)) {
774                return false;
775        }
776        if (strcmp($conn->selected, $mailbox) == 0) {
777                return true;
778        }
779   
780        if (iil_PutLine($conn->fp, "sel1 SELECT \"".iil_Escape($mailbox).'"')) {
781                do {
782                        $line = chop(iil_ReadLine($conn->fp, 300));
783                        $a = explode(' ', $line);
784                        if (count($a) == 3) {
785                                if (strcasecmp($a[2], 'EXISTS') == 0) {
786                                        $conn->exists = (int) $a[1];
787                                }
788                                if (strcasecmp($a[2], 'RECENT') == 0) {
789                                        $conn->recent = (int) $a[1];
790                                }
791                        }
792                        else if (preg_match('/\[?PERMANENTFLAGS\s+\(([^\)]+)\)\]/U', $line, $match)) {
793                                $conn->permanentflags = explode(' ', $match[1]);
794                        }
795                } while (!iil_StartsWith($line, 'sel1', true));
796
797                $a = explode(' ', $line);
798
799                if (strcasecmp($a[1], 'OK') == 0) {
800                        $conn->selected = $mailbox;
801                        return true;
802                }
803        }
804        return false;
805}
806
807function iil_C_CheckForRecent(&$conn, $mailbox) {
808        if (empty($mailbox)) {
809                $mailbox = 'INBOX';
810        }
811   
812        iil_C_Select($conn, $mailbox);
813        if ($conn->selected == $mailbox) {
814                return $conn->recent;
815        }
816        return false;
817}
818
819function iil_C_CountMessages(&$conn, $mailbox, $refresh = false) {
820        if ($refresh) {
821                $conn->selected = '';
822        }
823       
824        iil_C_Select($conn, $mailbox);
825        if ($conn->selected == $mailbox) {
826                return $conn->exists;
827        }
828        return false;
829}
830
831function iil_SplitHeaderLine($string) {
832        $pos=strpos($string, ':');
833        if ($pos>0) {
834                $res[0] = substr($string, 0, $pos);
835                $res[1] = trim(substr($string, $pos+1));
836                return $res;
837        }
838        return $string;
839}
840
841function iil_StrToTime($str) {
842        $IMAP_MONTHS    = $GLOBALS['IMAP_MONTHS'];
843        $IMAP_SERVER_TZ = $GLOBALS['IMAP_SERVER_TZ'];
844               
845        if ($str) {
846            $time1 = strtotime($str);
847        }
848        if ($time1 && $time1 != -1) {
849            return $time1-$IMAP_SERVER_TZ;
850        }
851        //echo '<!--'.$str.'//-->';
852       
853        //replace double spaces with single space
854        $str = trim($str);
855        $str = str_replace('  ', ' ', $str);
856       
857        //strip off day of week
858        $pos = strpos($str, ' ');
859        if (!is_numeric(substr($str, 0, $pos))) {
860            $str = substr($str, $pos+1);
861        }
862        //explode, take good parts
863        $a = explode(' ', $str);
864
865        $month_str = $a[1];
866        $month     = $IMAP_MONTHS[$month_str];
867        $day       = (int)$a[0];
868        $year      = (int)$a[2];
869        $time      = $a[3];
870        $tz_str    = $a[4];
871        $tz        = substr($tz_str, 0, 3);
872        $ta        = explode(':', $time);
873        $hour      = (int)$ta[0]-(int)$tz;
874        $minute    = (int)$ta[1];
875        $second    = (int)$ta[2];
876       
877        //make UNIX timestamp
878        $time2 = mktime($hour, $minute, $second, $month, $day, $year);
879        //echo '<!--'.$time1.' '.$time2.' //-->'."\n";
880        return $time2;
881}
882
883function iil_C_Sort(&$conn, $mailbox, $field, $add='', $is_uid=FALSE,
884    $encoding = 'US-ASCII') {
885
886        $field = strtoupper($field);
887        if ($field == 'INTERNALDATE') {
888            $field = 'ARRIVAL';
889        }
890       
891        $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1,
892        'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1);
893       
894        if (!$fields[$field]) {
895            return false;
896        }
897
898        /*  Do "SELECT" command */
899        if (!iil_C_Select($conn, $mailbox)) {
900            return false;
901        }
902   
903        $is_uid = $is_uid ? 'UID ' : '';
904       
905        if (!empty($add)) {
906            $add = " $add";
907        }
908
909        $command  = 's ' . $is_uid . 'SORT (' . $field . ') ';
910        $command .= $encoding . ' ALL' . $add;
911        $line     = $data = '';
912       
913        if (!iil_PutLineC($conn->fp, $command)) {
914            return false;
915        }
916        do {
917                $line = chop(iil_ReadLine($conn->fp, 1024));
918                if (iil_StartsWith($line, '* SORT')) {
919                        $data .= ($data ? ' ' : '') . substr($line, 7);
920                } else if (preg_match('/^[0-9 ]+$/', $line)) {
921                        $data .= $line;
922                }
923        } while (!iil_StartsWith($line, 's ', true));
924       
925        $result_code = iil_ParseResult($line);
926       
927        if ($result_code != 0) {
928                $conn->error = 'iil_C_Sort: ' . $line . "\n";
929                return false;
930        }
931       
932        $out = explode(' ',$data);
933        return $out;
934}
935
936function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field,
937    $normalize=true) {
938        global $IMAP_USE_INTERNAL_DATE;
939       
940        $c=0;
941        $result=array();
942        $fp = $conn->fp;
943               
944        if (empty($index_field)) {
945            $index_field = 'DATE';
946        }
947        $index_field = strtoupper($index_field);
948       
949        list($from_idx, $to_idx) = explode(':', $message_set);
950        if (empty($message_set) || (isset($to_idx)
951            && (int)$from_idx > (int)$to_idx)) {
952                return false;
953        }
954       
955        //$fields_a['DATE'] = ($IMAP_USE_INTERNAL_DATE?6:1);
956        $fields_a['DATE']         = 1;
957        $fields_a['INTERNALDATE'] = 6;
958        $fields_a['FROM']         = 1;
959        $fields_a['REPLY-TO']     = 1;
960        $fields_a['SENDER']       = 1;
961        $fields_a['TO']           = 1;
962        $fields_a['SUBJECT']      = 1;
963        $fields_a['UID']          = 2;
964        $fields_a['SIZE']         = 2;
965        $fields_a['SEEN']         = 3;
966        $fields_a['RECENT']       = 4;
967        $fields_a['DELETED']      = 5;
968       
969        $mode=$fields_a[$index_field];
970        if (!($mode > 0)) {
971            return false;
972        }
973   
974        /*  Do "SELECT" command */
975        if (!iil_C_Select($conn, $mailbox)) {
976            return false;
977        }
978   
979        /* FETCH date,from,subject headers */
980        if ($mode == 1) {
981                $key     = 'fhi' . ($c++);
982                $request = $key . " FETCH $message_set (BODY.PEEK[HEADER.FIELDS ($index_field)])";
983                if (!iil_PutLine($fp, $request)) {
984                    return false;
985                }
986                do {
987                       
988                        $line=chop(iil_ReadLine($fp, 200));
989                        $a=explode(' ', $line);
990                        if (($line[0] == '*') && ($a[2] == 'FETCH')
991                            && ($line[strlen($line)-1] != ')')) {
992                                $id=$a[1];
993
994                                $str=$line=chop(iil_ReadLine($fp, 300));
995
996                                while ($line[0] != ')') {                                       //caution, this line works only in this particular case
997                                        $line=chop(iil_ReadLine($fp, 300));
998                                        if ($line[0] != ')') {
999                                                if (ord($line[0]) <= 32) {                      //continuation from previous header line
1000                                                        $str.= ' ' . trim($line);
1001                                                }
1002                                                if ((ord($line[0]) > 32) || (strlen($line[0]) == 0)) {
1003                                                        list($field, $string) = iil_SplitHeaderLine($str);
1004                                                        if (strcasecmp($field, 'date') == 0) {
1005                                                                $result[$id] = iil_StrToTime($string);
1006                                                        } else {
1007                                                                $result[$id] = str_replace('"', '', $string);
1008                                                                if ($normalize) {
1009                                                                    $result[$id] = strtoupper($result[$id]);
1010                                                                }
1011                                                        }
1012                                                        $str=$line;
1013                                                }
1014                                        }
1015                                }
1016                        }
1017                        /*
1018                        $end_pos = strlen($line)-1;
1019                        if (($line[0]=="*") && ($a[2]=="FETCH") && ($line[$end_pos]=="}")) {
1020                                $id = $a[1];
1021                                $pos = strrpos($line, "{")+1;
1022                                $bytes = (int)substr($line, $pos, $end_pos-$pos);
1023                                $received = 0;
1024                                do {
1025                                        $line      = iil_ReadLine($fp, 0);
1026                                        $received += strlen($line);
1027                                        $line      = chop($line);
1028                                       
1029                                        if ($received>$bytes) {
1030                                                break;
1031                                        } else if (!$line) {
1032                                                continue;
1033                                        }
1034
1035                                        list($field, $string) = explode(': ', $line);
1036                                       
1037                                        if (strcasecmp($field, 'date') == 0) {
1038                                                $result[$id] = iil_StrToTime($string);
1039                                        } else if ($index_field != 'DATE') {
1040                                                $result[$id]=strtoupper(str_replace('"', '', $string));
1041                                        }
1042                                } while ($line[0] != ')');
1043                        } else {
1044                                //one line response, not expected so ignore                             
1045                        }
1046                        */
1047                } while (!iil_StartsWith($line, $key, true));
1048
1049        }else if ($mode == 6) {
1050
1051                $key     = 'fhi' . ($c++);
1052                $request = $key . " FETCH $message_set (INTERNALDATE)";
1053                if (!iil_PutLine($fp, $request)) {
1054                    return false;
1055                }
1056                do {
1057                        $line=chop(iil_ReadLine($fp, 200));
1058                        if ($line[0] == '*') {
1059                                /*
1060                                 * original:
1061                                 * "* 10 FETCH (INTERNALDATE "31-Jul-2002 09:18:02 -0500")"
1062                                 */
1063                                $paren_pos = strpos($line, '(');
1064                                $foo       = substr($line, 0, $paren_pos);
1065                                $a         = explode(' ', $foo);
1066                                $id        = $a[1];
1067                               
1068                                $open_pos  = strpos($line, '"') + 1;
1069                                $close_pos = strrpos($line, '"');
1070                                if ($open_pos && $close_pos) {
1071                                        $len         = $close_pos - $open_pos;
1072                                        $time_str    = substr($line, $open_pos, $len);
1073                                        $result[$id] = strtotime($time_str);
1074                                }
1075                        } else {
1076                                $a = explode(' ', $line);
1077                        }
1078                } while (!iil_StartsWith($a[0], $key, true));
1079        } else {
1080                if ($mode >= 3) {
1081                    $field_name = 'FLAGS';
1082                } else if ($index_field == 'SIZE') {
1083                    $field_name = 'RFC822.SIZE';
1084                } else {
1085                    $field_name = $index_field;
1086                }
1087       
1088                /*                      FETCH uid, size, flags          */
1089                $key     = 'fhi' .($c++);
1090                $request = $key . " FETCH $message_set ($field_name)";
1091
1092                if (!iil_PutLine($fp, $request)) {
1093                    return false;
1094                }
1095                do {
1096                        $line=chop(iil_ReadLine($fp, 200));
1097                        $a = explode(' ', $line);
1098                        if (($line[0] == '*') && ($a[2] == 'FETCH')) {
1099                                $line = str_replace('(', '', $line);
1100                                $line = str_replace(')', '', $line);
1101                                $a    = explode(' ', $line);
1102                               
1103                                $id = $a[1];
1104
1105                                if (isset($result[$id])) {
1106                                    continue; //if we already got the data, skip forward
1107                                }
1108                                if ($a[3]!=$field_name) {
1109                                        continue;  //make sure it's returning what we requested
1110                                }
1111               
1112                                /*  Caution, bad assumptions, next several lines */
1113                                if ($mode == 2) {
1114                                    $result[$id] = $a[4];
1115                                } else {
1116                                        $haystack    = strtoupper($line);
1117                                        $result[$id] = (strpos($haystack, $index_field) > 0 ? "F" : "N");
1118                                }
1119                        }
1120                } while (!iil_StartsWith($line, $key, true));
1121        }
1122
1123        //check number of elements...
1124        list($start_mid, $end_mid) = explode(':', $message_set);
1125        if (is_numeric($start_mid) && is_numeric($end_mid)) {
1126                //count how many we should have
1127                $should_have = $end_mid - $start_mid +1;
1128               
1129                //if we have less, try and fill in the "gaps"
1130                if (count($result) < $should_have) {
1131                        for ($i=$start_mid; $i<=$end_mid; $i++) {
1132                                if (!isset($result[$i])) {
1133                                        $result[$i] = '';
1134                                }
1135                        }
1136                }
1137        }
1138        return $result;
1139}
1140
1141function iil_CompressMessageSet($message_set) {
1142        //given a comma delimited list of independent mid's,
1143        //compresses by grouping sequences together
1144       
1145        //if less than 255 bytes long, let's not bother
1146        if (strlen($message_set)<255) {
1147            return $message_set;
1148        }
1149   
1150        //see if it's already been compress
1151        if (strpos($message_set, ':') !== false) {
1152            return $message_set;
1153        }
1154   
1155        //separate, then sort
1156        $ids = explode(',', $message_set);
1157        sort($ids);
1158       
1159        $result = array();
1160        $start  = $prev = $ids[0];
1161
1162        foreach ($ids as $id) {
1163                $incr = $id - $prev;
1164                if ($incr > 1) {                        //found a gap
1165                        if ($start == $prev) {
1166                            $result[] = $prev;  //push single id
1167                        } else {
1168                            $result[] = $start . ':' . $prev;   //push sequence as start_id:end_id
1169                        }
1170                        $start = $id;                   //start of new sequence
1171                }
1172                $prev = $id;
1173        }
1174
1175        //handle the last sequence/id
1176        if ($start==$prev) {
1177            $result[] = $prev;
1178        } else {
1179            $result[] = $start.':'.$prev;
1180        }
1181   
1182        //return as comma separated string
1183        return implode(',', $result);
1184}
1185
1186function iil_C_UIDsToMIDs(&$conn, $mailbox, $uids) {
1187        if (!is_array($uids) || count($uids) == 0) {
1188            return array();
1189        }
1190        return iil_C_Search($conn, $mailbox, 'UID ' . implode(',', $uids));
1191}
1192
1193function iil_C_UIDToMID(&$conn, $mailbox, $uid) {
1194        $result = iil_C_UIDsToMIDs($conn, $mailbox, array($uid));
1195        if (count($result) == 1) {
1196            return $result[0];
1197        }
1198        return false;
1199}
1200
1201function iil_C_FetchUIDs(&$conn,$mailbox) {
1202        global $clock;
1203       
1204        $num = iil_C_CountMessages($conn, $mailbox);
1205        if ($num == 0) {
1206            return array();
1207        }
1208        $message_set = '1' . ($num>1?':' . $num:'');
1209       
1210        return iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
1211}
1212
1213function iil_SortThreadHeaders($headers, $index_a, $uids) {
1214        asort($index_a);
1215        $result = array();
1216        foreach ($index_a as $mid=>$foobar) {
1217                $uid = $uids[$mid];
1218                $result[$uid] = $headers[$uid];
1219        }
1220        return $result;
1221}
1222
1223function iil_C_FetchThreadHeaders(&$conn, $mailbox, $message_set) {
1224        global $clock;
1225        global $index_a;
1226       
1227        list($from_idx, $to_idx) = explode(':', $message_set);
1228        if (empty($message_set) || (isset($to_idx)
1229        && (int)$from_idx > (int)$to_idx)) {
1230                return false;
1231        }
1232
1233        $result = array();
1234        $uids   = iil_C_FetchUIDs($conn, $mailbox);
1235        $debug  = false;
1236       
1237        $message_set = iil_CompressMessageSet($message_set);
1238   
1239        /* if we're missing any, get them */
1240        if ($message_set) {
1241                /* FETCH date,from,subject headers */
1242                $key        = 'fh';
1243                $fp         = $conn->fp;
1244                $request    = $key . " FETCH $message_set ";
1245                $request   .= "(BODY.PEEK[HEADER.FIELDS (SUBJECT MESSAGE-ID IN-REPLY-TO)])";
1246                $mid_to_id  = array();
1247                if (!iil_PutLine($fp, $request)) {
1248                    return false;
1249                }
1250                do {
1251                        $line = chop(iil_ReadLine($fp, 1024));
1252                        if ($debug) {
1253                            echo $line . "\n";
1254                        }
1255                        if (preg_match('/\{[0-9]+\}$/', $line)) {
1256                                $a       = explode(' ', $line);
1257                                $new = array();
1258
1259                                $new_thhd = new iilThreadHeader;
1260                                $new_thhd->id = $a[1];
1261                                do {
1262                                        $line = chop(iil_ReadLine($fp, 1024), "\r\n");
1263                                        if (iil_StartsWithI($line, 'Message-ID:')
1264                                                || (iil_StartsWithI($line,'In-Reply-To:'))
1265                                                || (iil_StartsWithI($line,'SUBJECT:'))) {
1266
1267                                                $pos        = strpos($line, ':');
1268                                                $field_name = substr($line, 0, $pos);
1269                                                $field_val  = substr($line, $pos+1);
1270
1271                                                $new[strtoupper($field_name)] = trim($field_val);
1272
1273                                        } else if (preg_match('/^\s+/', $line)) {
1274                                                $new[strtoupper($field_name)] .= trim($line);
1275                                        }
1276                                } while ($line[0] != ')');
1277               
1278                                $new_thhd->sbj = $new['SUBJECT'];
1279                                $new_thhd->mid = substr($new['MESSAGE-ID'], 1, -1);
1280                                $new_thhd->irt = substr($new['IN-REPLY-TO'], 1, -1);
1281                               
1282                                $result[$uids[$new_thhd->id]] = $new_thhd;
1283                        }
1284                } while (!iil_StartsWith($line, 'fh'));
1285        }
1286       
1287        /* sort headers */
1288        if (is_array($index_a)) {
1289                $result = iil_SortThreadHeaders($result, $index_a, $uids);     
1290        }
1291       
1292        //echo 'iil_FetchThreadHeaders:'."\n";
1293        //print_r($result);
1294       
1295        return $result;
1296}
1297
1298function iil_C_BuildThreads2(&$conn, $mailbox, $message_set, &$clock) {
1299        global $index_a;
1300
1301        list($from_idx, $to_idx) = explode(':', $message_set);
1302        if (empty($message_set) || (isset($to_idx)
1303                && (int)$from_idx > (int)$to_idx)) {
1304                return false;
1305        }
1306   
1307        $result    = array();
1308        $roots     = array();
1309        $root_mids = array();
1310        $sub_mids  = array();
1311        $strays    = array();
1312        $messages  = array();
1313        $fp        = $conn->fp;
1314        $debug     = false;
1315       
1316        $sbj_filter_pat = '/[a-z]{2,3}(\[[0-9]*\])?:(\s*)/i';
1317       
1318        /*  Do "SELECT" command */
1319        if (!iil_C_Select($conn, $mailbox)) {
1320            return false;
1321        }
1322   
1323        /* FETCH date,from,subject headers */
1324        $mid_to_id = array();
1325        $messages  = array();
1326        $headers   = iil_C_FetchThreadHeaders($conn, $mailbox, $message_set);
1327        if ($clock) {
1328            $clock->register('fetched headers');
1329        }
1330   
1331        if ($debug) {
1332            print_r($headers);
1333        }
1334   
1335        /* go through header records */
1336        foreach ($headers as $header) {
1337                //$id = $header['i'];
1338                //$new = array('id'=>$id, 'MESSAGE-ID'=>$header['m'],
1339                //                      'IN-REPLY-TO'=>$header['r'], 'SUBJECT'=>$header['s']);
1340                $id  = $header->id;
1341                $new = array('id' => $id, 'MESSAGE-ID' => $header->mid,
1342                        'IN-REPLY-TO' => $header->irt, 'SUBJECT' => $header->sbj);
1343
1344                /* add to message-id -> mid lookup table */
1345                $mid_to_id[$new['MESSAGE-ID']] = $id;
1346               
1347                /* if no subject, use message-id */
1348                if (empty($new['SUBJECT'])) {
1349                    $new['SUBJECT'] = $new['MESSAGE-ID'];
1350                }
1351       
1352                /* if subject contains 'RE:' or has in-reply-to header, it's a reply */
1353                $sbj_pre = '';
1354                $has_re = false;
1355                if (preg_match($sbj_filter_pat, $new['SUBJECT'])) {
1356                    $has_re = true;
1357                }
1358                if ($has_re || $new['IN-REPLY-TO']) {
1359                    $sbj_pre = 'RE:';
1360                }
1361       
1362                /* strip out 're:', 'fw:' etc */
1363                if ($has_re) {
1364                    $sbj = preg_replace($sbj_filter_pat, '', $new['SUBJECT']);
1365                } else {
1366                    $sbj = $new['SUBJECT'];
1367                }
1368                $new['SUBJECT'] = $sbj_pre.$sbj;
1369               
1370               
1371                /* if subject not a known thread-root, add to list */
1372                if ($debug) {
1373                    echo $id . ' ' . $new['SUBJECT'] . "\t" . $new['MESSAGE-ID'] . "\n";
1374                }
1375                $root_id = $roots[$sbj];
1376               
1377                if ($root_id && ($has_re || !$root_in_root[$root_id])) {
1378                        if ($debug) {
1379                            echo "\tfound root: $root_id\n";
1380                        }
1381                        $sub_mids[$new['MESSAGE-ID']] = $root_id;
1382                        $result[$root_id][]           = $id;
1383                } else if (!isset($roots[$sbj]) || (!$has_re && $root_in_root[$root_id])) {
1384                        /* try to use In-Reply-To header to find root
1385                                unless subject contains 'Re:' */
1386                        if ($has_re&&$new['IN-REPLY-TO']) {
1387                                if ($debug) {
1388                                    echo "\tlooking: ".$new['IN-REPLY-TO']."\n";
1389                                }
1390                                //reply to known message?
1391                                $temp = $sub_mids[$new['IN-REPLY-TO']];
1392                               
1393                                if ($temp) {
1394                                        //found it, root:=parent's root
1395                                        if ($debug) {
1396                                            echo "\tfound parent: ".$new['SUBJECT']."\n";
1397                                        }
1398                                        $result[$temp][]              = $id;
1399                                        $sub_mids[$new['MESSAGE-ID']] = $temp;
1400                                        $sbj                          = '';
1401                                } else {
1402                                        //if we can't find referenced parent, it's a "stray"
1403                                        $strays[$id] = $new['IN-REPLY-TO'];
1404                                }
1405                        }
1406                       
1407                        //add subject as root
1408                        if ($sbj) {
1409                                if ($debug) {
1410                                    echo "\t added to root\n";
1411                                }
1412                                $roots[$sbj]                  = $id;
1413                                $root_in_root[$id]            = !$has_re;
1414                                $sub_mids[$new['MESSAGE-ID']] = $id;
1415                                $result[$id]                  = array($id);
1416                        }
1417                        if ($debug) {
1418                            echo $new['MESSAGE-ID'] . "\t" . $sbj . "\n";
1419                        }
1420                }
1421        }
1422       
1423        //now that we've gone through all the messages,
1424        //go back and try and link up the stray threads
1425        if (count($strays) > 0) {
1426                foreach ($strays as $id=>$irt) {
1427                        $root_id = $sub_mids[$irt];
1428                        if (!$root_id || $root_id==$id) {
1429                            continue;
1430                        }
1431                        $result[$root_id] = array_merge($result[$root_id],$result[$id]);
1432                        unset($result[$id]);
1433                }
1434        }
1435       
1436        if ($clock) {
1437            $clock->register('data prepped');
1438        }
1439   
1440        if ($debug) {
1441            print_r($roots);
1442        }
1443
1444        return $result;
1445}
1446
1447function iil_SortThreads(&$tree, $index, $sort_order = 'ASC') {
1448        if (!is_array($tree) || !is_array($index)) {
1449            return false;
1450        }
1451   
1452        //create an id to position lookup table
1453        $i = 0;
1454        foreach ($index as $id=>$val) {
1455                $i++;
1456                $index[$id] = $i;
1457        }
1458        $max = $i+1;
1459       
1460        //for each tree, set array key to position
1461        $itree = array();
1462        foreach ($tree as $id=>$node) {
1463                if (count($tree[$id])<=1) {
1464                        //for "threads" with only one message, key is position of that message
1465                        $n         = $index[$id];
1466                        $itree[$n] = array($n=>$id);
1467                } else {
1468                        //for "threads" with multiple messages,
1469                        $min   = $max;
1470                        $new_a = array();
1471                        foreach ($tree[$id] as $mid) {
1472                                $new_a[$index[$mid]] = $mid;            //create new sub-array mapping position to id
1473                                $pos                 = $index[$mid];
1474                                if ($pos&&$pos<$min) {
1475                                    $min = $index[$mid];        //find smallest position
1476                                }
1477                        }
1478                        $n = $min;      //smallest position of child is thread position
1479                       
1480                        //assign smallest position to root level key
1481                        //set children array to one created above
1482                        ksort($new_a);
1483                        $itree[$n] = $new_a;
1484                }
1485        }
1486       
1487        //sort by key, this basically sorts all threads
1488        ksort($itree);
1489        $i   = 0;
1490        $out = array();
1491        foreach ($itree as $k=>$node) {
1492                $out[$i] = $itree[$k];
1493                $i++;
1494        }
1495       
1496        return $out;
1497}
1498
1499function iil_IndexThreads(&$tree) {
1500        /* creates array mapping mid to thread id */
1501       
1502        if (!is_array($tree)) {
1503            return false;
1504        }
1505   
1506        $t_index = array();
1507        foreach ($tree as $pos=>$kids) {
1508                foreach ($kids as $kid) $t_index[$kid] = $pos;
1509        }
1510       
1511        return $t_index;
1512}
1513
1514function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='')
1515{
1516        global $IMAP_USE_INTERNAL_DATE;
1517       
1518        $result = array();
1519        $fp     = $conn->fp;
1520       
1521        list($from_idx, $to_idx) = explode(':', $message_set);
1522        if (empty($message_set) || (isset($to_idx)
1523                && (int)$from_idx > (int)$to_idx)) {
1524                return false;
1525        }
1526               
1527        /*  Do "SELECT" command */
1528        if (!iil_C_Select($conn, $mailbox)) {
1529                $conn->error = "Couldn't select $mailbox";
1530                return false;
1531        }
1532               
1533        if ($add)
1534                $add = ' '.strtoupper(trim($add));
1535
1536        /* FETCH uid, size, flags and headers */
1537        $key      = 'FH12';
1538        $request  = $key . ($uidfetch ? ' UID' : '') . " FETCH $message_set ";
1539        $request .= "(UID RFC822.SIZE FLAGS INTERNALDATE ";
1540        if ($bodystr)
1541                $request .= "BODYSTRUCTURE ";
1542        $request .= "BODY.PEEK[HEADER.FIELDS ";
1543        $request .= "(DATE FROM TO SUBJECT REPLY-TO IN-REPLY-TO CC BCC ";
1544        $request .= "CONTENT-TRANSFER-ENCODING CONTENT-TYPE MESSAGE-ID ";
1545        $request .= "REFERENCES DISPOSITION-NOTIFICATION-TO X-PRIORITY".$add.")])";
1546
1547        if (!iil_PutLine($fp, $request)) {
1548                return false;
1549        }
1550        do {
1551                $line = iil_ReadLine($fp, 1024);
1552                $line = iil_MultLine($fp, $line);
1553
1554                $a    = explode(' ', $line);
1555                if (($line[0] == '*') && ($a[2] == 'FETCH')) {
1556                        $id = $a[1];
1557           
1558                        $result[$id]            = new iilBasicHeader;
1559                        $result[$id]->id        = $id;
1560                        $result[$id]->subject   = '';
1561                        $result[$id]->messageID = 'mid:' . $id;
1562
1563                        $lines = array();
1564                        $ln = 0;
1565                        /*
1566                            Sample reply line:
1567                            * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen)
1568                            INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...)
1569                            BODY[HEADER.FIELDS ...
1570                        */
1571
1572                        if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY/s', $line, $matches)) {
1573                                $str = $matches[1];
1574
1575                                // swap parents with quotes, then explode
1576                                $str = preg_replace('/[()]/', '"', $str);
1577                                $a = iil_ExplodeQuotedString(' ', $str);
1578
1579                                // did we get the right number of replies?
1580                                $parts_count = count($a);
1581                                if ($parts_count>=6) {
1582                                        for ($i=0; $i<$parts_count; $i=$i+2) {
1583                                                if (strcasecmp($a[$i],'UID') == 0)
1584                                                        $result[$id]->uid = $a[$i+1];
1585                                                else if (strcasecmp($a[$i],'RFC822.SIZE') == 0)
1586                                                        $result[$id]->size = $a[$i+1];
1587                                                else if (strcasecmp($a[$i],'INTERNALDATE') == 0)
1588                                                        $time_str = $a[$i+1];
1589                                                else if (strcasecmp($a[$i],'FLAGS') == 0)
1590                                                        $flags_str = $a[$i+1];
1591                                        }
1592
1593                                        $time_str = str_replace('"', '', $time_str);
1594                                       
1595                                        // if time is gmt...
1596                                        $time_str = str_replace('GMT','+0000',$time_str);
1597                                       
1598                                        //get timezone
1599                                        $time_str      = substr($time_str, 0, -1);
1600                                        $time_zone_str = substr($time_str, -5); // extract timezone
1601                                        $time_str      = substr($time_str, 0, -5); // remove timezone
1602                                        $time_zone     = (float)substr($time_zone_str, 1, 2); // get first two digits
1603                       
1604                                        if ($time_zone_str[3] != '0') {
1605                                                 $time_zone += 0.5;  //handle half hour offset
1606                                        }
1607                                        if ($time_zone_str[0] == '-') {
1608                                                $time_zone = $time_zone * -1.0; //minus?
1609                                        }
1610                                       
1611                                        //calculate timestamp
1612                                        $timestamp     = strtotime($time_str); //return's server's time
1613                                        $timestamp    -= $time_zone * 3600; //compensate for tz, get GMT
1614
1615                                        $result[$id]->internaldate = $time_str;
1616                                        $result[$id]->timestamp = $timestamp;
1617                                        $result[$id]->date = $time_str;
1618                                }
1619
1620                                // BODYSTRUCTURE
1621                                if($bodystr) {
1622                                        while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/s', $line, $m)) {
1623                                                $line2 = iil_ReadLine($fp, 1024);
1624                                                $line .= iil_MultLine($fp, $line2);
1625                                        }
1626                                        $result[$id]->body_structure = $m[1];
1627                                }
1628
1629                                // the rest of the result
1630                                preg_match('/ BODY\[HEADER.FIELDS \(.*\)\]\s*(.*)/s', $line, $m);
1631                                $reslines = explode("\n", trim($m[1], '"'));
1632                                // re-parse (see below)
1633                                foreach ($reslines as $line) {
1634                                        if (ord($line[0])<=32) {
1635                                                $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($line);
1636                                        } else {
1637                                                $lines[++$ln] = trim($line);
1638                                        }
1639                                }
1640                        }
1641
1642                        /*
1643                                Start parsing headers.  The problem is, some header "lines" take up multiple lines.
1644                                So, we'll read ahead, and if the one we're reading now is a valid header, we'll
1645                                process the previous line.  Otherwise, we'll keep adding the strings until we come
1646                                to the next valid header line.
1647                        */
1648       
1649                        do {
1650                                $line = chop(iil_ReadLine($fp, 300), "\r\n");
1651
1652                                // The preg_match below works around communigate imap, which outputs " UID <number>)".
1653                                // Without this, the while statement continues on and gets the "FH0 OK completed" message.
1654                                // If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249. 
1655                                // This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing
1656                                // If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin
1657                                // An alternative might be:
1658                                // if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break;
1659                                // however, unsure how well this would work with all imap clients.
1660                                if (preg_match("/^\s*UID [0-9]+\)$/", $line)) {
1661                                    break;
1662                                }
1663
1664                                // handle FLAGS reply after headers (AOL, Zimbra?)
1665                                if (preg_match('/\s+FLAGS \((.*)\)\)$/', $line, $matches)) {
1666                                        $flags_str = $matches[1];
1667                                        break;
1668                                }
1669
1670                                if (ord($line[0])<=32) {
1671                                        $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($line);
1672                                } else {
1673                                        $lines[++$ln] = trim($line);
1674                                }
1675                        // patch from "Maksim Rubis" <siburny@hotmail.com>
1676                        } while (trim($line[0]) != ')' && strncmp($line, $key, strlen($key)));
1677
1678                        if (strncmp($line, $key, strlen($key))) {
1679                                // process header, fill iilBasicHeader obj.
1680                                // initialize
1681                                if (is_array($headers)) {
1682                                        reset($headers);
1683                                        while (list($k, $bar) = each($headers)) {
1684                                                $headers[$k] = '';
1685                                        }
1686                                }
1687       
1688                                // create array with header field:data
1689                                while ( list($lines_key, $str) = each($lines) ) {
1690                                        list($field, $string) = iil_SplitHeaderLine($str);
1691                                       
1692                                        $field  = strtolower($field);
1693                                        $string = preg_replace('/\n\s*/', ' ', $string);
1694                                       
1695                                        switch ($field) {
1696                                        case 'date';
1697                                                if (!$IMAP_USE_INTERNAL_DATE) {
1698                                                        $result[$id]->date = $string;
1699                                                        $result[$id]->timestamp = iil_StrToTime($string);
1700                                                }
1701                                                break;
1702                                        case 'from':
1703                                                $result[$id]->from = $string;
1704                                                break;
1705                                        case 'to':
1706                                                $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string);
1707                                                break;
1708                                        case 'subject':
1709                                                $result[$id]->subject = $string;
1710                                                break;
1711                                        case 'reply-to':
1712                                                $result[$id]->replyto = $string;
1713                                                break;
1714                                        case 'cc':
1715                                                $result[$id]->cc = $string;
1716                                                break;
1717                                        case 'bcc':
1718                                                $result[$id]->bcc = $string;
1719                                                break;
1720                                        case 'content-transfer-encoding':
1721                                                $result[$id]->encoding = $string;
1722                                                break;
1723                                        case 'content-type':
1724                                                $ctype_parts = explode(";", $string);
1725                                                $result[$id]->ctype = array_shift($ctype_parts);
1726                                                foreach ($ctype_parts as $ctype_add) {
1727                                                        if (preg_match('/charset="?([a-z0-9\-\.\_]+)"?/i',
1728                                                                $ctype_add, $regs)) {
1729                                                                $result[$id]->charset = $regs[1];
1730                                                        }
1731                                                }
1732                                                break;
1733                                        case 'in-reply-to':
1734                                                $result[$id]->in_reply_to = preg_replace('/[\n<>]/', '', $string);
1735                                                break;
1736                                        case 'references':
1737                                                $result[$id]->references = $string;
1738                                                break;
1739                                        case 'return-receipt-to':
1740                                        case 'disposition-notification-to':
1741                                        case 'x-confirm-reading-to':
1742                                                $result[$id]->mdn_to = $string;
1743                                                break;
1744                                        case 'message-id':
1745                                                $result[$id]->messageID = $string;
1746                                                break;
1747                                        case 'x-priority':
1748                                                if (preg_match('/^(\d+)/', $string, $matches))
1749                                                        $result[$id]->priority = intval($matches[1]);
1750                                                break;
1751                                        default:
1752                                                if (strlen($field) > 2)
1753                                                        $result[$id]->others[$field] = $string;
1754                                                break;
1755                                        } // end switch ()
1756                                } // end while ()
1757                        } else {
1758                                $a = explode(' ', $line);
1759                        }
1760
1761                        // process flags
1762                        if (!empty($flags_str)) {
1763                                $flags_str = preg_replace('/[\\\"]/', '', $flags_str);
1764                                $flags_a   = explode(' ', $flags_str);
1765                                       
1766                                if (is_array($flags_a)) {
1767                                        reset($flags_a);
1768                                        while (list(,$val)=each($flags_a)) {
1769                                                if (strcasecmp($val,'Seen') == 0) {
1770                                                    $result[$id]->seen = true;
1771                                                } else if (strcasecmp($val, 'Deleted') == 0) {
1772                                                    $result[$id]->deleted=true;
1773                                                } else if (strcasecmp($val, 'Recent') == 0) {
1774                                                    $result[$id]->recent = true;
1775                                                } else if (strcasecmp($val, 'Answered') == 0) {
1776                                                        $result[$id]->answered = true;
1777                                                } else if (strcasecmp($val, '$Forwarded') == 0) {
1778                                                        $result[$id]->forwarded = true;
1779                                                } else if (strcasecmp($val, 'Draft') == 0) {
1780                                                        $result[$id]->is_draft = true;
1781                                                } else if (strcasecmp($val, '$MDNSent') == 0) {
1782                                                        $result[$id]->mdn_sent = true;
1783                                                } else if (strcasecmp($val, 'Flagged') == 0) {
1784                                                         $result[$id]->flagged = true;
1785                                                }
1786                                        }
1787                                        $result[$id]->flags = $flags_a;
1788                                }
1789                        }
1790                }
1791        } while (strcmp($a[0], $key) != 0);
1792
1793        return $result;
1794}
1795
1796function iil_C_FetchHeader(&$conn, $mailbox, $id, $uidfetch=false, $bodystr=false, $add='') {
1797
1798        $a  = iil_C_FetchHeaders($conn, $mailbox, $id, $uidfetch, $bodystr, $add);
1799        if (is_array($a)) {
1800                return array_shift($a);
1801        }
1802        return false;
1803}
1804
1805function iil_SortHeaders($a, $field, $flag) {
1806        if (empty($field)) {
1807            $field = 'uid';
1808        }
1809        $field = strtolower($field);
1810        if ($field == 'date' || $field == 'internaldate') {
1811            $field = 'timestamp';
1812        }
1813        if (empty($flag)) {
1814            $flag = 'ASC';
1815        }
1816   
1817        $flag     = strtoupper($flag);
1818        $stripArr = ($field=='subject') ? array('Re: ','Fwd: ','Fw: ','"') : array('"');
1819
1820        $c=count($a);
1821        if ($c > 0) {
1822                /*
1823                        Strategy:
1824                        First, we'll create an "index" array.
1825                        Then, we'll use sort() on that array,
1826                        and use that to sort the main array.
1827                */
1828               
1829                // create "index" array
1830                $index = array();
1831                reset($a);
1832                while (list($key, $val)=each($a)) {
1833
1834                        if ($field == 'timestamp') {
1835                                $data = @strtotime($val->date);
1836                                if ($data == false) {
1837                                        $data = $val->timestamp;
1838                                }
1839                        } else {
1840                                $data = $val->$field;
1841                                if (is_string($data)) {
1842                                        $data=strtoupper(str_replace($stripArr, '', $data));
1843                                }
1844                        }
1845                        $index[$key]=$data;
1846                }
1847               
1848                // sort index
1849                $i = 0;
1850                if ($flag == 'ASC') {
1851                        asort($index);
1852                } else {
1853                        arsort($index);
1854                }
1855       
1856                // form new array based on index
1857                $result = array();
1858                reset($index);
1859                while (list($key, $val)=each($index)) {
1860                        $result[$key]=$a[$key];
1861                        $i++;
1862                }
1863        }
1864       
1865        return $result;
1866}
1867
1868function iil_C_Expunge(&$conn, $mailbox, $messages=NULL) {
1869
1870        if (iil_C_Select($conn, $mailbox)) {
1871                $c = 0;
1872                $command = $messages ? "UID EXPUNGE $messages" : "EXPUNGE";
1873
1874                iil_PutLine($conn->fp, "exp1 $command");
1875                do {
1876                        $line=chop(iil_ReadLine($conn->fp, 100));
1877                        if ($line[0] == '*') {
1878                                $c++;
1879                        }
1880                } while (!iil_StartsWith($line, 'exp1', true));
1881               
1882                if (iil_ParseResult($line) == 0) {
1883                        $conn->selected = ''; //state has changed, need to reselect                     
1884                        //$conn->exists-=$c;
1885                        return $c;
1886                }
1887                $conn->error = $line;
1888        }
1889       
1890        return -1;
1891}
1892
1893function iil_C_ModFlag(&$conn, $mailbox, $messages, $flag, $mod) {
1894        if ($mod != '+' && $mod != '-') {
1895            return -1;
1896        }
1897   
1898        $fp    = $conn->fp;
1899        $flags = $GLOBALS['IMAP_FLAGS'];
1900       
1901        $flag = strtoupper($flag);
1902        $flag = $flags[$flag];
1903   
1904        if (iil_C_Select($conn, $mailbox)) {
1905                $c = 0;
1906                iil_PutLine($fp, "flg UID STORE $messages " . $mod . "FLAGS (" . $flag . ")");
1907                do {
1908                        $line=chop(iil_ReadLine($fp, 100));
1909                        if ($line[0] == '*') {
1910                            $c++;
1911                        }
1912                } while (!iil_StartsWith($line, 'flg', true));
1913
1914                if (iil_ParseResult($line) == 0) {
1915                        return $c;
1916                }
1917                $conn->error = $line;
1918                return -1;
1919        }
1920        $conn->error = 'Select failed';
1921        return -1;
1922}
1923
1924function iil_C_Flag(&$conn, $mailbox, $messages, $flag) {
1925        return iil_C_ModFlag($conn, $mailbox, $messages, $flag, '+');
1926}
1927
1928function iil_C_Unflag(&$conn, $mailbox, $messages, $flag) {
1929        return iil_C_ModFlag($conn, $mailbox, $messages, $flag, '-');
1930}
1931
1932function iil_C_Delete(&$conn, $mailbox, $messages) {
1933        return iil_C_ModFlag($conn, $mailbox, $messages, 'DELETED', '+');
1934}
1935
1936function iil_C_Undelete(&$conn, $mailbox, $messages) {
1937        return iil_C_ModFlag($conn, $mailbox, $messages, 'DELETED', '-');
1938}
1939
1940function iil_C_Unseen(&$conn, $mailbox, $messages) {
1941        return iil_C_ModFlag($conn, $mailbox, $messages, 'SEEN', '-');
1942}
1943
1944function iil_C_Copy(&$conn, $messages, $from, $to) {
1945        $fp = $conn->fp;
1946
1947        if (empty($from) || empty($to)) {
1948            return -1;
1949        }
1950   
1951        if (iil_C_Select($conn, $from)) {
1952                $c=0;
1953               
1954                iil_PutLine($fp, "cpy1 UID COPY $messages \"".iil_Escape($to)."\"");
1955                $line=iil_ReadReply($fp);
1956                return iil_ParseResult($line);
1957        } else {
1958                return -1;
1959        }
1960}
1961
1962function iil_FormatSearchDate($month, $day, $year) {
1963        $month  = (int) $month;
1964        $months = $GLOBALS['IMAP_MONTHS'];
1965        return $day . '-' . $months[$month] . '-' . $year;
1966}
1967
1968function iil_C_CountUnseen(&$conn, $folder) {
1969        $index = iil_C_Search($conn, $folder, 'ALL UNSEEN');
1970        if (is_array($index)) {
1971                if (($cnt = count($index)) && $index[0] != '') {
1972                        return $cnt;
1973                }
1974        }
1975        return false;
1976}
1977
1978function iil_C_UID2ID(&$conn, $folder, $uid) {
1979        if ($uid > 0) {
1980                $id_a = iil_C_Search($conn, $folder, "UID $uid");
1981                if (is_array($id_a) && count($id_a) == 1) {
1982                        return $id_a[0];
1983                }
1984        }
1985        return false;
1986}
1987
1988function iil_C_ID2UID(&$conn, $folder, $id) {
1989        $fp = $conn->fp;
1990        if ($id == 0) {
1991            return      -1;
1992        }
1993        $result = -1;
1994        if (iil_C_Select($conn, $folder)) {
1995                $key = 'FUID';
1996                if (iil_PutLine($fp, "$key FETCH $id (UID)")) {
1997                        do {
1998                                $line=chop(iil_ReadLine($fp, 1024));
1999                                if (preg_match("/^\* $id FETCH \(UID (.*)\)/i", $line, $r)) {
2000                                        $result = $r[1];
2001                                }
2002                        } while (!preg_match("/^$key/", $line));
2003                }
2004        }
2005        return $result;
2006}
2007
2008function iil_C_Search(&$conn, $folder, $criteria) {
2009        $fp = $conn->fp;
2010        if (iil_C_Select($conn, $folder)) {
2011                $c = 0;
2012               
2013                $query = 'srch1 SEARCH ' . chop($criteria);
2014                if (!iil_PutLineC($fp, $query)) {
2015                        return false;
2016                }
2017                do {
2018                        $line=trim(iil_ReadLine($fp, 10000));
2019                        if (preg_match('/^\* SEARCH/i', $line)) {
2020                                $str = trim(substr($line, 8));
2021                                $messages = explode(' ', $str);
2022                        }
2023                } while (!iil_StartsWith($line, 'srch1', true));
2024
2025                $result_code = iil_ParseResult($line);
2026                if ($result_code == 0) {
2027                    return $messages;
2028                }
2029                $conn->error = 'iil_C_Search: ' . $line . "\n";
2030                return false;   
2031        }
2032        $conn->error = "iil_C_Search: Couldn't select \"$folder\"\n";
2033        return false;
2034}
2035
2036function iil_C_Move(&$conn, $messages, $from, $to) {
2037
2038    if (!$from || !$to) {
2039        return -1;
2040    }
2041   
2042    $r = iil_C_Copy($conn, $messages, $from, $to);
2043
2044    if ($r==0) {
2045        return iil_C_Delete($conn, $from, $messages);
2046    }
2047    return $r;
2048}
2049
2050/**
2051 * Gets the delimiter, for example:
2052 * INBOX.foo -> .
2053 * INBOX/foo -> /
2054 * INBOX\foo -> \
2055 *
2056 * @return mixed A delimiter (string), or false.
2057 * @param object $conn The current connection.
2058 * @see iil_Connect()
2059 */
2060function iil_C_GetHierarchyDelimiter(&$conn) {
2061
2062        global $my_prefs;
2063       
2064        if ($conn->delimiter) {
2065                return $conn->delimiter;
2066        }
2067        if (!empty($my_prefs['delimiter'])) {
2068            return ($conn->delimiter = $my_prefs['delimiter']);
2069        }
2070   
2071        $fp        = $conn->fp;
2072        $delimiter = false;
2073       
2074        //try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8)
2075        if (!iil_PutLine($fp, 'ghd LIST "" ""')) {
2076            return false;
2077        }
2078   
2079        do {
2080                $line=iil_ReadLine($fp, 500);
2081                if ($line[0] == '*') {
2082                        $line = rtrim($line);
2083                        $a=iil_ExplodeQuotedString(' ', iil_UnEscape($line));
2084                        if ($a[0] == '*') {
2085                            $delimiter = str_replace('"', '', $a[count($a)-2]);
2086                        }
2087                }
2088        } while (!iil_StartsWith($line, 'ghd', true));
2089
2090        if (strlen($delimiter)>0) {
2091            return $delimiter;
2092        }
2093
2094        //if that fails, try namespace extension
2095        //try to fetch namespace data
2096        iil_PutLine($conn->fp, "ns1 NAMESPACE");
2097        do {
2098                $line = iil_ReadLine($conn->fp, 1024);
2099                if (iil_StartsWith($line, '* NAMESPACE')) {
2100                        $i = 0;
2101                        $line = iil_UnEscape($line);
2102                        $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
2103                }
2104        } while (!iil_StartsWith($line, 'ns1', true));
2105               
2106        if (!is_array($data)) {
2107            return false;
2108        }
2109   
2110        //extract user space data (opposed to global/shared space)
2111        $user_space_data = $data[0];
2112        if (!is_array($user_space_data)) {
2113            return false;
2114        }
2115   
2116        //get first element
2117        $first_userspace = $user_space_data[0];
2118        if (!is_array($first_userspace)) {
2119            return false;
2120        }
2121   
2122        //extract delimiter
2123        $delimiter = $first_userspace[1];       
2124
2125        return $delimiter;
2126}
2127
2128function iil_C_ListMailboxes(&$conn, $ref, $mailbox) {
2129        global $IGNORE_FOLDERS;
2130       
2131        $ignore = $IGNORE_FOLDERS[strtolower($conn->host)];
2132               
2133        $fp = $conn->fp;
2134       
2135        if (empty($mailbox)) {
2136            $mailbox = '*';
2137        }
2138       
2139        if (empty($ref) && $conn->rootdir) {
2140            $ref = $conn->rootdir;
2141        }
2142   
2143        // send command
2144        if (!iil_PutLine($fp, "lmb LIST \"".$ref."\" \"".iil_Escape($mailbox)."\"")) {
2145            return false;
2146        }
2147   
2148        $i = 0;
2149        // get folder list
2150        do {
2151                $line = iil_ReadLine($fp, 500);
2152                $line = iil_MultLine($fp, $line);
2153
2154                $a = explode(' ', $line);
2155                if (($line[0] == '*') && ($a[1] == 'LIST')) {
2156                        $line = rtrim($line);
2157                        // split one line
2158                        $a = iil_ExplodeQuotedString(' ', $line);
2159                        // last string is folder name
2160                        $folder = preg_replace(array('/^"/', '/"$/'), '', $a[count($a)-1]);
2161           
2162                        if (empty($ignore) || (!empty($ignore)
2163                                && !preg_match('/'.preg_quote(ignore, '/').'/i', $folder))) {
2164                                $folders[$i] = $folder;
2165                        }
2166           
2167                        // second from last is delimiter
2168                        $delim = trim($a[count($a)-2], '"');
2169                        // is it a container?
2170                        $i++;
2171                }
2172        } while (!iil_StartsWith($line, 'lmb', true));
2173
2174        if (is_array($folders)) {
2175            if (!empty($ref)) {
2176                // if rootdir was specified, make sure it's the first element
2177                // some IMAP servers (i.e. Courier) won't return it
2178                if ($ref[strlen($ref)-1]==$delim)
2179                    $ref = substr($ref, 0, strlen($ref)-1);
2180                if ($folders[0]!=$ref)
2181                    array_unshift($folders, $ref);
2182            }
2183            return $folders;
2184        } else if (iil_ParseResult($line) == 0) {
2185                return array('INBOX');
2186        } else {
2187                $conn->error = $line;
2188                return false;
2189        }
2190}
2191
2192function iil_C_ListSubscribed(&$conn, $ref, $mailbox) {
2193        global $IGNORE_FOLDERS;
2194       
2195        $ignore = $IGNORE_FOLDERS[strtolower($conn->host)];
2196       
2197        $fp = $conn->fp;
2198        if (empty($mailbox)) {
2199                $mailbox = '*';
2200        }
2201        if (empty($ref) && $conn->rootdir) {
2202                $ref = $conn->rootdir;
2203        }
2204        $folders = array();
2205
2206        // send command
2207        if (!iil_PutLine($fp, 'lsb LSUB "' . $ref . '" "' . iil_Escape($mailbox).'"')) {
2208                $conn->error = "Couldn't send LSUB command\n";
2209                return false;
2210        }
2211       
2212        $i = 0;
2213       
2214        // get folder list
2215        do {
2216                $line = iil_ReadLine($fp, 500);
2217                $line = iil_MultLine($fp, $line);
2218                $a    = explode(' ', $line);
2219       
2220                if (($line[0] == '*') && ($a[1] == 'LSUB' || $a[1] == 'LIST')) {
2221                        $line = rtrim($line);
2222           
2223                        // split one line
2224                        $a = iil_ExplodeQuotedString(' ', $line);
2225           
2226                        // last string is folder name
2227                        $folder = preg_replace(array('/^"/', '/"$/'), '', $a[count($a)-1]);
2228       
2229                        if ((!in_array($folder, $folders)) && (empty($ignore)
2230                                || (!empty($ignore) && !preg_match('/'.preg_quote(ignore, '/').'/i', $folder)))) {
2231                            $folders[$i] = $folder;
2232                        }
2233           
2234                        // second from last is delimiter
2235                        $delim = trim($a[count($a)-2], '"');
2236           
2237                        // is it a container?
2238                        $i++;
2239                }
2240        } while (!iil_StartsWith($line, 'lsb', true));
2241
2242        if (is_array($folders)) {
2243            if (!empty($ref)) {
2244                // if rootdir was specified, make sure it's the first element
2245                // some IMAP servers (i.e. Courier) won't return it
2246                if ($ref[strlen($ref)-1]==$delim) {
2247                    $ref = substr($ref, 0, strlen($ref)-1);
2248                }
2249                if ($folders[0]!=$ref) {
2250                    array_unshift($folders, $ref);
2251                }
2252            }
2253            return $folders;
2254        }
2255        $conn->error = $line;
2256        return false;
2257}
2258
2259function iil_C_Subscribe(&$conn, $folder) {
2260        $fp = $conn->fp;
2261
2262        $query = 'sub1 SUBSCRIBE "' . iil_Escape($folder). '"';
2263        iil_PutLine($fp, $query);
2264
2265        $line = trim(iil_ReadLine($fp, 10000));
2266        return iil_ParseResult($line);
2267}
2268
2269function iil_C_UnSubscribe(&$conn, $folder) {
2270        $fp = $conn->fp;
2271
2272        $query = 'usub1 UNSUBSCRIBE "' . iil_Escape($folder) . '"';
2273        iil_PutLine($fp, $query);
2274   
2275        $line = trim(iil_ReadLine($fp, 10000));
2276        return iil_ParseResult($line);
2277}
2278
2279function iil_C_FetchMIMEHeaders(&$conn, $mailbox, $id, $parts) {
2280       
2281        $fp     = $conn->fp;
2282
2283        if (!iil_C_Select($conn, $mailbox)) {
2284                return false;
2285        }
2286       
2287        $result = false;
2288        $parts = (array) $parts;
2289        $key = 'fmh0';
2290        $peeks = '';
2291        $idx = 0;
2292
2293        // format request
2294        foreach($parts as $part)
2295                $peeks[] = "BODY.PEEK[$part.MIME]";
2296       
2297        $request = "$key FETCH $id (" . implode(' ', $peeks) . ')';
2298
2299        // send request
2300        if (!iil_PutLine($fp, $request)) {
2301            return false;
2302        }
2303       
2304        do {
2305                $line = iil_ReadLine($fp, 1000);
2306                $line = iil_MultLine($fp, $line);
2307
2308                if (preg_match('/BODY\[([0-9\.]+)\.MIME\]/', $line, $matches)) {
2309                        $idx = $matches[1];
2310                        $result[$idx] = preg_replace('/^(\* '.$id.' FETCH \()?\s*BODY\['.$idx.'\.MIME\]\s+/', '', $line);
2311                        $result[$idx] = trim($result[$idx], '"');
2312                        $result[$idx] = rtrim($result[$idx], "\t\r\n\0\x0B");
2313                }
2314        } while (!iil_StartsWith($line, $key, true));
2315
2316        return $result;
2317}
2318
2319function iil_C_FetchPartHeader(&$conn, $mailbox, $id, $part) {
2320
2321        $part = empty($part) ? 'HEADER' : $part.'.MIME';
2322
2323        return iil_C_HandlePartBody($conn, $mailbox, $id, $part);
2324}
2325
2326function iil_C_HandlePartBody(&$conn, $mailbox, $id, $part='', $encoding=NULL, $print=NULL, $file=NULL) {
2327       
2328        $fp     = $conn->fp;
2329        $result = false;
2330       
2331        switch ($encoding) {
2332                case 'base64':
2333                        $mode = 1;
2334                break;
2335                case 'quoted-printable':
2336                        $mode = 2;
2337                break;
2338                case 'x-uuencode':
2339                case 'x-uue':
2340                case 'uue':
2341                case 'uuencode':
2342                        $mode = 3;
2343                break;
2344                default:
2345                        $mode = 0;
2346        }
2347       
2348        if (iil_C_Select($conn, $mailbox)) {
2349                $reply_key = '* ' . $id;
2350
2351                // format request
2352                $key     = 'ftch0';
2353                $request = $key . " FETCH $id (BODY.PEEK[$part])";
2354                // send request
2355                if (!iil_PutLine($fp, $request)) {
2356                    return false;
2357                }
2358
2359                // receive reply line
2360                do {
2361                        $line = chop(iil_ReadLine($fp, 1000));
2362                        $a    = explode(' ', $line);
2363                } while (!($end = iil_StartsWith($line, $key, true)) && $a[2] != 'FETCH');
2364                $len = strlen($line);
2365
2366                // handle empty "* X FETCH ()" response
2367                if ($line[$len-1] == ')' && $line[$len-2] != '(') {
2368                        // one line response, get everything between first and last quotes
2369                        if (substr($line, -4, 3) == 'NIL') {
2370                                // NIL response
2371                                $result = '';
2372                        } else {
2373                                $from = strpos($line, '"') + 1;
2374                                $to   = strrpos($line, '"');
2375                                $len  = $to - $from;
2376                                $result = substr($line, $from, $len);
2377                        }
2378           
2379                        if ($mode == 1)
2380                                $result = base64_decode($result);
2381                        else if ($mode == 2)
2382                                $result = quoted_printable_decode($result);
2383                        else if ($mode == 3)
2384                                $result = convert_uudecode($result);
2385
2386                } else if ($line[$len-1] == '}') {
2387                        //multi-line request, find sizes of content and receive that many bytes
2388                        $from     = strpos($line, '{') + 1;
2389                        $to       = strrpos($line, '}');
2390                        $len      = $to - $from;
2391                        $sizeStr  = substr($line, $from, $len);
2392                        $bytes    = (int)$sizeStr;
2393                        $prev     = '';
2394                       
2395                        while ($bytes > 0) {
2396                                $line      = iil_ReadLine($fp, 1024);
2397                                $len       = strlen($line);
2398               
2399                                if ($len > $bytes) {
2400                                        $line = substr($line, 0, $bytes);
2401                                }
2402                                $bytes -= strlen($line);
2403
2404                                if ($mode == 1) {
2405                                        $line = rtrim($line, "\t\r\n\0\x0B");
2406                                        // create chunks with proper length for base64 decoding
2407                                        $line = $prev.$line;
2408                                        $length = strlen($line);
2409                                        if ($length % 4) {
2410                                                $length = floor($length / 4) * 4;
2411                                                $prev = substr($line, $length);
2412                                                $line = substr($line, 0, $length);
2413                                        }
2414                                        else
2415                                                $prev = '';
2416
2417                                        if ($file)
2418                                                fwrite($file, base64_decode($line));
2419                                        else if ($print)
2420                                                echo base64_decode($line);
2421                                        else
2422                                                $result .= base64_decode($line);
2423                                } else if ($mode == 2) {
2424                                        $line = rtrim($line, "\t\r\0\x0B");
2425                                        if ($file)
2426                                                fwrite($file, quoted_printable_decode($line));
2427                                        else if ($print)
2428                                                echo quoted_printable_decode($line);
2429                                        else
2430                                                $result .= quoted_printable_decode($line);
2431                                } else if ($mode == 3) {
2432                                        $line = rtrim($line, "\t\r\n\0\x0B");
2433                                        if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line))
2434                                                continue;
2435                                        if ($file)
2436                                                fwrite($file, convert_uudecode($line));
2437                                        else if ($print)
2438                                                echo convert_uudecode($line);
2439                                        else
2440                                                $result .= convert_uudecode($line);
2441                                } else {
2442                                        $line = rtrim($line, "\t\r\n\0\x0B");
2443                                        if ($file)
2444                                                fwrite($file, $line . "\n");
2445                                        else if ($print)
2446                                                echo $line . "\n";
2447                                        else
2448                                                $result .= $line . "\n";
2449                                }
2450                        }
2451                }
2452
2453                // read in anything up until last line
2454                if (!$end)
2455                        do {
2456                                $line = iil_ReadLine($fp, 1024);
2457                        } while (!iil_StartsWith($line, $key, true));
2458       
2459                if ($result) {
2460                        $result = rtrim($result, "\t\r\n\0\x0B");
2461                        if ($file) {
2462                                fwrite($file, $result);
2463                        } else if ($print) {
2464                                echo $result;
2465                        } else
2466                                return $result; // substr($result, 0, strlen($result)-1);
2467                }
2468               
2469                return true;
2470        }
2471   
2472        return false;
2473}
2474
2475function iil_C_FetchPartBody(&$conn, $mailbox, $id, $part, $file=NULL) {
2476        return iil_C_HandlePartBody($conn, $mailbox, $id, $part, NULL, NULL, $file);
2477}
2478
2479function iil_C_PrintPartBody(&$conn, $mailbox, $id, $part) {
2480        iil_C_HandlePartBody($conn, $mailbox, $id, $part, NULL, true, NULL);
2481}
2482
2483function iil_C_CreateFolder(&$conn, $folder) {
2484        $fp = $conn->fp;
2485        if (iil_PutLine($fp, 'c CREATE "' . iil_Escape($folder) . '"')) {
2486                do {
2487                        $line=iil_ReadLine($fp, 300);
2488                } while ($line[0] != 'c');
2489        $conn->error = $line;
2490                return (iil_ParseResult($line) == 0);
2491        }
2492        return false;
2493}
2494
2495function iil_C_RenameFolder(&$conn, $from, $to) {
2496        $fp = $conn->fp;
2497        if (iil_PutLine($fp, 'r RENAME "' . iil_Escape($from) . '" "' . iil_Escape($to) . '"')) {
2498                do {
2499                        $line = iil_ReadLine($fp, 300);
2500                } while ($line[0] != 'r');
2501                return (iil_ParseResult($line) == 0);
2502        }
2503        return false;
2504}
2505
2506function iil_C_DeleteFolder(&$conn, $folder) {
2507        $fp = $conn->fp;
2508        if (iil_PutLine($fp, 'd DELETE "' . iil_Escape($folder). '"')) {
2509                do {
2510                        $line=iil_ReadLine($fp, 300);
2511                } while ($line[0] != 'd');
2512                return (iil_ParseResult($line) == 0);
2513        }
2514        $conn->error = "Couldn't send command\n";
2515        return false;
2516}
2517
2518function iil_C_Append(&$conn, $folder, &$message) {
2519        if (!$folder) {
2520                return false;
2521        }
2522        $fp = $conn->fp;
2523
2524        $message = str_replace("\r", '', $message);
2525        $message = str_replace("\n", "\r\n", $message);         
2526
2527        $len = strlen($message);
2528        if (!$len) {
2529                return false;
2530        }
2531
2532        $request = 'A APPEND "' . iil_Escape($folder) .'" (\\Seen) {' . $len . '}';
2533   
2534        if (iil_PutLine($fp, $request)) {
2535                $line=iil_ReadLine($fp, 100);           
2536                $sent = fwrite($fp, $message."\r\n");
2537                do {
2538                        $line=iil_ReadLine($fp, 1000);
2539                } while ($line[0] != 'A');
2540       
2541                $result = (iil_ParseResult($line) == 0);
2542                if (!$result) {
2543                    $conn->error .= $line . "\n";
2544                }
2545                return $result;
2546        }
2547
2548        $conn->error .= "Couldn't send command \"$request\"\n";
2549        return false;
2550}
2551
2552function iil_C_AppendFromFile(&$conn, $folder, $path) {
2553        if (!$folder) {
2554            return false;
2555        }
2556   
2557        //open message file
2558        $in_fp = false;                         
2559        if (file_exists(realpath($path))) {
2560                $in_fp = fopen($path, 'r');
2561        }
2562        if (!$in_fp) {
2563                $conn->error .= "Couldn't open $path for reading\n";
2564                return false;
2565        }
2566       
2567        $fp  = $conn->fp;
2568        $len = filesize($path);
2569        if (!$len) {
2570                return false;
2571        }
2572   
2573        //send APPEND command
2574        $request    = 'A APPEND "' . iil_Escape($folder) . '" (\\Seen) {' . $len . '}';
2575        $bytes_sent = 0;
2576        if (iil_PutLine($fp, $request)) {
2577                $line = iil_ReadLine($fp, 100);
2578                               
2579                //send file
2580                while (!feof($in_fp)) {
2581                        $buffer      = fgets($in_fp, 4096);
2582                        $bytes_sent += strlen($buffer);
2583                        iil_PutLine($fp, $buffer, false);
2584                }
2585                fclose($in_fp);
2586
2587                iil_PutLine($fp, '');
2588
2589                //read response
2590                do {
2591                        $line = iil_ReadLine($fp, 1000);
2592                } while ($line[0] != 'A');
2593                       
2594                $result = (iil_ParseResult($line) == 0);
2595                if (!$result) {
2596                    $conn->error .= $line . "\n";
2597                }
2598       
2599                return $result;
2600        }
2601       
2602        $conn->error .= "Couldn't send command \"$request\"\n";
2603        return false;
2604}
2605
2606function iil_C_FetchStructureString(&$conn, $folder, $id) {
2607        $fp     = $conn->fp;
2608        $result = false;
2609       
2610        if (iil_C_Select($conn, $folder)) {
2611                $key = 'F1247';
2612
2613                if (iil_PutLine($fp, "$key FETCH $id (BODYSTRUCTURE)")) {
2614                        do {
2615                                $line = iil_ReadLine($fp, 5000);
2616                                $line = iil_MultLine($fp, $line);
2617                                list(, $index, $cmd, $rest) = explode(' ', $line);
2618                                if ($cmd != 'FETCH' || $index == $id || preg_match("/^$key/", $line))
2619                                        $result .= $line;
2620                        } while (!preg_match("/^$key/", $line));
2621
2622                        $result = trim(substr($result, strpos($result, 'BODYSTRUCTURE')+13, -(strlen($result)-strrpos($result, $key)+1)));
2623                }
2624        }
2625        return $result;
2626}
2627
2628function iil_C_GetQuota(&$conn) {
2629/*
2630 * GETQUOTAROOT "INBOX"
2631 * QUOTAROOT INBOX user/rchijiiwa1
2632 * QUOTA user/rchijiiwa1 (STORAGE 654 9765)
2633 * OK Completed
2634 */
2635        $fp         = $conn->fp;
2636        $result     = false;
2637        $quota_lines = array();
2638       
2639        // get line(s) containing quota info
2640        if (iil_PutLine($fp, 'QUOT1 GETQUOTAROOT "INBOX"')) {
2641                do {
2642                        $line=chop(iil_ReadLine($fp, 5000));
2643                        if (iil_StartsWith($line, '* QUOTA ')) {
2644                                $quota_lines[] = $line;
2645                        }
2646                } while (!iil_StartsWith($line, 'QUOT1', true));
2647        }
2648       
2649        // return false if not found, parse if found
2650        $min_free = PHP_INT_MAX;
2651        foreach ($quota_lines as $key => $quota_line) {
2652                $quota_line   = preg_replace('/[()]/', '', $quota_line);
2653                $parts        = explode(' ', $quota_line);
2654                $storage_part = array_search('STORAGE', $parts);
2655               
2656                if (!$storage_part) continue;
2657       
2658                $used   = intval($parts[$storage_part+1]);
2659                $total  = intval($parts[$storage_part+2]);
2660                $free   = $total - $used;
2661       
2662                // return lowest available space from all quotas
2663                if ($free < $min_free) {
2664                        $min_free = $free;
2665                        $result['used']    = $used;
2666                        $result['total']   = $total;
2667                        $result['percent'] = min(100, round(($used/max(1,$total))*100));
2668                        $result['free']    = 100 - $result['percent'];
2669                }
2670        }
2671        return $result;
2672}
2673
2674function iil_C_ClearFolder(&$conn, $folder) {
2675        $num_in_trash = iil_C_CountMessages($conn, $folder);
2676        if ($num_in_trash > 0) {
2677                iil_C_Delete($conn, $folder, '1:*');
2678        }
2679        return (iil_C_Expunge($conn, $folder) >= 0);
2680}
2681
2682?>
Note: See TracBrowser for help on using the repository browser.