Page Menu
Home
Phorge
Search
Configure Global Search
Log In
Files
F224889
No One
Temporary
Actions
View File
Edit File
Delete File
View Transforms
Subscribe
Mute Notifications
Award Token
Flag For Later
Size
82 KB
Referenced Files
None
Subscribers
None
View Options
diff --git a/program/include/rcmail.php b/program/include/rcmail.php
index 51ca9213b..f109c16fd 100644
--- a/program/include/rcmail.php
+++ b/program/include/rcmail.php
@@ -1,955 +1,955 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcmail.php |
| |
| This file is part of the RoundCube Webmail client |
| Copyright (C) 2008-2009, RoundCube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Application class providing core functions and holding |
| instances of all 'global' objects like db- and imap-connections |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id: rcmail.php 328 2006-08-30 17:41:21Z thomasb $
*/
/**
* Application class of RoundCube Webmail
* implemented as singleton
*
* @package Core
*/
class rcmail
{
static public $main_tasks = array('mail','settings','addressbook','login','logout');
static private $instance;
public $config;
public $user;
public $db;
public $imap;
public $output;
public $task = 'mail';
public $action = '';
public $comm_path = './';
private $texts;
/**
* This implements the 'singleton' design pattern
*
- * @return object qvert The one and only instance
+ * @return object rcmail The one and only instance
*/
static function get_instance()
{
if (!self::$instance) {
self::$instance = new rcmail();
self::$instance->startup(); // init AFTER object was linked with self::$instance
}
return self::$instance;
}
/**
* Private constructor
*/
private function __construct()
{
// load configuration
$this->config = new rcube_config();
register_shutdown_function(array($this, 'shutdown'));
}
/**
* Initial startup function
* to register session, create database and imap connections
*
* @todo Remove global vars $DB, $USER
*/
private function startup()
{
$config_all = $this->config->all();
// initialize syslog
if ($this->config->get('log_driver') == 'syslog') {
$syslog_id = $this->config->get('syslog_id', 'roundcube');
$syslog_facility = $this->config->get('syslog_facility', LOG_USER);
openlog($syslog_id, LOG_ODELAY, $syslog_facility);
}
// set task and action properties
$this->set_task(strip_quotes(get_input_value('_task', RCUBE_INPUT_GPC)));
$this->action = asciiwords(get_input_value('_action', RCUBE_INPUT_GPC));
// connect to database
$GLOBALS['DB'] = $this->get_dbh();
// use database for storing session data
include_once('include/session.inc');
// set session domain
if (!empty($config_all['session_domain'])) {
ini_set('session.cookie_domain', $config_all['session_domain']);
}
// set session garbage collecting time according to session_lifetime
if (!empty($config_all['session_lifetime'])) {
ini_set('session.gc_maxlifetime', ($config_all['session_lifetime']) * 120);
}
// start PHP session (if not in CLI mode)
if ($_SERVER['REMOTE_ADDR'])
session_start();
// set initial session vars
if (!isset($_SESSION['auth_time'])) {
$_SESSION['auth_time'] = time();
$_SESSION['temp'] = true;
}
// create user object
$this->set_user(new rcube_user($_SESSION['user_id']));
// reset some session parameters when changing task
if ($_SESSION['task'] != $this->task)
unset($_SESSION['page']);
// set current task to session
$_SESSION['task'] = $this->task;
// create IMAP object
if ($this->task == 'mail')
$this->imap_init();
}
/**
* Setter for application task
*
* @param string Task to set
*/
public function set_task($task)
{
if (!in_array($task, self::$main_tasks))
$task = 'mail';
$this->task = $task;
$this->comm_path = $this->url(array('task' => $task));
if ($this->output)
$this->output->set_env('task', $task);
}
/**
* Setter for system user object
*
* @param object rcube_user Current user instance
*/
public function set_user($user)
{
if (is_object($user)) {
$this->user = $user;
$GLOBALS['USER'] = $this->user;
// overwrite config with user preferences
$this->config->merge((array)$this->user->get_prefs());
}
$_SESSION['language'] = $this->user->language = $this->language_prop($this->config->get('language', $_SESSION['language']));
// set localization
setlocale(LC_ALL, $_SESSION['language'] . '.utf8', 'en_US.utf8');
// workaround for http://bugs.php.net/bug.php?id=18556
if (in_array($_SESSION['language'], array('tr_TR', 'ku', 'az_AZ')))
setlocale(LC_CTYPE, 'en_US' . '.utf8');
}
/**
* Check the given string and return a valid language code
*
* @param string Language code
* @return string Valid language code
*/
private function language_prop($lang)
{
static $rcube_languages, $rcube_language_aliases;
// user HTTP_ACCEPT_LANGUAGE if no language is specified
if (empty($lang) || $lang == 'auto') {
$accept_langs = explode(',', $_SERVER['HTTP_ACCEPT_LANGUAGE']);
$lang = str_replace('-', '_', $accept_langs[0]);
}
if (empty($rcube_languages)) {
@include(INSTALL_PATH . 'program/localization/index.inc');
}
// check if we have an alias for that language
if (!isset($rcube_languages[$lang]) && isset($rcube_language_aliases[$lang])) {
$lang = $rcube_language_aliases[$lang];
}
// try the first two chars
else if (!isset($rcube_languages[$lang])) {
$short = substr($lang, 0, 2);
// check if we have an alias for the short language code
if (!isset($rcube_languages[$short]) && isset($rcube_language_aliases[$short])) {
$lang = $rcube_language_aliases[$short];
}
// expand 'nn' to 'nn_NN'
else if (!isset($rcube_languages[$short])) {
$lang = $short.'_'.strtoupper($short);
}
}
if (!isset($rcube_languages[$lang]) || !is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
$lang = 'en_US';
}
return $lang;
}
/**
* Get the current database connection
*
* @return object rcube_mdb2 Database connection object
*/
public function get_dbh()
{
if (!$this->db) {
$config_all = $this->config->all();
$this->db = new rcube_mdb2($config_all['db_dsnw'], $config_all['db_dsnr'], $config_all['db_persistent']);
$this->db->sqlite_initials = INSTALL_PATH . 'SQL/sqlite.initial.sql';
$this->db->set_debug((bool)$config_all['sql_debug']);
$this->db->db_connect('w');
}
return $this->db;
}
/**
* Return instance of the internal address book class
*
* @param boolean True if the address book needs to be writeable
* @return object rcube_contacts Address book object
*/
public function get_address_book($id, $writeable = false)
{
$contacts = null;
$ldap_config = (array)$this->config->get('ldap_public');
$abook_type = strtolower($this->config->get('address_book_type'));
if ($id && $ldap_config[$id]) {
$contacts = new rcube_ldap($ldap_config[$id]);
}
else if ($abook_type == 'ldap') {
// Use the first writable LDAP address book.
foreach ($ldap_config as $id => $prop) {
if (!$writeable || $prop['writable']) {
$contacts = new rcube_ldap($prop);
break;
}
}
}
else {
$contacts = new rcube_contacts($this->db, $this->user->ID);
}
return $contacts;
}
/**
* Init output object for GUI and add common scripts.
* This will instantiate a rcmail_template object and set
* environment vars according to the current session and configuration
*
* @param boolean True if this request is loaded in a (i)frame
* @return object rcube_template Reference to HTML output object
*/
public function load_gui($framed = false)
{
// init output page
if (!($this->output instanceof rcube_template))
$this->output = new rcube_template($this->task, $framed);
foreach (array('flag_for_deletion','read_when_deleted') as $js_config_var) {
$this->output->set_env($js_config_var, $this->config->get($js_config_var));
}
// set keep-alive/check-recent interval
if ($keep_alive = $this->config->get('keep_alive')) {
// be sure that it's less than session lifetime
if ($session_lifetime = $this->config->get('session_lifetime'))
$keep_alive = min($keep_alive, $session_lifetime * 60 - 30);
$this->output->set_env('keep_alive', max(60, $keep_alive));
}
if ($framed) {
$this->comm_path .= '&_framed=1';
$this->output->set_env('framed', true);
}
$this->output->set_env('task', $this->task);
$this->output->set_env('action', $this->action);
$this->output->set_env('comm_path', $this->comm_path);
$this->output->set_charset($this->config->get('charset', RCMAIL_CHARSET));
// add some basic label to client
$this->output->add_label('loading');
return $this->output;
}
/**
* Create an output object for JSON responses
*
* @return object rcube_json_output Reference to JSON output object
*/
public function init_json()
{
if (!($this->output instanceof rcube_json_output))
$this->output = new rcube_json_output($this->task);
return $this->output;
}
/**
* Create global IMAP object and connect to server
*
* @param boolean True if connection should be established
* @todo Remove global $IMAP
*/
public function imap_init($connect = false)
{
$this->imap = new rcube_imap($this->db);
$this->imap->debug_level = $this->config->get('debug_level');
$this->imap->skip_deleted = $this->config->get('skip_deleted');
// enable caching of imap data
if ($this->config->get('enable_caching')) {
$this->imap->set_caching(true);
}
// set pagesize from config
$this->imap->set_pagesize($this->config->get('pagesize', 50));
// Setting root and delimiter before iil_Connect can save time detecting them
// using NAMESPACE and LIST
$options = array(
'imap' => $this->config->get('imap_auth_type', 'check'),
'delimiter' => isset($_SESSION['imap_delimiter']) ? $_SESSION['imap_delimiter'] : $this->config->get('imap_delimiter'),
);
if (isset($_SESSION['imap_root']))
$options['rootdir'] = $_SESSION['imap_root'];
else if ($imap_root = $this->config->get('imap_root'))
$options['rootdir'] = $imap_root;
$this->imap->set_options($options);
// set global object for backward compatibility
$GLOBALS['IMAP'] = $this->imap;
if ($connect)
$this->imap_connect();
}
/**
* Connect to IMAP server with stored session data
*
* @return bool True on success, false on error
*/
public function imap_connect()
{
$conn = false;
if ($_SESSION['imap_host'] && !$this->imap->conn) {
if (!($conn = $this->imap->connect($_SESSION['imap_host'], $_SESSION['username'], $this->decrypt_passwd($_SESSION['password']), $_SESSION['imap_port'], $_SESSION['imap_ssl']))) {
if ($this->output)
$this->output->show_message($this->imap->error_code == -1 ? 'imaperror' : 'sessionerror', 'error');
}
$this->set_imap_prop();
}
return $conn;
}
/**
* Perfom login to the IMAP server and to the webmail service.
* This will also create a new user entry if auto_create_user is configured.
*
* @param string IMAP user name
* @param string IMAP password
* @param string IMAP host
* @return boolean True on success, False on failure
*/
function login($username, $pass, $host=NULL)
{
$user = NULL;
$config = $this->config->all();
if (!$host)
$host = $config['default_host'];
// Validate that selected host is in the list of configured hosts
if (is_array($config['default_host'])) {
$allowed = false;
foreach ($config['default_host'] as $key => $host_allowed) {
if (!is_numeric($key))
$host_allowed = $key;
if ($host == $host_allowed) {
$allowed = true;
break;
}
}
if (!$allowed)
return false;
}
else if (!empty($config['default_host']) && $host != $config['default_host'])
return false;
// parse $host URL
$a_host = parse_url($host);
if ($a_host['host']) {
$host = $a_host['host'];
$imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? $a_host['scheme'] : null;
$imap_port = isset($a_host['port']) ? $a_host['port'] : ($imap_ssl ? 993 : $config['default_port']);
}
else
$imap_port = $config['default_port'];
/* Modify username with domain if required
Inspired by Marco <P0L0_notspam_binware.org>
*/
// Check if we need to add domain
if (!empty($config['username_domain']) && !strpos($username, '@')) {
if (is_array($config['username_domain']) && isset($config['username_domain'][$host]))
$username .= '@'.$config['username_domain'][$host];
else if (is_string($config['username_domain']))
$username .= '@'.$config['username_domain'];
}
// try to resolve email address from virtuser table
if (!empty($config['virtuser_file']) && strpos($username, '@'))
$username = rcube_user::email2user($username);
// lowercase username if it's an e-mail address (#1484473)
if (strpos($username, '@'))
$username = rc_strtolower($username);
// user already registered -> overwrite username
if ($user = rcube_user::query($username, $host))
$username = $user->data['username'];
// exit if IMAP login failed
if (!($imap_login = $this->imap->connect($host, $username, $pass, $imap_port, $imap_ssl)))
return false;
// user already registered -> update user's record
if (is_object($user)) {
$user->touch();
}
// create new system user
else if ($config['auto_create_user']) {
if ($created = rcube_user::create($username, $host)) {
$user = $created;
// get existing mailboxes (but why?)
// $a_mailboxes = $this->imap->list_mailboxes();
}
}
else {
raise_error(array(
'code' => 600,
'type' => 'php',
'file' => RCMAIL_CONFIG_DIR."/main.inc.php",
'message' => "Acces denied for new user $username. 'auto_create_user' is disabled"
), true, false);
}
// login succeeded
if (is_object($user) && $user->ID) {
$this->set_user($user);
// set session vars
$_SESSION['user_id'] = $user->ID;
$_SESSION['username'] = $user->data['username'];
$_SESSION['imap_host'] = $host;
$_SESSION['imap_port'] = $imap_port;
$_SESSION['imap_ssl'] = $imap_ssl;
$_SESSION['password'] = $this->encrypt_passwd($pass);
$_SESSION['login_time'] = mktime();
if ($_REQUEST['_timezone'] != '_default_')
$_SESSION['timezone'] = floatval($_REQUEST['_timezone']);
// force reloading complete list of subscribed mailboxes
$this->set_imap_prop();
$this->imap->clear_cache('mailboxes');
if ($config['create_default_folders'])
$this->imap->create_default_folders();
return true;
}
return false;
}
/**
* Set root dir and last stored mailbox
* This must be done AFTER connecting to the server!
*/
public function set_imap_prop()
{
$this->imap->set_charset($this->config->get('default_charset', RCMAIL_CHARSET));
if ($default_folders = $this->config->get('default_imap_folders')) {
$this->imap->set_default_mailboxes($default_folders);
}
if (!empty($_SESSION['mbox'])) {
$this->imap->set_mailbox($_SESSION['mbox']);
}
if (isset($_SESSION['page'])) {
$this->imap->set_page($_SESSION['page']);
}
// cache IMAP root and delimiter in session for performance reasons
$_SESSION['imap_root'] = $this->imap->root_dir;
$_SESSION['imap_delimiter'] = $this->imap->delimiter;
}
/**
* Auto-select IMAP host based on the posted login information
*
* @return string Selected IMAP host
*/
public function autoselect_host()
{
$default_host = $this->config->get('default_host');
$host = null;
if (is_array($default_host)) {
$post_host = get_input_value('_host', RCUBE_INPUT_POST);
// direct match in default_host array
if ($default_host[$post_host] || in_array($post_host, array_values($default_host))) {
$host = $post_host;
}
// try to select host by mail domain
list($user, $domain) = explode('@', get_input_value('_user', RCUBE_INPUT_POST));
if (!empty($domain)) {
foreach ($default_host as $imap_host => $mail_domains) {
if (is_array($mail_domains) && in_array($domain, $mail_domains)) {
$host = $imap_host;
break;
}
}
}
// take the first entry if $host is still an array
if (empty($host)) {
$host = array_shift($default_host);
}
}
else if (empty($default_host)) {
$host = get_input_value('_host', RCUBE_INPUT_POST);
}
else
$host = $default_host;
return $host;
}
/**
* Get localized text in the desired language
*
* @param mixed Named parameters array or label name
* @return string Localized text
*/
public function gettext($attrib)
{
// load localization files if not done yet
if (empty($this->texts))
$this->load_language();
// extract attributes
if (is_string($attrib))
$attrib = array('name' => $attrib);
$nr = is_numeric($attrib['nr']) ? $attrib['nr'] : 1;
$vars = isset($attrib['vars']) ? $attrib['vars'] : '';
$command_name = !empty($attrib['command']) ? $attrib['command'] : NULL;
$alias = $attrib['name'] ? $attrib['name'] : ($command_name && $command_label_map[$command_name] ? $command_label_map[$command_name] : '');
// text does not exist
if (!($text_item = $this->texts[$alias])) {
/*
raise_error(array(
'code' => 500,
'type' => 'php',
'line' => __LINE__,
'file' => __FILE__,
'message' => "Missing localized text for '$alias' in '$sess_user_lang'"), TRUE, FALSE);
*/
return "[$alias]";
}
// make text item array
$a_text_item = is_array($text_item) ? $text_item : array('single' => $text_item);
// decide which text to use
if ($nr == 1) {
$text = $a_text_item['single'];
}
else if ($nr > 0) {
$text = $a_text_item['multiple'];
}
else if ($nr == 0) {
if ($a_text_item['none'])
$text = $a_text_item['none'];
else if ($a_text_item['single'])
$text = $a_text_item['single'];
else if ($a_text_item['multiple'])
$text = $a_text_item['multiple'];
}
// default text is single
if ($text == '') {
$text = $a_text_item['single'];
}
// replace vars in text
if (is_array($attrib['vars'])) {
foreach ($attrib['vars'] as $var_key => $var_value)
$a_replace_vars[$var_key{0}=='$' ? substr($var_key, 1) : $var_key] = $var_value;
}
if ($a_replace_vars)
$text = preg_replace('/\$\{?([_a-z]{1}[_a-z0-9]*)\}?/ei', '$a_replace_vars["\1"]', $text);
// format output
if (($attrib['uppercase'] && strtolower($attrib['uppercase']=='first')) || $attrib['ucfirst'])
return ucfirst($text);
else if ($attrib['uppercase'])
return strtoupper($text);
else if ($attrib['lowercase'])
return strtolower($text);
return $text;
}
/**
* Load a localization package
*
* @param string Language ID
*/
public function load_language($lang = null)
{
$lang = $this->language_prop(($lang ? $lang : $_SESSION['language']));
// load localized texts
if (empty($this->texts) || $lang != $_SESSION['language']) {
$this->texts = array();
// get english labels (these should be complete)
@include(INSTALL_PATH . 'program/localization/en_US/labels.inc');
@include(INSTALL_PATH . 'program/localization/en_US/messages.inc');
if (is_array($labels))
$this->texts = $labels;
if (is_array($messages))
$this->texts = array_merge($this->texts, $messages);
// include user language files
if ($lang != 'en' && is_dir(INSTALL_PATH . 'program/localization/' . $lang)) {
include_once(INSTALL_PATH . 'program/localization/' . $lang . '/labels.inc');
include_once(INSTALL_PATH . 'program/localization/' . $lang . '/messages.inc');
if (is_array($labels))
$this->texts = array_merge($this->texts, $labels);
if (is_array($messages))
$this->texts = array_merge($this->texts, $messages);
}
$_SESSION['language'] = $lang;
}
}
/**
* Read directory program/localization and return a list of available languages
*
* @return array List of available localizations
*/
public function list_languages()
{
static $sa_languages = array();
if (!sizeof($sa_languages)) {
@include(INSTALL_PATH . 'program/localization/index.inc');
if ($dh = @opendir(INSTALL_PATH . 'program/localization')) {
while (($name = readdir($dh)) !== false) {
if ($name{0}=='.' || !is_dir(INSTALL_PATH . 'program/localization/' . $name))
continue;
if ($label = $rcube_languages[$name])
$sa_languages[$name] = $label ? $label : $name;
}
closedir($dh);
}
}
return $sa_languages;
}
/**
* Check the auth hash sent by the client against the local session credentials
*
* @return boolean True if valid, False if not
*/
function authenticate_session()
{
global $SESS_CLIENT_IP, $SESS_CHANGED;
// advanced session authentication
if ($this->config->get('double_auth')) {
$now = time();
$valid = ($_COOKIE['sessauth'] == $this->get_auth_hash(session_id(), $_SESSION['auth_time']) ||
$_COOKIE['sessauth'] == $this->get_auth_hash(session_id(), $_SESSION['last_auth']));
// renew auth cookie every 5 minutes (only for GET requests)
if (!$valid || ($_SERVER['REQUEST_METHOD']!='POST' && $now - $_SESSION['auth_time'] > 300)) {
$_SESSION['last_auth'] = $_SESSION['auth_time'];
$_SESSION['auth_time'] = $now;
rcmail::setcookie('sessauth', $this->get_auth_hash(session_id(), $now), 0);
}
}
else {
$valid = $this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] == $SESS_CLIENT_IP : true;
}
// check session filetime
$lifetime = $this->config->get('session_lifetime');
if (!empty($lifetime) && isset($SESS_CHANGED) && $SESS_CHANGED + $lifetime*60 < time()) {
$valid = false;
}
return $valid;
}
/**
* Destroy session data and remove cookie
*/
public function kill_session()
{
$_SESSION = array('language' => $this->user->language, 'auth_time' => time(), 'temp' => true);
rcmail::setcookie('sessauth', '-del-', time() - 60);
$this->user->reset();
}
/**
* Do server side actions on logout
*/
public function logout_actions()
{
$config = $this->config->all();
// on logout action we're not connected to imap server
if (($config['logout_purge'] && !empty($config['trash_mbox'])) || $config['logout_expunge']) {
if (!$this->authenticate_session())
return;
$this->imap_init(true);
}
if ($config['logout_purge'] && !empty($config['trash_mbox'])) {
$this->imap->clear_mailbox($config['trash_mbox']);
}
if ($config['logout_expunge']) {
$this->imap->expunge('INBOX');
}
}
/**
* Function to be executed in script shutdown
* Registered with register_shutdown_function()
*/
public function shutdown()
{
if (is_object($this->imap)) {
$this->imap->close();
$this->imap->write_cache();
}
if (is_object($this->contacts))
$this->contacts->close();
// before closing the database connection, write session data
if ($_SERVER['REMOTE_ADDR'])
session_write_close();
}
/**
* Create unique authorization hash
*
* @param string Session ID
* @param int Timestamp
* @return string The generated auth hash
*/
private function get_auth_hash($sess_id, $ts)
{
$auth_string = sprintf('rcmail*sess%sR%s*Chk:%s;%s',
$sess_id,
$ts,
$this->config->get('ip_check') ? $_SERVER['REMOTE_ADDR'] : '***.***.***.***',
$_SERVER['HTTP_USER_AGENT']);
if (function_exists('sha1'))
return sha1($auth_string);
else
return md5($auth_string);
}
/**
* Encrypt IMAP password using DES encryption
*
* @param string Password to encrypt
* @return string Encryprted string
*/
public function encrypt_passwd($pass)
{
if (function_exists('mcrypt_module_open') && ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_ECB, ""))) {
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $this->config->get_des_key(), $iv);
$cypher = mcrypt_generic($td, $pass);
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
}
else if (function_exists('des')) {
$cypher = des($this->config->get_des_key(), $pass, 1, 0, NULL);
}
else {
$cypher = $pass;
raise_error(array(
'code' => 500,
'type' => 'php',
'file' => __FILE__,
'message' => "Could not convert encrypt password. Make sure Mcrypt is installed or lib/des.inc is available"
), true, false);
}
return base64_encode($cypher);
}
/**
* Decrypt IMAP password using DES encryption
*
* @param string Encrypted password
* @return string Plain password
*/
public function decrypt_passwd($cypher)
{
if (function_exists('mcrypt_module_open') && ($td = mcrypt_module_open(MCRYPT_TripleDES, "", MCRYPT_MODE_ECB, ""))) {
$iv = mcrypt_create_iv(mcrypt_enc_get_iv_size($td), MCRYPT_RAND);
mcrypt_generic_init($td, $this->config->get_des_key(), $iv);
$pass = mdecrypt_generic($td, base64_decode($cypher));
mcrypt_generic_deinit($td);
mcrypt_module_close($td);
}
else if (function_exists('des')) {
$pass = des($this->config->get_des_key(), base64_decode($cypher), 0, 0, NULL);
}
else {
$pass = base64_decode($cypher);
}
return preg_replace('/\x00/', '', $pass);
}
/**
* Build a valid URL to this instance of RoundCube
*
* @param mixed Either a string with the action or url parameters as key-value pairs
* @return string Valid application URL
*/
public function url($p)
{
if (!is_array($p))
$p = array('_action' => @func_get_arg(0));
if (!$p['task'] || !in_array($p['task'], rcmail::$main_tasks))
$p['task'] = $this->task;
$p['_task'] = $p['task'];
unset($p['task']);
$url = './';
$delm = '?';
foreach (array_reverse($p) as $par => $val)
{
if (!empty($val)) {
$url .= $delm.urlencode($par).'='.urlencode($val);
$delm = '&';
}
}
return $url;
}
/**
* Helper method to set a cookie with the current path and host settings
*
* @param string Cookie name
* @param string Cookie value
* @param string Expiration time
*/
public static function setcookie($name, $value, $exp = 0)
{
$cookie = session_get_cookie_params();
setcookie($name, $value, $exp, $cookie['path'], $cookie['domain'],
($_SERVER['HTTPS'] && ($_SERVER['HTTPS'] != 'off')));
}
}
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index 702625cea..79e148d8a 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1,1455 +1,1455 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/func.inc |
| |
| This file is part of the RoundCube Webmail client |
| Copyright (C) 2005-2009, RoundCube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Provide webmail functionality and GUI objects |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
require_once('include/rcube_smtp.inc');
-$EMAIL_ADDRESS_PATTERN = '/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/i';
+$EMAIL_ADDRESS_PATTERN = '([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})';
// actions that do not require imap connection
$NOIMAP_ACTIONS = array('spell', 'addcontact', 'autocomplete', 'upload', 'display-attachment', 'remove-attachment');
// 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 ($mbox = get_input_value('_mbox', RCUBE_INPUT_GPC))
$IMAP->set_mailbox(($_SESSION['mbox'] = $mbox));
else
$_SESSION['mbox'] = $IMAP->get_mailbox_name();
if (!empty($_GET['_page']))
$IMAP->set_page(($_SESSION['page'] = intval($_GET['_page'])));
// set default sort col/order to session
if (!isset($_SESSION['sort_col']))
$_SESSION['sort_col'] = $CONFIG['message_sort_col'];
if (!isset($_SESSION['sort_order']))
$_SESSION['sort_order'] = $CONFIG['message_sort_order'];
// set message set for search result
if (!empty($_REQUEST['_search']) && isset($_SESSION['search'][$_REQUEST['_search']]))
{
$IMAP->set_search_set($_SESSION['search'][$_REQUEST['_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 = $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']);
$IMAP->search($mbox_name, $_SESSION['search_filter'], RCMAIL_CHARSET, $_SESSION['sort_col']);
$_SESSION['search'][$search_request] = $IMAP->get_search_set();
$OUTPUT->set_env('search_request', $search_request);
}
// make sure the message count is refreshed (for default view)
$IMAP->messagecount($mbox_name, 'ALL', true);
}
// set current mailbox in client environment
$OUTPUT->set_env('mailbox', $mbox_name);
$OUTPUT->set_env('quota', $IMAP->get_capability('quota'));
$OUTPUT->set_env('delimiter', $IMAP->get_hierarchy_delimiter());
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');
$OUTPUT->set_pagetitle(rcmail_localize_foldername($mbox_name));
}
/**
* return the message list as HTML table
*/
function rcmail_message_list($attrib)
{
global $IMAP, $CONFIG, $COMM_PATH, $OUTPUT;
$skin_path = $CONFIG['skin_path'];
$image_tag = '<img 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'];
// add some labels to client
$OUTPUT->add_label('from', 'to');
// get message headers
$a_headers = $IMAP->list_headers('', '', $sort_col, $sort_order);
// add id to message list table if not specified
if (!strlen($attrib['id']))
$attrib['id'] = 'rcubemessagelist';
// allow the following attributes to be added to the <table> tag
$attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
$out = '<table' . $attrib_str . ">\n";
// 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');
else
$a_show_cols = preg_split('/[\s,;]+/', strip_quotes($attrib['columns']));
// store column list in a session-variable
$_SESSION['list_columns'] = $a_show_cols;
// define sortable columns
$a_sort_cols = array('subject', 'date', 'from', 'to', 'size');
$mbox = $IMAP->get_mailbox_name();
// show 'to' instead of from in sent messages
if (($mbox==$CONFIG['sent_mbox'] || $mbox==$CONFIG['drafts_mbox']) && ($f = array_search('from', $a_show_cols))
&& !array_search('to', $a_show_cols))
$a_show_cols[$f] = 'to';
// add col definition
$out .= '<colgroup>';
$out .= '<col class="icon" />';
foreach ($a_show_cols as $col)
$out .= ($col!='attachment') ? sprintf('<col class="%s" />', $col) : '<col class="icon" />';
$out .= "</colgroup>\n";
// add table title
$out .= "<thead><tr>\n<td class=\"icon\"> </td>\n";
$javascript = '';
foreach ($a_show_cols as $col)
{
// get column name
switch ($col)
{
case 'flag':
$col_name = sprintf($image_tag, $skin_path, $attrib['unflaggedicon'], '');
break;
case 'attachment':
$col_name = sprintf($image_tag, $skin_path, $attrib['attachmenticon'], '');
break;
default:
$col_name = Q(rcube_label($col));
}
// make sort links
$sort = '';
if (in_array($col, $a_sort_cols))
{
// have buttons configured
if (!empty($attrib['sortdescbutton']) || !empty($attrib['sortascbutton']))
{
$sort = ' ';
// asc link
if (!empty($attrib['sortascbutton']))
{
$sort .= $OUTPUT->button(array(
'command' => 'sort',
'prop' => $col.'_ASC',
'image' => $attrib['sortascbutton'],
'align' => 'absmiddle',
'title' => 'sortasc'));
}
// desc link
if (!empty($attrib['sortdescbutton']))
{
$sort .= $OUTPUT->button(array(
'command' => 'sort',
'prop' => $col.'_DESC',
'image' => $attrib['sortdescbutton'],
'align' => 'absmiddle',
'title' => 'sortdesc'));
}
}
// just add a link tag to the header
else
{
$col_name = sprintf(
'<a href="./#sort" onclick="return %s.command(\'sort\',\'%s\',this)" title="%s">%s</a>',
JS_OBJECT_NAME,
$col,
rcube_label('sortby'),
$col_name);
}
}
$sort_class = $col==$sort_col ? " sorted$sort_order" : '';
// put it all together
if ($col!='attachment')
$out .= '<td class="'.$col.$sort_class.'" id="rcm'.$col.'">' . "$col_name$sort</td>\n";
else
$out .= '<td class="icon" id="rcm'.$col.'">' . "$col_name$sort</td>\n";
}
$out .= "</tr></thead>\n<tbody>\n";
// no messages in this mailbox
if (!sizeof($a_headers))
$OUTPUT->show_message('nomessagesfound', 'notice');
$a_js_message_arr = array();
// create row for each message
foreach ($a_headers as $i => $header) //while (list($i, $header) = each($a_headers))
{
$message_icon = $attach_icon = $flagged_icon = '';
$js_row_arr = array();
$zebra_class = $i%2 ? ' even' : ' odd';
// set messag attributes to javascript array
if ($header->deleted)
$js_row_arr['deleted'] = true;
if (!$header->seen)
$js_row_arr['unread'] = true;
if ($header->answered)
$js_row_arr['replied'] = true;
if ($header->forwarded)
$js_row_arr['forwarded'] = true;
if ($header->flagged)
$js_row_arr['flagged'] = true;
// set message icon
if ($attrib['deletedicon'] && $header->deleted)
$message_icon = $attrib['deletedicon'];
else if ($attrib['repliedicon'] && $header->answered)
{
if ($attrib['forwardedrepliedicon'] && $header->forwarded)
$message_icon = $attrib['forwardedrepliedicon'];
else
$message_icon = $attrib['repliedicon'];
}
else if ($attrib['forwardedicon'] && $header->forwarded)
$message_icon = $attrib['forwardedicon'];
else if ($attrib['unreadicon'] && !$header->seen)
$message_icon = $attrib['unreadicon'];
else if ($attrib['messageicon'])
$message_icon = $attrib['messageicon'];
if ($attrib['flaggedicon'] && $header->flagged)
$flagged_icon = $attrib['flaggedicon'];
else if ($attrib['unflaggedicon'] && !$header->flagged)
$flagged_icon = $attrib['unflaggedicon'];
// set attachment icon
if ($attrib['attachmenticon'] && preg_match("/multipart\/m/i", $header->ctype))
$attach_icon = $attrib['attachmenticon'];
$out .= sprintf('<tr id="rcmrow%d" class="message%s%s%s%s">'."\n",
$header->uid,
$header->seen ? '' : ' unread',
$header->deleted ? ' deleted' : '',
$header->flagged ? ' flagged' : '',
$zebra_class);
$out .= sprintf("<td class=\"icon\">%s</td>\n", $message_icon ? sprintf($image_tag, $skin_path, $message_icon, '') : '');
$IMAP->set_charset(!empty($header->charset) ? $header->charset : $CONFIG['default_charset']);
// format each col
foreach ($a_show_cols as $col)
{
if ($col=='from' || $col=='to')
$cont = Q(rcmail_address_string($header->$col, 3, false, $attrib['addicon']), 'show');
else if ($col=='subject')
{
$action = $mbox==$CONFIG['drafts_mbox'] ? 'compose' : 'show';
$uid_param = $mbox==$CONFIG['drafts_mbox'] ? '_draft_uid' : '_uid';
$cont = abbreviate_string(trim($IMAP->decode_header($header->$col)), 160);
if (empty($cont)) $cont = rcube_label('nosubject');
$cont = sprintf('<a href="%s" onclick="return rcube_event.cancel(event)">%s</a>', Q(rcmail_url($action, array($uid_param=>$header->uid, '_mbox'=>$mbox))), Q($cont));
}
else if ($col=='flag')
$cont = $flagged_icon ? sprintf($image_tag, $skin_path, $flagged_icon, '') : '';
else if ($col=='size')
$cont = show_bytes($header->$col);
else if ($col=='date')
$cont = format_date($header->date);
else
$cont = Q($header->$col);
if ($col!='attachment')
$out .= '<td class="'.$col.'">' . $cont . "</td>\n";
else
$out .= sprintf("<td class=\"icon\">%s</td>\n", $attach_icon ? sprintf($image_tag, $skin_path, $attach_icon, '') : ' ');
}
$out .= "</tr>\n";
if (sizeof($js_row_arr))
$a_js_message_arr[$header->uid] = $js_row_arr;
}
// complete message table
$out .= "</tbody></table>\n";
$message_count = $IMAP->messagecount();
// set client env
$OUTPUT->add_gui_object('mailcontframe', 'mailcontframe');
$OUTPUT->add_gui_object('messagelist', $attrib['id']);
$OUTPUT->set_env('messagecount', $message_count);
$OUTPUT->set_env('current_page', $IMAP->list_page);
$OUTPUT->set_env('pagecount', ceil($message_count/$IMAP->page_size));
$OUTPUT->set_env('sort_col', $sort_col);
$OUTPUT->set_env('sort_order', $sort_order);
if ($attrib['messageicon'])
$OUTPUT->set_env('messageicon', $skin_path . $attrib['messageicon']);
if ($attrib['deletedicon'])
$OUTPUT->set_env('deletedicon', $skin_path . $attrib['deletedicon']);
if ($attrib['unreadicon'])
$OUTPUT->set_env('unreadicon', $skin_path . $attrib['unreadicon']);
if ($attrib['repliedicon'])
$OUTPUT->set_env('repliedicon', $skin_path . $attrib['repliedicon']);
if ($attrib['forwardedicon'])
$OUTPUT->set_env('forwardedicon', $skin_path . $attrib['forwardedicon']);
if ($attrib['forwardedrepliedicon'])
$OUTPUT->set_env('forwardedrepliedicon', $skin_path . $attrib['forwardedrepliedicon']);
if ($attrib['attachmenticon'])
$OUTPUT->set_env('attachmenticon', $skin_path . $attrib['attachmenticon']);
if ($attrib['flaggedicon'])
$OUTPUT->set_env('flaggedicon', $skin_path . $attrib['flaggedicon']);
if ($attrib['unflaggedicon'])
$OUTPUT->set_env('unflaggedicon', $skin_path . $attrib['unflaggedicon']);
$OUTPUT->set_env('messages', $a_js_message_arr);
$OUTPUT->set_env('coltypes', $a_show_cols);
$OUTPUT->include_script('list.js');
return $out;
}
/**
* return javascript commands to add rows to the message list
*/
function rcmail_js_message_list($a_headers, $insert_top=FALSE)
{
global $CONFIG, $IMAP, $OUTPUT;
if (empty($_SESSION['list_columns']))
$a_show_cols = is_array($CONFIG['list_cols']) ? $CONFIG['list_cols'] : array('subject');
else
$a_show_cols = $_SESSION['list_columns'];
$mbox = $IMAP->get_mailbox_name();
// show 'to' instead of from in sent messages
if (($mbox == $CONFIG['sent_mbox'] || $mbox == $CONFIG['drafts_mbox'])
&& (($f = array_search('from', $a_show_cols)) !== false) && array_search('to', $a_show_cols) === false)
$a_show_cols[$f] = 'to';
$OUTPUT->command('set_message_coltypes', $a_show_cols);
// loop through message headers
foreach ($a_headers as $n => $header)
{
$a_msg_cols = array();
$a_msg_flags = array();
if (empty($header))
continue;
$IMAP->set_charset(!empty($header->charset) ? $header->charset : $CONFIG['default_charset']);
// remove 'attachment' and 'flag' columns, we don't need them here
if(($key = array_search('attachment', $a_show_cols)) !== FALSE)
unset($a_show_cols[$key]);
if(($key = array_search('flag', $a_show_cols)) !== FALSE)
unset($a_show_cols[$key]);
// format each col; similar as in rcmail_message_list()
foreach ($a_show_cols as $col)
{
if ($col=='from' || $col=='to')
$cont = Q(rcmail_address_string($header->$col, 3), 'show');
else if ($col=='subject')
{
$action = $mbox==$CONFIG['drafts_mbox'] ? 'compose' : 'show';
$uid_param = $mbox==$CONFIG['drafts_mbox'] ? '_draft_uid' : '_uid';
$cont = abbreviate_string(trim($IMAP->decode_header($header->$col)), 160);
if (!$cont) $cont = rcube_label('nosubject');
$cont = sprintf('<a href="%s" onclick="return rcube_event.cancel(event)">%s</a>', Q(rcmail_url($action, array($uid_param=>$header->uid, '_mbox'=>$mbox))), 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['deleted'] = $header->deleted ? 1 : 0;
$a_msg_flags['unread'] = $header->seen ? 0 : 1;
$a_msg_flags['replied'] = $header->answered ? 1 : 0;
$a_msg_flags['forwarded'] = $header->forwarded ? 1 : 0;
$a_msg_flags['flagged'] = $header->flagged ? 1 : 0;
$OUTPUT->command('add_message_row',
$header->uid,
$a_msg_cols,
$a_msg_flags,
preg_match("/multipart\/m/i", $header->ctype),
$insert_top);
}
}
/**
* return an HTML iframe for loading mail content
*/
function rcmail_messagecontent_frame($attrib)
{
global $OUTPUT;
if (empty($attrib['id']))
$attrib['id'] = 'rcmailcontentwindow';
$attrib['name'] = $attrib['id'];
$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 $IMAP, $OUTPUT;
if (!$attrib['id'])
$attrib['id'] = 'rcmcountdisplay';
$OUTPUT->add_gui_object('countdisplay', $attrib['id']);
return html::span($attrib, rcmail_get_messagecount_text());
}
/**
*
*/
function rcmail_quota_display($attrib)
{
global $OUTPUT, $COMM_PATH;
if (!$attrib['id'])
$attrib['id'] = 'rcmquotadisplay';
if(isset($attrib['display']))
$_SESSION['quota_display'] = $attrib['display'];
$OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
return html::span($attrib, rcmail_quota_content(NULL, $attrib));
}
/**
*
*/
function rcmail_quota_content($quota=NULL, $attrib=NULL)
{
global $IMAP, $COMM_PATH, $RCMAIL;
$display = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
if (is_array($quota) && !empty($quota['used']) && !empty($quota['total']))
{
if (!isset($quota['percent']))
$quota['percent'] = $quota['used'] / $quota['total'];
}
elseif (!$IMAP->get_capability('QUOTA'))
return rcube_label('unknown');
else
$quota = $IMAP->get_quota();
if ($quota && !($quota['total']==0 && $RCMAIL->config->get('quota_zero_as_unlimited')))
{
$quota_text = sprintf('%s / %s (%.0f%%)',
show_bytes($quota['used'] * 1024),
show_bytes($quota['total'] * 1024),
$quota['percent']);
// show quota as image (by Brett Patterson)
if ($display == 'image' && function_exists('imagegif'))
{
if (!$attrib['width'])
$attrib['width'] = isset($_SESSION['quota_width']) ? $_SESSION['quota_width'] : 100;
else
$_SESSION['quota_width'] = $attrib['width'];
if (!$attrib['height'])
$attrib['height'] = isset($_SESSION['quota_height']) ? $_SESSION['quota_height'] : 14;
else
$_SESSION['quota_height'] = $attrib['height'];
$quota_text = sprintf('<img src="./bin/quotaimg.php?u=%s&q=%d&w=%d&h=%d" width="%d" height="%d" alt="%s" title="%s / %s" />',
$quota['used'], $quota['total'],
$attrib['width'], $attrib['height'],
$attrib['width'], $attrib['height'],
$quota_text,
show_bytes($quota['used'] * 1024),
show_bytes($quota['total'] * 1024));
}
}
else
$quota_text = rcube_label('unlimited');
return $quota_text;
}
/**
*
*/
function rcmail_get_messagecount_text($count=NULL, $page=NULL)
{
global $IMAP, $MESSAGE;
if (isset($MESSAGE->index))
{
return rcube_label(array('name' => 'messagenrof',
'vars' => array('nr' => $MESSAGE->index+1,
'count' => $count!==NULL ? $count : $IMAP->messagecount())));
}
if ($page===NULL)
$page = $IMAP->list_page;
$start_msg = ($page-1) * $IMAP->page_size + 1;
$max = $count!==NULL ? $count : $IMAP->messagecount();
if ($max==0)
$out = rcube_label('mailboxempty');
else
$out = rcube_label(array('name' => 'messagesfromto',
'vars' => array('from' => $start_msg,
'to' => min($max, $start_msg + $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());
}
/**
* 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 = array(), $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>
'/(<[\/]*st1:[^>]+>)/i', // Microsoft's Smart Tags <ST1>
'/<\/?rte_text>/i', // Rich Text Editor tags (#1485647)
'/<title>.*<\/title>/i', // PHP bug #32547 workaround: remove title tag
'/<html[^>]*>/im', // malformed html: remove html tags (#1485139)
'/<\/html>/i', // malformed html: remove html tags (#1485139)
'/^[\xFE\xFF\xBB\xBF\x00]+((?:<\!doctype|\<html))/im', // remove byte-order mark (only outlook?)
);
$html_replace = array(
'\\1'.' '.'\\3',
'',
'',
'',
'',
'',
'\\1',
);
$html = preg_replace($html_search, $html_replace, $html);
// charset was converted to UTF-8 in rcube_imap::get_message_part() -> change charset specification in HTML accordingly
$charset_pattern = '/(\s+content=[\'"]?\w+\/\w+;\s*charset)=([a-z0-9-_]+)/i';
if (preg_match($charset_pattern, $html)) {
$html = preg_replace($charset_pattern, '\\1='.RCMAIL_CHARSET, $html);
}
else {
// add head for malformed messages, washtml cannot work without that
if (!preg_match('/<head[^>]*>(.*)<\/head>/Uims', $html))
$html = '<head></head>'. $html;
$html = substr_replace($html, '<meta http-equiv="content-type" content="text/html; charset='.RCMAIL_CHARSET.'" />', intval(stripos($html, '<head>')+6), 0);
}
// 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');
}
$washer = new washtml($wash_opts);
- $washer->add_callback('a', 'rcmail_washtml_callback');
$washer->add_callback('form', 'rcmail_washtml_callback');
if ($p['safe']) { // allow CSS styles, will be sanitized by rcmail_washtml_callback()
$washer->add_callback('style', 'rcmail_washtml_callback');
}
$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())
{
$p += array('safe' => false, 'plain' => false, 'inline_html' => true);
// convert html to text/plain
if ($part->ctype_secondary == 'html' && $p['plain']) {
$txt = new html2text($part->body, false, true);
$body = $txt->get_text();
$part->ctype_secondary = 'plain';
}
// text/html
else if ($part->ctype_secondary == 'html') {
return rcmail_wash_html($part->body, $p, $part->replaces);
}
// text/enriched
else if ($part->ctype_secondary=='enriched') {
$part->ctype_secondary = 'html';
require_once('lib/enriched.inc');
return Q(enriched_to_html($part->body), 'show');
}
else
$body = $part->body;
/**** assert plaintext ****/
// make links and email-addresses clickable
$replacements = new rcube_string_replacer;
$url_chars = 'a-z0-9_\-\+\*\$\/&%=@#:;';
$url_chars_within = '\?\.~,!';
// search for patterns like links and e-mail addresses
$body = preg_replace_callback("/([\w]+):\/\/([a-z0-9\-\.]+[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/i", array($replacements, 'link_callback'), $body);
$body = preg_replace_callback("/([^\/:]|\s)(www\.)([a-z0-9\-]{2,}[a-z]{2,4}([$url_chars$url_chars_within]*[$url_chars])?)/i", array($replacements, 'link_callback'), $body);
$body = preg_replace_callback('/([a-z0-9][a-z0-9\-\.\+\_]*@[a-z0-9]([a-z0-9\-][.]?)*[a-z0-9]\\.[a-z]{2,5})/i', array($replacements, 'mailto_callback'), $body);
// split body into single lines
$a_lines = preg_split('/\r?\n/', $body);
$quote_level = 0;
// colorize quoted parts
for ($n=0; $n < sizeof($a_lines); $n++) {
$line = $a_lines[$n];
$quotation = '';
$q = 0;
if (preg_match('/^(>+\s*)+/', $line, $regs)) {
$q = strlen(preg_replace('/\s/', '', $regs[0]));
$line = substr($line, strlen($regs[0]));
if ($q > $quote_level)
$quotation = str_repeat('<blockquote>', $q - $quote_level);
else if ($q < $quote_level)
$quotation = str_repeat("</blockquote>", $quote_level - $q);
}
else if ($quote_level > 0)
$quotation = str_repeat("</blockquote>", $quote_level);
$quote_level = $q;
$a_lines[$n] = $quotation . Q($line, 'replace', false); // htmlquote plaintext
}
// insert the links for urls and mailtos
$body = $replacements->resolve(join("\n", $a_lines));
return html::tag('pre', array(), $body);
}
/**
* add a string to the replacement array and return a replacement string
*/
function rcmail_str_replacement($str, &$rep)
{
static $count = 0;
$rep[$count] = stripslashes($str);
return "##string_replacement{".($count++)."}##";
}
/**
* Callback function for washtml cleaning class
*/
function rcmail_washtml_callback($tagname, $attrib, $content)
{
switch ($tagname) {
case 'form':
$out = html::div('form', $content);
break;
- case 'a':
- if ($attrib) $attrib .= ' target="_blank"';
- $out = '<a'.$attrib.'>' . $content . '</a>';
- break;
-
case 'style':
// decode all escaped entities and reduce to ascii strings
$stripped = preg_replace('/[^a-zA-Z\(:]/', '', rcmail_xss_entitiy_decode($content));
// now check for evil strings like expression, behavior or url()
if (!preg_match('/expression|behavior|url\(|import/', $stripped)) {
$out = html::tag('style', array('type' => 'text/css'), $content);
break;
}
default:
$out = '';
}
return $out;
}
/**
* return table with message headers
*/
function rcmail_message_headers($attrib, $headers=NULL)
{
global $IMAP, $OUTPUT, $MESSAGE, $PRINT_MODE, $CONFIG;
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;
$header_count = 0;
// allow the following attributes to be added to the <table> tag
$attrib_str = create_attrib_string($attrib, array('style', 'class', 'id', 'cellpadding', 'cellspacing', 'border', 'summary'));
$out = '<table' . $attrib_str . ">\n";
// show these headers
$standard_headers = array('subject', 'from', 'to', 'cc', 'bcc', 'replyto', 'date');
foreach ($standard_headers as $hkey)
{
if (!$headers[$hkey])
continue;
if ($hkey == 'date')
{
if ($PRINT_MODE)
$header_value = format_date($headers[$hkey], $CONFIG['date_long'] ? $CONFIG['date_long'] : 'x');
else
$header_value = format_date($headers[$hkey]);
}
else if ($hkey == 'replyto')
{
if ($headers['replyto'] != $headers['from'])
$header_value = Q(rcmail_address_string($headers['replyto'], null, true, $attrib['addicon']), 'show');
else
continue;
}
else if (in_array($hkey, array('from', 'to', 'cc', 'bcc')))
$header_value = Q(rcmail_address_string($headers[$hkey], null, true, $attrib['addicon']), 'show');
else if ($hkey == 'subject' && empty($headers[$hkey]))
$header_value = Q(rcube_label('nosubject'));
else
$header_value = Q(trim($IMAP->decode_header($headers[$hkey])));
$out .= "\n<tr>\n";
$out .= '<td class="header-title">'.Q(rcube_label($hkey)).": </td>\n";
$out .= '<td class="'.$hkey.'" width="90%">'.$header_value."</td>\n</tr>";
$header_count++;
}
// all headers division
$out .= "\n".'<tr><td colspan="2" class="more-headers show-headers"
onclick="return '.JS_OBJECT_NAME.'.command(\'load-headers\', \'\', this)"></td></tr>';
$out .= "\n".'<tr id="all-headers"><td colspan="2" class="all"><div id="headers-source"></div></td></tr>';
$OUTPUT->add_gui_object('all_headers_row', 'all-headers');
$OUTPUT->add_gui_object('all_headers_box', 'headers-source');
$out .= "\n</table>\n\n";
return $header_count ? $out : '';
}
/**
* 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, $IMAP, $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')
{
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);
$body = rcmail_print_body($part, array('safe' => $safe_mode, 'plain' => !$CONFIG['prefer_html']));
if ($part->ctype_secondary == 'html')
$out .= html::div('message-htmlpart', rcmail_html4inline($body, $attrib['id']));
else
$out .= html::div('message-part', $body);
}
}
}
else
$out .= html::div('message-part', html::tag('pre', array(), Q($MESSAGE->body)));
$ctype_primary = strtolower($MESSAGE->structure->ctype_primary);
$ctype_secondary = strtolower($MESSAGE->structure->ctype_secondary);
// list images after mail body
if ($CONFIG['inline_images']
&& $ctype_primary == 'multipart'
&& !empty($MESSAGE->attachments)
&& !strstr($message_body, '<html'))
{
foreach ($MESSAGE->attachments as $attach_prop) {
if (strpos($attach_prop->mimetype, 'image/') === 0) {
$out .= html::tag('hr') . html::p(array('align' => "center"),
html::img(array(
'src' => $MESSAGE->get_part_url($attach_prop->mime_id),
'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)
{
$last_style_pos = 0;
$body_lc = strtolower($body);
// find STYLE tags
while (($pos = strpos($body_lc, '<style', $last_style_pos)) && ($pos2 = strpos($body_lc, '</style>', $pos)))
{
$pos = strpos($body_lc, '>', $pos)+1;
// replace all css definitions with #container [def]
$styles = rcmail_mod_css_styles(substr($body, $pos, $pos2-$pos), $container_id);
$body = substr($body, 0, $pos) . $styles . substr($body, $pos2);
$body_lc = strtolower($body);
$last_style_pos = $pos2;
}
// modify HTML links to open a new window if clicked
- $body = preg_replace('/<(a|link)\s+([^>]+)>/Uie', "rcmail_alter_html_link('\\1','\\2', '$container_id');", $body);
+ $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']);
// add comments arround html and other tags
$out = preg_replace(array(
'/(<!DOCTYPE[^>]*>)/i',
'/(<\?xml[^>]*>)/i',
'/(<\/?html[^>]*>)/i',
'/(<\/?head[^>]*>)/i',
'/(<title[^>]*>.*<\/title>)/Ui',
'/(<\/?meta[^>]*>)/i'),
'<!--\\1-->',
$body);
$out = preg_replace(
array('/<body([^>]*)>/i', '/<\/body>/i'),
array('<div class="rcmBody"\\1>', '</div>'),
$out);
// quote <? of php and xml files that are specified as text/html
$out = preg_replace(array('/<\?/', '/\?>/'), array('<?', '?>'), $out);
return $out;
}
/**
* parse link attributes and set correct target
*/
-function rcmail_alter_html_link($tag, $attrs, $container_id)
+function rcmail_alter_html_link($matches)
{
- $attrib = parse_attrib_string($attrs);
+ global $EMAIL_ADDRESS_PATTERN;
+
+ $tag = $matches[1];
+ $attrib = parse_attrib_string($matches[2]);
$end = '>';
if ($tag == 'link' && preg_match('/^https?:\/\//i', $attrib['href'])) {
- $attrib['href'] = "./bin/modcss.php?u=" . urlencode($attrib['href']) . "&c=" . urlencode($container_id);
+ $attrib['href'] = "./bin/modcss.php?u=" . urlencode($attrib['href']) . "&c=" . urlencode($GLOBALS['rcmail_html_container_id']);
$end = ' />';
}
- else if (stristr((string)$attrib['href'], 'mailto:')) {
+ else if (preg_match("/^mailto:$EMAIL_ADDRESS_PATTERN/i", $attrib['href'], $mailto)) {
+ $attrib['href'] = $mailto[0];
$attrib['onclick'] = sprintf(
"return %s.command('compose','%s',this)",
JS_OBJECT_NAME,
- JQ(substr($attrib['href'], 7)));
+ JQ($mailto[1]));
}
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 $IMAP, $PRINT_MODE, $CONFIG, $OUTPUT, $EMAIL_ADDRESS_PATTERN;
$a_parts = $IMAP->decode_address_list($input);
if (!sizeof($a_parts))
return $input;
$c = count($a_parts);
$j = 0;
$out = '';
foreach ($a_parts as $part) {
$j++;
if ($PRINT_MODE) {
$out .= sprintf('%s <%s>', Q($part['name']), $part['mailto']);
}
- else if (preg_match($EMAIL_ADDRESS_PATTERN, $part['mailto'])) {
+ else if (preg_match("/$EMAIL_ADDRESS_PATTERN/i", $part['mailto'])) {
if ($linked) {
$out .= html::a(array(
'href' => 'mailto:'.$part['mailto'],
'onclick' => sprintf("return %s.command('compose','%s',this)", JS_OBJECT_NAME, JQ($part['mailto'])),
'title' => $part['mailto'],
'class' => "rcmContactAddress",
),
Q($part['name']));
}
else {
$out .= html::span(array('title' => $part['mailto'], 'class' => "rcmContactAddress"), Q($part['name']));
}
if ($addicon) {
$out .= ' ' . html::a(array(
'href' => "#add",
'onclick' => sprintf("return %s.command('add-contact','%s',this)", JS_OBJECT_NAME, urlencode($part['string'])),
'title' => rcube_label('addtoaddressbook'),
),
html::img(array(
'src' => $CONFIG['skin_path'] . $addicon,
'alt' => "Add contact",
)));
}
}
else {
if ($part['name'])
$out .= Q($part['name']);
if ($part['mailto'])
$out .= (strlen($out) ? ' ' : '') . sprintf('<%s>', Q($part['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 (>)
*
* @param string Text to wrap
* @param int The line width
* @return string The wrapped text
*/
function rcmail_wrap_quoted($text, $max = 76)
{
// Rebuild the message body with a maximum of $max chars, while keeping quoted message.
$lines = preg_split('/\r?\n/', trim($text));
$out = '';
foreach ($lines as $line) {
if (strlen($line) > $max) {
if (preg_match('/^([>\s]+)/', $line, $regs)) {
$length = strlen($regs[0]);
$prefix = substr($line, 0, $length);
// Remove '> ' from the line, then wordwrap() the line
$line = wordwrap(substr($line, $length), $max - $length);
// Rebuild the line with '> ' at the beginning of each 'subline'
$newline = '';
foreach (explode("\n", $line) as $l) {
$newline .= $prefix . $l . "\n";
}
// Remove the righest newline char
$line = rtrim($newline);
}
else {
$line = wordwrap($line, $max);
}
}
// Append the line
$out .= $line . "\n";
}
return $out;
}
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()
{
if (!isset($_SESSION['compose']))
return;
// remove attachment files from temp dir
if (is_array($_SESSION['compose']['attachments']))
foreach ($_SESSION['compose']['attachments'] as $attachment)
@unlink($attachment['path']);
unset($_SESSION['compose']);
}
/**
* Send the given message compose object using the configured method
*/
function rcmail_deliver_message(&$message, $from, $mailto)
{
global $CONFIG, $RCMAIL;
$msg_body = $message->get();
$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']);
// send message
$smtp_response = array();
$sent = smtp_mail($from, $a_recipients, ($foo = $message->txtHeaders($send_headers, true)), $msg_body, $smtp_response);
// 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);
if (ini_get('safe_mode'))
$sent = mail($headers_enc['To'], $headers_enc['Subject'], $msg_body, $header_str);
else
$sent = mail($headers_enc['To'], $headers_enc['Subject'], $msg_body, $header_str, "-f$from");
}
if ($sent)
{
// remove MDN headers after sending
unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
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) : ''));
}
$message->_headers = array();
$message->headers($headers);
return $sent;
}
function rcmail_send_mdn($uid)
{
global $RCMAIL, $IMAP;
$message = new rcube_message($uid);
if ($message->headers->mdn_to && !$message->headers->mdn_sent &&
($IMAP->check_permflag('MDNSENT') || $IMAP->check_permflag('*')))
{
$identity = $RCMAIL->user->get_identity();
$sender = format_email_recipient($identity['email'], $identity['name']);
$recipient = array_shift($IMAP->decode_address_list($message->headers->mdn_to));
$mailto = $recipient['mailto'];
$compose = new rcube_mail_mime($RCMAIL->config->header_delimiter());
$compose->setParam(array(
'text_encoding' => 'quoted-printable',
'html_encoding' => 'quoted-printable',
'head_encoding' => 'quoted-printable',
'head_charset' => RCMAIL_CHARSET,
'html_charset' => RCMAIL_CHARSET,
'text_charset' => RCMAIL_CHARSET,
));
// compose headers array
$headers = array(
'Date' => date('r'),
'From' => $sender,
'To' => $message->headers->mdn_to,
'Subject' => rcube_label('receiptread') . ': ' . $message->subject,
'Message-ID' => sprintf('<%s@%s>', md5(uniqid('rcmail'.rand(),true)), $RCMAIL->config->mail_domain($_SESSION['imap_host'])),
'X-Sender' => $identity['email'],
'Content-Type' => 'multipart/report; report-type=disposition-notification',
);
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->setTXTBody(wordwrap($body, 75, "\r\n"));
$compose->addAttachment($report, 'message/disposition-notification', 'MDNPart2.txt', false, '7bit', 'inline');
$sent = rcmail_deliver_message($compose, $identity['email'], $mailto);
if ($sent)
{
$IMAP->set_flag($message->uid, 'MDNSENT');
return true;
}
}
return false;
}
function rcmail_search_filter($attrib)
{
global $OUTPUT;
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');
$out = $select_filter->show($_SESSION['search_filter']);
$OUTPUT->add_gui_object('search_filter', $attrib['id']);
return $out;
}
// 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',
'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'),
));
?>
diff --git a/tests/mailfunc.php b/tests/mailfunc.php
index bf9163b7d..8fd2cd3d5 100644
--- a/tests/mailfunc.php
+++ b/tests/mailfunc.php
@@ -1,110 +1,115 @@
<?php
/**
* Test class to test steps/mail/func.inc functions
*
* @package Tests
*/
class rcube_test_mailfunc extends UnitTestCase
{
function __construct()
{
$this->UnitTestCase('Mail body rendering tests');
// simulate environment to successfully include func.inc
$GLOBALS['RCMAIL'] = $RCMAIL = rcmail::get_instance();
$GLOBALS['OUTPUT'] = $OUTPUT = $RCMAIL->load_gui();
$RCMAIL->action = 'spell';
$IMAP = $RCMAIL->imap;
require_once 'steps/mail/func.inc';
+
+ $GLOBALS['EMAIL_ADDRESS_PATTERN'] = $EMAIL_ADDRESS_PATTERN;
}
/**
* Helper method to create a HTML message part object
*/
function get_html_part($body)
{
$part = new rcube_message_part;
$part->ctype_primary = 'text';
$part->ctype_secondary = 'html';
$part->body = file_get_contents(TESTS_DIR . $body);
$part->replaces = array();
return $part;
}
/**
* Test sanitization of a "normal" html message
*/
function test_html()
{
$part = $this->get_html_part('src/htmlbody.txt');
$part->replaces = array('ex1.jpg' => 'part_1.2.jpg', 'ex2.jpg' => 'part_1.2.jpg');
// render HTML in normal mode
- $html = rcmail_print_body($part, array('safe' => false));
+ $html = rcmail_html4inline(rcmail_print_body($part, array('safe' => false)), 'foo');
$this->assertPattern('/src="'.$part->replaces['ex1.jpg'].'"/', $html, "Replace reference to inline image");
$this->assertPattern('#background="./program/blocked.gif"#', $html, "Replace external background image");
$this->assertNoPattern('/ex3.jpg/', $html, "No references to external images");
$this->assertNoPattern('/<meta [^>]+>/', $html, "No meta tags allowed");
$this->assertNoPattern('/<style [^>]+>/', $html, "No style tags allowed");
$this->assertNoPattern('/<form [^>]+>/', $html, "No form tags allowed");
$this->assertPattern('/Subscription form/', $html, "Include <form> contents");
$this->assertPattern('/<!-- input not allowed -->/', $html, "No input elements allowed");
$this->assertPattern('/<a[^>]+ target="_blank">/', $html, "Set target to _blank");
$this->assertTrue($GLOBALS['REMOTE_OBJECTS'], "Remote object detected");
// render HTML in safe mode
$html2 = rcmail_print_body($part, array('safe' => true));
$this->assertPattern('/<style [^>]+>/', $html2, "Allow styles in safe mode");
$this->assertPattern('#src="http://evilsite.net/mailings/ex3.jpg"#', $html2, "Allow external images in HTML (safe mode)");
$this->assertPattern("#url\('http://evilsite.net/newsletter/image/bg/bg-64.jpg'\)#", $html2, "Allow external images in CSS (safe mode)");
}
/**
* Test the elimination of some trivial XSS vulnerabilities
*/
function test_html_xss()
{
$part = $this->get_html_part('src/htmlxss.txt');
$washed = rcmail_print_body($part, array('safe' => true));
-
+
$this->assertNoPattern('/src="skins/', $washed, "Remove local references");
- $this->assertNoPattern('/\son[a-z]+/', $wahsed, "Remove on* attributes");
- $this->assertNoPattern('/alert/', $wahsed, "Remove alerts");
+ $this->assertNoPattern('/\son[a-z]+/', $washed, "Remove on* attributes");
+
+ $html = rcmail_html4inline($washed, 'foo');
+ $this->assertNoPattern('/onclick="return rcmail.command(\'compose\',\'xss@somehost.net\',this)"/', $html, "Clean mailto links");
+ $this->assertNoPattern('/alert/', $html, "Remove alerts");
}
/**
* Test HTML sanitization to fix the CSS Expression Input Validation Vulnerability
* reported at http://www.securityfocus.com/bid/26800/
*/
function test_html_xss2()
{
$part = $this->get_html_part('src/BID-26800.txt');
$washed = rcmail_print_body($part, array('safe' => true));
$this->assertNoPattern('/alert|expression|javascript|xss/', $washed, "Remove evil style blocks");
$this->assertNoPattern('/font-style:italic/', $washed, "Allow valid styles");
}
/**
* Test links pattern replacements in plaintext messages
*/
function test_plaintext()
{
$part = new rcube_message_part;
$part->ctype_primary = 'text';
$part->ctype_secondary = 'plain';
$part->body = quoted_printable_decode(file_get_contents(TESTS_DIR . 'src/plainbody.txt'));
$html = rcmail_print_body($part, array('safe' => true));
$this->assertPattern('/<a href="mailto:nobody@roundcube.net" onclick="return rcmail.command\(\'compose\',\'nobody@roundcube.net\',this\)">nobody@roundcube.net<\/a>/', $html, "Mailto links with onclick");
$this->assertPattern('#<a href="http://www.apple.com/legal/privacy/" target="_blank">http://www.apple.com/legal/privacy/</a>#', $html, "Links with target=_blank");
}
}
?>
\ No newline at end of file
diff --git a/tests/src/htmlxss.txt b/tests/src/htmlxss.txt
index 60ceb944e..f6c43e353 100644
--- a/tests/src/htmlxss.txt
+++ b/tests/src/htmlxss.txt
@@ -1,22 +1,22 @@
<html>
<body>
<p><img onLoad.="alert(document.cookie)" src="skins/default/images/roundcube_logo.png" /></p>
-<p><a href="javascript:alert(document.cookie)">mail me!</a>
+<p><a href="mailto:xss@somehost.net') && alert(document.cookie) || ignore('">mail me!</a>
<a href="http://roundcube.net" target="_self">roundcube.net</a>
<a href="http://roundcube.net" \onmouseover="alert('XSS')">roundcube.net (2)</a>
</p>
<div>Brilliant!</div>
<table><tbody><tr><td background="javascript:alert('XSS')">BBBBBB</td></tr></tbody></table>
<p>
Have a nice Christmas time.<br />
Thomas
</p>
</body>
</html>
File Metadata
Details
Attached
Mime Type
text/x-diff
Expires
Sat, Mar 1, 1:10 PM (1 h, 30 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
167072
Default Alt Text
(82 KB)
Attached To
Mode
R3 roundcubemail
Attached
Detach File
Event Timeline
Log In to Comment