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

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