Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F117931706
D4637.1775466488.diff
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Authored By
Unknown
Size
145 KB
Referenced Files
None
Subscribers
None
D4637.1775466488.diff
View Options
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
Details
Attached
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)
Attached To
Mode
D4637: Add test for Cyrus imap proxy bug
Attached
Detach File
Event Timeline