MDL-84179 tool_messageinbound: Upgrade Roundcube to version 1.6.10

This commit is contained in:
meirzamoodle
2025-03-04 13:11:32 +07:00
parent 688478cfa4
commit 6cdbfa8b11
8 changed files with 265 additions and 77 deletions
@@ -554,7 +554,7 @@ class rcube_charset
}
// No match, check for UTF-8
// from http://w3.org/International/questions/qa-forms-utf-8.html
// from https://w3.org/International/questions/qa-forms-utf-8.html
if (preg_match('/\A(
[\x09\x0A\x0D\x20-\x7E]
| [\xC2-\xDF][\x80-\xBF]
@@ -676,7 +676,7 @@ class rcube_imap_generic
$gssapicontext->acquireCredentials($ccache);
$token = '';
$success = $gssapicontext->initSecContext($this->prefs['gssapi_context'], null, null, null, $token);
$success = $gssapicontext->initSecContext($this->prefs['gssapi_context'], '', 0, 0, $token);
$token = base64_encode($token);
}
catch (Exception $e) {
@@ -1054,8 +1054,8 @@ class rcube_imap_generic
}
if (!empty($this->prefs['socket_options'])) {
$options = array_intersect_key($this->prefs['socket_options'], ['ssl' => 1]);
$context = stream_context_create($options);
$options = array_intersect_key($this->prefs['socket_options'], ['ssl' => 1, 'socket' => 1]);
$context = stream_context_create($options);
$this->fp = stream_socket_client($host . ':' . $port, $errno, $errstr,
$this->prefs['timeout'], STREAM_CLIENT_CONNECT, $context);
}
@@ -2520,6 +2520,7 @@ class rcube_imap_generic
$result[$id]->id = $id;
$result[$id]->subject = '';
$result[$id]->messageID = 'mid:' . $id;
$result[$id]->folder = $mailbox;
$headers = null;
$line = substr($line, strlen($m[0]) + 2);
@@ -2551,6 +2552,21 @@ class rcube_imap_generic
}
}
}
else if ($name == 'ANNOTATION') {
$result[$id]->annotations = [];
if (!empty($value) && is_array($value)) {
$n = 0;
while (!empty($value[$n]) && is_string($value[$n])) {
$name = $value[$n++];
$list = $value[$n++];
$result[$id]->annotations[$name] = [];
$c = 0;
while (!empty($list[$c]) && is_string($list[$c])) {
$result[$id]->annotations[$name][$list[$c++]] = $list[$c++];
}
}
}
}
else if ($name == 'MODSEQ') {
$result[$id]->modseq = $value[0];
}
@@ -2686,14 +2702,15 @@ class rcube_imap_generic
* @param mixed $message_set Message(s) sequence identifier(s) or UID(s)
* @param bool $is_uid True if $message_set contains UIDs
* @param bool $bodystr Enable to add BODYSTRUCTURE data to the result
* @param array $add_headers List of additional headers
* @param array $add_headers List of additional headers to fetch
* @param array $query_items List of additional items to fetch
*
* @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 = [])
public function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = [], $query_items = [])
{
$query_items = ['UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE'];
$headers = ['DATE', 'FROM', 'TO', 'SUBJECT', 'CONTENT-TYPE', 'CC', 'REPLY-TO',
$query_items = array_unique(array_merge($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)) {
@@ -2718,12 +2735,13 @@ class rcube_imap_generic
* @param bool $is_uid True if $id is an UID
* @param bool $bodystr Enable to add BODYSTRUCTURE data to the result
* @param array $add_headers List of additional headers
* @param array $query_items List of additional items to fetch
*
* @return bool|rcube_message_header Message data, False on error
*/
public function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = [])
public function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = [], $query_items = [])
{
$a = $this->fetchHeaders($mailbox, $id, $is_uid, $bodystr, $add_headers);
$a = $this->fetchHeaders($mailbox, $id, $is_uid, $bodystr, $add_headers, $query_items);
if (is_array($a)) {
return array_shift($a);
@@ -2955,7 +2973,7 @@ class rcube_imap_generic
}
if ($result !== false) {
$result = $this->decodeContent($result, $mode, true);
$result = $this->decodeContent($result, $mode, true, $prev, $formatted);
}
}
// response with string literal
@@ -2988,7 +3006,7 @@ class rcube_imap_generic
}
$bytes -= $len;
$chunk = $this->decodeContent($chunk, $mode, $bytes <= 0, $prev);
$chunk = $this->decodeContent($chunk, $mode, $bytes <= 0, $prev, $formatted);
if ($file) {
if (fwrite($file, $chunk) === false) {
@@ -3024,14 +3042,15 @@ class rcube_imap_generic
/**
* 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
* @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
* @param bool $formatted Format the content for output
*
* @return string Encoded string
*/
protected static function decodeContent($chunk, $mode, $is_last = false, &$prev = '')
protected static function decodeContent($chunk, $mode, $is_last = false, &$prev = '', $formatted = false)
{
// BASE64
if ($mode == 1) {
@@ -3049,22 +3068,25 @@ class rcube_imap_generic
$prev = '';
}
return base64_decode($chunk);
}
// There might be multiple base64 blocks in a single message part,
// we have to pass them separately to base64_decode() (#9290)
$result = '';
foreach (preg_split('|=+|', $chunk, -1, \PREG_SPLIT_NO_EMPTY) as $_chunk) {
$result .= base64_decode($_chunk);
}
$chunk = $result;
}
// QUOTED-PRINTABLE
if ($mode == 2) {
elseif ($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);
$chunk = quoted_printable_decode($chunk);
}
// X-UUENCODE
if ($mode == 3) {
elseif ($mode == 3) {
if (!self::decodeContentChunk($chunk, $prev, $is_last)) {
return '';
}
@@ -3079,12 +3101,11 @@ class rcube_imap_generic
return '';
}
return convert_uudecode($chunk);
$chunk = convert_uudecode($chunk);
}
// Plain text formatted
// TODO: Formatting should be handled outside of this class
if ($mode == 4) {
elseif ($mode == 4) {
if (!self::decodeContentChunk($chunk, $prev, $is_last)) {
return '';
}
@@ -3092,8 +3113,10 @@ class rcube_imap_generic
if ($is_last) {
$chunk = rtrim($chunk, "\t\r\n\0\x0B");
}
}
return preg_replace('/[\t\r\0\x0B]+\n/', "\n", $chunk);
if ($formatted) {
$chunk = preg_replace('/[\t\r\0\x0B]+\n/', "\n", $chunk);
}
return $chunk;
@@ -3744,6 +3767,60 @@ class rcube_imap_generic
}
}
/**
* Send the STORE X ANNOTATION command (RFC5257)
*
* @param string $mailbox Mailbox name
* @param array $entries
*
* @return bool True on success, False on failure
*
* @since 1.6.10
*/
public function storeMessageAnnotation($mailbox, $uids, $entries)
{
if (!$this->hasCapability('ANNOTATE-EXPERIMENT-1')) {
return false;
}
if (empty($entries) || empty($uids)) {
$this->setError(self::ERROR_COMMAND, 'Wrong argument for STORE ANNOTATION command');
return false;
}
if (!$this->select($mailbox)) {
return false;
}
/* Example input compatible with rcube_message_header::$annotations:
$entries = [
'/comment' => [
'value.priv' => 'test1',
'value.shared' => null,
],
];
*/
$request = [];
foreach ($entries as $name => $annotation) {
if (!empty($annotation)) {
foreach ($annotation as $key => $value) {
$annotation[$key] = $this->escape($key) . ' ' . $this->escape($value, true);
}
$request[] = $this->escape($name);
$request[] = $annotation;
}
}
$result = $this->execute(
'UID STORE',
[$this->compressMessageSet($uids), 'ANNOTATION', $request],
self::COMMAND_NORESPONSE
);
return $result == self::ERROR_OK;
}
/**
* Returns BODYSTRUCTURE for the specified message.
*
@@ -208,6 +208,13 @@ class rcube_message_header
*/
public $flags = [];
/**
* Message annotations (RFC 5257)
*
* @var ?array
*/
public $annotations;
/**
* Extra flags (for the messages list)
*
@@ -735,8 +735,8 @@ class rcube_mime
*
* @return string
* @author Till Klampaeckel <[email protected]>
* @see http://de2.php.net/manual/en/ref.fileinfo.php
* @see http://de2.php.net/mime_content_type
* @see https://www.php.net/manual/en/ref.fileinfo.php
* @see https://www.php.net/mime_content_type
*/
public static function file_content_type($path, $name, $failover = 'application/octet-stream', $is_stream = false, $skip_suffix = false)
{
@@ -375,7 +375,7 @@ class rcube_result_thread
$regexp = '(' . $element . '|' . $item . ')';
if (isset($this->meta['pos'][$index])) {
if (preg_match('/([0-9]+)/', $this->raw_data, $m, null, $this->meta['pos'][$index])) {
if (preg_match('/([0-9]+)/', $this->raw_data, $m, 0, $this->meta['pos'][$index])) {
$result = $m[1];
}
}
@@ -587,8 +587,8 @@ class rcube_result_thread
protected function parse_thread($str, $begin = 0, $end = 0, $depth = 0)
{
// Don't be tempted to change $str to pass by reference to speed this up - it will slow it down by about
// 7 times instead :-) See comments on http://uk2.php.net/references and this article:
// http://derickrethans.nl/files/phparch-php-variables-article.pdf
// 7 times instead :-) See comments on https://www.php.net/references and this article:
// https://derickrethans.nl/files/phparch-php-variables-article.pdf
$node = '';
if (!$end) {
$end = strlen($str);
@@ -416,23 +416,41 @@ class rcube_utils
*/
public static function mod_css_styles($source, $container_id, $allow_remote = false, $prefix = '')
{
$last_pos = 0;
$replacements = new rcube_string_replacer;
// ignore the whole block if evil styles are detected
$source = self::xss_entity_decode($source);
$stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
$evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\((?!data:image)' : '');
if (preg_match("/$evilexpr/i", $stripped)) {
// No @import allowed
// TODO: We should just remove it, not invalidate the whole content
if (stripos($source, '@import') !== false) {
return '/* evil! */';
}
$strict_url_regexp = '!url\s*\(\s*["\']?(https?:)//[a-z0-9/._+-]+["\']?\s*\)!Uims';
// Incomplete style expression
if (strpos($source, '{') === false) {
return '/* invalid! */';
}
// To prevent from a double-escaping tricks we consider a script with
// any escape sequences (after de-escaping them above) an evil script.
// This probably catches many valid scripts, but we\'re on the safe side.
if (preg_match('/\\\[0-9a-fA-F]{2}/', $source)) {
return '/* evil! */';
}
// remove html comments
$source = preg_replace('/(^\s*<\!--)|(-->\s*$)/m', '', $source);
$url_callback = static function ($url) use ($allow_remote) {
if (strpos($url, 'data:image') === 0) {
return $url;
}
if ($allow_remote && preg_match('|^https?://[a-z0-9/._+-]+$|i', $url)) {
return $url;
}
};
$last_pos = 0;
$replacements = new rcube_string_replacer();
// cut out all contents between { and }
while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
$nested = strpos($source, '{', $pos+1);
@@ -441,36 +459,9 @@ class rcube_utils
}
$length = $pos2 - $pos - 1;
$styles = substr($source, $pos+1, $length);
$output = '';
$styles = self::sanitize_css_block($styles, $url_callback);
// check every css rule in the style block...
foreach (self::parse_css_block($styles) as $rule) {
// Remove 'page' attributes (#7604)
if ($rule[0] == 'page') {
continue;
}
// Convert position:fixed to position:absolute (#5264)
if ($rule[0] == 'position' && strcasecmp($rule[1], 'fixed') === 0) {
$rule[1] = 'absolute';
}
else if ($allow_remote) {
$stripped = preg_replace('/[^a-z\(:;]/i', '', $rule[1]);
// allow data:image and strict url() values only
if (
stripos($stripped, 'url(') !== false
&& stripos($stripped, 'url(data:image') === false
&& !preg_match($strict_url_regexp, $rule[1])
) {
$rule[1] = '/* evil! */';
}
}
$output .= sprintf(" %s: %s;", $rule[0] , $rule[1]);
}
$key = $replacements->add($output . ' ');
$key = $replacements->add(strlen($styles) ? " {$styles} " : '');
$repl = $replacements->get_replacement($key);
$source = substr_replace($source, $repl, $pos+1, $length);
$last_pos = $pos2 - ($length - strlen($repl));
@@ -518,6 +509,63 @@ class rcube_utils
return $source;
}
/**
* Parse and sanitize single CSS block
*
* @param string $styles CSS styles block
* @param ?callable $url_callback URL validator callback
*
* @return string
*/
public static function sanitize_css_block($styles, $url_callback = null)
{
$output = [];
// check every css rule in the style block...
foreach (self::parse_css_block($styles) as $rule) {
$property = $rule[0];
$value = $rule[1];
if ($property == 'page') {
// Remove 'page' attributes (#7604)
continue;
} elseif ($property == 'position' && strcasecmp($value, 'fixed') === 0) {
// Convert position:fixed to position:absolute (#5264)
$value = 'absolute';
} elseif (preg_match('/expression|image-set/i', $value)) {
continue;
} else {
$value = '';
foreach (self::explode_css_property_block($rule[1]) as $val) {
if ($url_callback && preg_match('/^url\s*\(/i', $val)) {
if (preg_match('/^url\s*\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)/iu', $val, $match)) {
if ($url = $url_callback($match[1])) {
$value .= ' url(' . $url . ')';
}
}
} else {
// whitelist ?
$value .= ' ' . $val;
// #1488535: Fix size units, so width:800 would be changed to width:800px
if ($val
&& preg_match('/^(left|right|top|bottom|width|height)/i', $property)
&& preg_match('/^[0-9]+$/', $val)
) {
$value .= 'px';
}
}
}
}
if (strlen($value)) {
$output[] = $property . ': ' . trim($value);
}
}
return count($output) > 0 ? implode('; ', $output) . ';' : '';
}
/**
* Explode css style. Property names will be lower-cased and trimmed.
* Values will be trimmed. Invalid entries will be skipped.
@@ -590,6 +638,41 @@ class rcube_utils
return $result;
}
/**
* Explode css style value
*
* @param string $style CSS style
*
* @return array List of CSS values
*/
public static function explode_css_property_block($style)
{
$style = preg_replace('/\s+/', ' ', $style);
$result = [];
$strlen = strlen($style);
$q = false;
// explode value
for ($p = $i = 0; $i < $strlen; $i++) {
if (($style[$i] == '"' || $style[$i] == "'") && ($i == 0 || $style[$i - 1] != '\\')) {
if ($q == $style[$i]) {
$q = false;
} elseif (!$q) {
$q = $style[$i];
}
}
if (!$q && $style[$i] == ' ' && ($i == 0 || !preg_match('/[,\(]/', $style[$i - 1]))) {
$result[] = substr($style, $p, $i - $p);
$p = $i + 1;
}
}
$result[] = (string) substr($style, $p);
return $result;
}
/**
* Generate CSS classes from mimetype and filename extension
*
@@ -1,13 +1,13 @@
Description of Roundcube Framework 1.6.6 library import into Moodle
Description of Roundcube Framework library import into Moodle
We now use the client part of Roundcube Framework as a library in Moodle.
This library is used to receive emails from Moodle.
This library is not used to send emails.
For more information on this version of Roundcube Framework, check out https://github.com/roundcube/roundcubemail/releases/tag/1.6.6
For more information on this version of Roundcube Framework, check out https://github.com/roundcube/roundcubemail/releases/tag/X.Y.Z
To upgrade this library:
1. Download the latest release of Roundcube Framework (roundcube-framework-xxx-tar.gz) in https://github.com/roundcube/roundcubemail/releases.
1. Download the latest release of Roundcube Framework (roundcube-framework-xyz-tar.gz) in https://github.com/roundcube/roundcubemail/releases.
2. Extract the contents of the release archive to a temp folder.
3. Copy the following files from the temp folder to the Moodle folder admin/tool/messageinbound/roundcube:
- rcube_charset.php
@@ -17,8 +17,29 @@ To upgrade this library:
- rcube_result_index.php
- rcube_result_thread.php
- rcube_utils.php
4. Find and replace all array_first() calls with array_shift() in the following files:
To ease the process, you can execute the below command:
```
cp rcube_charset.php \
rcube_imap_generic.php \
rcube_message_header.php \
rcube_mime.php \
rcube_result_index.php \
rcube_result_thread.php \
rcube_utils.php \
/path/to/moodle/admin/tool/messageinbound/roundcube/
```
4. Enter to the /path/to/moodle/admin/tool/messageinbound/roundcube/.
5. Find and replace all array_first() calls with array_shift() in the following files:
- rcube_imap_generic.php
- rcube_result_index.php
- rcube_result_thread.php
5. Update admin/tool/messageinbound/thirdpartylibs.xml.
To ease the process, you can execute the below command:
```
sed -i 's/array_first(/array_shift(/g' \
rcube_imap_generic.php \
rcube_result_index.php \
rcube_result_thread.php
```
6. Update admin/tool/messageinbound/thirdpartylibs.xml versions.
+1 -1
View File
@@ -5,7 +5,7 @@
<name>Roundcube Framework</name>
<license>GPL</license>
<licenseversion>3.0+</licenseversion>
<version>1.6.6</version>
<version>1.6.10</version>
<repository>https://github.com/roundcube/roundcubemail</repository>
<copyrights>
<copyright>The Roundcube Dev Team</copyright>