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 - RFC3501 [7.1] don't call CAPABILITY if was returned in server
56 optional resposne in iil_Connect()
57 - trim(chop()) replaced by trim()
58 - added iil_Escape() with support for " and \ in folder names
59 - support \ character in username in iil_C_Login()
60 - fixed iil_MultLine(): use iil_ReadBytes() instead of iil_ReadLine()
61 - fixed iil_C_FetchStructureString() to handle many literal strings in response
62 - removed hardcoded data size in iil_ReadLine()
63 - added iil_PutLine() wrapper for fputs()
64 - code cleanup and identation fixes
66 ********************************************************/
69 * @todo Possibly clean up more CS.
70 * @todo Try to replace most double-quotes with single-quotes.
71 * @todo Split this file into smaller files.
72 * @todo Refactor code.
73 * @todo Replace echo-debugging (make it adhere to config setting and log)
76 // changed path to work within roundcube webmail
77 include_once 'lib/icl_commons.inc';
80 if (!isset($IMAP_USE_HEADER_DATE) || !$IMAP_USE_HEADER_DATE) {
81 $IMAP_USE_INTERNAL_DATE = true;
85 * @todo Maybe use date() to generate this.
87 $GLOBALS['IMAP_MONTHS'] = array("Jan" => 1, "Feb" => 2, "Mar" => 3, "Apr" => 4,
88 "May" => 5, "Jun" => 6, "Jul" => 7, "Aug" => 8, "Sep" => 9, "Oct" => 10,
89 "Nov" => 11, "Dec" => 12);
91 $GLOBALS['IMAP_SERVER_TZ'] = date('Z');
98 * @todo Change class vars to public/private
115 var $capability = array();
119 * @todo Change class vars to public/private
144 var $mdn_sent = false;
145 var $is_reply = false;
147 var $deleted = false;
149 var $answered = false;
154 * @todo Change class vars to public/private
156 class iilThreadHeader
164 function iil_xor($string, $string2) {
166 $size = strlen($string);
167 for ($i=0; $i<$size; $i++) {
168 $result .= chr(ord($string[$i]) ^ ord($string2[$i]));
173 function iil_PutLine($fp, $string, $endln=true) {
174 // console('C: '. $string);
175 return fputs($fp, $string . ($endln ? "\r\n" : ''));
178 function iil_ReadLine($fp, $size) {
190 $buffer = fgets($fp, $size);
191 if ($buffer === false) {
194 // console('S: '. chop($buffer));
196 } while ($buffer[strlen($buffer)-1] != "\n");
201 function iil_MultLine($fp, $line) {
203 if (ereg('\{[0-9]+\}$', $line)) {
206 preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a);
208 while (strlen($out) < $bytes) {
209 $line = iil_ReadBytes($fp, $bytes);
212 $line = $a[1][0] . "\"$out\"";
213 // console('[...] '. $out);
218 function iil_ReadBytes($fp, $bytes) {
222 $data .= fread($fp, $bytes-$len);
223 if ($len == strlen($data)) {
224 break; //nothing was read -> exit to avoid apache lockups
226 $len = strlen($data);
227 } while ($len < $bytes);
232 function iil_ReadReply($fp) {
234 $line = trim(iil_ReadLine($fp, 1024));
235 } while ($line[0] == '*');
240 function iil_ParseResult($string) {
241 $a=explode(' ', $string);
243 if (strcasecmp($a[1], 'OK') == 0) {
245 } else if (strcasecmp($a[1], 'NO') == 0) {
247 } else if (strcasecmp($a[1], 'BAD') == 0) {
254 // check if $string starts with $match
255 function iil_StartsWith($string, $match) {
256 $len = strlen($match);
260 if (strncmp($string, $match, $len) == 0) {
266 function iil_StartsWithI($string, $match) {
267 $len = strlen($match);
271 if (strncasecmp($string, $match, $len) == 0) {
277 function iil_Escape($string)
279 return strtr($string, array('"'=>'\\"', '\\' => '\\\\'));
282 function iil_C_Authenticate(&$conn, $user, $pass, $encChallenge) {
287 // initialize ipad, opad
288 for ($i=0;$i<64;$i++) {
293 // pad $pass so it's 64 bytes
294 $padLen = 64 - strlen($pass);
295 for ($i=0;$i<$padLen;$i++) {
300 $hash = iil_xor($pass,$opad);
301 $hash .= pack("H*", md5(iil_xor($pass, $ipad) . base64_decode($encChallenge)));
305 $reply = base64_encode('"' . $user . '" "' . $hash . '"');
307 // send result, get reply
308 iil_PutLine($conn->fp, $reply);
309 $line = iil_ReadLine($conn->fp, 1024);
312 if (iil_ParseResult($line) == 0) {
317 $conn->error .= 'Authentication for ' . $user . ' failed (AUTH): "';
318 $conn->error .= htmlspecialchars($line) . '"';
319 $conn->errorNum = -2;
323 function iil_C_Login(&$conn, $user, $password) {
325 iil_PutLine($conn->fp, 'a001 LOGIN "'.iil_Escape($user).'" "'.iil_Escape($password).'"');
328 $line = iil_ReadReply($conn->fp);
329 if ($line === false) {
332 } while (!iil_StartsWith($line, "a001 "));
333 $a = explode(' ', $line);
334 if (strcmp($a[1], 'OK') == 0) {
343 $conn->error .= 'Authentication for ' . $user . ' failed (LOGIN): "';
344 $conn->error .= htmlspecialchars($line)."\"";
345 $conn->errorNum = -2;
350 function iil_ParseNamespace2($str, &$i, $len=0, $l) {
352 $str = str_replace('NIL', '()', $str);
360 for ($i;$i<$len;$i++) {
361 $c = (string)$str[$i];
362 if ($c == '(' && !$in_quotes) {
364 $data[$elem] = iil_ParseNamespace2($str, $i, $len, $l++);
366 } else if ($c == ')' && !$in_quotes) {
368 } else if ($c == '\\') {
371 $data[$elem] .= $c.$str[$i];
373 } else if ($c == '"') {
374 $in_quotes = !$in_quotes;
378 } else if ($in_quotes) {
385 function iil_C_NameSpace(&$conn) {
388 if (!in_array('NAMESPACE', $conn->capability)) {
392 if ($my_prefs["rootdir"]) {
396 iil_PutLine($conn->fp, "ns1 NAMESPACE");
398 $line = iil_ReadLine($conn->fp, 1024);
399 if (iil_StartsWith($line, '* NAMESPACE')) {
401 $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
403 } while (!iil_StartsWith($line, "ns1"));
405 if (!is_array($data)) {
409 $user_space_data = $data[0];
410 if (!is_array($user_space_data)) {
414 $first_userspace = $user_space_data[0];
415 if (count($first_userspace)!=2) {
419 $conn->rootdir = $first_userspace[0];
420 $conn->delimiter = $first_userspace[1];
421 $my_prefs["rootdir"] = substr($conn->rootdir, 0, -1);
426 function iil_Connect($host, $user, $password) {
427 global $iil_error, $iil_errornum;
428 global $ICL_SSL, $ICL_PORT;
429 global $IMAP_NO_CACHE;
430 global $my_prefs, $IMAP_USE_INTERNAL_DATE;
436 // $user = stripslashes($user);
437 // $password = stripslashes($password);
440 $auth_method = 'plain';
441 if (func_num_args() >= 4) {
442 $auth_array = func_get_arg(3);
443 if (is_array($auth_array)) {
444 $auth_method = $auth_array['imap'];
446 if (empty($auth_method)) {
447 $auth_method = "plain";
450 $message = "INITIAL: $auth_method\n";
454 //initialize connection
455 $conn = new iilConnection;
458 $conn->selected = '';
461 $conn->cache = array();
462 $conn->do_cache = (function_exists("cache_write")&&!$IMAP_NO_CACHE);
463 $conn->cache_dirty = array();
465 if ($my_prefs['sort_field'] == 'INTERNALDATE') {
466 $IMAP_USE_INTERNAL_DATE = true;
467 } else if ($my_prefs['sort_field'] == 'DATE') {
468 $IMAP_USE_INTERNAL_DATE = false;
470 //echo '<!-- conn sort_field: '.$my_prefs['sort_field'].' //-->';
474 $iil_error .= "Invalid host\n";
477 $iil_error .= "Invalid user\n";
479 if (empty($password)) {
480 $iil_error .= "Invalid password\n";
482 if (!empty($iil_error)) {
491 $host = $ICL_SSL . '://' . $host;
494 //open socket connection
495 $conn->fp = fsockopen($host, $ICL_PORT, $errno, $errstr, 10);
497 $iil_error = "Could not connect to $host at port $ICL_PORT: $errstr";
502 $iil_error .= "Socket connection established\r\n";
503 $line = iil_ReadLine($conn->fp, 1024);
505 // RFC3501 [7.1] optional CAPABILITY response
506 // commented out, because it's not working always as should
507 // if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
508 // $conn->capability = explode(' ', $matches[1]);
510 iil_PutLine($conn->fp, "cp01 CAPABILITY");
512 $line = trim(iil_ReadLine($conn->fp, 1024));
514 $conn->message .= "$line\n";
516 $a = explode(' ', $line);
517 if ($line[0] == '*') {
518 while (list($k, $w) = each($a)) {
519 if ($w != '*' && $w != 'CAPABILITY')
520 $conn->capability[] = $w;
523 } while ($a[0] != 'cp01');
526 if (strcasecmp($auth_method, "check") == 0) {
527 //check for supported auth methods
529 //default to plain text auth
530 $auth_method = 'plain';
533 foreach ($conn->capability as $c)
534 if (strcasecmp($c, 'AUTH=CRAM_MD5') == 0 ||
535 strcasecmp($c, 'AUTH=CRAM-MD5') == 0) {
536 $auth_method = 'auth';
541 if (strcasecmp($auth_method, 'auth') == 0) {
542 $conn->message .= "Trying CRAM-MD5\n";
544 //do CRAM-MD5 authentication
545 iil_PutLine($conn->fp, "a000 AUTHENTICATE CRAM-MD5");
546 $line = trim(iil_ReadLine($conn->fp, 1024));
548 $conn->message .= "$line\n";
550 if ($line[0] == '+') {
551 $conn->message .= 'Got challenge: ' . htmlspecialchars($line) . "\n";
553 //got a challenge string, try CRAM-5
554 $result = iil_C_Authenticate($conn, $user, $password, substr($line,2));
556 $conn->message .= "Tried CRAM-MD5: $result \n";
558 $conn->message .='No challenge ('.htmlspecialchars($line)."), try plain\n";
563 if ((!$result)||(strcasecmp($auth, "plain") == 0)) {
565 $result = iil_C_Login($conn, $user, $password);
566 $conn->message.="Tried PLAIN: $result \n";
569 $conn->message .= $auth;
572 iil_C_Namespace($conn);
575 $iil_error = $conn->error;
576 $iil_errornum = $conn->errorNum;
581 function iil_Close(&$conn) {
582 iil_C_WriteCache($conn);
583 if (iil_PutLine($conn->fp, "I LOGOUT")) {
584 fgets($conn->fp, 1024);
590 function iil_ClearCache($user, $host) {
593 function iil_C_WriteCache(&$conn) {
594 //echo "<!-- doing iil_C_WriteCache //-->\n";
595 if (!$conn->do_cache) return false;
597 if (is_array($conn->cache)) {
598 while (list($folder,$data)=each($conn->cache)) {
599 if ($folder && is_array($data) && $conn->cache_dirty[$folder]) {
600 $key = $folder.".imap";
601 $result = cache_write($conn->user, $conn->host, $key, $data, true);
602 //echo "<!-- writing $key $data: $result //-->\n";
608 function iil_C_EnableCache(&$conn) {
609 $conn->do_cache = true;
612 function iil_C_DisableCache(&$conn) {
613 $conn->do_cache = false;
616 function iil_C_LoadCache(&$conn, $folder) {
617 if (!$conn->do_cache) {
621 $key = $folder.'.imap';
622 if (!is_array($conn->cache[$folder])) {
623 $conn->cache[$folder] = cache_read($conn->user, $conn->host, $key);
624 $conn->cache_dirty[$folder] = false;
628 function iil_C_ExpireCachedItems(&$conn, $folder, $message_set) {
630 if (!$conn->do_cache) {
631 return; //caching disabled
633 if (!is_array($conn->cache[$folder])) {
634 return; //cache not initialized|empty
636 if (count($conn->cache[$folder]) == 0) {
637 return; //cache not initialized|empty
640 $uids = iil_C_FetchHeaderIndex($conn, $folder, $message_set, 'UID');
642 if (is_array($uids)) {
643 //echo "<!-- unsetting: ".implode(",",$uids)." //-->\n";
644 while (list($n,$uid)=each($uids)) {
645 unset($conn->cache[$folder][$uid]);
646 //$conn->cache[$folder][$uid] = false;
649 $conn->cache_dirty[$folder] = true;
652 //print_r($conn->cache);
653 //echo "\n".'//-->'."\n";
655 echo "<!-- failed to get uids: $message_set //-->\n";
659 if ($num_removed>0) {
661 reset($conn->cache[$folder]);
662 while (list($uid,$item)=each($conn->cache[$folder])) {
663 if ($item) $new_cache[$uid] = $conn->cache[$folder][$uid];
665 $conn->cache[$folder] = $new_cache;
670 function iil_ExplodeQuotedString($delimiter, $string) {
671 $quotes=explode('"', $string);
672 while ( list($key, $val) = each($quotes)) {
673 if (($key % 2) == 1) {
674 $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
677 $string=implode('"', $quotes);
679 $result=explode($delimiter, $string);
680 while ( list($key, $val) = each($result) ) {
681 $result[$key] = str_replace('_!@!_', $delimiter, $result[$key]);
687 function iil_CheckForRecent($host, $user, $password, $mailbox) {
688 if (empty($mailbox)) {
692 $conn = iil_Connect($host, $user, $password, 'plain');
695 iil_PutLine($fp, "a002 EXAMINE \"".iil_Escape($mailbox)."\"");
697 $line=chop(iil_ReadLine($fp, 300));
698 $a=explode(' ', $line);
699 if (($a[0] == '*') && (strcasecmp($a[2], 'RECENT') == 0)) {
700 $result = (int) $a[1];
702 } while (!iil_StartsWith($a[0], 'a002'));
704 iil_PutLine($fp, "a003 LOGOUT");
713 function iil_C_Select(&$conn, $mailbox) {
715 if (empty($mailbox)) {
718 if (strcmp($conn->selected, $mailbox) == 0) {
722 iil_C_LoadCache($conn, $mailbox);
724 if (iil_PutLine($conn->fp, "sel1 SELECT \"".iil_Escape($mailbox).'"')) {
726 $line = chop(iil_ReadLine($conn->fp, 300));
727 $a = explode(' ', $line);
728 if (count($a) == 3) {
729 if (strcasecmp($a[2], 'EXISTS') == 0) {
730 $conn->exists = (int) $a[1];
732 if (strcasecmp($a[2], 'RECENT') == 0) {
733 $conn->recent = (int) $a[1];
736 } while (!iil_StartsWith($line, 'sel1'));
738 $a = explode(' ', $line);
740 if (strcasecmp($a[1], 'OK') == 0) {
741 $conn->selected = $mailbox;
748 function iil_C_CheckForRecent(&$conn, $mailbox) {
749 if (empty($mailbox)) {
753 iil_C_Select($conn, $mailbox);
754 if ($conn->selected == $mailbox) {
755 return $conn->recent;
760 function iil_C_CountMessages(&$conn, $mailbox, $refresh = false) {
765 iil_C_Select($conn, $mailbox);
766 if ($conn->selected == $mailbox) {
767 return $conn->exists;
772 function iil_SplitHeaderLine($string) {
773 $pos=strpos($string, ':');
775 $res[0] = substr($string, 0, $pos);
776 $res[1] = trim(substr($string, $pos+1));
782 function iil_StrToTime($str) {
783 $IMAP_MONTHS = $GLOBALS['IMAP_MONTHS'];
784 $IMAP_SERVER_TZ = $GLOBALS['IMAP_SERVER_TR'];
787 $time1 = strtotime($str);
789 if ($time1 && $time1 != -1) {
790 return $time1-$IMAP_SERVER_TZ;
792 //echo '<!--'.$str.'//-->';
794 //replace double spaces with single space
796 $str = str_replace(' ', ' ', $str);
798 //strip off day of week
799 $pos = strpos($str, ' ');
800 if (!is_numeric(substr($str, 0, $pos))) {
801 $str = substr($str, $pos+1);
803 //explode, take good parts
804 $a = explode(' ', $str);
807 $month = $IMAP_MONTHS[$month_str];
812 $tz = substr($tz_str, 0, 3);
813 $ta = explode(':', $time);
814 $hour = (int)$ta[0]-(int)$tz;
815 $minute = (int)$ta[1];
816 $second = (int)$ta[2];
818 //make UNIX timestamp
819 $time2 = mktime($hour, $minute, $second, $month, $day, $year);
820 //echo '<!--'.$time1.' '.$time2.' //-->'."\n";
824 function iil_C_Sort(&$conn, $mailbox, $field, $add='', $is_uid=FALSE,
825 $encoding = 'US-ASCII') {
826 /* Do "SELECT" command */
827 if (!iil_C_Select($conn, $mailbox)) {
830 $field = strtoupper($field);
831 if ($field == 'INTERNALDATE') {
835 $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1,
836 'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1);
838 if (!$fields[$field]) {
842 $is_uid = $is_uid ? 'UID ' : '';
849 $command = 's ' . $is_uid . 'SORT (' . $field . ') ';
850 $command .= $encoding . ' ALL' . $add;
853 if (!iil_PutLine($fp, $command)) {
857 $line = chop(iil_ReadLine($fp, 1024));
858 if (iil_StartsWith($line, '* SORT')) {
859 $data .= ($data?' ':'') . substr($line, 7);
861 } while ($line[0]!='s');
864 $conn->error = $line;
868 $out = explode(' ',$data);
872 function iil_C_FetchHeaderIndex(&$conn, $mailbox, $message_set, $index_field,
874 global $IMAP_USE_INTERNAL_DATE;
880 if (empty($index_field)) {
881 $index_field = 'DATE';
883 $index_field = strtoupper($index_field);
885 list($from_idx, $to_idx) = explode(':', $message_set);
886 if (empty($message_set) || (isset($to_idx)
887 && (int)$from_idx > (int)$to_idx)) {
891 //$fields_a['DATE'] = ($IMAP_USE_INTERNAL_DATE?6:1);
892 $fields_a['DATE'] = 1;
893 $fields_a['INTERNALDATE'] = 6;
894 $fields_a['FROM'] = 1;
895 $fields_a['REPLY-TO'] = 1;
896 $fields_a['SENDER'] = 1;
898 $fields_a['SUBJECT'] = 1;
899 $fields_a['UID'] = 2;
900 $fields_a['SIZE'] = 2;
901 $fields_a['SEEN'] = 3;
902 $fields_a['RECENT'] = 4;
903 $fields_a['DELETED'] = 5;
905 $mode=$fields_a[$index_field];
910 /* Do "SELECT" command */
911 if (!iil_C_Select($conn, $mailbox)) {
915 /* FETCH date,from,subject headers */
917 $key = 'fhi' . ($c++);
918 $request = $key . " FETCH $message_set (BODY.PEEK[HEADER.FIELDS ($index_field)])";
919 if (!iil_PutLine($fp, $request)) {
924 $line=chop(iil_ReadLine($fp, 200));
925 $a=explode(' ', $line);
926 if (($line[0] == '*') && ($a[2] == 'FETCH')
927 && ($line[strlen($line)-1] != ')')) {
930 $str=$line=chop(iil_ReadLine($fp, 300));
932 while ($line[0] != ')') { //caution, this line works only in this particular case
933 $line=chop(iil_ReadLine($fp, 300));
934 if ($line[0] != ')') {
935 if (ord($line[0]) <= 32) { //continuation from previous header line
936 $str.= ' ' . trim($line);
938 if ((ord($line[0]) > 32) || (strlen($line[0]) == 0)) {
939 list($field, $string) = iil_SplitHeaderLine($str);
940 if (strcasecmp($field, 'date') == 0) {
941 $result[$id] = iil_StrToTime($string);
943 $result[$id] = str_replace('"', '', $string);
945 $result[$id] = strtoupper($result[$id]);
954 $end_pos = strlen($line)-1;
955 if (($line[0]=="*") && ($a[2]=="FETCH") && ($line[$end_pos]=="}")) {
957 $pos = strrpos($line, "{")+1;
958 $bytes = (int)substr($line, $pos, $end_pos-$pos);
961 $line = iil_ReadLine($fp, 0);
962 $received += strlen($line);
965 if ($received>$bytes) {
971 list($field, $string) = explode(': ', $line);
973 if (strcasecmp($field, 'date') == 0) {
974 $result[$id] = iil_StrToTime($string);
975 } else if ($index_field != 'DATE') {
976 $result[$id]=strtoupper(str_replace('"', '', $string));
978 } while ($line[0] != ')');
980 //one line response, not expected so ignore
983 } while (!iil_StartsWith($line, $key));
985 }else if ($mode == 6) {
987 $key = 'fhi' . ($c++);
988 $request = $key . " FETCH $message_set (INTERNALDATE)";
989 if (!iil_PutLine($fp, $request)) {
993 $line=chop(iil_ReadLine($fp, 200));
994 if ($line[0] == '*') {
997 * "* 10 FETCH (INTERNALDATE "31-Jul-2002 09:18:02 -0500")"
999 $paren_pos = strpos($line, '(');
1000 $foo = substr($line, 0, $paren_pos);
1001 $a = explode(' ', $foo);
1004 $open_pos = strpos($line, '"') + 1;
1005 $close_pos = strrpos($line, '"');
1006 if ($open_pos && $close_pos) {
1007 $len = $close_pos - $open_pos;
1008 $time_str = substr($line, $open_pos, $len);
1009 $result[$id] = strtotime($time_str);
1012 $a = explode(' ', $line);
1014 } while (!iil_StartsWith($a[0], $key));
1017 $field_name = 'FLAGS';
1018 } else if ($index_field == 'SIZE') {
1019 $field_name = 'RFC822.SIZE';
1021 $field_name = $index_field;
1024 /* FETCH uid, size, flags */
1025 $key = 'fhi' .($c++);
1026 $request = $key . " FETCH $message_set ($field_name)";
1028 if (!iil_PutLine($fp, $request)) {
1032 $line=chop(iil_ReadLine($fp, 200));
1033 $a = explode(' ', $line);
1034 if (($line[0] == '*') && ($a[2] == 'FETCH')) {
1035 $line = str_replace('(', '', $line);
1036 $line = str_replace(')', '', $line);
1037 $a = explode(' ', $line);
1041 if (isset($result[$id])) {
1042 continue; //if we already got the data, skip forward
1044 if ($a[3]!=$field_name) {
1045 continue; //make sure it's returning what we requested
1048 /* Caution, bad assumptions, next several lines */
1050 $result[$id] = $a[4];
1052 $haystack = strtoupper($line);
1053 $result[$id] = (strpos($haystack, $index_field) > 0 ? "F" : "N");
1056 } while (!iil_StartsWith($line, $key));
1059 //check number of elements...
1060 list($start_mid, $end_mid) = explode(':', $message_set);
1061 if (is_numeric($start_mid) && is_numeric($end_mid)) {
1062 //count how many we should have
1063 $should_have = $end_mid - $start_mid +1;
1065 //if we have less, try and fill in the "gaps"
1066 if (count($result) < $should_have) {
1067 for ($i=$start_mid; $i<=$end_mid; $i++) {
1068 if (!isset($result[$i])) {
1077 function iil_CompressMessageSet($message_set) {
1078 //given a comma delimited list of independent mid's,
1079 //compresses by grouping sequences together
1081 //if less than 255 bytes long, let's not bother
1082 if (strlen($message_set)<255) {
1083 return $message_set;
1086 //see if it's already been compress
1087 if (strpos($message_set, ':') !== false) {
1088 return $message_set;
1091 //separate, then sort
1092 $ids = explode(',', $message_set);
1096 $start = $prev = $ids[0];
1098 foreach ($ids as $id) {
1099 $incr = $id - $prev;
1100 if ($incr > 1) { //found a gap
1101 if ($start == $prev) {
1102 $result[] = $prev; //push single id
1104 $result[] = $start . ':' . $prev; //push sequence as start_id:end_id
1106 $start = $id; //start of new sequence
1111 //handle the last sequence/id
1112 if ($start==$prev) {
1115 $result[] = $start.':'.$prev;
1118 //return as comma separated string
1119 return implode(',', $result);
1122 function iil_C_UIDsToMIDs(&$conn, $mailbox, $uids) {
1123 if (!is_array($uids) || count($uids) == 0) {
1126 return iil_C_Search($conn, $mailbox, 'UID ' . implode(',', $uids));
1129 function iil_C_UIDToMID(&$conn, $mailbox, $uid) {
1130 $result = iil_C_UIDsToMIDs($conn, $mailbox, array($uid));
1131 if (count($result) == 1) {
1137 function iil_C_FetchUIDs(&$conn,$mailbox) {
1140 $num = iil_C_CountMessages($conn, $mailbox);
1144 $message_set = '1' . ($num>1?':' . $num:'');
1146 //if cache not enabled, just call iil_C_FetchHeaderIndex on 'UID' field
1147 if (!$conn->do_cache)
1148 return iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
1150 //otherwise, let's check cache first
1151 $key = $mailbox.'.uids';
1153 if ($conn->uid_cache) {
1154 $data = $conn->uid_cache;
1156 $data = cache_read($conn->user, $conn->host, $key);
1159 //was anything cached at all?
1160 if ($data === false) {
1164 //make sure number of messages were the same
1165 if ($cache_good > 0 && $data['n'] != $num) {
1169 //if everything's okay so far...
1170 if ($cache_good > 0) {
1171 //check UIDs of highest mid with current and cached
1172 $temp = iil_C_Search($conn, $mailbox, 'UID ' . $data['d'][$num]);
1173 if (!$temp || !is_array($temp) || $temp[0] != $num) {
1178 //if cached data's good, return it
1179 if ($cache_good > 0) {
1183 //otherwise, we need to fetch it
1184 $data = array('n' => $num, 'd' => array());
1185 $data['d'] = iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
1187 cache_write($conn->user, $conn->host, $key, $data);
1188 $conn->uid_cache = $data;
1192 function iil_SortThreadHeaders($headers, $index_a, $uids) {
1195 foreach ($index_a as $mid=>$foobar) {
1197 $result[$uid] = $headers[$uid];
1202 function iil_C_FetchThreadHeaders(&$conn, $mailbox, $message_set) {
1206 list($from_idx, $to_idx) = explode(':', $message_set);
1207 if (empty($message_set) || (isset($to_idx)
1208 && (int)$from_idx > (int)$to_idx)) {
1213 $uids = iil_C_FetchUIDs($conn, $mailbox);
1216 /* Get cached records where possible */
1217 if ($conn->do_cache) {
1218 $cached = cache_read($conn->user, $conn->host, $mailbox.'.thhd');
1219 if ($cached && is_array($uids) && count($uids)>0) {
1221 foreach ($uids as $id=>$uid) {
1222 if ($cached[$uid]) {
1223 $result[$uid] = $cached[$uid];
1224 $result[$uid]->id = $id;
1226 $needed_set .= ($needed_set ? ',' : '') . $id;
1230 $message_set = $needed_set;
1236 $message_set = iil_CompressMessageSet($message_set);
1238 echo "Still need: ".$message_set;
1241 /* if we're missing any, get them */
1243 /* FETCH date,from,subject headers */
1246 $request = $key . " FETCH $message_set ";
1247 $request .= "(BODY.PEEK[HEADER.FIELDS (SUBJECT MESSAGE-ID IN-REPLY-TO)])";
1248 $mid_to_id = array();
1249 if (!iil_PutLine($fp, $request)) {
1253 $line = chop(iil_ReadLine($fp, 1024));
1257 if (ereg('\{[0-9]+\}$', $line)) {
1258 $a = explode(' ', $line);
1261 $new_thhd = new iilThreadHeader;
1262 $new_thhd->id = $a[1];
1264 $line = chop(iil_ReadLine($fp, 1024), "\r\n");
1265 if (iil_StartsWithI($line, 'Message-ID:')
1266 || (iil_StartsWithI($line,'In-Reply-To:'))
1267 || (iil_StartsWithI($line,'SUBJECT:'))) {
1269 $pos = strpos($line, ':');
1270 $field_name = substr($line, 0, $pos);
1271 $field_val = substr($line, $pos+1);
1273 $new[strtoupper($field_name)] = trim($field_val);
1275 } else if (ereg('^[[:space:]]', $line)) {
1276 $new[strtoupper($field_name)] .= trim($line);
1278 } while ($line[0] != ')');
1280 $new_thhd->sbj = $new['SUBJECT'];
1281 $new_thhd->mid = substr($new['MESSAGE-ID'], 1, -1);
1282 $new_thhd->irt = substr($new['IN-REPLY-TO'], 1, -1);
1284 $result[$uids[$new_thhd->id]] = $new_thhd;
1286 } while (!iil_StartsWith($line, 'fh'));
1290 if (is_array($index_a)) {
1291 $result = iil_SortThreadHeaders($result, $index_a, $uids);
1294 /* write new set to cache */
1295 if ($conn->do_cache) {
1296 if (count($result)!=count($cached)) {
1297 cache_write($conn->user, $conn->host, $mailbox . '.thhd', $result);
1301 //echo 'iil_FetchThreadHeaders:'."\n";
1307 function iil_C_BuildThreads2(&$conn, $mailbox, $message_set, &$clock) {
1310 list($from_idx, $to_idx) = explode(':', $message_set);
1311 if (empty($message_set) || (isset($to_idx)
1312 && (int)$from_idx > (int)$to_idx)) {
1318 $root_mids = array();
1319 $sub_mids = array();
1321 $messages = array();
1325 $sbj_filter_pat = '[a-zA-Z]{2,3}(\[[0-9]*\])?:([[:space:]]*)';
1327 /* Do "SELECT" command */
1328 if (!iil_C_Select($conn, $mailbox)) {
1332 /* FETCH date,from,subject headers */
1333 $mid_to_id = array();
1334 $messages = array();
1335 $headers = iil_C_FetchThreadHeaders($conn, $mailbox, $message_set);
1337 $clock->register('fetched headers');
1344 /* go through header records */
1345 foreach ($headers as $header) {
1346 //$id = $header['i'];
1347 //$new = array('id'=>$id, 'MESSAGE-ID'=>$header['m'],
1348 // 'IN-REPLY-TO'=>$header['r'], 'SUBJECT'=>$header['s']);
1350 $new = array('id' => $id, 'MESSAGE-ID' => $header->mid,
1351 'IN-REPLY-TO' => $header->irt, 'SUBJECT' => $header->sbj);
1353 /* add to message-id -> mid lookup table */
1354 $mid_to_id[$new['MESSAGE-ID']] = $id;
1356 /* if no subject, use message-id */
1357 if (empty($new['SUBJECT'])) {
1358 $new['SUBJECT'] = $new['MESSAGE-ID'];
1361 /* if subject contains 'RE:' or has in-reply-to header, it's a reply */
1364 if (eregi($sbj_filter_pat, $new['SUBJECT'])) {
1367 if ($has_re||$new['IN-REPLY-TO']) {
1371 /* strip out 're:', 'fw:' etc */
1373 $sbj = ereg_replace($sbj_filter_pat, '', $new['SUBJECT']);
1375 $sbj = $new['SUBJECT'];
1377 $new['SUBJECT'] = $sbj_pre.$sbj;
1380 /* if subject not a known thread-root, add to list */
1382 echo $id . ' ' . $new['SUBJECT'] . "\t" . $new['MESSAGE-ID'] . "\n";
1384 $root_id = $roots[$sbj];
1386 if ($root_id && ($has_re || !$root_in_root[$root_id])) {
1388 echo "\tfound root: $root_id\n";
1390 $sub_mids[$new['MESSAGE-ID']] = $root_id;
1391 $result[$root_id][] = $id;
1392 } else if (!isset($roots[$sbj]) || (!$has_re && $root_in_root[$root_id])) {
1393 /* try to use In-Reply-To header to find root
1394 unless subject contains 'Re:' */
1395 if ($has_re&&$new['IN-REPLY-TO']) {
1397 echo "\tlooking: ".$new['IN-REPLY-TO']."\n";
1399 //reply to known message?
1400 $temp = $sub_mids[$new['IN-REPLY-TO']];
1403 //found it, root:=parent's root
1405 echo "\tfound parent: ".$new['SUBJECT']."\n";
1407 $result[$temp][] = $id;
1408 $sub_mids[$new['MESSAGE-ID']] = $temp;
1411 //if we can't find referenced parent, it's a "stray"
1412 $strays[$id] = $new['IN-REPLY-TO'];
1416 //add subject as root
1419 echo "\t added to root\n";
1422 $root_in_root[$id] = !$has_re;
1423 $sub_mids[$new['MESSAGE-ID']] = $id;
1424 $result[$id] = array($id);
1427 echo $new['MESSAGE-ID'] . "\t" . $sbj . "\n";
1432 //now that we've gone through all the messages,
1433 //go back and try and link up the stray threads
1434 if (count($strays) > 0) {
1435 foreach ($strays as $id=>$irt) {
1436 $root_id = $sub_mids[$irt];
1437 if (!$root_id || $root_id==$id) {
1440 $result[$root_id] = array_merge($result[$root_id],$result[$id]);
1441 unset($result[$id]);
1446 $clock->register('data prepped');
1456 function iil_SortThreads(&$tree, $index, $sort_order = 'ASC') {
1457 if (!is_array($tree) || !is_array($index)) {
1461 //create an id to position lookup table
1463 foreach ($index as $id=>$val) {
1469 //for each tree, set array key to position
1471 foreach ($tree as $id=>$node) {
1472 if (count($tree[$id])<=1) {
1473 //for "threads" with only one message, key is position of that message
1475 $itree[$n] = array($n=>$id);
1477 //for "threads" with multiple messages,
1480 foreach ($tree[$id] as $mid) {
1481 $new_a[$index[$mid]] = $mid; //create new sub-array mapping position to id
1482 $pos = $index[$mid];
1483 if ($pos&&$pos<$min) {
1484 $min = $index[$mid]; //find smallest position
1487 $n = $min; //smallest position of child is thread position
1489 //assign smallest position to root level key
1490 //set children array to one created above
1492 $itree[$n] = $new_a;
1496 //sort by key, this basically sorts all threads
1500 foreach ($itree as $k=>$node) {
1501 $out[$i] = $itree[$k];
1508 function iil_IndexThreads(&$tree) {
1509 /* creates array mapping mid to thread id */
1511 if (!is_array($tree)) {
1516 foreach ($tree as $pos=>$kids) {
1517 foreach ($kids as $kid) $t_index[$kid] = $pos;
1523 function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false)
1525 global $IMAP_USE_INTERNAL_DATE;
1531 list($from_idx, $to_idx) = explode(':', $message_set);
1532 if (empty($message_set) || (isset($to_idx)
1533 && (int)$from_idx > (int)$to_idx)) {
1537 /* Do "SELECT" command */
1538 if (!iil_C_Select($conn, $mailbox)) {
1539 $conn->error = "Couldn't select $mailbox";
1543 /* Get cached records where possible */
1544 if ($conn->do_cache) {
1545 $uids = iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, "UID");
1546 if (is_array($uids) && count($conn->cache[$mailbox]>0)) {
1548 while (list($id,$uid)=each($uids)) {
1549 if ($conn->cache[$mailbox][$uid]) {
1550 $result[$id] = $conn->cache[$mailbox][$uid];
1551 $result[$id]->id = $id;
1553 $needed_set.=($needed_set ? ',': '') . $id;
1556 //echo "<!-- iil_C_FetchHeader\nMessage Set: $message_set\nNeeded Set:$needed_set\n//-->\n";
1558 $message_set = iil_CompressMessageSet($needed_set);
1565 /* FETCH date,from,subject headers */
1566 $key = 'fh' . ($c++);
1567 $prefix = $uidfetch?' UID':'';
1568 $request = $key . $prefix;
1569 $request .= " FETCH $message_set (BODY.PEEK[HEADER.FIELDS ";
1570 $request .= "(DATE FROM TO SUBJECT REPLY-TO IN-REPLY-TO CC BCC ";
1571 $request .= "CONTENT-TRANSFER-ENCODING CONTENT-TYPE MESSAGE-ID ";
1572 $request .= "REFERENCES DISPOSITION-NOTIFICATION-TO X-PRIORITY)])";
1574 if (!iil_PutLine($fp, $request)) {
1578 $line = chop(iil_ReadLine($fp, 200));
1579 $a = explode(' ', $line);
1580 if (($line[0] == '*') && ($a[2] == 'FETCH')) {
1583 $result[$id] = new iilBasicHeader;
1584 $result[$id]->id = $id;
1585 $result[$id]->subject = '';
1586 $result[$id]->messageID = 'mid:' . $id;
1589 Start parsing headers. The problem is, some header "lines" take up multiple lines.
1590 So, we'll read ahead, and if the one we're reading now is a valid header, we'll
1591 process the previous line. Otherwise, we'll keep adding the strings until we come
1592 to the next valid header line.
1597 $line = chop(iil_ReadLine($fp, 300), "\r\n");
1598 if (ord($line[0])<=32) {
1599 $lines[$i] .= (empty($lines[$i])?'':"\n").trim($line);
1602 $lines[$i] = trim($line);
1605 The preg_match below works around communigate imap, which outputs " UID <number>)".
1606 Without this, the while statement continues on and gets the "fh0 OK completed" message.
1607 If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249.
1608 This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing
1609 If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin
1610 An alternative might be:
1611 if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break;
1612 however, unsure how well this would work with all imap clients.
1614 if (preg_match("/^\s*UID [0-9]+\)$/", $line)) {
1617 // patch from "Maksim Rubis" <siburny@hotmail.com>
1618 } while (trim($line[0]) != ')' && strncmp($line, $key, strlen($key)));
1620 if (strncmp($line, $key, strlen($key))) {
1621 //process header, fill iilBasicHeader obj.
1623 if (is_array($headers)) {
1625 while (list($k, $bar) = each($headers)) {
1630 // create array with header field:data
1631 while ( list($lines_key, $str) = each($lines) ) {
1632 list($field, $string) = iil_SplitHeaderLine($str);
1634 $field = strtolower($field);
1635 $string = ereg_replace("\n[[:space:]]*"," ",$string);
1639 $result[$id]->date = $string;
1640 $result[$id]->timestamp = iil_StrToTime($string);
1643 $result[$id]->from = $string;
1646 $result[$id]->to = $string;
1649 $result[$id]->subject = $string;
1652 $result[$id]->replyto = $string;
1655 $result[$id]->cc = $string;
1658 $result[$id]->bcc = $string;
1660 case 'content-transfer-encoding':
1661 $result[$id]->encoding = $string;
1663 case 'content-type':
1664 $ctype_parts = explode(";", $string);
1665 $result[$id]->ctype = array_shift($ctype_parts);
1666 foreach ($ctype_parts as $ctype_add) {
1667 if (preg_match('/charset="?([a-z0-9\-\.\_]+)"?/i',
1668 $ctype_add, $regs)) {
1669 $result[$id]->charset = $regs[1];
1674 $result[$id]->in_reply_to = ereg_replace("[\n<>]", '', $string);
1677 $result[$id]->references = $string;
1679 case 'return-receipt-to':
1680 case 'disposition-notification-to':
1681 case 'x-confirm-reading-to':
1682 $result[$id]->mdn_to = $string;
1685 $result[$id]->messageID = $string;
1688 if (preg_match('/^(\d+)/', $string, $matches))
1689 $result[$id]->priority = intval($matches[1]);
1694 $a = explode(' ', $line);
1697 } while (strcmp($a[0], $key) != 0);
1700 FETCH uid, size, flags
1701 Sample reply line: "* 3 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen \Deleted))"
1703 $command_key = 'fh' . ($c++);
1704 $request = $command_key . $prefix;
1705 $request .= " FETCH $message_set (UID RFC822.SIZE FLAGS INTERNALDATE)";
1707 if (!iil_PutLine($fp, $request)) {
1711 $line = chop(iil_ReadLine($fp, 200));
1712 //$a = explode(' ', $line);
1713 //if (($line[0]=="*") && ($a[2]=="FETCH")) {
1714 if ($line[0] == '*') {
1715 //echo "<!-- $line //-->\n";
1716 //get outter most parens
1717 $open_pos = strpos($line, "(") + 1;
1718 $close_pos = strrpos($line, ")");
1719 if ($open_pos && $close_pos) {
1720 //extract ID from pre-paren
1721 $pre_str = substr($line, 0, $open_pos);
1722 $pre_a = explode(' ', $line);
1726 $len = $close_pos - $open_pos;
1727 $str = substr($line, $open_pos, $len);
1729 //swap parents with quotes, then explode
1730 $str = eregi_replace("[()]", "\"", $str);
1731 $a = iil_ExplodeQuotedString(' ', $str);
1733 //did we get the right number of replies?
1734 $parts_count = count($a);
1735 if ($parts_count>=8) {
1736 for ($i=0;$i<$parts_count;$i=$i+2) {
1737 if (strcasecmp($a[$i],"UID") == 0) $result[$id]->uid=$a[$i+1];
1738 else if (strcasecmp($a[$i],"RFC822.SIZE") == 0) $result[$id]->size=$a[$i+1];
1739 else if (strcasecmp($a[$i],"INTERNALDATE") == 0) $time_str = $a[$i+1];
1740 else if (strcasecmp($a[$i],"FLAGS") == 0) $flags_str = $a[$i+1];
1744 $flags_str = eregi_replace('[\\\"]', '', $flags_str);
1745 $flags_a = explode(' ', $flags_str);
1748 trigger_error("<!-- ID: $id FLAGS: ".implode(",", $flags_a)." //-->\n",
1752 if (is_array($flags_a)) {
1754 while (list($key,$val)=each($flags_a)) {
1755 if (strcasecmp($val,'Seen') == 0) {
1756 $result[$id]->seen = true;
1757 } else if (strcasecmp($val, 'Deleted') == 0) {
1758 $result[$id]->deleted=true;
1759 } else if (strcasecmp($val, 'Recent') == 0) {
1760 $result[$id]->recent = true;
1761 } else if (strcasecmp($val, 'Answered') == 0) {
1762 $result[$id]->answered = true;
1763 } else if (strcasecmp($val, "\$MDNSent") == 0) {
1764 $result[$id]->mdn_sent = true;
1767 $result[$id]->flags = $flags_a;
1770 // if time is gmt...
1771 $time_str = str_replace('GMT','+0000',$time_str);
1774 $time_str = substr($time_str, 0, -1);
1775 $time_zone_str = substr($time_str, -5); //extract timezone
1776 $time_str = substr($time_str, 1, -6); //remove quotes
1777 $time_zone = (float)substr($time_zone_str, 1, 2); //get first two digits
1778 if ($time_zone_str[3] != '0') {
1779 $time_zone += 0.5; //handle half hour offset
1781 if ($time_zone_str[0] == '-') {
1782 $time_zone = $time_zone * -1.0; //minus?
1784 $result[$id]->internaldate = $time_str;
1786 if ($IMAP_USE_INTERNAL_DATE || empty($result[$id]->date)) {
1787 //calculate timestamp
1788 $timestamp = strtotime($time_str); //return's server's time
1789 $na_timestamp = $timestamp;
1790 $timestamp -= $time_zone * 3600; //compensate for tz, get GMT
1792 $result[$id]->timestamp = $timestamp;
1793 $result[$id]->date = $time_str;
1796 if ($conn->do_cache) {
1797 $uid = $result[$id]->uid;
1798 $conn->cache[$mailbox][$uid] = $result[$id];
1799 $conn->cache_dirty[$mailbox] = true;
1801 //echo "<!-- ID: $id : $time_str -- local: $na_timestamp (".date("F j, Y, g:i a", $na_timestamp).") tz: $time_zone -- GMT: ".$timestamp." (".date("F j, Y, g:i a", $timestamp).") //-->\n";
1803 //echo "<!-- ERROR: $id : $str //-->\n";
1807 } while (strpos($line, $command_key) === false);
1812 function iil_C_FetchHeader(&$conn, $mailbox, $id, $uidfetch=false) {
1814 $a = iil_C_FetchHeaders($conn, $mailbox, $id, $uidfetch);
1816 return array_shift($a);
1821 function iil_SortHeaders($a, $field, $flag) {
1822 if (empty($field)) {
1825 $field = strtolower($field);
1826 if ($field == 'date' || $field == 'internaldate') {
1827 $field = 'timestamp';
1833 $flag = strtoupper($flag);
1834 $stripArr = ($field=='subject') ? array('Re: ','Fwd: ','Fw: ','"') : array('"');
1840 First, we'll create an "index" array.
1841 Then, we'll use sort() on that array,
1842 and use that to sort the main array.
1845 // create "index" array
1848 while (list($key, $val)=each($a)) {
1850 if ($field == 'timestamp') {
1851 $data = @strtotime($val->date);
1852 if ($data == false) {
1853 $data = $val->timestamp;
1856 $data = $val->$field;
1857 if (is_string($data)) {
1858 $data=strtoupper(str_replace($stripArr, '', $data));
1866 if ($flag == 'ASC') {
1872 // form new array based on index
1875 while (list($key, $val)=each($index)) {
1876 $result[$key]=$a[$key];
1884 function iil_C_Expunge(&$conn, $mailbox) {
1886 if (iil_C_Select($conn, $mailbox)) {
1888 iil_PutLine($conn->fp, "exp1 EXPUNGE");
1890 $line=chop(iil_ReadLine($conn->fp, 100));
1891 if ($line[0] == '*') {
1894 } while (!iil_StartsWith($line, 'exp1'));
1896 if (iil_ParseResult($line) == 0) {
1897 $conn->selected = ''; //state has changed, need to reselect
1898 //$conn->exists-=$c;
1901 $conn->error = $line;
1907 function iil_C_ModFlag(&$conn, $mailbox, $messages, $flag, $mod) {
1908 if ($mod != '+' && $mod != '-') {
1915 'DELETED' => '\\Deleted',
1916 'RECENT' => '\\Recent',
1917 'ANSWERED' => '\\Answered',
1918 'DRAFT' => '\\Draft',
1919 'FLAGGED' => '\\Flagged',
1920 'MDNSENT' => "\$MDNSent");
1922 $flag = strtoupper($flag);
1923 $flag = $flags[$flag];
1925 if (iil_C_Select($conn, $mailbox)) {
1927 iil_PutLine($fp, "flg STORE $messages " . $mod . "FLAGS (" . $flag . ")");
1929 $line=chop(iil_ReadLine($fp, 100));
1930 if ($line[0] == '*') {
1933 } while (!iil_StartsWith($line, 'flg'));
1935 if (iil_ParseResult($line) == 0) {
1936 iil_C_ExpireCachedItems($conn, $mailbox, $messages);
1939 $conn->error = $line;
1942 $conn->error = 'Select failed';
1946 function iil_C_Flag(&$conn, $mailbox, $messages, $flag) {
1947 return iil_C_ModFlag($conn, $mailbox, $messages, $flag, '+');
1950 function iil_C_Unflag(&$conn, $mailbox, $messages, $flag) {
1951 return iil_C_ModFlag($conn, $mailbox, $messages, $flag, '-');
1954 function iil_C_Delete(&$conn, $mailbox, $messages) {
1955 return iil_C_ModFlag($conn, $mailbox, $messages, 'DELETED', '+');
1958 function iil_C_Undelete(&$conn, $mailbox, $messages) {
1959 return iil_C_ModFlag($conn, $mailbox, $messages, 'DELETED', '-');
1962 function iil_C_Unseen(&$conn, $mailbox, $messages) {
1963 return iil_C_ModFlag($conn, $mailbox, $messages, 'SEEN', '-');
1966 function iil_C_Copy(&$conn, $messages, $from, $to) {
1969 if (empty($from) || empty($to)) {
1973 if (iil_C_Select($conn, $from)) {
1976 iil_PutLine($fp, "cpy1 COPY $messages \"".iil_Escape($to)."\"");
1977 $line=iil_ReadReply($fp);
1978 return iil_ParseResult($line);
1984 function iil_FormatSearchDate($month, $day, $year) {
1985 $month = (int) $month;
1986 $months = $GLOBALS['IMAP_MONTHS'];
1987 return $day . '-' . $months[$month] . '-' . $year;
1990 function iil_C_CountUnseen(&$conn, $folder) {
1991 $index = iil_C_Search($conn, $folder, 'ALL UNSEEN');
1992 if (is_array($index)) {
1993 $str = implode(',', $index);
1997 return count($index);
2002 function iil_C_UID2ID(&$conn, $folder, $uid) {
2004 $id_a = iil_C_Search($conn, $folder, "UID $uid");
2005 if (is_array($id_a)) {
2006 $count = count($id_a);
2016 function iil_C_ID2UID(&$conn, $folder, $id) {
2022 if (iil_C_Select($conn, $folder)) {
2024 if (iil_PutLine($fp, "$key FETCH $id (UID)")) {
2026 $line=chop(iil_ReadLine($fp, 1024));
2027 if (eregi("^\* $id FETCH \(UID (.*)\)", $line, $r)) {
2030 } while (!preg_match("/^$key/", $line));
2036 function iil_C_Search(&$conn, $folder, $criteria) {
2038 if (iil_C_Select($conn, $folder)) {
2041 $query = 'srch1 SEARCH ' . chop($criteria);
2042 iil_PutLine($fp, $query);
2044 $line=trim(iil_ReadLine($fp, 10000));
2045 if (eregi("^\* SEARCH", $line)) {
2046 $str = trim(substr($line, 8));
2047 $messages = explode(' ', $str);
2049 } while (!iil_StartsWith($line, 'srch1'));
2051 $result_code = iil_ParseResult($line);
2052 if ($result_code == 0) {
2055 $conn->error = 'iil_C_Search: ' . $line . "\n";
2058 $conn->error = "iil_C_Search: Couldn't select \"$folder\"\n";
2062 function iil_C_Move(&$conn, $messages, $from, $to) {
2065 if (!$from || !$to) {
2068 $r = iil_C_Copy($conn, $messages, $from,$to);
2070 return iil_C_Delete($conn, $from, $messages);
2076 * Gets the delimiter, for example:
2081 * @return mixed A delimiter (string), or false.
2082 * @param object $conn The current connection.
2083 * @see iil_Connect()
2085 function iil_C_GetHierarchyDelimiter(&$conn) {
2086 if ($conn->delimiter) {
2087 return $conn->delimiter;
2093 //try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8)
2094 if (!iil_PutLine($fp, 'ghd LIST "" ""')) {
2099 $line=iil_ReadLine($fp, 500);
2100 if ($line[0] == '*') {
2101 $line = rtrim($line);
2102 $a=iil_ExplodeQuotedString(' ', $line);
2104 $delimiter = str_replace('"', '', $a[count($a)-2]);
2107 } while (!iil_StartsWith($line, 'ghd'));
2109 if (strlen($delimiter)>0) {
2113 //if that fails, try namespace extension
2114 //try to fetch namespace data
2115 iil_PutLine($conn->fp, "ns1 NAMESPACE");
2117 $line = iil_ReadLine($conn->fp, 1024);
2118 if (iil_StartsWith($line, '* NAMESPACE')) {
2120 $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
2122 } while (!iil_StartsWith($line, 'ns1'));
2124 if (!is_array($data)) {
2128 //extract user space data (opposed to global/shared space)
2129 $user_space_data = $data[0];
2130 if (!is_array($user_space_data)) {
2135 $first_userspace = $user_space_data[0];
2136 if (!is_array($first_userspace)) {
2141 $delimiter = $first_userspace[1];
2146 function iil_C_ListMailboxes(&$conn, $ref, $mailbox) {
2147 global $IGNORE_FOLDERS;
2149 $ignore = $IGNORE_FOLDERS[strtolower($conn->host)];
2153 if (empty($mailbox)) {
2157 if (empty($ref) && $conn->rootdir) {
2158 $ref = $conn->rootdir;
2162 if (!iil_PutLine($fp, "lmb LIST \"".$ref."\" \"".iil_Escape($mailbox)."\"")) {
2169 $line = iil_ReadLine($fp, 500);
2170 $line = iil_MultLine($fp, $line);
2172 $a = explode(' ', $line);
2173 if (($line[0] == '*') && ($a[1] == 'LIST')) {
2174 $line = rtrim($line);
2176 $a = iil_ExplodeQuotedString(' ', $line);
2177 // last string is folder name
2178 $folder = trim($a[count($a)-1], '"');
2180 if (empty($ignore) || (!empty($ignore)
2181 && !eregi($ignore, $folder))) {
2182 $folders[$i] = $folder;
2185 // second from last is delimiter
2186 $delim = trim($a[count($a)-2], '"');
2187 // is it a container?
2190 } while (!iil_StartsWith($line, 'lmb'));
2192 if (is_array($folders)) {
2194 // if rootdir was specified, make sure it's the first element
2195 // some IMAP servers (i.e. Courier) won't return it
2196 if ($ref[strlen($ref)-1]==$delim)
2197 $ref = substr($ref, 0, strlen($ref)-1);
2198 if ($folders[0]!=$ref)
2199 array_unshift($folders, $ref);
2202 } else if (iil_ParseResult($line) == 0) {
2203 return array('INBOX');
2205 $conn->error = $line;
2210 function iil_C_ListSubscribed(&$conn, $ref, $mailbox) {
2211 global $IGNORE_FOLDERS;
2213 $ignore = $IGNORE_FOLDERS[strtolower($conn->host)];
2216 if (empty($mailbox)) {
2219 if (empty($ref) && $conn->rootdir) {
2220 $ref = $conn->rootdir;
2225 if (!iil_PutLine($fp, 'lsb LSUB "' . $ref . '" "' . iil_Escape($mailbox).'"')) {
2226 $conn->error = "Couldn't send LSUB command\n";
2234 $line = iil_ReadLine($fp, 500);
2235 $line = iil_MultLine($fp, $line);
2236 $a = explode(' ', $line);
2238 if (($line[0] == '*') && ($a[1] == 'LSUB' || $a[1] == 'LIST')) {
2239 $line = rtrim($line);
2242 $a = iil_ExplodeQuotedString(' ', $line);
2244 // last string is folder name
2245 //$folder = UTF7DecodeString(str_replace('"', '', $a[count($a)-1]));
2246 $folder = trim($a[count($a)-1], '"');
2248 if ((!in_array($folder, $folders)) && (empty($ignore)
2249 || (!empty($ignore) && !eregi($ignore, $folder)))) {
2250 $folders[$i] = $folder;
2253 // second from last is delimiter
2254 $delim = trim($a[count($a)-2], '"');
2256 // is it a container?
2259 } while (!iil_StartsWith($line, 'lsb'));
2261 if (is_array($folders)) {
2263 // if rootdir was specified, make sure it's the first element
2264 // some IMAP servers (i.e. Courier) won't return it
2265 if ($ref[strlen($ref)-1]==$delim) {
2266 $ref = substr($ref, 0, strlen($ref)-1);
2268 if ($folders[0]!=$ref) {
2269 array_unshift($folders, $ref);
2274 $conn->error = $line;
2278 function iil_C_Subscribe(&$conn, $folder) {
2281 $query = 'sub1 SUBSCRIBE "' . iil_Escape($folder). '"';
2282 iil_PutLine($fp, $query);
2284 $line = trim(iil_ReadLine($fp, 10000));
2285 return iil_ParseResult($line);
2288 function iil_C_UnSubscribe(&$conn, $folder) {
2291 $query = 'usub1 UNSUBSCRIBE "' . iil_Escape($folder) . '"';
2292 iil_PutLine($fp, $query);
2294 $line = trim(iil_ReadLine($fp, 10000));
2295 return iil_ParseResult($line);
2298 function iil_C_FetchPartHeader(&$conn, $mailbox, $id, $part) {
2301 if (($part == 0) || (empty($part))) {
2307 if (iil_C_Select($conn, $mailbox)) {
2308 $key = 'fh' . ($c++);
2309 $request = $key . " FETCH $id (BODY.PEEK[$part])";
2310 if (!iil_PutLine($fp, $request)) return false;
2312 $line = chop(iil_ReadLine($fp, 200));
2313 $a = explode(' ', $line);
2314 if (($line[0] == '*') && ($a[2] == 'FETCH')
2315 && ($line[strlen($line)-1] != ')')) {
2316 $line=iil_ReadLine($fp, 300);
2317 while (trim($line) != ')') {
2319 $line=iil_ReadLine($fp, 300);
2322 } while (strcmp($a[0], $key) != 0);
2328 function iil_C_HandlePartBody(&$conn, $mailbox, $id, $part, $mode) {
2337 if (($part == 0) || empty($part)) {
2341 if (iil_C_Select($conn, $mailbox)) {
2342 $reply_key = '* ' . $id;
2345 $key = 'ftch' . ($c++) . ' ';
2346 $request = $key . "FETCH $id (BODY.PEEK[$part])";
2348 if (!iil_PutLine($fp, $request)) {
2352 // receive reply line
2354 $line = chop(iil_ReadLine($fp, 1000));
2355 $a = explode(' ', $line);
2356 } while ($a[2] != 'FETCH');
2357 $len = strlen($line);
2359 if ($line[$len-1] == ')') {
2360 // one line response, get everything between first and last quotes
2361 if (substr($line, -4, 3) == 'NIL') {
2365 $from = strpos($line, '"') + 1;
2366 $to = strrpos($line, '"');
2368 $result = substr($line, $from, $len);
2373 } else if ($mode == 3) {
2374 echo base64_decode($result);
2376 } else if ($line[$len-1] == '}') {
2377 //multi-line request, find sizes of content and receive that many bytes
2378 $from = strpos($line, '{') + 1;
2379 $to = strrpos($line, '}');
2381 $sizeStr = substr($line, $from, $len);
2382 $bytes = (int)$sizeStr;
2385 while ($received < $bytes) {
2386 $remaining = $bytes - $received;
2387 $line = iil_ReadLine($fp, 1024);
2388 $len = strlen($line);
2390 if ($len > $remaining) {
2391 $line = substr($line, 0, $remaining);
2393 $received += strlen($line);
2395 $result .= rtrim($line, "\t\r\n\0\x0B") . "\n";
2396 } else if ($mode == 2) {
2397 echo rtrim($line, "\t\r\n\0\x0B") . "\n"; flush();
2398 } else if ($mode == 3) {
2399 echo base64_decode($line); flush();
2403 // read in anything up until 'til last line
2405 $line = iil_ReadLine($fp, 1024);
2406 } while (!iil_StartsWith($line, $key));
2409 $result = rtrim($result, "\t\r\n\0\x0B");
2410 return $result; // substr($result, 0, strlen($result)-1);
2415 echo 'Select failed.';
2424 function iil_C_FetchPartBody(&$conn, $mailbox, $id, $part) {
2425 return iil_C_HandlePartBody($conn, $mailbox, $id, $part, 1);
2428 function iil_C_PrintPartBody(&$conn, $mailbox, $id, $part) {
2429 iil_C_HandlePartBody($conn, $mailbox, $id, $part, 2);
2432 function iil_C_PrintBase64Body(&$conn, $mailbox, $id, $part) {
2433 iil_C_HandlePartBody($conn, $mailbox, $id, $part, 3);
2436 function iil_C_CreateFolder(&$conn, $folder) {
2438 if (iil_PutLine($fp, 'c CREATE "' . iil_Escape($folder) . '"')) {
2440 $line=iil_ReadLine($fp, 300);
2441 } while ($line[0] != 'c');
2442 $conn->error = $line;
2443 return (iil_ParseResult($line) == 0);
2448 function iil_C_RenameFolder(&$conn, $from, $to) {
2450 if (iil_PutLine($fp, 'r RENAME "' . iil_Escape($from) . '" "' . iil_Escape($to) . '"')) {
2452 $line = iil_ReadLine($fp, 300);
2453 } while ($line[0] != 'r');
2454 return (iil_ParseResult($line) == 0);
2459 function iil_C_DeleteFolder(&$conn, $folder) {
2461 if (iil_PutLine($fp, 'd DELETE "' . iil_Escape($folder). '"')) {
2463 $line=iil_ReadLine($fp, 300);
2464 } while ($line[0] != 'd');
2465 return (iil_ParseResult($line) == 0);
2467 $conn->error = "Couldn't send command\n";
2471 function iil_C_Append(&$conn, $folder, &$message) {
2477 $message = str_replace("\r", '', $message);
2478 $message = str_replace("\n", "\r\n", $message);
2480 $len = strlen($message);
2485 $request = 'A APPEND "' . iil_Escape($folder) .'" (\\Seen) {' . $len . '}';
2487 if (iil_PutLine($fp, $request)) {
2488 $line=iil_ReadLine($fp, 100);
2489 $sent = fwrite($fp, $message."\r\n");
2491 $line=iil_ReadLine($fp, 1000);
2492 } while ($line[0] != 'A');
2494 $result = (iil_ParseResult($line) == 0);
2496 $conn->error .= $line . "\n";
2501 $conn->error .= "Couldn't send command \"$request\"\n";
2505 function iil_C_AppendFromFile(&$conn, $folder, $path) {
2512 if (file_exists(realpath($path))) {
2513 $in_fp = fopen($path, 'r');
2516 $conn->error .= "Couldn't open $path for reading\n";
2521 $len = filesize($path);
2526 //send APPEND command
2527 $request = 'A APPEND "' . iil_Escape($folder) . '" (\\Seen) {' . $len . '}';
2529 if (iil_PutLine($fp, $request)) {
2530 $line = iil_ReadLine($fp, 100);
2533 while (!feof($in_fp)) {
2534 $buffer = fgets($in_fp, 4096);
2535 $bytes_sent += strlen($buffer);
2536 iil_PutLine($fp, $buffer, false);
2540 iil_PutLine($fp, '');
2544 $line = iil_ReadLine($fp, 1000);
2545 } while ($line[0] != 'A');
2547 $result = (iil_ParseResult($line) == 0);
2549 $conn->error .= $line . "\n";
2555 $conn->error .= "Couldn't send command \"$request\"\n";
2559 function iil_C_FetchStructureString(&$conn, $folder, $id) {
2563 if (iil_C_Select($conn, $folder)) {
2566 if (iil_PutLine($fp, "$key FETCH $id (BODYSTRUCTURE)")) {
2568 $line = iil_ReadLine($fp, 5000);
2569 $line = iil_MultLine($fp, $line);
2571 } while (!preg_match("/^$key/", $line));
2573 $result = trim(substr($result, strpos($result, 'BODYSTRUCTURE')+13, -(strlen($result)-strrpos($result, $key)+1)));
2579 function iil_C_PrintSource(&$conn, $folder, $id, $part) {
2580 $header = iil_C_FetchPartHeader($conn, $folder, $id, $part);
2581 //echo str_replace("\r", '', $header);
2583 echo iil_C_PrintPartBody($conn, $folder, $id, $part);
2586 function iil_C_GetQuota(&$conn) {
2588 * GETQUOTAROOT "INBOX"
2589 * QUOTAROOT INBOX user/rchijiiwa1
2590 * QUOTA user/rchijiiwa1 (STORAGE 654 9765)
2597 //get line containing quota info
2598 if (iil_PutLine($fp, 'QUOT1 GETQUOTAROOT "INBOX"')) {
2600 $line=chop(iil_ReadLine($fp, 5000));
2601 if (iil_StartsWith($line, '* QUOTA ')) {
2602 $quota_line = $line;
2604 } while (!iil_StartsWith($line, 'QUOT1'));
2607 //return false if not found, parse if found
2608 if (!empty($quota_line)) {
2609 $quota_line = eregi_replace('[()]', '', $quota_line);
2610 $parts = explode(' ', $quota_line);
2611 $storage_part = array_search('STORAGE', $parts);
2612 if ($storage_part > 0) {
2614 $used = $parts[$storage_part+1];
2615 $total = $parts[$storage_part+2];
2617 $result['used'] = $used;
2618 $result['total'] = (empty($total)?"??":$total);
2619 $result['percent'] = (empty($total)?"??":round(($used/$total)*100));
2620 $result['free'] = 100 - $result['percent'];
2626 function iil_C_ClearFolder(&$conn, $folder) {
2627 $num_in_trash = iil_C_CountMessages($conn, $folder);
2628 if ($num_in_trash > 0) {
2629 iil_C_Delete($conn, $folder, '1:' . $num_in_trash);
2631 return (iil_C_Expunge($conn, $folder) >= 0);