From 3412e50b54e3daac8745234e21ab6e72be0ed165 Mon Sep 17 00:00:00 2001 From: Thomas Bruederli <thomas@roundcube.net> Date: Wed, 04 Jun 2014 11:20:33 -0400 Subject: [PATCH] Fix attachment menu structure and aria-attributes --- program/lib/Roundcube/rcube_imap_generic.php | 202 +++++++++++++++++++++++++++++++++----------------- 1 files changed, 133 insertions(+), 69 deletions(-) diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 70ffd0d..f45694d 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -48,6 +48,8 @@ '*' => '\\*', ); + public static $mupdate; + private $fp; private $host; private $logged = false; @@ -71,6 +73,7 @@ const COMMAND_NORESPONSE = 1; const COMMAND_CAPABILITY = 2; const COMMAND_LASTLINE = 4; + const COMMAND_ANONYMIZED = 8; const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n @@ -86,16 +89,28 @@ * * @param string $string Command string * @param bool $endln True if CRLF need to be added at the end of command + * @param bool $anonymized Don't write the given data to log but a placeholder * * @param int Number of bytes sent, False on error */ - function putLine($string, $endln=true) + function putLine($string, $endln=true, $anonymized=false) { if (!$this->fp) return false; if ($this->_debug) { - $this->debug('C: '. rtrim($string)); + // anonymize the sent command for logging + $cut = $endln ? 2 : 0; + if ($anonymized && preg_match('/^(A\d+ (?:[A-Z]+ )+)(.+)/', $string, $m)) { + $log = $m[1] . sprintf('****** [%d]', strlen($m[2]) - $cut); + } + else if ($anonymized) { + $log = sprintf('****** [%d]', strlen($string) - $cut); + } + else { + $log = rtrim($string); + } + $this->debug('C: ' . $log); } $res = fwrite($this->fp, $string . ($endln ? "\r\n" : '')); @@ -114,10 +129,11 @@ * * @param string $string Command string * @param bool $endln True if CRLF need to be added at the end of command + * @param bool $anonymized Don't write the given data to log but a placeholder * * @return int|bool Number of bytes sent, False on error */ - function putLineC($string, $endln=true) + function putLineC($string, $endln=true, $anonymized=false) { if (!$this->fp) { return false; @@ -136,7 +152,7 @@ $parts[$i+1] = sprintf("{%d+}\r\n", $matches[1]); } - $bytes = $this->putLine($parts[$i].$parts[$i+1], false); + $bytes = $this->putLine($parts[$i].$parts[$i+1], false, $anonymized); if ($bytes === false) return false; $res += $bytes; @@ -151,7 +167,7 @@ $i++; } else { - $bytes = $this->putLine($parts[$i], false); + $bytes = $this->putLine($parts[$i], false, $anonymized); if ($bytes === false) return false; $res += $bytes; @@ -517,7 +533,7 @@ $reply = base64_encode($user . ' ' . $hash); // send result - $this->putLine($reply); + $this->putLine($reply, true, true); } else { // RFC2831: DIGEST-MD5 @@ -535,7 +551,7 @@ base64_decode($challenge), $this->host, 'imap', $user)); // send result - $this->putLine($reply); + $this->putLine($reply, true, true); $line = trim($this->readReply()); if ($line[0] == '+') { @@ -575,7 +591,7 @@ // RFC 4959 (SASL-IR): save one round trip if ($this->getCapability('SASL-IR')) { list($result, $line) = $this->execute("AUTHENTICATE PLAIN", array($reply), - self::COMMAND_LASTLINE | self::COMMAND_CAPABILITY); + self::COMMAND_LASTLINE | self::COMMAND_CAPABILITY | self::COMMAND_ANONYMIZED); } else { $this->putLine($this->nextTag() . " AUTHENTICATE PLAIN"); @@ -586,7 +602,7 @@ } // send result, get reply and process it - $this->putLine($reply); + $this->putLine($reply, true, true); $line = $this->readReply(); $result = $this->parseResult($line); } @@ -617,7 +633,7 @@ function login($user, $password) { list($code, $response) = $this->execute('LOGIN', array( - $this->escape($user), $this->escape($password)), self::COMMAND_CAPABILITY); + $this->escape($user), $this->escape($password)), self::COMMAND_CAPABILITY | self::COMMAND_ANONYMIZED); // re-set capabilities list if untagged CAPABILITY response provided if (preg_match('/\* CAPABILITY (.+)/i', $response, $matches)) { @@ -704,22 +720,11 @@ */ function connect($host, $user, $password, $options=null) { - // set options - if (is_array($options)) { - $this->prefs = $options; - } - // set auth method - if (!empty($this->prefs['auth_type'])) { - $auth_method = strtoupper($this->prefs['auth_type']); - } else { - $auth_method = 'CHECK'; - } + // configure + $this->set_prefs($options); - if (!empty($this->prefs['disabled_caps'])) { - $this->prefs['disabled_caps'] = array_map('strtoupper', (array)$this->prefs['disabled_caps']); - } - - $result = false; + $auth_method = $this->prefs['auth_type']; + $result = false; // initialize connection $this->error = ''; @@ -800,23 +805,21 @@ // TLS connection if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) { - if (version_compare(PHP_VERSION, '5.1.0', '>=')) { - $res = $this->execute('STARTTLS'); + $res = $this->execute('STARTTLS'); - if ($res[0] != self::ERROR_OK) { - $this->closeConnection(); - return false; - } - - if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { - $this->setError(self::ERROR_BAD, "Unable to negotiate TLS"); - $this->closeConnection(); - return false; - } - - // Now we're secure, capabilities need to be reread - $this->clearCapability(); + if ($res[0] != self::ERROR_OK) { + $this->closeConnection(); + return false; } + + if (!stream_socket_enable_crypto($this->fp, true, STREAM_CRYPTO_METHOD_TLS_CLIENT)) { + $this->setError(self::ERROR_BAD, "Unable to negotiate TLS"); + $this->closeConnection(); + return false; + } + + // Now we're secure, capabilities need to be reread + $this->clearCapability(); } // Send ID info @@ -895,6 +898,36 @@ $this->closeConnection(); return false; + } + + /** + * Initializes environment + */ + protected function set_prefs($prefs) + { + // set preferences + if (is_array($prefs)) { + $this->prefs = $prefs; + } + + // set auth method + if (!empty($this->prefs['auth_type'])) { + $this->prefs['auth_type'] = strtoupper($this->prefs['auth_type']); + } + else { + $this->prefs['auth_type'] = 'CHECK'; + } + + // disabled capabilities + if (!empty($this->prefs['disabled_caps'])) { + $this->prefs['disabled_caps'] = array_map('strtoupper', (array)$this->prefs['disabled_caps']); + } + + // additional message flags + if (!empty($this->prefs['message_flags'])) { + $this->flags = array_merge($this->flags, $this->prefs['message_flags']); + unset($this->prefs['message_flags']); + } } /** @@ -1158,13 +1191,20 @@ * Folder creation (CREATE) * * @param string $mailbox Mailbox name + * @param array $types Optional folder types (RFC 6154) * * @return bool True on success, False on error */ - function createFolder($mailbox) + function createFolder($mailbox, $types = null) { - $result = $this->execute('CREATE', array($this->escape($mailbox)), - self::COMMAND_NORESPONSE); + $args = array($this->escape($mailbox)); + + // RFC 6154: CREATE-SPECIAL-USE + if (!empty($types) && $this->getCapability('CREATE-SPECIAL-USE')) { + $args[] = '(USE (' . implode(' ', $types) . '))'; + } + + $result = $this->execute('CREATE', $args, self::COMMAND_NORESPONSE); return ($result == self::ERROR_OK); } @@ -1260,10 +1300,12 @@ * @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) - * Possible: MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN + * @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 $select_opts List of selection options (RFC5258: LIST-EXTENDED) - * Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE + * Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE, + * SPECIAL-USE (RFC6154) * * @return array List of mailboxes or hash of options if $status_ops argument * is non-empty. @@ -1276,6 +1318,7 @@ } $args = array(); + $rets = array(); if (!empty($select_opts) && $this->getCapability('LIST-EXTENDED')) { $select_opts = (array) $select_opts; @@ -1286,11 +1329,21 @@ $args[] = $this->escape($ref); $args[] = $this->escape($mailbox); - if (!empty($status_opts) && $this->getCapability('LIST-STATUS')) { - $status_opts = (array) $status_opts; - $lstatus = true; + if (!empty($status_opts) && $this->getCapability('LIST-EXTENDED')) { + $rets = array_intersect($status_opts, array('SUBSCRIBED', 'CHILDREN')); + } - $args[] = 'RETURN (STATUS (' . implode(' ', $status_opts) . '))'; + if (!empty($status_opts) && $this->getCapability('LIST-STATUS')) { + $status_opts = array_intersect($status_opts, array('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN')); + + if (!empty($status_opts)) { + $lstatus = true; + $rets[] = 'STATUS (' . implode(' ', $status_opts) . ')'; + } + } + + if (!empty($rets)) { + $args[] = 'RETURN (' . implode(' ', $rets) . ')'; } list($code, $response) = $this->execute($subscribed ? 'LSUB' : 'LIST', $args); @@ -1333,9 +1386,8 @@ $folders[$mailbox] = array(); } - // store LSUB options only if not empty, this way - // we can detect a situation when LIST doesn't return specified folder - if (!empty($opts) || $cmd == 'LIST') { + // store folder options + if ($cmd == 'LIST') { // Add to options array if (empty($this->data['LIST'][$mailbox])) $this->data['LIST'][$mailbox] = $opts; @@ -1537,23 +1589,23 @@ * * @param string $mailbox Mailbox name * @param string $field Field to sort by (ARRIVAL, CC, DATE, FROM, SIZE, SUBJECT, TO) - * @param string $add Searching criteria + * @param string $criteria Searching criteria * @param bool $return_uid Enables UID SORT usage * @param string $encoding Character set * * @return rcube_result_index Response data */ - function sort($mailbox, $field, $add='', $return_uid=false, $encoding = 'US-ASCII') + function sort($mailbox, $field = 'ARRIVAL', $criteria = '', $return_uid = false, $encoding = 'US-ASCII') { - $field = strtoupper($field); + $old_sel = $this->selected; + $supported = array('ARRIVAL', 'CC', 'DATE', 'FROM', 'SIZE', 'SUBJECT', 'TO'); + $field = strtoupper($field); + if ($field == 'INTERNALDATE') { $field = 'ARRIVAL'; } - $fields = array('ARRIVAL' => 1,'CC' => 1,'DATE' => 1, - 'FROM' => 1, 'SIZE' => 1, 'SUBJECT' => 1, 'TO' => 1); - - if (!$fields[$field]) { + if (!in_array($field, $supported)) { return new rcube_result_index($mailbox); } @@ -1561,18 +1613,21 @@ return new rcube_result_index($mailbox); } + // return empty result when folder is empty and we're just after SELECT + if ($old_sel != $mailbox && !$this->data['EXISTS']) { + return new rcube_result_index($mailbox, '* SORT'); + } + // RFC 5957: SORT=DISPLAY if (($field == 'FROM' || $field == 'TO') && $this->getCapability('SORT=DISPLAY')) { $field = 'DISPLAY' . $field; } - // message IDs - if (!empty($add)) { - $add = $this->compressMessageSet($add); - } + $encoding = $encoding ? trim($encoding) : 'US-ASCII'; + $criteria = $criteria ? 'ALL ' . trim($criteria) : 'ALL'; list($code, $response) = $this->execute($return_uid ? 'UID SORT' : 'SORT', - array("($field)", $encoding, !empty($add) ? $add : 'ALL')); + array("($field)", $encoding, $criteria)); if ($code != self::ERROR_OK) { $response = null; @@ -1602,7 +1657,7 @@ // return empty result when folder is empty and we're just after SELECT if ($old_sel != $mailbox && !$this->data['EXISTS']) { - return new rcube_result_thread($mailbox); + return new rcube_result_thread($mailbox, '* THREAD'); } $encoding = $encoding ? trim($encoding) : 'US-ASCII'; @@ -2493,7 +2548,7 @@ } if ($binary) { - // WARNING: Use $formatting argument with care, this may break binary data stream + // WARNING: Use $formatted argument with care, this may break binary data stream $mode = -1; } @@ -2991,7 +3046,7 @@ } foreach ($entries as $name => $value) { - $entries[$name] = $this->escape($name) . ' ' . $this->escape($value); + $entries[$name] = $this->escape($name) . ' ' . $this->escape($value, true); } $entries = implode(' ', $entries); @@ -3140,6 +3195,11 @@ } 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)); @@ -3396,7 +3456,7 @@ } // Send command - if (!$this->putLineC($query)) { + if (!$this->putLineC($query, true, ($options & self::COMMAND_ANONYMIZED))) { $this->setError(self::ERROR_COMMAND, "Unable to send command: $query"); return $noresp ? self::ERROR_COMMAND : array(self::ERROR_COMMAND, ''); } @@ -3704,6 +3764,10 @@ $this->prefs['literal+'] = true; } + if (preg_match('/(\[| )MUPDATE=.*/', $str)) { + self::$mupdate = true; + } + if ($trusted) { $this->capability_readed = true; } -- Gitblit v1.9.1