Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F9890773
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Flag For Later
Award Token
Size
136 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/program/include/main.inc b/program/include/main.inc
index 8aa38c8d1..2d18567e7 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -1,2434 +1,2434 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/main.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Provide basic functions for the webmail package |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
/**
* Roundcube Webmail common functions
*
* @package Core
* @author Thomas Bruederli <roundcube@gmail.com>
*/
require_once 'utf7.inc';
require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
// define constannts for input reading
define('RCUBE_INPUT_GET', 0x0101);
define('RCUBE_INPUT_POST', 0x0102);
define('RCUBE_INPUT_GPC', 0x0103);
/**
* Return correct name for a specific database table
*
* @param string Table name
* @return string Translated table name
*/
function get_table_name($table)
{
global $CONFIG;
// return table name if configured
$config_key = 'db_table_'.$table;
if (strlen($CONFIG[$config_key]))
return $CONFIG[$config_key];
return $table;
}
/**
* Return correct name for a specific database sequence
* (used for Postgres only)
*
* @param string Secuence name
* @return string Translated sequence name
*/
function get_sequence_name($sequence)
{
// return sequence name if configured
$config_key = 'db_sequence_'.$sequence;
$opt = rcmail::get_instance()->config->get($config_key);
if (!empty($opt))
return $opt;
return $sequence;
}
/**
* Get localized text in the desired language
* It's a global wrapper for rcmail::gettext()
*
* @param mixed Named parameters array or label name
* @param string Domain to search in (e.g. plugin name)
* @return string Localized text
* @see rcmail::gettext()
*/
function rcube_label($p, $domain=null)
{
return rcmail::get_instance()->gettext($p, $domain);
}
/**
* Global wrapper of rcmail::text_exists()
* to check whether a text label is defined
*
* @see rcmail::text_exists()
*/
function rcube_label_exists($name, $domain=null, &$ref_domain = null)
{
return rcmail::get_instance()->text_exists($name, $domain, $ref_domain);
}
/**
* Overwrite action variable
*
* @param string New action value
*/
function rcmail_overwrite_action($action)
{
$app = rcmail::get_instance();
$app->action = $action;
$app->output->set_env('action', $action);
}
/**
* Compose an URL for a specific action
*
* @param string Request action
* @param array More URL parameters
* @param string Request task (omit if the same)
* @return The application URL
*/
function rcmail_url($action, $p=array(), $task=null)
{
$app = rcmail::get_instance();
return $app->url((array)$p + array('_action' => $action, 'task' => $task));
}
/**
* Garbage collector function for temp files.
* Remove temp files older than two days
*/
function rcmail_temp_gc()
{
$rcmail = rcmail::get_instance();
$tmp = unslashify($rcmail->config->get('temp_dir'));
$expire = mktime() - 172800; // expire in 48 hours
if ($dir = opendir($tmp)) {
while (($fname = readdir($dir)) !== false) {
if ($fname{0} == '.')
continue;
if (filemtime($tmp.'/'.$fname) < $expire)
@unlink($tmp.'/'.$fname);
}
closedir($dir);
}
}
/**
* Garbage collector for cache entries.
* Remove all expired message cache records
* @return void
*/
function rcmail_cache_gc()
{
$rcmail = rcmail::get_instance();
$db = $rcmail->get_dbh();
// get target timestamp
$ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
$db->query("DELETE FROM ".get_table_name('cache_messages')
." WHERE changed < " . $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));
}
/**
* Catch an error and throw an exception.
*
* @param int Level of the error
* @param string Error message
*/
function rcube_error_handler($errno, $errstr)
{
throw new ErrorException($errstr, 0, $errno);
}
/**
* Convert a string from one charset to another.
* Uses mbstring and iconv functions if possible
*
* @param string Input string
* @param string Suspected charset of the input string
* @param string Target charset to convert to; defaults to RCMAIL_CHARSET
* @return string Converted string
*/
function rcube_charset_convert($str, $from, $to=NULL)
{
static $iconv_options = null;
static $mbstring_loaded = null;
static $mbstring_list = null;
static $conv = null;
$error = false;
$to = empty($to) ? strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
$from = rcube_parse_charset($from);
if ($from == $to || empty($str) || empty($from))
return $str;
// convert charset using iconv module
if (function_exists('iconv') && $from != 'UTF7-IMAP' && $to != 'UTF7-IMAP') {
if ($iconv_options === null) {
// ignore characters not available in output charset
$iconv_options = '//IGNORE';
if (iconv('', $iconv_options, '') === false) {
// iconv implementation does not support options
$iconv_options = '';
}
}
// throw an exception if iconv reports an illegal character in input
// it means that input string has been truncated
set_error_handler('rcube_error_handler', E_NOTICE);
try {
$_iconv = iconv($from, $to . $iconv_options, $str);
} catch (ErrorException $e) {
$_iconv = false;
}
restore_error_handler();
if ($_iconv !== false) {
return $_iconv;
}
}
if ($mbstring_loaded === null)
$mbstring_loaded = extension_loaded('mbstring');
// convert charset using mbstring module
if ($mbstring_loaded) {
$aliases['WINDOWS-1257'] = 'ISO-8859-13';
if ($mbstring_list === null) {
$mbstring_list = mb_list_encodings();
$mbstring_list = array_map('strtoupper', $mbstring_list);
}
$mb_from = $aliases[$from] ? $aliases[$from] : $from;
$mb_to = $aliases[$to] ? $aliases[$to] : $to;
// return if encoding found, string matches encoding and convert succeeded
if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
return $out;
}
}
// convert charset using bundled classes/functions
if ($to == 'UTF-8') {
if ($from == 'UTF7-IMAP') {
if ($_str = utf7_to_utf8($str))
return $_str;
}
else if ($from == 'UTF-7') {
if ($_str = rcube_utf7_to_utf8($str))
return $_str;
}
else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
return utf8_encode($str);
}
else if (class_exists('utf8')) {
if (!$conv)
$conv = new utf8($from);
else
$conv->loadCharset($from);
if($_str = $conv->strToUtf8($str))
return $_str;
}
$error = true;
}
// encode string for output
if ($from == 'UTF-8') {
// @TODO: we need a function for UTF-7 (RFC2152) conversion
if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
if ($_str = utf8_to_utf7($str))
return $_str;
}
else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
return utf8_decode($str);
}
else if (class_exists('utf8')) {
if (!$conv)
$conv = new utf8($to);
else
$conv->loadCharset($from);
if ($_str = $conv->strToUtf8($str))
return $_str;
}
$error = true;
}
// return UTF-8 or original string
return $str;
}
/**
* Parse and validate charset name string (see #1485758).
* Sometimes charset string is malformed, there are also charset aliases
* but we need strict names for charset conversion (specially utf8 class)
*
* @param string Input charset name
* @return string The validated charset name
*/
function rcube_parse_charset($input)
{
static $charsets = array();
$charset = strtoupper($input);
if (isset($charsets[$input]))
return $charsets[$input];
$charset = preg_replace(array(
'/^[^0-9A-Z]+/', // e.g. _ISO-8859-JP$SIO
'/\$.*$/', // e.g. _ISO-8859-JP$SIO
'/UNICODE-1-1-*/', // RFC1641/1642
'/^X-/', // X- prefix (e.g. X-ROMAN8 => ROMAN8)
), '', $charset);
if ($charset == 'BINARY')
return $charsets[$input] = null;
# Aliases: some of them from HTML5 spec.
$aliases = array(
'USASCII' => 'WINDOWS-1252',
'ANSIX31101983' => 'WINDOWS-1252',
'ANSIX341968' => 'WINDOWS-1252',
'UNKNOWN8BIT' => 'ISO-8859-15',
'UNKNOWN' => 'ISO-8859-15',
'USERDEFINED' => 'ISO-8859-15',
'KSC56011987' => 'EUC-KR',
'GB2312' => 'GBK',
'GB231280' => 'GBK',
'UNICODE' => 'UTF-8',
'UTF7IMAP' => 'UTF7-IMAP',
'TIS620' => 'WINDOWS-874',
'ISO88599' => 'WINDOWS-1254',
'ISO885911' => 'WINDOWS-874',
'MACROMAN' => 'MACINTOSH',
'77' => 'MAC',
'128' => 'SHIFT-JIS',
'129' => 'CP949',
'130' => 'CP1361',
'134' => 'GBK',
'136' => 'BIG5',
'161' => 'WINDOWS-1253',
'162' => 'WINDOWS-1254',
'163' => 'WINDOWS-1258',
'177' => 'WINDOWS-1255',
'178' => 'WINDOWS-1256',
'186' => 'WINDOWS-1257',
'204' => 'WINDOWS-1251',
'222' => 'WINDOWS-874',
'238' => 'WINDOWS-1250',
'MS950' => 'CP950',
'WINDOWS949' => 'UHC',
);
// allow A-Z and 0-9 only
$str = preg_replace('/[^A-Z0-9]/', '', $charset);
if (isset($aliases[$str]))
$result = $aliases[$str];
// UTF
else if (preg_match('/U[A-Z][A-Z](7|8|16|32)(BE|LE)*/', $str, $m))
$result = 'UTF-' . $m[1] . $m[2];
// ISO-8859
else if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
$iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
// some clients sends windows-1252 text as latin1,
// it is safe to use windows-1252 for all latin1
$result = $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
}
// handle broken charset names e.g. WINDOWS-1250HTTP-EQUIVCONTENT-TYPE
else if (preg_match('/(WIN|WINDOWS)([0-9]+)/', $str, $m)) {
$result = 'WINDOWS-' . $m[2];
}
// LATIN
else if (preg_match('/LATIN(.*)/', $str, $m)) {
$aliases = array('2' => 2, '3' => 3, '4' => 4, '5' => 9, '6' => 10,
'7' => 13, '8' => 14, '9' => 15, '10' => 16,
'ARABIC' => 6, 'CYRILLIC' => 5, 'GREEK' => 7, 'GREEK1' => 7, 'HEBREW' => 8);
// some clients sends windows-1252 text as latin1,
// it is safe to use windows-1252 for all latin1
if ($m[1] == 1) {
$result = 'WINDOWS-1252';
}
// if iconv is not supported we need ISO labels, it's also safe for iconv
else if (!empty($aliases[$m[1]])) {
$result = 'ISO-8859-'.$aliases[$m[1]];
}
// iconv requires convertion of e.g. LATIN-1 to LATIN1
else {
$result = $str;
}
}
else {
$result = $charset;
}
$charsets[$input] = $result;
return $result;
}
/**
* Converts string from standard UTF-7 (RFC 2152) to UTF-8.
*
* @param string Input string
* @return string The converted string
*/
function rcube_utf7_to_utf8($str)
{
$Index_64 = array(
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
);
$u7len = strlen($str);
$str = strval($str);
$res = '';
for ($i=0; $u7len > 0; $i++, $u7len--)
{
$u7 = $str[$i];
if ($u7 == '+')
{
$i++;
$u7len--;
$ch = '';
for (; $u7len > 0; $i++, $u7len--)
{
$u7 = $str[$i];
if (!$Index_64[ord($u7)])
break;
$ch .= $u7;
}
if ($ch == '') {
if ($u7 == '-')
$res .= '+';
continue;
}
$res .= rcube_utf16_to_utf8(base64_decode($ch));
}
else
{
$res .= $u7;
}
}
return $res;
}
/**
* Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
*
* @param string Input string
* @return string The converted string
*/
function rcube_utf16_to_utf8($str)
{
$len = strlen($str);
$dec = '';
for ($i = 0; $i < $len; $i += 2) {
$c = ord($str[$i]) << 8 | ord($str[$i + 1]);
if ($c >= 0x0001 && $c <= 0x007F) {
$dec .= chr($c);
} else if ($c > 0x07FF) {
$dec .= chr(0xE0 | (($c >> 12) & 0x0F));
$dec .= chr(0x80 | (($c >> 6) & 0x3F));
$dec .= chr(0x80 | (($c >> 0) & 0x3F));
} else {
$dec .= chr(0xC0 | (($c >> 6) & 0x1F));
$dec .= chr(0x80 | (($c >> 0) & 0x3F));
}
}
return $dec;
}
/**
* Replacing specials characters to a specific encoding type
*
* @param string Input string
* @param string Encoding type: text|html|xml|js|url
* @param string Replace mode for tags: show|replace|remove
* @param boolean Convert newlines
* @return string The quoted string
*/
function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
{
static $html_encode_arr = false;
static $js_rep_table = false;
static $xml_rep_table = false;
if (!$enctype)
$enctype = $OUTPUT->type;
// encode for HTML output
if ($enctype=='html')
{
if (!$html_encode_arr)
{
$html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
unset($html_encode_arr['?']);
}
$ltpos = strpos($str, '<');
$encode_arr = $html_encode_arr;
// don't replace quotes and html tags
if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
{
unset($encode_arr['"']);
unset($encode_arr['<']);
unset($encode_arr['>']);
unset($encode_arr['&']);
}
else if ($mode=='remove')
$str = strip_tags($str);
$out = strtr($str, $encode_arr);
// avoid douple quotation of &
$out = preg_replace('/&([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
return $newlines ? nl2br($out) : $out;
}
// if the replace tables for XML and JS are not yet defined
if ($js_rep_table===false)
{
$js_rep_table = $xml_rep_table = array();
$xml_rep_table['&'] = '&';
for ($c=160; $c<256; $c++) // can be increased to support more charsets
$xml_rep_table[chr($c)] = "&#$c;";
$xml_rep_table['"'] = '"';
$js_rep_table['"'] = '\\"';
$js_rep_table["'"] = "\\'";
$js_rep_table["\\"] = "\\\\";
// Unicode line and paragraph separators (#1486310)
$js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '
';
$js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '
';
}
// encode for javascript use
if ($enctype=='js')
return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
// encode for plaintext
if ($enctype=='text')
return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
if ($enctype=='url')
return rawurlencode($str);
// encode for XML
if ($enctype=='xml')
return strtr($str, $xml_rep_table);
// no encoding given -> return original string
return $str;
}
/**
* Quote a given string.
* Shortcut function for rep_specialchars_output
*
* @return string HTML-quoted string
* @see rep_specialchars_output()
*/
function Q($str, $mode='strict', $newlines=TRUE)
{
return rep_specialchars_output($str, 'html', $mode, $newlines);
}
/**
* Quote a given string for javascript output.
* Shortcut function for rep_specialchars_output
*
* @return string JS-quoted string
* @see rep_specialchars_output()
*/
function JQ($str)
{
return rep_specialchars_output($str, 'js');
}
/**
* Read input value and convert it for internal use
* Performs stripslashes() and charset conversion if necessary
*
* @param string Field name to read
* @param int Source to get value from (GPC)
* @param boolean Allow HTML tags in field value
* @param string Charset to convert into
* @return string Field value or NULL if not available
*/
function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
{
$value = NULL;
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);
}
/**
* 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
* @return string Parsed value
*/
function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
{
global $OUTPUT;
if (empty($value))
return $value;
if (is_array($value)) {
foreach ($value as $idx => $val)
$value[$idx] = parse_input_value($val, $allow_html, $charset);
return $value;
}
// strip single quotes if magic_quotes_sybase is enabled
if (ini_get('magic_quotes_sybase'))
$value = str_replace("''", "'", $value);
// strip slashes if magic_quotes enabled
else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
$value = stripslashes($value);
// 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 ($charset && $output_charset)
$value = rcube_charset_convert($value, $output_charset, $charset);
return $value;
}
/**
* Convert array of request parameters (prefixed with _)
* to a regular array with non-prefixed keys.
*
* @param int Source to get value from (GPC)
* @return array Hash array with all request parameters
*/
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;
if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname))
$out[$fname] = get_input_value($key, $mode);
}
return $out;
}
/**
* Remove all non-ascii and non-word chars
* except ., -, _
*/
function asciiwords($str, $css_id = false, $replace_with = '')
{
$allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
return preg_replace("/[^$allowed]/i", $replace_with, $str);
}
/**
* Convert the given string into a valid HTML identifier
* Same functionality as done in app.js with rcube_webmail.html_identifier()
*/
function html_identifier($str, $encode=false)
{
if ($encode)
return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
else
return asciiwords($str, true, '_');
}
/**
* Remove single and double quotes from given string
*
* @param string Input value
* @return string Dequoted string
*/
function strip_quotes($str)
{
return str_replace(array("'", '"'), '', $str);
}
/**
* Remove new lines characters from given string
*
* @param string Input value
* @return string Stripped string
*/
function strip_newlines($str)
{
return preg_replace('/[\r\n]/', '', $str);
}
/**
* Create a HTML table based on the given data
*
* @param array Named table attributes
* @param mixed Table row data. Either a two-dimensional array or a valid SQL result set
* @param array List of cols to show
* @param string Name of the identifier col
* @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))
{
$db = $RCMAIL->get_dbh();
while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
{
$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 {
foreach ($table_data as $row_data)
{
$class = !empty($row_data['class']) ? $row_data['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);
}
/**
* Create an edit field for inclusion on a form
*
* @param string col field name
* @param string value field value
* @param array attrib HTML element attributes for field
* @param string type HTML element type (default 'text')
* @return string HTML field definition
*/
function rcmail_get_edit_field($col, $value, $attrib, $type='text')
{
static $colcounts = array();
$fname = '_'.$col;
$attrib['name'] = $fname . ($attrib['array'] ? '[]' : '');
$attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
if ($type == 'checkbox') {
$attrib['value'] = '1';
$input = new html_checkbox($attrib);
}
else if ($type == 'textarea') {
$attrib['cols'] = $attrib['size'];
$input = new html_textarea($attrib);
}
else if ($type == 'select') {
$input = new html_select($attrib);
$input->add('---', '');
$input->add(array_values($attrib['options']), array_keys($attrib['options']));
}
else {
if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden')
$attrib['type'] = 'text';
$input = new html_inputfield($attrib);
}
// use value from post
if (isset($_POST[$fname])) {
$postvalue = get_input_value($fname, RCUBE_INPUT_POST, true);
$value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
}
$out = $input->show($value);
return $out;
}
/**
* Replace all css definitions with #container [def]
* and remove css-inlined scripting
*
* @param string CSS source code
* @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' . (!$allow_remote ? '|url\(|import[^a]' : '');
+ if (preg_match("/$evilexpr/i", $stripped) // don't accept No-Gos
+ || (strpos($stripped, 'url(') && !preg_match('!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Ui', $source))) // only allow clean urls
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)));
$source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
$last_pos = $pos+2;
}
// remove html comments and add #container to each tag selector.
// also replace body definition because we also stripped off the <body> tag
$styles = preg_replace(
array(
'/(^\s*<!--)|(-->\s*$)/',
'/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
'/'.preg_quote($container_id, '/').'\s+body/i',
),
array(
'',
"\\1#$container_id \\2",
$container_id,
),
$source);
// put block contents back in
$styles = $replacements->resolve($styles);
return $styles;
}
/**
* Decode escaped entities used by known XSS exploits.
* See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
*
* @param string CSS content to decode
* @return string Decoded string
*/
function rcmail_xss_entity_decode($content)
{
$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;
}
/**
* preg_replace_callback callback for rcmail_xss_entity_decode_callback
*
* @param array matches result from preg_replace_callback
* @return string decoded entity
*/
function rcmail_xss_entity_decode_callback($matches)
{
return chr(hexdec($matches[1]));
}
/**
* Compose a valid attribute string for HTML tags
*
* @param array Named tag attributes
* @param array List of allowed attributes
* @return string HTML formatted attribute string
*/
function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
{
// allow the following attributes to be added to the <iframe> tag
$attrib_str = '';
foreach ($allowed_attribs as $a)
if (isset($attrib[$a]))
$attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '"', $attrib[$a]));
return $attrib_str;
}
/**
* Convert a HTML attribute string attributes to an associative array (name => value)
*
* @param string Input string
* @return array Key-value pairs of parsed attributes
*/
function parse_attrib_string($str)
{
$attrib = array();
preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
// convert attributes to an associative array (name => value)
if ($regs) {
foreach ($regs as $attr) {
$attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
}
}
return $attrib;
}
/**
* Improved equivalent to strtotime()
*
* @param string Date string
* @return int
*/
function rcube_strtotime($date)
{
// check for MS Outlook vCard date format YYYYMMDD
if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
}
else if (is_numeric($date))
return $date;
// support non-standard "GMTXXXX" literal
$date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
// if date parsing fails, we have a date in non-rfc format.
// remove token from the end and try again
while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
$d = explode(' ', $date);
array_pop($d);
if (!$d) break;
$date = implode(' ', $d);
}
return $ts;
}
/**
* 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 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, $convert=true)
{
global $RCMAIL, $CONFIG;
if (!empty($date))
$ts = rcube_strtotime($date);
if (empty($ts))
return '';
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
}
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 = $RCMAIL->config->get('date_today', $RCMAIL->config->get('time_format', 'H:i'));
$today = true;
}
else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
$format = $RCMAIL->config->get('date_short', 'D H:i');
else
$format = $RCMAIL->config->get('date_long', 'Y-m-d H:i');
}
// strftime() format
if (preg_match('/%[a-z]+/i', $format)) {
$format = strftime($format, $timestamp);
return $today ? (rcube_label('today') . ' ' . $format) : $format;
}
// parse format string manually in order to provide localized weekday and month names
// an alternative would be to convert the date() format string to fit with strftime()
$out = '';
for($i=0; $i<strlen($format); $i++) {
if ($format[$i]=='\\') // skip escape chars
continue;
// write char "as-is"
if ($format[$i]==' ' || $format{$i-1}=='\\')
$out .= $format[$i];
// weekday (short)
else if ($format[$i]=='D')
$out .= rcube_label(strtolower(date('D', $timestamp)));
// weekday long
else if ($format[$i]=='l')
$out .= rcube_label(strtolower(date('l', $timestamp)));
// month name (short)
else if ($format[$i]=='M')
$out .= rcube_label(strtolower(date('M', $timestamp)));
// month name (long)
else if ($format[$i]=='F')
$out .= rcube_label('long'.strtolower(date('M', $timestamp)));
else if ($format[$i]=='x')
$out .= strftime('%x %X', $timestamp);
else
$out .= date($format[$i], $timestamp);
}
if ($today) {
$label = rcube_label('today');
// replcae $ character with "Today" label (#1486120)
if (strpos($out, '$') !== false) {
$out = preg_replace('/\$/', $label, $out, 1);
}
else {
$out = $label . ' ' . $out;
}
}
return $out;
}
/**
* Compose a valid representation of name and e-mail address
*
* @param string E-mail address
* @param string Person name
* @return string Formatted string
*/
function format_email_recipient($email, $name='')
{
if ($name && $name != $email) {
// Special chars as defined by RFC 822 need to in quoted string (or escaped).
return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
}
return trim($email);
}
/**
* Return the mailboxlist in HTML
*
* @param array Named parameters
* @return string HTML code for the gui object
*/
function rcmail_mailbox_list($attrib)
{
global $RCMAIL;
static $a_mailboxes;
$attrib += array('maxlength' => 100, 'realnames' => false);
// add some labels to client
$RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
$type = $attrib['type'] ? $attrib['type'] : 'ul';
unset($attrib['type']);
if ($type=='ul' && !$attrib['id'])
$attrib['id'] = 'rcmboxlist';
if (empty($attrib['folder_name']))
$attrib['folder_name'] = '*';
// get mailbox list
$mbox_name = $RCMAIL->imap->get_mailbox_name();
// build the folders tree
if (empty($a_mailboxes)) {
// get mailbox list
$a_folders = $RCMAIL->imap->list_mailboxes('', $attrib['folder_name'], $attrib['folder_filter']);
$delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
$a_mailboxes = array();
foreach ($a_folders as $folder)
rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
}
// allow plugins to alter the folder tree or to localize folder names
$hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
if ($type == 'select') {
$select = new html_select($attrib);
// add no-selection option
if ($attrib['noselection'])
$select->add(rcube_label($attrib['noselection']), '');
rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
$out = $select->show();
}
else {
$js_mailboxlist = array();
$out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
$RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
$RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
$RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders'));
}
return $out;
}
/**
* Return the mailboxlist as html_select object
*
* @param array Named parameters
* @return html_select HTML drop-down object
*/
function rcmail_mailbox_select($p = array())
{
global $RCMAIL;
$p += array('maxlength' => 100, 'realnames' => false);
$a_mailboxes = array();
if (empty($p['folder_name']))
$p['folder_name'] = '*';
if ($p['unsubscribed'])
$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'], $p['folder_rights']);
$delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
foreach ($list as $folder) {
if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
}
$select = new html_select($p);
if ($p['noselection'])
$select->add($p['noselection'], '');
rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
return $select;
}
/**
* Create a hierarchical array of the mailbox list
* @access private
* @return void
*/
function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
{
global $RCMAIL;
// Handle namespace prefix
$prefix = '';
if (!$path) {
$n_folder = $folder;
$folder = $RCMAIL->imap->mod_mailbox($folder);
if ($n_folder != $folder) {
$prefix = substr($n_folder, 0, -strlen($folder));
}
}
$pos = strpos($folder, $delm);
if ($pos !== false) {
$subFolders = substr($folder, $pos+1);
$currentFolder = substr($folder, 0, $pos);
// sometimes folder has a delimiter as the last character
if (!strlen($subFolders))
$virtual = false;
else if (!isset($arrFolders[$currentFolder]))
$virtual = true;
else
$virtual = $arrFolders[$currentFolder]['virtual'];
}
else {
$subFolders = false;
$currentFolder = $folder;
$virtual = false;
}
$path .= $prefix.$currentFolder;
if (!isset($arrFolders[$currentFolder])) {
$arrFolders[$currentFolder] = array(
'id' => $path,
'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
'virtual' => $virtual,
'folders' => array());
}
else
$arrFolders[$currentFolder]['virtual'] = $virtual;
if (strlen($subFolders))
rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
}
/**
* Return html for a structured list <ul> for the mailbox tree
* @access private
* @return string
*/
function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
{
global $RCMAIL, $CONFIG;
$maxlength = intval($attrib['maxlength']);
$realnames = (bool)$attrib['realnames'];
$msgcounts = $RCMAIL->imap->get_cache('messagecount');
$out = '';
foreach ($arrFolders as $key => $folder) {
$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 && !$realnames) {
$foldername = rcube_label($folder_class);
}
else {
$foldername = $folder['name'];
// shorten the folder name to a given length
if ($maxlength && $maxlength > 1) {
$fname = abbreviate_string($foldername, $maxlength);
if ($fname != $foldername)
$title = $foldername;
$foldername = $fname;
}
}
// make folder name safe for ids and class names
$folder_id = html_identifier($folder['id'], true);
$classes = array('mailbox');
// set special class for Sent, Drafts, Trash and Junk
if ($folder_class)
$classes[] = $folder_class;
if ($folder['id'] == $mbox_name)
$classes[] = 'selected';
if ($folder['virtual'])
$classes[] = 'virtual';
else if ($unread)
$classes[] = 'unread';
$js_name = JQ($folder['id']);
$html_name = Q($foldername) . ($unread ? html::span('unreadcount', " ($unread)") : '');
$link_attrib = $folder['virtual'] ? array() : array(
'href' => rcmail_url('', array('_mbox' => $folder['id'])),
'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
'rel' => $folder['id'],
'title' => $title,
);
$out .= html::tag('li', array(
'id' => "rcmli".$folder_id,
'class' => join(' ', $classes),
'noclose' => true),
html::a($link_attrib, $html_name) .
(!empty($folder['folders']) ? html::div(array(
'class' => ($collapsed ? 'collapsed' : 'expanded'),
'style' => "position:absolute",
'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
), ' ') : ''));
$jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
if (!empty($folder['folders'])) {
$out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
}
$out .= "</li>\n";
}
return $out;
}
/**
* Return html for a flat list <select> for the mailbox tree
* @access private
* @return string
*/
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) {
// 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;
}
if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
$foldername = rcube_label($folder_class);
else {
$foldername = $folder['name'];
// shorten the folder name to a given length
if ($maxlength && $maxlength>1)
$foldername = abbreviate_string($foldername, $maxlength);
}
$select->add(str_repeat(' ', $nestLevel*4) . $foldername, $folder['id']);
if (!empty($folder['folders']))
$out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
$select, $realnames, $nestLevel+1, $opts);
}
return $out;
}
/**
* Return internal name for the given folder if it matches the configured special folders
* @access private
* @return string
*/
function rcmail_folder_classname($folder_id)
{
global $CONFIG;
if ($folder_id == 'INBOX')
return 'inbox';
// for these mailboxes we have localized labels and css classes
foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
{
if ($folder_id == $CONFIG[$smbx.'_mbox'])
return $smbx;
}
}
/**
* Try to localize the given IMAP folder name.
* UTF-7 decode it in case no localized text was found
*
* @param string Folder name
* @return string Localized folder name in UTF-8 encoding
*/
function rcmail_localize_foldername($name)
{
if ($folder_class = rcmail_folder_classname($name))
return rcube_label($folder_class);
else
return rcube_charset_convert($name, 'UTF7-IMAP');
}
function rcmail_localize_folderpath($path)
{
global $RCMAIL;
$protect_folders = $RCMAIL->config->get('protect_default_folders');
$default_folders = (array) $RCMAIL->config->get('default_imap_folders');
$delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
$path = explode($delimiter, $path);
$result = array();
foreach ($path as $idx => $dir) {
$directory = implode($delimiter, array_slice($path, 0, $idx+1));
if ($protect_folders && in_array($directory, $default_folders)) {
unset($result);
$result[] = rcmail_localize_foldername($directory);
}
else {
$result[] = rcube_charset_convert($dir, 'UTF7-IMAP');
}
}
return implode($delimiter, $result);
}
function rcmail_quota_display($attrib)
{
global $OUTPUT;
if (!$attrib['id'])
$attrib['id'] = 'rcmquotadisplay';
if(isset($attrib['display']))
$_SESSION['quota_display'] = $attrib['display'];
$OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
$quota = rcmail_quota_content($attrib);
$OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready');
return html::span($attrib, '');
}
function rcmail_quota_content($attrib=NULL)
{
global $RCMAIL;
$quota = $RCMAIL->imap->get_quota();
$quota = $RCMAIL->plugins->exec_hook('quota', $quota);
$quota_result = (array) $quota;
$quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
$quota_result['title'] = rcube_label('unlimited');
$quota_result['percent'] = 0;
}
else if ($quota['total']) {
if (!isset($quota['percent']))
$quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
$title = sprintf('%s / %s (%.0f%%)',
show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
$quota_result['percent']);
$quota_result['title'] = $title;
if ($attrib['width'])
$quota_result['width'] = $attrib['width'];
if ($attrib['height'])
$quota_result['height'] = $attrib['height'];
}
else {
$quota_result['title'] = rcube_label('unknown');
$quota_result['percent'] = 0;
}
return $quota_result;
}
/**
* Outputs error message according to server error/response codes
*
* @param string Fallback message label
* @param string Fallback message label arguments
*
* @return void
*/
function rcmail_display_server_error($fallback=null, $fallback_args=null)
{
global $RCMAIL;
$err_code = $RCMAIL->imap->get_error_code();
$res_code = $RCMAIL->imap->get_response_code();
if ($res_code == rcube_imap::NOPERM) {
$RCMAIL->output->show_message('errornoperm', 'error');
}
else if ($res_code == rcube_imap::READONLY) {
$RCMAIL->output->show_message('errorreadonly', 'error');
}
else if ($err_code && ($err_str = $RCMAIL->imap->get_error_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);
}
return true;
}
/**
* Output HTML editor scripts
*
* @param string Editor mode
* @return void
*/
function rcube_html_editor($mode='')
{
global $RCMAIL;
$hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
if ($hook['abort'])
return;
$lang = strtolower($_SESSION['language']);
// TinyMCE uses two-letter lang codes, with exception of Chinese
if (strpos($lang, 'zh_') === 0)
$lang = str_replace('_', '-', $lang);
else
$lang = substr($lang, 0, 2);
if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
$lang = 'en';
$RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
$RCMAIL->output->include_script('editor.js');
$RCMAIL->output->add_script(sprintf("rcmail_editor_init(%s)",
json_encode(array(
'mode' => $mode,
'skin_path' => '$__skin_path',
'lang' => $lang,
'spellcheck' => intval($RCMAIL->config->get('enable_spellcheck')),
'spelldict' => intval($RCMAIL->config->get('spellcheck_dictionary')),
))), 'foot');
}
/**
* Replaces TinyMCE's emoticon images with plain-text representation
*
* @param string HTML content
* @return string HTML content
*/
function rcmail_replace_emoticons($html)
{
$emoticons = array(
'8-)' => 'smiley-cool',
':-#' => 'smiley-foot-in-mouth',
':-*' => 'smiley-kiss',
':-X' => 'smiley-sealed',
':-P' => 'smiley-tongue-out',
':-@' => 'smiley-yell',
":'(" => 'smiley-cry',
':-(' => 'smiley-frown',
':-D' => 'smiley-laughing',
':-)' => 'smiley-smile',
':-S' => 'smiley-undecided',
':-$' => 'smiley-embarassed',
'O:-)' => 'smiley-innocent',
':-|' => 'smiley-money-mouth',
':-O' => 'smiley-surprised',
';-)' => 'smiley-wink',
);
foreach ($emoticons as $idx => $file) {
// <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
$search[] = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
$replace[] = $idx;
}
return preg_replace($search, $replace, $html);
}
/**
* Send the given message using the configured method
*
* @param object $message Reference to Mail_MIME object
* @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),
* 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=null, $smtp_opts=null)
{
global $CONFIG, $RCMAIL;
$headers = $message->headers();
// send thru SMTP server using custom SMTP library
if ($CONFIG['smtp_server']) {
// generate list of recipients
$a_recipients = array($mailto);
if (strlen($headers['Cc']))
$a_recipients[] = $headers['Cc'];
if (strlen($headers['Bcc']))
$a_recipients[] = $headers['Bcc'];
// clean Bcc from header for recipients
$send_headers = $headers;
unset($send_headers['Bcc']);
// here too, it because txtHeaders() below use $message->_headers not only $send_headers
unset($message->_headers['Bcc']);
$smtp_headers = $message->txtHeaders($send_headers, true);
if ($message->getParam('delay_file_io')) {
// use common temp dir
$temp_dir = $RCMAIL->config->get('temp_dir');
$body_file = tempnam($temp_dir, 'rcmMsg');
if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
raise_error(array('code' => 650, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not create message: ".$mime_result->getMessage()),
TRUE, FALSE);
return false;
}
$msg_body = fopen($body_file, 'r');
} else {
$msg_body = $message->get();
}
// send message
if (!is_object($RCMAIL->smtp))
$RCMAIL->smtp_init(true);
$sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
$smtp_response = $RCMAIL->smtp->get_response();
$smtp_error = $RCMAIL->smtp->get_error();
// log error
if (!$sent)
raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__,
'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
}
// send mail using PHP's mail() function
else {
// unset some headers because they will be added by the mail() function
$headers_enc = $message->headers($headers);
$headers_php = $message->_headers;
unset($headers_php['To'], $headers_php['Subject']);
// reset stored headers and overwrite
$message->_headers = array();
$header_str = $message->txtHeaders($headers_php);
// #1485779
if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
$headers_enc['To'] = implode(', ', $m[1]);
}
}
$msg_body = $message->get();
if (PEAR::isError($msg_body))
raise_error(array('code' => 650, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not create message: ".$msg_body->getMessage()),
TRUE, FALSE);
else {
$delim = $RCMAIL->config->header_delimiter();
$to = $headers_enc['To'];
$subject = $headers_enc['Subject'];
$header_str = rtrim($header_str);
if ($delim != "\r\n") {
$header_str = str_replace("\r\n", $delim, $header_str);
$msg_body = str_replace("\r\n", $delim, $msg_body);
$to = str_replace("\r\n", $delim, $to);
$subject = str_replace("\r\n", $delim, $subject);
}
if (ini_get('safe_mode'))
$sent = mail($to, $subject, $msg_body, $header_str);
else
$sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
}
}
if ($sent) {
$RCMAIL->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
// remove MDN headers after sending
unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
// get all recipients
if ($headers['Cc'])
$mailto .= $headers['Cc'];
if ($headers['Bcc'])
$mailto .= $headers['Bcc'];
if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
$mailto = implode(', ', array_unique($m[1]));
if ($CONFIG['smtp_log']) {
write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
$RCMAIL->user->get_username(),
$_SERVER['REMOTE_ADDR'],
$mailto,
!empty($smtp_response) ? join('; ', $smtp_response) : ''));
}
}
if (is_resource($msg_body)) {
fclose($msg_body);
}
$message->_headers = array();
$message->headers($headers);
return $sent;
}
// Returns unique Message-ID
function rcmail_gen_message_id()
{
global $RCMAIL;
$local_part = md5(uniqid('rcmail'.mt_rand(),true));
$domain_part = $RCMAIL->user->get_username('domain');
// Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']))
&& preg_match('/\.[a-z]+$/i', $host)) {
$domain_part = $host;
}
else if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['SERVER_NAME']))
&& preg_match('/\.[a-z]+$/i', $host)) {
$domain_part = $host;
}
}
return sprintf('<%s@%s>', $local_part, $domain_part);
}
// Returns RFC2822 formatted current date in user's timezone
function rcmail_user_date()
{
global $RCMAIL, $CONFIG;
// get user's timezone
$tz = $RCMAIL->config->get_timezone();
$date = time() + $tz * 60 * 60;
$date = gmdate('r', $date);
$tz = sprintf('%+05d', intval($tz) * 100 + ($tz - intval($tz)) * 60);
$date = preg_replace('/[+-][0-9]{4}$/', $tz, $date);
return $date;
}
/**
* Check if we can process not exceeding memory_limit
*
* @param integer Required amount of memory
* @return boolean
*/
function rcmail_mem_check($need)
{
$mem_limit = parse_bytes(ini_get('memory_limit'));
$memory = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
return $mem_limit && $memory + $need > $mem_limit ? false : true;
}
/**
* Check if working in SSL mode
*
* @param integer HTTPS port number
* @param boolean Enables 'use_https' option checking
* @return boolean
*/
function rcube_https_check($port=null, $use_https=true)
{
global $RCMAIL;
if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
return true;
if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
return true;
if ($port && $_SERVER['SERVER_PORT'] == $port)
return true;
if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
return true;
return false;
}
/**
* For backward compatibility.
*
* @global rcmail $RCMAIL
* @param string $var_name Variable name.
* @return void
*/
function rcube_sess_unset($var_name=null)
{
global $RCMAIL;
$RCMAIL->session->remove($var_name);
}
/**
* Replaces hostname variables
*
* @param string $name Hostname
* @param string $host Optional IMAP hostname
* @return string
*/
function rcube_parse_host($name, $host='')
{
// %n - host
$n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
// %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
$d = preg_replace('/^[^\.]+\./', '', $n);
// %h - IMAP host
$h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
// %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
$z = preg_replace('/^[^\.]+\./', '', $h);
// %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
if ( strpos($name, '%s') !== false ){
$user_email = rcube_idn_convert(get_input_value('_user', RCUBE_INPUT_POST), true);
if ( preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s) < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false )
return false;
}
$name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
return $name;
}
/**
* E-mail address validation
*
* @param string $email Email address
* @param boolean $dns_check True to check dns
* @return boolean
*/
function check_email($email, $dns_check=true)
{
// Check for invalid characters
if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
return false;
// Check for length limit specified by RFC 5321 (#1486453)
if (strlen($email) > 254)
return false;
$email_array = explode('@', $email);
// Check that there's one @ symbol
if (count($email_array) < 2)
return false;
$domain_part = array_pop($email_array);
$local_part = implode('@', $email_array);
// from PEAR::Validate
$regexp = '&^(?:
("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| #1 quoted name
([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*)) #2 OR dot-atom (RFC5322)
$&xi';
if (!preg_match($regexp, $local_part))
return false;
// Check domain part
if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part))
return true; // IP address
else {
// If not an IP address
$domain_array = explode('.', $domain_part);
if (sizeof($domain_array) < 2)
return false; // Not enough parts to be a valid domain
foreach ($domain_array as $part)
if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
return false;
if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
return true;
if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
$lookup = array();
@exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
foreach ($lookup as $line) {
if (strpos($line, 'MX preference'))
return true;
}
return false;
}
// find MX record(s)
if (getmxrr($domain_part, $mx_records))
return true;
// find any DNS record
if (checkdnsrr($domain_part, 'ANY'))
return true;
}
return false;
}
/*
* Idn_to_ascii wrapper.
* Intl/Idn modules version of this function doesn't work with e-mail address
*/
function rcube_idn_to_ascii($str)
{
return rcube_idn_convert($str, true);
}
/*
* Idn_to_ascii wrapper.
* Intl/Idn modules version of this function doesn't work with e-mail address
*/
function rcube_idn_to_utf8($str)
{
return rcube_idn_convert($str, false);
}
function rcube_idn_convert($input, $is_utf=false)
{
if ($at = strpos($input, '@')) {
$user = substr($input, 0, $at);
$domain = substr($input, $at+1);
}
else {
$domain = $input;
}
$domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
if ($domain === false) {
return '';
}
return $at ? $user . '@' . $domain : $domain;
}
/**
* Helper class to turn relative urls into absolute ones
* using a predefined base
*/
class rcube_base_replacer
{
private $base_url;
public function __construct($base)
{
$this->base_url = $base;
}
public function callback($matches)
{
return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
}
}
/****** debugging and logging functions ********/
/**
* Print or write debug messages
*
* @param mixed Debug message or data
* @return void
*/
function console()
{
$args = func_get_args();
if (class_exists('rcmail', false)) {
$rcmail = rcmail::get_instance();
if (is_object($rcmail->plugins)) {
$plugin = $rcmail->plugins->exec_hook('console', array('args' => $args));
if ($plugin['abort'])
return;
$args = $plugin['args'];
}
}
$msg = array();
foreach ($args as $arg)
$msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
write_log('console', join(";\n", $msg));
}
/**
* Append a line to a logfile in the logs directory.
* Date will be added automatically to the line.
*
* @param $name name of log file
* @param line Line to append
* @return void
*/
function write_log($name, $line)
{
global $CONFIG, $RCMAIL;
if (!is_string($line))
$line = var_export($line, true);
if (empty($CONFIG['log_date_format']))
$CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
$date = date($CONFIG['log_date_format']);
// trigger logging hook
if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
$log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
$name = $log['name'];
$line = $log['line'];
$date = $log['date'];
if ($log['abort'])
return true;
}
if ($CONFIG['log_driver'] == 'syslog') {
$prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
syslog($prio, $line);
return true;
}
else {
$line = sprintf("[%s]: %s\n", $date, $line);
// log_driver == 'file' is assumed here
if (empty($CONFIG['log_dir']))
$CONFIG['log_dir'] = INSTALL_PATH.'logs';
// try to open specific log file for writing
$logfile = $CONFIG['log_dir'].'/'.$name;
if ($fp = @fopen($logfile, 'a')) {
fwrite($fp, $line);
fflush($fp);
fclose($fp);
return true;
}
else
trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
}
return false;
}
/**
* Write login data (name, ID, IP address) to the 'userlogins' log file.
*
* @return void
*/
function rcmail_log_login()
{
global $RCMAIL;
if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
return;
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()));
}
/**
* Returns remote IP address and forwarded addresses if found
*
* @return string Remote IP address(es)
*/
function rcmail_remote_ip()
{
$address = $_SERVER['REMOTE_ADDR'];
// append the NGINX X-Real-IP header, if set
if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
$remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
}
// append the X-Forwarded-For header, if set
if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
}
if (!empty($remote_ip))
$address .= '(' . implode(',', $remote_ip) . ')';
return $address;
}
/**
* Check whether the HTTP referer matches the current request
*
* @return boolean True if referer is the same host+path, false if not
*/
function rcube_check_referer()
{
$uri = parse_url($_SERVER['REQUEST_URI']);
$referer = parse_url(rc_request_header('Referer'));
return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
}
/**
* @access private
* @return mixed
*/
function rcube_timer()
{
return microtime(true);
}
/**
* @access private
* @return void
*/
function rcube_print_time($timer, $label='Timer', $dest='console')
{
static $print_count = 0;
$print_count++;
$now = rcube_timer();
$diff = $now-$timer;
if (empty($label))
$label = 'Timer '.$print_count;
write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
}
/**
* Throw system error and show error page
*
* @param array Named parameters
* - code: Error code (required)
* - type: Error type [php|db|imap|javascript] (required)
* - message: Error message
* - file: File where error occured
* - line: Line where error occured
* @param boolean True to log the error
* @param boolean Terminate script execution
*/
// may be defined in Installer
if (!function_exists('raise_error')) {
function raise_error($arg=array(), $log=false, $terminate=false)
{
global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
// report bug (if not incompatible browser)
if ($log && $arg['type'] && $arg['message'])
rcube_log_bug($arg);
// display error page and terminate script
if ($terminate) {
$ERROR_CODE = $arg['code'];
$ERROR_MESSAGE = $arg['message'];
include INSTALL_PATH . 'program/steps/utils/error.inc';
exit;
}
}
}
/**
* Report error according to configured debug_level
*
* @param array Named parameters
* @return void
* @see raise_error()
*/
function rcube_log_bug($arg_arr)
{
global $CONFIG;
$program = strtoupper($arg_arr['type']);
$level = $CONFIG['debug_level'];
// disable errors for ajax requests, write to log instead (#1487831)
if (($level & 4) && !empty($_REQUEST['_remote'])) {
$level = ($level ^ 4) | 1;
}
// write error to local log file
if ($level & 1) {
$post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
$log_entry = sprintf("%s Error: %s%s (%s %s)",
$program,
$arg_arr['message'],
$arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
$_SERVER['REQUEST_METHOD'],
$_SERVER['REQUEST_URI'] . $post_query);
if (!write_log('errors', $log_entry)) {
// send error to PHPs error handler if write_log didn't succeed
trigger_error($arg_arr['message']);
}
}
// report the bug to the global bug reporting system
if ($level & 2) {
// TODO: Send error via HTTP
}
// show error if debug_mode is on
if ($level & 4) {
print "<b>$program Error";
if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
print " in $arg_arr[file] ($arg_arr[line])";
print ':</b> ';
print nl2br($arg_arr['message']);
print '<br />';
flush();
}
}
function rcube_upload_progress()
{
global $RCMAIL;
$prefix = ini_get('apc.rfc1867_prefix');
$params = array(
'action' => $RCMAIL->action,
'name' => get_input_value('_progress', RCUBE_INPUT_GET),
);
if (function_exists('apc_fetch')) {
$status = apc_fetch($prefix . $params['name']);
if (!empty($status)) {
$status['percent'] = round($status['current']/$status['total']*100);
$params = array_merge($status, $params);
}
}
if (isset($params['percent']))
$params['text'] = rcube_label(array('name' => 'uploadprogress', 'vars' => array(
'percent' => $params['percent'] . '%',
'current' => show_bytes($params['current']),
'total' => show_bytes($params['total'])
)));
$RCMAIL->output->command('upload_progress_update', $params);
$RCMAIL->output->send();
}
function rcube_upload_init()
{
global $RCMAIL;
// Enable upload progress bar
if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
if ($field_name = ini_get('apc.rfc1867_name')) {
$RCMAIL->output->set_env('upload_progress_name', $field_name);
$RCMAIL->output->set_env('upload_progress_time', (int) $seconds);
}
}
// find max filesize value
$max_filesize = parse_bytes(ini_get('upload_max_filesize'));
$max_postsize = parse_bytes(ini_get('post_max_size'));
if ($max_postsize && $max_postsize < $max_filesize)
$max_filesize = $max_postsize;
$RCMAIL->output->set_env('max_filesize', $max_filesize);
$max_filesize = show_bytes($max_filesize);
$RCMAIL->output->set_env('filesizeerror', rcube_label(array(
'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
return $max_filesize;
}
/**
* Initializes client-side autocompletion
*/
function rcube_autocomplete_init()
{
global $RCMAIL;
static $init;
if ($init)
return;
$init = 1;
if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) {
$book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql');
if (count($book_types) > 1) {
$RCMAIL->output->set_env('autocomplete_threads', $threads);
$RCMAIL->output->set_env('autocomplete_sources', $book_types);
}
}
$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', 'autocompletemore');
}
function rcube_fontdefs($font = null)
{
$fonts = array(
'Andale Mono' => '"Andale Mono",Times,monospace',
'Arial' => 'Arial,Helvetica,sans-serif',
'Arial Black' => '"Arial Black","Avant Garde",sans-serif',
'Book Antiqua' => '"Book Antiqua",Palatino,serif',
'Courier New' => '"Courier New",Courier,monospace',
'Georgia' => 'Georgia,Palatino,serif',
'Helvetica' => 'Helvetica,Arial,sans-serif',
'Impact' => 'Impact,Chicago,sans-serif',
'Tahoma' => 'Tahoma,Arial,Helvetica,sans-serif',
'Terminal' => 'Terminal,Monaco,monospace',
'Times New Roman' => '"Times New Roman",Times,serif',
'Trebuchet MS' => '"Trebuchet MS",Geneva,sans-serif',
'Verdana' => 'Verdana,Geneva,sans-serif',
);
if ($font)
return $fonts[$font];
return $fonts;
}
diff --git a/program/lib/washtml.php b/program/lib/washtml.php
index 9c8625f30..8bbc136e1 100644
--- a/program/lib/washtml.php
+++ b/program/lib/washtml.php
@@ -1,306 +1,314 @@
<?php
/* Washtml, a HTML sanityzer.
*
* Copyright (c) 2007 Frederic Motte <fmotte@ubixis.com>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
*
* THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
* IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
* IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
* THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
/* Please send me your comments about this code if you have some, thanks, Fred. */
/* OVERVIEW:
*
* Wahstml take an untrusted HTML and return a safe html string.
*
* SYNOPSIS:
*
* $washer = new washtml($config);
* $washer->wash($html);
* It return a sanityzed string of the $html parameter without html and head tags.
* $html is a string containing the html code to wash.
* $config is an array containing options:
* $config['allow_remote'] is a boolean to allow link to remote images.
* $config['blocked_src'] string with image-src to be used for blocked remote images
* $config['show_washed'] is a boolean to include washed out attributes as x-washed
* $config['cid_map'] is an array where cid urls index urls to replace them.
* $config['charset'] is a string containing the charset of the HTML document if it is not defined in it.
* $washer->extlinks is a reference to a boolean that is set to true if remote images were removed. (FE: show remote images link)
*
* INTERNALS:
*
* Only tags and attributes in the static lists $html_elements and $html_attributes
* are kept, inline styles are also filtered: all style identifiers matching
* /[a-z\-]/i are allowed. Values matching colors, sizes, /[a-z\-]/i and safe
* urls if allowed and cid urls if mapped are kept.
*
* BUGS: It MUST be safe !
* - Check regexp
* - urlencode URLs instead of htmlspecials
* - Check is a 3 bytes utf8 first char can eat '">'
* - Update PCRE: CVE-2007-1659 - CVE-2007-1660 - CVE-2007-1661 - CVE-2007-1662
* CVE-2007-4766 - CVE-2007-4767 - CVE-2007-4768
* http://lists.debian.org/debian-security-announce/debian-security-announce-2007/msg00177.html
* - ...
*
* MISSING:
* - relative links, can be implemented by prefixing an absolute path, ask me
* if you need it...
* - ...
*
* Dont be a fool:
* - Dont alter data on a GET: '<img src="http://yourhost/mail?action=delete&uid=3267" />'
* - ...
*
* Roundcube Changes:
* - added $block_elements
* - changed $ignore_elements behaviour
* - added RFC2397 support
* - base URL support
* - invalid HTML comments removal before parsing
*/
class washtml
{
/* Allowed HTML elements (default) */
static $html_elements = array('a', 'abbr', 'acronym', 'address', 'area', 'b',
'basefont', 'bdo', 'big', 'blockquote', 'br', 'caption', 'center',
'cite', 'code', 'col', 'colgroup', 'dd', 'del', 'dfn', 'dir', 'div', 'dl',
'dt', 'em', 'fieldset', 'font', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'hr', 'i',
'ins', 'label', 'legend', 'li', 'map', 'menu', 'nobr', 'ol', 'p', 'pre', 'q',
's', 'samp', 'small', 'span', 'strike', 'strong', 'sub', 'sup', 'table',
'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'tt', 'u', 'ul', 'var', 'wbr', 'img',
// form elements
'button', 'input', 'textarea', 'select', 'option', 'optgroup'
);
/* Ignore these HTML tags and their content */
static $ignore_elements = array('script', 'applet', 'embed', 'object', 'style');
/* Allowed HTML attributes */
static $html_attribs = array('name', 'class', 'title', 'alt', 'width', 'height',
'align', 'nowrap', 'col', 'row', 'id', 'rowspan', 'colspan', 'cellspacing',
'cellpadding', 'valign', 'bgcolor', 'color', 'border', 'bordercolorlight',
'bordercolordark', 'face', 'marginwidth', 'marginheight', 'axis', 'border',
'abbr', 'char', 'charoff', 'clear', 'compact', 'coords', 'vspace', 'hspace',
'cellborder', 'size', 'lang', 'dir',
// attributes of form elements
'type', 'rows', 'cols', 'disabled', 'readonly', 'checked', 'multiple', 'value'
);
/* Block elements which could be empty but cannot be returned in short form (<tag />) */
static $block_elements = array('div', 'p', 'pre', 'blockquote', 'a', 'font', 'center',
'table', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ol', 'dl', 'strong', 'i', 'b', 'u');
/* State for linked objects in HTML */
public $extlinks = false;
/* Current settings */
private $config = array();
/* Registered callback functions for tags */
private $handlers = array();
/* Allowed HTML elements */
private $_html_elements = array();
/* Ignore these HTML tags but process their content */
private $_ignore_elements = array();
/* Block elements which could be empty but cannot be returned in short form (<tag />) */
private $_block_elements = array();
/* Allowed HTML attributes */
private $_html_attribs = array();
/* Constructor */
public function __construct($p = array()) {
$this->_html_elements = array_flip((array)$p['html_elements']) + array_flip(self::$html_elements) ;
$this->_html_attribs = array_flip((array)$p['html_attribs']) + array_flip(self::$html_attribs);
$this->_ignore_elements = array_flip((array)$p['ignore_elements']) + array_flip(self::$ignore_elements);
$this->_block_elements = array_flip((array)$p['block_elements']) + array_flip(self::$block_elements);
unset($p['html_elements'], $p['html_attribs'], $p['ignore_elements'], $p['block_elements']);
$this->config = $p + array('show_washed'=>true, 'allow_remote'=>false, 'cid_map'=>array());
}
/* Register a callback function for a certain tag */
public function add_callback($tagName, $callback)
{
$this->handlers[$tagName] = $callback;
}
/* Check CSS style */
private function wash_style($style) {
$s = '';
foreach (explode(';', $style) as $declaration) {
if (preg_match('/^\s*([a-z\-]+)\s*:\s*(.*)\s*$/i', $declaration, $match)) {
$cssid = $match[1];
$str = $match[2];
$value = '';
while (sizeof($str) > 0 &&
preg_match('/^(url\(\s*[\'"]?([^\'"\)]*)[\'"]?\s*\)'./*1,2*/
'|rgb\(\s*[0-9]+\s*,\s*[0-9]+\s*,\s*[0-9]+\s*\)'.
'|-?[0-9.]+\s*(em|ex|px|cm|mm|in|pt|pc|deg|rad|grad|ms|s|hz|khz|%)?'.
'|#[0-9a-f]{3,6}|[a-z0-9", -]+'.
')\s*/i', $str, $match)) {
if ($match[2]) {
if (($src = $this->config['cid_map'][$match[2]])
|| ($src = $this->config['cid_map'][$this->config['base_url'].$match[2]])) {
$value .= ' url('.htmlspecialchars($src, ENT_QUOTES) . ')';
}
else if (preg_match('/^(http|https|ftp):.*$/i', $match[2], $url)) {
if ($this->config['allow_remote'])
$value .= ' url('.htmlspecialchars($url[0], ENT_QUOTES).')';
else
$this->extlinks = true;
}
else if (preg_match('/^data:.+/i', $match[2])) { // RFC2397
$value .= ' url('.htmlspecialchars($match[2], ENT_QUOTES).')';
}
}
else if ($match[0] != 'url' && $match[0] != 'rgb') //whitelist ?
$value .= ' ' . $match[0];
$str = substr($str, strlen($match[0]));
}
if ($value)
$s .= ($s?' ':'') . $cssid . ':' . $value . ';';
}
}
return $s;
}
/* Take a node and return allowed attributes and check values */
private function wash_attribs($node) {
$t = '';
$washed;
foreach ($node->attributes as $key => $plop) {
$key = strtolower($key);
$value = $node->getAttribute($key);
if (isset($this->_html_attribs[$key]) ||
($key == 'href' && preg_match('/^(http:|https:|ftp:|mailto:|#).+/i', $value)))
$t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
else if ($key == 'style' && ($style = $this->wash_style($value))) {
$quot = strpos($style, '"') !== false ? "'" : '"';
$t .= ' style=' . $quot . $style . $quot;
}
else if ($key == 'background' || ($key == 'src' && strtolower($node->tagName) == 'img')) { //check tagName anyway
if (($src = $this->config['cid_map'][$value])
|| ($src = $this->config['cid_map'][$this->config['base_url'].$value])) {
$t .= ' ' . $key . '="' . htmlspecialchars($src, ENT_QUOTES) . '"';
}
else if (preg_match('/^(http|https|ftp):.+/i', $value)) {
if ($this->config['allow_remote'])
$t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
else {
$this->extlinks = true;
if ($this->config['blocked_src'])
$t .= ' ' . $key . '="' . htmlspecialchars($this->config['blocked_src'], ENT_QUOTES) . '"';
}
}
else if (preg_match('/^data:.+/i', $value)) { // RFC2397
$t .= ' ' . $key . '="' . htmlspecialchars($value, ENT_QUOTES) . '"';
}
} else
$washed .= ($washed?' ':'') . $key;
}
return $t . ($washed && $this->config['show_washed']?' x-washed="'.$washed.'"':'');
}
/* The main loop that recurse on a node tree.
* It output only allowed tags with allowed attributes
* and allowed inline styles */
private function dumpHtml($node) {
if(!$node->hasChildNodes())
return '';
$node = $node->firstChild;
$dump = '';
do {
switch($node->nodeType) {
case XML_ELEMENT_NODE: //Check element
$tagName = strtolower($node->tagName);
if ($callback = $this->handlers[$tagName]) {
- $dump .= call_user_func($callback, $tagName, $this->wash_attribs($node), $this->dumpHtml($node));
+ $dump .= call_user_func($callback, $tagName, $this->wash_attribs($node), $this->dumpHtml($node), $this);
}
else if (isset($this->_html_elements[$tagName])) {
$content = $this->dumpHtml($node);
$dump .= '<' . $tagName . $this->wash_attribs($node) .
// create closing tag for block elements, but also for elements
// with content or with some attributes (eg. style, class) (#1486812)
($content != '' || $node->hasAttributes() || isset($this->_block_elements[$tagName]) ? ">$content</$tagName>" : ' />');
}
else if (isset($this->_ignore_elements[$tagName])) {
$dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' not allowed -->';
}
else {
$dump .= '<!-- ' . htmlspecialchars($tagName, ENT_QUOTES) . ' ignored -->';
$dump .= $this->dumpHtml($node); // ignore tags not its content
}
break;
case XML_CDATA_SECTION_NODE:
$dump .= $node->nodeValue;
break;
case XML_TEXT_NODE:
$dump .= htmlspecialchars($node->nodeValue);
break;
case XML_HTML_DOCUMENT_NODE:
$dump .= $this->dumpHtml($node);
break;
case XML_DOCUMENT_TYPE_NODE:
break;
default:
$dump . '<!-- node type ' . $node->nodeType . ' -->';
}
} while($node = $node->nextSibling);
return $dump;
}
/* Main function, give it untrusted HTML, tell it if you allow loading
* remote images and give it a map to convert "cid:" urls. */
public function wash($html)
{
// Charset seems to be ignored (probably if defined in the HTML document)
$node = new DOMDocument('1.0', $this->config['charset']);
$this->extlinks = false;
// Find base URL for images
if (preg_match('/<base\s+href=[\'"]*([^\'"]+)/is', $html, $matches))
$this->config['base_url'] = $matches[1];
else
$this->config['base_url'] = '';
// Remove invalid HTML comments (#1487759)
// Don't remove valid conditional comments
$html = preg_replace('/<!--[^->[\n]*>/', '', $html);
@$node->loadHTML($html);
return $this->dumpHtml($node);
}
+ /**
+ * Getter for config parameters
+ */
+ public function get_config($prop)
+ {
+ return $this->config[$prop];
+ }
+
}
?>
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 1063c8ddf..62474f16a 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1,1618 +1,1621 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/func.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2010, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Provide webmail functionality and GUI objects |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
// setup some global vars used by mail steps
$SENT_MBOX = $RCMAIL->config->get('sent_mbox');
$DRAFTS_MBOX = $RCMAIL->config->get('drafts_mbox');
$SEARCH_MODS_DEFAULT = array(
'*' => array('subject'=>1, 'from'=>1),
$SENT_MBOX => array('subject'=>1, 'to'=>1),
$DRAFTS_MBOX => array('subject'=>1, 'to'=>1)
);
// actions that do not require imap connection here
$NOIMAP_ACTIONS = array('addcontact', 'autocomplete', 'upload', 'display-attachment', 'remove-attachment', 'get');
// always instantiate imap object (but not yet connect to server)
$RCMAIL->imap_init();
// log in to imap server
if (!in_array($RCMAIL->action, $NOIMAP_ACTIONS) && !$RCMAIL->imap_connect()) {
$RCMAIL->kill_session();
if ($OUTPUT->ajax_call)
$OUTPUT->redirect(array(), 2000);
$OUTPUT->set_env('task', 'login');
$OUTPUT->send('login');
}
// set imap properties and session vars
if (strlen(trim($mbox = get_input_value('_mbox', RCUBE_INPUT_GPC, true))))
$RCMAIL->imap->set_mailbox(($_SESSION['mbox'] = $mbox));
else if ($RCMAIL->imap)
$_SESSION['mbox'] = $RCMAIL->imap->get_mailbox_name();
if (!empty($_GET['_page']))
$RCMAIL->imap->set_page(($_SESSION['page'] = intval($_GET['_page'])));
// set default sort col/order to session
if (!isset($_SESSION['sort_col']))
$_SESSION['sort_col'] = !empty($CONFIG['message_sort_col']) ? $CONFIG['message_sort_col'] : '';
if (!isset($_SESSION['sort_order']))
$_SESSION['sort_order'] = strtoupper($CONFIG['message_sort_order']) == 'ASC' ? 'ASC' : 'DESC';
// set threads mode
$a_threading = $RCMAIL->config->get('message_threading', array());
if (isset($_GET['_threads'])) {
if ($_GET['_threads'])
$a_threading[$_SESSION['mbox']] = true;
else
unset($a_threading[$_SESSION['mbox']]);
$RCMAIL->user->save_prefs(array('message_threading' => $a_threading));
}
$RCMAIL->imap->set_threading($a_threading[$_SESSION['mbox']]);
// set message set for search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'])
&& $_SESSION['search_request'] == $_REQUEST['_search']
) {
$RCMAIL->imap->set_search_set($_SESSION['search']);
$OUTPUT->set_env('search_request', $_REQUEST['_search']);
$OUTPUT->set_env('search_text', $_SESSION['last_text_search']);
}
// set main env variables, labels and page title
if (empty($RCMAIL->action) || $RCMAIL->action == 'list') {
$mbox_name = $RCMAIL->imap->get_mailbox_name();
if (empty($RCMAIL->action)) {
// initialize searching result if search_filter is used
if ($_SESSION['search_filter'] && $_SESSION['search_filter'] != 'ALL') {
$search_request = md5($mbox_name.$_SESSION['search_filter']);
$RCMAIL->imap->search($mbox_name, $_SESSION['search_filter'], RCMAIL_CHARSET, $_SESSION['sort_col']);
$_SESSION['search'] = $RCMAIL->imap->get_search_set();
$_SESSION['search_request'] = $search_request;
$OUTPUT->set_env('search_request', $search_request);
}
$search_mods = $RCMAIL->config->get('search_mods', $SEARCH_MODS_DEFAULT);
$OUTPUT->set_env('search_mods', $search_mods);
}
// set current mailbox and some other vars in client environment
$OUTPUT->set_env('mailbox', $mbox_name);
$OUTPUT->set_env('pagesize', $RCMAIL->imap->page_size);
$OUTPUT->set_env('quota', $RCMAIL->imap->get_capability('QUOTA'));
$OUTPUT->set_env('delimiter', $RCMAIL->imap->get_hierarchy_delimiter());
$OUTPUT->set_env('threading', (bool) $RCMAIL->imap->threading);
$OUTPUT->set_env('threads', $RCMAIL->imap->threading || $RCMAIL->imap->get_capability('THREAD'));
$OUTPUT->set_env('preview_pane_mark_read', $RCMAIL->config->get('preview_pane_mark_read', 0));
if ($CONFIG['flag_for_deletion'])
$OUTPUT->set_env('flag_for_deletion', true);
if ($CONFIG['read_when_deleted'])
$OUTPUT->set_env('read_when_deleted', true);
if ($CONFIG['skip_deleted'])
$OUTPUT->set_env('skip_deleted', true);
if ($CONFIG['display_next'])
$OUTPUT->set_env('display_next', true);
if ($CONFIG['forward_attachment'])
$OUTPUT->set_env('forward_attachment', true);
if ($CONFIG['trash_mbox'])
$OUTPUT->set_env('trash_mailbox', $CONFIG['trash_mbox']);
if ($CONFIG['drafts_mbox'])
$OUTPUT->set_env('drafts_mailbox', $CONFIG['drafts_mbox']);
if ($CONFIG['junk_mbox'])
$OUTPUT->set_env('junk_mailbox', $CONFIG['junk_mbox']);
if (!$OUTPUT->ajax_call)
$OUTPUT->add_label('checkingmail', 'deletemessage', 'movemessagetotrash',
'movingmessage', 'copyingmessage', 'deletingmessage', 'markingmessage',
'copy', 'move', 'quota');
$OUTPUT->set_pagetitle(rcmail_localize_foldername($RCMAIL->imap->mod_mailbox($mbox_name)));
}
/**
* return the message list as HTML table
*/
function rcmail_message_list($attrib)
{
global $RCMAIL, $CONFIG, $OUTPUT;
// add some labels to client
$OUTPUT->add_label('from', 'to');
// add id to message list table if not specified
if (!strlen($attrib['id']))
$attrib['id'] = 'rcubemessagelist';
// define list of cols to be displayed based on parameter or config
if (empty($attrib['columns'])) {
$a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
$OUTPUT->set_env('col_movable', !in_array('list_cols', (array)$CONFIG['dont_override']));
}
else {
$a_show_cols = preg_split('/[\s,;]+/', strip_quotes($attrib['columns']));
$attrib['columns'] = $a_show_cols;
}
// save some variables for use in ajax list
$_SESSION['list_attrib'] = $attrib;
$mbox = $RCMAIL->imap->get_mailbox_name();
$delim = $RCMAIL->imap->get_hierarchy_delimiter();
// show 'to' instead of 'from' in sent/draft messages
if ((strpos($mbox.$delim, $CONFIG['sent_mbox'].$delim)===0 || strpos($mbox.$delim, $CONFIG['drafts_mbox'].$delim)===0)
&& (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false)
$a_show_cols[$f] = 'to';
// make sure 'threads' and 'subject' columns are present
if (!in_array('subject', $a_show_cols))
array_unshift($a_show_cols, 'subject');
if (!in_array('threads', $a_show_cols))
array_unshift($a_show_cols, 'threads');
$skin_path = $_SESSION['skin_path'] = $CONFIG['skin_path'];
// set client env
$OUTPUT->add_gui_object('messagelist', $attrib['id']);
$OUTPUT->set_env('autoexpand_threads', intval($CONFIG['autoexpand_threads']));
$OUTPUT->set_env('sort_col', $_SESSION['sort_col']);
$OUTPUT->set_env('sort_order', $_SESSION['sort_order']);
$OUTPUT->set_env('messages', array());
$OUTPUT->set_env('coltypes', $a_show_cols);
$OUTPUT->include_script('list.js');
$thead = '';
foreach (rcmail_message_list_head($attrib, $a_show_cols) as $cell)
$thead .= html::tag('td', array('class' => $cell['className'], 'id' => $cell['id']), $cell['html']);
return html::tag('table',
$attrib,
html::tag('thead', null, html::tag('tr', null, $thead)) .
html::tag('tbody', null, ''),
array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
}
/**
* return javascript commands to add rows to the message list
*/
function rcmail_js_message_list($a_headers, $insert_top=FALSE, $a_show_cols=null)
{
global $CONFIG, $RCMAIL, $OUTPUT;
if (empty($a_show_cols)) {
if (!empty($_SESSION['list_attrib']['columns']))
$a_show_cols = $_SESSION['list_attrib']['columns'];
else
$a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
}
else {
if (!is_array($a_show_cols))
$a_show_cols = preg_split('/[\s,;]+/', strip_quotes($a_show_cols));
$head_replace = true;
}
$mbox = $RCMAIL->imap->get_mailbox_name();
$delim = $RCMAIL->imap->get_hierarchy_delimiter();
// make sure 'threads' and 'subject' columns are present
if (!in_array('subject', $a_show_cols))
array_unshift($a_show_cols, 'subject');
if (!in_array('threads', $a_show_cols))
array_unshift($a_show_cols, 'threads');
$_SESSION['list_attrib']['columns'] = $a_show_cols;
// show 'to' instead of 'from' in sent/draft messages
if ((strpos($mbox.$delim, $CONFIG['sent_mbox'].$delim)===0 || strpos($mbox.$delim, $CONFIG['drafts_mbox'].$delim)===0)
&& (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false)
$a_show_cols[$f] = 'to';
// Make sure there are no duplicated columns (#1486999)
$a_show_cols = array_unique($a_show_cols);
// Plugins may set header's list_cols/list_flags and other rcube_mail_header variables
// and list columns
$plugin = $RCMAIL->plugins->exec_hook('messages_list',
array('messages' => $a_headers, 'cols' => $a_show_cols));
$a_show_cols = $plugin['cols'];
$a_headers = $plugin['messages'];
$thead = $head_replace ? rcmail_message_list_head($_SESSION['list_attrib'], $a_show_cols) : NULL;
$OUTPUT->command('set_message_coltypes', $a_show_cols, $thead);
if (empty($a_headers))
return;
// remove 'threads', 'attachment', 'flag', 'status' columns, we don't need them here
foreach (array('threads', 'attachment', 'flag', 'status', 'priority') as $col) {
if (($key = array_search($col, $a_show_cols)) !== FALSE)
unset($a_show_cols[$key]);
}
// loop through message headers
foreach ($a_headers as $n => $header) {
if (empty($header))
continue;
$a_msg_cols = array();
$a_msg_flags = array();
$RCMAIL->imap->set_charset(!empty($header->charset) ? $header->charset : $CONFIG['default_charset']);
// format each col; similar as in rcmail_message_list()
foreach ($a_show_cols as $col) {
if (in_array($col, array('from', 'to', 'cc', 'replyto')))
$cont = Q(rcmail_address_string($header->$col, 3), 'show');
else if ($col=='subject') {
$cont = trim($RCMAIL->imap->decode_header($header->$col));
if (!$cont) $cont = rcube_label('nosubject');
$cont = Q($cont);
}
else if ($col=='size')
$cont = show_bytes($header->$col);
else if ($col=='date')
$cont = format_date($header->date);
else
$cont = Q($header->$col);
$a_msg_cols[$col] = $cont;
}
$a_msg_flags = array_change_key_case(array_map('intval', (array) $header->flags));
if ($header->depth)
$a_msg_flags['depth'] = $header->depth;
else if ($header->has_children)
$roots[] = $header->uid;
if ($header->parent_uid)
$a_msg_flags['parent_uid'] = $header->parent_uid;
if ($header->has_children)
$a_msg_flags['has_children'] = $header->has_children;
if ($header->unread_children)
$a_msg_flags['unread_children'] = $header->unread_children;
if ($header->others['list-post'])
$a_msg_flags['ml'] = 1;
if ($header->priority)
$a_msg_flags['prio'] = (int) $header->priority;
$a_msg_flags['ctype'] = Q($header->ctype);
$a_msg_flags['mbox'] = $mbox;
// merge with plugin result (Deprecated, use $header->flags)
if (!empty($header->list_flags) && is_array($header->list_flags))
$a_msg_flags = array_merge($a_msg_flags, $header->list_flags);
if (!empty($header->list_cols) && is_array($header->list_cols))
$a_msg_cols = array_merge($a_msg_cols, $header->list_cols);
$OUTPUT->command('add_message_row',
$header->uid,
$a_msg_cols,
$a_msg_flags,
$insert_top);
}
if ($RCMAIL->imap->threading) {
$OUTPUT->command('init_threads', (array) $roots, $mbox);
}
}
/*
* Creates <THEAD> for message list table
*/
function rcmail_message_list_head($attrib, $a_show_cols)
{
global $CONFIG;
$skin_path = $_SESSION['skin_path'];
$image_tag = html::img(array('src' => "%s%s", 'alt' => "%s"));
// check to see if we have some settings for sorting
$sort_col = $_SESSION['sort_col'];
$sort_order = $_SESSION['sort_order'];
// define sortable columns
$a_sort_cols = array('subject', 'date', 'from', 'to', 'size', 'cc');
if (!empty($attrib['optionsmenuicon'])) {
$onclick = 'return ' . JS_OBJECT_NAME . ".command('menu-open', 'messagelistmenu')";
if ($attrib['optionsmenuicon'] === true || $attrib['optionsmenuicon'] == 'true')
$list_menu = html::div(array('onclick' => $onclick, 'class' => 'listmenu',
'id' => 'listmenulink', 'title' => rcube_label('listoptions')));
else
$list_menu = html::a(array('href' => '#', 'onclick' => $onclick),
html::img(array('src' => $skin_path . $attrib['optionsmenuicon'],
'id' => 'listmenulink', 'title' => rcube_label('listoptions')))
);
}
else
$list_menu = '';
$cells = array();
foreach ($a_show_cols as $col) {
// get column name
switch ($col) {
case 'flag':
$col_name = '<span class="flagged"> </span>';
break;
case 'attachment':
case 'priority':
case 'status':
$col_name = '<span class="' . $col .'"> </span>';
break;
case 'threads':
$col_name = $list_menu;
break;
default:
$col_name = Q(rcube_label($col));
}
// make sort links
if (in_array($col, $a_sort_cols))
$col_name = html::a(array('href'=>"./#sort", 'onclick' => 'return '.JS_OBJECT_NAME.".command('sort','".$col."',this)", 'title' => rcube_label('sortby')), $col_name);
$sort_class = $col == $sort_col ? " sorted$sort_order" : '';
$class_name = $col.$sort_class;
// put it all together
$cells[] = array('className' => $class_name, 'id' => "rcm$col", 'html' => $col_name);
}
return $cells;
}
/**
* return an HTML iframe for loading mail content
*/
function rcmail_messagecontent_frame($attrib)
{
global $OUTPUT, $RCMAIL;
if (empty($attrib['id']))
$attrib['id'] = 'rcmailcontentwindow';
$attrib['name'] = $attrib['id'];
if ($RCMAIL->config->get('preview_pane'))
$OUTPUT->set_env('contentframe', $attrib['id']);
$OUTPUT->set_env('blankpage', $attrib['src'] ? $OUTPUT->abs_url($attrib['src']) : 'program/blank.gif');
return html::iframe($attrib);
}
function rcmail_messagecount_display($attrib)
{
global $RCMAIL;
if (!$attrib['id'])
$attrib['id'] = 'rcmcountdisplay';
$RCMAIL->output->add_gui_object('countdisplay', $attrib['id']);
$content = $RCMAIL->action != 'show' ? rcmail_get_messagecount_text() : rcube_label('loading');
return html::span($attrib, $content);
}
function rcmail_get_messagecount_text($count=NULL, $page=NULL)
{
global $RCMAIL;
if ($page===NULL)
$page = $RCMAIL->imap->list_page;
$start_msg = ($page-1) * $RCMAIL->imap->page_size + 1;
if ($count!==NULL)
$max = $count;
else if ($RCMAIL->action)
$max = $RCMAIL->imap->messagecount(NULL, $RCMAIL->imap->threading ? 'THREADS' : 'ALL');
if ($max==0)
$out = rcube_label('mailboxempty');
else
$out = rcube_label(array('name' => $RCMAIL->imap->threading ? 'threadsfromto' : 'messagesfromto',
'vars' => array('from' => $start_msg,
'to' => min($max, $start_msg + $RCMAIL->imap->page_size - 1),
'count' => $max)));
return Q($out);
}
function rcmail_mailbox_name_display($attrib)
{
global $RCMAIL;
if (!$attrib['id'])
$attrib['id'] = 'rcmmailboxname';
$RCMAIL->output->add_gui_object('mailboxname', $attrib['id']);
return html::span($attrib, rcmail_get_mailbox_name_text());
}
function rcmail_get_mailbox_name_text()
{
global $RCMAIL;
return rcmail_localize_foldername($RCMAIL->imap->get_mailbox_name());
}
function rcmail_send_unread_count($mbox_name, $force=false, $count=null, $mark='')
{
global $RCMAIL;
$old_unseen = rcmail_get_unseen_count($mbox_name);
if ($count === null)
$unseen = $RCMAIL->imap->messagecount($mbox_name, 'UNSEEN', $force);
else
$unseen = $count;
if ($unseen != $old_unseen || ($mbox_name == 'INBOX'))
$RCMAIL->output->command('set_unread_count', $mbox_name, $unseen,
($mbox_name == 'INBOX'), $unseen && $mark ? $mark : '');
rcmail_set_unseen_count($mbox_name, $unseen);
return $unseen;
}
function rcmail_set_unseen_count($mbox_name, $count)
{
// @TODO: this data is doubled (session and cache tables) if caching is enabled
// Make sure we have an array here (#1487066)
if (!is_array($_SESSION['unseen_count']))
$_SESSION['unseen_count'] = array();
$_SESSION['unseen_count'][$mbox_name] = $count;
}
function rcmail_get_unseen_count($mbox_name)
{
if (is_array($_SESSION['unseen_count']) && array_key_exists($mbox_name, $_SESSION['unseen_count']))
return $_SESSION['unseen_count'][$mbox_name];
else
return null;
}
/**
* Sets message is_safe flag according to 'show_images' option value
*
* @param object rcube_message Message
*/
function rcmail_check_safe(&$message)
{
global $RCMAIL;
$show_images = $RCMAIL->config->get('show_images');
if (!$message->is_safe
&& !empty($show_images)
&& $message->has_html_part())
{
switch($show_images) {
case '1': // known senders only
$CONTACTS = new rcube_contacts($RCMAIL->db, $_SESSION['user_id']);
if ($CONTACTS->search('email', $message->sender['mailto'], true, false)->count) {
$message->set_safe(true);
}
break;
case '2': // always
$message->set_safe(true);
break;
}
}
}
/**
* Cleans up the given message HTML Body (for displaying)
*
* @param string HTML
* @param array Display parameters
* @param array CID map replaces (inline images)
* @return string Clean HTML
*/
function rcmail_wash_html($html, $p, $cid_replaces)
{
global $REMOTE_OBJECTS;
$p += array('safe' => false, 'inline_html' => true);
// special replacements (not properly handled by washtml class)
$html_search = array(
'/(<\/nobr>)(\s+)(<nobr>)/i', // space(s) between <NOBR>
'/<title[^>]*>[^<]*<\/title>/i', // PHP bug #32547 workaround: remove title tag
'/^(\0\0\xFE\xFF|\xFF\xFE\0\0|\xFE\xFF|\xFF\xFE|\xEF\xBB\xBF)/', // byte-order mark (only outlook?)
'/<html\s[^>]+>/i', // washtml/DOMDocument cannot handle xml namespaces
);
$html_replace = array(
'\\1'.' '.'\\3',
'',
'',
'<html>',
);
$html = preg_replace($html_search, $html_replace, trim($html));
// PCRE errors handling (#1486856), should we use something like for every preg_* use?
if ($html === null && ($preg_error = preg_last_error()) != PREG_NO_ERROR) {
$errstr = "Could not clean up HTML message! PCRE Error: $preg_error.";
if ($preg_error == PREG_BACKTRACK_LIMIT_ERROR)
$errstr .= " Consider raising pcre.backtrack_limit!";
if ($preg_error == PREG_RECURSION_LIMIT_ERROR)
$errstr .= " Consider raising pcre.recursion_limit!";
raise_error(array('code' => 620, 'type' => 'php',
'line' => __LINE__, 'file' => __FILE__,
'message' => $errstr), true, false);
return '';
}
// fix (unknown/malformed) HTML tags before "wash"
$html = preg_replace_callback('/(<[\/]*)([^\s>]+)/', 'rcmail_html_tag_callback', $html);
// charset was converted to UTF-8 in rcube_imap::get_message_part(),
// change/add charset specification in HTML accordingly,
// washtml cannot work without that
$meta = '<meta http-equiv="Content-Type" content="text/html; charset='.RCMAIL_CHARSET.'" />';
// remove old meta tag and add the new one, making sure
// that it is placed in the head (#1488093)
$html = preg_replace('/<meta[^>]+charset=[a-z0-9-_]+[^>]*>/Ui', '', $html);
$html = preg_replace('/(<head[^>]*>)/Ui', '\\1'.$meta, $html, -1, $rcount);
if (!$rcount) {
$html = '<head>' . $meta . '</head>' . $html;
}
// turn relative into absolute urls
$html = rcmail_resolve_base($html);
// clean HTML with washhtml by Frederic Motte
$wash_opts = array(
'show_washed' => false,
'allow_remote' => $p['safe'],
'blocked_src' => "./program/blocked.gif",
'charset' => RCMAIL_CHARSET,
'cid_map' => $cid_replaces,
'html_elements' => array('body'),
);
if (!$p['inline_html']) {
$wash_opts['html_elements'] = array('html','head','title','body');
}
if ($p['safe']) {
$wash_opts['html_elements'][] = 'link';
$wash_opts['html_attribs'] = array('rel','type');
}
// overwrite washer options with options from plugins
if (isset($p['html_elements']))
$wash_opts['html_elements'] = $p['html_elements'];
if (isset($p['html_attribs']))
$wash_opts['html_attribs'] = $p['html_attribs'];
// initialize HTML washer
$washer = new washtml($wash_opts);
if (!$p['skip_washer_form_callback'])
$washer->add_callback('form', 'rcmail_washtml_callback');
// allow CSS styles, will be sanitized by rcmail_washtml_callback()
if (!$p['skip_washer_style_callback'])
$washer->add_callback('style', 'rcmail_washtml_callback');
// Remove non-UTF8 characters (#1487813)
$html = rc_utf8_clean($html);
$html = $washer->wash($html);
$REMOTE_OBJECTS = $washer->extlinks;
return $html;
}
/**
* Convert the given message part to proper HTML
* which can be displayed the message view
*
* @param object rcube_message_part Message part
* @param array Display parameters array
* @return string Formatted HTML string
*/
function rcmail_print_body($part, $p = array())
{
global $RCMAIL;
// trigger plugin hook
$data = $RCMAIL->plugins->exec_hook('message_part_before',
array('type' => $part->ctype_secondary, 'body' => $part->body, 'id' => $part->mime_id)
+ $p + array('safe' => false, 'plain' => false, 'inline_html' => true));
// convert html to text/plain
if ($data['type'] == 'html' && $data['plain']) {
$txt = new html2text($data['body'], false, true);
$body = $txt->get_text();
$part->ctype_secondary = 'plain';
}
// text/html
else if ($data['type'] == 'html') {
$body = rcmail_wash_html($data['body'], $data, $part->replaces);
$part->ctype_secondary = $data['type'];
}
// text/enriched
else if ($data['type'] == 'enriched') {
$part->ctype_secondary = 'html';
require_once(INSTALL_PATH . 'program/lib/enriched.inc');
$body = Q(enriched_to_html($data['body']), 'show');
}
else {
// assert plaintext
$body = $part->body;
$part->ctype_secondary = $data['type'] = 'plain';
}
// free some memory (hopefully)
unset($data['body']);
// plaintext postprocessing
if ($part->ctype_secondary == 'plain')
$body = rcmail_plain_body($body, $part->ctype_parameters['format'] == 'flowed');
// allow post-processing of the message body
$data = $RCMAIL->plugins->exec_hook('message_part_after',
array('type' => $part->ctype_secondary, 'body' => $body, 'id' => $part->mime_id) + $data);
return $data['type'] == 'html' ? $data['body'] : html::tag('pre', array(), $data['body']);
}
/**
* Handle links and citation marks in plain text message
*
* @param string Plain text string
* @param boolean Text uses format=flowed
*
* @return string Formatted HTML string
*/
function rcmail_plain_body($body, $flowed=false)
{
global $RCMAIL;
// make links and email-addresses clickable
$replacer = new rcube_string_replacer;
// search for patterns like links and e-mail addresses
$body = preg_replace_callback($replacer->link_pattern, array($replacer, 'link_callback'), $body);
$body = preg_replace_callback($replacer->mailto_pattern, array($replacer, 'mailto_callback'), $body);
// split body into single lines
$body = preg_split('/\r?\n/', $body);
$quote_level = 0;
$last = -1;
// find/mark quoted lines...
for ($n=0, $cnt=count($body); $n < $cnt; $n++) {
if ($body[$n][0] == '>' && preg_match('/^(>+\s*)+/', $body[$n], $regs)) {
$q = strlen(preg_replace('/\s/', '', $regs[0]));
$body[$n] = substr($body[$n], strlen($regs[0]));
if ($q > $quote_level) {
$body[$n] = $replacer->get_replacement($replacer->add(
str_repeat('<blockquote>', $q - $quote_level))) . $body[$n];
}
else if ($q < $quote_level) {
$body[$n] = $replacer->get_replacement($replacer->add(
str_repeat('</blockquote>', $quote_level - $q))) . $body[$n];
}
else if ($flowed) {
// previous line is flowed
if (isset($body[$last]) && $body[$n]
&& $body[$last][strlen($body[$last])-1] == ' ') {
// merge lines
$body[$last] .= $body[$n];
unset($body[$n]);
}
else {
$last = $n;
}
}
}
else {
$q = 0;
if ($flowed) {
// sig separator - line is fixed
if ($body[$n] == '-- ') {
$last = $last_sig = $n;
}
else {
// remove space-stuffing
if ($body[$n][0] == ' ')
$body[$n] = substr($body[$n], 1);
// previous line is flowed?
if (isset($body[$last]) && $body[$n]
&& $last !== $last_sig
&& $body[$last][strlen($body[$last])-1] == ' '
) {
$body[$last] .= $body[$n];
unset($body[$n]);
}
else {
$last = $n;
}
}
if ($quote_level > 0)
$body[$last] = $replacer->get_replacement($replacer->add(
str_repeat('</blockquote>', $quote_level))) . $body[$last];
}
else if ($quote_level > 0)
$body[$n] = $replacer->get_replacement($replacer->add(
str_repeat('</blockquote>', $quote_level))) . $body[$n];
}
$quote_level = $q;
}
$body = join("\n", $body);
// quote plain text (don't use Q() here, to display entities "as is")
$table = get_html_translation_table(HTML_SPECIALCHARS);
unset($table['?']);
$body = strtr($body, $table);
// colorize signature (up to <sig_max_lines> lines)
$len = strlen($body);
$sig_max_lines = $RCMAIL->config->get('sig_max_lines', 15);
while (($sp = strrpos($body, "-- \n", $sp ? -$len+$sp-1 : 0)) !== false) {
if ($sp == 0 || $body[$sp-1] == "\n") {
// do not touch blocks with more that X lines
if (substr_count($body, "\n", $sp) < $sig_max_lines)
$body = substr($body, 0, max(0, $sp))
.'<span class="sig">'.substr($body, $sp).'</span>';
break;
}
}
// insert url/mailto links and citation tags
$body = $replacer->resolve($body);
return $body;
}
/**
* Callback function for washtml cleaning class
*/
-function rcmail_washtml_callback($tagname, $attrib, $content)
+function rcmail_washtml_callback($tagname, $attrib, $content, $washtml)
{
switch ($tagname) {
case 'form':
$out = html::div('form', $content);
break;
case 'style':
// decode all escaped entities and reduce to ascii strings
$stripped = preg_replace('/[^a-zA-Z\(:;]/', '', rcmail_xss_entity_decode($content));
// now check for evil strings like expression, behavior or url()
- if (!preg_match('/expression|behavior|url\(|import[^a]/', $stripped)) {
- $out = html::tag('style', array('type' => 'text/css'), $content);
+ if (!preg_match('/expression|behavior/i', $stripped)) {
+ if (!$washtml->get_config('allow_remote') && preg_match('/url\(|import[^a]/i', $stripped))
+ $washtml->extlinks = true;
+ else
+ $out = html::tag('style', array('type' => 'text/css'), $content);
break;
}
default:
$out = '';
}
return $out;
}
/**
* Callback function for HTML tags fixing
*/
function rcmail_html_tag_callback($matches)
{
$tagname = $matches[2];
$tagname = preg_replace(array(
'/:.*$/', // Microsoft's Smart Tags <st1:xxxx>
'/[^a-z0-9_\[\]\!-]/i', // forbidden characters
), '', $tagname);
return $matches[1].$tagname;
}
/**
* return table with message headers
*/
function rcmail_message_headers($attrib, $headers=NULL)
{
global $OUTPUT, $MESSAGE, $PRINT_MODE, $RCMAIL;
static $sa_attrib;
// keep header table attrib
if (is_array($attrib) && !$sa_attrib)
$sa_attrib = $attrib;
else if (!is_array($attrib) && is_array($sa_attrib))
$attrib = $sa_attrib;
if (!isset($MESSAGE))
return FALSE;
// get associative array of headers object
if (!$headers)
$headers = is_object($MESSAGE->headers) ? get_object_vars($MESSAGE->headers) : $MESSAGE->headers;
// show these headers
$standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto',
'mail-reply-to', 'mail-followup-to', 'date');
$output_headers = array();
foreach ($standard_headers as $hkey) {
if ($headers[$hkey])
$value = $headers[$hkey];
else if ($headers['others'][$hkey])
$value = $headers['others'][$hkey];
else
continue;
if ($hkey == 'date') {
if ($PRINT_MODE)
$header_value = format_date($value, $RCMAIL->config->get('date_long', 'x'));
else
$header_value = format_date($value);
}
else if ($hkey == 'replyto') {
if ($headers['replyto'] != $headers['from'])
$header_value = rcmail_address_string($value, null, true, $attrib['addicon']);
else
continue;
}
else if ($hkey == 'mail-reply-to') {
if ($headers['mail-replyto'] != $headers['reply-to']
&& $headers['reply-to'] != $headers['from']
)
$header_value = rcmail_address_string($value, null, true, $attrib['addicon']);
else
continue;
}
else if ($hkey == 'mail-followup-to') {
$header_value = rcmail_address_string($value, null, true, $attrib['addicon']);
}
else if (in_array($hkey, array('from', 'to', 'cc', 'bcc')))
$header_value = rcmail_address_string($value, null, true, $attrib['addicon']);
else if ($hkey == 'subject' && empty($value))
$header_value = rcube_label('nosubject');
else
$header_value = trim($RCMAIL->imap->decode_header($value));
$output_headers[$hkey] = array(
'title' => rcube_label(preg_replace('/(^mail-|-)/', '', $hkey)),
'value' => $header_value, 'raw' => $value
);
}
$plugin = $RCMAIL->plugins->exec_hook('message_headers_output',
array('output' => $output_headers, 'headers' => $MESSAGE->headers));
// compose html table
$table = new html_table(array('cols' => 2));
foreach ($plugin['output'] as $hkey => $row) {
$table->add(array('class' => 'header-title'), Q($row['title']));
$table->add(array('class' => 'header '.$hkey), Q($row['value'], ($hkey == 'subject' ? 'strict' : 'show')));
}
return $table->show($attrib);
}
/**
* return block to show full message headers
*/
function rcmail_message_full_headers($attrib, $headers=NULL)
{
global $OUTPUT;
$html = html::div(array('class' => "more-headers show-headers", 'onclick' => "return ".JS_OBJECT_NAME.".command('load-headers','',this)"), '');
$html .= html::div(array('id' => "all-headers", 'class' => "all", 'style' => 'display:none'), html::div(array('id' => 'headers-source'), ''));
$OUTPUT->add_gui_object('all_headers_row', 'all-headers');
$OUTPUT->add_gui_object('all_headers_box', 'headers-source');
return html::div($attrib, $html);
}
/**
* Handler for the 'messagebody' GUI object
*
* @param array Named parameters
* @return string HTML content showing the message body
*/
function rcmail_message_body($attrib)
{
global $CONFIG, $OUTPUT, $MESSAGE, $RCMAIL, $REMOTE_OBJECTS;
if (!is_array($MESSAGE->parts) && empty($MESSAGE->body))
return '';
if (!$attrib['id'])
$attrib['id'] = 'rcmailMsgBody';
$safe_mode = $MESSAGE->is_safe || intval($_GET['_safe']);
$out = '';
$header_attrib = array();
foreach ($attrib as $attr => $value)
if (preg_match('/^headertable([a-z]+)$/i', $attr, $regs))
$header_attrib[$regs[1]] = $value;
if (!empty($MESSAGE->parts)) {
foreach ($MESSAGE->parts as $i => $part) {
if ($part->type == 'headers')
$out .= rcmail_message_headers(sizeof($header_attrib) ? $header_attrib : NULL, $part->headers);
else if ($part->type == 'content' && $part->size) {
// Check if we have enough memory to handle the message in it
// #1487424: we need up to 10x more memory than the body
if (!rcmail_mem_check($part->size * 10)) {
$out .= html::span('part-notice', rcube_label('messagetoobig'). ' '
. html::a('?_task=mail&_action=get&_download=1&_uid='.$MESSAGE->uid.'&_part='.$part->mime_id
.'&_mbox='. urlencode($RCMAIL->imap->get_mailbox_name()), rcube_label('download')));
continue;
}
if (empty($part->ctype_parameters) || empty($part->ctype_parameters['charset']))
$part->ctype_parameters['charset'] = $MESSAGE->headers->charset;
// fetch part if not available
if (!isset($part->body))
$part->body = $MESSAGE->get_part_content($part->mime_id);
// message is cached but not exists (#1485443), or other error
if ($part->body === false) {
rcmail_message_error($MESSAGE->uid);
}
$plugin = $RCMAIL->plugins->exec_hook('message_body_prefix', array(
'part' => $part, 'prefix' => ''));
$body = rcmail_print_body($part, array('safe' => $safe_mode, 'plain' => !$CONFIG['prefer_html']));
if ($part->ctype_secondary == 'html') {
- $body = rcmail_html4inline($body, $attrib['id'], 'rcmBody', $attrs);
+ $body = rcmail_html4inline($body, $attrib['id'], 'rcmBody', $attrs, $safe_mode);
$div_attr = array('class' => 'message-htmlpart');
$style = array();
if (!empty($attrs)) {
foreach ($attrs as $a_idx => $a_val)
$style[] = $a_idx . ': ' . $a_val;
if (!empty($style))
$div_attr['style'] = implode('; ', $style);
}
$out .= html::div($div_attr, $plugin['prefix'] . $body);
}
else
$out .= html::div('message-part', $plugin['prefix'] . $body);
}
}
}
else {
// Check if we have enough memory to handle the message in it
// #1487424: we need up to 10x more memory than the body
if (!rcmail_mem_check(strlen($MESSAGE->body) * 10)) {
$out .= html::span('part-notice', rcube_label('messagetoobig'). ' '
. html::a('?_task=mail&_action=get&_download=1&_uid='.$MESSAGE->uid.'&_part=0'
.'&_mbox='. urlencode($RCMAIL->imap->get_mailbox_name()), rcube_label('download')));
}
else {
$plugin = $RCMAIL->plugins->exec_hook('message_body_prefix', array(
'part' => $MESSAGE, 'prefix' => ''));
$out .= html::div('message-part', $plugin['prefix'] . html::tag('pre', array(),
rcmail_plain_body(Q($MESSAGE->body, 'strict', false))));
}
}
// list images after mail body
if ($CONFIG['inline_images'] && !empty($MESSAGE->attachments)) {
foreach ($MESSAGE->attachments as $attach_prop) {
// Content-Type: image/*...
if (preg_match('/^image\//i', $attach_prop->mimetype) ||
// ...or known file extension: many clients are using application/octet-stream
($attach_prop->filename &&
preg_match('/^application\/octet-stream$/i', $attach_prop->mimetype) &&
preg_match('/\.(jpg|jpeg|png|gif|bmp)$/i', $attach_prop->filename))
) {
$out .= html::tag('hr') . html::p(array('align' => "center"),
html::img(array(
'src' => $MESSAGE->get_part_url($attach_prop->mime_id, true),
'title' => $attach_prop->filename,
'alt' => $attach_prop->filename,
)));
}
}
}
// tell client that there are blocked remote objects
if ($REMOTE_OBJECTS && !$safe_mode)
$OUTPUT->set_env('blockedobjects', true);
return html::div($attrib, $out);
}
/**
* Convert all relative URLs according to a <base> in HTML
*/
function rcmail_resolve_base($body)
{
// check for <base href=...>
if (preg_match('!(<base.*href=["\']?)([hftps]{3,5}://[a-z0-9/.%-]+)!i', $body, $regs)) {
$replacer = new rcube_base_replacer($regs[2]);
// replace all relative paths
$body = preg_replace_callback('/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui', array($replacer, 'callback'), $body);
$body = preg_replace_callback('/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui', array($replacer, 'callback'), $body);
}
return $body;
}
/**
* modify a HTML message that it can be displayed inside a HTML page
*/
-function rcmail_html4inline($body, $container_id, $body_id='', &$attributes=null)
+function rcmail_html4inline($body, $container_id, $body_id='', &$attributes=null, $allow_remote=false)
{
$last_style_pos = 0;
$cont_id = $container_id.($body_id ? ' div.'.$body_id : '');
// find STYLE tags
while (($pos = stripos($body, '<style', $last_style_pos)) && ($pos2 = stripos($body, '</style>', $pos)))
{
$pos = strpos($body, '>', $pos)+1;
// replace all css definitions with #container [def]
$styles = rcmail_mod_css_styles(
- substr($body, $pos, $pos2-$pos), $cont_id);
+ substr($body, $pos, $pos2-$pos), $cont_id, $allow_remote);
$body = substr_replace($body, $styles, $pos, $pos2-$pos);
$last_style_pos = $pos2;
}
// modify HTML links to open a new window if clicked
$GLOBALS['rcmail_html_container_id'] = $container_id;
$body = preg_replace_callback('/<(a|link)\s+([^>]+)>/Ui', 'rcmail_alter_html_link', $body);
unset($GLOBALS['rcmail_html_container_id']);
$body = preg_replace(array(
// add comments arround html and other tags
'/(<!DOCTYPE[^>]*>)/i',
'/(<\?xml[^>]*>)/i',
'/(<\/?html[^>]*>)/i',
'/(<\/?head[^>]*>)/i',
'/(<title[^>]*>.*<\/title>)/Ui',
'/(<\/?meta[^>]*>)/i',
// quote <? of php and xml files that are specified as text/html
'/<\?/',
'/\?>/',
// replace <body> with <div>
'/<body([^>]*)>/i',
'/<\/body>/i',
),
array(
'<!--\\1-->',
'<!--\\1-->',
'<!--\\1-->',
'<!--\\1-->',
'<!--\\1-->',
'<!--\\1-->',
'<?',
'?>',
'<div class="'.$body_id.'"\\1>',
'</div>',
),
$body);
$attributes = array();
// Handle body attributes that doesn't play nicely with div elements
$regexp = '/<div class="' . preg_quote($body_id, '/') . '"([^>]*)/';
if (preg_match($regexp, $body, $m)) {
$attrs = $m[0];
// Get bgcolor, we'll set it as background-color of the message container
if ($m[1] && preg_match('/bgcolor=["\']*([a-z0-9#]+)["\']*/', $attrs, $mb)) {
$attributes['background-color'] = $mb[1];
$attrs = preg_replace('/bgcolor=["\']*([a-z0-9#]+)["\']*/', '', $attrs);
}
// Get background, we'll set it as background-image of the message container
if ($m[1] && preg_match('/background=["\']*([^"\'>\s]+)["\']*/', $attrs, $mb)) {
$attributes['background-image'] = 'url('.$mb[1].')';
$attrs = preg_replace('/background=["\']*([^"\'>\s]+)["\']*/', '', $attrs);
}
if (!empty($attributes)) {
$body = preg_replace($regexp, rtrim($attrs), $body, 1);
}
// handle body styles related to background image
if ($attributes['background-image']) {
// get body style
if (preg_match('/#'.preg_quote($cont_id, '/').'\s+\{([^}]+)}/i', $body, $m)) {
// get background related style
if (preg_match_all('/(background-position|background-repeat)\s*:\s*([^;]+);/i', $m[1], $ma, PREG_SET_ORDER)) {
foreach ($ma as $style)
$attributes[$style[1]] = $style[2];
}
}
}
}
// make sure there's 'rcmBody' div, we need it for proper css modification
// its name is hardcoded in rcmail_message_body() also
else {
$body = '<div class="' . $body_id . '">' . $body . '</div>';
}
return $body;
}
/**
* parse link attributes and set correct target
*/
function rcmail_alter_html_link($matches)
{
global $RCMAIL;
// Support unicode/punycode in top-level domain part
$EMAIL_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))';
$tag = $matches[1];
$attrib = parse_attrib_string($matches[2]);
$end = '>';
// Remove non-printable characters in URL (#1487805)
$attrib['href'] = preg_replace('/[\x00-\x1F]/', '', $attrib['href']);
if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href'])) {
$tempurl = 'tmp-' . md5($attrib['href']) . '.css';
$_SESSION['modcssurls'][$tempurl] = $attrib['href'];
$attrib['href'] = $RCMAIL->url(array('task' => 'utils', 'action' => 'modcss', 'u' => $tempurl, 'c' => $GLOBALS['rcmail_html_container_id']));
$end = ' />';
}
else if (preg_match('/^mailto:'.$EMAIL_PATTERN.'(\?[^"\'>]+)?/i', $attrib['href'], $mailto)) {
$attrib['href'] = $mailto[0];
$attrib['onclick'] = sprintf(
"return %s.command('compose','%s',this)",
JS_OBJECT_NAME,
JQ($mailto[1].$mailto[3]));
}
else if (!empty($attrib['href']) && $attrib['href'][0] != '#') {
$attrib['target'] = '_blank';
}
return "<$tag" . html::attrib_string($attrib, array('href','name','target','onclick','id','class','style','title','rel','type','media')) . $end;
}
/**
* decode address string and re-format it as HTML links
*/
function rcmail_address_string($input, $max=null, $linked=false, $addicon=null)
{
global $RCMAIL, $PRINT_MODE, $CONFIG;
$a_parts = $RCMAIL->imap->decode_address_list($input);
if (!sizeof($a_parts))
return $input;
$c = count($a_parts);
$j = 0;
$out = '';
if ($addicon && !isset($_SESSION['writeable_abook'])) {
$_SESSION['writeable_abook'] = $RCMAIL->get_address_sources(true) ? true : false;
}
foreach ($a_parts as $part) {
$j++;
$name = $part['name'];
$mailto = $part['mailto'];
$string = $part['string'];
// IDNA ASCII to Unicode
if ($name == $mailto)
$name = rcube_idn_to_utf8($name);
if ($string == $mailto)
$string = rcube_idn_to_utf8($string);
$mailto = rcube_idn_to_utf8($mailto);
if ($PRINT_MODE) {
$out .= sprintf('%s <%s>', Q($name), $mailto);
}
else if (check_email($part['mailto'], false)) {
if ($linked) {
$address = html::a(array(
'href' => 'mailto:'.$mailto,
'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($mailto)),
'title' => $mailto,
'class' => "rcmContactAddress",
),
Q($name ? $name : $mailto));
}
else {
$address = html::span(array('title' => $mailto, 'class' => "rcmContactAddress"),
Q($name ? $name : $mailto));
}
if ($addicon && $_SESSION['writeable_abook']) {
$address = html::span(null, $address . html::a(array(
'href' => "#add",
'onclick' => sprintf("return %s.command('add-contact','%s',this)", JS_OBJECT_NAME, urlencode($string)),
'title' => rcube_label('addtoaddressbook'),
),
html::img(array(
'src' => $CONFIG['skin_path'] . $addicon,
'alt' => "Add contact",
))));
}
$out .= $address;
}
else {
if ($name)
$out .= Q($name);
if ($mailto)
$out .= (strlen($out) ? ' ' : '') . sprintf('<%s>', Q($mailto));
}
if ($c>$j)
$out .= ','.($max ? ' ' : ' ');
if ($max && $j==$max && $c>$j) {
$out .= '...';
break;
}
}
return $out;
}
/**
* Wrap text to a given number of characters per line
* but respect the mail quotation of replies messages (>).
* Finally add another quotation level by prpending the lines
* with >
*
* @param string Text to wrap
* @param int The line width
* @return string The wrapped text
*/
function rcmail_wrap_and_quote($text, $length = 72)
{
// Rebuild the message body with a maximum of $max chars, while keeping quoted message.
$max = min(77, $length + 8);
$lines = preg_split('/\r?\n/', trim($text));
$out = '';
foreach ($lines as $line) {
// don't wrap already quoted lines
if ($line[0] == '>')
$line = '>' . rtrim($line);
else if (mb_strlen($line) > $max) {
$newline = '';
foreach(explode("\n", rc_wordwrap($line, $length - 2)) as $l) {
if (strlen($l))
$newline .= '> ' . $l . "\n";
else
$newline .= ">\n";
}
$line = rtrim($newline);
}
else
$line = '> ' . $line;
// Append the line
$out .= $line . "\n";
}
return $out;
}
function rcmail_draftinfo_encode($p)
{
$parts = array();
foreach ($p as $key => $val)
$parts[] = $key . '=' . ($key == 'folder' ? base64_encode($val) : $val);
return join('; ', $parts);
}
function rcmail_draftinfo_decode($str)
{
$info = array();
foreach (preg_split('/;\s+/', $str) as $part) {
list($key, $val) = explode('=', $part, 2);
if ($key == 'folder')
$val = base64_decode($val);
$info[$key] = $val;
}
return $info;
}
function rcmail_message_part_controls()
{
global $MESSAGE;
$part = asciiwords(get_input_value('_part', RCUBE_INPUT_GPC));
if (!is_object($MESSAGE) || !is_array($MESSAGE->parts) || !($_GET['_uid'] && $_GET['_part']) || !$MESSAGE->mime_parts[$part])
return '';
$part = $MESSAGE->mime_parts[$part];
$table = new html_table(array('cols' => 3));
if (!empty($part->filename)) {
$table->add('title', Q(rcube_label('filename')));
$table->add(null, Q($part->filename));
$table->add(null, '[' . html::a('?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING']), Q(rcube_label('download'))) . ']');
}
if (!empty($part->size)) {
$table->add('title', Q(rcube_label('filesize')));
$table->add(null, Q(show_bytes($part->size)));
}
return $table->show($attrib);
}
function rcmail_message_part_frame($attrib)
{
global $MESSAGE;
$part = $MESSAGE->mime_parts[asciiwords(get_input_value('_part', RCUBE_INPUT_GPC))];
$ctype_primary = strtolower($part->ctype_primary);
$attrib['src'] = './?' . str_replace('_frame=', ($ctype_primary=='text' ? '_show=' : '_preload='), $_SERVER['QUERY_STRING']);
return html::iframe($attrib);
}
/**
* clear message composing settings
*/
function rcmail_compose_cleanup($id)
{
if (!isset($_SESSION['compose_data_'.$id]))
return;
$rcmail = rcmail::get_instance();
$rcmail->plugins->exec_hook('attachments_cleanup', array('group' => $id));
$rcmail->session->remove('compose_data_'.$id);
}
/**
* Send the MDN response
*
* @param mixed $message Original message object (rcube_message) or UID
* @param array $smtp_error SMTP error array (reference)
*
* @return boolean Send status
*/
function rcmail_send_mdn($message, &$smtp_error)
{
global $RCMAIL;
if (!is_object($message) || !is_a($message, 'rcube_message'))
$message = new rcube_message($message);
if ($message->headers->mdn_to && empty($message->headers->flags['MDNSENT']) &&
($RCMAIL->imap->check_permflag('MDNSENT') || $RCMAIL->imap->check_permflag('*')))
{
$identity = $RCMAIL->user->get_identity();
$sender = format_email_recipient($identity['email'], $identity['name']);
$recipient = array_shift($RCMAIL->imap->decode_address_list($message->headers->mdn_to));
$mailto = $recipient['mailto'];
$compose = new Mail_mime("\r\n");
$compose->setParam('text_encoding', 'quoted-printable');
$compose->setParam('html_encoding', 'quoted-printable');
$compose->setParam('head_encoding', 'quoted-printable');
$compose->setParam('head_charset', RCMAIL_CHARSET);
$compose->setParam('html_charset', RCMAIL_CHARSET);
$compose->setParam('text_charset', RCMAIL_CHARSET);
// compose headers array
$headers = array(
'Date' => rcmail_user_date(),
'From' => $sender,
'To' => $message->headers->mdn_to,
'Subject' => rcube_label('receiptread') . ': ' . $message->subject,
'Message-ID' => rcmail_gen_message_id(),
'X-Sender' => $identity['email'],
'References' => trim($message->headers->references . ' ' . $message->headers->messageID),
);
if ($agent = $RCMAIL->config->get('useragent'))
$headers['User-Agent'] = $agent;
$body = rcube_label("yourmessage") . "\r\n\r\n" .
"\t" . rcube_label("to") . ': ' . rcube_imap::decode_mime_string($message->headers->to, $message->headers->charset) . "\r\n" .
"\t" . rcube_label("subject") . ': ' . $message->subject . "\r\n" .
"\t" . rcube_label("sent") . ': ' . format_date($message->headers->date, $RCMAIL->config->get('date_long')) . "\r\n" .
"\r\n" . rcube_label("receiptnote") . "\r\n";
$ua = $RCMAIL->config->get('useragent', "Roundcube Webmail (Version ".RCMAIL_VERSION.")");
$report = "Reporting-UA: $ua\r\n";
if ($message->headers->to)
$report .= "Original-Recipient: {$message->headers->to}\r\n";
$report .= "Final-Recipient: rfc822; {$identity['email']}\r\n" .
"Original-Message-ID: {$message->headers->messageID}\r\n" .
"Disposition: manual-action/MDN-sent-manually; displayed\r\n";
$compose->headers($headers);
$compose->setContentType('multipart/report', array('report-type'=> 'disposition-notification'));
$compose->setTXTBody(rc_wordwrap($body, 75, "\r\n"));
$compose->addAttachment($report, 'message/disposition-notification', 'MDNPart2.txt', false, '7bit', 'inline');
$sent = rcmail_deliver_message($compose, $identity['email'], $mailto, $smtp_error, $body_file);
if ($sent)
{
$RCMAIL->imap->set_flag($message->uid, 'MDNSENT');
return true;
}
}
return false;
}
// Fixes some content-type names
function rcmail_fix_mimetype($name)
{
// Some versions of Outlook create garbage Content-Type:
// application/pdf.A520491B_3BF7_494D_8855_7FAC2C6C0608
if (preg_match('/^application\/pdf.+/', $name))
$name = 'application/pdf';
return $name;
}
function rcmail_search_filter($attrib)
{
global $OUTPUT, $CONFIG;
if (!strlen($attrib['id']))
$attrib['id'] = 'rcmlistfilter';
$attrib['onchange'] = JS_OBJECT_NAME.'.filter_mailbox(this.value)';
/*
RFC3501 (6.4.4): 'ALL', 'RECENT',
'ANSWERED', 'DELETED', 'FLAGGED', 'SEEN',
'UNANSWERED', 'UNDELETED', 'UNFLAGGED', 'UNSEEN',
'NEW', // = (RECENT UNSEEN)
'OLD' // = NOT RECENT
*/
$select_filter = new html_select($attrib);
$select_filter->add(rcube_label('all'), 'ALL');
$select_filter->add(rcube_label('unread'), 'UNSEEN');
$select_filter->add(rcube_label('flagged'), 'FLAGGED');
$select_filter->add(rcube_label('unanswered'), 'UNANSWERED');
if (!$CONFIG['skip_deleted'])
$select_filter->add(rcube_label('deleted'), 'DELETED');
$select_filter->add(rcube_label('priority').': '.rcube_label('highest'), 'HEADER X-PRIORITY 1');
$select_filter->add(rcube_label('priority').': '.rcube_label('high'), 'HEADER X-PRIORITY 2');
$select_filter->add(rcube_label('priority').': '.rcube_label('normal'), 'NOT HEADER X-PRIORITY 1 NOT HEADER X-PRIORITY 2 NOT HEADER X-PRIORITY 4 NOT HEADER X-PRIORITY 5');
$select_filter->add(rcube_label('priority').': '.rcube_label('low'), 'HEADER X-PRIORITY 4');
$select_filter->add(rcube_label('priority').': '.rcube_label('lowest'), 'HEADER X-PRIORITY 5');
$out = $select_filter->show($_SESSION['search_filter']);
$OUTPUT->add_gui_object('search_filter', $attrib['id']);
return $out;
}
function rcmail_message_error($uid=null)
{
global $RCMAIL;
// Set env variables for messageerror.html template
if ($RCMAIL->action == 'show') {
$mbox_name = $RCMAIL->imap->get_mailbox_name();
$RCMAIL->output->set_env('mailbox', $mbox_name);
$RCMAIL->output->set_env('uid', null);
}
// display error message
$RCMAIL->output->show_message('messageopenerror', 'error');
// ... display message error page
$RCMAIL->output->send('messageerror');
}
// register UI objects
$OUTPUT->add_handlers(array(
'mailboxlist' => 'rcmail_mailbox_list',
'messages' => 'rcmail_message_list',
'messagecountdisplay' => 'rcmail_messagecount_display',
'quotadisplay' => 'rcmail_quota_display',
'mailboxname' => 'rcmail_mailbox_name_display',
'messageheaders' => 'rcmail_message_headers',
'messagefullheaders' => 'rcmail_message_full_headers',
'messagebody' => 'rcmail_message_body',
'messagecontentframe' => 'rcmail_messagecontent_frame',
'messagepartframe' => 'rcmail_message_part_frame',
'messagepartcontrols' => 'rcmail_message_part_controls',
'searchfilter' => 'rcmail_search_filter',
'searchform' => array($OUTPUT, 'search_form'),
));
// register action aliases
$RCMAIL->register_action_map(array(
'preview' => 'show.inc',
'print' => 'show.inc',
'moveto' => 'move_del.inc',
'delete' => 'move_del.inc',
'send' => 'sendmail.inc',
'expunge' => 'folders.inc',
'purge' => 'folders.inc',
'remove-attachment' => 'attachments.inc',
'display-attachment' => 'attachments.inc',
'upload' => 'attachments.inc',
'group-expand' => 'autocomplete.inc',
));
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Fri, Aug 21, 2:07 AM (1 d, 20 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1272871
Default Alt Text
(136 KB)
Attached To
Mode
R3 roundcubemail
Attached
Detach File
Event Timeline
Log In to Comment