From 1d51658ba5acce01cd2e6bb30579aaccdbc41214 Mon Sep 17 00:00:00 2001 From: alecpl Date: Tue, 18 May 2010 11:58:12 +0000 Subject: - Parse untagged CAPABILITY response for LOGIN command (#1486742) --- CHANGELOG | 1 + program/include/rcube_imap_generic.php | 245 +++++++++++++++++---------------- 2 files changed, 129 insertions(+), 117 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index 32d907bca..2df1a56fe 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,7 @@ CHANGELOG RoundCube Webmail =========================== +- Parse untagged CAPABILITY response for LOGIN command (#1486742) - Renamed all php-cli scripts to use .sh extension - Some files from /bin + spellchecking actions moved to the new 'utils' task - Added thread tree icons diff --git a/program/include/rcube_imap_generic.php b/program/include/rcube_imap_generic.php index 6ca6c8f5a..1cb4d4cc2 100644 --- a/program/include/rcube_imap_generic.php +++ b/program/include/rcube_imap_generic.php @@ -30,7 +30,7 @@ * Struct representing an e-mail message header * * @package Mail - * @author Aleksander Machniak + * @author Aleksander Machniak */ class rcube_mail_header { @@ -80,7 +80,7 @@ class iilBasicHeader extends rcube_mail_header * PHP based wrapper class to connect to an IMAP server * * @package Mail - * @author Aleksander Machniak + * @author Aleksander Machniak */ class rcube_imap_generic { @@ -118,7 +118,7 @@ class rcube_imap_generic function __construct() { } - + private function putLine($string, $endln=true) { if (!$this->fp) @@ -127,7 +127,7 @@ class rcube_imap_generic if (!empty($this->prefs['debug_mode'])) { write_log('imap', 'C: '. rtrim($string)); } - + return fputs($this->fp, $string . ($endln ? "\r\n" : '')); } @@ -173,16 +173,16 @@ class rcube_imap_generic if (!$this->fp) { return NULL; } - + if (!$size) { $size = 1024; } - + do { if (feof($this->fp)) { return $line ? $line : NULL; } - + $buffer = fgets($this->fp, $size); if ($buffer === false) { @@ -204,11 +204,11 @@ class rcube_imap_generic $line = chop($line); if (preg_match('/\{[0-9]+\}$/', $line)) { $out = ''; - + preg_match_all('/(.*)\{([0-9]+)\}$/', $line, $a); $bytes = $a[2][0]; while (strlen($out) < $bytes) { - $line = $this->readBytes($bytes); + $line = $this->readBytes($bytes); if ($line === NULL) break; $out .= $line; @@ -216,7 +216,7 @@ class rcube_imap_generic $line = $a[1][0] . '"' . ($escape ? $this->Escape($out) : $out) . '"'; } - + return $line; } @@ -237,17 +237,23 @@ class rcube_imap_generic } $len = $data_len; } - + return $data; } // don't use it in loops, until you exactly know what you're doing - private function readReply() + private function readReply($untagged=null) { do { $line = trim($this->readLine(1024)); + // store untagged response lines + if ($line[0] == '*') + $untagged[] = $line; } while ($line[0] == '*'); + if ($untagged) + $untagged = join("\n", $untagged); + return $line; } @@ -331,7 +337,7 @@ class rcube_imap_generic return false; } - // get capabilities (only once) because initial + // get capabilities (only once) because initial // optional CAPABILITY response may differ $this->capability = array(); @@ -348,7 +354,7 @@ class rcube_imap_generic } } } while ($a[0] != 'cp01'); - + $this->capability_readed = true; if (in_array($name, $this->capability)) { @@ -368,7 +374,7 @@ class rcube_imap_generic { $ipad = ''; $opad = ''; - + // initialize ipad, opad for ($i=0; $i<64; $i++) { $ipad .= chr(0x36); @@ -380,17 +386,17 @@ class rcube_imap_generic for ($i=0; $i<$padLen; $i++) { $pass .= chr(0); } - + // generate hash $hash = md5($this->_xor($pass,$opad) . pack("H*", md5($this->_xor($pass, $ipad) . base64_decode($encChallenge)))); - + // generate reply $reply = base64_encode($user . ' ' . $hash); - + // send result, get reply $this->putLine($reply); $line = $this->readLine(1024); - + // process result $result = $this->parseResult($line); if ($result == 0) { @@ -408,7 +414,12 @@ class rcube_imap_generic { $this->putLine('a001 LOGIN "'.$this->escape($user).'" "'.$this->escape($password).'"'); - $line = $this->readReply(); + $line = $this->readReply($untagged); + + // re-set capabilities list if untagged CAPABILITY response provided + if (preg_match('/\* CAPABILITY (.+)/i', $untagged, $matches)) { + $this->capability = explode(' ', strtoupper($matches[1])); + } // process result $result = $this->parseResult($line); @@ -420,7 +431,7 @@ class rcube_imap_generic @fclose($this->fp); $this->fp = false; - + $this->error = "Authentication for $user failed (LOGIN): $line"; $this->errornum = $result; @@ -433,11 +444,11 @@ class rcube_imap_generic $this->rootdir = $this->prefs['rootdir']; return true; } - + if (!$this->getCapability('NAMESPACE')) { return false; } - + if (!$this->putLine("ns1 NAMESPACE")) { return false; } @@ -463,12 +474,12 @@ class rcube_imap_generic if (count($first_userspace)!=2) { return false; } - + $this->rootdir = $first_userspace[0]; $this->delimiter = $first_userspace[1]; $this->prefs['rootdir'] = substr($this->rootdir, 0, -1); $this->prefs['delimiter'] = $this->delimiter; - + return true; } @@ -478,8 +489,8 @@ class rcube_imap_generic * INBOX.foo -> . * INBOX/foo -> / * INBOX\foo -> \ - * - * @return mixed A delimiter (string), or false. + * + * @return mixed A delimiter (string), or false. * @see connect() */ function getHierarchyDelimiter() @@ -497,7 +508,7 @@ class rcube_imap_generic if (!$this->putLine('ghd LIST "" ""')) { return false; } - + do { $line = $this->readLine(500); if ($line[0] == '*') { @@ -531,7 +542,7 @@ class rcube_imap_generic if (!is_array($data)) { return false; } - + // extract user space data (opposed to global/shared space) $user_space_data = $data[0]; if (!is_array($user_space_data)) { @@ -545,7 +556,7 @@ class rcube_imap_generic } // extract delimiter - $delimiter = $first_userspace[1]; + $delimiter = $first_userspace[1]; return $delimiter; } @@ -566,7 +577,7 @@ class rcube_imap_generic $message = "INITIAL: $auth_method\n"; $result = false; - + // initialize connection $this->error = ''; $this->errornum = 0; @@ -648,7 +659,7 @@ class rcube_imap_generic $this->errornum = -2; return false; } - + // Now we're authenticated, capabilities need to be reread $this->clearCapability(); } @@ -675,18 +686,18 @@ class rcube_imap_generic if ($line[0] == '+') { // got a challenge string, try CRAM-MD5 $result = $this->authenticate($user, $password, substr($line,2)); - + // stop if server sent BYE response if ($result == -3) { return false; } } - + if (!is_resource($result) && $orig_method == 'CHECK') { $auth_method = 'PLAIN'; } } - + if ($auth_method == 'PLAIN') { // do plain text auth $result = $this->login($user, $password); @@ -727,7 +738,7 @@ class rcube_imap_generic if ($this->selected == $mailbox) { return true; } - + if ($this->putLine("sel1 SELECT \"".$this->escape($mailbox).'"')) { do { $line = chop($this->readLine(300)); @@ -763,7 +774,7 @@ class rcube_imap_generic if (empty($mailbox)) { $mailbox = 'INBOX'; } - + $this->select($mailbox); if ($this->selected == $mailbox) { return $this->recent; @@ -777,7 +788,7 @@ class rcube_imap_generic if ($refresh) { $this->selected = ''; } - + $this->select($mailbox); if ($this->selected == $mailbox) { return $this->exists; @@ -792,7 +803,7 @@ class rcube_imap_generic if ($field == 'INTERNALDATE') { $field = 'ARRIVAL'; } - + $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1, 'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1); @@ -804,9 +815,9 @@ class rcube_imap_generic if (!$this->select($mailbox)) { return false; } - + $is_uid = $is_uid ? 'UID ' : ''; - + // message IDs if (is_array($add)) $add = $this->compressMessageSet(join(',', $add)); @@ -828,14 +839,14 @@ class rcube_imap_generic $data .= $line; } } while (!$this->startsWith($line, 's ', true, true)); - + $result_code = $this->parseResult($line); - + if ($result_code != 0) { $this->error = "Sort: $line"; return false; } - + return preg_split('/\s+/', $data, -1, PREG_SPLIT_NO_EMPTY); } @@ -851,9 +862,9 @@ class rcube_imap_generic return false; } } - + $index_field = empty($index_field) ? 'DATE' : strtoupper($index_field); - + $fields_a['DATE'] = 1; $fields_a['INTERNALDATE'] = 4; $fields_a['ARRIVAL'] = 4; @@ -877,7 +888,7 @@ class rcube_imap_generic if (!$this->select($mailbox)) { return false; } - + // build FETCH command string $key = 'fhi0'; $cmd = $uidfetch ? 'UID FETCH' : 'FETCH'; @@ -912,7 +923,7 @@ class rcube_imap_generic if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) { $id = $m[1]; $flags = NULL; - + if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) { $flags = explode(' ', strtoupper($matches[1])); if (in_array('\\DELETED', $flags)) { @@ -962,28 +973,28 @@ class rcube_imap_generic } } while (!$this->startsWith($line, $key, true, true)); - return $result; + return $result; } private function compressMessageSet($message_set) { - // given a comma delimited list of independent mid's, + // given a comma delimited list of independent mid's, // compresses by grouping sequences together - + // if less than 255 bytes long, let's not bother if (strlen($message_set)<255) { return $message_set; } - + // see if it's already been compress if (strpos($message_set, ':') !== false) { return $message_set; } - + // separate, then sort $ids = explode(',', $message_set); sort($ids); - + $result = array(); $start = $prev = $ids[0]; @@ -1006,7 +1017,7 @@ class rcube_imap_generic } else { $result[] = $start.':'.$prev; } - + // return as comma separated string return implode(',', $result); } @@ -1051,14 +1062,14 @@ class rcube_imap_generic $message_set = join(',', $message_set); else if (empty($message_set)) $message_set = '1:*'; - + return $this->fetchHeaderIndex($mailbox, $message_set, 'UID', false); } function fetchHeaders($mailbox, $message_set, $uidfetch=false, $bodystr=false, $add='') { $result = array(); - + if (!$this->select($mailbox)) { return false; } @@ -1089,15 +1100,15 @@ class rcube_imap_generic do { $line = $this->readLine(1024); $line = $this->multLine($line); - + if (!$line) break; - + $a = explode(' ', $line); if (($line[0] == '*') && ($a[2] == 'FETCH')) { $id = $a[1]; - + $result[$id] = new rcube_mail_header; $result[$id]->id = $id; $result[$id]->subject = ''; @@ -1133,16 +1144,16 @@ class rcube_imap_generic } $time_str = str_replace('"', '', $time_str); - + // if time is gmt... $time_str = str_replace('GMT','+0000',$time_str); - + $result[$id]->internaldate = $time_str; $result[$id]->timestamp = $this->StrToTime($time_str); $result[$id]->date = $time_str; } - // BODYSTRUCTURE + // BODYSTRUCTURE if($bodystr) { while (!preg_match('/ BODYSTRUCTURE (.*) BODY\[HEADER.FIELDS/s', $line, $m)) { $line2 = $this->readLine(1024); @@ -1168,13 +1179,13 @@ class rcube_imap_generic // 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. - + do { $line = chop($this->readLine(300), "\r\n"); // The preg_match below works around communigate imap, which outputs " UID )". // 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. + // 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: @@ -1198,7 +1209,7 @@ class rcube_imap_generic // patch from "Maksim Rubis" } while ($line[0] != ')' && !$this->startsWith($line, $key, true)); - if (strncmp($line, $key, strlen($key))) { + if (strncmp($line, $key, strlen($key))) { // process header, fill rcube_mail_header obj. // initialize if (is_array($headers)) { @@ -1211,10 +1222,10 @@ class rcube_imap_generic // create array with header field:data while ( list($lines_key, $str) = each($lines) ) { list($field, $string) = $this->splitHeaderLine($str); - + $field = strtolower($field); $string = preg_replace('/\n\s*/', ' ', $string); - + switch ($field) { case 'date'; $result[$id]->date = $string; @@ -1280,7 +1291,7 @@ class rcube_imap_generic if (!empty($flags_str)) { $flags_str = preg_replace('/[\\\"]/', '', $flags_str); $flags_a = explode(' ', $flags_str); - + if (is_array($flags_a)) { // reset($flags_a); foreach($flags_a as $flag) { @@ -1343,12 +1354,12 @@ class rcube_imap_generic $c = count($a); if ($c > 0) { - + // Strategy: // First, we'll create an "index" array. - // Then, we'll use sort() on that array, + // Then, we'll use sort() on that array, // and use that to sort the main array. - + // create "index" array $index = array(); reset($a); @@ -1366,7 +1377,7 @@ class rcube_imap_generic } $index[$key]=$data; } - + // sort index $i = 0; if ($flag == 'ASC') { @@ -1375,7 +1386,7 @@ class rcube_imap_generic arsort($index); } - // form new array based on index + // form new array based on index $result = array(); reset($index); while (list($key, $val) = each($index)) { @@ -1383,7 +1394,7 @@ class rcube_imap_generic $i++; } } - + return $result; } @@ -1392,7 +1403,7 @@ class rcube_imap_generic if (!$this->select($mailbox)) { return -1; } - + $c = 0; $command = $messages ? "UID EXPUNGE $messages" : "EXPUNGE"; @@ -1406,7 +1417,7 @@ class rcube_imap_generic $c++; } } while (!$this->startsWith($line, 'exp1', true, true)); - + if ($this->parseResult($line) == 0) { $this->selected = ''; // state has changed, need to reselect return $c; @@ -1420,13 +1431,13 @@ class rcube_imap_generic if ($mod != '+' && $mod != '-') { return -1; } - + $flag = $this->flags[strtoupper($flag)]; - + if (!$this->select($mailbox)) { return -1; } - + $c = 0; if (!$this->putLine("flg UID STORE $messages {$mod}FLAGS ($flag)")) { return false; @@ -1464,11 +1475,11 @@ class rcube_imap_generic if (empty($from) || empty($to)) { return -1; } - + if (!$this->select($from)) { return -1; } - + $this->putLine("cpy1 UID COPY $messages \"".$this->escape($to)."\""); $line = $this->readReply(); return $this->parseResult($line); @@ -1529,7 +1540,7 @@ class rcube_imap_generic $node += $this->parseThread($str, $start + 1, $off - 1, $root, $parent, $depth, $depthmap, $haschildren); } } - + return $node; } @@ -1542,7 +1553,7 @@ class rcube_imap_generic $encoding = $encoding ? trim($encoding) : 'US-ASCII'; $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES'; $criteria = $criteria ? 'ALL '.trim($criteria) : 'ALL'; - + if (!$this->putLineC("thrd1 THREAD $algorithm $encoding $criteria")) { return false; } @@ -1562,7 +1573,7 @@ class rcube_imap_generic } $this->error = "Thread: $line"; - return false; + return false; } function search($folder, $criteria, $return_uid=false) @@ -1593,7 +1604,7 @@ class rcube_imap_generic } $this->error = "Search: $line"; - return false; + return false; } function move($messages, $from, $to) @@ -1601,7 +1612,7 @@ class rcube_imap_generic if (!$from || !$to) { return -1; } - + $r = $this->copy($messages, $from, $to); if ($r==0) { @@ -1625,11 +1636,11 @@ class rcube_imap_generic if (empty($mailbox)) { $mailbox = '*'; } - + if (empty($ref) && $this->rootdir) { $ref = $this->rootdir; } - + if ($subscribed) { $key = 'lsb'; $command = 'LSUB'; @@ -1644,7 +1655,7 @@ class rcube_imap_generic $this->error = "Couldn't send $command command"; return false; } - + // get folder list do { $line = $this->readLine(500); @@ -1677,7 +1688,7 @@ class rcube_imap_generic if (!$this->select($mailbox)) { return false; } - + $result = false; $parts = (array) $parts; $key = 'fmh0'; @@ -1688,14 +1699,14 @@ class rcube_imap_generic // format request foreach($parts as $part) $peeks[] = "BODY.PEEK[$part.$type]"; - + $request = "$key FETCH $id (" . implode(' ', $peeks) . ')'; // send request if (!$this->putLine($request)) { return false; } - + do { $line = $this->readLine(1000); $line = $this->multLine($line); @@ -1740,7 +1751,7 @@ class rcube_imap_generic default: $mode = 0; } - + $reply_key = '* ' . $id; $result = false; @@ -1788,11 +1799,11 @@ class rcube_imap_generic $sizeStr = substr($line, $from, $len); $bytes = (int)$sizeStr; $prev = ''; - + while ($bytes > 0) { $line = $this->readLine(1024); $len = strlen($line); - + if ($len > $bytes) { $line = substr($line, 0, $bytes); $len = strlen($line); @@ -1811,7 +1822,7 @@ class rcube_imap_generic } else $prev = ''; - + if ($file) fwrite($file, base64_decode($line)); else if ($print) @@ -1847,7 +1858,7 @@ class rcube_imap_generic } } } - + // read in anything up until last line if (!$end) do { @@ -1922,7 +1933,7 @@ class rcube_imap_generic { $query = 'usub1 UNSUBSCRIBE "' . $this->escape($folder) . '"'; $this->putLine($query); - + $line = trim($this->readLine(512)); return ($this->parseResult($line) == 0); } @@ -1959,7 +1970,7 @@ class rcube_imap_generic do { $line = $this->readLine(); } while (!$this->startsWith($line, 'a ', true, true)); - + $result = ($this->parseResult($line) == 0); if (!$result) { $this->error = $line; @@ -1976,17 +1987,17 @@ class rcube_imap_generic if (!$folder) { return false; } - + // open message file $in_fp = false; if (file_exists(realpath($path))) { $in_fp = fopen($path, 'r'); } - if (!$in_fp) { + if (!$in_fp) { $this->error = "Couldn't open $path for reading"; return false; } - + $len = filesize($path); if (!$len) { return false; @@ -2036,7 +2047,7 @@ class rcube_imap_generic return $result; } - + $this->error = "Couldn't send command \"$request\""; return false; } @@ -2074,7 +2085,7 @@ class rcube_imap_generic */ $result = false; $quota_lines = array(); - + // get line(s) containing quota info if ($this->putLine('QUOT1 GETQUOTAROOT "INBOX"')) { do { @@ -2084,24 +2095,24 @@ class rcube_imap_generic } } while (!$this->startsWith($line, 'QUOT1', true, true)); } - + // 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) continue; - + $used = intval($parts[$storage_part+1]); $total = intval($parts[$storage_part+2]); - $free = $total - $used; - + $free = $total - $used; + // return lowest available space from all quotas - if ($free < $min_free) { - $min_free = $free; + if ($free < $min_free) { + $min_free = $free; $result['used'] = $used; $result['total'] = $total; $result['percent'] = min(100, round(($used/max(1,$total))*100)); @@ -2137,7 +2148,7 @@ class rcube_imap_generic $ts = (int) $ts; - return $ts < 0 ? 0 : $ts; + return $ts < 0 ? 0 : $ts; } private function SplitHeaderLine($string) @@ -2162,7 +2173,7 @@ class rcube_imap_generic $data = array(); $in_quotes = false; $elem = 0; - + for ($i;$i<$len;$i++) { $c = (string)$str[$i]; if ($c == '(' && !$in_quotes) { @@ -2185,18 +2196,18 @@ class rcube_imap_generic $data[$elem].=$c; } } - + return $data; } private function escape($string) { - return strtr($string, array('"'=>'\\"', '\\' => '\\\\')); + return strtr($string, array('"'=>'\\"', '\\' => '\\\\')); } private function unEscape($string) { - return strtr($string, array('\\"'=>'"', '\\\\' => '\\')); + return strtr($string, array('\\"'=>'"', '\\\\' => '\\')); } } -- cgit v1.2.3