alecpl
2009-05-29 0eeee634139a13e10f18a4e5168eca5105a1bb04
program/lib/imap.inc
@@ -52,16 +52,38 @@
      - $ICL_SSL is not boolean anymore but contains the connection schema (ssl or tls)
      - Removed some debuggers (echo ...)
      File altered by Aleksander Machniak <alec@alec.pl>
      - RFC3501 [7.1] don't call CAPABILITY if was returned in server
        optional resposne in iil_Connect()
      - trim(chop()) replaced by trim()
      - added iil_Escape() with support for " and \ in folder names
      - added iil_Escape()/iil_UnEscape() with support for " and \ in folder names
      - support \ character in username in iil_C_Login()
      - fixed iil_MultLine(): use iil_ReadBytes() instead of iil_ReadLine()
      - fixed iil_C_FetchStructureString() to handle many literal strings in response
      - removed hardcoded data size in iil_ReadLine() 
      - added iil_PutLine() wrapper for fputs()
      - code cleanup and identation fixes
      - removed flush() calls in iil_C_HandlePartBody() to prevent from memory leak (#1485187)
      - don't return "??" from iil_C_GetQuota()
      - RFC3501 [7.1] don't call CAPABILITY if was returned in server
        optional resposne in iil_Connect(), added iil_C_GetCapability()
      - remove 'undisclosed-recipients' string from 'To' header
      - iil_C_HandlePartBody(): added 6th argument and fixed endless loop
      - added iil_PutLineC()
      - fixed iil_C_Sort() to support very long and/or divided responses
      - added BYE/BAD response simple support for endless loop prevention
      - added 3rd argument in iil_StartsWith* functions
      - fix iil_C_FetchPartHeader() in some cases by use of iil_C_HandlePartBody()
      - allow iil_C_HandlePartBody() to fetch whole message
      - optimize iil_C_FetchHeaders() to use only one FETCH command
      - added 4th argument to iil_Connect()
      - allow setting rootdir and delimiter before connect
      - support multiquota result
      - include BODYSTRUCTURE in iil_C_FetchHeaders()
      - added iil_C_FetchMIMEHeaders() function
      - added \* flag support
      - use PREG instead of EREG
      - removed caching functions
      - handling connection startup response
      - added UID EXPUNGE support
      - fixed problem with double quotes and spaces in folder names in LIST and LSUB
********************************************************/
@@ -72,9 +94,6 @@
 * @todo Refactor code.
 * @todo Replace echo-debugging (make it adhere to config setting and log)
 */
// changed path to work within roundcube webmail
include_once 'lib/icl_commons.inc';
if (!isset($IMAP_USE_HEADER_DATE) || !$IMAP_USE_HEADER_DATE) {
@@ -98,7 +117,9 @@
    'DRAFT'    => '\\Draft',
    'FLAGGED'  => '\\Flagged',
    'FORWARDED' => '$Forwarded',
    'MDNSENT'  => '$MDNSent');
    'MDNSENT'  => '$MDNSent',
    '*'        => '\\*',
);
$iil_error;
$iil_errornum;
@@ -115,15 +136,13 @@
   var $selected;
   var $message;
   var $host;
   var $cache;
   var $uid_cache;
   var $do_cache;
   var $exists;
   var $recent;
   var $rootdir;
   var $delimiter;
   var $capability = array();
   var $permanentflags = array();
   var $capability_readed = false;
}
/**
@@ -148,6 +167,7 @@
   var $flags;
   var $timestamp;
   var $f;
   var $body_structure;
   var $internaldate;
   var $references;
   var $priority;
@@ -161,6 +181,7 @@
   var $forwarded = false;
   var $junk = false;
   var $flagged = false;
   var $others = array();
}
/**
@@ -184,11 +205,34 @@
}
function iil_PutLine($fp, $string, $endln=true) {
//   console('C: '. $string);
   return fputs($fp, $string . ($endln ? "\r\n" : ''));
//      console('C: '. rtrim($string));
        return fputs($fp, $string . ($endln ? "\r\n" : ''));
}
function iil_ReadLine($fp, $size) {
// iil_PutLine replacement with Command Continuation Requests (RFC3501 7.5) support
function iil_PutLineC($fp, $string, $endln=true) {
   if ($endln)
      $string .= "\r\n";
   $res = 0;
   if ($parts = preg_split('/(\{[0-9]+\}\r\n)/m', $string, -1, PREG_SPLIT_DELIM_CAPTURE)) {
      for($i=0, $cnt=count($parts); $i<$cnt; $i++) {
         if(preg_match('/^\{[0-9]+\}\r\n$/', $parts[$i+1])) {
            $res += iil_PutLine($fp, $parts[$i].$parts[$i+1], false);
            $line = iil_ReadLine($fp, 1000);
            // handle error in command
            if ($line[0] != '+')
               return false;
            $i++;
         }
         else
            $res += iil_PutLine($fp, $parts[$i], false);
      }
   }
   return $res;
}
function iil_ReadLine($fp, $size=1024) {
   $line = '';
   if (!$fp) {
@@ -211,9 +255,9 @@
   return $line;
}
function iil_MultLine($fp, $line) {
function iil_MultLine($fp, $line, $escape=false) {
   $line = chop($line);
   if (ereg('\{[0-9]+\}$', $line)) {
   if (preg_match('/\{[0-9]+\}$/', $line)) {
      $out = '';
        
      preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a);
@@ -222,7 +266,8 @@
         $line = iil_ReadBytes($fp, $bytes); 
         $out .= $line;
      }
      $line = $a[1][0] . "\"$out\"";
      $line = $a[1][0] . '"' . ($escape ? iil_Escape($out) : $out) . '"';
//      console('[...] '. $out);
   }
   return $line;
@@ -251,7 +296,7 @@
}
function iil_ParseResult($string) {
   $a=explode(' ', $string);
   $a = explode(' ', $string);
   if (count($a) > 2) {
      if (strcasecmp($a[1], 'OK') == 0) {
         return 0;
@@ -259,13 +304,15 @@
         return -1;
      } else if (strcasecmp($a[1], 'BAD') == 0) {
         return -2;
      } else if (strcasecmp($a[1], 'BYE') == 0) {
         return -3;
          }
   }
   return -3;
   return -4;
}
// check if $string starts with $match
function iil_StartsWith($string, $match) {
// check if $string starts with $match (or * BYE/BAD)
function iil_StartsWith($string, $match, $error=false) {
   $len = strlen($match);
   if ($len == 0) {
      return false;
@@ -273,10 +320,13 @@
   if (strncmp($string, $match, $len) == 0) {
      return true;
   }
   if ($error && preg_match('/^\* (BYE|BAD) /i', $string)) {
      return true;
   }
   return false;
}
function iil_StartsWithI($string, $match) {
function iil_StartsWithI($string, $match, $bye=false) {
   $len = strlen($match);
   if ($len == 0) {
      return false;
@@ -284,12 +334,61 @@
   if (strncasecmp($string, $match, $len) == 0) {
      return true;
   }
   if ($bye && strncmp($string, '* BYE ', 6) == 0) {
      return true;
   }
   return false;
}
function iil_Escape($string)
{
   return strtr($string, array('"'=>'\\"', '\\' => '\\\\')); 
}
function iil_UnEscape($string)
{
   return strtr($string, array('\\"'=>'"', '\\\\' => '\\'));
}
function iil_C_GetCapability(&$conn, $name)
{
   if (in_array($name, $conn->capability)) {
      return true;
   }
   else if ($conn->capability_readed) {
      return false;
   }
   // get capabilities (only once) because initial
   // optional CAPABILITY response may differ
   $conn->capability = array();
   iil_PutLine($conn->fp, "cp01 CAPABILITY");
   do {
      $line = trim(iil_ReadLine($conn->fp, 1024));
      $a = explode(' ', $line);
      if ($line[0] == '*') {
         while (list($k, $w) = each($a)) {
            if ($w != '*' && $w != 'CAPABILITY')
                   $conn->capability[] = strtoupper($w);
         }
      }
   } while ($a[0] != 'cp01');
   $conn->capability_readed = true;
   if (in_array($name, $conn->capability)) {
      return true;
   }
   return false;
}
function iil_C_ClearCapability(&$conn)
{
   $conn->capability = array();
   $conn->capability_readed = false;
}
function iil_C_Authenticate(&$conn, $user, $pass, $encChallenge) {
@@ -320,15 +419,20 @@
    $line = iil_ReadLine($conn->fp, 1024);
    
    // process result
    if (iil_ParseResult($line) == 0) {
    $result = iil_ParseResult($line);
    if ($result == 0) {
        $conn->error    .= '';
        $conn->errorNum  = 0;
        return $conn->fp;
    }
    if ($result == -3) fclose($conn->fp); // BYE response
    $conn->error    .= 'Authentication for ' . $user . ' failed (AUTH): "';
    $conn->error    .= htmlspecialchars($line) . '"';
    $conn->errorNum  = -2;
    return false;
    $conn->errorNum  = $result;
    return $result;
}
function iil_C_Login(&$conn, $user, $password) {
@@ -340,20 +444,22 @@
        if ($line === false) {
            break;
        }
    } while (!iil_StartsWith($line, "a001 "));
    $a = explode(' ', $line);
    if (strcmp($a[1], 'OK') == 0) {
        $result          = $conn->fp;
    } while (!iil_StartsWith($line, 'a001 ', true));
    // process result
    $result = iil_ParseResult($line);
    if ($result == 0) {
        $conn->error    .= '';
        $conn->errorNum  = 0;
        return $result;
        return $conn->fp;
    }
    $result = false;
    fclose($conn->fp);
    
    $conn->error    .= 'Authentication for ' . $user . ' failed (LOGIN): "';
    $conn->error    .= htmlspecialchars($line)."\"";
    $conn->errorNum  = -2;
    $conn->errorNum  = $result;
    return $result;
}
@@ -395,13 +501,14 @@
function iil_C_NameSpace(&$conn) {
   global $my_prefs;
   if (!in_array('NAMESPACE', $conn->capability)) {
       return false;
   if (isset($my_prefs['rootdir']) && is_string($my_prefs['rootdir'])) {
          $conn->rootdir = $my_prefs['rootdir'];
      return true;
   }
   if ($my_prefs["rootdir"]) {
       return true;
   if (!iil_C_GetCapability($conn, 'NAMESPACE')) {
       return false;
   }
    
   iil_PutLine($conn->fp, "ns1 NAMESPACE");
@@ -409,9 +516,10 @@
      $line = iil_ReadLine($conn->fp, 1024);
      if (iil_StartsWith($line, '* NAMESPACE')) {
         $i    = 0;
         $line = iil_UnEscape($line);
         $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
      }
   } while (!iil_StartsWith($line, "ns1"));
   } while (!iil_StartsWith($line, 'ns1', true));
   
   if (!is_array($data)) {
       return false;
@@ -429,49 +537,47 @@
    
   $conn->rootdir       = $first_userspace[0];
   $conn->delimiter     = $first_userspace[1];
   $my_prefs["rootdir"] = substr($conn->rootdir, 0, -1);
   $my_prefs['rootdir'] = substr($conn->rootdir, 0, -1);
   $my_prefs['delimiter'] = $conn->delimiter;
   
   return true;
}
function iil_Connect($host, $user, $password) {
function iil_Connect($host, $user, $password, $options=null) {
   global $iil_error, $iil_errornum;
   global $ICL_SSL, $ICL_PORT;
   global $IMAP_NO_CACHE;
   global $my_prefs, $IMAP_USE_INTERNAL_DATE;
   
   $iil_error = '';
   $iil_errornum = 0;
   //strip slashes
   // $user = stripslashes($user);
   // $password = stripslashes($password);
   //set auth method
   $auth_method = 'plain';
   if (func_num_args() >= 4) {
      $auth_array = func_get_arg(3);
      if (is_array($auth_array)) {
         $auth_method = $auth_array['imap'];
          }
      if (empty($auth_method)) {
              $auth_method = "plain";
          }
   // set some imap options
   if (is_array($options)) {
      foreach($options as $optkey => $optval) {
         if ($optkey == 'imap') {
            $auth_method = $optval;
         } else if ($optkey == 'rootdir') {
                $my_prefs['rootdir'] = $optval;
         } else if ($optkey == 'delimiter') {
                $my_prefs['delimiter'] = $optval;
         }
      }
   }
   if (empty($auth_method))
          $auth_method = 'check';
   $message = "INITIAL: $auth_method\n";
      
   $result = false;
   
   //initialize connection
   // initialize connection
   $conn              = new iilConnection;
   $conn->error       = '';
   $conn->errorNum    = 0;
   $conn->selected    = '';
   $conn->user        = $user;
   $conn->host        = $host;
   $conn->cache       = array();
   $conn->do_cache    = (function_exists("cache_write")&&!$IMAP_NO_CACHE);
   $conn->cache_dirty = array();
   
   if ($my_prefs['sort_field'] == 'INTERNALDATE') {
      $IMAP_USE_INTERNAL_DATE = true;
@@ -482,71 +588,88 @@
   
   //check input
   if (empty($host)) {
      $iil_error .= "Invalid host\n";
   }
   if (empty($user)) {
      $iil_error .= "Invalid user\n";
   }
   if (empty($password)) {
      $iil_error .= "Invalid password\n";
   }
   if (!empty($iil_error)) {
      $iil_error = "Empty host";
      $iil_errornum = -1;
      return false;
   }
   if (empty($user)) {
      $iil_error = "Empty user";
      $iil_errornum = -1;
      return false;
   }
   if (empty($password)) {
      $iil_error = "Empty password";
      $iil_errornum = -1;
      return false;
   }
   if (!$ICL_PORT) {
      $ICL_PORT = 143;
   }
   //check for SSL
   if ($ICL_SSL) {
   if ($ICL_SSL && $ICL_SSL != 'tls') {
      $host = $ICL_SSL . '://' . $host;
   }
   //open socket connection
   $conn->fp = fsockopen($host, $ICL_PORT, $errno, $errstr, 10);
   if (!$conn->fp) {
          $iil_error = "Could not connect to $host at port $ICL_PORT: $errstr";
          $iil_errornum = -1;
          $iil_errornum = -2;
      return false;
   }
   $iil_error .= "Socket connection established\r\n";
   $line       = iil_ReadLine($conn->fp, 1024);
   stream_set_timeout($conn->fp, 10);
   $line = stream_get_line($conn->fp, 8192, "\r\n");
   // Connected to wrong port or connection error?
   if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) {
      if ($line)
         $iil_error = "Wrong startup greeting ($host:$ICL_PORT): $line";
      else
         $iil_error = "Empty startup greeting ($host:$ICL_PORT)";
           $iil_errornum = -2;
           return false;
   }
   // RFC3501 [7.1] optional CAPABILITY response
   // commented out, because it's not working always as should
//   if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
//      $conn->capability = explode(' ', $matches[1]);
//   } else {
      iil_PutLine($conn->fp, "cp01 CAPABILITY");
      do {
         $line = trim(iil_ReadLine($conn->fp, 1024));
   if (preg_match('/\[CAPABILITY ([^]]+)\]/i', $line, $matches)) {
      $conn->capability = explode(' ', strtoupper($matches[1]));
   }
         $conn->message .= "$line\n";
   $conn->message .= $line;
         $a = explode(' ', $line);
         if ($line[0] == '*') {
            while (list($k, $w) = each($a)) {
               if ($w != '*' && $w != 'CAPABILITY')
                   $conn->capability[] = $w;
            }
   // TLS connection
   if ($ICL_SSL == 'tls' && iil_C_GetCapability($conn, 'STARTTLS')) {
           if (version_compare(PHP_VERSION, '5.1.0', '>=')) {
                     iil_PutLine($conn->fp, 'stls000 STARTTLS');
         $line = iil_ReadLine($conn->fp, 4096);
                   if (!iil_StartsWith($line, 'stls000 OK')) {
            $iil_error = "Server responded to STARTTLS with: $line";
            $iil_errornum = -2;
                          return false;
                   }
         if (!stream_socket_enable_crypto($conn->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) {
            $iil_error = "Unable to negotiate TLS";
            $iil_errornum = -2;
            return false;
         }
      } while ($a[0] != 'cp01');
//   }
         // Now we're authenticated, capabilities need to be reread
         iil_C_ClearCapability($conn);
           }
   }
   if (strcasecmp($auth_method, "check") == 0) {
      //check for supported auth methods
      //default to plain text auth
      $auth_method = 'plain';
      //check for CRAM-MD5
      foreach ($conn->capability as $c)
         if (strcasecmp($c, 'AUTH=CRAM_MD5') == 0 ||
            strcasecmp($c, 'AUTH=CRAM-MD5') == 0) {
            $auth_method = 'auth';
            break;
         }
      if (iil_C_GetCapability($conn, 'AUTH=CRAM-MD5') || iil_C_GetCapability($conn, 'AUTH=CRAM_MD5')) {
         $auth_method = 'auth';
      }
      else {
         //default to plain text auth
         $auth_method = 'plain';
      }
   }
   if (strcasecmp($auth_method, 'auth') == 0) {
@@ -563,7 +686,13 @@
         //got a challenge string, try CRAM-5
         $result = iil_C_Authenticate($conn, $user, $password, substr($line,2));
         // stop if server sent BYE response
         if($result == -3) {
                     $iil_error = $conn->error;
                     $iil_errornum = $conn->errorNum;
            return false;
         }
         $conn->message .= "Tried CRAM-MD5: $result \n";
      } else {
         $conn->message .='No challenge ('.htmlspecialchars($line)."), try plain\n";
@@ -574,12 +703,12 @@
   if ((!$result)||(strcasecmp($auth, "plain") == 0)) {
      //do plain text auth
      $result = iil_C_Login($conn, $user, $password);
      $conn->message.="Tried PLAIN: $result \n";
      $conn->message .= "Tried PLAIN: $result \n";
   }
      
   $conn->message .= $auth;
         
   if ($result) {
   if (!is_int($result)) {
      iil_C_Namespace($conn);
      return $conn;
   } else {
@@ -590,7 +719,6 @@
}
function iil_Close(&$conn) {
   iil_C_WriteCache($conn);
   if (iil_PutLine($conn->fp, "I LOGOUT")) {
      fgets($conn->fp, 1024);
      fclose($conn->fp);
@@ -598,100 +726,21 @@
   }
}
function iil_ClearCache($user, $host) {
}
function iil_C_WriteCache(&$conn) {
   //echo "<!-- doing iil_C_WriteCache //-->\n";
   if (!$conn->do_cache) return false;
   if (is_array($conn->cache)) {
      while (list($folder,$data)=each($conn->cache)) {
         if ($folder && is_array($data) && $conn->cache_dirty[$folder]) {
            $key = $folder.".imap";
            $result = cache_write($conn->user, $conn->host, $key, $data, true);
            //echo "<!-- writing $key $data: $result //-->\n";
         }
      }
   }
}
function iil_C_EnableCache(&$conn) {
   $conn->do_cache = true;
}
function iil_C_DisableCache(&$conn) {
   $conn->do_cache = false;
}
function iil_C_LoadCache(&$conn, $folder) {
   if (!$conn->do_cache) {
       return false;
   }
   $key = $folder.'.imap';
   if (!is_array($conn->cache[$folder])) {
      $conn->cache[$folder]       = cache_read($conn->user, $conn->host, $key);
      $conn->cache_dirty[$folder] = false;
   }
}
function iil_C_ExpireCachedItems(&$conn, $folder, $message_set) {
   if (!$conn->do_cache) {
      return;   //caching disabled
   }
   if (!is_array($conn->cache[$folder])) {
          return;   //cache not initialized|empty
   }
   if (count($conn->cache[$folder]) == 0) {
          return;   //cache not initialized|empty
   }
   $uids = iil_C_FetchHeaderIndex($conn, $folder, $message_set, 'UID');
   $num_removed = 0;
   if (is_array($uids)) {
      //echo "<!-- unsetting: ".implode(",",$uids)." //-->\n";
      while (list($n,$uid)=each($uids)) {
         unset($conn->cache[$folder][$uid]);
         //$conn->cache[$folder][$uid] = false;
         //$num_removed++;
      }
      $conn->cache_dirty[$folder] = true;
      //echo '<!--'."\n";
      //print_r($conn->cache);
      //echo "\n".'//-->'."\n";
   } else {
      echo "<!-- failed to get uids: $message_set //-->\n";
   }
   /*
   if ($num_removed>0) {
      $new_cache;
      reset($conn->cache[$folder]);
      while (list($uid,$item)=each($conn->cache[$folder])) {
         if ($item) $new_cache[$uid] = $conn->cache[$folder][$uid];
      }
      $conn->cache[$folder] = $new_cache;
   }
   */
}
function iil_ExplodeQuotedString($delimiter, $string) {
   $quotes=explode('"', $string);
   while ( list($key, $val) = each($quotes)) {
      if (($key % 2) == 1) {
         $quotes[$key] = str_replace($delimiter, "_!@!_", $quotes[$key]);
          }
   $result = array();
   $strlen = strlen($string);
   for ($q=$p=$i=0; $i < $strlen; $i++) {
           if ($string[$i] == "\"" && $string[$i-1] != "\\") {
         $q = $q ? false : true;
      }
      else if (!$q && preg_match("/$delimiter/", $string[$i])) {
         $result[] = substr($string, $p, $i - $p);
         $p = $i + 1;
      }
   }
   $string=implode('"', $quotes);
   $result=explode($delimiter, $string);
   while ( list($key, $val) = each($result) ) {
      $result[$key] = str_replace('_!@!_', $delimiter, $result[$key]);
   }
   $result[] = substr($string, $p);
   return $result;
}
@@ -709,8 +758,8 @@
         $a=explode(' ', $line);
         if (($a[0] == '*') && (strcasecmp($a[2], 'RECENT') == 0)) {
             $result = (int) $a[1];
            }
      } while (!iil_StartsWith($a[0], 'a002'));
              }
      } while (!iil_StartsWith($a[0], 'a002', true));
      iil_PutLine($fp, "a003 LOGOUT");
      fclose($fp);
