diff options
Diffstat (limited to 'program/lib/Roundcube')
-rw-r--r-- | program/lib/Roundcube/bootstrap.php | 6 | ||||
-rw-r--r-- | program/lib/Roundcube/html.php | 2 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube.php | 23 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_addressbook.php | 21 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_contacts.php | 40 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_csv2vcard.php | 44 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_db.php | 49 | ||||
-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_generic.php | 35 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_ldap.php | 38 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_message.php | 10 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_mime.php | 180 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_plugin.php | 10 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_plugin_api.php | 114 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_string_replacer.php | 4 | ||||
-rw-r--r-- | program/lib/Roundcube/rcube_utils.php | 16 |
17 files changed, 467 insertions, 144 deletions
diff --git a/program/lib/Roundcube/bootstrap.php b/program/lib/Roundcube/bootstrap.php index 929a4ff79..b7e69cb2a 100644 --- a/program/lib/Roundcube/bootstrap.php +++ b/program/lib/Roundcube/bootstrap.php @@ -46,8 +46,10 @@ if (php_sapi_name() != 'cli') { 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 7b30e60cb..dbc9ca51f 100644 --- a/program/lib/Roundcube/html.php +++ b/program/lib/Roundcube/html.php @@ -218,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'))); } /** diff --git a/program/lib/Roundcube/rcube.php b/program/lib/Roundcube/rcube.php index 77da83d8e..b681f0531 100644 --- a/program/lib/Roundcube/rcube.php +++ b/program/lib/Roundcube/rcube.php @@ -1082,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; @@ -1094,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); } } @@ -1140,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_addressbook.php b/program/lib/Roundcube/rcube_addressbook.php index cbc3c6773..84bd4bfcd 100644 --- a/program/lib/Roundcube/rcube_addressbook.php +++ b/program/lib/Roundcube/rcube_addressbook.php @@ -309,9 +309,14 @@ abstract class rcube_addressbook * List all active contact groups of this source * * @param string Optional search string to match group name + * @param int Matching mode: + * 0 - partial (*abc*), + * 1 - strict (=), + * 2 - prefix (abc*) + * * @return array Indexed list of contact groups, each a hash array */ - function list_groups($search = null) + function list_groups($search = null, $mode = 0) { /* empty for address books don't supporting groups */ return array(); @@ -370,9 +375,10 @@ abstract class rcube_addressbook /** * Add the given contact records the a certain group * - * @param string Group identifier - * @param array List of contact identifiers to be added - * @return int Number of contacts added + * @param string Group identifier + * @param array|string List of contact identifiers to be added + * + * @return int Number of contacts added */ function add_to_group($group_id, $ids) { @@ -383,9 +389,10 @@ abstract class rcube_addressbook /** * Remove the given contact records from a certain group * - * @param string Group identifier - * @param array List of contact identifiers to be removed - * @return int Number of deleted group members + * @param string Group identifier + * @param array|string List of contact identifiers to be removed + * + * @return int Number of deleted group members */ function remove_from_group($group_id, $ids) { diff --git a/program/lib/Roundcube/rcube_contacts.php b/program/lib/Roundcube/rcube_contacts.php index c66e98687..3919cdc6e 100644 --- a/program/lib/Roundcube/rcube_contacts.php +++ b/program/lib/Roundcube/rcube_contacts.php @@ -137,16 +137,34 @@ class rcube_contacts extends rcube_addressbook * List all active contact groups of this source * * @param string Search string to match group name + * @param int Matching mode: + * 0 - partial (*abc*), + * 1 - strict (=), + * 2 - prefix (abc*) + * * @return array Indexed list of contact groups, each a hash array */ - function list_groups($search = null) + function list_groups($search = null, $mode = 0) { $results = array(); if (!$this->groups) return $results; - $sql_filter = $search ? " AND " . $this->db->ilike('name', '%'.$search.'%') : ''; + if ($search) { + switch (intval($mode)) { + case 1: + $sql_filter = $this->db->ilike('name', $search); + break; + case 2: + $sql_filter = $this->db->ilike('name', $search . '%'); + break; + default: + $sql_filter = $this->db->ilike('name', '%' . $search . '%'); + } + + $sql_filter = " AND $sql_filter"; + } $sql_result = $this->db->query( "SELECT * FROM ".$this->db->table_name($this->db_groups). @@ -626,10 +644,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; @@ -883,9 +897,10 @@ class rcube_contacts extends rcube_addressbook /** * Add the given contact records the a certain group * - * @param string Group identifier - * @param array List of contact identifiers to be added - * @return int Number of contacts added + * @param string Group identifier + * @param array|string List of contact identifiers to be added + * + * @return int Number of contacts added */ function add_to_group($group_id, $ids) { @@ -930,9 +945,10 @@ class rcube_contacts extends rcube_addressbook /** * Remove the given contact records from a certain group * - * @param string Group identifier - * @param array List of contact identifiers to be removed - * @return int Number of deleted group members + * @param string Group identifier + * @param array|string List of contact identifiers to be removed + * + * @return int Number of deleted group members */ function remove_from_group($group_id, $ids) { diff --git a/program/lib/Roundcube/rcube_csv2vcard.php b/program/lib/Roundcube/rcube_csv2vcard.php index 0d3276b84..b0e9c2374 100644 --- a/program/lib/Roundcube/rcube_csv2vcard.php +++ b/program/lib/Roundcube/rcube_csv2vcard.php @@ -130,6 +130,21 @@ class rcube_csv2vcard 'work_state' => 'region:work', 'home_city_short' => 'locality:home', 'home_state_short' => 'region:home', + + // Atmail + 'date_of_birth' => 'birthday', + 'email' => 'email:pref', + 'home_mobile' => 'phone:cell', + 'home_zip' => 'zipcode:home', + 'info' => 'notes', + 'user_photo' => 'photo', + 'url' => 'website:homepage', + 'work_company' => 'organization', + 'work_dept' => 'departament', + 'work_fax' => 'phone:work,fax', + 'work_mobile' => 'phone:work,cell', + 'work_title' => 'jobtitle', + 'work_zip' => 'zipcode:work', ); /** @@ -230,8 +245,29 @@ class rcube_csv2vcard 'work_phone' => "Work Phone", 'work_address' => "Work Address", //'work_address_2' => "Work Address 2", + 'work_city' => "Work City", 'work_country' => "Work Country", + 'work_state' => "Work State", 'work_zipcode' => "Work ZipCode", + + // Atmail + 'date_of_birth' => "Date of Birth", + 'email' => "Email", + //'email_2' => "Email2", + //'email_3' => "Email3", + //'email_4' => "Email4", + //'email_5' => "Email5", + 'home_mobile' => "Home Mobile", + 'home_zip' => "Home Zip", + 'info' => "Info", + 'user_photo' => "User Photo", + 'url' => "URL", + 'work_company' => "Work Company", + 'work_dept' => "Work Dept", + 'work_fax' => "Work Fax", + 'work_mobile' => "Work Mobile", + 'work_title' => "Work Title", + 'work_zip' => "Work Zip", ); protected $local_label_map = array(); @@ -384,9 +420,13 @@ class rcube_csv2vcard $contact['birthday'] = $contact['birthday-y'] .'-' .$contact['birthday-m'] . '-' . $contact['birthday-d']; } + // Empty dates, e.g. "0/0/00", "0000-00-00 00:00:00" foreach (array('birthday', 'anniversary') as $key) { - if (!empty($contact[$key]) && $contact[$key] == '0/0/00') { // @TODO: localization? - unset($contact[$key]); + if (!empty($contact[$key])) { + $date = preg_replace('/[0[:^word:]]/', '', $contact[$key]); + if (empty($date)) { + unset($contact[$key]); + } } } diff --git a/program/lib/Roundcube/rcube_db.php b/program/lib/Roundcube/rcube_db.php index 4e6684c51..c96bccc90 100644 --- a/program/lib/Roundcube/rcube_db.php +++ b/program/lib/Roundcube/rcube_db.php @@ -405,21 +405,22 @@ class rcube_db $this->db_error_msg = null; // send query - $query = $this->dbh->query($query); + $result = $this->dbh->query($query); - if ($query === false) { + if ($result === false) { $error = $this->dbh->errorInfo(); $this->db_error = true; $this->db_error_msg = sprintf('[%s] %s', $error[1], $error[2]); rcube::raise_error(array('code' => 500, 'type' => 'db', 'line' => __LINE__, 'file' => __FILE__, - 'message' => $this->db_error_msg), true, false); + 'message' => $this->db_error_msg . " (SQL Query: $query)" + ), true, false); } - $this->last_result = $query; + $this->last_result = $result; - return $query; + return $result; } /** @@ -634,6 +635,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 @@ -648,6 +665,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 @@ -816,11 +847,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_generic.php b/program/lib/Roundcube/rcube_imap_generic.php index 04dc594ae..db50ffbab 100644 --- a/program/lib/Roundcube/rcube_imap_generic.php +++ b/program/lib/Roundcube/rcube_imap_generic.php @@ -2475,6 +2475,7 @@ class rcube_imap_generic $key = $this->nextTag(); $request = $key . ($is_uid ? ' UID' : '') . " FETCH $id ($fetch_mode.PEEK[$part]$partial)"; $result = false; + $found = false; // send request if (!$this->putLine($request)) { @@ -2494,18 +2495,25 @@ class rcube_imap_generic break; } - if (!preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) { + // skip irrelevant untagged responses (we have a result already) + if ($found || !preg_match('/^\* ([0-9]+) FETCH (.*)$/', $line, $m)) { continue; } $line = $m[2]; - $last = substr($line, -1); // handle one line response - if ($line[0] == '(' && $last == ')') { + if ($line[0] == '(' && substr($line, -1) == ')') { // tokenize content inside brackets - $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\$)/', '', $line)); - $result = count($tokens) == 1 ? $tokens[0] : false; + $tokens = $this->tokenizeResponse(preg_replace('/(^\(|\)$)/', '', $line)); + + for ($i=0; $i<count($tokens); $i+=2) { + if (preg_match('/^(BODY|BINARY)/i', $token)) { + $result = $tokens[$i+1]; + $found = true; + break; + } + } if ($result !== false) { if ($mode == 1) { @@ -2523,6 +2531,7 @@ class rcube_imap_generic else if (preg_match('/\{([0-9]+)\}$/', $line, $m)) { $bytes = (int) $m[1]; $prev = ''; + $found = true; while ($bytes > 0) { $line = $this->readLine(8192); @@ -3667,8 +3676,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_ldap.php b/program/lib/Roundcube/rcube_ldap.php index a2dd163e9..47e96c32b 100644 --- a/program/lib/Roundcube/rcube_ldap.php +++ b/program/lib/Roundcube/rcube_ldap.php @@ -1715,9 +1715,14 @@ class rcube_ldap extends rcube_addressbook * List all active contact groups of this source * * @param string Optional search string to match group name + * @param int Matching mode: + * 0 - partial (*abc*), + * 1 - strict (=), + * 2 - prefix (abc*) + * * @return array Indexed list of contact groups, each a hash array */ - function list_groups($search = null) + function list_groups($search = null, $mode = 0) { if (!$this->groups) return array(); @@ -1729,10 +1734,10 @@ class rcube_ldap extends rcube_addressbook $groups = array(); if ($search) { - $search = mb_strtolower($search); foreach ($group_cache as $group) { - if (strpos(mb_strtolower($group['name']), $search) !== false) + if ($this->compare_search_value('name', $group['name'], $search, $mode)) { $groups[] = $group; + } } } else @@ -1921,9 +1926,10 @@ class rcube_ldap extends rcube_addressbook /** * Add the given contact records the a certain group * - * @param string Group identifier - * @param array List of contact identifiers to be added - * @return int Number of contacts added + * @param string Group identifier + * @param array|string List of contact identifiers to be added + * + * @return int Number of contacts added */ function add_to_group($group_id, $contact_ids) { @@ -1937,8 +1943,8 @@ class rcube_ldap extends rcube_addressbook $group_name = $group_cache[$group_id]['name']; $member_attr = $group_cache[$group_id]['member_attr']; $group_dn = "cn=$group_name,$base_dn"; + $new_attrs = array(); - $new_attrs = array(); foreach ($contact_ids as $id) $new_attrs[$member_attr][] = self::dn_decode($id); @@ -1949,28 +1955,32 @@ class rcube_ldap extends rcube_addressbook $this->cache->remove('groups'); - return count($new_attrs['member']); + return count($new_attrs[$member_attr]); } /** * Remove the given contact records from a certain group * - * @param string Group identifier - * @param array List of contact identifiers to be removed - * @return int Number of deleted group members + * @param string Group identifier + * @param array|string List of contact identifiers to be removed + * + * @return int Number of deleted group members */ function remove_from_group($group_id, $contact_ids) { if (($group_cache = $this->cache->get('groups')) === null) $group_cache = $this->_fetch_groups(); + if (!is_array($contact_ids)) + $contact_ids = explode(',', $contact_ids); + $base_dn = $this->groups_base_dn; $group_name = $group_cache[$group_id]['name']; $member_attr = $group_cache[$group_id]['member_attr']; $group_dn = "cn=$group_name,$base_dn"; + $del_attrs = array(); - $del_attrs = array(); - foreach (explode(",", $contact_ids) as $id) + foreach ($contact_ids as $id) $del_attrs[$member_attr][] = self::dn_decode($id); if (!$this->ldap_mod_del($group_dn, $del_attrs)) { @@ -1980,7 +1990,7 @@ class rcube_ldap extends rcube_addressbook $this->cache->remove('groups'); - return count($del_attrs['member']); + return count($del_attrs[$member_attr]); } /** diff --git a/program/lib/Roundcube/rcube_message.php b/program/lib/Roundcube/rcube_message.php index 69735fc52..9db1fa30a 100644 --- a/program/lib/Roundcube/rcube_message.php +++ b/program/lib/Roundcube/rcube_message.php @@ -149,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; } @@ -361,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); } } @@ -369,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; @@ -642,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_mime.php b/program/lib/Roundcube/rcube_mime.php index 7cd520752..0a4bfbddb 100644 --- a/program/lib/Roundcube/rcube_mime.php +++ b/program/lib/Roundcube/rcube_mime.php @@ -127,10 +127,11 @@ class rcube_mime * @param int $max List only this number of addresses * @param boolean $decode Decode address strings * @param string $fallback Fallback charset if none specified + * @param boolean $addronly Return flat array with e-mail addresses only * - * @return array Indexed list of addresses + * @return array Indexed list of addresses */ - static function decode_address_list($input, $max = null, $decode = true, $fallback = null) + static function decode_address_list($input, $max = null, $decode = true, $fallback = null, $addronly = false) { $a = self::parse_address_list($input, $decode, $fallback); $out = array(); @@ -145,20 +146,21 @@ class rcube_mime foreach ($a as $val) { $j++; $address = trim($val['address']); - $name = trim($val['name']); - if ($name && $address && $name != $address) - $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address); - else if ($address) - $string = $address; - else if ($name) - $string = $name; - - $out[$j] = array( - 'name' => $name, - 'mailto' => $address, - 'string' => $string - ); + if ($addronly) { + $out[$j] = $address; + } + else { + $name = trim($val['name']); + if ($name && $address && $name != $address) + $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address); + else if ($address) + $string = $address; + else if ($name) + $string = $name; + + $out[$j] = array('name' => $name, 'mailto' => $address, 'string' => $string); + } if ($max && $j==$max) break; @@ -564,82 +566,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); } diff --git a/program/lib/Roundcube/rcube_plugin.php b/program/lib/Roundcube/rcube_plugin.php index 167a9eb4f..d24a2693c 100644 --- a/program/lib/Roundcube/rcube_plugin.php +++ b/program/lib/Roundcube/rcube_plugin.php @@ -92,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 a89f14712..4bb6c6677 100644 --- a/program/lib/Roundcube/rcube_plugin_api.php +++ b/program/lib/Roundcube/rcube_plugin_api.php @@ -228,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_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 |