diff options
Diffstat (limited to 'program/lib/Roundcube')
-rw-r--r-- | program/lib/Roundcube/bootstrap.php | 19 | ||||
-rw-r--r-- | program/lib/Roundcube/html.php | 14 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube.php | 26 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_contacts.php | 4 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_db.php | 45 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_db_pgsql.php | 15 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_image.php | 4 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_imap.php | 3 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_imap_generic.php | 61 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_message.php | 37 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_message_header.php | 7 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_mime.php | 178 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_plugin.php | 18 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_plugin_api.php | 120 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_session.php | 28 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_string_replacer.php | 4 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_utils.php | 16 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_vcard.php | 4 |
18 files changed, 463 insertions, 140 deletions
diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php index 0640a9448..b7e69cb2a 100644 --- a/program/lib/Roundcube/bootstrap.php +++ b/program/lib/Roundcube/bootstrap.php @@ -31,16 +31,25 @@ $config = array( // critical PHP settings here. Only these, which doesn't provide // an error/warning in the logs later. See (#1486307). 'mbstring.func_overload' => 0, - 'suhosin.session.encrypt' => 0, - 'session.auto_start' => 0, - 'file_uploads' => 1, 'magic_quotes_runtime' => 0, 'magic_quotes_sybase' => 0, // #1488506 ); + +// check these additional ini settings if not called via CLI +if (php_sapi_name() != 'cli') { + $config += array( + 'suhosin.session.encrypt' => 0, + 'session.auto_start' => 0, + 'file_uploads' => 1, + ); +} + foreach ($config as $optname => $optval) { if ($optval != ini_get($optname) && @ini_set($optname, $optval) === false) { - die("ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n" - ."Check your PHP configuration (including php_admin_flag)."); + $error = "ERROR: Wrong '$optname' option value and it wasn't possible to set it to required value ($optval).\n" + . "Check your PHP configuration (including php_admin_flag)."; + if (defined('STDERR')) fwrite(STDERR, $error); else echo $error; + exit(1); } } diff --git a/program/lib/Roundcube/html.php b/program/lib/Roundcube/html.php index 592720308..dbc9ca51f 100644 --- a/program/lib/Roundcube/html.php +++ b/program/lib/Roundcube/html.php @@ -35,6 +35,7 @@ class html public static $common_attrib = array('id','class','style','title','align'); public static $containers = array('iframe','div','span','p','h1','h2','h3','form','textarea','table','thead','tbody','tr','th','td','style','script'); + /** * Constructor * @@ -217,7 +218,7 @@ class html $attr = array('src' => $attr); } return self::tag('iframe', $attr, $cont, array_merge(self::$common_attrib, - array('src','name','width','height','border','frameborder'))); + array('src','name','width','height','border','frameborder','onload'))); } /** @@ -332,7 +333,16 @@ class html */ public static function quote($str) { - return @htmlspecialchars($str, ENT_COMPAT, RCUBE_CHARSET); + static $flags; + + if (!$flags) { + $flags = ENT_COMPAT; + if (defined('ENT_SUBSTITUTE')) { + $flags |= ENT_SUBSTITUTE; + } + } + + return @htmlspecialchars($str, $flags, RCUBE_CHARSET); } } diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index 3ae511e1e..b681f0531 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -405,6 +405,7 @@ class rcube $sess_domain = $this->config->get('session_domain'); $sess_path = $this->config->get('session_path'); $lifetime = $this->config->get('session_lifetime', 0) * 60; + $is_secure = $this->config->get('use_https') || rcube_utils::https_check(); // set session domain if ($sess_domain) { @@ -419,7 +420,7 @@ class rcube ini_set('session.gc_maxlifetime', $lifetime * 2); } - ini_set('session.cookie_secure', rcube_utils::https_check()); + ini_set('session.cookie_secure', $is_secure); ini_set('session.name', $sess_name ? $sess_name : 'roundcube_sessid'); ini_set('session.use_cookies', 1); ini_set('session.use_only_cookies', 1); @@ -1081,6 +1082,9 @@ class rcube 'message' => $arg->getMessage(), ); } + else if (is_string($arg)) { + $arg = array('message' => $arg, 'type' => 'php'); + } if (empty($arg['code'])) { $arg['code'] = 500; @@ -1093,14 +1097,24 @@ class rcube return; } - if (($log || $terminate) && $arg['type'] && $arg['message']) { + $cli = php_sapi_name() == 'cli'; + + if (($log || $terminate) && !$cli && $arg['type'] && $arg['message']) { $arg['fatal'] = $terminate; self::log_bug($arg); } - // display error page and terminate script - if ($terminate && is_object(self::$instance->output)) { - self::$instance->output->raise_error($arg['code'], $arg['message']); + // terminate script + if ($terminate) { + // display error page + if (is_object(self::$instance->output)) { + self::$instance->output->raise_error($arg['code'], $arg['message']); + } + else if ($cli) { + fwrite(STDERR, 'ERROR: ' . $arg['message']); + } + + exit(1); } } @@ -1139,7 +1153,7 @@ class rcube if (!self::write_log('errors', $log_entry)) { // send error to PHPs error handler if write_log didn't succeed - trigger_error($arg_arr['message']); + trigger_error($arg_arr['message'], E_USER_WARNING); } } diff --git a/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php index c66e98687..e4fd7dc10 100644 --- a/program/lib/Roundcube/rcube_contacts.php +++ b/program/lib/Roundcube/rcube_contacts.php @@ -626,10 +626,6 @@ class rcube_contacts extends rcube_addressbook $insert_id = $this->db->insert_id($this->db_name); } - // also add the newly created contact to the active group - if ($insert_id && $this->group_id) - $this->add_to_group($this->group_id, $insert_id); - $this->cache = null; return $insert_id; diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index 49bbe5c6e..d86e3dd98 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -444,17 +444,20 @@ class rcube_db * * @param mixed $result Optional query handle * @return mixed Number of rows or false on failure + * @deprecated This method shows very poor performance and should be avoided. */ public function num_rows($result = null) { if ($result || ($result === null && ($result = $this->last_result))) { // repeat query with SELECT COUNT(*) ... - if (preg_match('/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/i', $result->queryString, $m)) { + if (preg_match('/^SELECT\s+(?:ALL\s+|DISTINCT\s+)?(?:.*?)\s+FROM\s+(.*)$/ims', $result->queryString, $m)) { $query = $this->dbh->query('SELECT COUNT(*) FROM ' . $m[1], PDO::FETCH_NUM); return $query ? intval($query->fetchColumn(0)) : false; } else { - return count($result->fetchAll()); + $num = count($result->fetchAll()); + $result->execute(); // re-execute query because there's no seek(0) + return $num; } } @@ -631,6 +634,22 @@ class rcube_db } /** + * Escapes a string so it can be safely used in a query + * + * @param string $str A string to escape + * + * @return string Escaped string for use in a query + */ + public function escape($str) + { + if (is_null($str)) { + return 'NULL'; + } + + return substr($this->quote($str), 1, -1); + } + + /** * Quotes a string so it can be safely used as a table or column name * * @param string $str Value to quote @@ -645,6 +664,20 @@ class rcube_db } /** + * Escapes a string so it can be safely used in a query + * + * @param string $str A string to escape + * + * @return string Escaped string for use in a query + * @deprecated Replaced by rcube_db::escape + * @see rcube_db::escape + */ + public function escapeSimple($str) + { + return $this->escape($str); + } + + /** * Quotes a string so it can be safely used as a table or column name * * @param string $str Value to quote @@ -813,11 +846,9 @@ class rcube_db { $rcube = rcube::get_instance(); - // return table name if configured - $config_key = 'db_table_'.$table; - - if ($name = $rcube->config->get($config_key)) { - return $name; + // add prefix to the table name if configured + if ($prefix = $rcube->config->get('db_prefix')) { + return $prefix . $table; } return $table; diff --git a/program/lib/Roundcube/rcube_db_pgsql.php b/program/lib/Roundcube/rcube_db_pgsql.php index cf23c5e48..adfd2207b 100644 --- a/program/lib/Roundcube/rcube_db_pgsql.php +++ b/program/lib/Roundcube/rcube_db_pgsql.php @@ -53,19 +53,20 @@ class rcube_db_pgsql extends rcube_db /** * Return correct name for a specific database sequence * - * @param string $sequence Secuence name + * @param string $table Table name * * @return string Translated sequence name */ - protected function sequence_name($sequence) + protected function sequence_name($table) { - $rcube = rcube::get_instance(); + // Note: we support only one sequence per table + // Note: The sequence name must be <table_name>_seq + $sequence = $table . '_seq'; + $rcube = rcube::get_instance(); // return sequence name if configured - $config_key = 'db_sequence_'.$sequence; - - if ($name = $rcube->config->get($config_key)) { - return $name; + if ($prefix = $rcube->config->get('db_prefix')) { + return $prefix . $sequence; } return $sequence; diff --git a/program/lib/Roundcube/rcube_image.php b/program/lib/Roundcube/rcube_image.php index a55ba1600..735a0df01 100644 --- a/program/lib/Roundcube/rcube_image.php +++ b/program/lib/Roundcube/rcube_image.php @@ -124,6 +124,7 @@ class rcube_image } if ($result === '') { + @chmod($filename, 0600); return $type; } } @@ -183,6 +184,7 @@ class rcube_image } if ($result) { + @chmod($filename, 0600); return $type; } } @@ -223,6 +225,7 @@ class rcube_image $result = rcube::exec($convert . ' 2>&1 -colorspace RGB -quality 75 {in} {type}:{out}', $p); if ($result === '') { + @chmod($filename, 0600); return true; } } @@ -256,6 +259,7 @@ class rcube_image } if ($result) { + @chmod($filename, 0600); return true; } } diff --git a/program/lib/Roundcube/rcube_imap.php b/program/lib/Roundcube/rcube_imap.php index 0aa059c26..c67985186 100644 --- a/program/lib/Roundcube/rcube_imap.php +++ b/program/lib/Roundcube/rcube_imap.php @@ -3372,7 +3372,6 @@ class rcube_imap extends rcube_storage { if (!empty($this->options['fetch_headers'])) { $headers = explode(' ', $this->options['fetch_headers']); - $headers = array_map('strtoupper', $headers); } else { $headers = array(); @@ -3382,7 +3381,7 @@ class rcube_imap extends rcube_storage $headers = array_merge($headers, $this->all_headers); } - return implode(' ', array_unique($headers)); + return $headers; } diff --git a/program/lib/Roundcube/rcube_imap_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 2ac1355fd..460e6cc92 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -2265,24 +2265,53 @@ class rcube_imap_generic return $result; } - function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add = '') + /** + * Returns message(s) data (flags, headers, etc.) + * + * @param string $mailbox Mailbox name + * @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 + * + * @return bool|array List of rcube_message_header elements, False on error + */ + function fetchHeaders($mailbox, $message_set, $is_uid = false, $bodystr = false, $add_headers = array()) { $query_items = array('UID', 'RFC822.SIZE', 'FLAGS', 'INTERNALDATE'); - if ($bodystr) + $headers = array('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); + $headers = array_unique(array_merge($headers, $add_headers)); + } + + if ($bodystr) { $query_items[] = 'BODYSTRUCTURE'; - $query_items[] = 'BODY.PEEK[HEADER.FIELDS (' - . 'DATE FROM TO SUBJECT CONTENT-TYPE CC REPLY-TO LIST-POST DISPOSITION-NOTIFICATION-TO X-PRIORITY' - . ($add ? ' ' . trim($add) : '') - . ')]'; + } + + $query_items[] = 'BODY.PEEK[HEADER.FIELDS (' . implode(' ', $headers) . ')]'; $result = $this->fetch($mailbox, $message_set, $is_uid, $query_items); return $result; } - function fetchHeader($mailbox, $id, $uidfetch=false, $bodystr=false, $add='') + /** + * Returns message data (flags, headers, etc.) + * + * @param string $mailbox Mailbox name + * @param int $id Message sequence identifier or UID + * @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 + * + * @return bool|rcube_message_header Message data, False on error + */ + function fetchHeader($mailbox, $id, $is_uid = false, $bodystr = false, $add_headers = array()) { - $a = $this->fetchHeaders($mailbox, $id, $uidfetch, $bodystr, $add); + $a = $this->fetchHeaders($mailbox, $id, $is_uid, $bodystr, $add_headers); if (is_array($a)) { return array_shift($a); } @@ -3638,8 +3667,20 @@ class rcube_imap_generic */ static function strToTime($date) { - // support non-standard "GMTXXXX" literal - $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date); + // Clean malformed data + $date = preg_replace( + array( + '/GMT\s*([+-][0-9]+)/', // support non-standard "GMTXXXX" literal + '/[^a-z0-9\x20\x09:+-]/i', // remove any invalid characters + '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i', // remove weekday names + ), + array( + '\\1', + '', + '', + ), $date); + + $date = trim($date); // if date parsing fails, we have a date in non-rfc format // remove token from the end and try again diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php index 42d7b9bbe..9db1fa30a 100644 --- a/program/lib/Roundcube/rcube_message.php +++ b/program/lib/Roundcube/rcube_message.php @@ -85,12 +85,13 @@ class rcube_message $this->headers = $this->storage->get_message($uid); - if (!$this->headers) + if (!$this->headers) { return; + } $this->mime = new rcube_mime($this->headers->charset); - $this->subject = $this->mime->decode_mime_string($this->headers->subject); + $this->subject = $this->headers->get('subject'); list(, $this->sender) = each($this->mime->decode_address_list($this->headers->from, 1)); $this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$this->folder.':'.$uid])); @@ -125,15 +126,11 @@ class rcube_message */ public function get_header($name, $raw = false) { - if (empty($this->headers)) + if (empty($this->headers)) { return null; + } - if ($this->headers->$name) - $value = $this->headers->$name; - else if ($this->headers->others[$name]) - $value = $this->headers->others[$name]; - - return $raw ? $value : $this->mime->decode_header($value); + return $this->headers->get($name, !$raw); } @@ -152,12 +149,13 @@ class rcube_message * Compose a valid URL for getting a message part * * @param string $mime_id Part MIME-ID + * @param mixed $embed Mimetype class for parts to be embedded * @return string URL or false if part does not exist */ public function get_part_url($mime_id, $embed = false) { if ($this->mime_parts[$mime_id]) - return $this->opt['get_url'] . '&_part=' . $mime_id . ($embed ? '&_embed=1' : ''); + return $this->opt['get_url'] . '&_part=' . $mime_id . ($embed ? '&_embed=1&_mimeclass=' . $embed : ''); else return false; } @@ -364,7 +362,7 @@ class rcube_message // parse headers from message/rfc822 part if (!isset($structure->headers['subject']) && !isset($structure->headers['from'])) { - list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 8192)); + list($headers, $dump) = explode("\r\n\r\n", $this->get_part_content($structure->mime_id, null, true, 32768)); $structure->headers = rcube_mime::parse_headers($headers); } } @@ -372,7 +370,8 @@ class rcube_message $mimetype = $structure->mimetype; // show message headers - if ($recursive && is_array($structure->headers) && (isset($structure->headers['subject']) || isset($structure->headers['from']))) { + if ($recursive && is_array($structure->headers) && + (isset($structure->headers['subject']) || $structure->headers['from'] || $structure->headers['to'])) { $c = new stdClass; $c->type = 'headers'; $c->headers = $structure->headers; @@ -486,14 +485,6 @@ class rcube_message $this->parts[] = $c; } - // add html part as attachment - if ($html_part !== null && $structure->parts[$html_part] !== $print_part) { - $html_part = $structure->parts[$html_part]; - $html_part->mimetype = 'text/html'; - - $this->attachments[] = $html_part; - } - // add unsupported/unrecognized parts to attachments list if ($attach_part) { $this->attachments[] = $structure->parts[$attach_part]; @@ -578,10 +569,6 @@ class rcube_message if (!empty($mail_part->filename)) { $this->attachments[] = $mail_part; } - // list html part as attachment (here the part is most likely inside a multipart/related part) - else if ($this->parse_alternative && ($secondary_type == 'html' && !$this->opt['prefer_html'])) { - $this->attachments[] = $mail_part; - } } // part message/* else if ($primary_type == 'message') { @@ -657,7 +644,7 @@ class rcube_message $img_regexp = '/^image\/(gif|jpe?g|png|tiff|bmp|svg)/'; foreach ($this->inline_parts as $inline_object) { - $part_url = $this->get_part_url($inline_object->mime_id, true); + $part_url = $this->get_part_url($inline_object->mime_id, $inline_object->ctype_primary); if (isset($inline_object->content_id)) $a_replaces['cid:'.$inline_object->content_id] = $part_url; if ($inline_object->content_location) { diff --git a/program/lib/Roundcube/rcube_message_header.php b/program/lib/Roundcube/rcube_message_header.php index 274ae7f9f..2c5e2b6c8 100644 --- a/program/lib/Roundcube/rcube_message_header.php +++ b/program/lib/Roundcube/rcube_message_header.php @@ -215,7 +215,12 @@ class rcube_message_header $value = $this->others[$name]; } - return $decode ? rcube_mime::decode_header($value, $this->charset) : $value; + if ($decode) { + $value = rcube_mime::decode_header($value, $this->charset); + $value = rcube_charset::clean($value); + } + + return $value; } /** diff --git a/program/lib/Roundcube/rcube_mime.php b/program/lib/Roundcube/rcube_mime.php index b70d681c9..96296a57c 100644 --- a/program/lib/Roundcube/rcube_mime.php +++ b/program/lib/Roundcube/rcube_mime.php @@ -564,82 +564,122 @@ class rcube_mime /** - * Improved wordwrap function. + * Improved wordwrap function with multibyte support. + * The code is based on Zend_Text_MultiByte::wordWrap(). * - * @param string $string Text to wrap - * @param int $width Line width - * @param string $break Line separator - * @param bool $cut Enable to cut word - * @param string $charset Charset of $string + * @param string $string Text to wrap + * @param int $width Line width + * @param string $break Line separator + * @param bool $cut Enable to cut word + * @param string $charset Charset of $string + * @param bool $wrap_quoted When enabled quoted lines will not be wrapped * * @return string Text */ - public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null) + public static function wordwrap($string, $width=75, $break="\n", $cut=false, $charset=null, $wrap_quoted=true) { - if ($charset && function_exists('mb_internal_encoding')) { - mb_internal_encoding($charset); + if (!$charset) { + $charset = RCUBE_CHARSET; } - $para = preg_split('/\r?\n/', $string); - $string = ''; - - while (count($para)) { - $line = array_shift($para); - if ($line[0] == '>') { - $string .= $line . (count($para) ? $break : ''); - continue; + // detect available functions + $strlen_func = function_exists('iconv_strlen') ? 'iconv_strlen' : 'mb_strlen'; + $strpos_func = function_exists('iconv_strpos') ? 'iconv_strpos' : 'mb_strpos'; + $strrpos_func = function_exists('iconv_strrpos') ? 'iconv_strrpos' : 'mb_strrpos'; + $substr_func = function_exists('iconv_substr') ? 'iconv_substr' : 'mb_substr'; + + // Convert \r\n to \n, this is our line-separator + $string = str_replace("\r\n", "\n", $string); + $separator = "\n"; // must be 1 character length + $result = array(); + + while (($stringLength = $strlen_func($string, $charset)) > 0) { + $breakPos = $strpos_func($string, $separator, 0, $charset); + + // quoted line (do not wrap) + if ($wrap_quoted && $string[0] == '>') { + if ($breakPos === $stringLength - 1 || $breakPos === false) { + $subString = $string; + $cutLength = null; + } + else { + $subString = $substr_func($string, 0, $breakPos, $charset); + $cutLength = $breakPos + 1; + } + } + // next line found and current line is shorter than the limit + else if ($breakPos !== false && $breakPos < $width) { + if ($breakPos === $stringLength - 1) { + $subString = $string; + $cutLength = null; + } + else { + $subString = $substr_func($string, 0, $breakPos, $charset); + $cutLength = $breakPos + 1; + } } + else { + $subString = $substr_func($string, 0, $width, $charset); - $list = explode(' ', $line); - $len = 0; - while (count($list)) { - $line = array_shift($list); - $l = mb_strlen($line); - $space = $len ? 1 : 0; - $newlen = $len + $l + $space; - - if ($newlen <= $width) { - $string .= ($space ? ' ' : '').$line; - $len += ($space + $l); + // last line + if ($breakPos === false && $subString === $string) { + $cutLength = null; } else { - if ($l > $width) { - if ($cut) { - $start = 0; - while ($l) { - $str = mb_substr($line, $start, $width); - $strlen = mb_strlen($str); - $string .= ($len ? $break : '').$str; - $start += $strlen; - $l -= $strlen; - $len = $strlen; - } + $nextChar = $substr_func($string, $width, 1, $charset); + + if ($nextChar === ' ' || $nextChar === $separator) { + $afterNextChar = $substr_func($string, $width + 1, 1, $charset); + + if ($afterNextChar === false) { + $subString .= $nextChar; + } + + $cutLength = $strlen_func($subString, $charset) + 1; + } + else { + if ($strrpos_func[0] == 'm') { + $spacePos = $strrpos_func($subString, ' ', 0, $charset); } else { - $string .= ($len ? $break : '').$line; - if (count($list)) { - $string .= $break; + $spacePos = $strrpos_func($subString, ' ', $charset); + } + + if ($spacePos !== false) { + $subString = $substr_func($subString, 0, $spacePos, $charset); + $cutLength = $spacePos + 1; + } + else if ($cut === false) { + $spacePos = $strpos_func($string, ' ', 0, $charset); + + if ($spacePos !== false && $spacePos < $breakPos) { + $subString = $substr_func($string, 0, $spacePos, $charset); + $cutLength = $spacePos + 1; + } + else { + $subString = $string; + $cutLength = null; } - $len = 0; } - } - else { - $string .= $break.$line; - $len = $l; + else { + $subString = $substr_func($subString, 0, $width, $charset); + $cutLength = $width; + } } } } - if (count($para)) { - $string .= $break; - } - } + $result[] = $subString; - if ($charset && function_exists('mb_internal_encoding')) { - mb_internal_encoding(RCUBE_CHARSET); + if ($cutLength !== null) { + $string = $substr_func($string, $cutLength, ($stringLength - $cutLength), $charset); + } + else { + break; + } } - return $string; + return implode($break, $result); } @@ -769,11 +809,35 @@ class rcube_mime // fallback to some well-known types most important for daily emails if (empty($mime_types)) { - $mime_extensions = @include(RCUBE_CONFIG_DIR . '/mimetypes.php'); - $mime_extensions += array('gif' => 'image/gif', 'png' => 'image/png', 'jpg' => 'image/jpeg', 'jpeg' => 'image/jpeg', 'tif' => 'image/tiff'); + $mime_extensions = (array) @include(RCUBE_CONFIG_DIR . '/mimetypes.php'); - foreach ($mime_extensions as $ext => $mime) + foreach ($mime_extensions as $ext => $mime) { $mime_types[$mime][] = $ext; + } + } + + // Add some known aliases that aren't included by some mime.types (#1488891) + // the order is important here so standard extensions have higher prio + $aliases = array( + 'image/gif' => array('gif'), + 'image/png' => array('png'), + 'image/x-png' => array('png'), + 'image/jpeg' => array('jpg', 'jpeg', 'jpe'), + 'image/jpg' => array('jpg', 'jpeg', 'jpe'), + 'image/pjpeg' => array('jpg', 'jpeg', 'jpe'), + 'image/tiff' => array('tif'), + 'message/rfc822' => array('eml'), + 'text/x-mail' => array('eml'), + ); + + foreach ($aliases as $mime => $exts) { + $mime_types[$mime] = array_unique(array_merge((array) $mime_types[$mime], $exts)); + + foreach ($exts as $ext) { + if (!isset($mime_extensions[$ext])) { + $mime_extensions[$ext] = $mime; + } + } } return $mimetype ? $mime_types[$mimetype] : $mime_extensions; diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php index 9ea0f73d3..d24a2693c 100644 --- a/program/lib/Roundcube/rcube_plugin.php +++ b/program/lib/Roundcube/rcube_plugin.php @@ -60,6 +60,14 @@ abstract class rcube_plugin */ public $noframe = false; + /** + * A list of config option names that can be modified + * by the user via user interface (with save-prefs command) + * + * @var array + */ + public $allowed_prefs; + protected $home; protected $urlbase; private $mytask; @@ -84,6 +92,16 @@ abstract class rcube_plugin abstract function init(); /** + * Provide information about this + * + * @return array Meta information about a plugin or false if not implemented + */ + public static function info() + { + return false; + } + + /** * Attempt to load the given plugin which is required for the current plugin * * @param string Plugin name diff --git a/program/lib/Roundcube/rcube_plugin_api.php b/program/lib/Roundcube/rcube_plugin_api.php index 111c177d9..4bb6c6677 100644 --- a/program/lib/Roundcube/rcube_plugin_api.php +++ b/program/lib/Roundcube/rcube_plugin_api.php @@ -36,6 +36,7 @@ class rcube_plugin_api public $task = ''; public $output; public $handlers = array(); + public $allowed_prefs = array(); protected $plugins = array(); protected $tasks = array(); @@ -202,6 +203,11 @@ class rcube_plugin_api $plugin->init(); $this->plugins[$plugin_name] = $plugin; } + + if (!empty($plugin->allowed_prefs)) { + $this->allowed_prefs = array_merge($this->allowed_prefs, $plugin->allowed_prefs); + } + return true; } } @@ -222,6 +228,120 @@ class rcube_plugin_api } /** + * Get information about a specific plugin. + * This is either provided my a plugin's info() method or extracted from a package.xml or a composer.json file + * + * @param string Plugin name + * @return array Meta information about a plugin or False if plugin was not found + */ + public function get_info($plugin_name) + { + static $composer_lock, $license_uris = array( + 'Apache' => 'http://www.apache.org/licenses/LICENSE-2.0.html', + 'Apache-2' => 'http://www.apache.org/licenses/LICENSE-2.0.html', + 'Apache-1' => 'http://www.apache.org/licenses/LICENSE-1.0', + 'Apache-1.1' => 'http://www.apache.org/licenses/LICENSE-1.1', + 'GPL' => 'http://www.gnu.org/licenses/gpl.html', + 'GPLv2' => 'http://www.gnu.org/licenses/gpl-2.0.html', + 'GPL-2.0' => 'http://www.gnu.org/licenses/gpl-2.0.html', + 'GPLv3' => 'http://www.gnu.org/licenses/gpl-3.0.html', + 'GPL-3.0' => 'http://www.gnu.org/licenses/gpl-3.0.html', + 'GPL-3.0+' => 'http://www.gnu.org/licenses/gpl.html', + 'GPL-2.0+' => 'http://www.gnu.org/licenses/gpl.html', + 'LGPL' => 'http://www.gnu.org/licenses/lgpl.html', + 'LGPLv2' => 'http://www.gnu.org/licenses/lgpl-2.0.html', + 'LGPLv2.1' => 'http://www.gnu.org/licenses/lgpl-2.1.html', + 'LGPLv3' => 'http://www.gnu.org/licenses/lgpl.html', + 'LGPL-2.0' => 'http://www.gnu.org/licenses/lgpl-2.0.html', + 'LGPL-2.1' => 'http://www.gnu.org/licenses/lgpl-2.1.html', + 'LGPL-3.0' => 'http://www.gnu.org/licenses/lgpl.html', + 'LGPL-3.0+' => 'http://www.gnu.org/licenses/lgpl.html', + 'BSD' => 'http://opensource.org/licenses/bsd-license.html', + 'BSD-2-Clause' => 'http://opensource.org/licenses/BSD-2-Clause', + 'BSD-3-Clause' => 'http://opensource.org/licenses/BSD-3-Clause', + 'FreeBSD' => 'http://opensource.org/licenses/BSD-2-Clause', + 'MIT' => 'http://www.opensource.org/licenses/mit-license.php', + 'PHP' => 'http://opensource.org/licenses/PHP-3.0', + 'PHP-3' => 'http://www.php.net/license/3_01.txt', + 'PHP-3.0' => 'http://www.php.net/license/3_0.txt', + 'PHP-3.01' => 'http://www.php.net/license/3_01.txt', + ); + + $dir = dir($this->dir); + $fn = unslashify($dir->path) . DIRECTORY_SEPARATOR . $plugin_name . DIRECTORY_SEPARATOR . $plugin_name . '.php'; + $info = false; + + if (!class_exists($plugin_name)) + include($fn); + + if (class_exists($plugin_name)) + $info = $plugin_name::info(); + + // fall back to composer.json file + if (!$info) { + $composer = INSTALL_PATH . "/plugins/$plugin_name/composer.json"; + if (file_exists($composer) && ($json = @json_decode(file_get_contents($composer), true))) { + list($info['vendor'], $info['name']) = explode('/', $json['name']); + $info['license'] = $json['license']; + if ($license_uri = $license_uris[$info['license']]) + $info['license_uri'] = $license_uri; + } + + // read local composer.lock file (once) + if (!isset($composer_lock)) { + $composer_lock = @json_decode(@file_get_contents(INSTALL_PATH . "/composer.lock"), true); + if ($composer_lock['packages']) { + foreach ($composer_lock['packages'] as $i => $package) { + $composer_lock['installed'][$package['name']] = $package; + } + } + } + + // load additional information from local composer.lock file + if ($lock = $composer_lock['installed'][$json['name']]) { + $info['version'] = $lock['version']; + $info['uri'] = $lock['homepage'] ? $lock['homepage'] : $lock['source']['uri']; + $info['src_uri'] = $lock['dist']['uri'] ? $lock['dist']['uri'] : $lock['source']['uri']; + } + } + + // fall back to package.xml file + if (!$info) { + $package = INSTALL_PATH . "/plugins/$plugin_name/package.xml"; + if (file_exists($package) && ($file = file_get_contents($package))) { + $doc = new DOMDocument(); + $doc->loadXML($file); + $xpath = new DOMXPath($doc); + $xpath->registerNamespace('rc', "http://pear.php.net/dtd/package-2.0"); + $data = array(); + + // XPaths of plugin metadata elements + $metadata = array( + 'name' => 'string(//rc:package/rc:name)', + 'version' => 'string(//rc:package/rc:version/rc:release)', + 'license' => 'string(//rc:package/rc:license)', + 'license_uri' => 'string(//rc:package/rc:license/@uri)', + 'src_uri' => 'string(//rc:package/rc:srcuri)', + 'uri' => 'string(//rc:package/rc:uri)', + ); + + foreach ($metadata as $key => $path) { + $info[$key] = $xpath->evaluate($path); + } + + // dependent required plugins (can be used, but not included in config) + $deps = $xpath->evaluate('//rc:package/rc:dependencies/rc:required/rc:package/rc:name'); + for ($i = 0; $i < $deps->length; $i++) { + $dn = $deps->item($i)->nodeValue; + $info['requires'][] = $dn; + } + } + } + + return $info; + } + + /** * Allows a plugin object to register a callback for a certain hook * * @param string $hook Hook name diff --git a/program/lib/Roundcube/rcube_session.php b/program/lib/Roundcube/rcube_session.php index 82ff8a804..dedde2284 100644 --- a/program/lib/Roundcube/rcube_session.php +++ b/program/lib/Roundcube/rcube_session.php @@ -203,10 +203,15 @@ class rcube_session if (is_array($a_oldvars)) { // remove unset keys on oldvars foreach ((array)$this->unsets as $var) { - $path = explode('.', $var); - $k = array_pop($path); - $node = &$this->get_node($path, $a_oldvars); - unset($node[$k]); + if (isset($a_oldvars[$var])) { + unset($a_oldvars[$var]); + } + else { + $path = explode('.', $var); + $k = array_pop($path); + $node = &$this->get_node($path, $a_oldvars); + unset($node[$k]); + } } $newvars = $this->serialize(array_merge( @@ -402,7 +407,7 @@ class rcube_session /** * Unset a session variable * - * @param string Varibale name (can be a path denoting a certain node in the session array, e.g. compose.attachments.5) + * @param string Variable name (can be a path denoting a certain node in the session array, e.g. compose.attachments.5) * @return boolean True on success */ public function remove($var=null) @@ -413,10 +418,15 @@ class rcube_session $this->unsets[] = $var; - $path = explode('.', $var); - $key = array_pop($path); - $node = &$this->get_node($path, $_SESSION); - unset($node[$key]); + if (isset($_SESSION[$var])) { + unset($_SESSION[$var]); + } + else { + $path = explode('.', $var); + $key = array_pop($path); + $node = &$this->get_node($path, $_SESSION); + unset($node[$key]); + } return true; } diff --git a/program/lib/Roundcube/rcube_string_replacer.php b/program/lib/Roundcube/rcube_string_replacer.php index b8768bc98..0fc90a55a 100644 --- a/program/lib/Roundcube/rcube_string_replacer.php +++ b/program/lib/Roundcube/rcube_string_replacer.php @@ -95,12 +95,12 @@ class rcube_string_replacer $attrib = (array)$this->options['link_attribs']; $attrib['href'] = $url_prefix . $url; - $i = $this->add($prefix . html::a($attrib, rcube::Q($url)) . $suffix); + $i = $this->add(html::a($attrib, rcube::Q($url)) . $suffix); } // Return valid link for recognized schemes, otherwise // return the unmodified string for unrecognized schemes. - return $i >= 0 ? $this->get_replacement($i) : $matches[0]; + return $i >= 0 ? $prefix . $this->get_replacement($i) : $matches[0]; } /** diff --git a/program/lib/Roundcube/rcube_utils.php b/program/lib/Roundcube/rcube_utils.php index 1ae782a25..fabe0f060 100644 --- a/program/lib/Roundcube/rcube_utils.php +++ b/program/lib/Roundcube/rcube_utils.php @@ -729,8 +729,20 @@ class rcube_utils return $date; } - // support non-standard "GMTXXXX" literal - $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date); + // Clean malformed data + $date = preg_replace( + array( + '/GMT\s*([+-][0-9]+)/', // support non-standard "GMTXXXX" literal + '/[^a-z0-9\x20\x09:+-]/i', // remove any invalid characters + '/\s*(Mon|Tue|Wed|Thu|Fri|Sat|Sun)\s*/i', // remove weekday names + ), + array( + '\\1', + '', + '', + ), $date); + + $date = trim($date); // if date parsing fails, we have a date in non-rfc format. // remove token from the end and try again diff --git a/program/lib/Roundcube/rcube_vcard.php b/program/lib/Roundcube/rcube_vcard.php index de28767f8..54bb9521d 100644 --- a/program/lib/Roundcube/rcube_vcard.php +++ b/program/lib/Roundcube/rcube_vcard.php @@ -491,7 +491,9 @@ class rcube_vcard if (preg_match('/^END:VCARD$/i', $line)) { // parse vcard $obj = new rcube_vcard(self::cleanup($vcard_block), $charset, true, self::$fieldmap); - if (!empty($obj->displayname) || !empty($obj->email)) { + // FN and N is required by vCard format (RFC 2426) + // on import we can be less restrictive, let's addressbook decide + if (!empty($obj->displayname) || !empty($obj->surname) || !empty($obj->firstname) || !empty($obj->email)) { $out[] = $obj; } |