@@ -730,8 +779,6 @@
      return true;
   }
    
   iil_C_LoadCache($conn, $mailbox);
   if (iil_PutLine($conn->fp, "sel1 SELECT \"".iil_Escape($mailbox).'"')) {
      do {
         $line = chop(iil_ReadLine($conn->fp, 300));
@@ -747,7 +794,7 @@
         else if (preg_match('/\[?PERMANENTFLAGS\s+\(([^\)]+)\)\]/U', $line, $match)) {
            $conn->permanentflags = explode(' ', $match[1]);
         }
      } while (!iil_StartsWith($line, 'sel1'));
      } while (!iil_StartsWith($line, 'sel1', true));
      $a = explode(' ', $line);
@@ -773,7 +820,7 @@
function iil_C_CountMessages(&$conn, $mailbox, $refresh = false) {
   if ($refresh) {
      $conn->selected= '';
      $conn->selected = '';
   }
   
   iil_C_Select($conn, $mailbox);
@@ -795,7 +842,7 @@
function iil_StrToTime($str) {
   $IMAP_MONTHS    = $GLOBALS['IMAP_MONTHS'];
   $IMAP_SERVER_TZ = $GLOBALS['IMAP_SERVER_TR'];
   $IMAP_SERVER_TZ = $GLOBALS['IMAP_SERVER_TZ'];
      
   if ($str) {
           $time1 = strtotime($str);
@@ -837,10 +884,7 @@
function iil_C_Sort(&$conn, $mailbox, $field, $add='', $is_uid=FALSE,
    $encoding = 'US-ASCII') {
   /*  Do "SELECT" command */
   if (!iil_C_Select($conn, $mailbox)) {
       return false;
   }
   $field = strtoupper($field);
   if ($field == 'INTERNALDATE') {
       $field = 'ARRIVAL';
@@ -852,6 +896,11 @@
   if (!$fields[$field]) {
       return false;
   }
   /*  Do "SELECT" command */
   if (!iil_C_Select($conn, $mailbox)) {
       return false;
   }
    
   $is_uid = $is_uid ? 'UID ' : '';
   
@@ -859,24 +908,27 @@
       $add = " $add";
   }
   $fp       = $conn->fp;
   $command  = 's ' . $is_uid . 'SORT (' . $field . ') ';
   $command .= $encoding . ' ALL' . $add;
   $line     = $data = '';
   
   if (!iil_PutLine($fp, $command)) {
   if (!iil_PutLineC($conn->fp, $command)) {
       return false;
   }
   do {
      $line = chop(iil_ReadLine($fp, 1024));
      $line = chop(iil_ReadLine($conn->fp, 1024));
      if (iil_StartsWith($line, '* SORT')) {
         $data .= ($data?' ':'') . substr($line, 7);
          }
   } while ($line[0]!='s');
         $data .= ($data ? ' ' : '') . substr($line, 7);
          } else if (preg_match('/^[0-9 ]+$/', $line)) {
         $data .= $line;
      }
   } while (!iil_StartsWith($line, 's ', true));
   
   if (empty($data)) {
      $conn->error = $line;
      return false;
   $result_code = iil_ParseResult($line);
   if ($result_code != 0) {
                $conn->error = 'iil_C_Sort: ' . $line . "\n";
                return false;
   }
   
   $out = explode(' ',$data);
@@ -994,7 +1046,7 @@
            //one line response, not expected so ignore            
         }
         */
      } while (!iil_StartsWith($line, $key));
      } while (!iil_StartsWith($line, $key, true));
   }else if ($mode == 6) {
@@ -1025,7 +1077,7 @@
         } else {
            $a = explode(' ', $line);
         }
      } while (!iil_StartsWith($a[0], $key));
      } while (!iil_StartsWith($a[0], $key, true));
   } else {
      if ($mode >= 3) {
          $field_name = 'FLAGS';
@@ -1067,7 +1119,7 @@
               $result[$id] = (strpos($haystack, $index_field) > 0 ? "F" : "N");
            }
         }
      } while (!iil_StartsWith($line, $key));
      } while (!iil_StartsWith($line, $key, true));
   }
   //check number of elements...
