2 /////////////////////////////////////////////////////////
4 // Iloha IMAP Library (IIL)
6 // (C)Copyright 2002 Ryo Chijiiwa <Ryo@IlohaMail.org>
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
12 /////////////////////////////////////////////////////////
14 /********************************************************
16 FILE: include/imap.inc
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
23 Function containing "_C_" in name require connection handler to be
24 passed as one of the parameters. To obtain connection handler, use
27 IlohaMail-0.9-20050415
29 File altered by Thomas Bruederli <roundcube@gmail.com>
30 to fit enhanced equirements by the RoundCube Webmail:
31 - Added list of server capabilites and check these before invoking commands
32 - Added junk flag to iilBasicHeader
33 - Enhanced error reporting on fsockopen()
34 - Additional parameter for SORT command
35 - Removed Call-time pass-by-reference because deprecated
36 - Parse charset from content-type in iil_C_FetchHeaders()
37 - Enhanced heaer sorting
38 - Pass message as reference in iil_C_Append (to save memory)
39 - Added BCC and REFERENCE to the list of headers to fetch in iil_C_FetchHeaders()
40 - Leave messageID unchanged in iil_C_FetchHeaders()
41 - Avoid stripslahes in iil_Connect()
42 - Escape quotes and backslashes in iil_C_Login()
43 - Added patch to iil_SortHeaders() by Richard Green
44 - Removed <br> from error messages (better for logging)
45 - Added patch to iil_C_Sort() enabling UID SORT commands
46 - Added function iil_C_ID2UID()
47 - Casting date parts in iil_StrToTime() to avoid mktime() warnings
48 - Also acceppt LIST responses in iil_C_ListSubscribed()
49 - Sanity check of $message_set in iil_C_FetchHeaders(), iil_C_FetchHeaderIndex(), iil_C_FetchThreadHeaders()
50 - Implemented UID FETCH in iil_C_FetchHeaders()
51 - Abort do-loop on socket errors (fgets returns false)
52 - $ICL_SSL is not boolean anymore but contains the connection schema (ssl or tls)
53 - Removed some debuggers (echo ...)
54 File altered by Aleksander Machniak <alec@alec.pl>
55 - trim(chop()) replaced by trim()
56 - added iil_Escape() with support for " and \ in folder names
57 - support \ character in username in iil_C_Login()
58 - fixed iil_MultLine(): use iil_ReadBytes() instead of iil_ReadLine()
59 - fixed iil_C_FetchStructureString() to handle many literal strings in response
60 - removed hardcoded data size in iil_ReadLine()
61 - added iil_PutLine() wrapper for fputs()
62 - code cleanup and identation fixes
63 - removed flush() calls in iil_C_HandlePartBody() to prevent from memory leak (#1485187)
64 - don't return "??" from iil_C_GetQuota()
65 - RFC3501 [7.1] don't call CAPABILITY if was returned in server
66 optional resposne in iil_Connect(), added iil_C_GetCapability()
67 - remove 'undisclosed-recipients' string from 'To' header
68 - iil_C_HandlePartBody(): added 6th argument and fixed endless loop
69 - added iil_PutLineC()
70 - fixed iil_C_Sort() to support very long and/or divided responses
71 - added BYE response simple support for endless loop prevention
72 - added 3rd argument in iil_StartsWith* functions
73 - fix iil_C_FetchPartHeader() in some cases by use of iil_C_HandlePartBody()
74 - allow iil_C_HandlePartBody() to fetch whole message
75 - optimize iil_C_FetchHeaders() to use only one FETCH command
76 - added 4th argument to iil_Connect()
77 - allow setting rootdir and delimiter before connect
78 - support multiquota result
80 ********************************************************/
83 * @todo Possibly clean up more CS.
84 * @todo Try to replace most double-quotes with single-quotes.
85 * @todo Split this file into smaller files.
86 * @todo Refactor code.
87 * @todo Replace echo-debugging (make it adhere to config setting and log)
90 // changed path to work within roundcube webmail
91 include_once 'lib/icl_commons.inc';
94 if (!isset($IMAP_USE_HEADER_DATE) || !$IMAP_USE_HEADER_DATE) {
95 $IMAP_USE_INTERNAL_DATE = true;
99 * @todo Maybe use date() to generate this.
101 $GLOBALS['IMAP_MONTHS'] = array("Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4,
102 "May" => 5, "Jun" => 6, "Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10,
103 "Nov" => 11, "Dec" => 12);
105 $GLOBALS['IMAP_SERVER_TZ'] = date('Z');
107 $GLOBALS['IMAP_FLAGS'] = array(
109 'DELETED' => '\\Deleted',
110 'RECENT' => '\\Recent',
111 'ANSWERED' => '\\Answered',
112 'DRAFT' => '\\Draft',
113 'FLAGGED' => '\\Flagged',
114 'FORWARDED' => '$Forwarded',
115 'MDNSENT' => '$MDNSent');
122 * @todo Change class vars to public/private
139 var $capability = array();
140 var $permanentflags = array();
141 var $capability_readed = false;
145 * @todo Change class vars to public/private
170 var $mdn_sent = false;
171 var $is_draft = false;
173 var $deleted = false;
175 var $answered = false;
176 var $forwarded = false;
178 var $flagged = false;
182 * @todo Change class vars to public/private
184 class iilThreadHeader
192 function iil_xor($string, $string2) {
194 $size = strlen($string);
195 for ($i=0; $i<$size; $i++) {
196 $result .= chr(ord($string[$i]) ^ ord($string2[$i]));
201 function iil_PutLine($fp, $string, $endln=true) {
202 // console('C: '. rtrim($string));
203 return fputs($fp, $string . ($endln ? "\r\n" : ''));
206 // iil_PutLine replacement with Command Continuation Requests (RFC3501 7.5) support
207 function iil_PutLineC($fp, $string, $endln=true) {
212 if ($parts = preg_split('/(\{[0-9]+\}\r\n)/m', $string, -1, PREG_SPLIT_DELIM_CAPTURE)) {
213 for($i=0, $cnt=count($parts); $i<$cnt; $i++) {
214 if(preg_match('/^\{[0-9]+\}\r\n$/', $parts[$i+1])) {
215 $res += iil_PutLine($fp, $parts[$i].$parts[$i+1], false);
216 $line = iil_ReadLine($fp, 1000);
220 $res += iil_PutLine($fp, $parts[$i], false);
226 function iil_ReadLine($fp, $size) {
238 $buffer = fgets($fp, $size);
239 if ($buffer === false) {
242 // console('S: '. chop($buffer));
244 } while ($buffer[strlen($buffer)-1] != "\n");
249 function iil_MultLine($fp, $line) {
251 if (ereg('\{[0-9]+\}$', $line)) {
254 preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a);
256 while (strlen($out) < $bytes) {
257 $line = iil_ReadBytes($fp, $bytes);
260 $line = $a[1][0] . "\"$out\"";
261 // console('[...] '. $out);
266 function iil_ReadBytes($fp, $bytes) {
270 $data .= fread($fp, $bytes-$len);
271 if ($len == strlen($data)) {
272 break; //nothing was read -> exit to avoid apache lockups
274 $len = strlen($data);
275 } while ($len < $bytes);
280 function iil_ReadReply($fp) {
282 $line = trim(iil_ReadLine($fp, 1024));
283 } while ($line[0] == '*');
288 function iil_ParseResult($string) {
289 $a=explode(' ', $string);
291 if (strcasecmp($a[1], 'OK') == 0) {
293 } else if (strcasecmp($a[1], 'NO') == 0) {
295 } else if (strcasecmp($a[1], 'BAD') == 0) {
297 } else if (strcasecmp($a[1], 'BYE') == 0) {
304 // check if $string starts with $match
305 function iil_StartsWith($string, $match, $bye=false) {
306 $len = strlen($match);
310 if (strncmp($string, $match, $len) == 0) {
313 if ($bye && strncmp($string, '* BYE ', 6) == 0) {
319 function iil_StartsWithI($string, $match, $bye=false) {
320 $len = strlen($match);
324 if (strncasecmp($string, $match, $len) == 0) {
327 if ($bye && strncmp($string, '* BYE ', 6) == 0) {
333 function iil_Escape($string)
335 return strtr($string, array('"'=>'\\"', '\\' => '\\\\'));
338 function iil_C_GetCapability(&$conn, $name)
340 if (in_array($name, $conn->capability)) {
343 else if ($conn->capability_readed) {
347 // get capabilities (only once) because initial
348 // optional CAPABILITY response may differ
349 $conn->capability = array();
351 iil_PutLine($conn->fp, "cp01 CAPABILITY");
353 $line = trim(iil_ReadLine($conn->fp, 1024));
354 $a = explode(' ', $line);
355 if ($line[0] == '*') {
356 while (list($k, $w) = each($a)) {
357 if ($w != '*' && $w != 'CAPABILITY')
358 $conn->capability[] = strtoupper($w);
361 } while ($a[0] != 'cp01');
363 $conn->capability_readed = true;
365 if (in_array($name, $conn->capability)) {
372 function iil_C_Authenticate(&$conn, $user, $pass, $encChallenge) {
377 // initialize ipad, opad
378 for ($i=0;$i<64;$i++) {
383 // pad $pass so it's 64 bytes
384 $padLen = 64 - strlen($pass);
385 for ($i=0;$i<$padLen;$i++) {
390 $hash = md5(iil_xor($pass,$opad) . pack("H*", md5(iil_xor($pass, $ipad) . base64_decode($encChallenge))));
393 $reply = base64_encode($user . ' ' . $hash);
395 // send result, get reply
396 iil_PutLine($conn->fp, $reply);
397 $line = iil_ReadLine($conn->fp, 1024);
400 $result = iil_ParseResult($line);
407 if ($result == -3) fclose($conn->fp); // BYE response
409 $conn->error .= 'Authentication for ' . $user . ' failed (AUTH): "';
410 $conn->error .= htmlspecialchars($line) . '"';
411 $conn->errorNum = $result;
416 function iil_C_Login(&$conn, $user, $password) {
418 iil_PutLine($conn->fp, 'a001 LOGIN "'.iil_Escape($user).'" "'.iil_Escape($password).'"');
421 $line = iil_ReadReply($conn->fp);
422 if ($line === false) {
425 } while (!iil_StartsWith($line, 'a001 ', true));
428 $result = iil_ParseResult($line);
438 $conn->error .= 'Authentication for ' . $user . ' failed (LOGIN): "';
439 $conn->error .= htmlspecialchars($line)."\"";
440 $conn->errorNum = $result;
445 function iil_ParseNamespace2($str, &$i, $len=0, $l) {
447 $str = str_replace('NIL', '()', $str);
455 for ($i;$i<$len;$i++) {
456 $c = (string)$str[$i];
457 if ($c == '(' && !$in_quotes) {
459 $data[$elem] = iil_ParseNamespace2($str, $i, $len, $l++);
461 } else if ($c == ')' && !$in_quotes) {
463 } else if ($c == '\\') {
466 $data[$elem] .= $c.$str[$i];
468 } else if ($c == '"') {
469 $in_quotes = !$in_quotes;
473 } else if ($in_quotes) {
480 function iil_C_NameSpace(&$conn) {
483 if (isset($my_prefs['rootdir']) && is_string($my_prefs['rootdir'])) {
484 $conn->rootdir = $my_prefs['rootdir'];
488 if (!iil_C_GetCapability($conn, 'NAMESPACE')) {
492 iil_PutLine($conn->fp, "ns1 NAMESPACE");
494 $line = iil_ReadLine($conn->fp, 1024);
495 if (iil_StartsWith($line, '* NAMESPACE')) {
497 $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
499 } while (!iil_StartsWith($line, 'ns1', true));
501 if (!is_array($data)) {
505 $user_space_data = $data[0];
506 if (!is_array($user_space_data)) {
510 $first_userspace = $user_space_data[0];
511 if (count($first_userspace)!=2) {
515 $conn->rootdir = $first_userspace[0];
516 $conn->delimiter = $first_userspace[1];
517 $my_prefs['rootdir'] = substr($conn->rootdir, 0, -1);
518 $my_prefs['delimiter'] = $conn->delimiter;
523 function iil_Connect($host, $user, $password, $options=null) {
524 global $iil_error, $iil_errornum;
525 global $ICL_SSL, $ICL_PORT;
526 global $IMAP_NO_CACHE;
527 global $my_prefs, $IMAP_USE_INTERNAL_DATE;
532 // set some imap options
533 if (is_array($options)) {
534 foreach($options as $optkey => $optval) {
535 if ($optkey == 'imap') {
536 $auth_method = $optval;
537 } else if ($optkey == 'rootdir') {
538 $my_prefs['rootdir'] = $optval;
539 } else if ($optkey == 'delimiter') {
540 $my_prefs['delimiter'] = $optval;
545 if (empty($auth_method))
546 $auth_method = 'plain';
548 $message = "INITIAL: $auth_method\n";
552 //initialize connection
553 $conn = new iilConnection;
556 $conn->selected = '';
559 $conn->cache = array();
560 $conn->do_cache = (function_exists("cache_write")&&!$IMAP_NO_CACHE);
561 $conn->cache_dirty = array();
563 if ($my_prefs['sort_field'] == 'INTERNALDATE') {
564 $IMAP_USE_INTERNAL_DATE = true;
565 } else if ($my_prefs['sort_field'] == 'DATE') {
566 $IMAP_USE_INTERNAL_DATE = false;
568 //echo '<!-- conn sort_field: '.$my_prefs['sort_field'].' //-->';
572 $iil_error = "Empty host";
577 $iil_error = "Empty user";
581 if (empty($password)) {
582 $iil_error = "Empty password";
592 $host = $ICL_SSL . '://' . $host;
595 //open socket connection
596 $conn->fp = fsockopen($host, $ICL_PORT, $errno, $errstr, 10);
598 $iil_error = "Could not connect to $host at port $ICL_PORT: $errstr";
603 $iil_error .= "Socket connection established\r\n";
604 $line = iil_ReadLine($conn->fp, 4096);
606 // RFC3501 [7.1] optional CAPABILITY response
607 if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
608 $conn->capability = explode(' ', strtoupper($matches[1]));
611 $conn->message .= $line;
613 if (strcasecmp($auth_method, "check") == 0) {
614 //check for supported auth methods
615 if (iil_C_GetCapability($conn, 'AUTH=CRAM-MD5') || iil_C_GetCapability($conn, 'AUTH=CRAM_MD5')) {
616 $auth_method = 'auth';
619 //default to plain text auth
620 $auth_method = 'plain';
624 if (strcasecmp($auth_method, 'auth') == 0) {
625 $conn->message .= "Trying CRAM-MD5\n";
627 //do CRAM-MD5 authentication
628 iil_PutLine($conn->fp, "a000 AUTHENTICATE CRAM-MD5");
629 $line = trim(iil_ReadLine($conn->fp, 1024));
631 $conn->message .= "$line\n";
633 if ($line[0] == '+') {
634 $conn->message .= 'Got challenge: ' . htmlspecialchars($line) . "\n";
636 //got a challenge string, try CRAM-5
637 $result = iil_C_Authenticate($conn, $user, $password, substr($line,2));
639 // stop if server sent BYE response
641 $iil_error = $conn->error;
642 $iil_errornum = $conn->errorNum;
645 $conn->message .= "Tried CRAM-MD5: $result \n";
647 $conn->message .='No challenge ('.htmlspecialchars($line)."), try plain\n";
652 if ((!$result)||(strcasecmp($auth, "plain") == 0)) {
654 $result = iil_C_Login($conn, $user, $password);
655 $conn->message .= "Tried PLAIN: $result \n";
658 $conn->message .= $auth;
660 if (!is_int($result)) {
661 iil_C_Namespace($conn);
664 $iil_error = $conn->error;
665 $iil_errornum = $conn->errorNum;
670 function iil_Close(&$conn) {
671 iil_C_WriteCache($conn);
672 if (iil_PutLine($conn->fp, "I LOGOUT")) {
673 fgets($conn->fp, 1024);
679 function iil_ClearCache($user, $host) {
682 function iil_C_WriteCache(&$conn) {
683 //echo "<!-- doing iil_C_WriteCache //-->\n";
684 if (!$conn->do_cache) return false;
686 if (is_array($conn->cache)) {
687 while (list($folder,$data)=each($conn->cache)) {
688 if ($folder && is_array($data) && $conn->cache_dirty[$folder]) {
689 $key = $folder.".imap";
690 $result = cache_write($conn->user, $conn->host, $key, $data, true);
691 //echo "<!-- writing $key $data: $result //-->\n";
697 function iil_C_EnableCache(&$conn) {
698 $conn->do_cache = true;
701 function iil_C_DisableCache(&$conn) {
702 $conn->do_cache = false;
705 function iil_C_LoadCache(&$conn, $folder) {
706 if (!$conn->do_cache) {
710 $key = $folder.'.imap';
711 if (!is_array($conn->cache[$folder])) {
712 $conn->cache[$folder] = cache_read($conn->user, $conn->host, $key);
713 $conn->cache_dirty[$folder] = false;
717 function iil_C_ExpireCachedItems(&$conn, $folder, $message_set) {
719 if (!$conn->do_cache) {
720 return; //caching disabled
722 if (!is_array($conn->cache[$folder])) {
723 return; //cache not initialized|empty
725 if (count($conn->cache[$folder]) == 0) {
726 return; //cache not initialized|empty
729 $uids = iil_C_FetchHeaderIndex($conn, $folder, $message_set, 'UID');
731 if (is_array($uids)) {
732 //echo "<!-- unsetting: ".implode(",",$uids)." //-->\n";
733 while (list($n,$uid)=each($uids)) {
734 unset($conn->cache[$folder][$uid]);
735 //$conn->cache[$folder][$uid] = false;
738 $conn->cache_dirty[$folder] = true;
741 //print_r($conn->cache);
742 //echo "\n".'//-->'."\n";
744 echo "<!-- failed to get uids: $message_set //-->\n";
748 if ($num_removed>0) {
750 reset($conn->cache[$folder]);
751 while (list($uid,$item)=each($conn->cache[$folder])) {
752 if ($item) $new_cache[$uid] = $conn->cache[$folder][$uid];
754 $conn->cache[$folder] = $new_cache;
759 function iil_ExplodeQuotedString($delimiter, $string) {
760 $quotes = explode('"', $string);
761 while ( list($key, $val) = each($quotes)) {
762 if (($key % 2) == 1) {
763 $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
766 $string = implode('"', $quotes);
768 $result = explode($delimiter, $string);
769 while ( list($key, $val) = each($result) ) {
770 $result[$key] = str_replace('_!@!_', $delimiter, $result[$key]);
776 function iil_CheckForRecent($host, $user, $password, $mailbox) {
777 if (empty($mailbox)) {
781 $conn = iil_Connect($host, $user, $password, 'plain');
784 iil_PutLine($fp, "a002 EXAMINE \"".iil_Escape($mailbox)."\"");
786 $line=chop(iil_ReadLine($fp, 300));
787 $a=explode(' ', $line);
788 if (($a[0] == '*') && (strcasecmp($a[2], 'RECENT') == 0)) {
789 $result = (int) $a[1];
791 } while (!iil_StartsWith($a[0], 'a002', true));
793 iil_PutLine($fp, "a003 LOGOUT");
802 function iil_C_Select(&$conn, $mailbox) {
804 if (empty($mailbox)) {
807 if (strcmp($conn->selected, $mailbox) == 0) {
811 iil_C_LoadCache($conn, $mailbox);
813 if (iil_PutLine($conn->fp, "sel1 SELECT \"".iil_Escape($mailbox).'"')) {
815 $line = chop(iil_ReadLine($conn->fp, 300));
816 $a = explode(' ', $line);
817 if (count($a) == 3) {
818 if (strcasecmp($a[2], 'EXISTS') == 0) {
819 $conn->exists = (int) $a[1];
821 if (strcasecmp($a[2], 'RECENT') == 0) {
822 $conn->recent = (int) $a[1];
825 else if (preg_match('/\[?PERMANENTFLAGS\s+\(([^\)]+)\)\]/U', $line, $match)) {
826 $conn->permanentflags = explode(' ', $match[1]);
828 } while (!iil_StartsWith($line, 'sel1', true));
830 $a = explode(' ', $line);
832 if (strcasecmp($a[1], 'OK') == 0) {
833 $conn->selected = $mailbox;
840 function iil_C_CheckForRecent(&$conn, $mailbox) {
841 if (empty($mailbox)) {
845 iil_C_Select($conn, $mailbox);
846 if ($conn->selected == $mailbox) {
847 return $conn->recent;
852 function iil_C_CountMessages(&$conn, $mailbox, $refresh = false) {
854 $conn->selected = '';
857 iil_C_Select($conn, $mailbox);
858 if ($conn->selected == $mailbox) {
859 return $conn->exists;
864 function iil_SplitHeaderLine($string) {
865 $pos=strpos($string, ':');
867 $res[0] = substr($string, 0, $pos);
868 $res[1] = trim(substr($string, $pos+1));
874 function iil_StrToTime($str) {
875 $IMAP_MONTHS = $GLOBALS['IMAP_MONTHS'];
876 $IMAP_SERVER_TZ = $GLOBALS['IMAP_SERVER_TZ'];
879 $time1 = strtotime($str);
881 if ($time1 && $time1 != -1) {
882 return $time1-$IMAP_SERVER_TZ;
884 //echo '<!--'.$str.'//-->';
886 //replace double spaces with single space
888 $str = str_replace(' ', ' ', $str);
890 //strip off day of week
891 $pos = strpos($str, ' ');
892 if (!is_numeric(substr($str, 0, $pos))) {
893 $str = substr($str, $pos+1);
895 //explode, take good parts
896 $a = explode(' ', $str);
899 $month = $IMAP_MONTHS[$month_str];
904 $tz = substr($tz_str, 0, 3);
905 $ta = explode(':', $time);
906 $hour = (int)$ta[0]-(int)$tz;
907 $minute = (int)$ta[1];
908 $second = (int)$ta[2];
910 //make UNIX timestamp
911 $time2 = mktime($hour, $minute, $second, $month, $day, $year);
912 //echo '<!--'.$time1.' '.$time2.' //-->'."\n";
916 function iil_C_Sort(&$conn, $mailbox, $field, $add='', $is_uid=FALSE,
917 $encoding = 'US-ASCII') {
919 $field = strtoupper($field);
920 if ($field == 'INTERNALDATE') {
924 $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1,
925 'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1);
927 if (!$fields[$field]) {
931 /* Do "SELECT" command */
932 if (!iil_C_Select($conn, $mailbox)) {
936 $is_uid = $is_uid ? 'UID ' : '';
942 $command = 's ' . $is_uid . 'SORT (' . $field . ') ';
943 $command .= $encoding . ' ALL' . $add;
946 if (!iil_PutLineC($conn->fp, $command)) {
950 $line = chop(iil_ReadLine($conn->fp, 1024));
951 if (iil_StartsWith($line, '* SORT')) {
952 $data .= ($data ? ' ' : '') . substr($line, 7);
953 } else if (preg_match('/^[0-9 ]+$/', $line)) {
956 } while (!iil_StartsWith($line, 's ', true));
958 $result_code = iil_ParseResult($line);
960 if ($result_code != 0) {
961 $conn->error = 'iil_C_Sort: ' . $line . "\n";
965 $out = explode(' ',$data);
969 function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field,
971 global $IMAP_USE_INTERNAL_DATE;
977 if (empty($index_field)) {
978 $index_field = 'DATE';
980 $index_field = strtoupper($index_field);
982 list($from_idx, $to_idx) = explode(':', $message_set);
983 if (empty($message_set) || (isset($to_idx)
984 && (int)$from_idx > (int)$to_idx)) {
988 //$fields_a['DATE'] = ($IMAP_USE_INTERNAL_DATE?6:1);
989 $fields_a['DATE'] = 1;
990 $fields_a['INTERNALDATE'] = 6;
991 $fields_a['FROM'] = 1;
992 $fields_a['REPLY-TO'] = 1;
993 $fields_a['SENDER'] = 1;
995 $fields_a['SUBJECT'] = 1;
996 $fields_a['UID'] = 2;
997 $fields_a['SIZE'] = 2;
998 $fields_a['SEEN'] = 3;
999 $fields_a['RECENT'] = 4;
1000 $fields_a['DELETED'] = 5;
1002 $mode=$fields_a[$index_field];
1007 /* Do "SELECT" command */
1008 if (!iil_C_Select($conn, $mailbox)) {
1012 /* FETCH date,from,subject headers */
1014 $key = 'fhi' . ($c++);
1015 $request = $key . " FETCH $message_set (BODY.PEEK[HEADER.FIELDS ($index_field)])";
1016 if (!iil_PutLine($fp, $request)) {
1021 $line=chop(iil_ReadLine($fp, 200));
1022 $a=explode(' ', $line);
1023 if (($line[0] == '*') && ($a[2] == 'FETCH')
1024 && ($line[strlen($line)-1] != ')')) {
1027 $str=$line=chop(iil_ReadLine($fp, 300));
1029 while ($line[0] != ')') { //caution, this line works only in this particular case
1030 $line=chop(iil_ReadLine($fp, 300));
1031 if ($line[0] != ')') {
1032 if (ord($line[0]) <= 32) { //continuation from previous header line
1033 $str.= ' ' . trim($line);
1035 if ((ord($line[0]) > 32) || (strlen($line[0]) == 0)) {
1036 list($field, $string) = iil_SplitHeaderLine($str);
1037 if (strcasecmp($field, 'date') == 0) {
1038 $result[$id] = iil_StrToTime($string);
1040 $result[$id] = str_replace('"', '', $string);
1042 $result[$id] = strtoupper($result[$id]);
1051 $end_pos = strlen($line)-1;
1052 if (($line[0]=="*") && ($a[2]=="FETCH") && ($line[$end_pos]=="}")) {
1054 $pos = strrpos($line, "{")+1;
1055 $bytes = (int)substr($line, $pos, $end_pos-$pos);
1058 $line = iil_ReadLine($fp, 0);
1059 $received += strlen($line);
1060 $line = chop($line);
1062 if ($received>$bytes) {
1064 } else if (!$line) {
1068 list($field, $string) = explode(': ', $line);
1070 if (strcasecmp($field, 'date') == 0) {
1071 $result[$id] = iil_StrToTime($string);
1072 } else if ($index_field != 'DATE') {
1073 $result[$id]=strtoupper(str_replace('"', '', $string));
1075 } while ($line[0] != ')');
1077 //one line response, not expected so ignore
1080 } while (!iil_StartsWith($line, $key, true));
1082 }else if ($mode == 6) {
1084 $key = 'fhi' . ($c++);
1085 $request = $key . " FETCH $message_set (INTERNALDATE)";
1086 if (!iil_PutLine($fp, $request)) {
1090 $line=chop(iil_ReadLine($fp, 200));
1091 if ($line[0] == '*') {
1094 * "* 10 FETCH (INTERNALDATE "31-Jul-2002 09:18:02 -0500")"
1096 $paren_pos = strpos($line, '(');
1097 $foo = substr($line, 0, $paren_pos);
1098 $a = explode(' ', $foo);
1101 $open_pos = strpos($line, '"') + 1;
1102 $close_pos = strrpos($line, '"');
1103 if ($open_pos && $close_pos) {
1104 $len = $close_pos - $open_pos;
1105 $time_str = substr($line, $open_pos, $len);
1106 $result[$id] = strtotime($time_str);
1109 $a = explode(' ', $line);
1111 } while (!iil_StartsWith($a[0], $key, true));
1114 $field_name = 'FLAGS';
1115 } else if ($index_field == 'SIZE') {
1116 $field_name = 'RFC822.SIZE';
1118 $field_name = $index_field;
1121 /* FETCH uid, size, flags */
1122 $key = 'fhi' .($c++);
1123 $request = $key . " FETCH $message_set ($field_name)";
1125 if (!iil_PutLine($fp, $request)) {
1129 $line=chop(iil_ReadLine($fp, 200));
1130 $a = explode(' ', $line);
1131 if (($line[0] == '*') && ($a[2] == 'FETCH')) {
1132 $line = str_replace('(', '', $line);
1133 $line = str_replace(')', '', $line);
1134 $a = explode(' ', $line);
1138 if (isset($result[$id])) {
1139 continue; //if we already got the data, skip forward
1141 if ($a[3]!=$field_name) {
1142 continue; //make sure it's returning what we requested
1145 /* Caution, bad assumptions, next several lines */
1147 $result[$id] = $a[4];
1149 $haystack = strtoupper($line);
1150 $result[$id] = (strpos($haystack, $index_field) > 0 ? "F" : "N");
1153 } while (!iil_StartsWith($line, $key, true));
1156 //check number of elements...
1157 list($start_mid, $end_mid) = explode(':', $message_set);
1158 if (is_numeric($start_mid) && is_numeric($end_mid)) {
1159 //count how many we should have
1160 $should_have = $end_mid - $start_mid +1;
1162 //if we have less, try and fill in the "gaps"
1163 if (count($result) < $should_have) {
1164 for ($i=$start_mid; $i<=$end_mid; $i++) {
1165 if (!isset($result[$i])) {
1174 function iil_CompressMessageSet($message_set) {
1175 //given a comma delimited list of independent mid's,
1176 //compresses by grouping sequences together
1178 //if less than 255 bytes long, let's not bother
1179 if (strlen($message_set)<255) {
1180 return $message_set;
1183 //see if it's already been compress
1184 if (strpos($message_set, ':') !== false) {
1185 return $message_set;
1188 //separate, then sort
1189 $ids = explode(',', $message_set);
1193 $start = $prev = $ids[0];
1195 foreach ($ids as $id) {
1196 $incr = $id - $prev;
1197 if ($incr > 1) { //found a gap
1198 if ($start == $prev) {
1199 $result[] = $prev; //push single id
1201 $result[] = $start . ':' . $prev; //push sequence as start_id:end_id
1203 $start = $id; //start of new sequence
1208 //handle the last sequence/id
1209 if ($start==$prev) {
1212 $result[] = $start.':'.$prev;
1215 //return as comma separated string
1216 return implode(',', $result);
1219 function iil_C_UIDsToMIDs(&$conn, $mailbox, $uids) {
1220 if (!is_array($uids) || count($uids) == 0) {
1223 return iil_C_Search($conn, $mailbox, 'UID ' . implode(',', $uids));
1226 function iil_C_UIDToMID(&$conn, $mailbox, $uid) {
1227 $result = iil_C_UIDsToMIDs($conn, $mailbox, array($uid));
1228 if (count($result) == 1) {
1234 function iil_C_FetchUIDs(&$conn,$mailbox) {
1237 $num = iil_C_CountMessages($conn, $mailbox);
1241 $message_set = '1' . ($num>1?':' . $num:'');
1243 //if cache not enabled, just call iil_C_FetchHeaderIndex on 'UID' field
1244 if (!$conn->do_cache)
1245 return iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
1247 //otherwise, let's check cache first
1248 $key = $mailbox.'.uids';
1250 if ($conn->uid_cache) {
1251 $data = $conn->uid_cache;
1253 $data = cache_read($conn->user, $conn->host, $key);
1256 //was anything cached at all?
1257 if ($data === false) {
1261 //make sure number of messages were the same
1262 if ($cache_good > 0 && $data['n'] != $num) {
1266 //if everything's okay so far...
1267 if ($cache_good > 0) {
1268 //check UIDs of highest mid with current and cached
1269 $temp = iil_C_Search($conn, $mailbox, 'UID ' . $data['d'][$num]);
1270 if (!$temp || !is_array($temp) || $temp[0] != $num) {
1275 //if cached data's good, return it
1276 if ($cache_good > 0) {
1280 //otherwise, we need to fetch it
1281 $data = array('n' => $num, 'd' => array());
1282 $data['d'] = iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
1284 cache_write($conn->user, $conn->host, $key, $data);
1285 $conn->uid_cache = $data;
1289 function iil_SortThreadHeaders($headers, $index_a, $uids) {
1292 foreach ($index_a as $mid=>$foobar) {
1294 $result[$uid] = $headers[$uid];
1299 function iil_C_FetchThreadHeaders(&$conn, $mailbox, $message_set) {
1303 list($from_idx, $to_idx) = explode(':', $message_set);
1304 if (empty($message_set) || (isset($to_idx)
1305 && (int)$from_idx > (int)$to_idx)) {
1310 $uids = iil_C_FetchUIDs($conn, $mailbox);
1313 /* Get cached records where possible */
1314 if ($conn->do_cache) {
1315 $cached = cache_read($conn->user, $conn->host, $mailbox.'.thhd');
1316 if ($cached && is_array($uids) && count($uids)>0) {
1318 foreach ($uids as $id=>$uid) {
1319 if ($cached[$uid]) {
1320 $result[$uid] = $cached[$uid];
1321 $result[$uid]->id = $id;
1323 $needed_set .= ($needed_set ? ',' : '') . $id;
1327 $message_set = $needed_set;
1333 $message_set = iil_CompressMessageSet($message_set);
1335 echo "Still need: ".$message_set;
1338 /* if we're missing any, get them */
1340 /* FETCH date,from,subject headers */
1343 $request = $key . " FETCH $message_set ";
1344 $request .= "(BODY.PEEK[HEADER.FIELDS (SUBJECT MESSAGE-ID IN-REPLY-TO)])";
1345 $mid_to_id = array();
1346 if (!iil_PutLine($fp, $request)) {
1350 $line = chop(iil_ReadLine($fp, 1024));
1354 if (ereg('\{[0-9]+\}$', $line)) {
1355 $a = explode(' ', $line);
1358 $new_thhd = new iilThreadHeader;
1359 $new_thhd->id = $a[1];
1361 $line = chop(iil_ReadLine($fp, 1024), "\r\n");
1362 if (iil_StartsWithI($line, 'Message-ID:')
1363 || (iil_StartsWithI($line,'In-Reply-To:'))
1364 || (iil_StartsWithI($line,'SUBJECT:'))) {
1366 $pos = strpos($line, ':');
1367 $field_name = substr($line, 0, $pos);
1368 $field_val = substr($line, $pos+1);
1370 $new[strtoupper($field_name)] = trim($field_val);
1372 } else if (ereg('^[[:space:]]', $line)) {
1373 $new[strtoupper($field_name)] .= trim($line);
1375 } while ($line[0] != ')');
1377 $new_thhd->sbj = $new['SUBJECT'];
1378 $new_thhd->mid = substr($new['MESSAGE-ID'], 1, -1);
1379 $new_thhd->irt = substr($new['IN-REPLY-TO'], 1, -1);
1381 $result[$uids[$new_thhd->id]] = $new_thhd;
1383 } while (!iil_StartsWith($line, 'fh'));
1387 if (is_array($index_a)) {
1388 $result = iil_SortThreadHeaders($result, $index_a, $uids);
1391 /* write new set to cache */
1392 if ($conn->do_cache) {
1393 if (count($result)!=count($cached)) {
1394 cache_write($conn->user, $conn->host, $mailbox . '.thhd', $result);
1398 //echo 'iil_FetchThreadHeaders:'."\n";
1404 function iil_C_BuildThreads2(&$conn, $mailbox, $message_set, &$clock) {
1407 list($from_idx, $to_idx) = explode(':', $message_set);
1408 if (empty($message_set) || (isset($to_idx)
1409 && (int)$from_idx > (int)$to_idx)) {
1415 $root_mids = array();
1416 $sub_mids = array();
1418 $messages = array();
1422 $sbj_filter_pat = '[a-zA-Z]{2,3}(\[[0-9]*\])?:([[:space:]]*)';
1424 /* Do "SELECT" command */
1425 if (!iil_C_Select($conn, $mailbox)) {
1429 /* FETCH date,from,subject headers */
1430 $mid_to_id = array();
1431 $messages = array();
1432 $headers = iil_C_FetchThreadHeaders($conn, $mailbox, $message_set);
1434 $clock->register('fetched headers');
1441 /* go through header records */
1442 foreach ($headers as $header) {
1443 //$id = $header['i'];
1444 //$new = array('id'=>$id, 'MESSAGE-ID'=>$header['m'],
1445 // 'IN-REPLY-TO'=>$header['r'], 'SUBJECT'=>$header['s']);
1447 $new = array('id' => $id, 'MESSAGE-ID' => $header->mid,
1448 'IN-REPLY-TO' => $header->irt, 'SUBJECT' => $header->sbj);
1450 /* add to message-id -> mid lookup table */
1451 $mid_to_id[$new['MESSAGE-ID']] = $id;
1453 /* if no subject, use message-id */
1454 if (empty($new['SUBJECT'])) {
1455 $new['SUBJECT'] = $new['MESSAGE-ID'];
1458 /* if subject contains 'RE:' or has in-reply-to header, it's a reply */
1461 if (eregi($sbj_filter_pat, $new['SUBJECT'])) {
1464 if ($has_re||$new['IN-REPLY-TO']) {
1468 /* strip out 're:', 'fw:' etc */
1470 $sbj = ereg_replace($sbj_filter_pat, '', $new['SUBJECT']);
1472 $sbj = $new['SUBJECT'];
1474 $new['SUBJECT'] = $sbj_pre.$sbj;
1477 /* if subject not a known thread-root, add to list */
1479 echo $id . ' ' . $new['SUBJECT'] . "\t" . $new['MESSAGE-ID'] . "\n";
1481 $root_id = $roots[$sbj];
1483 if ($root_id && ($has_re || !$root_in_root[$root_id])) {
1485 echo "\tfound root: $root_id\n";
1487 $sub_mids[$new['MESSAGE-ID']] = $root_id;
1488 $result[$root_id][] = $id;
1489 } else if (!isset($roots[$sbj]) || (!$has_re && $root_in_root[$root_id])) {
1490 /* try to use In-Reply-To header to find root
1491 unless subject contains 'Re:' */
1492 if ($has_re&&$new['IN-REPLY-TO']) {
1494 echo "\tlooking: ".$new['IN-REPLY-TO']."\n";
1496 //reply to known message?
1497 $temp = $sub_mids[$new['IN-REPLY-TO']];
1500 //found it, root:=parent's root
1502 echo "\tfound parent: ".$new['SUBJECT']."\n";
1504 $result[$temp][] = $id;
1505 $sub_mids[$new['MESSAGE-ID']] = $temp;
1508 //if we can't find referenced parent, it's a "stray"
1509 $strays[$id] = $new['IN-REPLY-TO'];
1513 //add subject as root
1516 echo "\t added to root\n";
1519 $root_in_root[$id] = !$has_re;
1520 $sub_mids[$new['MESSAGE-ID']] = $id;
1521 $result[$id] = array($id);
1524 echo $new['MESSAGE-ID'] . "\t" . $sbj . "\n";
1529 //now that we've gone through all the messages,
1530 //go back and try and link up the stray threads
1531 if (count($strays) > 0) {
1532 foreach ($strays as $id=>$irt) {
1533 $root_id = $sub_mids[$irt];
1534 if (!$root_id || $root_id==$id) {
1537 $result[$root_id] = array_merge($result[$root_id],$result[$id]);
1538 unset($result[$id]);
1543 $clock->register('data prepped');
1553 function iil_SortThreads(&$tree, $index, $sort_order = 'ASC') {
1554 if (!is_array($tree) || !is_array($index)) {
1558 //create an id to position lookup table
1560 foreach ($index as $id=>$val) {
1566 //for each tree, set array key to position
1568 foreach ($tree as $id=>$node) {
1569 if (count($tree[$id])<=1) {
1570 //for "threads" with only one message, key is position of that message
1572 $itree[$n] = array($n=>$id);
1574 //for "threads" with multiple messages,
1577 foreach ($tree[$id] as $mid) {
1578 $new_a[$index[$mid]] = $mid; //create new sub-array mapping position to id
1579 $pos = $index[$mid];
1580 if ($pos&&$pos<$min) {
1581 $min = $index[$mid]; //find smallest position
1584 $n = $min; //smallest position of child is thread position
1586 //assign smallest position to root level key
1587 //set children array to one created above
1589 $itree[$n] = $new_a;
1593 //sort by key, this basically sorts all threads
1597 foreach ($itree as $k=>$node) {
1598 $out[$i] = $itree[$k];
1605 function iil_IndexThreads(&$tree) {
1606 /* creates array mapping mid to thread id */
1608 if (!is_array($tree)) {
1613 foreach ($tree as $pos=>$kids) {
1614 foreach ($kids as $kid) $t_index[$kid] = $pos;
1620 function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false)
1622 global $IMAP_USE_INTERNAL_DATE;
1627 list($from_idx, $to_idx) = explode(':', $message_set);
1628 if (empty($message_set) || (isset($to_idx)
1629 && (int)$from_idx > (int)$to_idx)) {
1633 /* Do "SELECT" command */
1634 if (!iil_C_Select($conn, $mailbox)) {
1635 $conn->error = "Couldn't select $mailbox";
1639 /* Get cached records where possible */
1640 if ($conn->do_cache) {
1641 $uids = iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, "UID");
1642 if (is_array($uids) && count($conn->cache[$mailbox]>0)) {
1644 while (list($id,$uid)=each($uids)) {
1645 if ($conn->cache[$mailbox][$uid]) {
1646 $result[$id] = $conn->cache[$mailbox][$uid];
1647 $result[$id]->id = $id;
1649 $needed_set.=($needed_set ? ',': '') . $id;
1652 //echo "<!-- iil_C_FetchHeader\nMessage Set: $message_set\nNeeded Set:$needed_set\n//-->\n";
1654 $message_set = iil_CompressMessageSet($needed_set);
1661 /* FETCH uid, size, flags and headers */
1663 $request = $key . ($uidfetch ? ' UID' : '') . " FETCH $message_set ";
1664 $request .= "(UID RFC822.SIZE FLAGS INTERNALDATE BODY.PEEK[HEADER.FIELDS ";
1665 $request .= "(DATE FROM TO SUBJECT REPLY-TO IN-REPLY-TO CC BCC ";
1666 $request .= "CONTENT-TRANSFER-ENCODING CONTENT-TYPE MESSAGE-ID ";
1667 $request .= "REFERENCES DISPOSITION-NOTIFICATION-TO X-PRIORITY)])";
1669 if (!iil_PutLine($fp, $request)) {
1673 $line = chop(iil_ReadLine($fp, 1024));
1674 $a = explode(' ', $line);
1675 if (($line[0] == '*') && ($a[2] == 'FETCH')) {
1678 $result[$id] = new iilBasicHeader;
1679 $result[$id]->id = $id;
1680 $result[$id]->subject = '';
1681 $result[$id]->messageID = 'mid:' . $id;
1685 * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen)
1686 INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODY[HEADER.FIELDS ...
1689 if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY\[HEADER/', $line, $matches)) {
1692 //swap parents with quotes, then explode
1693 $str = eregi_replace("[()]", "\"", $str);
1694 $a = iil_ExplodeQuotedString(' ', $str);
1696 //did we get the right number of replies?
1697 $parts_count = count($a);
1698 if ($parts_count>=8) {
1699 for ($i=0; $i<$parts_count; $i=$i+2) {
1700 if (strcasecmp($a[$i],'UID') == 0)
1701 $result[$id]->uid = $a[$i+1];
1702 else if (strcasecmp($a[$i],'RFC822.SIZE') == 0)
1703 $result[$id]->size = $a[$i+1];
1704 else if (strcasecmp($a[$i],'INTERNALDATE') == 0)
1705 $time_str = $a[$i+1];
1706 else if (strcasecmp($a[$i],'FLAGS') == 0)
1707 $flags_str = $a[$i+1];
1711 $flags_str = eregi_replace('[\\\"]', '', $flags_str);
1712 $flags_a = explode(' ', $flags_str);
1714 if (is_array($flags_a)) {
1716 while (list(,$val)=each($flags_a)) {
1717 if (strcasecmp($val,'Seen') == 0) {
1718 $result[$id]->seen = true;
1719 } else if (strcasecmp($val, 'Deleted') == 0) {
1720 $result[$id]->deleted=true;
1721 } else if (strcasecmp($val, 'Recent') == 0) {
1722 $result[$id]->recent = true;
1723 } else if (strcasecmp($val, 'Answered') == 0) {
1724 $result[$id]->answered = true;
1725 } else if (strcasecmp($val, '$Forwarded') == 0) {
1726 $result[$id]->forwarded = true;
1727 } else if (strcasecmp($val, 'Draft') == 0) {
1728 $result[$id]->is_draft = true;
1729 } else if (strcasecmp($val, '$MDNSent') == 0) {
1730 $result[$id]->mdn_sent = true;
1731 } else if (strcasecmp($val, 'Flagged') == 0) {
1732 $result[$id]->flagged = true;
1735 $result[$id]->flags = $flags_a;
1738 $time_str = str_replace('"', '', $time_str);
1740 // if time is gmt...
1741 $time_str = str_replace('GMT','+0000',$time_str);
1744 $time_str = substr($time_str, 0, -1);
1745 $time_zone_str = substr($time_str, -5); // extract timezone
1746 $time_str = substr($time_str, 0, -5); // remove timezone
1747 $time_zone = (float)substr($time_zone_str, 1, 2); // get first two digits
1749 if ($time_zone_str[3] != '0') {
1750 $time_zone += 0.5; //handle half hour offset
1752 if ($time_zone_str[0] == '-') {
1753 $time_zone = $time_zone * -1.0; //minus?
1756 //calculate timestamp
1757 $timestamp = strtotime($time_str); //return's server's time
1758 $timestamp -= $time_zone * 3600; //compensate for tz, get GMT
1760 $result[$id]->internaldate = $time_str;
1761 $result[$id]->timestamp = $timestamp;
1762 $result[$id]->date = $time_str;
1767 Start parsing headers. The problem is, some header "lines" take up multiple lines.
1768 So, we'll read ahead, and if the one we're reading now is a valid header, we'll
1769 process the previous line. Otherwise, we'll keep adding the strings until we come
1770 to the next valid header line.
1776 $line = chop(iil_ReadLine($fp, 300), "\r\n");
1778 if (ord($line[0])<=32) {
1779 $lines[$i] .= (empty($lines[$i])?'':"\n").trim($line);
1782 $lines[$i] = trim($line);
1785 The preg_match below works around communigate imap, which outputs " UID <number>)".
1786 Without this, the while statement continues on and gets the "FH0 OK completed" message.
1787 If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249.
1788 This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing
1789 If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin
1790 An alternative might be:
1791 if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break;
1792 however, unsure how well this would work with all imap clients.
1794 if (preg_match("/^\s*UID [0-9]+\)$/", $line)) {
1797 // patch from "Maksim Rubis" <siburny@hotmail.com>
1798 } while (trim($line[0]) != ')' && strncmp($line, $key, strlen($key)));
1800 if (strncmp($line, $key, strlen($key))) {
1801 //process header, fill iilBasicHeader obj.
1803 if (is_array($headers)) {
1805 while (list($k, $bar) = each($headers)) {
1810 // create array with header field:data
1811 while ( list($lines_key, $str) = each($lines) ) {
1812 list($field, $string) = iil_SplitHeaderLine($str);
1814 $field = strtolower($field);
1815 $string = ereg_replace("\n[[:space:]]*"," ",$string);
1819 if (!$IMAP_USE_INTERNAL_DATE) {
1820 $result[$id]->date = $string;
1821 $result[$id]->timestamp = iil_StrToTime($string);
1825 $result[$id]->from = $string;
1828 $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string);
1831 $result[$id]->subject = $string;
1834 $result[$id]->replyto = $string;
1837 $result[$id]->cc = $string;
1840 $result[$id]->bcc = $string;
1842 case 'content-transfer-encoding':
1843 $result[$id]->encoding = $string;
1845 case 'content-type':
1846 $ctype_parts = explode(";", $string);
1847 $result[$id]->ctype = array_shift($ctype_parts);
1848 foreach ($ctype_parts as $ctype_add) {
1849 if (preg_match('/charset="?([a-z0-9\-\.\_]+)"?/i',
1850 $ctype_add, $regs)) {
1851 $result[$id]->charset = $regs[1];
1856 $result[$id]->in_reply_to = ereg_replace("[\n<>]", '', $string);
1859 $result[$id]->references = $string;
1861 case 'return-receipt-to':
1862 case 'disposition-notification-to':
1863 case 'x-confirm-reading-to':
1864 $result[$id]->mdn_to = $string;
1867 $result[$id]->messageID = $string;
1870 if (preg_match('/^(\d+)/', $string, $matches))
1871 $result[$id]->priority = intval($matches[1]);
1876 if ($conn->do_cache) {
1877 $uid = $result[$id]->uid;
1878 $conn->cache[$mailbox][$uid] = $result[$id];
1879 $conn->cache_dirty[$mailbox] = true;
1882 $a = explode(' ', $line);
1885 } while (strcmp($a[0], $key) != 0);
1890 function iil_C_FetchHeader(&$conn, $mailbox, $id, $uidfetch=false) {
1892 $a = iil_C_FetchHeaders($conn, $mailbox, $id, $uidfetch);
1894 return array_shift($a);
1899 function iil_SortHeaders($a, $field, $flag) {
1900 if (empty($field)) {
1903 $field = strtolower($field);
1904 if ($field == 'date' || $field == 'internaldate') {
1905 $field = 'timestamp';
1911 $flag = strtoupper($flag);
1912 $stripArr = ($field=='subject') ? array('Re: ','Fwd: ','Fw: ','"') : array('"');
1918 First, we'll create an "index" array.
1919 Then, we'll use sort() on that array,
1920 and use that to sort the main array.
1923 // create "index" array
1926 while (list($key, $val)=each($a)) {
1928 if ($field == 'timestamp') {
1929 $data = @strtotime($val->date);
1930 if ($data == false) {
1931 $data = $val->timestamp;
1934 $data = $val->$field;
1935 if (is_string($data)) {
1936 $data=strtoupper(str_replace($stripArr, '', $data));
1944 if ($flag == 'ASC') {
1950 // form new array based on index
1953 while (list($key, $val)=each($index)) {
1954 $result[$key]=$a[$key];
1962 function iil_C_Expunge(&$conn, $mailbox) {
1964 if (iil_C_Select($conn, $mailbox)) {
1966 iil_PutLine($conn->fp, "exp1 EXPUNGE");
1968 $line=chop(iil_ReadLine($conn->fp, 100));
1969 if ($line[0] == '*') {
1972 } while (!iil_StartsWith($line, 'exp1', true));
1974 if (iil_ParseResult($line) == 0) {
1975 $conn->selected = ''; //state has changed, need to reselect
1976 //$conn->exists-=$c;
1979 $conn->error = $line;
1985 function iil_C_ModFlag(&$conn, $mailbox, $messages, $flag, $mod) {
1986 if ($mod != '+' && $mod != '-') {
1991 $flags = $GLOBALS['IMAP_FLAGS'];
1993 $flag = strtoupper($flag);
1994 $flag = $flags[$flag];
1996 if (iil_C_Select($conn, $mailbox)) {
1998 iil_PutLine($fp, "flg STORE $messages " . $mod . "FLAGS (" . $flag . ")");
2000 $line=chop(iil_ReadLine($fp, 100));
2001 if ($line[0] == '*') {
2004 } while (!iil_StartsWith($line, 'flg', true));
2006 if (iil_ParseResult($line) == 0) {
2007 iil_C_ExpireCachedItems($conn, $mailbox, $messages);
2010 $conn->error = $line;
2013 $conn->error = 'Select failed';
2017 function iil_C_Flag(&$conn, $mailbox, $messages, $flag) {
2018 return iil_C_ModFlag($conn, $mailbox, $messages, $flag, '+');
2021 function iil_C_Unflag(&$conn, $mailbox, $messages, $flag) {
2022 return iil_C_ModFlag($conn, $mailbox, $messages, $flag, '-');
2025 function iil_C_Delete(&$conn, $mailbox, $messages) {
2026 return iil_C_ModFlag($conn, $mailbox, $messages, 'DELETED', '+');
2029 function iil_C_Undelete(&$conn, $mailbox, $messages) {
2030 return iil_C_ModFlag($conn, $mailbox, $messages, 'DELETED', '-');
2033 function iil_C_Unseen(&$conn, $mailbox, $messages) {
2034 return iil_C_ModFlag($conn, $mailbox, $messages, 'SEEN', '-');
2037 function iil_C_Copy(&$conn, $messages, $from, $to) {
2040 if (empty($from) || empty($to)) {
2044 if (iil_C_Select($conn, $from)) {
2047 iil_PutLine($fp, "cpy1 COPY $messages \"".iil_Escape($to)."\"");
2048 $line=iil_ReadReply($fp);
2049 return iil_ParseResult($line);
2055 function iil_FormatSearchDate($month, $day, $year) {
2056 $month = (int) $month;
2057 $months = $GLOBALS['IMAP_MONTHS'];
2058 return $day . '-' . $months[$month] . '-' . $year;
2061 function iil_C_CountUnseen(&$conn, $folder) {
2062 $index = iil_C_Search($conn, $folder, 'ALL UNSEEN');
2063 if (is_array($index)) {
2064 if (($cnt = count($index)) && $index[0] != '') {
2071 function iil_C_UID2ID(&$conn, $folder, $uid) {
2073 $id_a = iil_C_Search($conn, $folder, "UID $uid");
2074 if (is_array($id_a) && count($id_a) == 1) {
2081 function iil_C_ID2UID(&$conn, $folder, $id) {
2087 if (iil_C_Select($conn, $folder)) {
2089 if (iil_PutLine($fp, "$key FETCH $id (UID)")) {
2091 $line=chop(iil_ReadLine($fp, 1024));
2092 if (eregi("^\* $id FETCH \(UID (.*)\)", $line, $r)) {
2095 } while (!preg_match("/^$key/", $line));
2101 function iil_C_Search(&$conn, $folder, $criteria) {
2103 if (iil_C_Select($conn, $folder)) {
2106 $query = 'srch1 SEARCH ' . chop($criteria);
2107 iil_PutLineC($fp, $query);
2109 $line=trim(iil_ReadLine($fp, 10000));
2110 if (eregi("^\* SEARCH", $line)) {
2111 $str = trim(substr($line, 8));
2112 $messages = explode(' ', $str);
2114 } while (!iil_StartsWith($line, 'srch1', true));
2116 $result_code = iil_ParseResult($line);
2117 if ($result_code == 0) {
2120 $conn->error = 'iil_C_Search: ' . $line . "\n";
2123 $conn->error = "iil_C_Search: Couldn't select \"$folder\"\n";
2127 function iil_C_Move(&$conn, $messages, $from, $to) {
2130 if (!$from || !$to) {
2133 $r = iil_C_Copy($conn, $messages, $from,$to);
2135 return iil_C_Delete($conn, $from, $messages);
2141 * Gets the delimiter, for example:
2146 * @return mixed A delimiter (string), or false.
2147 * @param object $conn The current connection.
2148 * @see iil_Connect()
2150 function iil_C_GetHierarchyDelimiter(&$conn) {
2154 if ($conn->delimiter) {
2155 return $conn->delimiter;
2157 if (!empty($my_prefs['delimiter'])) {
2158 return ($conn->delimiter = $my_prefs['delimiter']);
2164 //try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8)
2165 if (!iil_PutLine($fp, 'ghd LIST "" ""')) {
2170 $line=iil_ReadLine($fp, 500);
2171 if ($line[0] == '*') {
2172 $line = rtrim($line);
2173 $a=iil_ExplodeQuotedString(' ', $line);
2175 $delimiter = str_replace('"', '', $a[count($a)-2]);
2178 } while (!iil_StartsWith($line, 'ghd', true));
2180 if (strlen($delimiter)>0) {
2184 //if that fails, try namespace extension
2185 //try to fetch namespace data
2186 iil_PutLine($conn->fp, "ns1 NAMESPACE");
2188 $line = iil_ReadLine($conn->fp, 1024);
2189 if (iil_StartsWith($line, '* NAMESPACE')) {
2191 $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
2193 } while (!iil_StartsWith($line, 'ns1', true));
2195 if (!is_array($data)) {
2199 //extract user space data (opposed to global/shared space)
2200 $user_space_data = $data[0];
2201 if (!is_array($user_space_data)) {
2206 $first_userspace = $user_space_data[0];
2207 if (!is_array($first_userspace)) {
2212 $delimiter = $first_userspace[1];
2217 function iil_C_ListMailboxes(&$conn, $ref, $mailbox) {
2218 global $IGNORE_FOLDERS;
2220 $ignore = $IGNORE_FOLDERS[strtolower($conn->host)];
2224 if (empty($mailbox)) {
2228 if (empty($ref) && $conn->rootdir) {
2229 $ref = $conn->rootdir;
2233 if (!iil_PutLine($fp, "lmb LIST \"".$ref."\" \"".iil_Escape($mailbox)."\"")) {
2240 $line = iil_ReadLine($fp, 500);
2241 $line = iil_MultLine($fp, $line);
2243 $a = explode(' ', $line);
2244 if (($line[0] == '*') && ($a[1] == 'LIST')) {
2245 $line = rtrim($line);
2247 $a = iil_ExplodeQuotedString(' ', $line);
2248 // last string is folder name
2249 $folder = trim($a[count($a)-1], '"');
2251 if (empty($ignore) || (!empty($ignore)
2252 && !eregi($ignore, $folder))) {
2253 $folders[$i] = $folder;
2256 // second from last is delimiter
2257 $delim = trim($a[count($a)-2], '"');
2258 // is it a container?
2261 } while (!iil_StartsWith($line, 'lmb', true));
2263 if (is_array($folders)) {
2265 // if rootdir was specified, make sure it's the first element
2266 // some IMAP servers (i.e. Courier) won't return it
2267 if ($ref[strlen($ref)-1]==$delim)
2268 $ref = substr($ref, 0, strlen($ref)-1);
2269 if ($folders[0]!=$ref)
2270 array_unshift($folders, $ref);
2273 } else if (iil_ParseResult($line) == 0) {
2274 return array('INBOX');
2276 $conn->error = $line;
2281 function iil_C_ListSubscribed(&$conn, $ref, $mailbox) {
2282 global $IGNORE_FOLDERS;
2284 $ignore = $IGNORE_FOLDERS[strtolower($conn->host)];
2287 if (empty($mailbox)) {
2290 if (empty($ref) && $conn->rootdir) {
2291 $ref = $conn->rootdir;
2296 if (!iil_PutLine($fp, 'lsb LSUB "' . $ref . '" "' . iil_Escape($mailbox).'"')) {
2297 $conn->error = "Couldn't send LSUB command\n";
2305 $line = iil_ReadLine($fp, 500);
2306 $line = iil_MultLine($fp, $line);
2307 $a = explode(' ', $line);
2309 if (($line[0] == '*') && ($a[1] == 'LSUB' || $a[1] == 'LIST')) {
2310 $line = rtrim($line);
2313 $a = iil_ExplodeQuotedString(' ', $line);
2315 // last string is folder name
2316 //$folder = UTF7DecodeString(str_replace('"', '', $a[count($a)-1]));
2317 $folder = trim($a[count($a)-1], '"');
2319 if ((!in_array($folder, $folders)) && (empty($ignore)
2320 || (!empty($ignore) && !eregi($ignore, $folder)))) {
2321 $folders[$i] = $folder;
2324 // second from last is delimiter
2325 $delim = trim($a[count($a)-2], '"');
2327 // is it a container?
2330 } while (!iil_StartsWith($line, 'lsb', true));
2332 if (is_array($folders)) {
2334 // if rootdir was specified, make sure it's the first element
2335 // some IMAP servers (i.e. Courier) won't return it
2336 if ($ref[strlen($ref)-1]==$delim) {
2337 $ref = substr($ref, 0, strlen($ref)-1);
2339 if ($folders[0]!=$ref) {
2340 array_unshift($folders, $ref);
2345 $conn->error = $line;
2349 function iil_C_Subscribe(&$conn, $folder) {
2352 $query = 'sub1 SUBSCRIBE "' . iil_Escape($folder). '"';
2353 iil_PutLine($fp, $query);
2355 $line = trim(iil_ReadLine($fp, 10000));
2356 return iil_ParseResult($line);
2359 function iil_C_UnSubscribe(&$conn, $folder) {
2362 $query = 'usub1 UNSUBSCRIBE "' . iil_Escape($folder) . '"';
2363 iil_PutLine($fp, $query);
2365 $line = trim(iil_ReadLine($fp, 10000));
2366 return iil_ParseResult($line);
2369 function iil_C_FetchPartHeader(&$conn, $mailbox, $id, $part) {
2371 $part = empty($part) ? 'HEADER' : $part.'.MIME';
2373 return iil_C_HandlePartBody($conn, $mailbox, $id, $part, 1);
2376 function iil_C_HandlePartBody(&$conn, $mailbox, $id, $part='', $mode=1, $file=NULL) {
2378 1: return string (or write to $file pointer)
2380 3: base64 and print (or write to $file pointer)
2386 if (iil_C_Select($conn, $mailbox)) {
2387 $reply_key = '* ' . $id;
2390 $key = 'ftch' . ($c++) . ' ';
2391 $request = $key . "FETCH $id (BODY.PEEK[$part])";
2393 if (!iil_PutLine($fp, $request)) {
2397 // receive reply line
2399 $line = chop(iil_ReadLine($fp, 1000));
2400 $a = explode(' ', $line);
2401 } while ($a[2] != 'FETCH');
2402 $len = strlen($line);
2404 // handle empty "* X FETCH ()" response
2405 if ($line[$len-1] == ')' && $line[$len-2] != '(') {
2406 // one line response, get everything between first and last quotes
2407 if (substr($line, -4, 3) == 'NIL') {
2411 $from = strpos($line, '"') + 1;
2412 $to = strrpos($line, '"');
2414 $result = substr($line, $from, $len);
2419 } else if ($mode == 3) {
2421 fwrite($file, base64_decode($result));
2423 echo base64_decode($result);
2425 } else if ($line[$len-1] == '}') {
2426 //multi-line request, find sizes of content and receive that many bytes
2427 $from = strpos($line, '{') + 1;
2428 $to = strrpos($line, '}');
2430 $sizeStr = substr($line, $from, $len);
2431 $bytes = (int)$sizeStr;
2433 while ($bytes > 0) {
2434 $line = iil_ReadLine($fp, 1024);
2435 $len = strlen($line);
2437 if ($len > $bytes) {
2438 $line = substr($line, 0, $bytes);
2440 $bytes -= strlen($line);
2444 fwrite($file, rtrim($line, "\t\r\n\0\x0B") . "\n");
2446 $result .= rtrim($line, "\t\r\n\0\x0B") . "\n";
2447 } else if ($mode == 2) {
2448 echo rtrim($line, "\t\r\n\0\x0B") . "\n";
2449 } else if ($mode == 3) {
2451 fwrite($file, base64_decode($line));
2453 echo base64_decode($line);
2457 // read in anything up until last line
2459 $line = iil_ReadLine($fp, 1024);
2460 } while (!iil_StartsWith($line, $key, true));
2462 if ($mode == 3 && $file) {
2467 $result = rtrim($result, "\t\r\n\0\x0B");
2469 fwrite($file, $result);
2472 return $result; // substr($result, 0, strlen($result)-1);
2477 echo 'Select failed.';
2482 fwrite($file, $result);
2491 function iil_C_FetchPartBody(&$conn, $mailbox, $id, $part, $file=NULL) {
2492 return iil_C_HandlePartBody($conn, $mailbox, $id, $part, 1, $file);
2495 function iil_C_PrintPartBody(&$conn, $mailbox, $id, $part) {
2496 iil_C_HandlePartBody($conn, $mailbox, $id, $part, 2);
2499 function iil_C_PrintBase64Body(&$conn, $mailbox, $id, $part) {
2500 iil_C_HandlePartBody($conn, $mailbox, $id, $part, 3);
2503 function iil_C_CreateFolder(&$conn, $folder) {
2505 if (iil_PutLine($fp, 'c CREATE "' . iil_Escape($folder) . '"')) {
2507 $line=iil_ReadLine($fp, 300);
2508 } while ($line[0] != 'c');
2509 $conn->error = $line;
2510 return (iil_ParseResult($line) == 0);
2515 function iil_C_RenameFolder(&$conn, $from, $to) {
2517 if (iil_PutLine($fp, 'r RENAME "' . iil_Escape($from) . '" "' . iil_Escape($to) . '"')) {
2519 $line = iil_ReadLine($fp, 300);
2520 } while ($line[0] != 'r');
2521 return (iil_ParseResult($line) == 0);
2526 function iil_C_DeleteFolder(&$conn, $folder) {
2528 if (iil_PutLine($fp, 'd DELETE "' . iil_Escape($folder). '"')) {
2530 $line=iil_ReadLine($fp, 300);
2531 } while ($line[0] != 'd');
2532 return (iil_ParseResult($line) == 0);
2534 $conn->error = "Couldn't send command\n";
2538 function iil_C_Append(&$conn, $folder, &$message) {
2544 $message = str_replace("\r", '', $message);
2545 $message = str_replace("\n", "\r\n", $message);
2547 $len = strlen($message);
2552 $request = 'A APPEND "' . iil_Escape($folder) .'" (\\Seen) {' . $len . '}';
2554 if (iil_PutLine($fp, $request)) {
2555 $line=iil_ReadLine($fp, 100);
2556 $sent = fwrite($fp, $message."\r\n");
2558 $line=iil_ReadLine($fp, 1000);
2559 } while ($line[0] != 'A');
2561 $result = (iil_ParseResult($line) == 0);
2563 $conn->error .= $line . "\n";
2568 $conn->error .= "Couldn't send command \"$request\"\n";
2572 function iil_C_AppendFromFile(&$conn, $folder, $path) {
2579 if (file_exists(realpath($path))) {
2580 $in_fp = fopen($path, 'r');
2583 $conn->error .= "Couldn't open $path for reading\n";
2588 $len = filesize($path);
2593 //send APPEND command
2594 $request = 'A APPEND "' . iil_Escape($folder) . '" (\\Seen) {' . $len . '}';
2596 if (iil_PutLine($fp, $request)) {
2597 $line = iil_ReadLine($fp, 100);
2600 while (!feof($in_fp)) {
2601 $buffer = fgets($in_fp, 4096);
2602 $bytes_sent += strlen($buffer);
2603 iil_PutLine($fp, $buffer, false);
2607 iil_PutLine($fp, '');
2611 $line = iil_ReadLine($fp, 1000);
2612 } while ($line[0] != 'A');
2614 $result = (iil_ParseResult($line) == 0);
2616 $conn->error .= $line . "\n";
2622 $conn->error .= "Couldn't send command \"$request\"\n";
2626 function iil_C_FetchStructureString(&$conn, $folder, $id) {
2630 if (iil_C_Select($conn, $folder)) {
2633 if (iil_PutLine($fp, "$key FETCH $id (BODYSTRUCTURE)")) {
2635 $line = iil_ReadLine($fp, 5000);
2636 $line = iil_MultLine($fp, $line);
2637 list(, $index, $cmd, $rest) = explode(' ', $line);
2638 if ($cmd != 'FETCH' || $index == $id || preg_match("/^$key/", $line))
2640 } while (!preg_match("/^$key/", $line));
2642 $result = trim(substr($result, strpos($result, 'BODYSTRUCTURE')+13, -(strlen($result)-strrpos($result, $key)+1)));
2648 function iil_C_PrintSource(&$conn, $folder, $id, $part) {
2649 $header = iil_C_FetchPartHeader($conn, $folder, $id, $part);
2650 //echo str_replace("\r", '', $header);
2652 echo iil_C_PrintPartBody($conn, $folder, $id, $part);
2655 function iil_C_GetQuota(&$conn) {
2657 * GETQUOTAROOT "INBOX"
2658 * QUOTAROOT INBOX user/rchijiiwa1
2659 * QUOTA user/rchijiiwa1 (STORAGE 654 9765)
2664 $quota_lines = array();
2666 // get line(s) containing quota info
2667 if (iil_PutLine($fp, 'QUOT1 GETQUOTAROOT "INBOX"')) {
2669 $line=chop(iil_ReadLine($fp, 5000));
2670 if (iil_StartsWith($line, '* QUOTA ')) {
2671 $quota_lines[] = $line;
2673 } while (!iil_StartsWith($line, 'QUOT1', true));
2676 // return false if not found, parse if found
2677 $min_free = PHP_INT_MAX;
2678 foreach ($quota_lines as $key => $quota_line) {
2679 $quota_line = eregi_replace('[()]', '', $quota_line);
2680 $parts = explode(' ', $quota_line);
2681 $storage_part = array_search('STORAGE', $parts);
2683 if (!$storage_part) continue;
2685 $used = intval($parts[$storage_part+1]);
2686 $total = intval($parts[$storage_part+2]);
2687 $free = $total - $used;
2689 // return lowest available space from all quotas
2690 if ($free < $min_free) {
2692 $result['used'] = $used;
2693 $result['total'] = $total;
2694 $result['percent'] = min(100, round(($used/max(1,$total))*100));
2695 $result['free'] = 100 - $result['percent'];
2701 function iil_C_ClearFolder(&$conn, $folder) {
2702 $num_in_trash = iil_C_CountMessages($conn, $folder);
2703 if ($num_in_trash > 0) {
2704 iil_C_Delete($conn, $folder, '1:' . $num_in_trash);
2706 return (iil_C_Expunge($conn, $folder) >= 0);