From e02694c3a6dbe753c5683d201b6b6b14c2b30660 Mon Sep 17 00:00:00 2001 From: thomascube <thomas@roundcube.net> Date: Sat, 10 Dec 2011 09:16:31 -0500 Subject: [PATCH] Backported CSS sanitization (r5586:r5590) --- program/include/main.inc | 289 +++++++++++++++++++++++++++++++-------------------------- 1 files changed, 155 insertions(+), 134 deletions(-) diff --git a/program/include/main.inc b/program/include/main.inc index a3edbf7..d40dbda 100644 --- a/program/include/main.inc +++ b/program/include/main.inc @@ -97,9 +97,9 @@ * * @see rcmail::text_exists() */ -function rcube_label_exists($name, $domain=null) +function rcube_label_exists($name, $domain=null, &$ref_domain = null) { - return rcmail::get_instance()->text_exists($name, $domain); + return rcmail::get_instance()->text_exists($name, $domain, $ref_domain); } @@ -169,11 +169,17 @@ // get target timestamp $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1); - $db->query("DELETE FROM ".get_table_name('messages')." - WHERE created < " . $db->fromunixtime($ts)); + $db->query("DELETE FROM ".get_table_name('cache_messages') + ." WHERE changed < " . $db->fromunixtime($ts)); - $db->query("DELETE FROM ".get_table_name('cache')." - WHERE created < " . $db->fromunixtime($ts)); + $db->query("DELETE FROM ".get_table_name('cache_index') + ." WHERE changed < " . $db->fromunixtime($ts)); + + $db->query("DELETE FROM ".get_table_name('cache_thread') + ." WHERE changed < " . $db->fromunixtime($ts)); + + $db->query("DELETE FROM ".get_table_name('cache') + ." WHERE created < " . $db->fromunixtime($ts)); } @@ -634,20 +640,23 @@ function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL) { $value = NULL; - - if ($source==RCUBE_INPUT_GET && isset($_GET[$fname])) - $value = $_GET[$fname]; - else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname])) - $value = $_POST[$fname]; - else if ($source==RCUBE_INPUT_GPC) - { + + if ($source == RCUBE_INPUT_GET) { + if (isset($_GET[$fname])) + $value = $_GET[$fname]; + } + else if ($source == RCUBE_INPUT_POST) { + if (isset($_POST[$fname])) + $value = $_POST[$fname]; + } + else if ($source == RCUBE_INPUT_GPC) { if (isset($_POST[$fname])) $value = $_POST[$fname]; else if (isset($_GET[$fname])) $value = $_GET[$fname]; else if (isset($_COOKIE[$fname])) $value = $_COOKIE[$fname]; - } + } return parse_input_value($value, $allow_html, $charset); } @@ -655,7 +664,7 @@ /** * Parse/validate input value. See get_input_value() * Performs stripslashes() and charset conversion if necessary - * + * * @param string Input value * @param boolean Allow HTML tags in field value * @param string Charset to convert into @@ -681,15 +690,21 @@ else if (get_magic_quotes_gpc() || get_magic_quotes_runtime()) $value = stripslashes($value); - // remove HTML tags if not allowed + // remove HTML tags if not allowed if (!$allow_html) $value = strip_tags($value); - + + $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null; + + // remove invalid characters (#1488124) + if ($output_charset == 'UTF-8') + $value = rc_utf8_clean($value); + // convert to internal charset - if (is_object($OUTPUT) && $charset) - return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset); - else - return $value; + if ($charset && $output_charset) + $value = rcube_charset_convert($value, $output_charset, $charset); + + return $value; } /** @@ -699,15 +714,16 @@ * @param int Source to get value from (GPC) * @return array Hash array with all request parameters */ -function request2param($mode = RCUBE_INPUT_GPC) +function request2param($mode = RCUBE_INPUT_GPC, $ignore = 'task|action') { $out = array(); $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST); foreach ($src as $key => $value) { $fname = $key[0] == '_' ? substr($key, 1) : $key; - $out[$fname] = get_input_value($key, $mode); + if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname)) + $out[$fname] = get_input_value($key, $mode); } - + return $out; } @@ -723,12 +739,14 @@ /** * Convert the given string into a valid HTML identifier - * Same functionality as done in app.js with this.identifier_expr - * + * Same functionality as done in app.js with rcube_webmail.html_identifier() */ -function html_identifier($str) +function html_identifier($str, $encode=false) { - return asciiwords($str, true, '_'); + if ($encode) + return rtrim(strtr(base64_encode($str), '+/', '-_'), '='); + else + return asciiwords($str, true, '_'); } /** @@ -765,52 +783,48 @@ * @return string HTML table code */ function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col) - { +{ global $RCMAIL; - + $table = new html_table(/*array('cols' => count($a_show_cols))*/); - + // add table header if (!$attrib['noheader']) foreach ($a_show_cols as $col) $table->add_header($col, Q(rcube_label($col))); - + $c = 0; - if (!is_array($table_data)) + if (!is_array($table_data)) { $db = $RCMAIL->get_dbh(); while ($table_data && ($sql_arr = $db->fetch_assoc($table_data))) { - $zebra_class = $c % 2 ? 'even' : 'odd'; - $table->add_row(array('id' => 'rcmrow' . html_identifier($sql_arr[$id_col]), 'class' => $zebra_class)); + $table->add_row(array('id' => 'rcmrow' . html_identifier($sql_arr[$id_col]))); // format each col foreach ($a_show_cols as $col) $table->add($col, Q($sql_arr[$col])); - + $c++; } } - else - { + else { foreach ($table_data as $row_data) { - $zebra_class = $c % 2 ? 'even' : 'odd'; - if (!empty($row_data['class'])) - $zebra_class .= ' '.$row_data['class']; + $class = !empty($row_data['class']) ? $row_data['class'] : ''; - $table->add_row(array('id' => 'rcmrow' . html_identifier($row_data[$id_col]), 'class' => $zebra_class)); + $table->add_row(array('id' => 'rcmrow' . html_identifier($row_data[$id_col]), 'class' => $class)); // format each col foreach ($a_show_cols as $col) $table->add($col, Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col])); - + $c++; } } return $table->show($attrib); - } +} /** @@ -869,23 +883,37 @@ * @param string Container ID to use as prefix * @return string Modified CSS source */ -function rcmail_mod_css_styles($source, $container_id) +function rcmail_mod_css_styles($source, $container_id, $allow_remote=false) { $last_pos = 0; $replacements = new rcube_string_replacer; // ignore the whole block if evil styles are detected - $stripped = preg_replace('/[^a-z\(:;]/', '', rcmail_xss_entity_decode($source)); - if (preg_match('/expression|behavior|url\(|import[^a]/', $stripped)) + $source = rcmail_xss_entity_decode($source); + $stripped = preg_replace('/[^a-z\(:;]/i', '', $source); + $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : ''); + if (preg_match("/$evilexpr/i", $stripped)) return '/* evil! */'; - // remove css comments (sometimes used for some ugly hacks) - $source = preg_replace('!/\*(.+)\*/!Ums', '', $source); - // cut out all contents between { and } - while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) - { - $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1))); + while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) { + $styles = substr($source, $pos+1, $pos2-($pos+1)); + + // check every line of a style block... + if ($allow_remote) { + $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY); + foreach ($a_styles as $line) { + $stripped = preg_replace('/[^a-z\(:;]/i', '', $line); + // ... and only allow strict url() values + if (stripos($stripped, 'url(') && !preg_match('!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims', $line)) { + $a_styles = array('/* evil! */'); + break; + } + } + $styles = join(";\n", $a_styles); + } + + $key = $replacements->add($styles); $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2); $last_pos = $pos+2; } @@ -923,7 +951,7 @@ { $out = html_entity_decode(html_entity_decode($content)); $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out); - $out = preg_replace('#/\*.*\*/#Um', '', $out); + $out = preg_replace('#/\*.*\*/#Ums', '', $out); return $out; } @@ -1015,15 +1043,15 @@ * Convert the given date to a human readable form * This uses the date formatting properties from config * - * @param mixed Date representation (string or timestamp) + * @param mixed Date representation (string or timestamp) * @param string Date format to use + * @param bool Enables date convertion according to user timezone + * * @return string Formatted date string */ -function format_date($date, $format=NULL) +function format_date($date, $format=NULL, $convert=true) { - global $CONFIG; - - $ts = NULL; + global $RCMAIL, $CONFIG; if (!empty($date)) $ts = rcube_strtotime($date); @@ -1031,37 +1059,37 @@ if (empty($ts)) return ''; - // get user's timezone - if ($CONFIG['timezone'] === 'auto') - $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600; - else { - $tz = $CONFIG['timezone']; - if ($CONFIG['dst_active']) - $tz++; + if ($convert) { + // get user's timezone offset + $tz = $RCMAIL->config->get_timezone(); + + // convert time to user's timezone + $timestamp = $ts - date('Z', $ts) + ($tz * 3600); + + // get current timestamp in user's timezone + $now = time(); // local time + $now -= (int)date('Z'); // make GMT time + $now += ($tz * 3600); // user's time } - - // convert time to user's timezone - $timestamp = $ts - date('Z', $ts) + ($tz * 3600); - - // get current timestamp in user's timezone - $now = time(); // local time - $now -= (int)date('Z'); // make GMT time - $now += ($tz * 3600); // user's time - $now_date = getdate($now); - - $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']); - $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']); + else { + $now = time(); + $timestamp = $ts; + } // define date format depending on current time if (!$format) { + $now_date = getdate($now); + $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']); + $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']); + if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) { - $format = $CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i'; + $format = $RCMAIL->config->get('date_today', $RCMAIL->config->get('time_format', 'H:i')); $today = true; } else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now) - $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i'; + $format = $RCMAIL->config->get('date_short', 'D H:i'); else - $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i'; + $format = $RCMAIL->config->get('date_long', 'Y-m-d H:i'); } // strftime() format @@ -1213,9 +1241,9 @@ $p['folder_name'] = '*'; if ($p['unsubscribed']) - $list = $RCMAIL->imap->list_unsubscribed('', $p['folder_name'], $p['folder_filter']); + $list = $RCMAIL->imap->list_unsubscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']); else - $list = $RCMAIL->imap->list_mailboxes('', $p['folder_name'], $p['folder_filter']); + $list = $RCMAIL->imap->list_mailboxes('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']); $delimiter = $RCMAIL->imap->get_hierarchy_delimiter(); @@ -1229,7 +1257,7 @@ if ($p['noselection']) $select->add($p['noselection'], ''); - rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p['exceptions']); + rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p); return $select; } @@ -1278,11 +1306,6 @@ $path .= $prefix.$currentFolder; if (!isset($arrFolders[$currentFolder])) { - // Check \Noselect option (if options are in cache) - if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) { - $virtual = in_array('\\Noselect', $opts); - } - $arrFolders[$currentFolder] = array( 'id' => $path, 'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'), @@ -1310,13 +1333,14 @@ $realnames = (bool)$attrib['realnames']; $msgcounts = $RCMAIL->imap->get_cache('messagecount'); - $idx = 0; $out = ''; foreach ($arrFolders as $key => $folder) { - $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd'; - $title = null; + $title = null; + $folder_class = rcmail_folder_classname($folder['id']); + $collapsed = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false; + $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0; - if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) { + if ($folder_class && !$realnames) { $foldername = rcube_label($folder_class); } else { @@ -1332,30 +1356,15 @@ } // make folder name safe for ids and class names - $folder_id = html_identifier($folder['id']); + $folder_id = html_identifier($folder['id'], true); $classes = array('mailbox'); // set special class for Sent, Drafts, Trash and Junk - if ($folder['id'] == $CONFIG['sent_mbox']) - $classes[] = 'sent'; - else if ($folder['id'] == $CONFIG['drafts_mbox']) - $classes[] = 'drafts'; - else if ($folder['id'] == $CONFIG['trash_mbox']) - $classes[] = 'trash'; - else if ($folder['id'] == $CONFIG['junk_mbox']) - $classes[] = 'junk'; - else if ($folder['id'] == 'INBOX') - $classes[] = 'inbox'; - else - $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true); - - $classes[] = $zebra_class; + if ($folder_class) + $classes[] = $folder_class; if ($folder['id'] == $mbox_name) $classes[] = 'selected'; - - $collapsed = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false; - $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0; if ($folder['virtual']) $classes[] = 'virtual'; @@ -1390,7 +1399,6 @@ } $out .= "</li>\n"; - $idx++; } return $out; @@ -1402,12 +1410,22 @@ * @access private * @return string */ -function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0, $exceptions=array()) +function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0, $opts=array()) { + global $RCMAIL; + $out = ''; foreach ($arrFolders as $key => $folder) { - if (!empty($exceptions) && in_array($folder['id'], $exceptions)) { + // skip exceptions (and its subfolders) + if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) { + continue; + } + + // skip folders in which it isn't possible to create subfolders + if (!empty($opts['skip_noinferiors']) && ($attrs = $RCMAIL->imap->mailbox_attributes($folder['id'])) + && in_array('\\Noinferiors', $attrs) + ) { continue; } @@ -1425,7 +1443,7 @@ if (!empty($folder['folders'])) $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, - $select, $realnames, $nestLevel+1, $exceptions); + $select, $realnames, $nestLevel+1, $opts); } return $out; @@ -1574,7 +1592,11 @@ $RCMAIL->output->show_message('errorreadonly', 'error'); } else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) { - $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str)); + // try to detect access rights problem and display appropriate message + if (stripos($err_str, 'Permission denied') !== false) + $RCMAIL->output->show_message('errornoperm', 'error'); + else + $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str)); } else if ($fallback) { $RCMAIL->output->show_message($fallback, 'error', $fallback_args); @@ -1597,7 +1619,7 @@ $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode)); if ($hook['abort']) - return; + return; $lang = strtolower($_SESSION['language']); @@ -1609,9 +1631,14 @@ $RCMAIL->output->include_script('tiny_mce/tiny_mce.js'); $RCMAIL->output->include_script('editor.js'); - $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');", - JQ($lang), intval($CONFIG['enable_spellcheck']), $mode), - 'foot'); + $RCMAIL->output->add_script(sprintf("rcmail_editor_init(%s)", + json_encode(array( + 'mode' => $mode, + 'skin_path' => '$__skin_path', + 'lang' => $lang, + 'spellcheck' => intval($CONFIG['enable_spellcheck']), + 'spelldict' => intval($CONFIG['spellcheck_dictionary']), + ))), 'foot'); } @@ -1659,12 +1686,13 @@ * @param string $from Sender address string * @param array $mailto Array of recipient address strings * @param array $smtp_error SMTP error array (reference) - * @param string $body_file Location of file with saved message body (reference) + * @param string $body_file Location of file with saved message body (reference), + * used when delay_file_io is enabled * @param array $smtp_opts SMTP options (e.g. DSN request) * * @return boolean Send status. */ -function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file, $smtp_opts=null) +function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file=null, $smtp_opts=null) { global $CONFIG, $RCMAIL; @@ -1823,17 +1851,10 @@ // Returns RFC2822 formatted current date in user's timezone function rcmail_user_date() { - global $CONFIG; + global $RCMAIL, $CONFIG; // get user's timezone - if ($CONFIG['timezone'] === 'auto') { - $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600; - } - else { - $tz = $CONFIG['timezone']; - if ($CONFIG['dst_active']) - $tz++; - } + $tz = $RCMAIL->config->get_timezone(); $date = time() + $tz * 60 * 60; $date = gmdate('r', $date); @@ -2143,8 +2164,8 @@ if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user) return; - write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s', - $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip())); + write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s in session %s', + $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip(), session_id())); } @@ -2381,5 +2402,5 @@ $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15)); $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length')); - $RCMAIL->output->add_label('autocompletechars'); + $RCMAIL->output->add_label('autocompletechars', 'autocompletemore'); } -- Gitblit v1.9.1