@@ -1157,50 +1209,7 @@
   }
   $message_set = '1' . ($num>1?':' . $num:'');
   
   //if cache not enabled, just call iil_C_FetchHeaderIndex on 'UID' field
   if (!$conn->do_cache)
      return iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
   //otherwise, let's check cache first
   $key        = $mailbox.'.uids';
   $cache_good = true;
   if ($conn->uid_cache) {
       $data = $conn->uid_cache;
   } else {
       $data = cache_read($conn->user, $conn->host, $key);
   }
   //was anything cached at all?
   if ($data === false) {
       $cache_good = -1;
   }
   //make sure number of messages were the same
   if ($cache_good > 0 && $data['n'] != $num) {
       $cache_good = -2;
   }
   //if everything's okay so far...
   if ($cache_good > 0) {
      //check UIDs of highest mid with current and cached
      $temp = iil_C_Search($conn, $mailbox, 'UID ' . $data['d'][$num]);
      if (!$temp || !is_array($temp) || $temp[0] != $num) {
          $cache_good = -3;
          }
   }
   //if cached data's good, return it
   if ($cache_good > 0) {
      return $data['d'];
   }
   //otherwise, we need to fetch it
   $data      = array('n' => $num, 'd' => array());
   $data['d'] = iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
   cache_write($conn->user, $conn->host, $key, $data);
   $conn->uid_cache = $data;
   return $data['d'];
   return iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, 'UID');
}
function iil_SortThreadHeaders($headers, $index_a, $uids) {
@@ -1227,30 +1236,7 @@
   $uids   = iil_C_FetchUIDs($conn, $mailbox);
   $debug  = false;
   
   /* Get cached records where possible */
   if ($conn->do_cache) {
      $cached = cache_read($conn->user, $conn->host, $mailbox.'.thhd');
      if ($cached && is_array($uids) && count($uids)>0) {
         $needed_set = '';
         foreach ($uids as $id=>$uid) {
            if ($cached[$uid]) {
               $result[$uid]     = $cached[$uid];
               $result[$uid]->id = $id;
            } else {
                $needed_set .= ($needed_set ? ',' : '') . $id;
                     }
         }
         if ($needed_set) {
             $message_set = $needed_set;
         } else {
             $message_set = '';
              }
      }
   }
   $message_set = iil_CompressMessageSet($message_set);
   if ($debug) {
       echo "Still need: ".$message_set;
   }
    
   /* if we're missing any, get them */
   if ($message_set) {
@@ -1268,7 +1254,7 @@
         if ($debug) {
             echo $line . "\n";
              }
         if (ereg('\{[0-9]+\}$', $line)) {
         if (preg_match('/\{[0-9]+\}$/', $line)) {
            $a     = explode(' ', $line);
            $new = array();
@@ -1286,7 +1272,7 @@
                  $new[strtoupper($field_name)] = trim($field_val);
               } else if (ereg('^[[:space:]]', $line)) {
               } else if (preg_match('/^\s+/', $line)) {
                  $new[strtoupper($field_name)] .= trim($line);
               }
            } while ($line[0] != ')');
@@ -1303,13 +1289,6 @@
   /* sort headers */
   if (is_array($index_a)) {
      $result = iil_SortThreadHeaders($result, $index_a, $uids);   
   }
   /* write new set to cache */
   if ($conn->do_cache) {
      if (count($result)!=count($cached)) {
         cache_write($conn->user, $conn->host, $mailbox . '.thhd', $result);
          }
   }
   
   //echo 'iil_FetchThreadHeaders:'."\n";
@@ -1336,7 +1315,7 @@
   $fp        = $conn->fp;
   $debug     = false;
   
   $sbj_filter_pat = '[a-zA-Z]{2,3}(\[[0-9]*\])?:([[:space:]]*)';
   $sbj_filter_pat = '/[a-z]{2,3}(\[[0-9]*\])?:(\s*)/i';
   
   /*  Do "SELECT" command */
   if (!iil_C_Select($conn, $mailbox)) {
@@ -1373,18 +1352,18 @@
      }
        
      /* if subject contains 'RE:' or has in-reply-to header, it's a reply */
      $sbj_pre ='';
      $sbj_pre = '';
      $has_re = false;
      if (eregi($sbj_filter_pat, $new['SUBJECT'])) {
      if (preg_match($sbj_filter_pat, $new['SUBJECT'])) {
          $has_re = true;
      }
          if ($has_re||$new['IN-REPLY-TO']) {
          if ($has_re || $new['IN-REPLY-TO']) {
               $sbj_pre = 'RE:';
      }
        
      /* strip out 're:', 'fw:' etc */
      if ($has_re) {
          $sbj = ereg_replace($sbj_filter_pat, '', $new['SUBJECT']);
          $sbj = preg_replace($sbj_filter_pat, '', $new['SUBJECT']);
      } else {
          $sbj = $new['SUBJECT'];
      }
@@ -1534,11 +1513,10 @@
   return $t_index;
}
function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false)
function iil_C_FetchHeaders(&$conn, $mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='')
{
   global $IMAP_USE_INTERNAL_DATE;
   
   $c      = 0;
   $result = array();
   $fp     = $conn->fp;
   
@@ -1553,43 +1531,28 @@
      $conn->error = "Couldn't select $mailbox";
      return false;
   }
   /* Get cached records where possible */
   if ($conn->do_cache) {
      $uids = iil_C_FetchHeaderIndex($conn, $mailbox, $message_set, "UID");
      if (is_array($uids) && count($conn->cache[$mailbox]>0)) {
         $needed_set = '';
         while (list($id,$uid)=each($uids)) {
            if ($conn->cache[$mailbox][$uid]) {
               $result[$id]     = $conn->cache[$mailbox][$uid];
               $result[$id]->id = $id;
            } else {
                $needed_set.=($needed_set ? ',': '') . $id;
            }
         }
         //echo "<!-- iil_C_FetchHeader\nMessage Set: $message_set\nNeeded Set:$needed_set\n//-->\n";
         if ($needed_set) {
            $message_set = iil_CompressMessageSet($needed_set);
         } else {
            return $result;
         }
      }
   }
   /* FETCH date,from,subject headers */
   $key     = 'fh' . ($c++);
   $prefix     = $uidfetch?' UID':'';
   $request  = $key . $prefix;
   $request .= " FETCH $message_set (BODY.PEEK[HEADER.FIELDS ";
   if ($add)
      $add = ' '.strtoupper(trim($add));
   /* FETCH uid, size, flags and headers */
   $key       = 'FH12';
   $request  = $key . ($uidfetch ? ' UID' : '') . " FETCH $message_set ";
   $request .= "(UID RFC822.SIZE FLAGS INTERNALDATE ";
   if ($bodystr)
      $request .= "BODYSTRUCTURE ";
   $request .= "BODY.PEEK[HEADER.FIELDS ";
   $request .= "(DATE FROM TO SUBJECT REPLY-TO IN-REPLY-TO CC BCC ";
   $request .= "CONTENT-TRANSFER-ENCODING CONTENT-TYPE MESSAGE-ID ";
   $request .= "REFERENCES DISPOSITION-NOTIFICATION-TO X-PRIORITY)])";
   $request .= "REFERENCES DISPOSITION-NOTIFICATION-TO X-PRIORITY".$add.")])";
   if (!iil_PutLine($fp, $request)) {
      return false;
   }
   do {
      $line = chop(iil_ReadLine($fp, 200));
      $line = iil_ReadLine($fp, 1024);
      $line = iil_MultLine($fp, $line);
      $a    = explode(' ', $line);
      if (($line[0] == '*') && ($a[2] == 'FETCH')) {
         $id = $a[1];
@@ -1599,41 +1562,124 @@
         $result[$id]->subject   = '';
         $result[$id]->messageID = 'mid:' . $id;
         $lines = array();
         $ln = 0;
         /*
             Sample reply line:
             * 321 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen)
             INTERNALDATE "16-Nov-2008 21:08:46 +0100" BODYSTRUCTURE (...)
             BODY[HEADER.FIELDS ...
         */
         if (preg_match('/^\* [0-9]+ FETCH \((.*) BODY/s', $line, $matches)) {
            $str = $matches[1];
            // swap parents with quotes, then explode
            $str = preg_replace('/[()]/', '"', $str);
            $a = iil_ExplodeQuotedString(' ', $str);
            // did we get the right number of replies?
            $parts_count = count($a);
            if ($parts_count>=6) {
               for ($i=0; $i<$parts_count; $i=$i+2) {
                  if (strcasecmp($a[$i],'UID') == 0)
                     $result[$id]->uid = $a[$i+1];
                  else if (strcasecmp($a[$i],'RFC822.SIZE') == 0)
                     $result[$id]->size = $a[$i+1];
                  else if (strcasecmp($a[$i],'INTERNALDATE') == 0)
                     $time_str = $a[$i+1];
                  else if (strcasecmp($a[$i],'FLAGS') == 0)
                     $flags_str = $a[$i+1];
               }
               $time_str = str_replace('"', '', $time_str);
               // if time is gmt...
                              $time_str = str_replace('GMT','+0000',$time_str);
               //get timezone
               $time_str      = substr($time_str, 0, -1);
               $time_zone_str = substr($time_str, -5); // extract timezone
               $time_str      = substr($time_str, 0, -5); // remove timezone
               $time_zone     = (float)substr($time_zone_str, 1, 2); // get first two digits
               if ($time_zone_str[3] != '0') {
                        $time_zone += 0.5;  //handle half hour offset
               }
               if ($time_zone_str[0] == '-') {
                       $time_zone = $time_zone * -1.0; //minus?
               }
               //calculate timestamp
                                        $timestamp     = strtotime($time_str); //return's server's time
               $timestamp    -= $time_zone * 3600; //compensate for tz, get GMT
               $result[$id]->internaldate = $time_str;
               $result[$id]->timestamp = $timestamp;
               $result[$id]->date = $time_str;
            }
            // BODYSTRUCTURE
            if($bodystr) {
               while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/s', $line, $m)) {
                  $line2 = iil_ReadLine($fp, 1024);
                  $line .= iil_MultLine($fp, $line2);
               }
               $result[$id]->body_structure = $m[1];
            }
            // the rest of the result
            preg_match('/ BODY\[HEADER.FIELDS \(.*\)\]\s*(.*)/s', $line, $m);
            $reslines = explode("\n", trim($m[1], '"'));
            // re-parse (see below)
            foreach ($reslines as $line) {
               if (ord($line[0])<=32) {
                  $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($line);
               } else {
                  $lines[++$ln] = trim($line);
               }
            }
         }
         /*
            Start parsing headers.  The problem is, some header "lines" take up multiple lines.
            So, we'll read ahead, and if the one we're reading now is a valid header, we'll
            process the previous line.  Otherwise, we'll keep adding the strings until we come
            to the next valid header line.
         */
         $i     = 0;
         $lines = array();
         do {
            $line = chop(iil_ReadLine($fp, 300), "\r\n");
            if (ord($line[0])<=32) {
                $lines[$i] .= (empty($lines[$i])?'':"\n").trim($line);
            } else {
               $i++;
               $lines[$i] = trim($line);
            }
            /*
               The preg_match below works around communigate imap, which outputs " UID <number>)".
               Without this, the while statement continues on and gets the "fh0 OK completed" message.
               If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249.
               This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing
               If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin
               An alternative might be:
               if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break;
               however, unsure how well this would work with all imap clients.
            */
            // The preg_match below works around communigate imap, which outputs " UID <number>)".
            // Without this, the while statement continues on and gets the "FH0 OK completed" message.
            // If this loop gets the ending message, then the outer loop does not receive it from radline on line 1249.
            // This in causes the if statement on line 1278 to never be true, which causes the headers to end up missing
            // If the if statement was changed to pick up the fh0 from this loop, then it causes the outer loop to spin
            // An alternative might be:
            // if (!preg_match("/:/",$line) && preg_match("/\)$/",$line)) break;
            // however, unsure how well this would work with all imap clients.
            if (preg_match("/^\s*UID [0-9]+\)$/", $line)) {
                break;
            }
            // handle FLAGS reply after headers (AOL, Zimbra?)
            if (preg_match('/\s+FLAGS \((.*)\)\)$/', $line, $matches)) {
               $flags_str = $matches[1];
               break;
            }
            if (ord($line[0])<=32) {
               $lines[$ln] .= (empty($lines[$ln])?'':"\n").trim($line);
            } else {
               $lines[++$ln] = trim($line);
            }
         // patch from "Maksim Rubis" <siburny@hotmail.com>
         } while (trim($line[0]) != ')' && strncmp($line, $key, strlen($key)));
         if (strncmp($line, $key, strlen($key))) { 
            //process header, fill iilBasicHeader obj.
            //   initialize
            // process header, fill iilBasicHeader obj.
            // initialize
            if (is_array($headers)) {
               reset($headers);
               while (list($k, $bar) = each($headers)) {
@@ -1641,23 +1687,25 @@
               }
            }
   
            //   create array with header field:data
            // create array with header field:data
            while ( list($lines_key, $str) = each($lines) ) {
               list($field, $string) = iil_SplitHeaderLine($str);
               
               $field  = strtolower($field);
                                        $string = ereg_replace("\n[[:space:]]*"," ",$string);
               $string = preg_replace('/\n\s*/', ' ', $string);
               
               switch ($field) {
               case 'date';
                  $result[$id]->date = $string;
                  $result[$id]->timestamp = iil_StrToTime($string);
                  if (!$IMAP_USE_INTERNAL_DATE) {
                     $result[$id]->date = $string;
                     $result[$id]->timestamp = iil_StrToTime($string);
                  }
                  break;
               case 'from':
                  $result[$id]->from = $string;
                  break;
               case 'to':
                  $result[$id]->to = $string;
                  $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string);
                  break;
               case 'subject':
                  $result[$id]->subject = $string;
@@ -1685,7 +1733,7 @@
                  }
                  break;
               case 'in-reply-to':
                  $result[$id]->in_reply_to = ereg_replace("[\n<>]", '', $string);
                  $result[$id]->in_reply_to = preg_replace('/[\n<>]/', '', $string);
                  break;
               case 'references':
                  $result[$id]->references = $string;
@@ -1702,131 +1750,54 @@
                  if (preg_match('/^(\d+)/', $string, $matches))
                     $result[$id]->priority = intval($matches[1]);
                  break;
               default:
                  if (strlen($field) > 2)
                     $result[$id]->others[$field] = $string;
                  break;
               } // end switch ()
            } // end while ()
         } else {
            $a = explode(' ', $line);
         }
      }
   } while (strcmp($a[0], $key) != 0);
   /*
      FETCH uid, size, flags
      Sample reply line: "* 3 FETCH (UID 2417 RFC822.SIZE 2730 FLAGS (\Seen \Deleted))"
   */
   $command_key = 'fh' . ($c++);
   $request     = $command_key . $prefix;
   $request    .= " FETCH $message_set (UID RFC822.SIZE FLAGS INTERNALDATE)";
   if (!iil_PutLine($fp, $request)) {
       return false;
   }
   do {
      $line = chop(iil_ReadLine($fp, 200));
      //$a = explode(' ', $line);
      //if (($line[0]=="*") && ($a[2]=="FETCH")) {
      if ($line[0] == '*') {
         //echo "<!-- $line //-->\n";
         //get outter most parens
         $open_pos = strpos($line, "(") + 1;
         $close_pos = strrpos($line, ")");
         if ($open_pos && $close_pos) {
            //extract ID from pre-paren
            $pre_str = substr($line, 0, $open_pos);
            $pre_a = explode(' ', $line);
            $id = $pre_a[1];
            //get data
            $len = $close_pos - $open_pos;
            $str = substr($line, $open_pos, $len);
            //swap parents with quotes, then explode
            $str = eregi_replace("[()]", "\"", $str);
            $a = iil_ExplodeQuotedString(' ', $str);
            //did we get the right number of replies?
            $parts_count = count($a);
            if ($parts_count>=8) {
               for ($i=0;$i<$parts_count;$i=$i+2) {
                  if (strcasecmp($a[$i],"UID") == 0) $result[$id]->uid=$a[$i+1];
                  else if (strcasecmp($a[$i],"RFC822.SIZE") == 0) $result[$id]->size=$a[$i+1];
                  else if (strcasecmp($a[$i],"INTERNALDATE") == 0) $time_str = $a[$i+1];
                  else if (strcasecmp($a[$i],"FLAGS") == 0) $flags_str = $a[$i+1];
               }
               // process flags
               $flags_str = eregi_replace('[\\\"]', '', $flags_str);
               $flags_a   = explode(' ', $flags_str);
         // process flags
         if (!empty($flags_str)) {
            $flags_str = preg_replace('/[\\\"]/', '', $flags_str);
            $flags_a   = explode(' ', $flags_str);
               
               if (is_array($flags_a)) {
                  reset($flags_a);
                  while (list($key,$val)=each($flags_a)) {
                     if (strcasecmp($val,'Seen') == 0) {
                         $result[$id]->seen = true;
                     } else if (strcasecmp($val, 'Deleted') == 0) {
                         $result[$id]->deleted=true;
                     } else if (strcasecmp($val, 'Recent') == 0) {
                         $result[$id]->recent = true;
                     } else if (strcasecmp($val, 'Answered') == 0) {
                         $result[$id]->answered = true;
                     } else if (strcasecmp($val, '$Forwarded') == 0) {
                         $result[$id]->forwarded = true;
                     } else if (strcasecmp($val, 'Draft') == 0) {
                         $result[$id]->is_draft = true;
                     } else if (strcasecmp($val, '$MDNSent') == 0) {
                         $result[$id]->mdn_sent = true;
                     } else if (strcasecmp($val, 'Flagged') == 0) {
                          $result[$id]->flagged = true;
                     }
            if (is_array($flags_a)) {
               reset($flags_a);
               while (list(,$val)=each($flags_a)) {
                  if (strcasecmp($val,'Seen') == 0) {
                      $result[$id]->seen = true;
                  } else if (strcasecmp($val, 'Deleted') == 0) {
                      $result[$id]->deleted=true;
                  } else if (strcasecmp($val, 'Recent') == 0) {
                      $result[$id]->recent = true;
                  } else if (strcasecmp($val, 'Answered') == 0) {
                     $result[$id]->answered = true;
                  } else if (strcasecmp($val, '$Forwarded') == 0) {
                     $result[$id]->forwarded = true;
                  } else if (strcasecmp($val, 'Draft') == 0) {
                     $result[$id]->is_draft = true;
                  } else if (strcasecmp($val, '$MDNSent') == 0) {
                     $result[$id]->mdn_sent = true;
                  } else if (strcasecmp($val, 'Flagged') == 0) {
                           $result[$id]->flagged = true;
                  }
                  $result[$id]->flags = $flags_a;
               }
               // if time is gmt...
               $time_str = str_replace('GMT','+0000',$time_str);
               //get timezone
               $time_str      = substr($time_str, 0, -1);
               $time_zone_str = substr($time_str, -5); //extract timezone
               $time_str      = substr($time_str, 1, -6); //remove quotes
               $time_zone     = (float)substr($time_zone_str, 1, 2); //get first two digits
               if ($time_zone_str[3] != '0') {
                   $time_zone += 0.5;  //handle half hour offset
               }
               if ($time_zone_str[0] == '-') {
                  $time_zone = $time_zone * -1.0; //minus?
               }
               $result[$id]->internaldate = $time_str;
               if ($IMAP_USE_INTERNAL_DATE || empty($result[$id]->date)) {
                  //calculate timestamp
                  $timestamp     = strtotime($time_str); //return's server's time
                  $na_timestamp  = $timestamp;
                  $timestamp    -= $time_zone * 3600; //compensate for tz, get GMT
                  $result[$id]->timestamp = $timestamp;
                  $result[$id]->date = $time_str;
               }
               if ($conn->do_cache) {
                  $uid = $result[$id]->uid;
                  $conn->cache[$mailbox][$uid] = $result[$id];
                  $conn->cache_dirty[$mailbox] = true;
               }
               //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";
            } else {
               //echo "<!-- ERROR: $id : $str //-->\n";
               $result[$id]->flags = $flags_a;
            }
         }
      }
   } while (strpos($line, $command_key) === false);
   } while (strcmp($a[0], $key) != 0);
   return $result;
}
function iil_C_FetchHeader(&$conn, $mailbox, $id, $uidfetch=false) {
   $fp = $conn->fp;
   $a  = iil_C_FetchHeaders($conn, $mailbox, $id, $uidfetch);
function iil_C_FetchHeader(&$conn, $mailbox, $id, $uidfetch=false, $bodystr=false, $add='') {
   $a  = iil_C_FetchHeaders($conn, $mailbox, $id, $uidfetch, $bodystr, $add);
   if (is_array($a)) {
      return array_shift($a);
   }
@@ -1896,17 +1867,19 @@
   return $result;
}
function iil_C_Expunge(&$conn, $mailbox) {
function iil_C_Expunge(&$conn, $mailbox, $messages=NULL) {
   if (iil_C_Select($conn, $mailbox)) {
      $c = 0;
      iil_PutLine($conn->fp, "exp1 EXPUNGE");
      $command = $messages ? "UID EXPUNGE $messages" : "EXPUNGE";
      iil_PutLine($conn->fp, "exp1 $command");
      do {
         $line=chop(iil_ReadLine($conn->fp, 100));
         if ($line[0] == '*') {
                     $c++;
              }
      } while (!iil_StartsWith($line, 'exp1'));
      } while (!iil_StartsWith($line, 'exp1', true));
      
      if (iil_ParseResult($line) == 0) {
         $conn->selected = ''; //state has changed, need to reselect         
@@ -1932,16 +1905,15 @@
    
   if (iil_C_Select($conn, $mailbox)) {
      $c = 0;
      iil_PutLine($fp, "flg STORE $messages " . $mod . "FLAGS (" . $flag . ")");
      iil_PutLine($fp, "flg UID STORE $messages " . $mod . "FLAGS (" . $flag . ")");
      do {
         $line=chop(iil_ReadLine($fp, 100));
         if ($line[0] == '*') {
             $c++;
              }
      } while (!iil_StartsWith($line, 'flg'));
      } while (!iil_StartsWith($line, 'flg', true));
      if (iil_ParseResult($line) == 0) {
         iil_C_ExpireCachedItems($conn, $mailbox, $messages);
         return $c;
      }
      $conn->error = $line;
@@ -1981,7 +1953,7 @@
   if (iil_C_Select($conn, $from)) {
      $c=0;
      
      iil_PutLine($fp, "cpy1 COPY $messages \"".iil_Escape($to)."\"");
      iil_PutLine($fp, "cpy1 UID COPY $messages \"".iil_Escape($to)."\"");
      $line=iil_ReadReply($fp);
      return iil_ParseResult($line);
   } else {
@@ -1998,11 +1970,9 @@
function iil_C_CountUnseen(&$conn, $folder) {
   $index = iil_C_Search($conn, $folder, 'ALL UNSEEN');
   if (is_array($index)) {
      $str = implode(',', $index);
      if (empty($str)) {
          return false;
          }
      return count($index);
      if (($cnt = count($index)) && $index[0] != '') {
         return $cnt;
      }
   }
   return false;
}
@@ -2010,11 +1980,7 @@
function iil_C_UID2ID(&$conn, $folder, $uid) {
   if ($uid > 0) {
      $id_a = iil_C_Search($conn, $folder, "UID $uid");
      if (is_array($id_a)) {
         $count = count($id_a);
         if ($count > 1) {
             return false;
              }
      if (is_array($id_a) && count($id_a) == 1) {
         return $id_a[0];
      }
   }
@@ -2032,7 +1998,7 @@
      if (iil_PutLine($fp, "$key FETCH $id (UID)")) {
         do {
            $line=chop(iil_ReadLine($fp, 1024));
            if (eregi("^\* $id FETCH \(UID (.*)\)", $line, $r)) {
            if (preg_match("/^\* $id FETCH \(UID (.*)\)/i", $line, $r)) {
               $result = $r[1];
            }
         } while (!preg_match("/^$key/", $line));
@@ -2047,15 +2013,17 @@
      $c = 0;
      
      $query = 'srch1 SEARCH ' . chop($criteria);
      iil_PutLine($fp, $query);
      if (!iil_PutLineC($fp, $query)) {
         return false;
      }
      do {
         $line=trim(iil_ReadLine($fp, 10000));
         if (eregi("^\* SEARCH", $line)) {
         if (preg_match('/^\* SEARCH/i', $line)) {
            $str = trim(substr($line, 8));
            $messages = explode(' ', $str);
         }
      } while (!iil_StartsWith($line, 'srch1'));
      } while (!iil_StartsWith($line, 'srch1', true));
      $result_code = iil_ParseResult($line);
      if ($result_code == 0) {
          return $messages;
@@ -2068,12 +2036,13 @@
}
function iil_C_Move(&$conn, $messages, $from, $to) {
    $fp = $conn->fp;
    if (!$from || !$to) {
        return -1;
    }
    $r = iil_C_Copy($conn, $messages, $from,$to);
    $r = iil_C_Copy($conn, $messages, $from, $to);
    if ($r==0) {
        return iil_C_Delete($conn, $from, $messages);
    }
@@ -2091,8 +2060,14 @@
 * @see iil_Connect()
 */
function iil_C_GetHierarchyDelimiter(&$conn) {
   global $my_prefs;
   if ($conn->delimiter) {
        return $conn->delimiter;
          return $conn->delimiter;
   }
   if (!empty($my_prefs['delimiter'])) {
           return ($conn->delimiter = $my_prefs['delimiter']);
   }
    
   $fp        = $conn->fp;
@@ -2107,17 +2082,17 @@
      $line=iil_ReadLine($fp, 500);
      if ($line[0] == '*') {
         $line = rtrim($line);
         $a=iil_ExplodeQuotedString(' ', $line);
         $a=iil_ExplodeQuotedString(' ', iil_UnEscape($line));
         if ($a[0] == '*') {
             $delimiter = str_replace('"', '', $a[count($a)-2]);
              }
      }
   } while (!iil_StartsWith($line, 'ghd'));
   } while (!iil_StartsWith($line, 'ghd', true));
   if (strlen($delimiter)>0) {
       return $delimiter;
   }
   //if that fails, try namespace extension
   //try to fetch namespace data
   iil_PutLine($conn->fp, "ns1 NAMESPACE");
@@ -2125,9 +2100,10 @@
      $line = iil_ReadLine($conn->fp, 1024);
      if (iil_StartsWith($line, '* NAMESPACE')) {
         $i = 0;
         $line = iil_UnEscape($line);
         $data = iil_ParseNamespace2(substr($line,11), $i, 0, 0);
      }
   } while (!iil_StartsWith($line, 'ns1'));
   } while (!iil_StartsWith($line, 'ns1', true));
      
   if (!is_array($data)) {
       return false;
@@ -2175,7 +2151,7 @@
   // get folder list
   do {
      $line = iil_ReadLine($fp, 500);
      $line = iil_MultLine($fp, $line);
      $line = iil_MultLine($fp, $line, true);
      $a = explode(' ', $line);
      if (($line[0] == '*') && ($a[1] == 'LIST')) {
@@ -2183,10 +2159,10 @@
              // split one line
         $a = iil_ExplodeQuotedString(' ', $line);
              // last string is folder name
         $folder = trim($a[count($a)-1], '"');
         $folder = preg_replace(array('/^"/', '/"$/'), '', iil_UnEscape($a[count($a)-1]));
            
              if (empty($ignore) || (!empty($ignore)
                     && !eregi($ignore, $folder))) {
                     && !preg_match('/'.preg_quote(ignore, '/').'/i', $folder))) {
                     $folders[$i] = $folder;
              }
            
@@ -2195,7 +2171,7 @@
              // is it a container?
              $i++;
      }
   } while (!iil_StartsWith($line, 'lmb'));
   } while (!iil_StartsWith($line, 'lmb', true));
   if (is_array($folders)) {
           if (!empty($ref)) {
@@ -2240,7 +2216,7 @@
   // get folder list
   do {
      $line = iil_ReadLine($fp, 500);
      $line = iil_MultLine($fp, $line);
      $line = iil_MultLine($fp, $line, true);
      $a    = explode(' ', $line);
        
      if (($line[0] == '*') && ($a[1] == 'LSUB' || $a[1] == 'LIST')) {
@@ -2248,13 +2224,11 @@
            
              // split one line
         $a = iil_ExplodeQuotedString(' ', $line);
              // last string is folder name
              //$folder = UTF7DecodeString(str_replace('"', '', $a[count($a)-1]));
              $folder = trim($a[count($a)-1], '"');
             $folder = preg_replace(array('/^"/', '/"$/'), '', iil_UnEscape($a[count($a)-1]));
         if ((!in_array($folder, $folders)) && (empty($ignore)
                     || (!empty($ignore) && !eregi($ignore, $folder)))) {
                     || (!empty($ignore) && !preg_match('/'.preg_quote(ignore, '/').'/i', $folder)))) {
             $folders[$i] = $folder;
              }
            
@@ -2264,7 +2238,7 @@
              // is it a container?
              $i++;
      }
   } while (!iil_StartsWith($line, 'lsb'));
   } while (!iil_StartsWith($line, 'lsb', true));
   if (is_array($folders)) {
           if (!empty($ref)) {
@@ -2303,68 +2277,95 @@
   return iil_ParseResult($line);
}
function iil_C_FetchPartHeader(&$conn, $mailbox, $id, $part) {
function iil_C_FetchMIMEHeaders(&$conn, $mailbox, $id, $parts) {
   $fp     = $conn->fp;
   $result = false;
   if (($part == 0) || (empty($part))) {
       $part = 'HEADER';
   } else {
           $part .= '.MIME';
   }
   if (iil_C_Select($conn, $mailbox)) {
      $key     = 'fh' . ($c++);
      $request = $key . " FETCH $id (BODY.PEEK[$part])";
      if (!iil_PutLine($fp, $request)) return false;
      do {
         $line = chop(iil_ReadLine($fp, 200));
         $a    = explode(' ', $line);
         if (($line[0] == '*') && ($a[2] == 'FETCH')
                && ($line[strlen($line)-1] != ')')) {
            $line=iil_ReadLine($fp, 300);
            while (trim($line) != ')') {
               $result .= $line;
               $line=iil_ReadLine($fp, 300);
            }
         }
      } while (strcmp($a[0], $key) != 0);
   if (!iil_C_Select($conn, $mailbox)) {
      return false;
   }
   
   $result = false;
   $parts = (array) $parts;
   $key = 'fmh0';
   $peeks = '';
   $idx = 0;
   // format request
   foreach($parts as $part)
      $peeks[] = "BODY.PEEK[$part.MIME]";
   $request = "$key FETCH $id (" . implode(' ', $peeks) . ')';
   // send request
   if (!iil_PutLine($fp, $request)) {
       return false;
   }
   do {
           $line = iil_ReadLine($fp, 1000);
           $line = iil_MultLine($fp, $line);
      if (preg_match('/BODY\[([0-9\.]+)\.MIME\]/', $line, $matches)) {
         $idx = $matches[1];
         $result[$idx] = preg_replace('/^(\* '.$id.' FETCH \()?\s*BODY\['.$idx.'\.MIME\]\s+/', '', $line);
         $result[$idx] = trim($result[$idx], '"');
             $result[$idx] = rtrim($result[$idx], "\t\r\n\0\x0B");
          }
   } while (!iil_StartsWith($line, $key, true));
   return $result;
}
function iil_C_HandlePartBody(&$conn, $mailbox, $id, $part, $mode) {
   /* modes:
        1: return string
        2: print
        3: base64 and print
   */
function iil_C_FetchPartHeader(&$conn, $mailbox, $id, $part) {
   $part = empty($part) ? 'HEADER' : $part.'.MIME';
        return iil_C_HandlePartBody($conn, $mailbox, $id, $part);
}
function iil_C_HandlePartBody(&$conn, $mailbox, $id, $part='', $encoding=NULL, $print=NULL, $file=NULL) {
   
   $fp     = $conn->fp;
   $result = false;
   if (($part == 0) || empty($part)) {
       $part = 'TEXT';
   switch ($encoding) {
      case 'base64':
         $mode = 1;
      break;
      case 'quoted-printable':
         $mode = 2;
      break;
      case 'x-uuencode':
      case 'x-uue':
      case 'uue':
      case 'uuencode':
         $mode = 3;
      break;
      default:
         $mode = 0;
   }
   if (iil_C_Select($conn, $mailbox)) {
          $reply_key = '* ' . $id;
          // format request
      $key     = 'ftch' . ($c++) . ' ';
      $request = $key . "FETCH $id (BODY.PEEK[$part])";
      $key     = 'ftch0';
      $request = $key . " FETCH $id (BODY.PEEK[$part])";
          // send request
      if (!iil_PutLine($fp, $request)) {
          return false;
          }
          // receive reply line
          do {
              $line = chop(iil_ReadLine($fp, 1000));
              $a    = explode(' ', $line);
          } while ($a[2] != 'FETCH');
          } while (!($end = iil_StartsWith($line, $key, true)) && $a[2] != 'FETCH');
          $len = strlen($line);
          if ($line[$len-1] == ')') {
      // handle empty "* X FETCH ()" response
          if ($line[$len-1] == ')' && $line[$len-2] != '(') {
              // one line response, get everything between first and last quotes
         if (substr($line, -4, 3) == 'NIL') {
            // NIL response
@@ -2376,11 +2377,13 @@
            $result = substr($line, $from, $len);
         }
       
                   if ($mode == 2) {
                      echo $result;
                   } else if ($mode == 3) {
                      echo base64_decode($result);
                   }
              if ($mode == 1)
            $result = base64_decode($result);
         else if ($mode == 2)
            $result = quoted_printable_decode($result);
         else if ($mode == 3)
            $result = convert_uudecode($result);
          } else if ($line[$len-1] == '}') {
                   //multi-line request, find sizes of content and receive that many bytes
              $from     = strpos($line, '{') + 1;
@@ -2388,57 +2391,94 @@
              $len      = $to - $from;
                   $sizeStr  = substr($line, $from, $len);
              $bytes    = (int)$sizeStr;
                   $received = 0;
              while ($received < $bytes) {
                     $remaining = $bytes - $received;
                      $line      = iil_ReadLine($fp, 1024);
         $prev     = '';
              while ($bytes > 0) {
                          $line      = iil_ReadLine($fp, 1024);
                     $len       = strlen($line);
                
                      if ($len > $remaining) {
                             $line = substr($line, 0, $remaining);
                      if ($len > $bytes) {
                             $line = substr($line, 0, $bytes);
                      }
                     $received += strlen($line);
                     $bytes -= strlen($line);
                      if ($mode == 1) {
                             $result .= rtrim($line, "\t\r\n\0\x0B") . "\n";
                      } else if ($mode == 2) {
                             echo rtrim($line, "\t\r\n\0\x0B") . "\n"; flush();
                      } else if ($mode == 3) {
                        echo base64_decode($line); flush();
                     }
               $line = rtrim($line, "\t\r\n\0\x0B");
               // create chunks with proper length for base64 decoding
               $line = $prev.$line;
               $length = strlen($line);
               if ($length % 4) {
                  $length = floor($length / 4) * 4;
                  $prev = substr($line, $length);
                  $line = substr($line, 0, $length);
               }
               else
                  $prev = '';
               if ($file)
                  fwrite($file, base64_decode($line));
                        else if ($print)
                  echo base64_decode($line);
               else
                  $result .= base64_decode($line);
            } else if ($mode == 2) {
               $line = rtrim($line, "\t\r\0\x0B");
               if ($file)
                  fwrite($file, quoted_printable_decode($line));
                        else if ($print)
                  echo quoted_printable_decode($line);
               else
                  $result .= quoted_printable_decode($line);
            } else if ($mode == 3) {
               $line = rtrim($line, "\t\r\n\0\x0B");
               if ($line == 'end' || preg_match('/^begin\s+[0-7]+\s+.+$/', $line))
                  continue;
               if ($file)
                  fwrite($file, convert_uudecode($line));
                        else if ($print)
                  echo convert_uudecode($line);
               else
                  $result .= convert_uudecode($line);
            } else {
               $line = rtrim($line, "\t\r\n\0\x0B");
               if ($file)
                  fwrite($file, $line . "\n");
                             else if ($print)
                  echo $line . "\n";
               else
                  $result .= $line . "\n";
            }
              }
          }
           // read in anything up until 'til last line
      do {
              $line = iil_ReadLine($fp, 1024);
      } while (!iil_StartsWith($line, $key));
           // read in anything up until last line
      if (!$end)
         do {
                 $line = iil_ReadLine($fp, 1024);
         } while (!iil_StartsWith($line, $key, true));
        
          if ($result) {
             $result = rtrim($result, "\t\r\n\0\x0B");
              return $result; // substr($result, 0, strlen($result)-1);
         if ($file) {
            fwrite($file, $result);
         } else if ($print) {
            echo $result;
         } else
            return $result; // substr($result, 0, strlen($result)-1);
          }
          
      return false;
   } else {
      echo 'Select failed.';
      return true;
   }
    
   if ($mode==1) {
          return $result;
   }
   return $received;
   return false;
}
function iil_C_FetchPartBody(&$conn, $mailbox, $id, $part) {
   return iil_C_HandlePartBody($conn, $mailbox, $id, $part, 1);
function iil_C_FetchPartBody(&$conn, $mailbox, $id, $part, $file=NULL) {
   return iil_C_HandlePartBody($conn, $mailbox, $id, $part, NULL, NULL, $file);
}
function iil_C_PrintPartBody(&$conn, $mailbox, $id, $part) {
   iil_C_HandlePartBody($conn, $mailbox, $id, $part, 2);
}
function iil_C_PrintBase64Body(&$conn, $mailbox, $id, $part) {
   iil_C_HandlePartBody($conn, $mailbox, $id, $part, 3);
   iil_C_HandlePartBody($conn, $mailbox, $id, $part, NULL, true, NULL);
}
function iil_C_CreateFolder(&$conn, $folder) {
@@ -2586,47 +2626,46 @@
   return $result;
}
function iil_C_PrintSource(&$conn, $folder, $id, $part) {
   $header = iil_C_FetchPartHeader($conn, $folder, $id, $part);
   //echo str_replace("\r", '', $header);
   echo $header;
   echo iil_C_PrintPartBody($conn, $folder, $id, $part);
}
function iil_C_GetQuota(&$conn) {
/*
 * GETQUOTAROOT "INBOX"
 * QUOTAROOT INBOX user/rchijiiwa1
 * QUOTA user/rchijiiwa1 (STORAGE 654 9765)
 b OK Completed
 * OK Completed
 */
   $fp         = $conn->fp;
   $result     = false;
   $quota_line = '';
   $quota_lines = array();
   
   //get line containing quota info
   // get line(s) containing quota info
   if (iil_PutLine($fp, 'QUOT1 GETQUOTAROOT "INBOX"')) {
      do {
         $line=chop(iil_ReadLine($fp, 5000));
         if (iil_StartsWith($line, '* QUOTA ')) {
            $quota_line = $line;
            $quota_lines[] = $line;
              }
      } while (!iil_StartsWith($line, 'QUOT1'));
      } while (!iil_StartsWith($line, 'QUOT1', true));
   }
   
   //return false if not found, parse if found
   if (!empty($quota_line)) {
      $quota_line   = eregi_replace('[()]', '', $quota_line);
   // return false if not found, parse if found
   $min_free = PHP_INT_MAX;
   foreach ($quota_lines as $key => $quota_line) {
      $quota_line   = preg_replace('/[()]/', '', $quota_line);
      $parts        = explode(' ', $quota_line);
      $storage_part = array_search('STORAGE', $parts);
      if ($storage_part > 0) {
         $result = array();
         $used   = $parts[$storage_part+1];
         $total  = $parts[$storage_part+2];
      if (!$storage_part) continue;
      $used   = intval($parts[$storage_part+1]);
      $total   = intval($parts[$storage_part+2]);
      $free   = $total - $used;
      // return lowest available space from all quotas
      if ($free < $min_free) {
              $min_free = $free;
         $result['used']    = $used;
         $result['total']   = (empty($total)?"??":$total);
         $result['percent'] = (empty($total)?"??":round(($used/$total)*100));
         $result['total']   = $total;
         $result['percent'] = min(100, round(($used/max(1,$total))*100));
         $result['free']    = 100 - $result['percent'];
      }
   }
@@ -2636,7 +2675,7 @@
function iil_C_ClearFolder(&$conn, $folder) {
   $num_in_trash = iil_C_CountMessages($conn, $folder);
   if ($num_in_trash > 0) {
      iil_C_Delete($conn, $folder, '1:' . $num_in_trash);
      iil_C_Delete($conn, $folder, '1:*');
   }
   return (iil_C_Expunge($conn, $folder) >= 0);
}