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

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