Page MenuHomePhorge

D4637.1775466488.diff
No OneTemporary

Authored By
Unknown
Size
145 KB
Referenced Files
None
Subscribers
None

D4637.1775466488.diff

diff --git a/src/include/rcube_imap_generic.php b/src/include/rcube_imap_generic.php
--- a/src/include/rcube_imap_generic.php
+++ b/src/include/rcube_imap_generic.php
@@ -1,6 +1,6 @@
<?php
-/**
+/*
+-----------------------------------------------------------------------+
| This file is part of the Roundcube Webmail client |
| |
@@ -26,9 +26,6 @@
/**
* PHP based wrapper class to connect to an IMAP server
- *
- * @package Framework
- * @subpackage Storage
*/
class rcube_imap_generic
{
@@ -37,44 +34,46 @@
public $result;
public $resultcode;
public $selected;
- public $data = array();
- public $flags = array(
- 'SEEN' => '\\Seen',
- 'DELETED' => '\\Deleted',
- 'ANSWERED' => '\\Answered',
- 'DRAFT' => '\\Draft',
- 'FLAGGED' => '\\Flagged',
+ public $data = [];
+ public $flags = [
+ 'SEEN' => '\\Seen',
+ 'DELETED' => '\\Deleted',
+ 'ANSWERED' => '\\Answered',
+ 'DRAFT' => '\\Draft',
+ 'FLAGGED' => '\\Flagged',
'FORWARDED' => '$Forwarded',
- 'MDNSENT' => '$MDNSent',
- '*' => '\\*',
- );
+ 'MDNSENT' => '$MDNSent',
+ '*' => '\\*',
+ ];
protected $fp;
protected $host;
+ protected $user;
protected $cmd_tag;
protected $cmd_num = 0;
protected $resourceid;
- protected $prefs = array();
+ protected $extensions_enabled;
+ protected $prefs = [];
protected $logged = false;
- protected $capability = array();
- protected $capability_readed = false;
+ protected $capability = [];
+ protected $capability_read = false;
protected $debug = false;
protected $debug_handler = false;
- const ERROR_OK = 0;
- const ERROR_NO = -1;
- const ERROR_BAD = -2;
- const ERROR_BYE = -3;
- const ERROR_UNKNOWN = -4;
- const ERROR_COMMAND = -5;
- const ERROR_READONLY = -6;
+ public const ERROR_OK = 0;
+ public const ERROR_NO = -1;
+ public const ERROR_BAD = -2;
+ public const ERROR_BYE = -3;
+ public const ERROR_UNKNOWN = -4;
+ public const ERROR_COMMAND = -5;
+ public const ERROR_READONLY = -6;
- const COMMAND_NORESPONSE = 1;
- const COMMAND_CAPABILITY = 2;
- const COMMAND_LASTLINE = 4;
- const COMMAND_ANONYMIZED = 8;
+ public const COMMAND_NORESPONSE = 1;
+ public const COMMAND_CAPABILITY = 2;
+ public const COMMAND_LASTLINE = 4;
+ public const COMMAND_ANONYMIZED = 8;
- const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n
+ public const DEBUG_LINE_LENGTH = 4098; // 4KB + 2B for \r\n
/**
@@ -84,7 +83,7 @@
* @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
+ * @return int Number of bytes sent, False on error
*/
protected function putLine($string, $endln = true, $anonymized = false)
{
@@ -97,11 +96,9 @@
$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) {
+ } elseif ($anonymized) {
$log = sprintf('****** [%d]', strlen($string) - $cut);
- }
- else {
+ } else {
$log = rtrim($string);
}
@@ -123,7 +120,7 @@
/**
* Send command to the connection stream with Command Continuation
- * Requests (RFC3501 7.5) and LITERAL+ (RFC2088) support
+ * Requests (RFC3501 7.5) and LITERAL+ (RFC2088) and LITERAL- (RFC7888) support.
*
* @param string $string Command string
* @param bool $endln True if CRLF need to be added at the end of command
@@ -131,7 +128,7 @@
*
* @return int|bool Number of bytes sent, False on error
*/
- protected function putLineC($string, $endln=true, $anonymized=false)
+ protected function putLineC($string, $endln = true, $anonymized = false)
{
if (!$this->fp) {
return false;
@@ -142,15 +139,20 @@
}
$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 ($i+1 < $cnt && preg_match('/^\{([0-9]+)\}\r\n$/', $parts[$i+1], $matches)) {
- // LITERAL+ support
- if ($this->prefs['literal+']) {
- $parts[$i+1] = sprintf("{%d+}\r\n", $matches[1]);
+ 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 ($i + 1 < $cnt && preg_match('/^\{([0-9]+)\}\r\n$/', $parts[$i + 1], $matches)) {
+ // LITERAL+/LITERAL- support
+ $literal_plus = false;
+ if (
+ !empty($this->prefs['literal+'])
+ || (!empty($this->prefs['literal-']) && $matches[1] <= 4096)
+ ) {
+ $parts[$i + 1] = sprintf("{%d+}\r\n", $matches[1]);
+ $literal_plus = true;
}
- $bytes = $this->putLine($parts[$i].$parts[$i+1], false, $anonymized);
+ $bytes = $this->putLine($parts[$i] . $parts[$i + 1], false, $anonymized);
if ($bytes === false) {
return false;
}
@@ -158,17 +160,16 @@
$res += $bytes;
// don't wait if server supports LITERAL+ capability
- if (!$this->prefs['literal+']) {
+ if (!$literal_plus) {
$line = $this->readLine(1000);
// handle error in command
- if ($line[0] != '+') {
+ if (!isset($line[0]) || $line[0] != '+') {
return false;
}
}
$i++;
- }
- else {
+ } else {
$bytes = $this->putLine($parts[$i], false, $anonymized);
if ($bytes === false) {
return false;
@@ -199,7 +200,7 @@
do {
if ($this->eof()) {
- return $line ?: null;
+ return $line;
}
$buffer = fgets($this->fp, $size);
@@ -210,12 +211,43 @@
}
if ($this->debug) {
- $this->debug('S: '. rtrim($buffer));
+ $this->debug('S: ' . rtrim($buffer));
}
$line .= $buffer;
+ } while (substr($buffer, -1) != "\n");
+
+ return $line;
+ }
+
+ /**
+ * Reads a line of data from the connection stream including all
+ * string continuation literals.
+ *
+ * @param int $size Buffer size
+ *
+ * @return string Line of text response
+ */
+ protected function readFullLine($size = 1024)
+ {
+ $line = $this->readLine($size);
+
+ // include all string literals untile the real end of "line"
+ while (preg_match('/\{([0-9]+)\}\r\n$/', $line, $m)) {
+ $bytes = $m[1];
+ $out = '';
+
+ while (strlen($out) < $bytes) {
+ $out = $this->readBytes($bytes);
+ if ($out === '') {
+ break;
+ }
+
+ $line .= $out;
+ }
+
+ $line .= $this->readLine($size);
}
- while (substr($buffer, -1) != "\n");
return $line;
}
@@ -224,8 +256,8 @@
* Reads more data from the connection stream when provided
* data contain string literal
*
- * @param string $line Response text
- * @param bool $escape Enables escaping
+ * @param string $line Response text
+ * @param bool $escape Enables escaping
*
* @return string Line of text response
*/
@@ -239,7 +271,7 @@
while (strlen($out) < $bytes) {
$line = $this->readBytes($bytes);
- if ($line === null) {
+ if ($line === '') {
break;
}
@@ -265,9 +297,9 @@
$len = 0;
while ($len < $bytes && !$this->eof()) {
- $d = fread($this->fp, $bytes-$len);
+ $d = fread($this->fp, $bytes - $len);
if ($this->debug) {
- $this->debug('S: '. $d);
+ $this->debug('S: ' . $d);
}
$data .= $d;
$data_len = strlen($data);
@@ -289,14 +321,15 @@
*/
protected function readReply(&$untagged = null)
{
- do {
+ while (true) {
$line = trim($this->readLine(1024));
// store untagged response lines
- if ($line[0] == '*') {
+ if (isset($line[0]) && $line[0] == '*') {
$untagged[] = $line;
+ } else {
+ break;
}
}
- while ($line[0] == '*');
if ($untagged) {
$untagged = implode("\n", $untagged);
@@ -321,14 +354,11 @@
if ($res == 'OK') {
$this->errornum = self::ERROR_OK;
- }
- else if ($res == 'NO') {
+ } elseif ($res == 'NO') {
$this->errornum = self::ERROR_NO;
- }
- else if ($res == 'BAD') {
+ } elseif ($res == 'BAD') {
$this->errornum = self::ERROR_BAD;
- }
- else if ($res == 'BYE') {
+ } elseif ($res == 'BYE') {
$this->closeSocket();
$this->errornum = self::ERROR_BYE;
}
@@ -336,26 +366,25 @@
if ($str) {
$str = trim($str);
// get response string and code (RFC5530)
- if (preg_match("/^\[([a-z-]+)\]/i", $str, $m)) {
+ if (preg_match('/^\\[([a-z-]+)\\]/i', $str, $m)) {
$this->resultcode = strtoupper($m[1]);
$str = trim(substr($str, strlen($m[1]) + 2));
- }
- else {
+ } else {
$this->resultcode = null;
// parse response for [APPENDUID 1204196876 3456]
- if (preg_match("/^\[APPENDUID [0-9]+ ([0-9]+)\]/i", $str, $m)) {
+ if (preg_match('/^\\[APPENDUID [0-9]+ ([0-9]+)\\]/i', $str, $m)) {
$this->data['APPENDUID'] = $m[1];
}
// parse response for [COPYUID 1204196876 3456:3457 123:124]
- else if (preg_match("/^\[COPYUID [0-9]+ ([0-9,:]+) ([0-9,:]+)\]/i", $str, $m)) {
- $this->data['COPYUID'] = array($m[1], $m[2]);
+ elseif (preg_match('/^\\[COPYUID [0-9]+ ([0-9,:]+) ([0-9,:]+)\\]/i', $str, $m)) {
+ $this->data['COPYUID'] = [$m[1], $m[2]];
}
}
$this->result = $str;
if ($this->errornum != self::ERROR_OK) {
- $this->error = $err_prefix ? $err_prefix.$str : $str;
+ $this->error = $err_prefix ? $err_prefix . $str : $str;
}
}
@@ -372,7 +401,7 @@
*/
protected function eof()
{
- if (!is_resource($this->fp)) {
+ if (!$this->fp) {
return true;
}
@@ -380,8 +409,8 @@
// by the server, feof() will hang.
$start = microtime(true);
- if (feof($this->fp) ||
- ($this->prefs['timeout'] && (microtime(true) - $start > $this->prefs['timeout']))
+ if (feof($this->fp)
+ || ($this->prefs['timeout'] && (microtime(true) - $start > $this->prefs['timeout']))
) {
$this->closeSocket();
return true;
@@ -395,8 +424,10 @@
*/
protected function closeSocket()
{
- @fclose($this->fp);
- $this->fp = null;
+ if ($this->fp) {
+ fclose($this->fp);
+ $this->fp = null;
+ }
}
/**
@@ -450,18 +481,17 @@
*/
protected function hasCapability($name)
{
- if (empty($this->capability) || $name == '') {
+ if (empty($this->capability) || empty($name)) {
return false;
}
if (in_array($name, $this->capability)) {
return true;
- }
- else if (strpos($name, '=')) {
+ } elseif (strpos($name, '=')) {
return false;
}
- $result = array();
+ $result = [];
foreach ($this->capability as $cap) {
$entry = explode('=', $cap);
if ($entry[0] == $name) {
@@ -485,8 +515,7 @@
if (!empty($result)) {
return $result;
- }
- else if ($this->capability_readed) {
+ } elseif ($this->capability_read) {
return false;
}
@@ -498,7 +527,7 @@
$this->parseCapability($result[1]);
}
- $this->capability_readed = true;
+ $this->capability_read = true;
return $this->hasCapability($name);
}
@@ -508,8 +537,8 @@
*/
public function clearCapability()
{
- $this->capability = array();
- $this->capability_readed = false;
+ $this->capability = [];
+ $this->capability_read = false;
}
/**
@@ -519,14 +548,14 @@
* @param string $pass Password
* @param string $type Authentication type (PLAIN/CRAM-MD5/DIGEST-MD5)
*
- * @return resource Connection resourse on success, error code on error
+ * @return resource|int Connection resource on success, error code on error
*/
protected function authenticate($user, $pass, $type = 'PLAIN')
{
if ($type == 'CRAM-MD5' || $type == 'DIGEST-MD5') {
if ($type == 'DIGEST-MD5' && !class_exists('Auth_SASL')) {
return $this->setError(self::ERROR_BYE,
- "The Auth_SASL package is required for DIGEST-MD5 authentication");
+ 'The Auth_SASL package is required for DIGEST-MD5 authentication');
}
$this->putLine($this->nextTag() . " AUTHENTICATE $type");
@@ -534,8 +563,7 @@
if ($line[0] == '+') {
$challenge = substr($line, 2);
- }
- else {
+ } else {
return $this->parseResult($line);
}
@@ -543,17 +571,17 @@
// RFC2195: CRAM-MD5
$ipad = '';
$opad = '';
- $xor = function($str1, $str2) {
+ $xor = static function ($str1, $str2) {
$result = '';
$size = strlen($str1);
- for ($i=0; $i<$size; $i++) {
+ for ($i = 0; $i < $size; $i++) {
$result .= chr(ord($str1[$i]) ^ ord($str2[$i]));
}
return $result;
};
// initialize ipad, opad
- for ($i=0; $i<64; $i++) {
+ for ($i = 0; $i < 64; $i++) {
$ipad .= chr(0x36);
$opad .= chr(0x5C);
}
@@ -562,26 +590,24 @@
$pass = str_pad($pass, 64, chr(0));
// generate hash
- $hash = md5($xor($pass, $opad) . pack("H*",
+ $hash = md5($xor($pass, $opad) . pack('H*',
md5($xor($pass, $ipad) . base64_decode($challenge))));
$reply = base64_encode($user . ' ' . $hash);
// send result
$this->putLine($reply, true, true);
- }
- else {
+ } else {
// RFC2831: DIGEST-MD5
// proxy authorization
if (!empty($this->prefs['auth_cid'])) {
$authc = $this->prefs['auth_cid'];
$pass = $this->prefs['auth_pw'];
- }
- else {
+ } else {
$authc = $user;
$user = '';
}
- $auth_sasl = new Auth_SASL;
+ $auth_sasl = new Auth_SASL();
$auth_sasl = $auth_sasl->factory('digestmd5');
$reply = base64_encode($auth_sasl->getResponse($authc, $pass,
base64_decode($challenge), $this->host, 'imap', $user));
@@ -599,7 +625,7 @@
$challenge = base64_decode($challenge);
if (strpos($challenge, 'rspauth=') === false) {
return $this->setError(self::ERROR_BAD,
- "Unexpected response from server to DIGEST-MD5 response");
+ 'Unexpected response from server to DIGEST-MD5 response');
}
$this->putLine('');
@@ -607,21 +633,20 @@
$line = $this->readReply();
$result = $this->parseResult($line);
- }
- else if ($type == 'GSSAPI') {
+ } elseif ($type == 'GSSAPI') {
if (!extension_loaded('krb5')) {
return $this->setError(self::ERROR_BYE,
- "The krb5 extension is required for GSSAPI authentication");
+ 'The krb5 extension is required for GSSAPI authentication');
}
if (empty($this->prefs['gssapi_cn'])) {
return $this->setError(self::ERROR_BYE,
- "The gssapi_cn parameter is required for GSSAPI authentication");
+ 'The gssapi_cn parameter is required for GSSAPI authentication');
}
if (empty($this->prefs['gssapi_context'])) {
return $this->setError(self::ERROR_BYE,
- "The gssapi_context parameter is required for GSSAPI authentication");
+ 'The gssapi_context parameter is required for GSSAPI authentication');
}
putenv('KRB5CCNAME=' . $this->prefs['gssapi_cn']);
@@ -635,13 +660,12 @@
$token = '';
$success = $gssapicontext->initSecContext($this->prefs['gssapi_context'], null, null, null, $token);
$token = base64_encode($token);
- }
- catch (Exception $e) {
- trigger_error($e->getMessage(), E_USER_WARNING);
- return $this->setError(self::ERROR_BYE, "GSSAPI authentication failed");
+ } catch (Exception $e) {
+ trigger_error($e->getMessage(), \E_USER_WARNING);
+ return $this->setError(self::ERROR_BYE, 'GSSAPI authentication failed');
}
- $this->putLine($this->nextTag() . " AUTHENTICATE GSSAPI " . $token);
+ $this->putLine($this->nextTag() . ' AUTHENTICATE GSSAPI ' . $token);
$line = trim($this->readReply());
if ($line[0] != '+') {
@@ -652,11 +676,11 @@
$itoken = base64_decode(substr($line, 2));
if (!$gssapicontext->unwrap($itoken, $itoken)) {
- throw new Exception("GSSAPI SASL input token unwrap failed");
+ throw new Exception('GSSAPI SASL input token unwrap failed');
}
if (strlen($itoken) < 4) {
- throw new Exception("GSSAPI SASL input token invalid");
+ throw new Exception('GSSAPI SASL input token invalid');
}
// Integrity/encryption layers are not supported. The first bit
@@ -664,33 +688,30 @@
// 0x00 should not occur, but support broken implementations.
$server_layers = ord($itoken[0]);
if ($server_layers && ($server_layers & 0x1) != 0x1) {
- throw new Exception("Server requires GSSAPI SASL integrity/encryption");
+ throw new Exception('Server requires GSSAPI SASL integrity/encryption');
}
// Construct output token. 0x01 in the first octet = SASL layer "none",
// zero in the following three octets = no data follows.
// See https://github.com/cyrusimap/cyrus-sasl/blob/e41cfb986c1b1935770de554872247453fdbb079/plugins/gssapi.c#L1284
- if (!$gssapicontext->wrap(pack("CCCC", 0x1, 0, 0, 0), $otoken, true)) {
- throw new Exception("GSSAPI SASL output token wrap failed");
+ if (!$gssapicontext->wrap(pack('CCCC', 0x1, 0, 0, 0), $otoken, true)) {
+ throw new Exception('GSSAPI SASL output token wrap failed');
}
- }
- catch (Exception $e) {
- trigger_error($e->getMessage(), E_USER_WARNING);
- return $this->setError(self::ERROR_BYE, "GSSAPI authentication failed");
+ } catch (Exception $e) {
+ trigger_error($e->getMessage(), \E_USER_WARNING);
+ return $this->setError(self::ERROR_BYE, 'GSSAPI authentication failed');
}
$this->putLine(base64_encode($otoken));
$line = $this->readReply();
$result = $this->parseResult($line);
- }
- else if ($type == 'PLAIN') {
+ } elseif ($type == 'PLAIN') {
// proxy authorization
if (!empty($this->prefs['auth_cid'])) {
$authc = $this->prefs['auth_cid'];
$pass = $this->prefs['auth_pw'];
- }
- else {
+ } else {
$authc = $user;
$user = '';
}
@@ -699,11 +720,10 @@
// RFC 4959 (SASL-IR): save one round trip
if ($this->getCapability('SASL-IR')) {
- list($result, $line) = $this->execute("AUTHENTICATE PLAIN", array($reply),
+ [$result, $line] = $this->execute('AUTHENTICATE PLAIN', [$reply],
self::COMMAND_LASTLINE | self::COMMAND_CAPABILITY | self::COMMAND_ANONYMIZED);
- }
- else {
- $this->putLine($this->nextTag() . " AUTHENTICATE PLAIN");
+ } else {
+ $this->putLine($this->nextTag() . ' AUTHENTICATE PLAIN');
$line = trim($this->readReply());
if ($line[0] != '+') {
@@ -715,9 +735,8 @@
$line = $this->readReply();
$result = $this->parseResult($line);
}
- }
- else if ($type == 'LOGIN') {
- $this->putLine($this->nextTag() . " AUTHENTICATE LOGIN");
+ } elseif ($type == 'LOGIN') {
+ $this->putLine($this->nextTag() . ' AUTHENTICATE LOGIN');
$line = trim($this->readReply());
if ($line[0] != '+') {
@@ -736,6 +755,24 @@
$line = $this->readReply();
$result = $this->parseResult($line);
+ } elseif (($type == 'XOAUTH2') || ($type == 'OAUTHBEARER')) {
+ $auth = ($type == 'XOAUTH2')
+ ? base64_encode("user=$user\1auth=$pass\1\1") // XOAUTH: original extension, still widely used
+ : base64_encode("n,a=$user,\1auth=$pass\1\1"); // OAUTHBEARER: official RFC 7628
+ $this->putLine($this->nextTag() . " AUTHENTICATE $type $auth", true, true);
+
+ $line = trim($this->readReply());
+
+ if ($line[0] == '+') {
+ // send empty line
+ $this->putLine('', true, true);
+ $line = $this->readReply();
+ }
+
+ $result = $this->parseResult($line);
+ } else {
+ $line = 'not supported';
+ $result = self::ERROR_UNKNOWN;
}
if ($result === self::ERROR_OK) {
@@ -753,20 +790,20 @@
/**
* LOGIN Authentication
*
- * @param string $user Username
- * @param string $pass Password
+ * @param string $user Username
+ * @param string $password Password
*
- * @return resource Connection resourse on success, error code on error
+ * @return resource|int Connection resource on success, error code on error
*/
protected function login($user, $password)
{
// Prevent from sending credentials in plain text when connection is not secure
if ($this->getCapability('LOGINDISABLED')) {
- return $this->setError(self::ERROR_BAD, "Login disabled by IMAP server");
+ return $this->setError(self::ERROR_BAD, 'Login disabled by IMAP server');
}
- list($code, $response) = $this->execute('LOGIN', array(
- $this->escape($user), $this->escape($password)), self::COMMAND_CAPABILITY | self::COMMAND_ANONYMIZED);
+ [$code, $response] = $this->execute('LOGIN', [$this->escape($user, true), $this->escape($password, true)],
+ self::COMMAND_CAPABILITY | self::COMMAND_ANONYMIZED);
// re-set capabilities list if untagged CAPABILITY response provided
if (preg_match('/\* CAPABILITY (.+)/i', $response, $matches)) {
@@ -787,20 +824,19 @@
*/
public function getHierarchyDelimiter()
{
- if (isset($this->prefs['delimiter'])) {
+ if (!empty($this->prefs['delimiter'])) {
return $this->prefs['delimiter'];
}
// try (LIST "" ""), should return delimiter (RFC2060 Sec 6.3.8)
- list($code, $response) = $this->execute('LIST',
- array($this->escape(''), $this->escape('')));
+ [$code, $response] = $this->execute('LIST', [$this->escape(''), $this->escape('')]);
if ($code == self::ERROR_OK) {
$args = $this->tokenizeResponse($response, 4);
$delimiter = $args[3];
if (strlen($delimiter) > 0) {
- return ($this->prefs['delimiter'] = $delimiter);
+ return $this->prefs['delimiter'] = $delimiter;
}
}
}
@@ -820,22 +856,22 @@
return self::ERROR_BAD;
}
- list($code, $response) = $this->execute('NAMESPACE');
+ [$code, $response] = $this->execute('NAMESPACE');
if ($code == self::ERROR_OK && preg_match('/^\* NAMESPACE /', $response)) {
$response = substr($response, 11);
$data = $this->tokenizeResponse($response);
}
- if (!is_array($data)) {
+ if (!isset($data) || !is_array($data)) {
return $code;
}
- $this->prefs['namespace'] = array(
+ $this->prefs['namespace'] = [
'personal' => $data[0],
'other' => $data[1],
'shared' => $data[2],
- );
+ ];
return $this->prefs['namespace'];
}
@@ -850,7 +886,7 @@
*
* @return bool True on success, False on failure
*/
- public function connect($host, $user, $password, $options = array())
+ public function connect($host, $user, $password, $options = [])
{
// configure
$this->set_prefs($options);
@@ -862,17 +898,17 @@
// check input
if (empty($host)) {
- $this->setError(self::ERROR_BAD, "Empty host");
+ $this->setError(self::ERROR_BAD, 'Empty host');
return false;
}
if (empty($user)) {
- $this->setError(self::ERROR_NO, "Empty user");
+ $this->setError(self::ERROR_NO, 'Empty user');
return false;
}
if (empty($password) && empty($options['gssapi_cn'])) {
- $this->setError(self::ERROR_NO, "Empty password");
+ $this->setError(self::ERROR_NO, 'Empty password');
return false;
}
@@ -881,13 +917,13 @@
return false;
}
- // Send ID info
- if (!empty($this->prefs['ident']) && $this->getCapability('ID')) {
- $this->data['ID'] = $this->id($this->prefs['ident']);
+ // Send pre authentication ID info (#7860)
+ if (!empty($this->prefs['preauth_ident']) && $this->getCapability('ID')) {
+ $this->data['ID'] = $this->id($this->prefs['preauth_ident']);
}
$auth_method = $this->prefs['auth_type'];
- $auth_methods = array();
+ $auth_methods = [];
$result = null;
// check for supported auth methods
@@ -897,7 +933,7 @@
}
// Use best (for security) supported authentication method
- $all_methods = array('DIGEST-MD5', 'CRAM-MD5', 'CRAM_MD5', 'PLAIN', 'LOGIN');
+ $all_methods = ['DIGEST-MD5', 'CRAM-MD5', 'CRAM_MD5', 'PLAIN', 'LOGIN'];
if (!empty($this->prefs['gssapi_cn'])) {
array_unshift($all_methods, 'GSSAPI');
@@ -916,7 +952,7 @@
}
// pre-login capabilities can be not complete
- $this->capability_readed = false;
+ $this->capability_read = false;
// Authenticate
switch ($auth_method) {
@@ -927,6 +963,8 @@
case 'GSSAPI':
case 'PLAIN':
case 'LOGIN':
+ case 'XOAUTH2':
+ case 'OAUTHBEARER':
$result = $this->authenticate($user, $password, $auth_method);
break;
@@ -943,8 +981,14 @@
if (!empty($this->prefs['force_caps'])) {
$this->clearCapability();
}
+
$this->logged = true;
+ // Send ID info after authentication to ensure reliable result (#7517)
+ if (!empty($this->prefs['ident']) && $this->getCapability('ID')) {
+ $this->data['ID'] = $this->id($this->prefs['ident']);
+ }
+
return true;
}
@@ -966,13 +1010,12 @@
$this->error = '';
$this->errornum = self::ERROR_OK;
- if (!$this->prefs['port']) {
- $this->prefs['port'] = 143;
- }
+ $port = empty($this->prefs['port']) ? 143 : $this->prefs['port'];
+ $ssl_mode = $this->prefs['ssl_mode'] ?? null;
// check for SSL
- if (!empty($this->prefs['ssl_mode']) && $this->prefs['ssl_mode'] != 'tls') {
- $host = $this->prefs['ssl_mode'] . '://' . $host;
+ if (!empty($ssl_mode) && $ssl_mode != 'tls') {
+ $host = $ssl_mode . '://' . $host;
}
if (empty($this->prefs['timeout']) || $this->prefs['timeout'] < 0) {
@@ -983,22 +1026,22 @@
// set connection identifier for debug output
$this->resourceid = strtoupper(substr(md5(microtime() . $host . $this->user), 0, 4));
- $_host = ($this->prefs['ssl_mode'] == 'tls' ? 'tls://' : '') . $host . ':' . $this->prefs['port'];
+ $_host = ($ssl_mode == 'tls' ? 'tls://' : '') . $host . ':' . $port;
$this->debug("Connecting to $_host...");
}
if (!empty($this->prefs['socket_options'])) {
- $context = stream_context_create($this->prefs['socket_options']);
- $this->fp = stream_socket_client($host . ':' . $this->prefs['port'], $errno, $errstr,
- $this->prefs['timeout'], STREAM_CLIENT_CONNECT, $context);
- }
- else {
- $this->fp = @fsockopen($host, $this->prefs['port'], $errno, $errstr, $this->prefs['timeout']);
+ $options = array_intersect_key($this->prefs['socket_options'], ['ssl' => 1]);
+ $context = stream_context_create($options);
+ $this->fp = stream_socket_client($host . ':' . $port, $errno, $errstr,
+ $this->prefs['timeout'], \STREAM_CLIENT_CONNECT, $context);
+ } else {
+ $this->fp = @fsockopen($host, $port, $errno, $errstr, $this->prefs['timeout']);
}
if (!$this->fp) {
- $this->setError(self::ERROR_BAD, sprintf("Could not connect to %s:%d: %s",
- $host, $this->prefs['port'], $errstr ?: "Unknown reason"));
+ $this->setError(self::ERROR_BAD, sprintf('Could not connect to %s:%d: %s',
+ $host, $port, $errstr ?: 'Unknown reason'));
return false;
}
@@ -1010,15 +1053,16 @@
$line = trim(fgets($this->fp, 8192));
if ($this->debug && $line) {
- $this->debug('S: '. $line);
+ $this->debug('S: ' . $line);
}
// Connected to wrong port or connection error?
if (!preg_match('/^\* (OK|PREAUTH)/i', $line)) {
- if ($line)
- $error = sprintf("Wrong startup greeting (%s:%d): %s", $host, $this->prefs['port'], $line);
- else
- $error = sprintf("Empty startup greeting (%s:%d)", $host, $this->prefs['port']);
+ if ($line) {
+ $error = sprintf('Wrong startup greeting (%s:%d): %s', $host, $port, $line);
+ } else {
+ $error = sprintf('Empty startup greeting (%s:%d)', $host, $port);
+ }
$this->setError(self::ERROR_BAD, $error);
$this->closeConnection();
@@ -1033,27 +1077,26 @@
}
// TLS connection
- if ($this->prefs['ssl_mode'] == 'tls' && $this->getCapability('STARTTLS')) {
+ if ($ssl_mode == 'tls' && $this->getCapability('STARTTLS')) {
$res = $this->execute('STARTTLS');
- if ($res[0] != self::ERROR_OK) {
+ if (empty($res) || $res[0] != self::ERROR_OK) {
$this->closeConnection();
return false;
}
if (isset($this->prefs['socket_options']['ssl']['crypto_method'])) {
$crypto_method = $this->prefs['socket_options']['ssl']['crypto_method'];
- }
- else {
+ } else {
// There is no flag to enable all TLS methods. Net_SMTP
// handles enabling TLS similarly.
- $crypto_method = STREAM_CRYPTO_METHOD_TLS_CLIENT
- | @STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
- | @STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
+ $crypto_method = \STREAM_CRYPTO_METHOD_TLS_CLIENT
+ | @\STREAM_CRYPTO_METHOD_TLSv1_1_CLIENT
+ | @\STREAM_CRYPTO_METHOD_TLSv1_2_CLIENT;
}
if (!stream_socket_enable_crypto($this->fp, true, $crypto_method)) {
- $this->setError(self::ERROR_BAD, "Unable to negotiate TLS");
+ $this->setError(self::ERROR_BAD, 'Unable to negotiate TLS');
$this->closeConnection();
return false;
}
@@ -1078,14 +1121,13 @@
// set auth method
if (!empty($this->prefs['auth_type'])) {
$this->prefs['auth_type'] = strtoupper($this->prefs['auth_type']);
- }
- else {
+ } 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']);
+ $this->prefs['disabled_caps'] = array_map('strtoupper', (array) $this->prefs['disabled_caps']);
}
// additional message flags
@@ -1115,6 +1157,7 @@
}
$this->closeSocket();
+ $this->clearCapability();
}
/**
@@ -1123,7 +1166,7 @@
* @param string $mailbox Mailbox name
* @param array $qresync_data QRESYNC data (RFC5162)
*
- * @return boolean True on success, false on error
+ * @return bool True on success, false on error
*/
public function select($mailbox, $qresync_data = null)
{
@@ -1135,7 +1178,7 @@
return true;
}
- $params = array($this->escape($mailbox));
+ $params = [$this->escape($mailbox)];
// QRESYNC data items
// 0. the last known UIDVALIDITY,
@@ -1148,10 +1191,10 @@
$qresync_data[2] = self::compressMessageSet($qresync_data[2]);
}
- $params[] = array('QRESYNC', $qresync_data);
+ $params[] = ['QRESYNC', $qresync_data];
}
- list($code, $response) = $this->execute('SELECT', $params);
+ [$code, $response] = $this->execute('SELECT', $params);
if ($code == self::ERROR_OK) {
$this->clear_mailbox_cache();
@@ -1164,58 +1207,57 @@
$pos += 7;
switch ($token) {
- case 'UIDNEXT':
- case 'UIDVALIDITY':
- case 'UNSEEN':
- if ($len = strspn($line, '0123456789', $pos)) {
- $this->data[$token] = (int) substr($line, $pos, $len);
- }
- break;
+ case 'UIDNEXT':
+ case 'UIDVALIDITY':
+ case 'UNSEEN':
+ if ($len = strspn($line, '0123456789', $pos)) {
+ $this->data[$token] = (int) substr($line, $pos, $len);
+ }
+ break;
- case 'HIGHESTMODSEQ':
- if ($len = strspn($line, '0123456789', $pos)) {
- $this->data[$token] = (string) substr($line, $pos, $len);
- }
- break;
+ case 'HIGHESTMODSEQ':
+ if ($len = strspn($line, '0123456789', $pos)) {
+ $this->data[$token] = (string) substr($line, $pos, $len);
+ }
+ break;
- case 'NOMODSEQ':
- $this->data[$token] = true;
- break;
+ case 'NOMODSEQ':
+ $this->data[$token] = true;
+ break;
- case 'PERMANENTFLAGS':
- $start = strpos($line, '(', $pos);
- $end = strrpos($line, ')');
- if ($start && $end) {
- $flags = substr($line, $start + 1, $end - $start - 1);
- $this->data[$token] = explode(' ', $flags);
- }
- break;
+ case 'PERMANENTFLAGS':
+ $start = strpos($line, '(', $pos);
+ $end = strrpos($line, ')');
+ if ($start && $end) {
+ $flags = substr($line, $start + 1, $end - $start - 1);
+ $this->data[$token] = explode(' ', $flags);
+ }
+ break;
}
- }
- else if (preg_match('/^\* ([0-9]+) (EXISTS|RECENT|FETCH)/i', $line, $match)) {
+ } elseif (preg_match('/^\* ([0-9]+) (EXISTS|RECENT|FETCH)/i', $line, $match)) {
$token = strtoupper($match[2]);
switch ($token) {
- case 'EXISTS':
- case 'RECENT':
- $this->data[$token] = (int) $match[1];
- break;
+ case 'EXISTS':
+ case 'RECENT':
+ $this->data[$token] = (int) $match[1];
+ break;
- case 'FETCH':
- // QRESYNC FETCH response (RFC5162)
- $line = substr($line, strlen($match[0]));
- $fetch_data = $this->tokenizeResponse($line, 1);
- $data = array('id' => $match[1]);
+ case 'FETCH':
+ // QRESYNC FETCH response (RFC5162)
+ $line = substr($line, strlen($match[0]));
+ $fetch_data = $this->tokenizeResponse($line, 1);
+ $data = ['id' => $match[1]];
- for ($i=0, $size=count($fetch_data); $i<$size; $i+=2) {
- $data[strtolower($fetch_data[$i])] = $fetch_data[$i+1];
- }
+ for ($i = 0, $size = count($fetch_data); $i < $size; $i += 2) {
+ $data[strtolower($fetch_data[$i])] = $fetch_data[$i + 1];
+ }
- $this->data['QRESYNC'][$data['uid']] = $data;
- break;
+ $this->data['QRESYNC'][$data['uid']] = $data;
+ break;
}
}
// QRESYNC VANISHED response (RFC5162)
- else if (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) {
+ elseif (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) {
$line = substr($line, strlen($match[0]));
$v_data = $this->tokenizeResponse($line, 1);
@@ -1241,9 +1283,10 @@
* in RFC3501: UIDNEXT, UIDVALIDITY, RECENT
*
* @return array Status item-value hash
+ *
* @since 0.5-beta
*/
- public function status($mailbox, $items = array())
+ public function status($mailbox, $items = [])
{
if (!strlen($mailbox)) {
return false;
@@ -1256,14 +1299,14 @@
$items[] = 'UNSEEN';
}
- list($code, $response) = $this->execute('STATUS',
- array($this->escape($mailbox), '(' . implode(' ', $items) . ')'), 0, '/^\* STATUS /i');
+ [$code, $response] = $this->execute('STATUS',
+ [$this->escape($mailbox), '(' . implode(' ', $items) . ')'], 0, '/^\* STATUS /i');
if ($code == self::ERROR_OK && $response) {
- $result = array();
+ $result = [];
$response = substr($response, 9); // remove prefix "* STATUS "
- list($mbox, $items) = $this->tokenizeResponse($response, 2);
+ [$mbox, $items] = $this->tokenizeResponse($response, 2);
// Fix for #1487859. Some buggy server returns not quoted
// folder name with spaces. Let's try to handle this situation
@@ -1276,11 +1319,11 @@
return $result;
}
- for ($i=0, $len=count($items); $i<$len; $i += 2) {
- $result[$items[$i]] = $items[$i+1];
+ for ($i = 0, $len = count($items); $i < $len; $i += 2) {
+ $result[$items[$i]] = $items[$i + 1];
}
- $this->data['STATUS:'.$mailbox] = $result;
+ $this->data['STATUS:' . $mailbox] = $result;
return $result;
}
@@ -1294,7 +1337,7 @@
* @param string $mailbox Mailbox name
* @param string|array $messages Message UIDs to expunge
*
- * @return boolean True on success, False on error
+ * @return bool True on success, False on error
*/
public function expunge($mailbox, $messages = null)
{
@@ -1302,8 +1345,8 @@
return false;
}
- if (!$this->data['READ-WRITE']) {
- $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
+ if (empty($this->data['READ-WRITE'])) {
+ $this->setError(self::ERROR_READONLY, 'Mailbox is read-only');
return false;
}
@@ -1312,9 +1355,8 @@
if (!empty($messages) && $messages != '*' && $this->hasCapability('UIDPLUS')) {
$messages = self::compressMessageSet($messages);
- $result = $this->execute('UID EXPUNGE', array($messages), self::COMMAND_NORESPONSE);
- }
- else {
+ $result = $this->execute('UID EXPUNGE', [$messages], self::COMMAND_NORESPONSE);
+ } else {
$result = $this->execute('EXPUNGE', null, self::COMMAND_NORESPONSE);
}
@@ -1329,7 +1371,8 @@
/**
* Executes CLOSE command
*
- * @return boolean True on success, False on error
+ * @return bool True on success, False on error
+ *
* @since 0.5
*/
public function close()
@@ -1349,12 +1392,11 @@
*
* @param string $mailbox Mailbox name
*
- * @return boolean True on success, False on error
+ * @return bool True on success, False on error
*/
public function subscribe($mailbox)
{
- $result = $this->execute('SUBSCRIBE', array($this->escape($mailbox)),
- self::COMMAND_NORESPONSE);
+ $result = $this->execute('SUBSCRIBE', [$this->escape($mailbox)], self::COMMAND_NORESPONSE);
return $result == self::ERROR_OK;
}
@@ -1364,12 +1406,11 @@
*
* @param string $mailbox Mailbox name
*
- * @return boolean True on success, False on error
+ * @return bool True on success, False on error
*/
public function unsubscribe($mailbox)
{
- $result = $this->execute('UNSUBSCRIBE', array($this->escape($mailbox)),
- self::COMMAND_NORESPONSE);
+ $result = $this->execute('UNSUBSCRIBE', [$this->escape($mailbox)], self::COMMAND_NORESPONSE);
return $result == self::ERROR_OK;
}
@@ -1384,7 +1425,7 @@
*/
public function createFolder($mailbox, $types = null)
{
- $args = array($this->escape($mailbox));
+ $args = [$this->escape($mailbox)];
// RFC 6154: CREATE-SPECIAL-USE
if (!empty($types) && $this->getCapability('CREATE-SPECIAL-USE')) {
@@ -1399,14 +1440,14 @@
/**
* Folder renaming (RENAME)
*
- * @param string $mailbox Mailbox name
+ * @param string $from Mailbox name
+ * @param string $to Mailbox name
*
* @return bool True on success, False on error
*/
public function renameFolder($from, $to)
{
- $result = $this->execute('RENAME', array($this->escape($from), $this->escape($to)),
- self::COMMAND_NORESPONSE);
+ $result = $this->execute('RENAME', [$this->escape($from), $this->escape($to)], self::COMMAND_NORESPONSE);
return $result == self::ERROR_OK;
}
@@ -1416,12 +1457,16 @@
*
* @param string $mailbox Mailbox name
*
- * @return boolean True on success, False on error
+ * @return bool True on success, False on error
*/
public function deleteFolder($mailbox)
{
- $result = $this->execute('DELETE', array($this->escape($mailbox)),
- self::COMMAND_NORESPONSE);
+ // Unselect the folder to prevent "BYE Fatal error: Mailbox has been (re)moved" on Cyrus IMAP
+ if ($this->selected === $mailbox && $this->hasCapability('UNSELECT')) {
+ $this->execute('UNSELECT', [], self::COMMAND_NORESPONSE);
+ }
+
+ $result = $this->execute('DELETE', [$this->escape($mailbox)], self::COMMAND_NORESPONSE);
return $result == self::ERROR_OK;
}
@@ -1431,25 +1476,27 @@
*
* @param string $mailbox Mailbox name
*
- * @return boolean True on success, False on error
+ * @return bool True on success, False on error
*/
public function clearFolder($mailbox)
{
- $res = false;
if ($this->countMessages($mailbox) > 0) {
$res = $this->flag($mailbox, '1:*', 'DELETED');
+ } else {
+ return true;
}
- if ($res) {
+ if (!empty($res)) {
if ($this->selected === $mailbox) {
$res = $this->close();
- }
- else {
+ } else {
$res = $this->expunge($mailbox);
}
+
+ return $res;
}
- return $res;
+ return false;
}
/**
@@ -1460,10 +1507,10 @@
* @param array $return_opts (see self::_listMailboxes)
* @param array $select_opts (see self::_listMailboxes)
*
- * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response
+ * @return array|bool List of mailboxes or hash of options if STATUS/MYRIGHTS response
* is requested, False on error.
*/
- public function listMailboxes($ref, $mailbox, $return_opts = array(), $select_opts = array())
+ public function listMailboxes($ref, $mailbox, $return_opts = [], $select_opts = [])
{
return $this->_listMailboxes($ref, $mailbox, false, $return_opts, $select_opts);
}
@@ -1475,10 +1522,10 @@
* @param string $mailbox Mailbox name
* @param array $return_opts (see self::_listMailboxes)
*
- * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response
+ * @return array|bool List of mailboxes or hash of options if STATUS/MYRIGHTS response
* is requested, False on error.
*/
- public function listSubscribed($ref, $mailbox, $return_opts = array())
+ public function listSubscribed($ref, $mailbox, $return_opts = [])
{
return $this->_listMailboxes($ref, $mailbox, true, $return_opts, null);
}
@@ -1491,23 +1538,23 @@
* @param bool $subscribed Enables returning subscribed mailboxes only
* @param array $return_opts List of RETURN options (RFC5819: LIST-STATUS, RFC5258: LIST-EXTENDED)
* Possible: MESSAGES, RECENT, UIDNEXT, UIDVALIDITY, UNSEEN,
- * MYRIGHTS, SUBSCRIBED, CHILDREN
+ * MYRIGHTS, SUBSCRIBED, CHILDREN
* @param array $select_opts List of selection options (RFC5258: LIST-EXTENDED)
* Possible: SUBSCRIBED, RECURSIVEMATCH, REMOTE,
- * SPECIAL-USE (RFC6154)
+ * SPECIAL-USE (RFC6154)
*
- * @return array|bool List of mailboxes or hash of options if STATUS/MYROGHTS response
+ * @return array|bool List of mailboxes or hash of options if STATUS/MYRIGHTS response
* is requested, False on error.
*/
- protected function _listMailboxes($ref, $mailbox, $subscribed=false,
- $return_opts=array(), $select_opts=array())
+ protected function _listMailboxes($ref, $mailbox, $subscribed = false, $return_opts = [], $select_opts = [])
{
if (!strlen($mailbox)) {
$mailbox = '*';
}
- $args = array();
- $rets = array();
+ $lstatus = false;
+ $args = [];
+ $rets = [];
if (!empty($select_opts) && $this->getCapability('LIST-EXTENDED')) {
$select_opts = (array) $select_opts;
@@ -1515,20 +1562,18 @@
$args[] = '(' . implode(' ', $select_opts) . ')';
}
- $lstatus = false;
-
$args[] = $this->escape($ref);
$args[] = $this->escape($mailbox);
if (!empty($return_opts) && $this->getCapability('LIST-EXTENDED')) {
- $ext_opts = array('SUBSCRIBED', 'CHILDREN');
+ $ext_opts = ['SUBSCRIBED', 'CHILDREN'];
$rets = array_intersect($return_opts, $ext_opts);
$return_opts = array_diff($return_opts, $rets);
}
if (!empty($return_opts) && $this->getCapability('LIST-STATUS')) {
$lstatus = true;
- $status_opts = array('MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN');
+ $status_opts = ['MESSAGES', 'RECENT', 'UIDNEXT', 'UIDVALIDITY', 'UNSEEN', 'SIZE'];
$opts = array_diff($return_opts, $status_opts);
$status_opts = array_diff($return_opts, $opts);
@@ -1545,17 +1590,17 @@
$args[] = 'RETURN (' . implode(' ', $rets) . ')';
}
- list($code, $response) = $this->execute($subscribed ? 'LSUB' : 'LIST', $args);
+ [$code, $response] = $this->execute($subscribed ? 'LSUB' : 'LIST', $args);
if ($code == self::ERROR_OK) {
- $folders = array();
+ $folders = [];
$last = 0;
$pos = 0;
$response .= "\r\n";
- while ($pos = strpos($response, "\r\n", $pos+1)) {
+ while ($pos = strpos($response, "\r\n", $pos + 1)) {
// literal string, not real end-of-command-line
- if ($response[$pos-1] == '}') {
+ if ($response[$pos - 1] == '}') {
continue;
}
@@ -1571,19 +1616,30 @@
// * LIST (<options>) <delimiter> <mailbox>
if ($cmd == 'LIST' || $cmd == 'LSUB') {
- list($opts, $delim, $mailbox) = $this->tokenizeResponse($line, 3);
+ [$opts, $delim, $mailbox] = $this->tokenizeResponse($line, 3);
// Remove redundant separator at the end of folder name, UW-IMAP bug? (#1488879)
if ($delim) {
$mailbox = rtrim($mailbox, $delim);
}
+ // Make it easier for the client to deal with INBOX folder
+ // by always returning the word with all capital letters
+ if (strlen($mailbox) == 5
+ && ($mailbox[0] == 'i' || $mailbox[0] == 'I')
+ && ($mailbox[1] == 'n' || $mailbox[1] == 'N')
+ && ($mailbox[2] == 'b' || $mailbox[2] == 'B')
+ && ($mailbox[3] == 'o' || $mailbox[3] == 'O')
+ && ($mailbox[4] == 'x' || $mailbox[4] == 'X')
+ ) {
+ $mailbox = 'INBOX';
+ }
+
// Add to result array
if (!$lstatus) {
$folders[] = $mailbox;
- }
- else {
- $folders[$mailbox] = array();
+ } else {
+ $folders[$mailbox] = [];
}
// store folder options
@@ -1591,26 +1647,24 @@
// Add to options array
if (empty($this->data['LIST'][$mailbox])) {
$this->data['LIST'][$mailbox] = $opts;
- }
- else if (!empty($opts)) {
+ } elseif (!empty($opts)) {
$this->data['LIST'][$mailbox] = array_unique(array_merge(
$this->data['LIST'][$mailbox], $opts));
}
}
- }
- else if ($lstatus) {
+ } elseif ($lstatus) {
// * STATUS <mailbox> (<result>)
if ($cmd == 'STATUS') {
- list($mailbox, $status) = $this->tokenizeResponse($line, 2);
+ [$mailbox, $status] = $this->tokenizeResponse($line, 2);
- for ($i=0, $len=count($status); $i<$len; $i += 2) {
- list($name, $value) = $this->tokenizeResponse($status, 2);
+ for ($i = 0, $len = count($status); $i < $len; $i += 2) {
+ [$name, $value] = $this->tokenizeResponse($status, 2);
$folders[$mailbox][$name] = $value;
}
}
// * MYRIGHTS <mailbox> <acl>
- else if ($cmd == 'MYRIGHTS') {
- list($mailbox, $acl) = $this->tokenizeResponse($line, 2);
+ elseif ($cmd == 'MYRIGHTS') {
+ [$mailbox, $acl] = $this->tokenizeResponse($line, 2);
$folders[$mailbox]['MYRIGHTS'] = $acl;
}
}
@@ -1635,9 +1689,10 @@
return $this->data['EXISTS'];
}
- if (isset($this->data["STATUS:".$mailbox])) {
- $cache = $this->data["STATUS:".$mailbox];
- if (!empty($cache) && isset($cache['MESSAGES'])) {
+ // Check internal cache
+ if (!empty($this->data['STATUS:' . $mailbox])) {
+ $cache = $this->data['STATUS:' . $mailbox];
+ if (isset($cache['MESSAGES'])) {
return (int) $cache['MESSAGES'];
}
}
@@ -1665,13 +1720,13 @@
}
// Check internal cache
- $cache = $this->data['STATUS:'.$mailbox];
+ $cache = $this->data['STATUS:' . $mailbox];
if (!empty($cache) && isset($cache['RECENT'])) {
return (int) $cache['RECENT'];
}
// Try STATUS (should be faster than SELECT)
- $counts = $this->status($mailbox, array('RECENT'));
+ $counts = $this->status($mailbox, ['RECENT']);
if (is_array($counts)) {
return (int) $counts['RECENT'];
}
@@ -1689,9 +1744,11 @@
public function countUnseen($mailbox)
{
// Check internal cache
- $cache = $this->data['STATUS:'.$mailbox];
- if (!empty($cache) && isset($cache['UNSEEN'])) {
- return (int) $cache['UNSEEN'];
+ if (!empty($this->data['STATUS:' . $mailbox])) {
+ $cache = $this->data['STATUS:' . $mailbox];
+ if (isset($cache['UNSEEN'])) {
+ return (int) $cache['UNSEEN'];
+ }
}
// Try STATUS (should be faster than SELECT+SEARCH)
@@ -1701,7 +1758,7 @@
}
// Invoke SEARCH as a fallback
- $index = $this->search($mailbox, 'ALL UNSEEN', false, array('COUNT'));
+ $index = $this->search($mailbox, 'ALL UNSEEN', false, ['COUNT']);
if (!$index->is_error()) {
return $index->count();
}
@@ -1714,10 +1771,11 @@
*
* @param array $items Client identification information key/value hash
*
- * @return array Server identification information key/value hash
+ * @return array|false Server identification information key/value hash, False on error
+ *
* @since 0.6
*/
- public function id($items = array())
+ public function id($items = [])
{
if (is_array($items) && !empty($items)) {
foreach ($items as $key => $value) {
@@ -1726,17 +1784,20 @@
}
}
- list($code, $response) = $this->execute('ID',
- array(!empty($args) ? '(' . implode(' ', (array) $args) . ')' : $this->escape(null)),
- 0, '/^\* ID /i');
+ [$code, $response] = $this->execute('ID',
+ [!empty($args) ? '(' . implode(' ', (array) $args) . ')' : $this->escape(null)],
+ 0, '/^\* ID /i'
+ );
if ($code == self::ERROR_OK && $response) {
$response = substr($response, 5); // remove prefix "* ID "
$items = $this->tokenizeResponse($response, 1);
- $result = null;
+ $result = [];
- for ($i=0, $len=count($items); $i<$len; $i += 2) {
- $result[$items[$i]] = $items[$i+1];
+ if (is_array($items)) {
+ for ($i = 0, $len = count($items); $i < $len; $i += 2) {
+ $result[$items[$i]] = $items[$i + 1];
+ }
}
return $result;
@@ -1751,6 +1812,7 @@
* @param mixed $extension Extension name to enable (or array of names)
*
* @return array|bool List of enabled extensions, False on error
+ *
* @since 0.6
*/
public function enable($extension)
@@ -1764,7 +1826,7 @@
}
if (!is_array($extension)) {
- $extension = array($extension);
+ $extension = [$extension];
}
if (!empty($this->extensions_enabled)) {
@@ -1781,13 +1843,13 @@
}
}
- list($code, $response) = $this->execute('ENABLE', $extension, 0, '/^\* ENABLED /i');
+ [$code, $response] = $this->execute('ENABLE', $extension, 0, '/^\* ENABLED /i');
if ($code == self::ERROR_OK && $response) {
$response = substr($response, 10); // remove prefix "* ENABLED "
$result = (array) $this->tokenizeResponse($response);
- $this->extensions_enabled = array_unique(array_merge((array)$this->extensions_enabled, $result));
+ $this->extensions_enabled = array_unique(array_merge((array) $this->extensions_enabled, $result));
return $this->extensions_enabled;
}
@@ -1809,7 +1871,7 @@
public function sort($mailbox, $field = 'ARRIVAL', $criteria = '', $return_uid = false, $encoding = 'US-ASCII')
{
$old_sel = $this->selected;
- $supported = array('ARRIVAL', 'CC', 'DATE', 'FROM', 'SIZE', 'SUBJECT', 'TO');
+ $supported = ['ARRIVAL', 'CC', 'DATE', 'FROM', 'SIZE', 'SUBJECT', 'TO'];
$field = strtoupper($field);
if ($field == 'INTERNALDATE') {
@@ -1825,7 +1887,7 @@
}
// return empty result when folder is empty and we're just after SELECT
- if ($old_sel != $mailbox && !$this->data['EXISTS']) {
+ if ($old_sel != $mailbox && empty($this->data['EXISTS'])) {
return new rcube_result_index($mailbox, '* SORT');
}
@@ -1837,8 +1899,8 @@
$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, $criteria));
+ [$code, $response] = $this->execute($return_uid ? 'UID SORT' : 'SORT',
+ ["($field)", $encoding, $criteria]);
if ($code != self::ERROR_OK) {
$response = null;
@@ -1873,10 +1935,10 @@
$encoding = $encoding ? trim($encoding) : 'US-ASCII';
$algorithm = $algorithm ? trim($algorithm) : 'REFERENCES';
- $criteria = $criteria ? 'ALL '.trim($criteria) : 'ALL';
+ $criteria = $criteria ? 'ALL ' . trim($criteria) : 'ALL';
- list($code, $response) = $this->execute($return_uid ? 'UID THREAD' : 'THREAD',
- array($algorithm, $encoding, $criteria));
+ [$code, $response] = $this->execute($return_uid ? 'UID THREAD' : 'THREAD',
+ [$algorithm, $encoding, $criteria]);
if ($code != self::ERROR_OK) {
$response = null;
@@ -1895,7 +1957,7 @@
*
* @return rcube_result_index Result data
*/
- public function search($mailbox, $criteria, $return_uid = false, $items = array())
+ public function search($mailbox, $criteria, $return_uid = false, $items = [])
{
$old_sel = $this->selected;
@@ -1911,7 +1973,7 @@
// If ESEARCH is supported always use ALL
// but not when items are specified or using simple id2uid search
if (empty($items) && preg_match('/[^0-9]/', $criteria)) {
- $items = array('ALL');
+ $items = ['ALL'];
}
$esearch = empty($items) ? false : $this->getCapability('ESEARCH');
@@ -1925,13 +1987,11 @@
if (!empty($criteria)) {
$params .= ($params ? ' ' : '') . $criteria;
- }
- else {
+ } else {
$params .= 'ALL';
}
- list($code, $response) = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH',
- array($params));
+ [$code, $response] = $this->execute($return_uid ? 'UID SEARCH' : 'SEARCH', [$params]);
if ($code != self::ERROR_OK) {
$response = null;
@@ -1952,8 +2012,8 @@
*
* @return rcube_result_index Response data
*/
- public function index($mailbox, $message_set, $index_field='', $skip_deleted=true,
- $uidfetch=false, $return_uid=false)
+ public function index($mailbox, $message_set, $index_field = '', $skip_deleted = true,
+ $uidfetch = false, $return_uid = false)
{
$msg_index = $this->fetchHeaderIndex($mailbox, $message_set,
$index_field, $skip_deleted, $uidfetch, $return_uid);
@@ -1962,8 +2022,7 @@
asort($msg_index); // ASC
$msg_index = array_keys($msg_index);
$msg_index = '* SEARCH ' . implode(' ', $msg_index);
- }
- else {
+ } else {
$msg_index = is_array($msg_index) ? '* SEARCH' : null;
}
@@ -1985,41 +2044,45 @@
public function fetchHeaderIndex($mailbox, $message_set, $index_field = '', $skip_deleted = true,
$uidfetch = false, $return_uid = false)
{
+ // Validate input
if (is_array($message_set)) {
if (!($message_set = $this->compressMessageSet($message_set))) {
return false;
}
- }
- else {
- list($from_idx, $to_idx) = explode(':', $message_set);
- if (empty($message_set) ||
- (isset($to_idx) && $to_idx != '*' && (int)$from_idx > (int)$to_idx)
- ) {
+ } elseif (empty($message_set)) {
+ return false;
+ } elseif (strpos($message_set, ':')) {
+ [$from_idx, $to_idx] = explode(':', $message_set);
+ if ($to_idx != '*' && (int) $from_idx > (int) $to_idx) {
return false;
}
}
$index_field = empty($index_field) ? 'DATE' : strtoupper($index_field);
- $fields_a['DATE'] = 1;
- $fields_a['INTERNALDATE'] = 4;
- $fields_a['ARRIVAL'] = 4;
- $fields_a['FROM'] = 1;
- $fields_a['REPLY-TO'] = 1;
- $fields_a['SENDER'] = 1;
- $fields_a['TO'] = 1;
- $fields_a['CC'] = 1;
- $fields_a['SUBJECT'] = 1;
- $fields_a['UID'] = 2;
- $fields_a['SIZE'] = 2;
- $fields_a['SEEN'] = 3;
- $fields_a['RECENT'] = 3;
- $fields_a['DELETED'] = 3;
-
- if (!($mode = $fields_a[$index_field])) {
+ $supported = [
+ 'DATE' => 1,
+ 'INTERNALDATE' => 4,
+ 'ARRIVAL' => 4,
+ 'FROM' => 1,
+ 'REPLY-TO' => 1,
+ 'SENDER' => 1,
+ 'TO' => 1,
+ 'CC' => 1,
+ 'SUBJECT' => 1,
+ 'UID' => 2,
+ 'SIZE' => 2,
+ 'SEEN' => 3,
+ 'RECENT' => 3,
+ 'DELETED' => 3,
+ ];
+
+ if (empty($supported[$index_field])) {
return false;
}
+ $mode = $supported[$index_field];
+
// Select the mailbox
if (!$this->select($mailbox)) {
return false;
@@ -2028,7 +2091,7 @@
// build FETCH command string
$key = $this->nextTag();
$cmd = $uidfetch ? 'UID FETCH' : 'FETCH';
- $fields = array();
+ $fields = [];
if ($return_uid) {
$fields[] = 'UID';
@@ -2042,30 +2105,26 @@
$fields[] = 'INTERNALDATE';
}
$fields[] = "BODY.PEEK[HEADER.FIELDS ($index_field)]";
- }
- else if ($mode == 2) {
+ } elseif ($mode == 2) {
if ($index_field == 'SIZE') {
$fields[] = 'RFC822.SIZE';
- }
- else if (!$return_uid || $index_field != 'UID') {
+ } elseif (!$return_uid || $index_field != 'UID') {
$fields[] = $index_field;
}
- }
- else if ($mode == 3 && !$skip_deleted) {
+ } elseif ($mode == 3 && !$skip_deleted) {
$fields[] = 'FLAGS';
- }
- else if ($mode == 4) {
+ } elseif ($mode == 4) {
$fields[] = 'INTERNALDATE';
}
- $request = "$key $cmd $message_set (" . implode(' ', $fields) . ")";
+ $request = "$key $cmd $message_set (" . implode(' ', $fields) . ')';
if (!$this->putLine($request)) {
$this->setError(self::ERROR_COMMAND, "Failed to send $cmd command");
return false;
}
- $result = array();
+ $result = [];
do {
$line = rtrim($this->readLine(200));
@@ -2078,11 +2137,11 @@
if ($return_uid) {
if (preg_match('/UID ([0-9]+)/', $line, $matches)) {
$id = (int) $matches[1];
- }
- else {
+ } else {
continue;
}
}
+
if ($skip_deleted && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
$flags = explode(' ', strtoupper($matches[1]));
if (in_array('\\DELETED', $flags)) {
@@ -2092,7 +2151,7 @@
if ($mode == 1 && $index_field == 'DATE') {
if (preg_match('/BODY\[HEADER\.FIELDS \("*DATE"*\)\] (.*)/', $line, $matches)) {
- $value = preg_replace(array('/^"*[a-z]+:/i'), '', $matches[1]);
+ $value = preg_replace(['/^"*[a-z]+:/i'], '', $matches[1]);
$value = trim($value);
$result[$id] = rcube_utils::strtotime($value);
}
@@ -2100,46 +2159,37 @@
if (empty($result[$id])) {
if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) {
$result[$id] = rcube_utils::strtotime($matches[1]);
- }
- else {
+ } else {
$result[$id] = 0;
}
}
- }
- else if ($mode == 1) {
+ } elseif ($mode == 1) {
if (preg_match('/BODY\[HEADER\.FIELDS \("?(FROM|REPLY-TO|SENDER|TO|SUBJECT)"?\)\] (.*)/', $line, $matches)) {
- $value = preg_replace(array('/^"*[a-z]+:/i', '/\s+$/sm'), array('', ''), $matches[2]);
+ $value = preg_replace(['/^"*[a-z]+:/i', '/\s+$/sm'], ['', ''], $matches[2]);
$result[$id] = trim($value);
- }
- else {
+ } else {
$result[$id] = '';
}
- }
- else if ($mode == 2) {
+ } elseif ($mode == 2) {
if (preg_match('/' . $index_field . ' ([0-9]+)/', $line, $matches)) {
$result[$id] = trim($matches[1]);
- }
- else {
+ } else {
$result[$id] = 0;
}
- }
- else if ($mode == 3) {
+ } elseif ($mode == 3) {
if (!$flags && preg_match('/FLAGS \(([^)]+)\)/', $line, $matches)) {
$flags = explode(' ', $matches[1]);
}
- $result[$id] = in_array("\\".$index_field, (array) $flags) ? 1 : 0;
- }
- else if ($mode == 4) {
+ $result[$id] = in_array('\\' . $index_field, (array) $flags) ? 1 : 0;
+ } elseif ($mode == 4) {
if (preg_match('/INTERNALDATE "([^"]+)"/', $line, $matches)) {
$result[$id] = rcube_utils::strtotime($matches[1]);
- }
- else {
+ } else {
$result[$id] = 0;
}
}
}
- }
- while (!$this->startsWith($line, $key, true, true));
+ } while (!$this->startsWith($line, $key, true, true));
return $result;
}
@@ -2168,7 +2218,7 @@
* Returns message unique identifier (UID)
*
* @param string $mailbox Mailbox name
- * @param int $uid Message sequence identifier
+ * @param int $id Message sequence identifier
*
* @return int Message unique identifier
*/
@@ -2182,8 +2232,8 @@
return null;
}
- if ($uid = $this->data['UID-MAP'][$id]) {
- return $uid;
+ if (!empty($this->data['UID-MAP'][$id])) {
+ return $this->data['UID-MAP'][$id];
}
if (isset($this->data['EXISTS']) && $id > $this->data['EXISTS']) {
@@ -2246,12 +2296,12 @@
return false;
}
- if (!$this->data['READ-WRITE']) {
- $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
+ if (empty($this->data['READ-WRITE'])) {
+ $this->setError(self::ERROR_READONLY, 'Mailbox is read-only');
return false;
}
- if ($this->flags[strtoupper($flag)]) {
+ if (!empty($this->flags[strtoupper($flag)])) {
$flag = $this->flags[strtoupper($flag)];
}
@@ -2265,16 +2315,17 @@
// Clear internal status cache
if ($flag == 'SEEN') {
- unset($this->data['STATUS:'.$mailbox]['UNSEEN']);
+ unset($this->data['STATUS:' . $mailbox]['UNSEEN']);
}
if ($mod != '+' && $mod != '-') {
$mod = '+';
}
- $result = $this->execute('UID STORE', array(
- $this->compressMessageSet($messages), $mod . 'FLAGS.SILENT', "($flag)"),
- self::COMMAND_NORESPONSE);
+ $result = $this->execute('UID STORE',
+ [$this->compressMessageSet($messages), $mod . 'FLAGS.SILENT', "($flag)"],
+ self::COMMAND_NORESPONSE
+ );
return $result == self::ERROR_OK;
}
@@ -2298,11 +2349,12 @@
}
// Clear internal status cache
- unset($this->data['STATUS:'.$to]);
+ unset($this->data['STATUS:' . $to]);
- $result = $this->execute('UID COPY', array(
- $this->compressMessageSet($messages), $this->escape($to)),
- self::COMMAND_NORESPONSE);
+ $result = $this->execute('UID COPY',
+ [$this->compressMessageSet($messages), $this->escape($to)],
+ self::COMMAND_NORESPONSE
+ );
return $result == self::ERROR_OK;
}
@@ -2322,8 +2374,8 @@
return false;
}
- if (!$this->data['READ-WRITE']) {
- $this->setError(self::ERROR_READONLY, "Mailbox is read-only");
+ if (empty($this->data['READ-WRITE'])) {
+ $this->setError(self::ERROR_READONLY, 'Mailbox is read-only');
return false;
}
@@ -2333,12 +2385,13 @@
unset($this->data['COPYUID']);
// Clear internal status cache
- unset($this->data['STATUS:'.$to]);
+ unset($this->data['STATUS:' . $to]);
$this->clear_status_cache($from);
- $result = $this->execute('UID MOVE', array(
- $this->compressMessageSet($messages), $this->escape($to)),
- self::COMMAND_NORESPONSE);
+ $result = $this->execute('UID MOVE',
+ [$this->compressMessageSet($messages), $this->escape($to)],
+ self::COMMAND_NORESPONSE
+ );
return $result == self::ERROR_OK;
}
@@ -2348,15 +2401,14 @@
if ($result) {
// Clear internal status cache
- unset($this->data['STATUS:'.$from]);
+ unset($this->data['STATUS:' . $from]);
$result = $this->flag($from, $messages, 'DELETED');
if ($messages == '*') {
// CLOSE+SELECT should be faster than EXPUNGE
$this->close();
- }
- else {
+ } else {
$this->expunge($from, $messages);
}
}
@@ -2375,9 +2427,10 @@
* @param bool $vanished Enables VANISHED parameter (RFC5162) for CHANGEDSINCE query
*
* @return array List of rcube_message_header elements, False on error
+ *
* @since 0.6
*/
- public function fetch($mailbox, $message_set, $is_uid = false, $query_items = array(),
+ public function fetch($mailbox, $message_set, $is_uid = false, $query_items = [],
$mod_seq = null, $vanished = false)
{
if (!$this->select($mailbox)) {
@@ -2385,14 +2438,14 @@
}
$message_set = $this->compressMessageSet($message_set);
- $result = array();
+ $result = [];
$key = $this->nextTag();
$cmd = ($is_uid ? 'UID ' : '') . 'FETCH';
- $request = "$key $cmd $message_set (" . implode(' ', $query_items) . ")";
+ $request = "$key $cmd $message_set (" . implode(' ', $query_items) . ')';
if ($mod_seq !== null && $this->hasCapability('CONDSTORE')) {
- $request .= " (CHANGEDSINCE $mod_seq" . ($vanished ? " VANISHED" : '') .")";
+ $request .= " (CHANGEDSINCE $mod_seq" . ($vanished ? ' VANISHED' : '') . ')';
}
if (!$this->putLine($request)) {
@@ -2401,7 +2454,7 @@
}
do {
- $line = $this->readLine(4096);
+ $line = $this->readFullLine(4096);
if (!$line) {
break;
@@ -2415,90 +2468,57 @@
if (preg_match('/^\* ([0-9]+) FETCH/', $line, $m)) {
$id = intval($m[1]);
- $result[$id] = new rcube_message_header;
+ $result[$id] = new rcube_message_header();
$result[$id]->id = $id;
$result[$id]->subject = '';
$result[$id]->messageID = 'mid:' . $id;
$headers = null;
- $lines = array();
$line = substr($line, strlen($m[0]) + 2);
- $ln = 0;
-
- // get complete entry
- while (preg_match('/\{([0-9]+)\}\r\n$/', $line, $m)) {
- $bytes = $m[1];
- $out = '';
-
- while (strlen($out) < $bytes) {
- $out = $this->readBytes($bytes);
- if ($out === null) {
- break;
- }
- $line .= $out;
- }
-
- $str = $this->readLine(4096);
- if ($str === false) {
- break;
- }
-
- $line .= $str;
- }
// Tokenize response and assign to object properties
- while (list($name, $value) = $this->tokenizeResponse($line, 2)) {
+ while (($tokens = $this->tokenizeResponse($line, 2)) && count($tokens) == 2) {
+ [$name, $value] = $tokens;
if ($name == 'UID') {
$result[$id]->uid = intval($value);
- }
- else if ($name == 'RFC822.SIZE') {
+ } elseif ($name == 'RFC822.SIZE') {
$result[$id]->size = intval($value);
- }
- else if ($name == 'RFC822.TEXT') {
+ } elseif ($name == 'RFC822.TEXT') {
$result[$id]->body = $value;
- }
- else if ($name == 'INTERNALDATE') {
+ } elseif ($name == 'INTERNALDATE') {
$result[$id]->internaldate = $value;
$result[$id]->date = $value;
$result[$id]->timestamp = rcube_utils::strtotime($value);
- }
- else if ($name == 'FLAGS') {
+ } elseif ($name == 'FLAGS') {
if (!empty($value)) {
- foreach ((array)$value as $flag) {
- $flag = str_replace(array('$', "\\"), '', $flag);
+ foreach ((array) $value as $flag) {
+ $flag = str_replace(['$', '\\'], '', $flag);
$flag = strtoupper($flag);
$result[$id]->flags[$flag] = true;
}
}
- }
- else if ($name == 'MODSEQ') {
+ } elseif ($name == 'MODSEQ') {
$result[$id]->modseq = $value[0];
- }
- else if ($name == 'ENVELOPE') {
+ } elseif ($name == 'ENVELOPE') {
$result[$id]->envelope = $value;
- }
- else if ($name == 'BODYSTRUCTURE' || ($name == 'BODY' && count($value) > 2)) {
+ } elseif ($name == 'BODYSTRUCTURE' || ($name == 'BODY' && count($value) > 2)) {
if (!is_array($value[0]) && (strtolower($value[0]) == 'message' && strtolower($value[1]) == 'rfc822')) {
- $value = array($value);
+ $value = [$value];
}
$result[$id]->bodystructure = $value;
- }
- else if ($name == 'RFC822') {
+ } elseif ($name == 'RFC822') {
$result[$id]->body = $value;
- }
- else if (stripos($name, 'BODY[') === 0) {
+ } elseif (stripos($name, 'BODY[') === 0) {
$name = str_replace(']', '', substr($name, 5));
if ($name == 'HEADER.FIELDS') {
// skip ']' after headers list
$this->tokenizeResponse($line, 1);
$headers = $this->tokenizeResponse($line, 1);
- }
- else if (strlen($name)) {
+ } elseif (strlen($name)) {
$result[$id]->bodypart[$name] = $value;
- }
- else {
+ } else {
$result[$id]->body = $value;
}
}
@@ -2507,89 +2527,97 @@
// create array with header field:data
if (!empty($headers)) {
$headers = explode("\n", trim($headers));
+ $lines = [];
+ $ln = 0;
+
foreach ($headers as $resln) {
- if (ord($resln[0]) <= 32) {
- $lines[$ln] .= (empty($lines[$ln]) ? '' : "\n") . trim($resln);
- }
- else {
+ if (!isset($resln[0]) || ord($resln[0]) <= 32) {
+ $lines[$ln] = ($lines[$ln] ?? '') . (empty($lines[$ln]) ? '' : "\n") . trim($resln);
+ } else {
$lines[++$ln] = trim($resln);
}
}
foreach ($lines as $str) {
- list($field, $string) = explode(':', $str, 2);
+ if (strpos($str, ':') === false) {
+ continue;
+ }
+
+ [$field, $string] = explode(':', $str, 2);
$field = strtolower($field);
$string = preg_replace('/\n[\t\s]*/', ' ', trim($string));
switch ($field) {
- case 'date';
- $string = substr($string, 0, 128);
- $result[$id]->date = $string;
- $result[$id]->timestamp = rcube_utils::strtotime($string);
- break;
- case 'to':
- $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string);
- break;
- case 'from':
- case 'subject':
- $string = substr($string, 0, 2048);
- case 'cc':
- case 'bcc':
- case 'references':
- $result[$id]->{$field} = $string;
- break;
- case 'reply-to':
- $result[$id]->replyto = $string;
- break;
- case 'content-transfer-encoding':
- $result[$id]->encoding = substr($string, 0, 32);
- break;
- case 'content-type':
- $ctype_parts = preg_split('/[; ]+/', $string);
- $result[$id]->ctype = strtolower(array_shift($ctype_parts));
- if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) {
- $result[$id]->charset = $regs[1];
- }
- break;
- case 'in-reply-to':
- $result[$id]->in_reply_to = str_replace(array("\n", '<', '>'), '', $string);
- break;
- case 'return-receipt-to':
- case 'disposition-notification-to':
- case 'x-confirm-reading-to':
- $result[$id]->mdn_to = substr($string, 0, 2048);
- break;
- case 'message-id':
- $result[$id]->messageID = substr($string, 0, 2048);
- break;
- case 'x-priority':
- if (preg_match('/^(\d+)/', $string, $matches)) {
- $result[$id]->priority = intval($matches[1]);
- }
- break;
- default:
- if (strlen($field) < 3) {
+ case 'date':
+ $string = substr($string, 0, 128);
+ $result[$id]->date = $string;
+ $result[$id]->timestamp = rcube_utils::strtotime($string);
break;
- }
- if ($result[$id]->others[$field]) {
- $string = array_merge((array)$result[$id]->others[$field], (array)$string);
- }
- $result[$id]->others[$field] = $string;
+ case 'to':
+ $result[$id]->to = preg_replace('/undisclosed-recipients:[;,]*/', '', $string);
+ break;
+ case 'from':
+ case 'subject':
+ $string = substr($string, 0, 2048);
+ case 'cc':
+ case 'bcc':
+ case 'references':
+ $result[$id]->{$field} = $string;
+ break;
+ case 'reply-to':
+ $result[$id]->replyto = $string;
+ break;
+ case 'content-transfer-encoding':
+ $result[$id]->encoding = substr($string, 0, 32);
+ break;
+ case 'content-type':
+ $ctype_parts = preg_split('/[; ]+/', $string);
+ $result[$id]->ctype = strtolower(array_first($ctype_parts));
+ if (preg_match('/charset\s*=\s*"?([a-z0-9\-\.\_]+)"?/i', $string, $regs)) {
+ $result[$id]->charset = $regs[1];
+ }
+ break;
+ case 'in-reply-to':
+ $result[$id]->in_reply_to = str_replace(["\n", '<', '>'], '', $string);
+ break;
+ case 'disposition-notification-to':
+ case 'x-confirm-reading-to':
+ $result[$id]->mdn_to = substr($string, 0, 2048);
+ break;
+ case 'message-id':
+ $result[$id]->messageID = substr($string, 0, 2048);
+ break;
+ case 'x-priority':
+ if (preg_match('/^(\d+)/', $string, $matches)) {
+ $result[$id]->priority = intval($matches[1]);
+ }
+ break;
+ default:
+ if (strlen($field) < 3) {
+ break;
+ }
+ if (!empty($result[$id]->others[$field])) {
+ $string = array_merge((array) $result[$id]->others[$field], (array) $string);
+ }
+ $result[$id]->others[$field] = $string;
}
}
}
}
// VANISHED response (QRESYNC RFC5162)
// Sample: * VANISHED (EARLIER) 300:310,405,411
- else if (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) {
+ elseif (preg_match('/^\* VANISHED [()EARLIER]*/i', $line, $match)) {
$line = substr($line, strlen($match[0]));
$v_data = $this->tokenizeResponse($line, 1);
$this->data['VANISHED'] = $v_data;
}
+ } while (!$this->startsWith($line, $key, true));
+
+ if ($this->parseResult($line, 'FETCH: ') != self::ERROR_OK) {
+ return false;
}
- while (!$this->startsWith($line, $key, true));
return $result;
}
@@ -2605,11 +2633,11 @@
*
* @return bool|array List of rcube_message_header elements, False on error
*/
- public function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = array())
+ public function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = [])
{
- $query_items = array('UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE');
- $headers = array('DATE', 'FROM', 'TO', 'SUBJECT', 'CONTENT-TYPE', 'CC', 'REPLY-TO',
- 'LIST-POST', 'DISPOSITION-NOTIFICATION-TO', 'X-PRIORITY');
+ $query_items = ['UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE'];
+ $headers = ['DATE', 'FROM', 'TO', 'SUBJECT', 'CONTENT-TYPE', 'CC', 'REPLY-TO',
+ 'LIST-POST', 'DISPOSITION-NOTIFICATION-TO', 'X-PRIORITY'];
if (!empty($add_headers)) {
$add_headers = array_map('strtoupper', $add_headers);
@@ -2636,11 +2664,12 @@
*
* @return bool|rcube_message_header Message data, False on error
*/
- public function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = array())
+ public function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = [])
{
$a = $this->fetchHeaders($mailbox, $id, $is_uid, $bodystr, $add_headers);
+
if (is_array($a)) {
- return array_shift($a);
+ return array_first($a);
}
return false;
@@ -2651,54 +2680,54 @@
*
* @param array $messages Array of rcube_message_header objects
* @param string $field Name of the property to sort by
- * @param string $flag Sorting order (ASC|DESC)
+ * @param string $order Sorting order (ASC|DESC)
*
* @return array Sorted input array
*/
- public static function sortHeaders($messages, $field, $flag)
+ public static function sortHeaders($messages, $field, $order = 'ASC')
{
$field = empty($field) ? 'uid' : strtolower($field);
- $order = empty($flag) ? 'ASC' : strtoupper($flag);
- $index = array();
+ $order = empty($order) ? 'ASC' : strtoupper($order);
+ $index = [];
reset($messages);
// Create an index
foreach ($messages as $key => $headers) {
switch ($field) {
- case 'arrival':
- $field = 'internaldate';
- // no-break
- case 'date':
- case 'internaldate':
- case 'timestamp':
- $value = rcube_utils::strtotime($headers->$field);
- if (!$value && $field != 'timestamp') {
- $value = $headers->timestamp;
- }
+ case 'arrival':
+ $field = 'internaldate';
+ // no-break
+ case 'date':
+ case 'internaldate':
+ case 'timestamp':
+ $value = rcube_utils::strtotime($headers->{$field});
+ if (!$value && $field != 'timestamp') {
+ $value = $headers->timestamp;
+ }
- break;
+ break;
- default:
- // @TODO: decode header value, convert to UTF-8
- $value = $headers->$field;
- if (is_string($value)) {
- $value = str_replace('"', '', $value);
+ default:
+ // @TODO: decode header value, convert to UTF-8
+ $value = $headers->{$field};
+ if (is_string($value)) {
+ $value = str_replace('"', '', $value);
- if ($field == 'subject') {
- $value = preg_replace('/^(Re:\s*|Fwd:\s*|Fw:\s*)+/i', '', $value);
+ if ($field == 'subject') {
+ $value = rcube_utils::remove_subject_prefix($value);
+ }
}
- }
}
$index[$key] = $value;
}
- $sort_order = $flag == 'ASC' ? SORT_ASC : SORT_DESC;
- $sort_flags = SORT_STRING | SORT_FLAG_CASE;
+ $sort_order = $order == 'ASC' ? \SORT_ASC : \SORT_DESC;
+ $sort_flags = \SORT_STRING | \SORT_FLAG_CASE;
- if (in_array($field, array('arrival', 'date', 'internaldate', 'timestamp'))) {
- $sort_flags = SORT_NUMERIC;
+ if (in_array($field, ['arrival', 'date', 'internaldate', 'timestamp', 'size', 'uid', 'id'])) {
+ $sort_flags = \SORT_NUMERIC;
}
array_multisort($index, $sort_order, $sort_flags, $messages);
@@ -2712,7 +2741,7 @@
* @param string $mailbox Mailbox name
* @param int $uid Message UID
* @param array $parts Message part identifiers
- * @param bool $mime Use MIME instad of HEADER
+ * @param bool $mime Use MIME instead of HEADER
*
* @return array|bool Array containing headers string for each specified body
* False on failure.
@@ -2723,10 +2752,9 @@
return false;
}
- $result = false;
$parts = (array) $parts;
$key = $this->nextTag();
- $peeks = array();
+ $peeks = [];
$type = $mime ? 'MIME' : 'HEADER';
// format request
@@ -2738,23 +2766,23 @@
// send request
if (!$this->putLine($request)) {
- $this->setError(self::ERROR_COMMAND, "Failed to send UID FETCH command");
+ $this->setError(self::ERROR_COMMAND, 'Failed to send UID FETCH command');
return false;
}
+ $result = [];
+
do {
$line = $this->readLine(1024);
-
if (preg_match('/^\* [0-9]+ FETCH [0-9UID( ]+/', $line, $m)) {
$line = ltrim(substr($line, strlen($m[0])));
- while (preg_match('/^BODY\[([0-9\.]+)\.'.$type.'\]/', $line, $matches)) {
+ while (preg_match('/^\s*BODY\[([0-9\.]+)\.' . $type . '\]/', $line, $matches)) {
$line = substr($line, strlen($matches[0]));
$result[$matches[1]] = trim($this->multLine($line));
$line = $this->readLine(1024);
}
}
- }
- while (!$this->startsWith($line, $key, true));
+ } while (!$this->startsWith($line, $key, true));
return $result;
}
@@ -2764,7 +2792,7 @@
*/
public function fetchPartHeader($mailbox, $id, $is_uid = false, $part = null)
{
- $part = empty($part) ? 'HEADER' : $part.'.MIME';
+ $part = empty($part) ? 'HEADER' : $part . '.MIME';
return $this->handlePartBody($mailbox, $id, $is_uid, $part);
}
@@ -2772,35 +2800,37 @@
/**
* Fetches body of the specified message part
*/
- public function handlePartBody($mailbox, $id, $is_uid=false, $part='', $encoding=null, $print=null, $file=null, $formatted=false, $max_bytes=0)
+ public function handlePartBody($mailbox, $id, $is_uid = false, $part = '', $encoding = null, $print = null,
+ $file = null, $formatted = false, $max_bytes = 0)
{
if (!$this->select($mailbox)) {
return false;
}
- $binary = true;
+ $binary = true;
+ $initiated = false;
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;
+ 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 = $formatted ? 4 : 0;
}
// Use BINARY extension when possible (and safe)
- $binary = $binary && $mode && preg_match('/^[0-9.]+$/', $part) && $this->hasCapability('BINARY');
+ $binary = $binary && $mode && preg_match('/^[0-9.]+$/', (string) $part) && $this->hasCapability('BINARY');
$fetch_mode = $binary ? 'BINARY' : 'BODY';
$partial = $max_bytes ? sprintf('<0.%d>', $max_bytes) : '';
@@ -2847,108 +2877,79 @@
if ($line[0] == '(' && substr($line, -1) == ')') {
// tokenize content inside brackets
// the content can be e.g.: (UID 9844 BODY[2.4] NIL)
- $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\)$)/', '', $line));
+ $line = preg_replace('/(^\(|\)$)/', '', $line);
+ $tokens = $this->tokenizeResponse($line);
- for ($i=0; $i<count($tokens); $i+=2) {
+ for ($i = 0; $i < count($tokens); $i += 2) {
if (preg_match('/^(BODY|BINARY)/i', $tokens[$i])) {
- $result = $tokens[$i+1];
+ $result = $tokens[$i + 1];
$found = true;
break;
}
}
+ // Cyrus IMAP does not return a NO-response on error, but we can detect it
+ // and fallback to a non-binary fetch (#9097)
+ if ($binary && !$found) {
+ $binary = $initiated = false;
+ $line = trim($this->readLine(1024)); // the OK response line
+ continue;
+ }
+
if ($result !== false) {
- if ($mode == 1) {
- $result = base64_decode($result);
- }
- else if ($mode == 2) {
- $result = quoted_printable_decode($result);
- }
- else if ($mode == 3) {
- $result = convert_uudecode($result);
- }
+ $result = $this->decodeContent($result, $mode, true);
}
}
// response with string literal
- else if (preg_match('/\{([0-9]+)\}$/', $line, $m)) {
+ elseif (preg_match('/\{([0-9]+)\}$/', $line, $m)) {
$bytes = (int) $m[1];
$prev = '';
$found = true;
+ $chunkSize = 1024 * 1024;
// empty body
if (!$bytes) {
$result = '';
}
- else while ($bytes > 0) {
- $line = $this->readLine(8192);
-
- if ($line === null) {
- break;
- }
+ // An optimal path for a case when we need the body as-is in a string
+ elseif (!$mode && !$file && !$print) {
+ $result = $this->readBytes($bytes);
+ } else {
+ while ($bytes > 0) {
+ $chunk = $this->readBytes($bytes > $chunkSize ? $chunkSize : $bytes);
+
+ if ($chunk === '') {
+ break;
+ }
- $len = strlen($line);
+ $len = strlen($chunk);
- if ($len > $bytes) {
- $line = substr($line, 0, $bytes);
- $len = strlen($line);
- }
- $bytes -= $len;
-
- // BASE64
- if ($mode == 1) {
- $line = preg_replace('|[^a-zA-Z0-9+=/]|', '', $line);
- // 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 = '';
- }
- $line = base64_decode($line);
- }
- // QUOTED-PRINTABLE
- else if ($mode == 2) {
- $line = rtrim($line, "\t\r\0\x0B");
- $line = quoted_printable_decode($line);
- }
- // UUENCODE
- 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 ($len > $bytes) {
+ $chunk = substr($chunk, 0, $bytes);
+ $len = strlen($chunk);
}
- $line = convert_uudecode($line);
- }
- // default
- else if ($formatted) {
- $line = rtrim($line, "\t\r\n\0\x0B") . "\n";
- }
+ $bytes -= $len;
- if ($file) {
- if (fwrite($file, $line) === false) {
- break;
+ $chunk = $this->decodeContent($chunk, $mode, $bytes <= 0, $prev);
+
+ if ($file) {
+ if (fwrite($file, $chunk) === false) {
+ break;
+ }
+ } elseif ($print) {
+ echo $chunk;
+ } else {
+ $result .= $chunk;
}
}
- else if ($print) {
- echo $line;
- }
- else {
- $result .= $line;
- }
}
}
- }
- while (!$this->startsWith($line, $key, true) || !$initiated);
+ } while (!$this->startsWith($line, $key, true) || !$initiated);
if ($result !== false) {
if ($file) {
return fwrite($file, $result);
- }
- else if ($print) {
+ } elseif ($print) {
echo $result;
return true;
}
@@ -2959,6 +2960,104 @@
return false;
}
+ /**
+ * Decodes a chunk of a message part content from a FETCH response.
+ *
+ * @param string $chunk Content
+ * @param int $mode Encoding mode
+ * @param bool $is_last Whether it is a last chunk of data
+ * @param string $prev Extra content from the previous chunk
+ *
+ * @return string Encoded string
+ */
+ protected static function decodeContent($chunk, $mode, $is_last = false, &$prev = '')
+ {
+ // BASE64
+ if ($mode == 1) {
+ $chunk = $prev . preg_replace('|[^a-zA-Z0-9+=/]|', '', $chunk);
+
+ // create chunks with proper length for base64 decoding
+ $length = strlen($chunk);
+
+ if ($length % 4) {
+ $length = floor($length / 4) * 4;
+ $prev = substr($chunk, $length);
+ $chunk = substr($chunk, 0, $length);
+ } else {
+ $prev = '';
+ }
+
+ return base64_decode($chunk);
+ }
+
+ // QUOTED-PRINTABLE
+ if ($mode == 2) {
+ if (!self::decodeContentChunk($chunk, $prev, $is_last)) {
+ return '';
+ }
+
+ $chunk = preg_replace('/[\t\r\0\x0B]+\n/', "\n", $chunk);
+
+ return quoted_printable_decode($chunk);
+ }
+
+ // X-UUENCODE
+ if ($mode == 3) {
+ if (!self::decodeContentChunk($chunk, $prev, $is_last)) {
+ return '';
+ }
+
+ $chunk = preg_replace(
+ ['/\r?\n/', '/(^|\n)end$/', '/^begin\s+[0-7]{3,4}\s+[^\n]+\n/'],
+ ["\n", '', ''],
+ $chunk
+ );
+
+ if (!strlen($chunk)) {
+ return '';
+ }
+
+ return convert_uudecode($chunk);
+ }
+
+ // Plain text formatted
+ // TODO: Formatting should be handled outside of this class
+ if ($mode == 4) {
+ if (!self::decodeContentChunk($chunk, $prev, $is_last)) {
+ return '';
+ }
+
+ if ($is_last) {
+ $chunk = rtrim($chunk, "\t\r\n\0\x0B");
+ }
+
+ return preg_replace('/[\t\r\0\x0B]+\n/', "\n", $chunk);
+ }
+
+ return $chunk;
+ }
+
+ /**
+ * A helper for a new-line aware parsing. See self::decodeContent().
+ */
+ private static function decodeContentChunk(&$chunk, &$prev, $is_last)
+ {
+ $chunk = $prev . $chunk;
+ $prev = '';
+
+ if (!$is_last) {
+ if (($pos = strrpos($chunk, "\n")) !== false) {
+ $prev = substr($chunk, $pos + 1);
+ $chunk = substr($chunk, 0, $pos + 1);
+ } else {
+ $prev = $chunk;
+ return false;
+ }
+ }
+
+ return true;
+ }
+
/**
* Handler for IMAP APPEND command
*
@@ -2970,7 +3069,7 @@
*
* @return string|bool On success APPENDUID response (if available) or True, False on failure
*/
- public function append($mailbox, &$message, $flags = array(), $date = null, $binary = false)
+ public function append($mailbox, &$message, $flags = [], $date = null, $binary = false)
{
unset($this->data['APPENDUID']);
@@ -2979,20 +3078,19 @@
}
$binary = $binary && $this->getCapability('BINARY');
- $literal_plus = !$binary && $this->prefs['literal+'];
+ $literal_plus = !$binary && !empty($this->prefs['literal+']);
$len = 0;
- $msg = is_array($message) ? $message : array(&$message);
+ $msg = is_array($message) ? $message : [&$message];
$chunk_size = 512000;
- for ($i=0, $cnt=count($msg); $i<$cnt; $i++) {
+ for ($i = 0, $cnt = count($msg); $i < $cnt; $i++) {
if (is_resource($msg[$i])) {
$stat = fstat($msg[$i]);
if ($stat === false) {
return false;
}
$len += $stat['size'];
- }
- else {
+ } else {
if (!$binary) {
$msg[$i] = str_replace("\r", '', $msg[$i]);
$msg[$i] = str_replace("\n", "\r\n", $msg[$i]);
@@ -3016,7 +3114,7 @@
// send APPEND command
if (!$this->putLine($request)) {
- $this->setError(self::ERROR_COMMAND, "Failed to send APPEND command");
+ $this->setError(self::ERROR_COMMAND, 'Failed to send APPEND command');
return false;
}
@@ -3064,7 +3162,7 @@
} while (!$this->startsWith($line, $key, true, true));
// Clear internal status cache
- unset($this->data['STATUS:'.$mailbox]);
+ unset($this->data['STATUS:' . $mailbox]);
if ($this->parseResult($line, 'APPEND: ') != self::ERROR_OK) {
return false;
@@ -3089,19 +3187,19 @@
*
* @return string|bool On success APPENDUID response (if available) or True, False on failure
*/
- public function appendFromFile($mailbox, $path, $headers=null, $flags = array(), $date = null, $binary = false)
+ public function appendFromFile($mailbox, $path, $headers = null, $flags = [], $date = null, $binary = false)
{
// open message file
if (file_exists(realpath($path))) {
$fp = fopen($path, 'r');
}
- if (!$fp) {
+ if (empty($fp)) {
$this->setError(self::ERROR_UNKNOWN, "Couldn't open $path for reading");
return false;
}
- $message = array();
+ $message = [];
if ($headers) {
$message[] = trim($headers, "\r\n") . "\r\n\r\n";
}
@@ -3115,7 +3213,7 @@
*
* @param string $mailbox Mailbox name
*
- * @return array Quota information
+ * @return array|false Quota information, False on error
*/
public function getQuota($mailbox = null)
{
@@ -3128,48 +3226,50 @@
// * QUOTA user/sample (STORAGE 654 9765)
// a0001 OK Completed
- list($code, $response) = $this->execute('GETQUOTAROOT', array($this->escape($mailbox)), 0, '/^\* QUOTA /i');
+ [$code, $response] = $this->execute('GETQUOTAROOT', [$this->escape($mailbox)], 0, '/^\* QUOTA /i');
- $result = false;
- $min_free = PHP_INT_MAX;
- $all = array();
+ if ($code != self::ERROR_OK) {
+ return false;
+ }
- if ($code == self::ERROR_OK) {
- foreach (explode("\n", $response) as $line) {
- list(, , $quota_root) = $this->tokenizeResponse($line, 3);
+ $min_free = \PHP_INT_MAX;
+ $result = [];
+ $all = [];
- $quotas = $this->tokenizeResponse($line, 1);
+ foreach (explode("\n", $response) as $line) {
+ $tokens = $this->tokenizeResponse($line, 3);
+ $quota_root = $tokens[2] ?? null;
+ $quotas = $this->tokenizeResponse($line, 1);
- if (empty($quotas)) {
- continue;
- }
+ if (empty($quotas)) {
+ continue;
+ }
- foreach (array_chunk($quotas, 3) as $quota) {
- list($type, $used, $total) = $quota;
- $type = strtolower($type);
+ foreach (array_chunk($quotas, 3) as $quota) {
+ [$type, $used, $total] = $quota;
+ $type = strtolower($type);
- if ($type && $total) {
- $all[$quota_root][$type]['used'] = intval($used);
- $all[$quota_root][$type]['total'] = intval($total);
- }
+ if ($type && $total) {
+ $all[$quota_root][$type]['used'] = intval($used);
+ $all[$quota_root][$type]['total'] = intval($total);
}
+ }
- if (empty($all[$quota_root]['storage'])) {
- continue;
- }
+ 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 = $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'];
}
}
@@ -3186,11 +3286,11 @@
* @param string $root Quota root
* @param array $quota Quota limits e.g. ['storage' => 1024000']
*
- * @return boolean True on success, False on failure
+ * @return bool True on success, False on failure
*/
public function setQuota($root, $quota)
{
- $fn = function ($key, $value) {
+ $fn = static function ($key, $value) {
return strtoupper($key) . ' ' . $value;
};
@@ -3199,7 +3299,7 @@
$result = $this->execute('SETQUOTA', [$this->escape($root), "({$quota})"],
self::COMMAND_NORESPONSE);
- return ($result == self::ERROR_OK);
+ return $result == self::ERROR_OK;
}
/**
@@ -3209,7 +3309,7 @@
* @param string $user User name
* @param mixed $acl ACL string or array
*
- * @return boolean True on success, False on failure
+ * @return bool True on success, False on failure
*
* @since 0.5-beta
*/
@@ -3219,11 +3319,12 @@
$acl = implode('', $acl);
}
- $result = $this->execute('SETACL', array(
- $this->escape($mailbox), $this->escape($user), strtolower($acl)),
- self::COMMAND_NORESPONSE);
+ $result = $this->execute('SETACL',
+ [$this->escape($mailbox), $this->escape($user), strtolower($acl)],
+ self::COMMAND_NORESPONSE
+ );
- return ($result == self::ERROR_OK);
+ return $result == self::ERROR_OK;
}
/**
@@ -3232,17 +3333,18 @@
* @param string $mailbox Mailbox name
* @param string $user User name
*
- * @return boolean True on success, False on failure
+ * @return bool True on success, False on failure
*
* @since 0.5-beta
*/
public function deleteACL($mailbox, $user)
{
- $result = $this->execute('DELETEACL', array(
- $this->escape($mailbox), $this->escape($user)),
- self::COMMAND_NORESPONSE);
+ $result = $this->execute('DELETEACL',
+ [$this->escape($mailbox), $this->escape($user)],
+ self::COMMAND_NORESPONSE
+ );
- return ($result == self::ERROR_OK);
+ return $result == self::ERROR_OK;
}
/**
@@ -3251,11 +3353,12 @@
* @param string $mailbox Mailbox name
*
* @return array User-rights array on success, NULL on error
+ *
* @since 0.5-beta
*/
public function getACL($mailbox)
{
- list($code, $response) = $this->execute('GETACL', array($this->escape($mailbox)), 0, '/^\* ACL /i');
+ [$code, $response] = $this->execute('GETACL', [$this->escape($mailbox)], 0, '/^\* ACL /i');
if ($code == self::ERROR_OK && $response) {
// Parse server response (remove "* ACL ")
@@ -3269,15 +3372,15 @@
// so we could return only standard rights defined in RFC4314,
// excluding 'c' and 'd' defined in RFC2086.
if ($size % 2 == 0) {
- for ($i=0; $i<$size; $i++) {
+ for ($i = 0; $i < $size; $i++) {
$ret[$ret[$i]] = str_split($ret[++$i]);
- unset($ret[$i-1]);
+ unset($ret[$i - 1]);
unset($ret[$i]);
}
return $ret;
}
- $this->setError(self::ERROR_COMMAND, "Incomplete ACL response");
+ $this->setError(self::ERROR_COMMAND, 'Incomplete ACL response');
}
}
@@ -3288,12 +3391,13 @@
* @param string $user User name
*
* @return array List of user rights
+ *
* @since 0.5-beta
*/
public function listRights($mailbox, $user)
{
- list($code, $response) = $this->execute('LISTRIGHTS',
- array($this->escape($mailbox), $this->escape($user)), 0, '/^\* LISTRIGHTS /i');
+ [$code, $response] = $this->execute('LISTRIGHTS',
+ [$this->escape($mailbox), $this->escape($user)], 0, '/^\* LISTRIGHTS /i');
if ($code == self::ERROR_OK && $response) {
// Parse server response (remove "* LISTRIGHTS ")
@@ -3304,10 +3408,10 @@
$granted = $this->tokenizeResponse($response, 1);
$optional = trim($response);
- return array(
+ return [
'granted' => str_split($granted),
'optional' => explode(' ', $optional),
- );
+ ];
}
}
@@ -3317,11 +3421,12 @@
* @param string $mailbox Mailbox name
*
* @return array MYRIGHTS response on success, NULL on error
+ *
* @since 0.5-beta
*/
public function myRights($mailbox)
{
- list($code, $response) = $this->execute('MYRIGHTS', array($this->escape($mailbox)), 0, '/^\* MYRIGHTS /i');
+ [$code, $response] = $this->execute('MYRIGHTS', [$this->escape($mailbox)], 0, '/^\* MYRIGHTS /i');
if ($code == self::ERROR_OK && $response) {
// Parse server response (remove "* MYRIGHTS ")
@@ -3340,13 +3445,14 @@
* @param string $mailbox Mailbox name
* @param array $entries Entry-value array (use NULL value as NIL)
*
- * @return boolean True on success, False on failure
+ * @return bool True on success, False on failure
+ *
* @since 0.5-beta
*/
public function setMetadata($mailbox, $entries)
{
if (!is_array($entries) || empty($entries)) {
- $this->setError(self::ERROR_COMMAND, "Wrong argument for SETMETADATA command");
+ $this->setError(self::ERROR_COMMAND, 'Wrong argument for SETMETADATA command');
return false;
}
@@ -3355,11 +3461,12 @@
}
$entries = implode(' ', $entries);
- $result = $this->execute('SETMETADATA', array(
- $this->escape($mailbox), '(' . $entries . ')'),
- self::COMMAND_NORESPONSE);
+ $result = $this->execute('SETMETADATA',
+ [$this->escape($mailbox), '(' . $entries . ')'],
+ self::COMMAND_NORESPONSE
+ );
- return ($result == self::ERROR_OK);
+ return $result == self::ERROR_OK;
}
/**
@@ -3368,7 +3475,7 @@
* @param string $mailbox Mailbox name
* @param array $entries Entry names array
*
- * @return boolean True on success, False on failure
+ * @return bool True on success, False on failure
*
* @since 0.5-beta
*/
@@ -3379,10 +3486,11 @@
}
if (empty($entries)) {
- $this->setError(self::ERROR_COMMAND, "Wrong argument for SETMETADATA command");
+ $this->setError(self::ERROR_COMMAND, 'Wrong argument for SETMETADATA command');
return false;
}
+ $data = [];
foreach ($entries as $entry) {
$data[$entry] = null;
}
@@ -3401,44 +3509,39 @@
*
* @since 0.5-beta
*/
- public function getMetadata($mailbox, $entries, $options=array())
+ public function getMetadata($mailbox, $entries, $options = [])
{
if (!is_array($entries)) {
- $entries = array($entries);
+ $entries = [$entries];
}
- // create entries string
- foreach ($entries as $idx => $name) {
- $entries[$idx] = $this->escape($name);
- }
-
- $optlist = '';
- $entlist = '(' . implode(' ', $entries) . ')';
+ $args = [];
// create options string
if (is_array($options)) {
- $options = array_change_key_case($options, CASE_UPPER);
- $opts = array();
+ $options = array_change_key_case($options, \CASE_UPPER);
+ $opts = [];
if (!empty($options['MAXSIZE'])) {
- $opts[] = 'MAXSIZE '.intval($options['MAXSIZE']);
+ $opts[] = 'MAXSIZE ' . intval($options['MAXSIZE']);
}
- if (!empty($options['DEPTH'])) {
- $opts[] = 'DEPTH '.intval($options['DEPTH']);
+
+ if (isset($options['DEPTH'])) {
+ $opts[] = 'DEPTH ' . $this->escape($options['DEPTH']);
}
- if ($opts) {
- $optlist = '(' . implode(' ', $opts) . ')';
+ if (!empty($opts)) {
+ $args[] = $opts;
}
}
- $optlist .= ($optlist ? ' ' : '') . $entlist;
+ $args[] = $this->escape($mailbox);
+ $args[] = array_map([$this, 'escape'], $entries);
- list($code, $response) = $this->execute('GETMETADATA', array(
- $this->escape($mailbox), $optlist));
+ [$code, $response] = $this->execute('GETMETADATA', $args);
if ($code == self::ERROR_OK) {
- $result = array();
+ $result = [];
$data = $this->tokenizeResponse($response);
// The METADATA response can contain multiple entries in a single
@@ -3450,8 +3553,8 @@
&& is_array($data[++$i])
) {
for ($x = 0, $size2 = count($data[$i]); $x < $size2; $x += 2) {
- if ($data[$i][$x+1] !== null) {
- $result[$mbox][$data[$i][$x]] = $data[$i][$x+1];
+ if ($data[$i][$x + 1] !== null) {
+ $result[$mbox][$data[$i][$x]] = $data[$i][$x + 1];
}
}
}
@@ -3468,13 +3571,14 @@
* @param array $data Data array where each item is an array with
* three elements: entry name, attribute name, value
*
- * @return boolean True on success, False on failure
+ * @return bool True on success, False on failure
+ *
* @since 0.5-beta
*/
public function setAnnotation($mailbox, $data)
{
if (!is_array($data) || empty($data)) {
- $this->setError(self::ERROR_COMMAND, "Wrong argument for SETANNOTATION command");
+ $this->setError(self::ERROR_COMMAND, 'Wrong argument for SETANNOTATION command');
return false;
}
@@ -3485,10 +3589,9 @@
}
$entries = implode(' ', $entries);
- $result = $this->execute('SETANNOTATION', array(
- $this->escape($mailbox), $entries), self::COMMAND_NORESPONSE);
+ $result = $this->execute('SETANNOTATION', [$this->escape($mailbox), $entries], self::COMMAND_NORESPONSE);
- return ($result == self::ERROR_OK);
+ return $result == self::ERROR_OK;
}
/**
@@ -3498,14 +3601,14 @@
* @param array $data Data array where each item is an array with
* two elements: entry name and attribute name
*
- * @return boolean True on success, False on failure
+ * @return bool True on success, False on failure
*
* @since 0.5-beta
*/
public function deleteAnnotation($mailbox, $data)
{
if (!is_array($data) || empty($data)) {
- $this->setError(self::ERROR_COMMAND, "Wrong argument for SETANNOTATION command");
+ $this->setError(self::ERROR_COMMAND, 'Wrong argument for SETANNOTATION command');
return false;
}
@@ -3526,7 +3629,7 @@
public function getAnnotation($mailbox, $entries, $attribs)
{
if (!is_array($entries)) {
- $entries = array($entries);
+ $entries = [$entries];
}
// create entries string
@@ -3537,7 +3640,7 @@
$entries = '(' . implode(' ', $entries) . ')';
if (!is_array($attribs)) {
- $attribs = array($attribs);
+ $attribs = [$attribs];
}
// create attributes string
@@ -3546,24 +3649,23 @@
}
$attribs = '(' . implode(' ', $attribs) . ')';
- list($code, $response) = $this->execute('GETANNOTATION', array(
- $this->escape($mailbox), $entries, $attribs));
+ [$code, $response] = $this->execute('GETANNOTATION', [$this->escape($mailbox), $entries, $attribs]);
if ($code == self::ERROR_OK) {
- $result = array();
- $data = $this->tokenizeResponse($response);
+ $result = [];
+ $data = $this->tokenizeResponse($response);
+ $last_entry = null;
// Here we returns only data compatible with METADATA result format
if (!empty($data) && ($size = count($data))) {
- for ($i=0; $i<$size; $i++) {
+ for ($i = 0; $i < $size; $i++) {
$entry = $data[$i];
if (isset($mbox) && is_array($entry)) {
$attribs = $entry;
$entry = $last_entry;
- }
- else if ($entry == '*') {
- if ($data[$i+1] == 'ANNOTATION') {
- $mbox = $data[$i+2];
+ } elseif ($entry == '*') {
+ if ($data[$i + 1] == 'ANNOTATION') {
+ $mbox = $data[$i + 2];
unset($data[$i]); // "*"
unset($data[++$i]); // "ANNOTATION"
unset($data[++$i]); // Mailbox
@@ -3574,27 +3676,25 @@
unset($data[$i]);
}
continue;
- }
- else if (isset($mbox)) {
+ } elseif (isset($mbox)) {
$attribs = $data[++$i];
- }
- else {
+ } else {
unset($data[$i]);
continue;
}
if (!empty($attribs)) {
- for ($x=0, $len=count($attribs); $x<$len;) {
+ for ($x = 0, $len = count($attribs); $x < $len;) {
$attr = $attribs[$x++];
$value = $attribs[$x++];
if ($attr == 'value.priv' && $value !== null) {
$result[$mbox]['/private' . $entry] = $value;
- }
- else if ($attr == 'value.shared' && $value !== null) {
+ } elseif ($attr == 'value.shared' && $value !== null) {
$result[$mbox]['/shared' . $entry] = $value;
}
}
}
+
$last_entry = $entry;
unset($data[$i]);
}
@@ -3611,15 +3711,16 @@
* @param int $id Message sequence number or UID
* @param bool $is_uid True if $id is an UID
*
- * @return array/bool Body structure array or False on error.
+ * @return array|bool Body structure array or False on error.
+ *
* @since 0.6
*/
public function getStructure($mailbox, $id, $is_uid = false)
{
- $result = $this->fetch($mailbox, $id, $is_uid, array('BODYSTRUCTURE'));
+ $result = $this->fetch($mailbox, $id, $is_uid, ['BODYSTRUCTURE']);
- if (is_array($result)) {
- $result = array_shift($result);
+ if (is_array($result) && !empty($result)) {
+ $result = array_first($result);
return $result->bodystructure;
}
@@ -3637,7 +3738,7 @@
public static function getStructurePartData($structure, $part)
{
$part_a = self::getStructurePartArray($structure, $part);
- $data = array();
+ $data = [];
if (empty($part_a)) {
return $data;
@@ -3646,17 +3747,16 @@
// content-type
if (is_array($part_a[0])) {
$data['type'] = 'multipart';
- }
- else {
+ } else {
$data['type'] = strtolower($part_a[0]);
$data['subtype'] = strtolower($part_a[1]);
$data['encoding'] = strtolower($part_a[5]);
// charset
if (is_array($part_a[2])) {
- foreach ($part_a[2] as $key => $val) {
+ foreach ($part_a[2] as $key => $val) {
if (strcasecmp($val, 'charset') == 0) {
- $data['charset'] = $part_a[2][$key+1];
+ $data['charset'] = $part_a[2][$key + 1];
break;
}
}
@@ -3688,13 +3788,12 @@
if (strpos($part, '.') > 0) {
$orig_part = $part;
$pos = strpos($part, '.');
- $rest = substr($orig_part, $pos+1);
+ $rest = substr($orig_part, $pos + 1);
$part = substr($orig_part, 0, $pos);
- return self::getStructurePartArray($a[$part-1], $rest);
- }
- else if ($part > 0) {
- return (is_array($a[$part-1])) ? $a[$part-1] : $a;
+ return self::getStructurePartArray($a[$part - 1], $rest);
+ } elseif ($part > 0) {
+ return is_array($a[$part - 1]) ? $a[$part - 1] : $a;
}
}
@@ -3702,6 +3801,7 @@
* Creates next command identifier (tag)
*
* @return string Command identifier
+ *
* @since 0.5-beta
*/
public function nextTag()
@@ -3721,9 +3821,10 @@
* @param string $filter Line filter (regexp)
*
* @return mixed Response code or list of response code and data
+ *
* @since 0.5-beta
*/
- public function execute($command, $arguments = array(), $options = 0, $filter = null)
+ public function execute($command, $arguments = [], $options = 0, $filter = null)
{
$tag = $this->nextTag();
$query = $tag . ' ' . $command;
@@ -3737,20 +3838,19 @@
}
// Send command
- if (!$this->putLineC($query, true, ($options & self::COMMAND_ANONYMIZED))) {
+ if (!$this->putLineC($query, true, $options & self::COMMAND_ANONYMIZED)) {
preg_match('/^[A-Z0-9]+ ((UID )?[A-Z]+)/', $query, $matches);
$cmd = $matches[1] ?: 'UNKNOWN';
$this->setError(self::ERROR_COMMAND, "Failed to send $cmd command");
- return $noresp ? self::ERROR_COMMAND : array(self::ERROR_COMMAND, '');
+ return $noresp ? self::ERROR_COMMAND : [self::ERROR_COMMAND, ''];
}
// Parse response
do {
- $line = $this->readLine(4096);
+ $line = $this->readFullLine(4096);
if ($response !== null) {
- // TODO: Better string literals handling with filter
if (!$filter || preg_match($filter, $line)) {
$response .= $line;
}
@@ -3758,12 +3858,11 @@
// parse untagged response for [COPYUID 1204196876 3456:3457 123:124] (RFC6851)
if ($line && $command == 'UID MOVE') {
- if (preg_match("/^\* OK \[COPYUID [0-9]+ ([0-9,:]+) ([0-9,:]+)\]/i", $line, $m)) {
- $this->data['COPYUID'] = array($m[1], $m[2]);
+ if (preg_match('/^\\* OK \\[COPYUID [0-9]+ ([0-9,:]+) ([0-9,:]+)\\]/i', $line, $m)) {
+ $this->data['COPYUID'] = [$m[1], $m[2]];
}
}
- }
- while (!$this->startsWith($line, $tag . ' ', true, true));
+ } while (!$this->startsWith($line, $tag . ' ', true, true));
$code = $this->parseResult($line, $command . ': ');
@@ -3786,10 +3885,10 @@
// return last line only (without command tag, result and response code)
if ($line && ($options & self::COMMAND_LASTLINE)) {
- $response = preg_replace("/^$tag (OK|NO|BAD|BYE|PREAUTH)?\s*(\[[a-z-]+\])?\s*/i", '', trim($line));
+ $response = preg_replace("/^$tag (OK|NO|BAD|BYE|PREAUTH)?\\s*(\\[[a-z-]+\\])?\\s*/i", '', trim($line));
}
- return $noresp ? $code : array($code, $response);
+ return $noresp ? $code : [$code, $response];
}
/**
@@ -3799,11 +3898,12 @@
* @param int $num Number of tokens to return
*
* @return mixed Tokens array or string if $num=1
+ *
* @since 0.5-beta
*/
- public static function tokenizeResponse(&$str, $num=0)
+ public static function tokenizeResponse(&$str, $num = 0)
{
- $result = array();
+ $result = [];
while (!$num || count($result) < $num) {
// remove spaces from the beginning of the string
@@ -3816,62 +3916,63 @@
switch ($str[0]) {
- // String literal
- case '{':
- if (($epos = strpos($str, "}\r\n", 1)) == false) {
- // error
- }
- if (!is_numeric(($bytes = substr($str, 1, $epos - 1)))) {
- // error
- }
+ // String literal
+ case '{':
+ if (($epos = strpos($str, "}\r\n", 1)) == false) {
+ // error
+ }
+ if (!is_numeric($bytes = substr($str, 1, $epos - 1))) {
+ // error
+ }
- $result[] = $bytes ? substr($str, $epos + 3, $bytes) : '';
- $str = substr($str, $epos + 3 + $bytes);
- break;
+ $result[] = $bytes ? substr($str, $epos + 3, $bytes) : '';
+ $str = substr($str, $epos + 3 + $bytes);
+ break;
- // Quoted string
- case '"':
- $len = strlen($str);
+ // Quoted string (<< reindent once https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/7179 is fixed)
+ case '"':
+ $len = strlen($str);
- for ($pos=1; $pos<$len; $pos++) {
- if ($str[$pos] == '"') {
- break;
- }
- if ($str[$pos] == "\\") {
- if ($str[$pos + 1] == '"' || $str[$pos + 1] == "\\") {
- $pos++;
+ for ($pos = 1; $pos < $len; $pos++) {
+ if ($str[$pos] == '"') {
+ break;
+ }
+ if ($str[$pos] == '\\') {
+ if ($str[$pos + 1] == '"' || $str[$pos + 1] == '\\') {
+ $pos++;
+ }
}
}
- }
- // we need to strip slashes for a quoted string
- $result[] = stripslashes(substr($str, 1, $pos - 1));
- $str = substr($str, $pos + 1);
- break;
+ // we need to strip slashes for a quoted string
+ $result[] = stripslashes(substr($str, 1, $pos - 1));
+ $str = substr($str, $pos + 1);
+ break;
- // Parenthesized list
- case '(':
- $str = substr($str, 1);
- $result[] = self::tokenizeResponse($str);
- break;
+ // Parenthesized list (<< reindent once https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/7179 is fixed)
+ case '(':
+ $str = substr($str, 1);
+ $result[] = self::tokenizeResponse($str);
+ break;
- case ')':
- $str = substr($str, 1);
- return $result;
+ case ')':
+ $str = substr($str, 1);
+ return $result;
- // String atom, number, astring, NIL, *, %
- default:
- // excluded chars: SP, CTL, ), DEL
- // we do not exclude [ and ] (#1489223)
- if (preg_match('/^([^\x00-\x20\x29\x7F]+)/', $str, $m)) {
- $result[] = $m[1] == 'NIL' ? null : $m[1];
- $str = substr($str, strlen($m[1]));
- }
- break;
+ // String atom, number, astring, NIL, *, % (<< reindent once https://github.com/PHP-CS-Fixer/PHP-CS-Fixer/issues/7179 is fixed)
+ default:
+ // excluded chars: SP, CTL, ), DEL
+ // we do not exclude [ and ] (#1489223)
+ if (preg_match('/^([^\x00-\x20\x29\x7F]+)/', $str, $m)) {
+ $result[] = $m[1] == 'NIL' ? null : $m[1];
+ $str = substr($str, strlen($m[1]));
+ }
+
+ break;
}
}
- return $num == 1 ? $result[0] : $result;
+ return $num == 1 ? ($result[0] ?? '') : $result;
}
/**
@@ -3879,16 +3980,16 @@
*/
protected static function r_implode($element)
{
+ if (!is_array($element)) {
+ return $element;
+ }
+
+ reset($element);
+
$string = '';
- if (is_array($element)) {
- reset($element);
- foreach ($element as $value) {
- $string .= ' ' . self::r_implode($value);
- }
- }
- else {
- return $element;
+ foreach ($element as $value) {
+ $string .= ' ' . self::r_implode($value);
}
return '(' . trim($string) . ')';
@@ -3902,7 +4003,7 @@
*
* @return string Compressed sequence-set
*/
- public static function compressMessageSet($messages, $force=false)
+ public static function compressMessageSet($messages, $force = false)
{
// given a comma delimited list of independent mid's,
// compresses by grouping sequences together
@@ -3923,7 +4024,7 @@
sort($messages);
- $result = array();
+ $result = [];
$start = $prev = $messages[0];
foreach ($messages as $id) {
@@ -3931,8 +4032,7 @@
if ($incr > 1) { // found a gap
if ($start == $prev) {
$result[] = $prev; // push single id
- }
- else {
+ } else {
$result[] = $start . ':' . $prev; // push sequence as start_id:end_id
}
$start = $id; // start of new sequence
@@ -3943,9 +4043,8 @@
// handle the last sequence/id
if ($start == $prev) {
$result[] = $prev;
- }
- else {
- $result[] = $start.':'.$prev;
+ } else {
+ $result[] = $start . ':' . $prev;
}
// return as comma separated string
@@ -3964,19 +4063,25 @@
public static function uncompressMessageSet($messages)
{
if (empty($messages)) {
- return array();
+ return [];
}
- $result = array();
+ $result = [];
$messages = explode(',', $messages);
foreach ($messages as $idx => $part) {
$items = explode(':', $part);
- $max = max($items[0], $items[1]);
- for ($x=$items[0]; $x<=$max; $x++) {
- $result[] = (int)$x;
+ if (!empty($items[1]) && $items[1] > $items[0]) {
+ $max = $items[1];
+ } else {
+ $max = $items[0];
+ }
+
+ for ($x = $items[0]; $x <= $max; $x++) {
+ $result[] = (int) $x;
}
+
unset($messages[$idx]);
}
@@ -3990,7 +4095,7 @@
{
unset($this->data['STATUS:' . $mailbox]);
- $keys = array('EXISTS', 'RECENT', 'UNSEEN', 'UID-MAP');
+ $keys = ['EXISTS', 'RECENT', 'UNSEEN', 'UID-MAP'];
foreach ($keys as $key) {
unset($this->data[$key]);
@@ -4004,8 +4109,8 @@
{
$this->clear_status_cache($this->selected);
- $keys = array('UIDNEXT', 'UIDVALIDITY', 'HIGHESTMODSEQ', 'NOMODSEQ',
- 'PERMANENTFLAGS', 'QRESYNC', 'VANISHED', 'READ-WRITE');
+ $keys = ['UIDNEXT', 'UIDVALIDITY', 'HIGHESTMODSEQ', 'NOMODSEQ',
+ 'PERMANENTFLAGS', 'QRESYNC', 'VANISHED', 'READ-WRITE'];
foreach ($keys as $key) {
unset($this->data[$key]);
@@ -4021,19 +4126,19 @@
*/
protected function flagsToStr($flags)
{
- foreach ((array)$flags as $idx => $flag) {
+ foreach ((array) $flags as $idx => $flag) {
if ($flag = $this->flags[strtoupper($flag)]) {
$flags[$idx] = $flag;
}
}
- return implode(' ', (array)$flags);
+ return implode(' ', (array) $flags);
}
/**
* CAPABILITY response parser
*/
- protected function parseCapability($str, $trusted=false)
+ protected function parseCapability($str, $trusted = false)
{
$str = preg_replace('/^\* CAPABILITY /i', '', $str);
@@ -4045,23 +4150,26 @@
if (!isset($this->prefs['literal+']) && in_array('LITERAL+', $this->capability)) {
$this->prefs['literal+'] = true;
+ } elseif (!isset($this->prefs['literal-']) && in_array('LITERAL-', $this->capability)) {
+ $this->prefs['literal-'] = true;
}
if ($trusted) {
- $this->capability_readed = true;
+ $this->capability_read = true;
}
}
/**
* Escapes a string when it contains special characters (RFC3501)
*
- * @param string $string IMAP string
- * @param boolean $force_quotes Forces string quoting (for atoms)
+ * @param string $string IMAP string
+ * @param bool $force_quotes Forces string quoting (for atoms)
*
* @return string String atom, quoted-string or string literal
+ *
* @todo lists
*/
- public static function escape($string, $force_quotes=false)
+ public static function escape($string, $force_quotes = false)
{
if ($string === null) {
return 'NIL';
@@ -4088,8 +4196,8 @@
/**
* Set the value of the debugging flag.
*
- * @param bool $debug New value for the debugging flag.
- * @param ?callable $handler Logging handler function
+ * @param bool $debug New value for the debugging flag.
+ * @param callable $handler Logging handler function
*
* @since 0.5-stable
*/
@@ -4119,9 +4227,8 @@
}
if ($this->debug_handler) {
- call_user_func_array($this->debug_handler, array($this, $message));
- }
- else {
+ call_user_func_array($this->debug_handler, [$this, $message]);
+ } else {
echo "DEBUG: $message\n";
}
}
diff --git a/src/tests/Infrastructure/IMAPTest.php b/src/tests/Infrastructure/IMAPTest.php
--- a/src/tests/Infrastructure/IMAPTest.php
+++ b/src/tests/Infrastructure/IMAPTest.php
@@ -49,7 +49,6 @@
$email = self::$user->email;
$mailbox = "user/{$email}/test";
- // $this->assertTrue($imap->createFolder($mailbox));
$imap->createFolder($mailbox);
$this->assertEquals(1, count($imap->listMailboxes('', $mailbox)));
$imap->setACL($mailbox, 'cyrus-admin', 'c');
@@ -58,13 +57,11 @@
'/private/vendor/kolab/folder-type' => 'event'
]));
-
$metadata = $imap->getMetadata($mailbox, [
'/shared/vendor/kolab/folder-type',
'/private/vendor/kolab/folder-type'
]);
- print_r($metadata);
$this->assertEquals('event', $metadata[$mailbox]['/shared/vendor/kolab/folder-type']);
$this->assertEquals('event', $metadata[$mailbox]['/private/vendor/kolab/folder-type']);
@@ -72,6 +69,26 @@
$this->assertEquals(0, count($imap->listMailboxes('', $mailbox)));
}
+ /**
+ * Test for a Cyrus proxy bug regarding VANISHED modifier
+ * that is required by Kolab cache in webmail libkolab plugin
+ */
+ public function testFetchVanished()
+ {
+ $imap = $this->getImap();
+
+ $imap->createFolder('Test');
+
+ $this->assertTrue($imap->select('Test'));
+ $this->assertSame(['CONDSTORE', 'QRESYNC'], $imap->enable('QRESYNC'));
+
+ $result = $imap->fetch('Test', '1:*', true, ['FLAGS'], $imap->data['HIGHESTMODSEQ'], true);
+
+ // TODO: We check the command failure only, we would have to put some
+ // messages in the folder to test the command response is correct
+ $this->assertSame([], $result);
+ }
+
/**
* @doesNotPerformAssertions
*/
diff --git a/src/tests/TestCaseTrait.php b/src/tests/TestCaseTrait.php
--- a/src/tests/TestCaseTrait.php
+++ b/src/tests/TestCaseTrait.php
@@ -575,6 +575,7 @@
if (!$createInBackends) {
Queue::fake();
}
+
$user = User::firstOrCreate(['email' => $email], $attrib);
if ($user->trashed()) {

File Metadata

Mime Type
text/plain
Expires
Mon, Apr 6, 9:08 AM (15 h, 35 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
18833692
Default Alt Text
D4637.1775466488.diff (145 KB)

Event Timeline