From 2965a981b7ec22866fbdf2d567d87e2d068d3617 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli <thomas@roundcube.net> Date: Fri, 31 Jul 2015 16:04:08 -0400 Subject: [PATCH] Allow to search and import missing PGP pubkeys from keyservers using Publickey.js --- program/lib/Roundcube/rcube_imap_generic.php | 347 +++++++++++++++++++++++++++++++++------------------------ 1 files changed, 199 insertions(+), 148 deletions(-) diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 5aaad0a..1a55fad 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -1,6 +1,6 @@ <?php -/** +/* +-----------------------------------------------------------------------+ | This file is part of the Roundcube Webmail client | | Copyright (C) 2005-2012, The Roundcube Dev Team | @@ -47,8 +47,6 @@ 'MDNSENT' => '$MDNSent', '*' => '\\*', ); - - public static $mupdate; protected $fp; protected $host; @@ -1108,7 +1106,8 @@ // folder name with spaces. Let's try to handle this situation if (!is_array($items) && ($pos = strpos($response, '(')) !== false) { $response = substr($response, $pos); - $items = $this->tokenizeResponse($response, 1); + $items = $this->tokenizeResponse($response, 1); + if (!is_array($items)) { return $result; } @@ -1146,7 +1145,7 @@ } // Clear internal status cache - unset($this->data['STATUS:'.$mailbox]); + $this->clear_status_cache($mailbox); if (!empty($messages) && $messages != '*' && $this->hasCapability('UIDPLUS')) { $messages = self::compressMessageSet($messages); @@ -1293,15 +1292,15 @@ * * @param string $ref Reference name * @param string $mailbox Mailbox name - * @param array $status_opts (see self::_listMailboxes) + * @param array $return_opts (see self::_listMailboxes) * @param array $select_opts (see self::_listMailboxes) * - * @return array List of mailboxes or hash of options if $status_opts argument - * is non-empty. + * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response + * is requested, False on error. */ - function listMailboxes($ref, $mailbox, $status_opts=array(), $select_opts=array()) + function listMailboxes($ref, $mailbox, $return_opts=array(), $select_opts=array()) { - return $this->_listMailboxes($ref, $mailbox, false, $status_opts, $select_opts); + return $this->_listMailboxes($ref, $mailbox, false, $return_opts, $select_opts); } /** @@ -1309,14 +1308,14 @@ * * @param string $ref Reference name * @param string $mailbox Mailbox name - * @param array $status_opts (see self::_listMailboxes) + * @param array $return_opts (see self::_listMailboxes) * - * @return array List of mailboxes or hash of options if $status_opts argument - * is non-empty. + * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response + * is requested, False on error. */ - function listSubscribed($ref, $mailbox, $status_opts=array()) + function listSubscribed($ref, $mailbox, $return_opts=array()) { - return $this->_listMailboxes($ref, $mailbox, true, $status_opts, NULL); + return $this->_listMailboxes($ref, $mailbox, true, $return_opts, NULL); } /** @@ -1325,18 +1324,18 @@ * @param string $ref Reference name * @param string $mailbox Mailbox name * @param bool $subscribed Enables returning subscribed mailboxes only - * @param array $status_opts List of STATUS options - * (RFC5819: LIST-STATUS: MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN) - * or RETURN options (RFC5258: LIST_EXTENDED: SUBSCRIBED, CHILDREN) + * @param array $return_opts List of RETURN options (RFC5819: LIST-STATUS, RFC5258: LIST-EXTENDED) + * Possible: MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN, + * MYRIGHTS, SUBSCRIBED, CHILDREN * @param array $select_opts List of selection options (RFC5258: LIST-EXTENDED) * Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE, * SPECIAL-USE (RFC6154) * - * @return array List of mailboxes or hash of options if $status_ops argument - * is non-empty. + * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response + * is requested, False on error. */ protected function _listMailboxes($ref, $mailbox, $subscribed=false, - $status_opts=array(), $select_opts=array()) + $return_opts=array(), $select_opts=array()) { if (!strlen($mailbox)) { $mailbox = '*'; @@ -1354,16 +1353,24 @@ $args[] = $this->escape($ref); $args[] = $this->escape($mailbox); - if (!empty($status_opts) && $this->getCapability('LIST-EXTENDED')) { - $rets = array_intersect($status_opts, array('SUBSCRIBED', 'CHILDREN')); + if (!empty($return_opts) && $this->getCapability('LIST-EXTENDED')) { + $ext_opts = array('SUBSCRIBED', 'CHILDREN'); + $rets = array_intersect($return_opts, $ext_opts); + $return_opts = array_diff($return_opts, $rets); } - if (!empty($status_opts) && $this->getCapability('LIST-STATUS')) { - $status_opts = array_intersect($status_opts, array('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN')); + if (!empty($return_opts) && $this->getCapability('LIST-STATUS')) { + $lstatus = true; + $status_opts = array('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN'); + $opts = array_diff($return_opts, $status_opts); + $status_opts = array_diff($return_opts, $opts); if (!empty($status_opts)) { - $lstatus = true; $rets[] = 'STATUS (' . implode(' ', $status_opts) . ')'; + } + + if (!empty($opts)) { + $rets = array_merge($rets, $opts); } } @@ -1388,9 +1395,10 @@ $line = substr($response, $last, $pos - $last); $last = $pos + 2; - if (!preg_match('/^\* (LIST|LSUB|STATUS) /i', $line, $m)) { + if (!preg_match('/^\* (LIST|LSUB|STATUS|MYRIGHTS) /i', $line, $m)) { continue; } + $cmd = strtoupper($m[1]); $line = substr($line, strlen($m[0])); @@ -1421,13 +1429,20 @@ $this->data['LIST'][$mailbox], $opts)); } } - // * STATUS <mailbox> (<result>) - else if ($cmd == 'STATUS') { - list($mailbox, $status) = $this->tokenizeResponse($line, 2); + else if ($lstatus) { + // * STATUS <mailbox> (<result>) + if ($cmd == 'STATUS') { + list($mailbox, $status) = $this->tokenizeResponse($line, 2); - for ($i=0, $len=count($status); $i<$len; $i += 2) { - list($name, $value) = $this->tokenizeResponse($status, 2); - $folders[$mailbox][$name] = $value; + for ($i=0, $len=count($status); $i<$len; $i += 2) { + list($name, $value) = $this->tokenizeResponse($status, 2); + $folders[$mailbox][$name] = $value; + } + } + // * MYRIGHTS <mailbox> <acl> + else if ($cmd == 'MYRIGHTS') { + list($mailbox, $acl) = $this->tokenizeResponse($line, 2); + $folders[$mailbox]['MYRIGHTS'] = $acl; } } } @@ -1445,13 +1460,9 @@ * * @return int Number of messages, False on error */ - function countMessages($mailbox, $refresh = false) + function countMessages($mailbox) { - if ($refresh) { - $this->selected = null; - } - - if ($this->selected === $mailbox) { + if ($this->selected === $mailbox && isset($this->data['EXISTS'])) { return $this->data['EXISTS']; } @@ -1479,14 +1490,20 @@ */ function countRecent($mailbox) { - if (!strlen($mailbox)) { - $mailbox = 'INBOX'; + if ($this->selected === $mailbox && isset($this->data['RECENT'])) { + return $this->data['RECENT']; } - $this->select($mailbox); + // Check internal cache + $cache = $this->data['STATUS:'.$mailbox]; + if (!empty($cache) && isset($cache['RECENT'])) { + return (int) $cache['RECENT']; + } - if ($this->selected === $mailbox) { - return $this->data['RECENT']; + // Try STATUS (should be faster than SELECT) + $counts = $this->status($mailbox, array('RECENT')); + if (is_array($counts)) { + return (int) $counts['RECENT']; } return false; @@ -1688,7 +1705,6 @@ $encoding = $encoding ? trim($encoding) : 'US-ASCII'; $algorithm = $algorithm ? trim($algorithm) : 'REFERENCES'; $criteria = $criteria ? 'ALL '.trim($criteria) : 'ALL'; - $data = ''; list($code, $response) = $this->execute($return_uid ? 'UID THREAD' : 'THREAD', array($algorithm, $encoding, $criteria)); @@ -1902,8 +1918,8 @@ $result[$id] = ''; } } else if ($mode == 2) { - if (preg_match('/(UID|RFC822\.SIZE) ([0-9]+)/', $line, $matches)) { - $result[$id] = trim($matches[2]); + if (preg_match('/' . $index_field . ' ([0-9]+)/', $line, $matches)) { + $result[$id] = trim($matches[1]); } else { $result[$id] = 0; } @@ -2012,10 +2028,6 @@ */ protected function modFlag($mailbox, $messages, $flag, $mod = '+') { - if ($mod != '+' && $mod != '-') { - $mod = '+'; - } - if (!$this->select($mailbox)) { return false; } @@ -2025,12 +2037,31 @@ return false; } + if ($this->flags[strtoupper($flag)]) { + $flag = $this->flags[strtoupper($flag)]; + } + + if (!$flag) { + return false; + } + + // if PERMANENTFLAGS is not specified all flags are allowed + if (!empty($this->data['PERMANENTFLAGS']) + && !in_array($flag, (array) $this->data['PERMANENTFLAGS']) + && !in_array('\\*', (array) $this->data['PERMANENTFLAGS']) + ) { + return false; + } + // Clear internal status cache if ($flag == 'SEEN') { unset($this->data['STATUS:'.$mailbox]['UNSEEN']); } - $flag = $this->flags[strtoupper($flag)]; + if ($mod != '+' && $mod != '-') { + $mod = '+'; + } + $result = $this->execute('UID STORE', array( $this->compressMessageSet($messages), $mod . 'FLAGS.SILENT', "($flag)"), self::COMMAND_NORESPONSE); @@ -2093,7 +2124,7 @@ // Clear internal status cache unset($this->data['STATUS:'.$to]); - unset($this->data['STATUS:'.$from]); + $this->clear_status_cache($from); $result = $this->execute('UID MOVE', array( $this->compressMessageSet($messages), $this->escape($to)), @@ -2538,50 +2569,61 @@ return false; } - 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; - } - - // Use BINARY extension when possible (and safe) - $binary = $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY'); - $fetch_mode = $binary ? 'BINARY' : 'BODY'; - $partial = $max_bytes ? sprintf('<0.%d>', $max_bytes) : ''; - - // format request - $key = $this->nextTag(); - $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)"; - $result = false; - $found = false; - - // send request - if (!$this->putLine($request)) { - $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); - return false; - } - - if ($binary) { - // WARNING: Use $formatted argument with care, this may break binary data stream - $mode = -1; - } + $binary = true; do { + if (!$initiated) { + 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; + } + + // Use BINARY extension when possible (and safe) + $binary = $binary && $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY'); + $fetch_mode = $binary ? 'BINARY' : 'BODY'; + $partial = $max_bytes ? sprintf('<0.%d>', $max_bytes) : ''; + + // format request + $key = $this->nextTag(); + $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)"; + $result = false; + $found = false; + $initiated = true; + + // send request + if (!$this->putLine($request)) { + $this->setError(self::ERROR_COMMAND, "Unable to send command: $request"); + return false; + } + + if ($binary) { + // WARNING: Use $formatted argument with care, this may break binary data stream + $mode = -1; + } + } + $line = trim($this->readLine(1024)); if (!$line) { break; + } + + // handle UNKNOWN-CTE response - RFC 3516, try again with standard BODY request + if ($binary && !$found && preg_match('/^' . $key . ' NO \[UNKNOWN-CTE\]/i', $line)) { + $binary = $initiated = false; + continue; } // skip irrelevant untagged responses (we have a result already) @@ -2644,7 +2686,7 @@ // BASE64 if ($mode == 1) { - $line = rtrim($line, "\t\r\n\0\x0B"); + $line = preg_replace('|[^a-zA-Z0-9+=/]|', '', $line); // create chunks with proper length for base64 decoding $line = $prev.$line; $length = strlen($line); @@ -2689,7 +2731,7 @@ } } } - } while (!$this->startsWith($line, $key, true)); + } while (!$this->startsWith($line, $key, true) || !$initiated); if ($result !== false) { if ($file) { @@ -2859,57 +2901,64 @@ /** * Returns QUOTA information * + * @param string $mailbox Mailbox name + * * @return array Quota information */ - function getQuota() + function getQuota($mailbox = null) { - /* - * GETQUOTAROOT "INBOX" - * QUOTAROOT INBOX user/rchijiiwa1 - * QUOTA user/rchijiiwa1 (STORAGE 654 9765) - * OK Completed - */ - $result = false; - $quota_lines = array(); - $key = $this->nextTag(); - $command = $key . ' GETQUOTAROOT INBOX'; - - // get line(s) containing quota info - if ($this->putLine($command)) { - do { - $line = rtrim($this->readLine(5000)); - if (preg_match('/^\* QUOTA /', $line)) { - $quota_lines[] = $line; - } - } while (!$this->startsWith($line, $key, true, true)); - } - else { - $this->setError(self::ERROR_COMMAND, "Unable to send command: $command"); + if ($mailbox === null || $mailbox === '') { + $mailbox = 'INBOX'; } - // return false if not found, parse if found + // a0001 GETQUOTAROOT INBOX + // * QUOTAROOT INBOX user/sample + // * QUOTA user/sample (STORAGE 654 9765) + // a0001 OK Completed + + list($code, $response) = $this->execute('GETQUOTAROOT', array($this->escape($mailbox))); + + $result = false; $min_free = PHP_INT_MAX; - foreach ($quota_lines as $key => $quota_line) { - $quota_line = str_replace(array('(', ')'), '', $quota_line); - $parts = explode(' ', $quota_line); - $storage_part = array_search('STORAGE', $parts); + $all = array(); - if (!$storage_part) { - continue; + if ($code == self::ERROR_OK) { + foreach (explode("\n", $response) as $line) { + if (preg_match('/^\* QUOTA /', $line)) { + list(, , $quota_root) = $this->tokenizeResponse($line, 3); + + while ($line) { + list($type, $used, $total) = $this->tokenizeResponse($line, 1); + $type = strtolower($type); + + if ($type && $total) { + $all[$quota_root][$type]['used'] = intval($used); + $all[$quota_root][$type]['total'] = intval($total); + } + } + + if (empty($all[$quota_root]['storage'])) { + continue; + } + + $used = $all[$quota_root]['storage']['used']; + $total = $all[$quota_root]['storage']['total']; + $free = $total - $used; + + // calculate lowest available space from all storage quotas + if ($free < $min_free) { + $min_free = $free; + $result['used'] = $used; + $result['total'] = $total; + $result['percent'] = min(100, round(($used/max(1,$total))*100)); + $result['free'] = 100 - $result['percent']; + } + } } + } - $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'] = $total; - $result['percent'] = min(100, round(($used/max(1,$total))*100)); - $result['free'] = 100 - $result['percent']; - } + if (!empty($result)) { + $result['all'] = $all; } return $result; @@ -3167,9 +3216,9 @@ for ($i=0; $i<$size; $i++) { if (isset($mbox) && is_array($data[$i])) { $size_sub = count($data[$i]); - for ($x=0; $x<$size_sub; $x++) { + for ($x=0; $x<$size_sub; $x+=2) { if ($data[$i][$x+1] !== null) - $result[$mbox][$data[$i][$x]] = $data[$i][++$x]; + $result[$mbox][$data[$i][$x]] = $data[$i][$x+1]; } unset($data[$i]); } @@ -3187,8 +3236,8 @@ } } else if (isset($mbox)) { - if ($data[$i+1] !== null) - $result[$mbox][$data[$i]] = $data[++$i]; + if ($data[++$i] !== null) + $result[$mbox][$data[$i-1]] = $data[$i]; unset($data[$i]); unset($data[$i-1]); } @@ -3222,11 +3271,6 @@ } foreach ($data as $entry) { - // Workaround cyrus-murder bug, the entry[2] string needs to be escaped - if (self::$mupdate) { - $entry[2] = addcslashes($entry[2], '\\"'); - } - // ANNOTATEMORE drafts before version 08 require quoted parameters $entries[] = sprintf('%s (%s %s)', $this->escape($entry[0], true), $this->escape($entry[1], true), $this->escape($entry[2], true)); @@ -3719,6 +3763,17 @@ } /** + * Clear internal status cache + */ + protected function clear_status_cache($mailbox) + { + unset($this->data['STATUS:' . $mailbox]); + unset($this->data['EXISTS']); + unset($this->data['RECENT']); + unset($this->data['UNSEEN']); + } + + /** * Converts flags array into string for inclusion in IMAP command * * @param array $flags Flags (see self::flags) @@ -3789,10 +3844,6 @@ if (!isset($this->prefs['literal+']) && in_array('LITERAL+', $this->capability)) { $this->prefs['literal+'] = true; - } - - if (preg_match('/(\[| )MUPDATE=.*/', $str)) { - self::$mupdate = true; } if ($trusted) { -- Gitblit v1.9.1