From 0597586b89e86e8b4e22080d4c453475057b47b5 Mon Sep 17 00:00:00 2001 From: alecpl Date: Wed, 30 Nov 2011 11:35:43 +0000 Subject: - Applied fixes from trunk up to r5512 --- CHANGELOG | 2 ++ plugins/archive/localization/fr_FR.inc | 2 +- program/include/rcube_ldap.php | 57 +++++++++++++++++++------------- program/include/rcube_message.php | 6 ++-- program/lib/washtml.php | 2 +- program/steps/mail/func.inc | 4 +-- program/steps/mail/get.inc | 59 +++++++++++++++++++++++++++++++--- program/steps/mail/show.inc | 2 +- 8 files changed, 99 insertions(+), 35 deletions(-) diff --git a/CHANGELOG b/CHANGELOG index bec2a6147..97adf7c9c 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,6 +1,8 @@ CHANGELOG Roundcube Webmail =========================== +- Fix handling of empty tags in HTML messages (#1488225) +- Add content filter for embedded attachments to protect from XSS on IE (#1487895) - Use strpos() instead of strstr() when possible (#1488211) - Fix handling HTML entities when converting HTML to text (#1488212) - Fix fit_string_to_size() renders browser and ui unresponsive (#1488207) diff --git a/plugins/archive/localization/fr_FR.inc b/plugins/archive/localization/fr_FR.inc index f44f30f44..498a091fe 100644 --- a/plugins/archive/localization/fr_FR.inc +++ b/plugins/archive/localization/fr_FR.inc @@ -2,7 +2,7 @@ $labels = array(); $labels['buttontitle'] = 'Archiver ce message'; -$labels['archived'] = 'Message archivé avec success'; +$labels['archived'] = 'Message archivĂ© avec success'; $labels['archivefolder'] = 'Archive'; ?> diff --git a/program/include/rcube_ldap.php b/program/include/rcube_ldap.php index bf3ec4af6..801450452 100644 --- a/program/include/rcube_ldap.php +++ b/program/include/rcube_ldap.php @@ -732,16 +732,16 @@ class rcube_ldap extends rcube_addressbook $function = $this->_scope2func($this->prop['scope']); $this->ldap_result = @$function($this->conn, $this->base_dn, $this->filter ? $this->filter : '(objectclass=*)', - array_values($this->fieldmap), 0, (int)$this->prop['sizelimit'], (int)$this->prop['timelimit']); + array_values($this->fieldmap), 0, $this->page_size, (int)$this->prop['timelimit']); $this->result = new rcube_result_set(0); - if (!$this->ldap_result) { + if (!$this->ldap_result) { $this->_debug("S: ".ldap_error($this->conn)); - return $this->result; - } + return $this->result; + } - $this->_debug("S: ".ldap_count_entries($this->conn, $this->ldap_result)." record(s)"); + $this->_debug("S: ".ldap_count_entries($this->conn, $this->ldap_result)." record(s)"); // get all entries of this page and post-filter those that really match the query $search = mb_strtolower($value); @@ -1043,39 +1043,33 @@ class rcube_ldap extends rcube_addressbook $replacedata = array(); $deletedata = array(); - // flatten composite fields in $record - if (is_array($record['address'])) { - foreach ($record['address'] as $i => $struct) { - foreach ($struct as $col => $val) { - $record[$col][$i] = $val; - } - } - } + $ldap_data = $this->_map_data($save_cols); + $old_data = $record['_raw_attrib']; foreach ($this->fieldmap as $col => $fld) { - $val = $save_cols[$col]; + $val = $ldap_data[$fld]; if ($fld) { // remove empty array values if (is_array($val)) $val = array_filter($val); // The field does exist compare it to the ldap record. - if ($record[$col] != $val) { + if ($old_data[$fld] != $val) { // Changed, but find out how. - if (!isset($record[$col])) { + if (!isset($old_data[$fld])) { // Field was not set prior, need to add it. $newdata[$fld] = $val; - } // end if - elseif ($val == '') { + } + else if ($val == '') { // Field supplied is empty, verify that it is not required. if (!in_array($fld, $this->prop['required_fields'])) { // It is not, safe to clear. - $deletedata[$fld] = $record[$col]; - } // end if + $deletedata[$fld] = $old_data[$fld]; + } } // end elseif else { // The data was modified, save it out. $replacedata[$fld] = $val; - } // end else + } } // end if } // end if } // end foreach @@ -1303,6 +1297,9 @@ class rcube_ldap extends rcube_addressbook for ($i=0; $i < $rec[$lf]['count']; $i++) { if (!($value = $rec[$lf][$i])) continue; + + $out['_raw_attrib'][$lf][$i] = $value; + if ($rf == 'email' && $this->mail_domain && !strpos($value, '@')) $out[$rf][] = sprintf('%s@%s', $value, $this->mail_domain); else if (in_array($rf, array('street','zipcode','locality','country','region'))) @@ -1315,7 +1312,7 @@ class rcube_ldap extends rcube_addressbook // Make sure name fields aren't arrays (#1488108) if (is_array($out[$rf]) && in_array($rf, array('name', 'surname', 'firstname', 'middlename', 'nickname'))) { - $out[$rf] = $out[$rf][0]; + $out[$rf] = $out['_raw_attrib'][$lf] = $out[$rf][0]; } } @@ -1337,6 +1334,20 @@ class rcube_ldap extends rcube_addressbook */ private function _map_data($save_cols) { + // flatten composite fields first + foreach ($this->coltypes as $col => $colprop) { + if (is_array($colprop['childs']) && ($values = $this->get_col_values($col, $save_cols, false))) { + foreach ($values as $subtype => $childs) { + $subtype = $subtype ? ':'.$subtype : ''; + foreach ($childs as $i => $child_values) { + foreach ((array)$child_values as $childcol => $value) { + $save_cols[$childcol.$subtype][$i] = $value; + } + } + } + } + } + $ldap_data = array(); foreach ($this->fieldmap as $col => $fld) { $val = $save_cols[$col]; @@ -1586,11 +1597,13 @@ class rcube_ldap extends rcube_addressbook $base_dn = $this->groups_base_dn; $new_dn = "cn=$group_name,$base_dn"; $new_gid = self::dn_encode($group_name); + $member_attr = $this->prop['groups']['member_attr']; $name_attr = $this->prop['groups']['name_attr']; $new_entry = array( 'objectClass' => $this->prop['groups']['object_classes'], $name_attr => $group_name, + $member_attr => '', ); $this->_debug("C: Add [dn: $new_dn]: ".print_r($new_entry, true)); diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php index 0ecd86c4c..633f59be2 100644 --- a/program/include/rcube_message.php +++ b/program/include/rcube_message.php @@ -142,10 +142,10 @@ class rcube_message * @param string $mime_id Part MIME-ID * @return string URL or false if part does not exist */ - public function get_part_url($mime_id) + public function get_part_url($mime_id, $embed = false) { if ($this->mime_parts[$mime_id]) - return $this->opt['get_url'] . '&_part=' . $mime_id; + return $this->opt['get_url'] . '&_part=' . $mime_id . ($embed ? '&_embed=1' : ''); else return false; } @@ -511,7 +511,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); + $part_url = $this->get_part_url($inline_object->mime_id, true); if ($inline_object->content_id) $a_replaces['cid:'.$inline_object->content_id] = $part_url; if ($inline_object->content_location) { diff --git a/program/lib/washtml.php b/program/lib/washtml.php index 9292aa158..9c8625f30 100644 --- a/program/lib/washtml.php +++ b/program/lib/washtml.php @@ -108,7 +108,7 @@ class washtml /* Block elements which could be empty but cannot be returned in short form () */ static $block_elements = array('div', 'p', 'pre', 'blockquote', 'a', 'font', 'center', - 'table', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'dl', 'strong', 'i', 'b'); + 'table', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'dl', 'strong', 'i', 'b', 'u'); /* State for linked objects in HTML */ public $extlinks = false; diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc index 8407b06cd..c070dad3a 100644 --- a/program/steps/mail/func.inc +++ b/program/steps/mail/func.inc @@ -550,7 +550,7 @@ function rcmail_check_safe(&$message) * @param array CID map replaces (inline images) * @return string Clean HTML */ -function rcmail_wash_html($html, $p = array(), $cid_replaces) +function rcmail_wash_html($html, $p, $cid_replaces) { global $REMOTE_OBJECTS; @@ -1052,7 +1052,7 @@ function rcmail_message_body($attrib) ) { $out .= html::tag('hr') . html::p(array('align' => "center"), html::img(array( - 'src' => $MESSAGE->get_part_url($attach_prop->mime_id), + 'src' => $MESSAGE->get_part_url($attach_prop->mime_id, true), 'title' => $attach_prop->filename, 'alt' => $attach_prop->filename, ))); diff --git a/program/steps/mail/get.inc b/program/steps/mail/get.inc index 828f8debc..aee2c4d1a 100644 --- a/program/steps/mail/get.inc +++ b/program/steps/mail/get.inc @@ -134,11 +134,24 @@ else if ($pid = get_input_value('_part', RCUBE_INPUT_GET)) { header("Content-Disposition: $disposition; filename=\"$filename\""); - // turn off output buffering and print part content - if ($part->body) - echo $part->body; - else if ($part->size) - $IMAP->get_message_part($MESSAGE->uid, $part->mime_id, $part, true); + // do content filtering to avoid XSS through fake images + if (!empty($_REQUEST['_embed']) && $browser->ie && $browser->ver <= 8) { + if ($part->body) + echo preg_match('/<(script|iframe|object)/i', $part->body) ? '' : $part->body; + else if ($part->size) { + $stdout = fopen('php://output', 'w'); + stream_filter_register('rcube_content', 'rcube_content_filter') or die('Failed to register content filter'); + stream_filter_append($stdout, 'rcube_content'); + $IMAP->get_message_part($MESSAGE->uid, $part->mime_id, $part, false, $stdout); + } + } + else { + // turn off output buffering and print part content + if ($part->body) + echo $part->body; + else if ($part->size) + $IMAP->get_message_part($MESSAGE->uid, $part->mime_id, $part, true); + } } exit; @@ -166,3 +179,39 @@ header('HTTP/1.1 404 Not Found'); exit; + +/** + * PHP stream filter to detect html/javascript code in attachments + */ +class rcube_content_filter extends php_user_filter +{ + private $buffer = ''; + private $cutoff = 2048; + + function onCreate() + { + $this->cutoff = rand(2048, 3027); + return true; + } + + function filter($in, $out, &$consumed, $closing) + { + while ($bucket = stream_bucket_make_writeable($in)) { + $this->buffer .= $bucket->data; + + // check for evil content and abort + if (preg_match('/<(script|iframe|object)/i', $this->buffer)) + return PSFS_ERR_FATAL; + + // keep buffer small enough + if (strlen($this->buffer) > 4096) + $this->buffer = substr($this->buffer, $this->cutoff); + + $consumed += $bucket->datalen; + stream_bucket_append($out, $bucket); + } + + return PSFS_PASS_ON; + } +} + diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc index 8976e863a..d928cfd68 100644 --- a/program/steps/mail/show.inc +++ b/program/steps/mail/show.inc @@ -132,7 +132,7 @@ function rcmail_message_attachments($attrib) $ol .= html::tag('li', null, html::a(array( - 'href' => $MESSAGE->get_part_url($attach_prop->mime_id), + 'href' => $MESSAGE->get_part_url($attach_prop->mime_id, false), 'onclick' => sprintf( 'return %s.command(\'load-attachment\',{part:\'%s\', mimetype:\'%s\'},this)', JS_OBJECT_NAME, -- cgit v1.2.3