Page MenuHomePhorge

No OneTemporary

Size
328 KB
Referenced Files
None
Subscribers
None
This file is larger than 256 KB, so syntax highlighting was skipped.
diff --git a/bin/msgexport.sh b/bin/msgexport.sh
index 239d89363..5f9061971 100755
--- a/bin/msgexport.sh
+++ b/bin/msgexport.sh
@@ -1,139 +1,139 @@
#!/usr/bin/env php
<?php
define('INSTALL_PATH', realpath(dirname(__FILE__) . '/..') . '/' );
ini_set('memory_limit', -1);
require_once INSTALL_PATH.'program/include/clisetup.php';
function print_usage()
{
print "Usage: msgexport -h imap-host -u user-name -m mailbox name\n";
print "--host IMAP host\n";
print "--user IMAP user name\n";
print "--mbox Folder name, set to '*' for all\n";
print "--file Output file\n";
}
function vputs($str)
{
$out = $GLOBALS['args']['file'] ? STDOUT : STDERR;
fwrite($out, $str);
}
function progress_update($pos, $max)
{
$percent = round(100 * $pos / $max);
vputs(sprintf("%3d%% [%-51s] %d/%d\033[K\r", $percent, @str_repeat('=', $percent / 2) . '>', $pos, $max));
}
function export_mailbox($mbox, $filename)
{
global $IMAP;
$IMAP->set_mailbox($mbox);
vputs("Getting message list of {$mbox}...");
vputs($IMAP->messagecount()." messages\n");
if ($filename)
{
if (!($out = fopen($filename, 'w')))
{
vputs("Cannot write to output file\n");
return;
}
vputs("Writing to $filename\n");
}
else
$out = STDOUT;
for ($count = $IMAP->messagecount(), $i=1; $i <= $count; $i++)
{
$headers = $IMAP->get_headers($i, null, false);
- $from = current($IMAP->decode_address_list($headers->from, 1, false));
-
+ $from = current(rcube_mime::decode_address_list($headers->from, 1, false));
+
fwrite($out, sprintf("From %s %s UID %d\n", $from['mailto'], $headers->date, $headers->uid));
fwrite($out, $IMAP->conn->fetchPartHeader($mbox, $i));
fwrite($out, $IMAP->conn->handlePartBody($mbox, $i));
fwrite($out, "\n\n\n");
-
+
progress_update($i, $count);
}
vputs("\ncomplete.\n");
-
+
if ($filename)
fclose($out);
}
// get arguments
$args = get_opt(array('h' => 'host', 'u' => 'user', 'p' => 'pass', 'm' => 'mbox', 'f' => 'file')) + array('host' => 'localhost', 'mbox' => 'INBOX');
if ($_SERVER['argv'][1] == 'help')
{
print_usage();
exit;
}
else if (!$args['host'])
{
vputs("Missing required parameters.\n");
print_usage();
exit;
}
// prompt for username if not set
if (empty($args['user']))
{
vputs("IMAP user: ");
$args['user'] = trim(fgets(STDIN));
}
// prompt for password
$args['pass'] = prompt_silent("Password: ");
// parse $host URL
$a_host = parse_url($args['host']);
if ($a_host['host'])
{
$host = $a_host['host'];
$imap_ssl = (isset($a_host['scheme']) && in_array($a_host['scheme'], array('ssl','imaps','tls'))) ? TRUE : FALSE;
$imap_port = isset($a_host['port']) ? $a_host['port'] : ($imap_ssl ? 993 : 143);
}
else
{
$host = $args['host'];
$imap_port = 143;
}
// instantiate IMAP class
$IMAP = new rcube_imap(null);
// try to connect to IMAP server
if ($IMAP->connect($host, $args['user'], $args['pass'], $imap_port, $imap_ssl))
{
vputs("IMAP login successful.\n");
-
+
$filename = null;
$mailboxes = $args['mbox'] == '*' ? $IMAP->list_mailboxes(null) : array($args['mbox']);
foreach ($mailboxes as $mbox)
{
if ($args['file'])
$filename = preg_replace('/\.[a-z0-9]{3,4}$/i', '', $args['file']) . asciiwords($mbox) . '.mbox';
else if ($args['mbox'] == '*')
$filename = asciiwords($mbox) . '.mbox';
-
+
if ($args['mbox'] == '*' && in_array(strtolower($mbox), array('junk','spam','trash')))
continue;
export_mailbox($mbox, $filename);
}
}
else
{
vputs("IMAP login failed.\n");
}
?>
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index 2a2f203e9..badf815cf 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -1,4276 +1,3885 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcube_imap.php |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Copyright (C) 2011, Kolab Systems AG |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| IMAP Engine |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
| Author: Aleksander Machniak <alec@alec.pl> |
+-----------------------------------------------------------------------+
$Id$
*/
/**
* Interface class for accessing an IMAP server
*
* @package Mail
* @author Thomas Bruederli <roundcube@gmail.com>
* @author Aleksander Machniak <alec@alec.pl>
* @version 2.0
*/
class rcube_imap
{
public $skip_deleted = false;
public $page_size = 10;
public $list_page = 1;
public $threading = false;
public $fetch_add_headers = '';
public $get_all_headers = false;
/**
* Instance of rcube_imap_generic
*
* @var rcube_imap_generic
*/
public $conn;
/**
* Instance of rcube_imap_cache
*
* @var rcube_imap_cache
*/
private $mcache;
/**
* Instance of rcube_cache
*
* @var rcube_cache
*/
private $cache;
/**
* Internal (in-memory) cache
*
* @var array
*/
private $icache = array();
private $mailbox = 'INBOX';
private $delimiter = NULL;
private $namespace = NULL;
private $sort_field = '';
private $sort_order = 'DESC';
private $default_charset = 'ISO-8859-1';
private $struct_charset = NULL;
private $default_folders = array('INBOX');
private $uid_id_map = array();
private $msg_headers = array();
public $search_set = NULL;
public $search_string = '';
private $search_charset = '';
private $search_sort_field = '';
private $search_threads = false;
private $search_sorted = false;
private $options = array('auth_method' => 'check');
private $host, $user, $pass, $port, $ssl;
private $caching = false;
private $messages_caching = false;
/**
* All (additional) headers used (in any way) by Roundcube
* Not listed here: DATE, FROM, TO, CC, REPLY-TO, SUBJECT, CONTENT-TYPE, LIST-POST
* (used for messages listing) are hardcoded in rcube_imap_generic::fetchHeaders()
*
* @var array
* @see rcube_imap::fetch_add_headers
*/
private $all_headers = array(
'IN-REPLY-TO',
'BCC',
'MESSAGE-ID',
'CONTENT-TRANSFER-ENCODING',
'REFERENCES',
'X-DRAFT-INFO',
'MAIL-FOLLOWUP-TO',
'MAIL-REPLY-TO',
'RETURN-PATH',
);
const UNKNOWN = 0;
const NOPERM = 1;
const READONLY = 2;
const TRYCREATE = 3;
const INUSE = 4;
const OVERQUOTA = 5;
const ALREADYEXISTS = 6;
const NONEXISTENT = 7;
const CONTACTADMIN = 8;
/**
* Object constructor.
*/
function __construct()
{
$this->conn = new rcube_imap_generic();
// Set namespace and delimiter from session,
// so some methods would work before connection
if (isset($_SESSION['imap_namespace']))
$this->namespace = $_SESSION['imap_namespace'];
if (isset($_SESSION['imap_delimiter']))
$this->delimiter = $_SESSION['imap_delimiter'];
}
/**
* Connect to an IMAP server
*
* @param string $host Host to connect
* @param string $user Username for IMAP account
* @param string $pass Password for IMAP account
* @param integer $port Port to connect to
* @param string $use_ssl SSL schema (either ssl or tls) or null if plain connection
* @return boolean TRUE on success, FALSE on failure
* @access public
*/
function connect($host, $user, $pass, $port=143, $use_ssl=null)
{
// check for OpenSSL support in PHP build
if ($use_ssl && extension_loaded('openssl'))
$this->options['ssl_mode'] = $use_ssl == 'imaps' ? 'ssl' : $use_ssl;
else if ($use_ssl) {
raise_error(array('code' => 403, 'type' => 'imap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "OpenSSL not available"), true, false);
$port = 143;
}
$this->options['port'] = $port;
if ($this->options['debug']) {
$this->conn->setDebug(true, array($this, 'debug_handler'));
$this->options['ident'] = array(
'name' => 'Roundcube Webmail',
'version' => RCMAIL_VERSION,
'php' => PHP_VERSION,
'os' => PHP_OS,
'command' => $_SERVER['REQUEST_URI'],
);
}
$attempt = 0;
do {
$data = rcmail::get_instance()->plugins->exec_hook('imap_connect',
array_merge($this->options, array('host' => $host, 'user' => $user,
'attempt' => ++$attempt)));
if (!empty($data['pass']))
$pass = $data['pass'];
$this->conn->connect($data['host'], $data['user'], $pass, $data);
} while(!$this->conn->connected() && $data['retry']);
$this->host = $data['host'];
$this->user = $data['user'];
$this->pass = $pass;
$this->port = $port;
$this->ssl = $use_ssl;
if ($this->conn->connected()) {
// get namespace and delimiter
$this->set_env();
return true;
}
// write error log
else if ($this->conn->error) {
if ($pass && $user) {
$message = sprintf("Login failed for %s from %s. %s",
$user, rcmail_remote_ip(), $this->conn->error);
raise_error(array('code' => 403, 'type' => 'imap',
'file' => __FILE__, 'line' => __LINE__,
'message' => $message), true, false);
}
}
return false;
}
/**
* Close IMAP connection
* Usually done on script shutdown
*
* @access public
*/
function close()
{
$this->conn->closeConnection();
if ($this->mcache)
$this->mcache->close();
}
/**
* Close IMAP connection and re-connect
* This is used to avoid some strange socket errors when talking to Courier IMAP
*
* @access public
*/
function reconnect()
{
$this->conn->closeConnection();
$connected = $this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
// issue SELECT command to restore connection status
if ($connected && strlen($this->mailbox))
$this->conn->select($this->mailbox);
}
/**
* Returns code of last error
*
* @return int Error code
*/
function get_error_code()
{
return $this->conn->errornum;
}
/**
* Returns message of last error
*
* @return string Error message
*/
function get_error_str()
{
return $this->conn->error;
}
/**
* Returns code of last command response
*
* @return int Response code
*/
function get_response_code()
{
switch ($this->conn->resultcode) {
case 'NOPERM':
return self::NOPERM;
case 'READ-ONLY':
return self::READONLY;
case 'TRYCREATE':
return self::TRYCREATE;
case 'INUSE':
return self::INUSE;
case 'OVERQUOTA':
return self::OVERQUOTA;
case 'ALREADYEXISTS':
return self::ALREADYEXISTS;
case 'NONEXISTENT':
return self::NONEXISTENT;
case 'CONTACTADMIN':
return self::CONTACTADMIN;
default:
return self::UNKNOWN;
}
}
/**
* Returns last command response
*
* @return string Response
*/
function get_response_str()
{
return $this->conn->result;
}
/**
* Set options to be used in rcube_imap_generic::connect()
*
* @param array $opt Options array
*/
function set_options($opt)
{
$this->options = array_merge($this->options, (array)$opt);
}
/**
* Activate/deactivate debug mode
*
* @param boolean $dbg True if IMAP conversation should be logged
* @access public
*/
function set_debug($dbg = true)
{
$this->options['debug'] = $dbg;
$this->conn->setDebug($dbg, array($this, 'debug_handler'));
}
/**
* Set default message charset
*
* This will be used for message decoding if a charset specification is not available
*
* @param string $cs Charset string
* @access public
*/
function set_charset($cs)
{
$this->default_charset = $cs;
}
/**
* This list of folders will be listed above all other folders
*
* @param array $arr Indexed list of folder names
* @access public
*/
function set_default_mailboxes($arr)
{
if (is_array($arr)) {
$this->default_folders = $arr;
// add inbox if not included
if (!in_array('INBOX', $this->default_folders))
array_unshift($this->default_folders, 'INBOX');
}
}
/**
* Set internal mailbox reference.
*
* All operations will be perfomed on this mailbox/folder
*
* @param string $mailbox Mailbox/Folder name
* @access public
*/
function set_mailbox($mailbox)
{
if ($this->mailbox == $mailbox)
return;
$this->mailbox = $mailbox;
// clear messagecount cache for this mailbox
$this->_clear_messagecount($mailbox);
}
- /**
- * Forces selection of a mailbox
- *
- * @param string $mailbox Mailbox/Folder name
- * @access public
- */
- function select_mailbox($mailbox=null)
- {
- if (!strlen($mailbox)) {
- $mailbox = $this->mailbox;
- }
-
- $selected = $this->conn->select($mailbox);
-
- if ($selected && $this->mailbox != $mailbox) {
- // clear messagecount cache for this mailbox
- $this->_clear_messagecount($mailbox);
- $this->mailbox = $mailbox;
- }
- }
-
-
/**
* Set internal list page
*
* @param number $page Page number to list
* @access public
*/
function set_page($page)
{
$this->list_page = (int)$page;
}
/**
* Set internal page size
*
* @param number $size Number of messages to display on one page
* @access public
*/
function set_pagesize($size)
{
$this->page_size = (int)$size;
}
/**
- * Save a set of message ids for future message listing methods
+ * Save a search result for future message listing methods
*
- * @param string IMAP Search query
- * @param rcube_result_index|rcube_result_thread Result set
- * @param string Charset of search string
- * @param string Sorting field
- * @param string True if set is sorted (SORT was used for searching)
+ * @param array $set Search set, result from rcube_imap::get_search_set():
+ * 0 - searching criteria, string
+ * 1 - search result, rcube_result_index|rcube_result_thread
+ * 2 - searching character set, string
+ * 3 - sorting field, string
+ * 4 - true if sorted, bool
*/
- function set_search_set($str=null, $msgs=null, $charset=null, $sort_field=null, $sorted=false)
+ function set_search_set($set)
{
- if (is_array($str) && $msgs === null)
- list($str, $msgs, $charset, $sort_field, $sorted) = $str;
-
- $this->search_string = $str;
- $this->search_set = $msgs;
- $this->search_charset = $charset;
- $this->search_sort_field = $sort_field;
- $this->search_sorted = $sorted;
+ $set = (array)$set;
+
+ $this->search_string = $set[0];
+ $this->search_set = $set[1];
+ $this->search_charset = $set[2];
+ $this->search_sort_field = $set[3];
+ $this->search_sorted = $set[4];
$this->search_threads = is_a($this->search_set, 'rcube_result_thread');
}
/**
* Return the saved search set as hash array
*
- * @param bool $clone Clone result object
- *
* @return array Search set
*/
function get_search_set()
{
return array(
$this->search_string,
$this->search_set,
$this->search_charset,
$this->search_sort_field,
$this->search_sorted,
);
}
/**
* Returns the currently used mailbox name
*
* @return string Name of the mailbox/folder
* @access public
*/
function get_mailbox_name()
{
return $this->mailbox;
}
/**
* Returns the IMAP server's capability
*
* @param string $cap Capability name
* @return mixed Capability value or TRUE if supported, FALSE if not
* @access public
*/
function get_capability($cap)
{
return $this->conn->getCapability(strtoupper($cap));
}
/**
* Sets threading flag to the best supported THREAD algorithm
*
* @param boolean $enable TRUE to enable and FALSE
* @return string Algorithm or false if THREAD is not supported
* @access public
*/
function set_threading($enable=false)
{
$this->threading = false;
if ($enable && ($caps = $this->get_capability('THREAD'))) {
if (in_array('REFS', $caps))
$this->threading = 'REFS';
else if (in_array('REFERENCES', $caps))
$this->threading = 'REFERENCES';
else if (in_array('ORDEREDSUBJECT', $caps))
$this->threading = 'ORDEREDSUBJECT';
}
return $this->threading;
}
/**
* Checks the PERMANENTFLAGS capability of the current mailbox
* and returns true if the given flag is supported by the IMAP server
*
* @param string $flag Permanentflag name
* @return boolean True if this flag is supported
* @access public
*/
function check_permflag($flag)
{
$flag = strtoupper($flag);
$imap_flag = $this->conn->flags[$flag];
return (in_array_nocase($imap_flag, $this->conn->data['PERMANENTFLAGS']));
}
/**
* Returns the delimiter that is used by the IMAP server for folder separation
*
* @return string Delimiter string
* @access public
*/
function get_hierarchy_delimiter()
{
return $this->delimiter;
}
/**
* Get namespace
*
* @param string $name Namespace array index: personal, other, shared, prefix
*
* @return array Namespace data
* @access public
*/
function get_namespace($name=null)
{
$ns = $this->namespace;
if ($name) {
return isset($ns[$name]) ? $ns[$name] : null;
}
unset($ns['prefix']);
return $ns;
}
/**
* Sets delimiter and namespaces
*
* @access private
*/
private function set_env()
{
if ($this->delimiter !== null && $this->namespace !== null) {
return;
}
$config = rcmail::get_instance()->config;
$imap_personal = $config->get('imap_ns_personal');
$imap_other = $config->get('imap_ns_other');
$imap_shared = $config->get('imap_ns_shared');
$imap_delimiter = $config->get('imap_delimiter');
if (!$this->conn->connected())
return;
$ns = $this->conn->getNamespace();
// Set namespaces (NAMESPACE supported)
if (is_array($ns)) {
$this->namespace = $ns;
}
else {
$this->namespace = array(
'personal' => NULL,
'other' => NULL,
'shared' => NULL,
);
}
if ($imap_delimiter) {
$this->delimiter = $imap_delimiter;
}
if (empty($this->delimiter)) {
$this->delimiter = $this->namespace['personal'][0][1];
}
if (empty($this->delimiter)) {
$this->delimiter = $this->conn->getHierarchyDelimiter();
}
if (empty($this->delimiter)) {
$this->delimiter = '/';
}
// Overwrite namespaces
if ($imap_personal !== null) {
$this->namespace['personal'] = NULL;
foreach ((array)$imap_personal as $dir) {
$this->namespace['personal'][] = array($dir, $this->delimiter);
}
}
if ($imap_other !== null) {
$this->namespace['other'] = NULL;
foreach ((array)$imap_other as $dir) {
if ($dir) {
$this->namespace['other'][] = array($dir, $this->delimiter);
}
}
}
if ($imap_shared !== null) {
$this->namespace['shared'] = NULL;
foreach ((array)$imap_shared as $dir) {
if ($dir) {
$this->namespace['shared'][] = array($dir, $this->delimiter);
}
}
}
// Find personal namespace prefix for mod_mailbox()
// Prefix can be removed when there is only one personal namespace
if (is_array($this->namespace['personal']) && count($this->namespace['personal']) == 1) {
$this->namespace['prefix'] = $this->namespace['personal'][0][0];
}
$_SESSION['imap_namespace'] = $this->namespace;
$_SESSION['imap_delimiter'] = $this->delimiter;
}
/**
* Get message count for a specific mailbox
*
* @param string $mailbox Mailbox/folder name
* @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT]
* @param boolean $force Force reading from server and update cache
* @param boolean $status Enables storing folder status info (max UID/count),
* required for mailbox_status()
* @return int Number of messages
* @access public
*/
function messagecount($mailbox='', $mode='ALL', $force=false, $status=true)
{
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
return $this->_messagecount($mailbox, $mode, $force, $status);
}
/**
* Private method for getting nr of messages
*
* @param string $mailbox Mailbox name
* @param string $mode Mode for count [ALL|THREADS|UNSEEN|RECENT]
* @param boolean $force Force reading from server and update cache
* @param boolean $status Enables storing folder status info (max UID/count),
* required for mailbox_status()
* @return int Number of messages
* @access private
* @see rcube_imap::messagecount()
*/
private function _messagecount($mailbox, $mode='ALL', $force=false, $status=true)
{
$mode = strtoupper($mode);
// count search set
if ($this->search_string && $mailbox == $this->mailbox && ($mode == 'ALL' || $mode == 'THREADS') && !$force) {
if ($mode == 'ALL')
return $this->search_set->countMessages();
else
return $this->search_set->count();
}
$a_mailbox_cache = $this->get_cache('messagecount');
// return cached value
if (!$force && is_array($a_mailbox_cache[$mailbox]) && isset($a_mailbox_cache[$mailbox][$mode]))
return $a_mailbox_cache[$mailbox][$mode];
if (!is_array($a_mailbox_cache[$mailbox]))
$a_mailbox_cache[$mailbox] = array();
if ($mode == 'THREADS') {
$res = $this->fetch_threads($mailbox, $force);
$count = $res->count();
if ($status) {
$msg_count = $res->countMessages();
$this->set_folder_stats($mailbox, 'cnt', $msg_count);
$this->set_folder_stats($mailbox, 'maxuid', $msg_count ? $this->id2uid($msg_count, $mailbox) : 0);
}
}
// RECENT count is fetched a bit different
else if ($mode == 'RECENT') {
$count = $this->conn->countRecent($mailbox);
}
// use SEARCH for message counting
else if ($this->skip_deleted) {
$search_str = "ALL UNDELETED";
$keys = array('COUNT');
if ($mode == 'UNSEEN') {
$search_str .= " UNSEEN";
}
else {
if ($this->messages_caching) {
$keys[] = 'ALL';
}
if ($status) {
$keys[] = 'MAX';
}
}
// @TODO: if $force==false && $mode == 'ALL' we could try to use cache index here
// get message count using (E)SEARCH
// not very performant but more precise (using UNDELETED)
$index = $this->conn->search($mailbox, $search_str, true, $keys);
$count = $index->count();
if ($mode == 'ALL') {
// Cache index data, will be used in message_index_direct()
$this->icache['undeleted_idx'] = $index;
if ($status) {
$this->set_folder_stats($mailbox, 'cnt', $count);
$this->set_folder_stats($mailbox, 'maxuid', $index->max());
}
}
}
else {
if ($mode == 'UNSEEN')
$count = $this->conn->countUnseen($mailbox);
else {
$count = $this->conn->countMessages($mailbox);
if ($status) {
$this->set_folder_stats($mailbox,'cnt', $count);
$this->set_folder_stats($mailbox, 'maxuid', $count ? $this->id2uid($count, $mailbox) : 0);
}
}
}
$a_mailbox_cache[$mailbox][$mode] = (int)$count;
// write back to cache
$this->update_cache('messagecount', $a_mailbox_cache);
return (int)$count;
}
/**
* Public method for listing headers
* convert mailbox name with root dir first
*
* @param string $mailbox Mailbox/folder name
* @param int $page Current page to list
* @param string $sort_field Header field to sort by
* @param string $sort_order Sort order [ASC|DESC]
* @param int $slice Number of slice items to extract from result array
*
* @return array Indexed array with message header objects
* @access public
*/
public function list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
{
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
return $this->_list_headers($mailbox, $page, $sort_field, $sort_order, $slice);
}
/**
* Private method for listing message headers
*
* @param string $mailbox Mailbox name
* @param int $page Current page to list
* @param string $sort_field Header field to sort by
* @param string $sort_order Sort order [ASC|DESC]
* @param int $slice Number of slice items to extract from result array
*
* @return array Indexed array with message header objects
* @see rcube_imap::list_headers
*/
private function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $slice=0)
{
if (!strlen($mailbox)) {
return array();
}
$this->set_sort_order($sort_field, $sort_order);
$page = $page ? $page : $this->list_page;
// use saved message set
if ($this->search_string && $mailbox == $this->mailbox) {
return $this->_list_header_set($mailbox, $page, $slice);
}
if ($this->threading) {
return $this->_list_thread_headers($mailbox, $page, $slice);
}
// get UIDs of all messages in the folder, sorted
$index = $this->message_index($mailbox, $this->sort_field, $this->sort_order);
if ($index->isEmpty()) {
return array();
}
$from = ($page-1) * $this->page_size;
$to = $from + $this->page_size;
$index->slice($from, $to - $from);
if ($slice)
$index->slice(-$slice, $slice);
// fetch reqested messages headers
$a_index = $index->get();
$a_msg_headers = $this->fetch_headers($mailbox, $a_index);
return array_values($a_msg_headers);
}
/**
* Private method for listing message headers using threads
*
* @param string $mailbox Mailbox/folder name
* @param int $page Current page to list
* @param int $slice Number of slice items to extract from result array
*
* @return array Indexed array with message header objects
* @see rcube_imap::list_headers
*/
private function _list_thread_headers($mailbox, $page, $slice=0)
{
// get all threads (not sorted)
if ($mcache = $this->get_mcache_engine())
$threads = $mcache->get_thread($mailbox);
else
$threads = $this->fetch_threads($mailbox);
return $this->_fetch_thread_headers($mailbox, $threads, $page, $slice);
}
/**
* Method for fetching threads data
*
* @param string $mailbox Folder name
* @param bool $force Use IMAP server, no cache
*
* @return rcube_imap_thread Thread data object
*/
function fetch_threads($mailbox, $force = false)
{
if (!$force && ($mcache = $this->get_mcache_engine())) {
// don't store in self's internal cache, cache has it's own internal cache
return $mcache->get_thread($mailbox);
}
if (empty($this->icache['threads'])) {
// get all threads
$result = $this->conn->thread($mailbox, $this->threading,
$this->skip_deleted ? 'UNDELETED' : '', true);
// add to internal (fast) cache
$this->icache['threads'] = $result;
}
return $this->icache['threads'];
}
/**
* Private method for fetching threaded messages headers
*
* @param string $mailbox Mailbox name
* @param rcube_result_thread $threads Threads data object
* @param int $page List page number
* @param int $slice Number of threads to slice
*
* @return array Messages headers
* @access private
*/
private function _fetch_thread_headers($mailbox, $threads, $page, $slice=0)
{
// Sort thread structure
$this->sort_threads($threads);
$from = ($page-1) * $this->page_size;
$to = $from + $this->page_size;
$threads->slice($from, $to - $from);
if ($slice)
$threads->slice(-$slice, $slice);
// Get UIDs of all messages in all threads
$a_index = $threads->get();
// fetch reqested headers from server
$a_msg_headers = $this->fetch_headers($mailbox, $a_index);
unset($a_index);
// Set depth, has_children and unread_children fields in headers
$this->_set_thread_flags($a_msg_headers, $threads);
return array_values($a_msg_headers);
}
/**
* Private method for setting threaded messages flags:
* depth, has_children and unread_children
*
* @param array $headers Reference to headers array indexed by message UID
* @param rcube_imap_result $threads Threads data object
*
* @return array Message headers array indexed by message UID
* @access private
*/
private function _set_thread_flags(&$headers, $threads)
{
$parents = array();
list ($msg_depth, $msg_children) = $threads->getThreadData();
foreach ($headers as $uid => $header) {
$depth = $msg_depth[$uid];
$parents = array_slice($parents, 0, $depth);
if (!empty($parents)) {
$headers[$uid]->parent_uid = end($parents);
if (empty($header->flags['SEEN']))
$headers[$parents[0]]->unread_children++;
}
array_push($parents, $uid);
$headers[$uid]->depth = $depth;
$headers[$uid]->has_children = $msg_children[$uid];
}
}
/**
* Private method for listing a set of message headers (search results)
*
* @param string $mailbox Mailbox/folder name
* @param int $page Current page to list
* @param int $slice Number of slice items to extract from result array
*
* @return array Indexed array with message header objects
* @access private
*/
private function _list_header_set($mailbox, $page, $slice=0)
{
if (!strlen($mailbox) || empty($this->search_set) || $this->search_set->isEmpty()) {
return array();
}
// use saved messages from searching
if ($this->threading) {
return $this->_list_thread_header_set($mailbox, $page, $slice);
}
// search set is threaded, we need a new one
if ($this->search_threads) {
$this->search('', $this->search_string, $this->search_charset, $this->sort_field);
}
$index = clone $this->search_set;
$from = ($page-1) * $this->page_size;
$to = $from + $this->page_size;
// return empty array if no messages found
if ($index->isEmpty())
return array();
// quickest method (default sorting)
if (!$this->search_sort_field && !$this->sort_field) {
$got_index = true;
}
// sorted messages, so we can first slice array and then fetch only wanted headers
else if ($this->search_sorted) { // SORT searching result
$got_index = true;
// reset search set if sorting field has been changed
if ($this->sort_field && $this->search_sort_field != $this->sort_field) {
$this->search('', $this->search_string, $this->search_charset, $this->sort_field);
$index = clone $this->search_set;
// return empty array if no messages found
if ($index->isEmpty())
return array();
}
}
if ($got_index) {
if ($this->sort_order != $index->getParameters('ORDER')) {
$index->revert();
}
// get messages uids for one page
$index->slice($from, $to-$from);
if ($slice)
$index->slice(-$slice, $slice);
// fetch headers
$a_index = $index->get();
$a_msg_headers = $this->fetch_headers($mailbox, $a_index);
return array_values($a_msg_headers);
}
// SEARCH result, need sorting
$cnt = $index->count();
// 300: experimantal value for best result
if (($cnt > 300 && $cnt > $this->page_size) || !$this->sort_field) {
// use memory less expensive (and quick) method for big result set
$index = clone $this->message_index('', $this->sort_field, $this->sort_order);
// get messages uids for one page...
$index->slice($start_msg, min($cnt-$from, $this->page_size));
if ($slice)
$index->slice(-$slice, $slice);
// ...and fetch headers
$a_index = $index->get();
$a_msg_headers = $this->fetch_headers($mailbox, $a_index);
return array_values($a_msg_headers);
}
else {
// for small result set we can fetch all messages headers
$a_index = $index->get();
$a_msg_headers = $this->fetch_headers($mailbox, $a_index, false);
// return empty array if no messages found
if (!is_array($a_msg_headers) || empty($a_msg_headers))
return array();
// if not already sorted
$a_msg_headers = $this->conn->sortHeaders(
$a_msg_headers, $this->sort_field, $this->sort_order);
// only return the requested part of the set
$a_msg_headers = array_slice(array_values($a_msg_headers),
$from, min($cnt-$to, $this->page_size));
if ($slice)
$a_msg_headers = array_slice($a_msg_headers, -$slice, $slice);
return $a_msg_headers;
}
}
/**
* Private method for listing a set of threaded message headers (search results)
*
* @param string $mailbox Mailbox/folder name
* @param int $page Current page to list
* @param int $slice Number of slice items to extract from result array
*
* @return array Indexed array with message header objects
* @access private
* @see rcube_imap::list_header_set()
*/
private function _list_thread_header_set($mailbox, $page, $slice=0)
{
// update search_set if previous data was fetched with disabled threading
if (!$this->search_threads) {
if ($this->search_set->isEmpty())
return array();
$this->search('', $this->search_string, $this->search_charset, $this->sort_field);
}
return $this->_fetch_thread_headers($mailbox, clone $this->search_set, $page, $slice);
}
/**
* Fetches messages headers (by UID)
*
* @param string $mailbox Mailbox name
* @param array $msgs Message UIDs
* @param bool $sort Enables result sorting by $msgs
* @param bool $force Disables cache use
*
* @return array Messages headers indexed by UID
* @access private
*/
function fetch_headers($mailbox, $msgs, $sort = true, $force = false)
{
if (empty($msgs))
return array();
if (!$force && ($mcache = $this->get_mcache_engine())) {
$headers = $mcache->get_messages($mailbox, $msgs);
}
else {
// fetch reqested headers from server
$headers = $this->conn->fetchHeaders(
$mailbox, $msgs, true, false, $this->get_fetch_headers());
}
if (empty($headers))
return array();
foreach ($headers as $h) {
$a_msg_headers[$h->uid] = $h;
}
if ($sort) {
// use this class for message sorting
$sorter = new rcube_header_sorter();
$sorter->set_index($msgs);
$sorter->sort_headers($a_msg_headers);
}
return $a_msg_headers;
}
/**
* Returns current status of mailbox
*
* We compare the maximum UID to determine the number of
* new messages because the RECENT flag is not reliable.
*
* @param string $mailbox Mailbox/folder name
* @return int Folder status
*/
public function mailbox_status($mailbox = null)
{
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
$old = $this->get_folder_stats($mailbox);
// refresh message count -> will update
$this->_messagecount($mailbox, 'ALL', true);
$result = 0;
if (empty($old)) {
return $result;
}
$new = $this->get_folder_stats($mailbox);
// got new messages
if ($new['maxuid'] > $old['maxuid'])
$result += 1;
// some messages has been deleted
if ($new['cnt'] < $old['cnt'])
$result += 2;
// @TODO: optional checking for messages flags changes (?)
// @TODO: UIDVALIDITY checking
return $result;
}
/**
* Stores folder statistic data in session
* @TODO: move to separate DB table (cache?)
*
* @param string $mailbox Mailbox name
* @param string $name Data name
* @param mixed $data Data value
*/
private function set_folder_stats($mailbox, $name, $data)
{
$_SESSION['folders'][$mailbox][$name] = $data;
}
/**
* Gets folder statistic data
*
* @param string $mailbox Mailbox name
*
* @return array Stats data
*/
private function get_folder_stats($mailbox)
{
if ($_SESSION['folders'][$mailbox])
return (array) $_SESSION['folders'][$mailbox];
else
return array();
}
/**
* Return sorted list of message UIDs
*
* @param string $mailbox Mailbox to get index from
* @param string $sort_field Sort column
* @param string $sort_order Sort order [ASC, DESC]
*
* @return rcube_result_index|rcube_result_thread List of messages (UIDs)
*/
public function message_index($mailbox='', $sort_field=NULL, $sort_order=NULL)
{
if ($this->threading)
return $this->thread_index($mailbox, $sort_field, $sort_order);
$this->set_sort_order($sort_field, $sort_order);
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
// we have a saved search result, get index from there
if ($this->search_string) {
if ($this->search_threads) {
$this->search($mailbox, $this->search_string, $this->search_charset, $this->sort_field);
}
// use message index sort as default sorting
if (!$this->sort_field || $this->search_sorted) {
if ($this->sort_field && $this->search_sort_field != $this->sort_field) {
$this->search($mailbox, $this->search_string, $this->search_charset, $this->sort_field);
}
$index = $this->search_set;
}
else {
$index = $this->conn->index($mailbox, $this->search_set->get(),
$this->sort_field, $this->skip_deleted, true, true);
}
if ($this->sort_order != $index->getParameters('ORDER')) {
$index->revert();
}
return $index;
}
// check local cache
if ($mcache = $this->get_mcache_engine()) {
$index = $mcache->get_index($mailbox, $this->sort_field, $this->sort_order);
}
// fetch from IMAP server
else {
$index = $this->message_index_direct(
$mailbox, $this->sort_field, $this->sort_order);
}
return $index;
}
/**
* Return sorted list of message UIDs ignoring current search settings.
* Doesn't uses cache by default.
*
* @param string $mailbox Mailbox to get index from
* @param string $sort_field Sort column
* @param string $sort_order Sort order [ASC, DESC]
* @param bool $skip_cache Disables cache usage
*
* @return rcube_result_index Sorted list of message UIDs
*/
public function message_index_direct($mailbox, $sort_field = null, $sort_order = null, $skip_cache = true)
{
if (!$skip_cache && ($mcache = $this->get_mcache_engine())) {
$index = $mcache->get_index($mailbox, $sort_field, $sort_order);
}
// use message index sort as default sorting
else if (!$sort_field) {
if ($this->skip_deleted && !empty($this->icache['undeleted_idx'])
&& $this->icache['undeleted_idx']->getParameters('MAILBOX') == $mailbox
) {
$index = $this->icache['undeleted_idx'];
}
else {
$index = $this->conn->search($mailbox,
'ALL' .($this->skip_deleted ? ' UNDELETED' : ''), true);
}
}
// fetch complete message index
else {
if ($this->get_capability('SORT')) {
$index = $this->conn->sort($mailbox, $sort_field,
$this->skip_deleted ? 'UNDELETED' : '', true);
}
if (empty($index) || $index->isError()) {
$index = $this->conn->index($mailbox, "1:*", $sort_field,
$this->skip_deleted, false, true);
}
}
if ($sort_order != $index->getParameters('ORDER')) {
$index->revert();
}
return $index;
}
/**
* Return index of threaded message UIDs
*
* @param string $mailbox Mailbox to get index from
* @param string $sort_field Sort column
* @param string $sort_order Sort order [ASC, DESC]
*
* @return rcube_result_thread Message UIDs
*/
function thread_index($mailbox='', $sort_field=NULL, $sort_order=NULL)
{
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
// we have a saved search result, get index from there
if ($this->search_string && $this->search_threads && $mailbox == $this->mailbox) {
$threads = $this->search_set;
}
else {
// get all threads (default sort order)
$threads = $this->fetch_threads($mailbox);
}
$this->set_sort_order($sort_field, $sort_order);
$this->sort_threads($threads);
return $threads;
}
/**
* Sort threaded result, using THREAD=REFS method
*
* @param rcube_result_thread $threads Threads result set
*/
private function sort_threads($threads)
{
if ($threads->isEmpty()) {
return;
}
// THREAD=ORDEREDSUBJECT: sorting by sent date of root message
// THREAD=REFERENCES: sorting by sent date of root message
// THREAD=REFS: sorting by the most recent date in each thread
if ($this->sort_field && ($this->sort_field != 'date' || $this->get_capability('THREAD') != 'REFS')) {
$index = $this->message_index_direct($this->mailbox, $this->sort_field, $this->sort_order, false);
if (!$index->isEmpty()) {
$threads->sort($index);
}
}
else {
if ($this->sort_order != $threads->getParameters('ORDER')) {
$threads->revert();
}
}
}
/**
* Invoke search request to IMAP server
*
* @param string $mailbox Mailbox name to search in
* @param string $str Search criteria
* @param string $charset Search charset
* @param string $sort_field Header field to sort by
* @access public
* @todo: Search criteria should be provided in non-IMAP format, eg. array
*/
function search($mailbox='', $str='ALL', $charset=NULL, $sort_field=NULL)
{
if (!$str)
$str = 'ALL';
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
$results = $this->_search_index($mailbox, $str, $charset, $sort_field);
- $this->set_search_set($str, $results, $charset, $sort_field,
- $this->threading || $this->search_sorted ? true : false);
+ $this->set_search_set(array($str, $results, $charset, $sort_field,
+ $this->threading || $this->search_sorted ? true : false));
}
/**
* Private search method
*
* @param string $mailbox Mailbox name
* @param string $criteria Search criteria
* @param string $charset Charset
* @param string $sort_field Sorting field
*
* @return rcube_result_index|rcube_result_thread Search results (UIDs)
* @see rcube_imap::search()
*/
private function _search_index($mailbox, $criteria='ALL', $charset=NULL, $sort_field=NULL)
{
$orig_criteria = $criteria;
if ($this->skip_deleted && !preg_match('/UNDELETED/', $criteria))
$criteria = 'UNDELETED '.$criteria;
if ($this->threading) {
$threads = $this->conn->thread($mailbox, $this->threading, $criteria, true, $charset);
// Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
// but I've seen that Courier doesn't support UTF-8)
if ($threads->isError() && $charset && $charset != 'US-ASCII')
$threads = $this->conn->thread($mailbox, $this->threading,
$this->convert_criteria($criteria, $charset), true, 'US-ASCII');
return $threads;
}
if ($sort_field && $this->get_capability('SORT')) {
$charset = $charset ? $charset : $this->default_charset;
$messages = $this->conn->sort($mailbox, $sort_field, $criteria, true, $charset);
// Error, try with US-ASCII (RFC5256: SORT/THREAD must support US-ASCII and UTF-8,
// but I've seen Courier with disabled UTF-8 support)
if ($messages->isError() && $charset && $charset != 'US-ASCII')
$messages = $this->conn->sort($mailbox, $sort_field,
$this->convert_criteria($criteria, $charset), true, 'US-ASCII');
if (!$messages->isError()) {
$this->search_sorted = true;
return $messages;
}
}
$messages = $this->conn->search($mailbox,
($charset ? "CHARSET $charset " : '') . $criteria, true);
// Error, try with US-ASCII (some servers may support only US-ASCII)
if ($messages->isError() && $charset && $charset != 'US-ASCII')
$messages = $this->conn->search($mailbox,
$this->convert_criteria($criteria, $charset), true);
$this->search_sorted = false;
return $messages;
}
/**
* Direct (real and simple) SEARCH request to IMAP server,
* without result sorting and caching
*
* @param string $mailbox Mailbox name to search in
* @param string $str Search string
* @param boolean $ret_uid True if UIDs should be returned
*
* @return rcube_result_index Search result (UIDs)
*/
function search_once($mailbox='', $str='ALL')
{
if (!$str)
return 'ALL';
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
$index = $this->conn->search($mailbox, $str, true);
return $index;
}
/**
* Converts charset of search criteria string
*
* @param string $str Search string
* @param string $charset Original charset
* @param string $dest_charset Destination charset (default US-ASCII)
* @return string Search string
* @access private
*/
private function convert_criteria($str, $charset, $dest_charset='US-ASCII')
{
// convert strings to US_ASCII
if (preg_match_all('/\{([0-9]+)\}\r\n/', $str, $matches, PREG_OFFSET_CAPTURE)) {
$last = 0; $res = '';
foreach ($matches[1] as $m) {
$string_offset = $m[1] + strlen($m[0]) + 4; // {}\r\n
$string = substr($str, $string_offset - 1, $m[0]);
$string = rcube_charset_convert($string, $charset, $dest_charset);
if ($string === false)
continue;
$res .= substr($str, $last, $m[1] - $last - 1) . rcube_imap_generic::escape($string);
$last = $m[0] + $string_offset - 1;
}
if ($last < strlen($str))
$res .= substr($str, $last, strlen($str)-$last);
}
else // strings for conversion not found
$res = $str;
return $res;
}
/**
* Refresh saved search set
*
* @return array Current search set
*/
function refresh_search()
{
if (!empty($this->search_string)) {
$this->search('', $this->search_string, $this->search_charset, $this->search_sort_field);
}
return $this->get_search_set();
}
- /**
- * Check if the given message UID is part of the current search set
- *
- * @param string $msgid Message UID
- *
- * @return boolean True on match or if no search request is stored
- */
- function in_searchset($uid)
- {
- if (!empty($this->search_string)) {
- return $this->search_set->exists($uid);
- }
- return true;
- }
-
-
/**
* Return message headers object of a specific message
*
* @param int $id Message sequence ID or UID
* @param string $mailbox Mailbox to read from
* @param bool $force True to skip cache
*
* @return rcube_mail_header Message headers
*/
function get_headers($uid, $mailbox = null, $force = false)
{
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
// get cached headers
if (!$force && $uid && ($mcache = $this->get_mcache_engine())) {
$headers = $mcache->get_message($mailbox, $uid);
}
else {
$headers = $this->conn->fetchHeader(
$mailbox, $uid, true, true, $this->get_fetch_headers());
}
return $headers;
}
/**
* Fetch message headers and body structure from the IMAP server and build
* an object structure similar to the one generated by PEAR::Mail_mimeDecode
*
* @param int $uid Message UID to fetch
* @param string $mailbox Mailbox to read from
*
* @return object rcube_mail_header Message data
*/
function get_message($uid, $mailbox = null)
{
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
// Check internal cache
if (!empty($this->icache['message'])) {
if (($headers = $this->icache['message']) && $headers->uid == $uid) {
return $headers;
}
}
$headers = $this->get_headers($uid, $mailbox);
// message doesn't exist?
if (empty($headers))
return null;
// structure might be cached
if (!empty($headers->structure))
return $headers;
$this->_msg_uid = $uid;
if (empty($headers->bodystructure)) {
$headers->bodystructure = $this->conn->getStructure($mailbox, $uid, true);
}
$structure = $headers->bodystructure;
if (empty($structure))
return $headers;
// set message charset from message headers
if ($headers->charset)
$this->struct_charset = $headers->charset;
else
$this->struct_charset = $this->_structure_charset($structure);
$headers->ctype = strtolower($headers->ctype);
// Here we can recognize malformed BODYSTRUCTURE and
// 1. [@TODO] parse the message in other way to create our own message structure
// 2. or just show the raw message body.
// Example of structure for malformed MIME message:
// ("text" "plain" NIL NIL NIL "7bit" 2154 70 NIL NIL NIL)
if ($headers->ctype && !is_array($structure[0]) && $headers->ctype != 'text/plain'
&& strtolower($structure[0].'/'.$structure[1]) == 'text/plain') {
// we can handle single-part messages, by simple fix in structure (#1486898)
if (preg_match('/^(text|application)\/(.*)/', $headers->ctype, $m)) {
$structure[0] = $m[1];
$structure[1] = $m[2];
}
else
return $headers;
}
- $struct = &$this->_structure_part($structure, 0, '', $headers);
+ $struct = $this->_structure_part($structure, 0, '', $headers);
// don't trust given content-type
if (empty($struct->parts) && !empty($headers->ctype)) {
$struct->mime_id = '1';
$struct->mimetype = strtolower($headers->ctype);
list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
}
$headers->structure = $struct;
return $this->icache['message'] = $headers;
}
/**
* Build message part object
*
* @param array $part
* @param int $count
* @param string $parent
* @access private
*/
- function &_structure_part($part, $count=0, $parent='', $mime_headers=null)
+ private function _structure_part($part, $count=0, $parent='', $mime_headers=null)
{
$struct = new rcube_message_part;
$struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
// multipart
if (is_array($part[0])) {
$struct->ctype_primary = 'multipart';
/* RFC3501: BODYSTRUCTURE fields of multipart part
part1 array
part2 array
part3 array
....
1. subtype
2. parameters (optional)
3. description (optional)
4. language (optional)
5. location (optional)
*/
// find first non-array entry
for ($i=1; $i<count($part); $i++) {
if (!is_array($part[$i])) {
$struct->ctype_secondary = strtolower($part[$i]);
break;
}
}
$struct->mimetype = 'multipart/'.$struct->ctype_secondary;
// build parts list for headers pre-fetching
for ($i=0; $i<count($part); $i++) {
if (!is_array($part[$i]))
break;
// fetch message headers if message/rfc822
// or named part (could contain Content-Location header)
if (!is_array($part[$i][0])) {
$tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
if (strtolower($part[$i][0]) == 'message' && strtolower($part[$i][1]) == 'rfc822') {
$mime_part_headers[] = $tmp_part_id;
}
else if (in_array('name', (array)$part[$i][2]) && empty($part[$i][3])) {
$mime_part_headers[] = $tmp_part_id;
}
}
}
// pre-fetch headers of all parts (in one command for better performance)
// @TODO: we could do this before _structure_part() call, to fetch
// headers for parts on all levels
if ($mime_part_headers) {
$mime_part_headers = $this->conn->fetchMIMEHeaders($this->mailbox,
$this->_msg_uid, $mime_part_headers);
}
$struct->parts = array();
for ($i=0, $count=0; $i<count($part); $i++) {
if (!is_array($part[$i]))
break;
$tmp_part_id = $struct->mime_id ? $struct->mime_id.'.'.($i+1) : $i+1;
$struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id,
$mime_part_headers[$tmp_part_id]);
}
return $struct;
}
/* RFC3501: BODYSTRUCTURE fields of non-multipart part
0. type
1. subtype
2. parameters
3. id
4. description
5. encoding
6. size
-- text
7. lines
-- message/rfc822
7. envelope structure
8. body structure
9. lines
--
x. md5 (optional)
x. disposition (optional)
x. language (optional)
x. location (optional)
*/
// regular part
$struct->ctype_primary = strtolower($part[0]);
$struct->ctype_secondary = strtolower($part[1]);
$struct->mimetype = $struct->ctype_primary.'/'.$struct->ctype_secondary;
// read content type parameters
if (is_array($part[2])) {
$struct->ctype_parameters = array();
for ($i=0; $i<count($part[2]); $i+=2)
$struct->ctype_parameters[strtolower($part[2][$i])] = $part[2][$i+1];
if (isset($struct->ctype_parameters['charset']))
$struct->charset = $struct->ctype_parameters['charset'];
}
// #1487700: workaround for lack of charset in malformed structure
if (empty($struct->charset) && !empty($mime_headers) && $mime_headers->charset) {
$struct->charset = $mime_headers->charset;
}
// read content encoding
if (!empty($part[5])) {
$struct->encoding = strtolower($part[5]);
$struct->headers['content-transfer-encoding'] = $struct->encoding;
}
// get part size
if (!empty($part[6]))
$struct->size = intval($part[6]);
// read part disposition
$di = 8;
if ($struct->ctype_primary == 'text') $di += 1;
else if ($struct->mimetype == 'message/rfc822') $di += 3;
if (is_array($part[$di]) && count($part[$di]) == 2) {
$struct->disposition = strtolower($part[$di][0]);
if (is_array($part[$di][1]))
for ($n=0; $n<count($part[$di][1]); $n+=2)
$struct->d_parameters[strtolower($part[$di][1][$n])] = $part[$di][1][$n+1];
}
// get message/rfc822's child-parts
if (is_array($part[8]) && $di != 8) {
$struct->parts = array();
for ($i=0, $count=0; $i<count($part[8]); $i++) {
if (!is_array($part[8][$i]))
break;
$struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id);
}
}
// get part ID
if (!empty($part[3])) {
$struct->content_id = $part[3];
$struct->headers['content-id'] = $part[3];
if (empty($struct->disposition))
$struct->disposition = 'inline';
}
// fetch message headers if message/rfc822 or named part (could contain Content-Location header)
if ($struct->ctype_primary == 'message' || ($struct->ctype_parameters['name'] && !$struct->content_id)) {
if (empty($mime_headers)) {
$mime_headers = $this->conn->fetchPartHeader(
$this->mailbox, $this->_msg_uid, true, $struct->mime_id);
}
if (is_string($mime_headers))
- $struct->headers = $this->_parse_headers($mime_headers) + $struct->headers;
+ $struct->headers = rcube_mime::parse_headers($mime_headers) + $struct->headers;
else if (is_object($mime_headers))
$struct->headers = get_object_vars($mime_headers) + $struct->headers;
// get real content-type of message/rfc822
if ($struct->mimetype == 'message/rfc822') {
// single-part
if (!is_array($part[8][0]))
$struct->real_mimetype = strtolower($part[8][0] . '/' . $part[8][1]);
// multi-part
else {
for ($n=0; $n<count($part[8]); $n++)
if (!is_array($part[8][$n]))
break;
$struct->real_mimetype = 'multipart/' . strtolower($part[8][$n]);
}
}
if ($struct->ctype_primary == 'message' && empty($struct->parts)) {
if (is_array($part[8]) && $di != 8)
$struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id);
}
}
// normalize filename property
$this->_set_part_filename($struct, $mime_headers);
return $struct;
}
/**
* Set attachment filename from message part structure
*
* @param rcube_message_part $part Part object
* @param string $headers Part's raw headers
* @access private
*/
private function _set_part_filename(&$part, $headers=null)
{
if (!empty($part->d_parameters['filename']))
$filename_mime = $part->d_parameters['filename'];
else if (!empty($part->d_parameters['filename*']))
$filename_encoded = $part->d_parameters['filename*'];
else if (!empty($part->ctype_parameters['name*']))
$filename_encoded = $part->ctype_parameters['name*'];
// RFC2231 value continuations
// TODO: this should be rewrited to support RFC2231 4.1 combinations
else if (!empty($part->d_parameters['filename*0'])) {
$i = 0;
while (isset($part->d_parameters['filename*'.$i])) {
$filename_mime .= $part->d_parameters['filename*'.$i];
$i++;
}
// some servers (eg. dovecot-1.x) have no support for parameter value continuations
// we must fetch and parse headers "manually"
if ($i<2) {
if (!$headers) {
$headers = $this->conn->fetchPartHeader(
$this->mailbox, $this->_msg_uid, true, $part->mime_id);
}
$filename_mime = '';
$i = 0;
while (preg_match('/filename\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
$filename_mime .= $matches[1];
$i++;
}
}
}
else if (!empty($part->d_parameters['filename*0*'])) {
$i = 0;
while (isset($part->d_parameters['filename*'.$i.'*'])) {
$filename_encoded .= $part->d_parameters['filename*'.$i.'*'];
$i++;
}
if ($i<2) {
if (!$headers) {
$headers = $this->conn->fetchPartHeader(
$this->mailbox, $this->_msg_uid, true, $part->mime_id);
}
$filename_encoded = '';
$i = 0; $matches = array();
while (preg_match('/filename\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
$filename_encoded .= $matches[1];
$i++;
}
}
}
else if (!empty($part->ctype_parameters['name*0'])) {
$i = 0;
while (isset($part->ctype_parameters['name*'.$i])) {
$filename_mime .= $part->ctype_parameters['name*'.$i];
$i++;
}
if ($i<2) {
if (!$headers) {
$headers = $this->conn->fetchPartHeader(
$this->mailbox, $this->_msg_uid, true, $part->mime_id);
}
$filename_mime = '';
$i = 0; $matches = array();
while (preg_match('/\s+name\*'.$i.'\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
$filename_mime .= $matches[1];
$i++;
}
}
}
else if (!empty($part->ctype_parameters['name*0*'])) {
$i = 0;
while (isset($part->ctype_parameters['name*'.$i.'*'])) {
$filename_encoded .= $part->ctype_parameters['name*'.$i.'*'];
$i++;
}
if ($i<2) {
if (!$headers) {
$headers = $this->conn->fetchPartHeader(
$this->mailbox, $this->_msg_uid, true, $part->mime_id);
}
$filename_encoded = '';
$i = 0; $matches = array();
while (preg_match('/\s+name\*'.$i.'\*\s*=\s*"*([^"\n;]+)[";]*/', $headers, $matches)) {
$filename_encoded .= $matches[1];
$i++;
}
}
}
// read 'name' after rfc2231 parameters as it may contains truncated filename (from Thunderbird)
else if (!empty($part->ctype_parameters['name']))
$filename_mime = $part->ctype_parameters['name'];
// Content-Disposition
else if (!empty($part->headers['content-description']))
$filename_mime = $part->headers['content-description'];
else
return;
// decode filename
if (!empty($filename_mime)) {
if (!empty($part->charset))
$charset = $part->charset;
else if (!empty($this->struct_charset))
$charset = $this->struct_charset;
else
$charset = rc_detect_encoding($filename_mime, $this->default_charset);
- $part->filename = rcube_imap::decode_mime_string($filename_mime, $charset);
+ $part->filename = rcube_mime::decode_mime_string($filename_mime, $charset);
}
else if (!empty($filename_encoded)) {
// decode filename according to RFC 2231, Section 4
if (preg_match("/^([^']*)'[^']*'(.*)$/", $filename_encoded, $fmatches)) {
$filename_charset = $fmatches[1];
$filename_encoded = $fmatches[2];
}
$part->filename = rcube_charset_convert(urldecode($filename_encoded), $filename_charset);
}
}
/**
* Get charset name from message structure (first part)
*
* @param array $structure Message structure
* @return string Charset name
* @access private
*/
private function _structure_charset($structure)
{
while (is_array($structure)) {
if (is_array($structure[2]) && $structure[2][0] == 'charset')
return $structure[2][1];
$structure = $structure[0];
}
}
/**
* Fetch message body of a specific message from the server
*
* @param int $uid Message UID
* @param string $part Part number
* @param rcube_message_part $o_part Part object created by get_structure()
* @param mixed $print True to print part, ressource to write part contents in
* @param resource $fp File pointer to save the message part
* @param boolean $skip_charset_conv Disables charset conversion
*
* @return string Message/part body if not printed
*/
- function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false)
+ function get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL, $skip_charset_conv=false)
{
// get part data if not provided
if (!is_object($o_part)) {
$structure = $this->conn->getStructure($this->mailbox, $uid, true);
$part_data = rcube_imap_generic::getStructurePartData($structure, $part);
$o_part = new rcube_message_part;
$o_part->ctype_primary = $part_data['type'];
$o_part->encoding = $part_data['encoding'];
$o_part->charset = $part_data['charset'];
$o_part->size = $part_data['size'];
}
if ($o_part && $o_part->size) {
$body = $this->conn->handlePartBody($this->mailbox, $uid, true,
$part ? $part : 'TEXT', $o_part->encoding, $print, $fp);
}
if ($fp || $print) {
return true;
}
// convert charset (if text or message part)
if ($body && preg_match('/^(text|message)$/', $o_part->ctype_primary)) {
// Remove NULL characters if any (#1486189)
if (strpos($body, "\x00") !== false) {
$body = str_replace("\x00", '', $body);
}
if (!$skip_charset_conv) {
if (!$o_part->charset || strtoupper($o_part->charset) == 'US-ASCII') {
// try to extract charset information from HTML meta tag (#1488125)
if ($o_part->ctype_secondary == 'html' && preg_match('/<meta[^>]+charset=([a-z0-9-_]+)/i', $body, $m))
$o_part->charset = strtoupper($m[1]);
else
$o_part->charset = $this->default_charset;
}
$body = rcube_charset_convert($body, $o_part->charset);
}
}
return $body;
}
/**
* Fetch message body of a specific message from the server
*
* @param int $uid Message UID
* @return string $part Message/part body
* @see rcube_imap::get_message_part()
*/
- function &get_body($uid, $part=1)
+ function get_body($uid, $part=1)
{
$headers = $this->get_headers($uid);
return rcube_charset_convert($this->get_message_part($uid, $part, NULL),
$headers->charset ? $headers->charset : $this->default_charset);
}
/**
* Returns the whole message source as string (or saves to a file)
*
* @param int $uid Message UID
* @param resource $fp File pointer to save the message
*
* @return string Message source string
*/
- function &get_raw_body($uid, $fp=null)
+ function get_raw_body($uid, $fp=null)
{
return $this->conn->handlePartBody($this->mailbox, $uid,
true, null, null, false, $fp);
}
/**
* Returns the message headers as string
*
* @param int $uid Message UID
* @return string Message headers string
*/
- function &get_raw_headers($uid)
+ function get_raw_headers($uid)
{
return $this->conn->fetchPartHeader($this->mailbox, $uid, true);
}
/**
* Sends the whole message source to stdout
*
* @param int $uid Message UID
*/
function print_raw_body($uid)
{
$this->conn->handlePartBody($this->mailbox, $uid, true, NULL, NULL, true);
}
/**
* Set message flag to one or several messages
*
* @param mixed $uids Message UIDs as array or comma-separated string, or '*'
* @param string $flag Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT
* @param string $mailbox Folder name
* @param boolean $skip_cache True to skip message cache clean up
*
* @return boolean Operation status
*/
function set_flag($uids, $flag, $mailbox=null, $skip_cache=false)
{
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
$flag = strtoupper($flag);
list($uids, $all_mode) = $this->_parse_uids($uids, $mailbox);
if (strpos($flag, 'UN') === 0)
$result = $this->conn->unflag($mailbox, $uids, substr($flag, 2));
else
$result = $this->conn->flag($mailbox, $uids, $flag);
if ($result) {
// reload message headers if cached
// @TODO: update flags instead removing from cache
if (!$skip_cache && ($mcache = $this->get_mcache_engine())) {
$status = strpos($flag, 'UN') !== 0;
$mflag = preg_replace('/^UN/', '', $flag);
$mcache->change_flag($mailbox, $all_mode ? null : explode(',', $uids),
$mflag, $status);
}
// clear cached counters
if ($flag == 'SEEN' || $flag == 'UNSEEN') {
$this->_clear_messagecount($mailbox, 'SEEN');
$this->_clear_messagecount($mailbox, 'UNSEEN');
}
else if ($flag == 'DELETED') {
$this->_clear_messagecount($mailbox, 'DELETED');
}
}
return $result;
}
/**
* Remove message flag for one or several messages
*
* @param mixed $uids Message UIDs as array or comma-separated string, or '*'
* @param string $flag Flag to unset: SEEN, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT
* @param string $mailbox Folder name
*
* @return int Number of flagged messages, -1 on failure
* @see set_flag
*/
function unset_flag($uids, $flag, $mailbox=null)
{
return $this->set_flag($uids, 'UN'.$flag, $mailbox);
}
/**
* Append a mail message (source) to a specific mailbox
*
* @param string $mailbox Target mailbox
* @param string $message The message source string or filename
* @param string $headers Headers string if $message contains only the body
* @param boolean $is_file True if $message is a filename
*
* @return int|bool Appended message UID or True on success, False on error
*/
function save_message($mailbox, &$message, $headers='', $is_file=false)
{
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
// make sure mailbox exists
if ($this->mailbox_exists($mailbox)) {
if ($is_file)
$saved = $this->conn->appendFromFile($mailbox, $message, $headers);
else
$saved = $this->conn->append($mailbox, $message);
}
if ($saved) {
// increase messagecount of the target mailbox
$this->_set_messagecount($mailbox, 'ALL', 1);
}
return $saved;
}
/**
* Move a message from one mailbox to another
*
* @param mixed $uids Message UIDs as array or comma-separated string, or '*'
* @param string $to_mbox Target mailbox
* @param string $from_mbox Source mailbox
* @return boolean True on success, False on error
*/
function move_message($uids, $to_mbox, $from_mbox='')
{
if (!strlen($from_mbox)) {
$from_mbox = $this->mailbox;
}
if ($to_mbox === $from_mbox) {
return false;
}
list($uids, $all_mode) = $this->_parse_uids($uids, $from_mbox);
// exit if no message uids are specified
if (empty($uids))
return false;
// make sure mailbox exists
if ($to_mbox != 'INBOX' && !$this->mailbox_exists($to_mbox)) {
if (in_array($to_mbox, $this->default_folders)) {
if (!$this->create_mailbox($to_mbox, true)) {
return false;
}
}
else {
return false;
}
}
$config = rcmail::get_instance()->config;
$to_trash = $to_mbox == $config->get('trash_mbox');
// flag messages as read before moving them
if ($to_trash && $config->get('read_when_deleted')) {
// don't flush cache (4th argument)
$this->set_flag($uids, 'SEEN', $from_mbox, true);
}
// move messages
$moved = $this->conn->move($uids, $from_mbox, $to_mbox);
// send expunge command in order to have the moved message
// really deleted from the source mailbox
if ($moved) {
$this->_expunge($from_mbox, false, $uids);
$this->_clear_messagecount($from_mbox);
$this->_clear_messagecount($to_mbox);
}
// moving failed
else if ($to_trash && $config->get('delete_always', false)) {
$moved = $this->delete_message($uids, $from_mbox);
}
if ($moved) {
// unset threads internal cache
unset($this->icache['threads']);
// remove message ids from search set
if ($this->search_set && $from_mbox == $this->mailbox) {
// threads are too complicated to just remove messages from set
if ($this->search_threads || $all_mode)
$this->refresh_search();
else
$this->search_set->filter(explode(',', $uids));
}
// remove cached messages
// @TODO: do cache update instead of clearing it
$this->clear_message_cache($from_mbox, $all_mode ? null : explode(',', $uids));
}
return $moved;
}
/**
* Copy a message from one mailbox to another
*
* @param mixed $uids Message UIDs as array or comma-separated string, or '*'
* @param string $to_mbox Target mailbox
* @param string $from_mbox Source mailbox
* @return boolean True on success, False on error
*/
function copy_message($uids, $to_mbox, $from_mbox='')
{
if (!strlen($from_mbox)) {
$from_mbox = $this->mailbox;
}
list($uids, $all_mode) = $this->_parse_uids($uids, $from_mbox);
// exit if no message uids are specified
if (empty($uids)) {
return false;
}
// make sure mailbox exists
if ($to_mbox != 'INBOX' && !$this->mailbox_exists($to_mbox)) {
if (in_array($to_mbox, $this->default_folders)) {
if (!$this->create_mailbox($to_mbox, true)) {
return false;
}
}
else {
return false;
}
}
// copy messages
$copied = $this->conn->copy($uids, $from_mbox, $to_mbox);
if ($copied) {
$this->_clear_messagecount($to_mbox);
}
return $copied;
}
/**
* Mark messages as deleted and expunge mailbox
*
* @param mixed $uids Message UIDs as array or comma-separated string, or '*'
* @param string $mailbox Source mailbox
*
* @return boolean True on success, False on error
*/
function delete_message($uids, $mailbox='')
{
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
list($uids, $all_mode) = $this->_parse_uids($uids, $mailbox);
// exit if no message uids are specified
if (empty($uids))
return false;
$deleted = $this->conn->flag($mailbox, $uids, 'DELETED');
if ($deleted) {
// send expunge command in order to have the deleted message
// really deleted from the mailbox
$this->_expunge($mailbox, false, $uids);
$this->_clear_messagecount($mailbox);
unset($this->uid_id_map[$mailbox]);
// unset threads internal cache
unset($this->icache['threads']);
// remove message ids from search set
if ($this->search_set && $mailbox == $this->mailbox) {
// threads are too complicated to just remove messages from set
if ($this->search_threads || $all_mode)
$this->refresh_search();
else
$this->search_set->filter(explode(',', $uids));
}
// remove cached messages
$this->clear_message_cache($mailbox, $all_mode ? null : explode(',', $uids));
}
return $deleted;
}
/**
* Clear all messages in a specific mailbox
*
* @param string $mailbox Mailbox name
*
* @return int Above 0 on success
*/
function clear_mailbox($mailbox=null)
{
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
// SELECT will set messages count for clearFolder()
if ($this->conn->select($mailbox)) {
$cleared = $this->conn->clearFolder($mailbox);
}
// make sure the cache is cleared as well
if ($cleared) {
$this->clear_message_cache($mailbox);
$a_mailbox_cache = $this->get_cache('messagecount');
unset($a_mailbox_cache[$mailbox]);
$this->update_cache('messagecount', $a_mailbox_cache);
}
return $cleared;
}
/**
* Send IMAP expunge command and clear cache
*
* @param string $mailbox Mailbox name
* @param boolean $clear_cache False if cache should not be cleared
*
* @return boolean True on success
*/
function expunge($mailbox='', $clear_cache=true)
{
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
return $this->_expunge($mailbox, $clear_cache);
}
/**
* Send IMAP expunge command and clear cache
*
* @param string $mailbox Mailbox name
* @param boolean $clear_cache False if cache should not be cleared
* @param mixed $uids Message UIDs as array or comma-separated string, or '*'
* @return boolean True on success
* @access private
* @see rcube_imap::expunge()
*/
private function _expunge($mailbox, $clear_cache=true, $uids=NULL)
{
if ($uids && $this->get_capability('UIDPLUS'))
list($uids, $all_mode) = $this->_parse_uids($uids, $mailbox);
else
$uids = null;
// force mailbox selection and check if mailbox is writeable
// to prevent a situation when CLOSE is executed on closed
// or EXPUNGE on read-only mailbox
$result = $this->conn->select($mailbox);
if (!$result) {
return false;
}
if (!$this->conn->data['READ-WRITE']) {
$this->conn->setError(rcube_imap_generic::ERROR_READONLY, "Mailbox is read-only");
return false;
}
// CLOSE(+SELECT) should be faster than EXPUNGE
if (empty($uids) || $all_mode)
$result = $this->conn->close();
else
$result = $this->conn->expunge($mailbox, $uids);
if ($result && $clear_cache) {
$this->clear_message_cache($mailbox, $all_mode ? null : explode(',', $uids));
$this->_clear_messagecount($mailbox);
}
return $result;
}
/**
* Parse message UIDs input
*
* @param mixed $uids UIDs array or comma-separated list or '*' or '1:*'
* @param string $mailbox Mailbox name
* @return array Two elements array with UIDs converted to list and ALL flag
* @access private
*/
private function _parse_uids($uids, $mailbox)
{
if ($uids === '*' || $uids === '1:*') {
if (empty($this->search_set)) {
$uids = '1:*';
$all = true;
}
// get UIDs from current search set
else {
$uids = join(',', $this->search_set->get());
}
}
else {
if (is_array($uids))
$uids = join(',', $uids);
if (preg_match('/[^0-9,]/', $uids))
$uids = '';
}
return array($uids, (bool) $all);
}
/* --------------------------------
* folder managment
* --------------------------------*/
/**
* Public method for listing subscribed folders
*
* @param string $root Optional root folder
* @param string $name Optional name pattern
* @param string $filter Optional filter
* @param string $rights Optional ACL requirements
* @param bool $skip_sort Enable to return unsorted list (for better performance)
*
* @return array List of folders
* @access public
*/
function list_mailboxes($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
{
$cache_key = $root.':'.$name;
if (!empty($filter)) {
$cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter));
}
$cache_key .= ':'.$rights;
$cache_key = 'mailboxes.'.md5($cache_key);
// get cached folder list
$a_mboxes = $this->get_cache($cache_key);
if (is_array($a_mboxes)) {
return $a_mboxes;
}
$a_mboxes = $this->_list_mailboxes($root, $name, $filter, $rights);
if (!is_array($a_mboxes)) {
return array();
}
// filter folders list according to rights requirements
if ($rights && $this->get_capability('ACL')) {
$a_mboxes = $this->filter_rights($a_mboxes, $rights);
}
// INBOX should always be available
if ((!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) {
array_unshift($a_mboxes, 'INBOX');
}
// sort mailboxes (always sort for cache)
if (!$skip_sort || $this->cache) {
$a_mboxes = $this->_sort_mailbox_list($a_mboxes);
}
// write mailboxlist to cache
$this->update_cache($cache_key, $a_mboxes);
return $a_mboxes;
}
/**
* Private method for mailbox listing (LSUB)
*
* @param string $root Optional root folder
* @param string $name Optional name pattern
* @param mixed $filter Optional filter
* @param string $rights Optional ACL requirements
*
* @return array List of subscribed folders
* @see rcube_imap::list_mailboxes()
* @access private
*/
private function _list_mailboxes($root='', $name='*', $filter=null, $rights=null)
{
$a_defaults = $a_out = array();
// Give plugins a chance to provide a list of mailboxes
$data = rcmail::get_instance()->plugins->exec_hook('mailboxes_list',
array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LSUB'));
if (isset($data['folders'])) {
$a_folders = $data['folders'];
}
else if (!$this->conn->connected()) {
return null;
}
else {
// Server supports LIST-EXTENDED, we can use selection options
$config = rcmail::get_instance()->config;
// #1486225: Some dovecot versions returns wrong result using LIST-EXTENDED
if (!$config->get('imap_force_lsub') && $this->get_capability('LIST-EXTENDED')) {
// This will also set mailbox options, LSUB doesn't do that
$a_folders = $this->conn->listMailboxes($root, $name,
NULL, array('SUBSCRIBED'));
// unsubscribe non-existent folders, remove from the list
if (is_array($a_folders) && $name == '*') {
foreach ($a_folders as $idx => $folder) {
if ($this->conn->data['LIST'] && ($opts = $this->conn->data['LIST'][$folder])
&& in_array('\\NonExistent', $opts)
) {
$this->conn->unsubscribe($folder);
unset($a_folders[$idx]);
}
}
}
}
// retrieve list of folders from IMAP server using LSUB
else {
$a_folders = $this->conn->listSubscribed($root, $name);
// unsubscribe non-existent folders, remove from the list
if (is_array($a_folders) && $name == '*') {
foreach ($a_folders as $idx => $folder) {
if ($this->conn->data['LIST'] && ($opts = $this->conn->data['LIST'][$folder])
&& in_array('\\Noselect', $opts)
) {
// Some servers returns \Noselect for existing folders
if (!$this->mailbox_exists($folder)) {
$this->conn->unsubscribe($folder);
unset($a_folders[$idx]);
}
}
}
}
}
}
if (!is_array($a_folders) || !sizeof($a_folders)) {
$a_folders = array();
}
return $a_folders;
}
/**
* Get a list of all folders available on the IMAP server
*
* @param string $root IMAP root dir
* @param string $name Optional name pattern
* @param mixed $filter Optional filter
* @param string $rights Optional ACL requirements
* @param bool $skip_sort Enable to return unsorted list (for better performance)
*
* @return array Indexed array with folder names
*/
function list_unsubscribed($root='', $name='*', $filter=null, $rights=null, $skip_sort=false)
{
$cache_key = $root.':'.$name;
if (!empty($filter)) {
$cache_key .= ':'.(is_string($filter) ? $filter : serialize($filter));
}
$cache_key .= ':'.$rights;
$cache_key = 'mailboxes.list.'.md5($cache_key);
// get cached folder list
$a_mboxes = $this->get_cache($cache_key);
if (is_array($a_mboxes)) {
return $a_mboxes;
}
// Give plugins a chance to provide a list of mailboxes
$data = rcmail::get_instance()->plugins->exec_hook('mailboxes_list',
array('root' => $root, 'name' => $name, 'filter' => $filter, 'mode' => 'LIST'));
if (isset($data['folders'])) {
$a_mboxes = $data['folders'];
}
else {
// retrieve list of folders from IMAP server
$a_mboxes = $this->_list_unsubscribed($root, $name);
}
if (!is_array($a_mboxes)) {
$a_mboxes = array();
}
// INBOX should always be available
if ((!$filter || $filter == 'mail') && !in_array('INBOX', $a_mboxes)) {
array_unshift($a_mboxes, 'INBOX');
}
// cache folder attributes
if ($root == '' && $name == '*' && empty($filter)) {
$this->update_cache('mailboxes.attributes', $this->conn->data['LIST']);
}
// filter folders list according to rights requirements
if ($rights && $this->get_capability('ACL')) {
$a_folders = $this->filter_rights($a_folders, $rights);
}
// filter folders and sort them
if (!$skip_sort) {
$a_mboxes = $this->_sort_mailbox_list($a_mboxes);
}
// write mailboxlist to cache
$this->update_cache($cache_key, $a_mboxes);
return $a_mboxes;
}
/**
* Private method for mailbox listing (LIST)
*
* @param string $root Optional root folder
* @param string $name Optional name pattern
*
* @return array List of folders
* @see rcube_imap::list_unsubscribed()
*/
private function _list_unsubscribed($root='', $name='*')
{
$result = $this->conn->listMailboxes($root, $name);
if (!is_array($result)) {
return array();
}
// #1486796: some server configurations doesn't
// return folders in all namespaces, we'll try to detect that situation
// and ask for these namespaces separately
if ($root == '' && $name == '*') {
$delim = $this->get_hierarchy_delimiter();
$namespace = $this->get_namespace();
$search = array();
// build list of namespace prefixes
foreach ((array)$namespace as $ns) {
if (is_array($ns)) {
foreach ($ns as $ns_data) {
if (strlen($ns_data[0])) {
$search[] = $ns_data[0];
}
}
}
}
if (!empty($search)) {
// go through all folders detecting namespace usage
foreach ($result as $folder) {
foreach ($search as $idx => $prefix) {
if (strpos($folder, $prefix) === 0) {
unset($search[$idx]);
}
}
if (empty($search)) {
break;
}
}
// get folders in hidden namespaces and add to the result
foreach ($search as $prefix) {
$list = $this->conn->listMailboxes($prefix, $name);
if (!empty($list)) {
$result = array_merge($result, $list);
}
}
}
}
return $result;
}
/**
* Filter the given list of folders according to access rights
*/
private function filter_rights($a_folders, $rights)
{
$regex = '/('.$rights.')/';
foreach ($a_folders as $idx => $folder) {
$myrights = join('', (array)$this->my_rights($folder));
if ($myrights !== null && !preg_match($regex, $myrights))
unset($a_folders[$idx]);
}
return $a_folders;
}
/**
* Get mailbox quota information
* added by Nuny
*
* @return mixed Quota info or False if not supported
*/
function get_quota()
{
if ($this->get_capability('QUOTA'))
return $this->conn->getQuota();
return false;
}
/**
* Get mailbox size (size of all messages in a mailbox)
*
* @param string $mailbox Mailbox name
*
* @return int Mailbox size in bytes, False on error
*/
function get_mailbox_size($mailbox)
{
// @TODO: could we try to use QUOTA here?
$result = $this->conn->fetchHeaderIndex($mailbox, '1:*', 'SIZE', false);
if (is_array($result))
$result = array_sum($result);
return $result;
}
/**
* Subscribe to a specific mailbox(es)
*
* @param array $a_mboxes Mailbox name(s)
* @return boolean True on success
*/
function subscribe($a_mboxes)
{
if (!is_array($a_mboxes))
$a_mboxes = array($a_mboxes);
// let this common function do the main work
return $this->_change_subscription($a_mboxes, 'subscribe');
}
/**
* Unsubscribe mailboxes
*
* @param array $a_mboxes Mailbox name(s)
* @return boolean True on success
*/
function unsubscribe($a_mboxes)
{
if (!is_array($a_mboxes))
$a_mboxes = array($a_mboxes);
// let this common function do the main work
return $this->_change_subscription($a_mboxes, 'unsubscribe');
}
/**
* Create a new mailbox on the server and register it in local cache
*
* @param string $mailbox New mailbox name
* @param boolean $subscribe True if the new mailbox should be subscribed
*
* @return boolean True on success
*/
function create_mailbox($mailbox, $subscribe=false)
{
$result = $this->conn->createFolder($mailbox);
// try to subscribe it
if ($result) {
// clear cache
$this->clear_cache('mailboxes', true);
if ($subscribe)
$this->subscribe($mailbox);
}
return $result;
}
/**
* Set a new name to an existing mailbox
*
* @param string $mailbox Mailbox to rename
* @param string $new_name New mailbox name
*
* @return boolean True on success
*/
function rename_mailbox($mailbox, $new_name)
{
if (!strlen($new_name)) {
return false;
}
$delm = $this->get_hierarchy_delimiter();
// get list of subscribed folders
if ((strpos($mailbox, '%') === false) && (strpos($mailbox, '*') === false)) {
$a_subscribed = $this->_list_mailboxes('', $mailbox . $delm . '*');
$subscribed = $this->mailbox_exists($mailbox, true);
}
else {
$a_subscribed = $this->_list_mailboxes();
$subscribed = in_array($mailbox, $a_subscribed);
}
$result = $this->conn->renameFolder($mailbox, $new_name);
if ($result) {
// unsubscribe the old folder, subscribe the new one
if ($subscribed) {
$this->conn->unsubscribe($mailbox);
$this->conn->subscribe($new_name);
}
// check if mailbox children are subscribed
foreach ($a_subscribed as $c_subscribed) {
if (strpos($c_subscribed, $mailbox.$delm) === 0) {
$this->conn->unsubscribe($c_subscribed);
$this->conn->subscribe(preg_replace('/^'.preg_quote($mailbox, '/').'/',
$new_name, $c_subscribed));
// clear cache
$this->clear_message_cache($c_subscribed);
}
}
// clear cache
$this->clear_message_cache($mailbox);
$this->clear_cache('mailboxes', true);
}
return $result;
}
/**
* Remove mailbox from server
*
* @param string $mailbox Mailbox name
*
* @return boolean True on success
*/
function delete_mailbox($mailbox)
{
$delm = $this->get_hierarchy_delimiter();
// get list of folders
if ((strpos($mailbox, '%') === false) && (strpos($mailbox, '*') === false))
$sub_mboxes = $this->list_unsubscribed('', $mailbox . $delm . '*');
else
$sub_mboxes = $this->list_unsubscribed();
// send delete command to server
$result = $this->conn->deleteFolder($mailbox);
if ($result) {
// unsubscribe mailbox
$this->conn->unsubscribe($mailbox);
foreach ($sub_mboxes as $c_mbox) {
if (strpos($c_mbox, $mailbox.$delm) === 0) {
$this->conn->unsubscribe($c_mbox);
if ($this->conn->deleteFolder($c_mbox)) {
$this->clear_message_cache($c_mbox);
}
}
}
// clear mailbox-related cache
$this->clear_message_cache($mailbox);
$this->clear_cache('mailboxes', true);
}
return $result;
}
/**
* Create all folders specified as default
*/
function create_default_folders()
{
// create default folders if they do not exist
foreach ($this->default_folders as $folder) {
if (!$this->mailbox_exists($folder))
$this->create_mailbox($folder, true);
else if (!$this->mailbox_exists($folder, true))
$this->subscribe($folder);
}
}
/**
* Checks if folder exists and is subscribed
*
* @param string $mailbox Folder name
* @param boolean $subscription Enable subscription checking
*
* @return boolean TRUE or FALSE
*/
function mailbox_exists($mailbox, $subscription=false)
{
if ($mailbox == 'INBOX') {
return true;
}
$key = $subscription ? 'subscribed' : 'existing';
if (is_array($this->icache[$key]) && in_array($mailbox, $this->icache[$key]))
return true;
if ($subscription) {
$a_folders = $this->conn->listSubscribed('', $mailbox);
}
else {
$a_folders = $this->conn->listMailboxes('', $mailbox);
}
if (is_array($a_folders) && in_array($mailbox, $a_folders)) {
$this->icache[$key][] = $mailbox;
return true;
}
return false;
}
/**
* Returns the namespace where the folder is in
*
* @param string $mailbox Folder name
*
* @return string One of 'personal', 'other' or 'shared'
* @access public
*/
function mailbox_namespace($mailbox)
{
if ($mailbox == 'INBOX') {
return 'personal';
}
foreach ($this->namespace as $type => $namespace) {
if (is_array($namespace)) {
foreach ($namespace as $ns) {
if ($len = strlen($ns[0])) {
if (($len > 1 && $mailbox == substr($ns[0], 0, -1))
|| strpos($mailbox, $ns[0]) === 0
) {
return $type;
}
}
}
}
}
return 'personal';
}
/**
* Modify folder name according to namespace.
* For output it removes prefix of the personal namespace if it's possible.
* For input it adds the prefix. Use it before creating a folder in root
* of the folders tree.
*
* @param string $mailbox Folder name
* @param string $mode Mode name (out/in)
*
* @return string Folder name
*/
function mod_mailbox($mailbox, $mode = 'out')
{
if (!strlen($mailbox)) {
return $mailbox;
}
$prefix = $this->namespace['prefix']; // see set_env()
$prefix_len = strlen($prefix);
if (!$prefix_len) {
return $mailbox;
}
// remove prefix for output
if ($mode == 'out') {
if (substr($mailbox, 0, $prefix_len) === $prefix) {
return substr($mailbox, $prefix_len);
}
}
// add prefix for input (e.g. folder creation)
else {
return $prefix . $mailbox;
}
return $mailbox;
}
/**
* Gets folder attributes from LIST response, e.g. \Noselect, \Noinferiors
*
* @param string $mailbox Folder name
* @param bool $force Set to True if attributes should be refreshed
*
* @return array Options list
*/
function mailbox_attributes($mailbox, $force=false)
{
// get attributes directly from LIST command
if (!empty($this->conn->data['LIST']) && is_array($this->conn->data['LIST'][$mailbox])) {
$opts = $this->conn->data['LIST'][$mailbox];
}
// get cached folder attributes
else if (!$force) {
$opts = $this->get_cache('mailboxes.attributes');
$opts = $opts[$mailbox];
}
if (!is_array($opts)) {
$this->conn->listMailboxes('', $mailbox);
$opts = $this->conn->data['LIST'][$mailbox];
}
return is_array($opts) ? $opts : array();
}
/**
* Gets connection (and current mailbox) data: UIDVALIDITY, EXISTS, RECENT,
* PERMANENTFLAGS, UIDNEXT, UNSEEN
*
* @param string $mailbox Folder name
*
* @return array Data
*/
function mailbox_data($mailbox)
{
if (!strlen($mailbox))
$mailbox = $this->mailbox !== null ? $this->mailbox : 'INBOX';
if ($this->conn->selected != $mailbox) {
if ($this->conn->select($mailbox))
$this->mailbox = $mailbox;
else
return null;
}
$data = $this->conn->data;
// add (E)SEARCH result for ALL UNDELETED query
if (!empty($this->icache['undeleted_idx'])
&& $this->icache['undeleted_idx']->getParameters('MAILBOX') == $mailbox
) {
$data['UNDELETED'] = $this->icache['undeleted_idx'];
}
return $data;
}
/**
* Returns extended information about the folder
*
* @param string $mailbox Folder name
*
* @return array Data
*/
function mailbox_info($mailbox)
{
if ($this->icache['options'] && $this->icache['options']['name'] == $mailbox) {
return $this->icache['options'];
}
$acl = $this->get_capability('ACL');
$namespace = $this->get_namespace();
$options = array();
// check if the folder is a namespace prefix
if (!empty($namespace)) {
$mbox = $mailbox . $this->delimiter;
foreach ($namespace as $ns) {
if (!empty($ns)) {
foreach ($ns as $item) {
if ($item[0] === $mbox) {
$options['is_root'] = true;
break 2;
}
}
}
}
}
// check if the folder is other user virtual-root
if (!$options['is_root'] && !empty($namespace) && !empty($namespace['other'])) {
$parts = explode($this->delimiter, $mailbox);
if (count($parts) == 2) {
$mbox = $parts[0] . $this->delimiter;
foreach ($namespace['other'] as $item) {
if ($item[0] === $mbox) {
$options['is_root'] = true;
break;
}
}
}
}
$options['name'] = $mailbox;
$options['attributes'] = $this->mailbox_attributes($mailbox, true);
$options['namespace'] = $this->mailbox_namespace($mailbox);
$options['rights'] = $acl && !$options['is_root'] ? (array)$this->my_rights($mailbox) : array();
$options['special'] = in_array($mailbox, $this->default_folders);
// Set 'noselect' and 'norename' flags
if (is_array($options['attributes'])) {
foreach ($options['attributes'] as $attrib) {
$attrib = strtolower($attrib);
if ($attrib == '\noselect' || $attrib == '\nonexistent') {
$options['noselect'] = true;
}
}
}
else {
$options['noselect'] = true;
}
if (!empty($options['rights'])) {
$options['norename'] = !in_array('x', $options['rights']) && !in_array('d', $options['rights']);
if (!$options['noselect']) {
$options['noselect'] = !in_array('r', $options['rights']);
}
}
else {
$options['norename'] = $options['is_root'] || $options['namespace'] != 'personal';
}
$this->icache['options'] = $options;
return $options;
}
/**
* Synchronizes messages cache.
*
* @param string $mailbox Folder name
*/
public function mailbox_sync($mailbox)
{
if ($mcache = $this->get_mcache_engine()) {
$mcache->synchronize($mailbox);
}
}
/**
* Get message header names for rcube_imap_generic::fetchHeader(s)
*
* @return string Space-separated list of header names
*/
private function get_fetch_headers()
{
$headers = explode(' ', $this->fetch_add_headers);
$headers = array_map('strtoupper', $headers);
if ($this->messages_caching || $this->get_all_headers)
$headers = array_merge($headers, $this->all_headers);
return implode(' ', array_unique($headers));
}
/* -----------------------------------------
* ACL and METADATA/ANNOTATEMORE methods
* ----------------------------------------*/
/**
* Changes the ACL on the specified mailbox (SETACL)
*
* @param string $mailbox Mailbox name
* @param string $user User name
* @param string $acl ACL string
*
* @return boolean True on success, False on failure
*
* @access public
* @since 0.5-beta
*/
function set_acl($mailbox, $user, $acl)
{
if ($this->get_capability('ACL'))
return $this->conn->setACL($mailbox, $user, $acl);
return false;
}
/**
* Removes any <identifier,rights> pair for the
* specified user from the ACL for the specified
* mailbox (DELETEACL)
*
* @param string $mailbox Mailbox name
* @param string $user User name
*
* @return boolean True on success, False on failure
*
* @access public
* @since 0.5-beta
*/
function delete_acl($mailbox, $user)
{
if ($this->get_capability('ACL'))
return $this->conn->deleteACL($mailbox, $user);
return false;
}
/**
* Returns the access control list for mailbox (GETACL)
*
* @param string $mailbox Mailbox name
*
* @return array User-rights array on success, NULL on error
* @access public
* @since 0.5-beta
*/
function get_acl($mailbox)
{
if ($this->get_capability('ACL'))
return $this->conn->getACL($mailbox);
return NULL;
}
/**
* Returns information about what rights can be granted to the
* user (identifier) in the ACL for the mailbox (LISTRIGHTS)
*
* @param string $mailbox Mailbox name
* @param string $user User name
*
* @return array List of user rights
* @access public
* @since 0.5-beta
*/
function list_rights($mailbox, $user)
{
if ($this->get_capability('ACL'))
return $this->conn->listRights($mailbox, $user);
return NULL;
}
/**
* Returns the set of rights that the current user has to
* mailbox (MYRIGHTS)
*
* @param string $mailbox Mailbox name
*
* @return array MYRIGHTS response on success, NULL on error
* @access public
* @since 0.5-beta
*/
function my_rights($mailbox)
{
if ($this->get_capability('ACL'))
return $this->conn->myRights($mailbox);
return NULL;
}
/**
* Sets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
*
* @param string $mailbox Mailbox name (empty for server metadata)
* @param array $entries Entry-value array (use NULL value as NIL)
*
* @return boolean True on success, False on failure
* @access public
* @since 0.5-beta
*/
function set_metadata($mailbox, $entries)
{
if ($this->get_capability('METADATA') ||
(!strlen($mailbox) && $this->get_capability('METADATA-SERVER'))
) {
return $this->conn->setMetadata($mailbox, $entries);
}
else if ($this->get_capability('ANNOTATEMORE') || $this->get_capability('ANNOTATEMORE2')) {
foreach ((array)$entries as $entry => $value) {
list($ent, $attr) = $this->md2annotate($entry);
$entries[$entry] = array($ent, $attr, $value);
}
return $this->conn->setAnnotation($mailbox, $entries);
}
return false;
}
/**
* Unsets IMAP metadata/annotations (SETMETADATA/SETANNOTATION)
*
* @param string $mailbox Mailbox name (empty for server metadata)
* @param array $entries Entry names array
*
* @return boolean True on success, False on failure
*
* @access public
* @since 0.5-beta
*/
function delete_metadata($mailbox, $entries)
{
if ($this->get_capability('METADATA') ||
(!strlen($mailbox) && $this->get_capability('METADATA-SERVER'))
) {
return $this->conn->deleteMetadata($mailbox, $entries);
}
else if ($this->get_capability('ANNOTATEMORE') || $this->get_capability('ANNOTATEMORE2')) {
foreach ((array)$entries as $idx => $entry) {
list($ent, $attr) = $this->md2annotate($entry);
$entries[$idx] = array($ent, $attr, NULL);
}
return $this->conn->setAnnotation($mailbox, $entries);
}
return false;
}
/**
* Returns IMAP metadata/annotations (GETMETADATA/GETANNOTATION)
*
* @param string $mailbox Mailbox name (empty for server metadata)
* @param array $entries Entries
* @param array $options Command options (with MAXSIZE and DEPTH keys)
*
* @return array Metadata entry-value hash array on success, NULL on error
*
* @access public
* @since 0.5-beta
*/
function get_metadata($mailbox, $entries, $options=array())
{
if ($this->get_capability('METADATA') ||
(!strlen($mailbox) && $this->get_capability('METADATA-SERVER'))
) {
return $this->conn->getMetadata($mailbox, $entries, $options);
}
else if ($this->get_capability('ANNOTATEMORE') || $this->get_capability('ANNOTATEMORE2')) {
$queries = array();
$res = array();
// Convert entry names
foreach ((array)$entries as $entry) {
list($ent, $attr) = $this->md2annotate($entry);
$queries[$attr][] = $ent;
}
// @TODO: Honor MAXSIZE and DEPTH options
foreach ($queries as $attrib => $entry)
if ($result = $this->conn->getAnnotation($mailbox, $entry, $attrib))
$res = array_merge_recursive($res, $result);
return $res;
}
return NULL;
}
/**
* Converts the METADATA extension entry name into the correct
* entry-attrib names for older ANNOTATEMORE version.
*
* @param string $entry Entry name
*
* @return array Entry-attribute list, NULL if not supported (?)
*/
private function md2annotate($entry)
{
if (substr($entry, 0, 7) == '/shared') {
return array(substr($entry, 7), 'value.shared');
}
else if (substr($entry, 0, 8) == '/private') {
return array(substr($entry, 8), 'value.priv');
}
// @TODO: log error
return NULL;
}
/* --------------------------------
* internal caching methods
* --------------------------------*/
/**
* Enable or disable indexes caching
*
* @param string $type Cache type (@see rcmail::get_cache)
* @access public
*/
function set_caching($type)
{
if ($type) {
$this->caching = $type;
}
else {
if ($this->cache)
$this->cache->close();
$this->cache = null;
$this->caching = false;
}
}
/**
* Getter for IMAP cache object
*/
private function get_cache_engine()
{
if ($this->caching && !$this->cache) {
$rcmail = rcmail::get_instance();
$this->cache = $rcmail->get_cache('IMAP', $this->caching);
}
return $this->cache;
}
/**
* Returns cached value
*
* @param string $key Cache key
* @return mixed
* @access public
*/
function get_cache($key)
{
if ($cache = $this->get_cache_engine()) {
return $cache->get($key);
}
}
/**
* Update cache
*
* @param string $key Cache key
* @param mixed $data Data
* @access public
*/
function update_cache($key, $data)
{
if ($cache = $this->get_cache_engine()) {
$cache->set($key, $data);
}
}
/**
* Clears the cache.
*
* @param string $key Cache key name or pattern
* @param boolean $prefix_mode Enable it to clear all keys starting
* with prefix specified in $key
* @access public
*/
function clear_cache($key=null, $prefix_mode=false)
{
if ($cache = $this->get_cache_engine()) {
$cache->remove($key, $prefix_mode);
}
}
/* --------------------------------
* message caching methods
* --------------------------------*/
/**
* Enable or disable messages caching
*
* @param boolean $set Flag
*/
function set_messages_caching($set)
{
if ($set) {
$this->messages_caching = true;
}
else {
if ($this->mcache)
$this->mcache->close();
$this->mcache = null;
$this->messages_caching = false;
}
}
+
/**
* Getter for messages cache object
*/
private function get_mcache_engine()
{
if ($this->messages_caching && !$this->mcache) {
$rcmail = rcmail::get_instance();
if ($dbh = $rcmail->get_dbh()) {
$this->mcache = new rcube_imap_cache(
$dbh, $this, $rcmail->user->ID, $this->skip_deleted);
}
}
return $this->mcache;
}
+
/**
* Clears the messages cache.
*
* @param string $mailbox Folder name
* @param array $uids Optional message UIDs to remove from cache
*/
function clear_message_cache($mailbox = null, $uids = null)
{
if ($mcache = $this->get_mcache_engine()) {
$mcache->clear($mailbox, $uids);
}
}
-
- /* --------------------------------
- * encoding/decoding methods
- * --------------------------------*/
-
- /**
- * Split an address list into a structured array list
- *
- * @param string $input Input string
- * @param int $max List only this number of addresses
- * @param boolean $decode Decode address strings
- * @return array Indexed list of addresses
- */
- function decode_address_list($input, $max=null, $decode=true)
- {
- $a = $this->_parse_address_list($input, $decode);
- $out = array();
- // Special chars as defined by RFC 822 need to in quoted string (or escaped).
- $special_chars = '[\(\)\<\>\\\.\[\]@,;:"]';
-
- if (!is_array($a))
- return $out;
-
- $c = count($a);
- $j = 0;
-
- foreach ($a as $val) {
- $j++;
- $address = trim($val['address']);
- $name = trim($val['name']);
-
- if ($name && $address && $name != $address)
- $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
- else if ($address)
- $string = $address;
- else if ($name)
- $string = $name;
-
- $out[$j] = array(
- 'name' => $name,
- 'mailto' => $address,
- 'string' => $string
- );
-
- if ($max && $j==$max)
- break;
- }
-
- return $out;
- }
-
-
- /**
- * Decode a message header value
- *
- * @param string $input Header value
- * @param boolean $remove_quotas Remove quotes if necessary
- * @return string Decoded string
- */
- function decode_header($input, $remove_quotes=false)
- {
- $str = rcube_imap::decode_mime_string((string)$input, $this->default_charset);
- if ($str[0] == '"' && $remove_quotes)
- $str = str_replace('"', '', $str);
-
- return $str;
- }
-
-
- /**
- * Decode a mime-encoded string to internal charset
- *
- * @param string $input Header value
- * @param string $fallback Fallback charset if none specified
- *
- * @return string Decoded string
- * @static
- */
- public static function decode_mime_string($input, $fallback=null)
- {
- if (!empty($fallback)) {
- $default_charset = $fallback;
- }
- else {
- $default_charset = rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1');
- }
-
- // rfc: all line breaks or other characters not found
- // in the Base64 Alphabet must be ignored by decoding software
- // delete all blanks between MIME-lines, differently we can
- // receive unnecessary blanks and broken utf-8 symbols
- $input = preg_replace("/\?=\s+=\?/", '?==?', $input);
-
- // encoded-word regexp
- $re = '/=\?([^?]+)\?([BbQq])\?([^\n]*?)\?=/';
-
- // Find all RFC2047's encoded words
- if (preg_match_all($re, $input, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
- // Initialize variables
- $tmp = array();
- $out = '';
- $start = 0;
-
- foreach ($matches as $idx => $m) {
- $pos = $m[0][1];
- $charset = $m[1][0];
- $encoding = $m[2][0];
- $text = $m[3][0];
- $length = strlen($m[0][0]);
-
- // Append everything that is before the text to be decoded
- if ($start != $pos) {
- $substr = substr($input, $start, $pos-$start);
- $out .= rcube_charset_convert($substr, $default_charset);
- $start = $pos;
- }
- $start += $length;
-
- // Per RFC2047, each string part "MUST represent an integral number
- // of characters . A multi-octet character may not be split across
- // adjacent encoded-words." However, some mailers break this, so we
- // try to handle characters spanned across parts anyway by iterating
- // through and aggregating sequential encoded parts with the same
- // character set and encoding, then perform the decoding on the
- // aggregation as a whole.
-
- $tmp[] = $text;
- if ($next_match = $matches[$idx+1]) {
- if ($next_match[0][1] == $start
- && $next_match[1][0] == $charset
- && $next_match[2][0] == $encoding
- ) {
- continue;
- }
- }
-
- $count = count($tmp);
- $text = '';
-
- // Decode and join encoded-word's chunks
- if ($encoding == 'B' || $encoding == 'b') {
- // base64 must be decoded a segment at a time
- for ($i=0; $i<$count; $i++)
- $text .= base64_decode($tmp[$i]);
- }
- else { //if ($encoding == 'Q' || $encoding == 'q') {
- // quoted printable can be combined and processed at once
- for ($i=0; $i<$count; $i++)
- $text .= $tmp[$i];
-
- $text = str_replace('_', ' ', $text);
- $text = quoted_printable_decode($text);
- }
-
- $out .= rcube_charset_convert($text, $charset);
- $tmp = array();
- }
-
- // add the last part of the input string
- if ($start != strlen($input)) {
- $out .= rcube_charset_convert(substr($input, $start), $default_charset);
- }
-
- // return the results
- return $out;
- }
-
- // no encoding information, use fallback
- return rcube_charset_convert($input, $default_charset);
- }
-
-
- /**
- * Decode a mime part
- *
- * @param string $input Input string
- * @param string $encoding Part encoding
- * @return string Decoded string
- */
- function mime_decode($input, $encoding='7bit')
- {
- switch (strtolower($encoding)) {
- case 'quoted-printable':
- return quoted_printable_decode($input);
- case 'base64':
- return base64_decode($input);
- case 'x-uuencode':
- case 'x-uue':
- case 'uue':
- case 'uuencode':
- return convert_uudecode($input);
- case '7bit':
- default:
- return $input;
- }
- }
-
-
/* --------------------------------
* private methods
* --------------------------------*/
/**
* Validate the given input and save to local properties
*
* @param string $sort_field Sort column
* @param string $sort_order Sort order
* @access private
*/
private function set_sort_order($sort_field, $sort_order)
{
if ($sort_field != null)
$this->sort_field = asciiwords($sort_field);
if ($sort_order != null)
$this->sort_order = strtoupper($sort_order) == 'DESC' ? 'DESC' : 'ASC';
}
/**
* Sort mailboxes first by default folders and then in alphabethical order
*
* @param array $a_folders Mailboxes list
* @access private
*/
private function _sort_mailbox_list($a_folders)
{
$a_out = $a_defaults = $folders = array();
$delimiter = $this->get_hierarchy_delimiter();
// find default folders and skip folders starting with '.'
foreach ($a_folders as $i => $folder) {
if ($folder[0] == '.')
continue;
if (($p = array_search($folder, $this->default_folders)) !== false && !$a_defaults[$p])
$a_defaults[$p] = $folder;
else
$folders[$folder] = rcube_charset_convert($folder, 'UTF7-IMAP');
}
// sort folders and place defaults on the top
asort($folders, SORT_LOCALE_STRING);
ksort($a_defaults);
$folders = array_merge($a_defaults, array_keys($folders));
// finally we must rebuild the list to move
// subfolders of default folders to their place...
// ...also do this for the rest of folders because
// asort() is not properly sorting case sensitive names
while (list($key, $folder) = each($folders)) {
// set the type of folder name variable (#1485527)
$a_out[] = (string) $folder;
unset($folders[$key]);
$this->_rsort($folder, $delimiter, $folders, $a_out);
}
return $a_out;
}
/**
* @access private
*/
private function _rsort($folder, $delimiter, &$list, &$out)
{
while (list($key, $name) = each($list)) {
if (strpos($name, $folder.$delimiter) === 0) {
// set the type of folder name variable (#1485527)
$out[] = (string) $name;
unset($list[$key]);
$this->_rsort($name, $delimiter, $list, $out);
}
}
reset($list);
}
/**
* Find UID of the specified message sequence ID
*
* @param int $id Message (sequence) ID
* @param string $mailbox Mailbox name
*
* @return int Message UID
*/
- function id2uid($id, $mailbox = null)
+ private function id2uid($id, $mailbox = null)
{
if (!strlen($mailbox)) {
$mailbox = $this->mailbox;
}
if ($uid = array_search($id, (array)$this->uid_id_map[$mailbox])) {
return $uid;
}
$uid = $this->conn->ID2UID($mailbox, $id);
$this->uid_id_map[$mailbox][$uid] = $id;
return $uid;
}
/**
* Subscribe/unsubscribe a list of mailboxes and update local cache
* @access private
*/
private function _change_subscription($a_mboxes, $mode)
{
$updated = false;
if (is_array($a_mboxes))
foreach ($a_mboxes as $i => $mailbox) {
$a_mboxes[$i] = $mailbox;
if ($mode == 'subscribe')
$updated = $this->conn->subscribe($mailbox);
else if ($mode == 'unsubscribe')
$updated = $this->conn->unsubscribe($mailbox);
}
// clear cached mailbox list(s)
if ($updated) {
$this->clear_cache('mailboxes', true);
}
return $updated;
}
/**
* Increde/decrese messagecount for a specific mailbox
* @access private
*/
private function _set_messagecount($mailbox, $mode, $increment)
{
$mode = strtoupper($mode);
$a_mailbox_cache = $this->get_cache('messagecount');
if (!is_array($a_mailbox_cache[$mailbox]) || !isset($a_mailbox_cache[$mailbox][$mode]) || !is_numeric($increment))
return false;
// add incremental value to messagecount
$a_mailbox_cache[$mailbox][$mode] += $increment;
// there's something wrong, delete from cache
if ($a_mailbox_cache[$mailbox][$mode] < 0)
unset($a_mailbox_cache[$mailbox][$mode]);
// write back to cache
$this->update_cache('messagecount', $a_mailbox_cache);
return true;
}
/**
* Remove messagecount of a specific mailbox from cache
* @access private
*/
private function _clear_messagecount($mailbox, $mode=null)
{
$a_mailbox_cache = $this->get_cache('messagecount');
if (is_array($a_mailbox_cache[$mailbox])) {
if ($mode) {
unset($a_mailbox_cache[$mailbox][$mode]);
}
else {
unset($a_mailbox_cache[$mailbox]);
}
$this->update_cache('messagecount', $a_mailbox_cache);
}
}
- /**
- * Split RFC822 header string into an associative array
- * @access private
- */
- private function _parse_headers($headers)
- {
- $a_headers = array();
- $headers = preg_replace('/\r?\n(\t| )+/', ' ', $headers);
- $lines = explode("\n", $headers);
- $c = count($lines);
-
- for ($i=0; $i<$c; $i++) {
- if ($p = strpos($lines[$i], ': ')) {
- $field = strtolower(substr($lines[$i], 0, $p));
- $value = trim(substr($lines[$i], $p+1));
- if (!empty($value))
- $a_headers[$field] = $value;
- }
- }
-
- return $a_headers;
- }
-
-
- /**
- * @access private
- */
- private function _parse_address_list($str, $decode=true)
- {
- // remove any newlines and carriage returns before
- $str = preg_replace('/\r?\n(\s|\t)?/', ' ', $str);
-
- // extract list items, remove comments
- $str = self::explode_header_string(',;', $str, true);
- $result = array();
-
- // simplified regexp, supporting quoted local part
- $email_rx = '(\S+|("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+"))@\S+';
-
- foreach ($str as $key => $val) {
- $name = '';
- $address = '';
- $val = trim($val);
-
- if (preg_match('/(.*)<('.$email_rx.')>$/', $val, $m)) {
- $address = $m[2];
- $name = trim($m[1]);
- }
- else if (preg_match('/^('.$email_rx.')$/', $val, $m)) {
- $address = $m[1];
- $name = '';
- }
- else {
- $name = $val;
- }
-
- // dequote and/or decode name
- if ($name) {
- if ($name[0] == '"' && $name[strlen($name)-1] == '"') {
- $name = substr($name, 1, -1);
- $name = stripslashes($name);
- }
- if ($decode) {
- $name = $this->decode_header($name);
- }
- }
-
- if (!$address && $name) {
- $address = $name;
- }
-
- if ($address) {
- $result[$key] = array('name' => $name, 'address' => $address);
- }
- }
-
- return $result;
- }
-
-
- /**
- * Explodes header (e.g. address-list) string into array of strings
- * using specified separator characters with proper handling
- * of quoted-strings and comments (RFC2822)
- *
- * @param string $separator String containing separator characters
- * @param string $str Header string
- * @param bool $remove_comments Enable to remove comments
- *
- * @return array Header items
- */
- static function explode_header_string($separator, $str, $remove_comments=false)
- {
- $length = strlen($str);
- $result = array();
- $quoted = false;
- $comment = 0;
- $out = '';
-
- for ($i=0; $i<$length; $i++) {
- // we're inside a quoted string
- if ($quoted) {
- if ($str[$i] == '"') {
- $quoted = false;
- }
- else if ($str[$i] == '\\') {
- if ($comment <= 0) {
- $out .= '\\';
- }
- $i++;
- }
- }
- // we're inside a comment string
- else if ($comment > 0) {
- if ($str[$i] == ')') {
- $comment--;
- }
- else if ($str[$i] == '(') {
- $comment++;
- }
- else if ($str[$i] == '\\') {
- $i++;
- }
- continue;
- }
- // separator, add to result array
- else if (strpos($separator, $str[$i]) !== false) {
- if ($out) {
- $result[] = $out;
- }
- $out = '';
- continue;
- }
- // start of quoted string
- else if ($str[$i] == '"') {
- $quoted = true;
- }
- // start of comment
- else if ($remove_comments && $str[$i] == '(') {
- $comment++;
- }
-
- if ($comment <= 0) {
- $out .= $str[$i];
- }
- }
-
- if ($out && $comment <= 0) {
- $result[] = $out;
- }
-
- return $result;
- }
-
-
/**
* This is our own debug handler for the IMAP connection
* @access public
*/
public function debug_handler(&$imap, $message)
{
write_log('imap', $message);
}
} // end class rcube_imap
/**
* Class representing a message part
*
* @package Mail
*/
class rcube_message_part
{
var $mime_id = '';
var $ctype_primary = 'text';
var $ctype_secondary = 'plain';
var $mimetype = 'text/plain';
var $disposition = '';
var $filename = '';
var $encoding = '8bit';
var $charset = '';
var $size = 0;
var $headers = array();
var $d_parameters = array();
var $ctype_parameters = array();
function __clone()
{
if (isset($this->parts))
foreach ($this->parts as $idx => $part)
if (is_object($part))
$this->parts[$idx] = clone $part;
}
}
/**
* Class for sorting an array of rcube_mail_header objects in a predetermined order.
*
* @package Mail
* @author Eric Stadtherr
*/
class rcube_header_sorter
{
private $uids = array();
/**
* Set the predetermined sort order.
*
* @param array $index Numerically indexed array of IMAP UIDs
*/
function set_index($index)
{
$index = array_flip($index);
$this->uids = $index;
}
/**
* Sort the array of header objects
*
* @param array $headers Array of rcube_mail_header objects indexed by UID
*/
function sort_headers(&$headers)
{
uksort($headers, array($this, "compare_uids"));
}
/**
* Sort method called by uksort()
*
* @param int $a Array key (UID)
* @param int $b Array key (UID)
*/
function compare_uids($a, $b)
{
// then find each sequence number in my ordered list
$posa = isset($this->uids[$a]) ? intval($this->uids[$a]) : -1;
$posb = isset($this->uids[$b]) ? intval($this->uids[$b]) : -1;
// return the relative position as the comparison value
return $posa - $posb;
}
}
diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php
index 633f59be2..d05b57abe 100644
--- a/program/include/rcube_message.php
+++ b/program/include/rcube_message.php
@@ -1,748 +1,664 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcube_message.php |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2008-2010, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Logical representation of a mail message with all its data |
| and related functions |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
/**
* Logical representation of a mail message with all its data
* and related functions
*
* @package Mail
* @author Thomas Bruederli <roundcube@gmail.com>
*/
class rcube_message
{
/**
* Instace of rcmail.
*
* @var rcmail
*/
private $app;
/**
* Instance of imap class
*
* @var rcube_imap
*/
private $imap;
+
+ /**
+ * Instance of mime class
+ *
+ * @var rcube_mime
+ */
+ private $mime;
private $opt = array();
private $inline_parts = array();
private $parse_alternative = false;
public $uid = null;
public $headers;
public $parts = array();
public $mime_parts = array();
public $attachments = array();
public $subject = '';
public $sender = null;
public $is_safe = false;
/**
* __construct
*
* Provide a uid, and parse message structure.
*
* @param string $uid The message UID.
*
- * @uses rcmail::get_instance()
- * @uses rcube_imap::decode_mime_string()
- * @uses self::set_safe()
- *
* @see self::$app, self::$imap, self::$opt, self::$structure
*/
function __construct($uid)
{
- $this->app = rcmail::get_instance();
+ $this->uid = $uid;
+ $this->app = rcmail::get_instance();
$this->imap = $this->app->imap;
$this->imap->get_all_headers = true;
- $this->uid = $uid;
$this->headers = $this->imap->get_message($uid);
if (!$this->headers)
return;
- $this->subject = rcube_imap::decode_mime_string(
- $this->headers->subject, $this->headers->charset);
- list(, $this->sender) = each($this->imap->decode_address_list($this->headers->from));
+ $this->mime = new rcube_mime($this->headers->charset);
+
+ $this->subject = $this->mime->decode_mime_string($this->headers->subject);
+ list(, $this->sender) = each($this->mime->decode_address_list($this->headers->from, 1));
$this->set_safe((intval($_GET['_safe']) || $_SESSION['safe_messages'][$uid]));
$this->opt = array(
'safe' => $this->is_safe,
'prefer_html' => $this->app->config->get('prefer_html'),
'get_url' => rcmail_url('get', array(
'_mbox' => $this->imap->get_mailbox_name(), '_uid' => $uid))
);
if (!empty($this->headers->structure)) {
$this->get_mime_numbers($this->headers->structure);
$this->parse_structure($this->headers->structure);
}
else {
$this->body = $this->imap->get_body($uid);
}
// notify plugins and let them analyze this structured message object
$this->app->plugins->exec_hook('message_load', array('object' => $this));
}
/**
* Return a (decoded) message header
*
* @param string $name Header name
* @param bool $row Don't mime-decode the value
* @return string Header value
*/
public function get_header($name, $raw = false)
{
+ if (empty($this->headers))
+ return null;
+
if ($this->headers->$name)
$value = $this->headers->$name;
else if ($this->headers->others[$name])
$value = $this->headers->others[$name];
- return $raw ? $value : $this->imap->decode_header($value);
+ return $raw ? $value : $this->mime->decode_header($value);
}
/**
* Set is_safe var and session data
*
* @param bool $safe enable/disable
*/
public function set_safe($safe = true)
{
$this->is_safe = $safe;
$_SESSION['safe_messages'][$this->uid] = $this->is_safe;
}
/**
* Compose a valid URL for getting a message part
*
* @param string $mime_id Part MIME-ID
* @return string URL or false if part does not exist
*/
public function get_part_url($mime_id, $embed = false)
{
if ($this->mime_parts[$mime_id])
return $this->opt['get_url'] . '&_part=' . $mime_id . ($embed ? '&_embed=1' : '');
else
return false;
}
/**
* Get content of a specific part of this message
*
* @param string $mime_id Part MIME-ID
* @param resource $fp File pointer to save the message part
* @return string Part content
*/
public function get_part_content($mime_id, $fp=NULL)
{
if ($part = $this->mime_parts[$mime_id]) {
// stored in message structure (winmail/inline-uuencode)
if ($part->encoding == 'stream') {
if ($fp) {
fwrite($fp, $part->body);
}
return $fp ? true : $part->body;
}
// get from IMAP
return $this->imap->get_message_part($this->uid, $mime_id, $part, NULL, $fp);
} else
return null;
}
/**
* Determine if the message contains a HTML part
*
* @return bool True if a HTML is available, False if not
*/
function has_html_part()
{
// check all message parts
foreach ($this->parts as $pid => $part) {
$mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary);
if ($mimetype == 'text/html')
return true;
}
return false;
}
/**
* Return the first HTML part of this message
*
* @return string HTML message part content
*/
function first_html_part()
{
// check all message parts
foreach ($this->mime_parts as $mime_id => $part) {
$mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary);
if ($mimetype == 'text/html') {
return $this->imap->get_message_part($this->uid, $mime_id, $part);
}
}
}
/**
* Return the first text part of this message
*
* @param rcube_message_part $part Reference to the part if found
* @return string Plain text message/part content
*/
function first_text_part(&$part=null)
{
// no message structure, return complete body
if (empty($this->parts))
return $this->body;
// check all message parts
foreach ($this->mime_parts as $mime_id => $part) {
$mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
if ($mimetype == 'text/plain') {
return $this->imap->get_message_part($this->uid, $mime_id, $part);
}
else if ($mimetype == 'text/html') {
$out = $this->imap->get_message_part($this->uid, $mime_id, $part);
// remove special chars encoding
$trans = array_flip(get_html_translation_table(HTML_ENTITIES));
$out = strtr($out, $trans);
// create instance of html2text class
$txt = new html2text($out);
return $txt->get_text();
}
}
$part = null;
return null;
}
/**
* Raad the message structure returend by the IMAP server
* and build flat lists of content parts and attachments
*
* @param rcube_message_part $structure Message structure node
* @param bool $recursive True when called recursively
*/
private function parse_structure($structure, $recursive = false)
{
// real content-type of message/rfc822 part
if ($structure->mimetype == 'message/rfc822' && $structure->real_mimetype)
$mimetype = $structure->real_mimetype;
else
$mimetype = $structure->mimetype;
// show message headers
if ($recursive && is_array($structure->headers) && isset($structure->headers['subject'])) {
$c = new stdClass;
$c->type = 'headers';
$c->headers = &$structure->headers;
$this->parts[] = $c;
}
// Allow plugins to handle message parts
$plugin = $this->app->plugins->exec_hook('message_part_structure',
array('object' => $this, 'structure' => $structure,
'mimetype' => $mimetype, 'recursive' => $recursive));
if ($plugin['abort'])
return;
$structure = $plugin['structure'];
list($message_ctype_primary, $message_ctype_secondary) = explode('/', $plugin['mimetype']);
// print body if message doesn't have multiple parts
if ($message_ctype_primary == 'text' && !$recursive) {
$structure->type = 'content';
$this->parts[] = &$structure;
// Parse simple (plain text) message body
if ($message_ctype_secondary == 'plain')
foreach ((array)$this->uu_decode($structure) as $uupart) {
$this->mime_parts[$uupart->mime_id] = $uupart;
$this->attachments[] = $uupart;
}
}
// the same for pgp signed messages
else if ($mimetype == 'application/pgp' && !$recursive) {
$structure->type = 'content';
$this->parts[] = &$structure;
}
// message contains (more than one!) alternative parts
else if ($mimetype == 'multipart/alternative'
&& is_array($structure->parts) && count($structure->parts) > 1
) {
// get html/plaintext parts
$plain_part = $html_part = $print_part = $related_part = null;
foreach ($structure->parts as $p => $sub_part) {
$sub_mimetype = $sub_part->mimetype;
// check if sub part is
if ($sub_mimetype == 'text/plain')
$plain_part = $p;
else if ($sub_mimetype == 'text/html')
$html_part = $p;
else if ($sub_mimetype == 'text/enriched')
$enriched_part = $p;
else if (in_array($sub_mimetype, array('multipart/related', 'multipart/mixed', 'multipart/alternative')))
$related_part = $p;
}
// parse related part (alternative part could be in here)
if ($related_part !== null && !$this->parse_alternative) {
$this->parse_alternative = true;
$this->parse_structure($structure->parts[$related_part], true);
$this->parse_alternative = false;
// if plain part was found, we should unset it if html is preferred
if ($this->opt['prefer_html'] && count($this->parts))
$plain_part = null;
}
// choose html/plain part to print
if ($html_part !== null && $this->opt['prefer_html']) {
$print_part = &$structure->parts[$html_part];
}
else if ($enriched_part !== null) {
$print_part = &$structure->parts[$enriched_part];
}
else if ($plain_part !== null) {
$print_part = &$structure->parts[$plain_part];
}
// add the right message body
if (is_object($print_part)) {
$print_part->type = 'content';
$this->parts[] = $print_part;
}
// show plaintext warning
else if ($html_part !== null && empty($this->parts)) {
$c = new stdClass;
$c->type = 'content';
$c->ctype_primary = 'text';
$c->ctype_secondary = 'plain';
$c->body = rcube_label('htmlmessage');
$this->parts[] = $c;
}
// add html part as attachment
if ($html_part !== null && $structure->parts[$html_part] !== $print_part) {
$html_part = &$structure->parts[$html_part];
$html_part->filename = rcube_label('htmlmessage');
$html_part->mimetype = 'text/html';
$this->attachments[] = $html_part;
}
}
// this is an ecrypted message -> create a plaintext body with the according message
else if ($mimetype == 'multipart/encrypted') {
$p = new stdClass;
$p->type = 'content';
$p->ctype_primary = 'text';
$p->ctype_secondary = 'plain';
$p->body = rcube_label('encryptedmessage');
$p->size = strlen($p->body);
$this->parts[] = $p;
}
// message contains multiple parts
else if (is_array($structure->parts) && !empty($structure->parts)) {
// iterate over parts
for ($i=0; $i < count($structure->parts); $i++) {
$mail_part = &$structure->parts[$i];
$primary_type = $mail_part->ctype_primary;
$secondary_type = $mail_part->ctype_secondary;
// real content-type of message/rfc822
if ($mail_part->real_mimetype) {
$part_orig_mimetype = $mail_part->mimetype;
$part_mimetype = $mail_part->real_mimetype;
list($primary_type, $secondary_type) = explode('/', $part_mimetype);
}
else
$part_mimetype = $mail_part->mimetype;
// multipart/alternative
if ($primary_type == 'multipart') {
$this->parse_structure($mail_part, true);
// list message/rfc822 as attachment as well (mostly .eml)
if ($part_orig_mimetype == 'message/rfc822' && !empty($mail_part->filename))
$this->attachments[] = $mail_part;
}
// part text/[plain|html] or delivery status
else if ((($part_mimetype == 'text/plain' || $part_mimetype == 'text/html') && $mail_part->disposition != 'attachment') ||
in_array($part_mimetype, array('message/delivery-status', 'text/rfc822-headers', 'message/disposition-notification'))
) {
// Allow plugins to handle also this part
$plugin = $this->app->plugins->exec_hook('message_part_structure',
array('object' => $this, 'structure' => $mail_part,
'mimetype' => $part_mimetype, 'recursive' => true));
if ($plugin['abort'])
continue;
if ($part_mimetype == 'text/html') {
$got_html_part = true;
}
$mail_part = $plugin['structure'];
list($primary_type, $secondary_type) = explode('/', $plugin['mimetype']);
// add text part if it matches the prefs
if (!$this->parse_alternative ||
($secondary_type == 'html' && $this->opt['prefer_html']) ||
($secondary_type == 'plain' && !$this->opt['prefer_html'])
) {
$mail_part->type = 'content';
$this->parts[] = $mail_part;
}
// list as attachment as well
if (!empty($mail_part->filename))
$this->attachments[] = $mail_part;
}
// part message/*
else if ($primary_type == 'message') {
$this->parse_structure($mail_part, true);
// list as attachment as well (mostly .eml)
if (!empty($mail_part->filename))
$this->attachments[] = $mail_part;
}
// ignore "virtual" protocol parts
else if ($primary_type == 'protocol') {
continue;
}
// part is Microsoft Outlook TNEF (winmail.dat)
else if ($part_mimetype == 'application/ms-tnef') {
foreach ((array)$this->tnef_decode($mail_part) as $tpart) {
$this->mime_parts[$tpart->mime_id] = $tpart;
$this->attachments[] = $tpart;
}
}
// part is a file/attachment
else if (preg_match('/^(inline|attach)/', $mail_part->disposition) ||
$mail_part->headers['content-id'] ||
($mail_part->filename &&
(empty($mail_part->disposition) || preg_match('/^[a-z0-9!#$&.+^_-]+$/i', $mail_part->disposition)))
) {
// skip apple resource forks
if ($message_ctype_secondary == 'appledouble' && $secondary_type == 'applefile')
continue;
// part belongs to a related message and is linked
if ($mimetype == 'multipart/related'
&& ($mail_part->headers['content-id'] || $mail_part->headers['content-location'])) {
if ($mail_part->headers['content-id'])
$mail_part->content_id = preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']);
if ($mail_part->headers['content-location'])
$mail_part->content_location = $mail_part->headers['content-base'] . $mail_part->headers['content-location'];
$this->inline_parts[] = $mail_part;
}
// attachment encapsulated within message/rfc822 part needs further decoding (#1486743)
else if ($part_orig_mimetype == 'message/rfc822') {
$this->parse_structure($mail_part, true);
// list as attachment as well (mostly .eml)
if (!empty($mail_part->filename))
$this->attachments[] = $mail_part;
}
// regular attachment with valid content type
// (content-type name regexp according to RFC4288.4.2)
else if (preg_match('/^[a-z0-9!#$&.+^_-]+\/[a-z0-9!#$&.+^_-]+$/i', $part_mimetype)) {
if (!$mail_part->filename)
$mail_part->filename = 'Part '.$mail_part->mime_id;
$this->attachments[] = $mail_part;
}
// attachment with invalid content type
// replace malformed content type with application/octet-stream (#1487767)
else if ($mail_part->filename) {
$mail_part->ctype_primary = 'application';
$mail_part->ctype_secondary = 'octet-stream';
$mail_part->mimetype = 'application/octet-stream';
$this->attachments[] = $mail_part;
}
}
// attachment part as message/rfc822 (#1488026)
else if ($mail_part->mimetype == 'message/rfc822') {
$this->parse_structure($mail_part);
}
}
// if this was a related part try to resolve references
if ($mimetype == 'multipart/related' && sizeof($this->inline_parts)) {
$a_replaces = array();
$img_regexp = '/^image\/(gif|jpe?g|png|tiff|bmp|svg)/';
foreach ($this->inline_parts as $inline_object) {
$part_url = $this->get_part_url($inline_object->mime_id, true);
if ($inline_object->content_id)
$a_replaces['cid:'.$inline_object->content_id] = $part_url;
if ($inline_object->content_location) {
$a_replaces[$inline_object->content_location] = $part_url;
}
if (!empty($inline_object->filename)) {
// MS Outlook sends sometimes non-related attachments as related
// In this case multipart/related message has only one text part
// We'll add all such attachments to the attachments list
if (!isset($got_html_part) && empty($inline_object->content_id)) {
$this->attachments[] = $inline_object;
}
// MS Outlook sometimes also adds non-image attachments as related
// We'll add all such attachments to the attachments list
// Warning: some browsers support pdf in <img/>
else if (!preg_match($img_regexp, $inline_object->mimetype)) {
$this->attachments[] = $inline_object;
}
// @TODO: we should fetch HTML body and find attachment's content-id
// to handle also image attachments without reference in the body
// @TODO: should we list all image attachments in text mode?
}
}
// add replace array to each content part
// (will be applied later when part body is available)
foreach ($this->parts as $i => $part) {
if ($part->type == 'content')
$this->parts[$i]->replaces = $a_replaces;
}
}
}
// message is a single part non-text
else if ($structure->filename) {
$this->attachments[] = $structure;
}
// message is a single part non-text (without filename)
else if (preg_match('/application\//i', $mimetype)) {
$structure->filename = 'Part '.$structure->mime_id;
$this->attachments[] = $structure;
}
}
/**
* Fill aflat array with references to all parts, indexed by part numbers
*
* @param rcube_message_part $part Message body structure
*/
private function get_mime_numbers(&$part)
{
if (strlen($part->mime_id))
$this->mime_parts[$part->mime_id] = &$part;
if (is_array($part->parts))
for ($i=0; $i<count($part->parts); $i++)
$this->get_mime_numbers($part->parts[$i]);
}
/**
* Decode a Microsoft Outlook TNEF part (winmail.dat)
*
* @param rcube_message_part $part Message part to decode
* @return array
*/
function tnef_decode(&$part)
{
// @TODO: attachment may be huge, hadle it via file
if (!isset($part->body))
$part->body = $this->imap->get_message_part($this->uid, $part->mime_id, $part);
$parts = array();
$tnef = new tnef_decoder;
$tnef_arr = $tnef->decompress($part->body);
foreach ($tnef_arr as $pid => $winatt) {
$tpart = new rcube_message_part;
$tpart->filename = trim($winatt['name']);
$tpart->encoding = 'stream';
$tpart->ctype_primary = trim(strtolower($winatt['type']));
$tpart->ctype_secondary = trim(strtolower($winatt['subtype']));
$tpart->mimetype = $tpart->ctype_primary . '/' . $tpart->ctype_secondary;
$tpart->mime_id = 'winmail.' . $part->mime_id . '.' . $pid;
$tpart->size = $winatt['size'];
$tpart->body = $winatt['stream'];
$parts[] = $tpart;
unset($tnef_arr[$pid]);
}
return $parts;
}
/**
* Parse message body for UUencoded attachments bodies
*
* @param rcube_message_part $part Message part to decode
* @return array
*/
function uu_decode(&$part)
{
// @TODO: messages may be huge, hadle body via file
if (!isset($part->body))
$part->body = $this->imap->get_message_part($this->uid, $part->mime_id, $part);
$parts = array();
// FIXME: line length is max.65?
$uu_regexp = '/begin [0-7]{3,4} ([^\n]+)\n(([\x21-\x7E]{0,65}\n)+)`\nend/s';
if (preg_match_all($uu_regexp, $part->body, $matches, PREG_SET_ORDER)) {
// remove attachments bodies from the message body
$part->body = preg_replace($uu_regexp, '', $part->body);
// update message content-type
$part->ctype_primary = 'multipart';
$part->ctype_secondary = 'mixed';
$part->mimetype = $part->ctype_primary . '/' . $part->ctype_secondary;
// add attachments to the structure
foreach ($matches as $pid => $att) {
$uupart = new rcube_message_part;
$uupart->filename = trim($att[1]);
$uupart->encoding = 'stream';
$uupart->body = convert_uudecode($att[2]);
$uupart->size = strlen($uupart->body);
$uupart->mime_id = 'uu.' . $part->mime_id . '.' . $pid;
$ctype = rc_mime_content_type($uupart->body, $uupart->filename, 'application/octet-stream', true);
$uupart->mimetype = $ctype;
list($uupart->ctype_primary, $uupart->ctype_secondary) = explode('/', $ctype);
$parts[] = $uupart;
unset($matches[$pid]);
}
}
return $parts;
}
-
-
- /**
- * Interpret a format=flowed message body according to RFC 2646
- *
- * @param string $text Raw body formatted as flowed text
- * @return string Interpreted text with unwrapped lines and stuffed space removed
- */
- public static function unfold_flowed($text)
- {
- $text = preg_split('/\r?\n/', $text);
- $last = -1;
- $q_level = 0;
-
- foreach ($text as $idx => $line) {
- if ($line[0] == '>' && preg_match('/^(>+\s*)/', $line, $regs)) {
- $q = strlen(str_replace(' ', '', $regs[0]));
- $line = substr($line, strlen($regs[0]));
-
- if ($q == $q_level && $line
- && isset($text[$last])
- && $text[$last][strlen($text[$last])-1] == ' '
- ) {
- $text[$last] .= $line;
- unset($text[$idx]);
- }
- else {
- $last = $idx;
- }
- }
- else {
- $q = 0;
- if ($line == '-- ') {
- $last = $idx;
- }
- else {
- // remove space-stuffing
- $line = preg_replace('/^\s/', '', $line);
-
- if (isset($text[$last]) && $line
- && $text[$last] != '-- '
- && $text[$last][strlen($text[$last])-1] == ' '
- ) {
- $text[$last] .= $line;
- unset($text[$idx]);
- }
- else {
- $text[$idx] = $line;
- $last = $idx;
- }
- }
- }
- $q_level = $q;
- }
-
- return implode("\r\n", $text);
- }
-
-
- /**
- * Wrap the given text to comply with RFC 2646
- *
- * @param string $text Text to wrap
- * @param int $length Length
- * @return string Wrapped text
- */
- public static function format_flowed($text, $length = 72)
- {
- $text = preg_split('/\r?\n/', $text);
-
- foreach ($text as $idx => $line) {
- if ($line != '-- ') {
- if ($line[0] == '>' && preg_match('/^(>+)/', $line, $regs)) {
- $prefix = $regs[0];
- $level = strlen($prefix);
- $line = rtrim(substr($line, $level));
- $line = $prefix . rc_wordwrap($line, $length - $level - 2, " \r\n$prefix ");
- }
- else if ($line) {
- $line = rc_wordwrap(rtrim($line), $length - 2, " \r\n");
- // space-stuffing
- $line = preg_replace('/(^|\r\n)(From| |>)/', '\\1 \\2', $line);
- }
-
- $text[$idx] = $line;
- }
- }
-
- return implode("\r\n", $text);
- }
-
}
diff --git a/program/include/rcube_mime.php b/program/include/rcube_mime.php
new file mode 100644
index 000000000..4d97199c3
--- /dev/null
+++ b/program/include/rcube_mime.php
@@ -0,0 +1,484 @@
+<?php
+
+/**
+ +-----------------------------------------------------------------------+
+ | program/include/rcube_mime.php |
+ | |
+ | This file is part of the Roundcube Webmail client |
+ | Copyright (C) 2005-2012, The Roundcube Dev Team |
+ | Copyright (C) 2011-2012, Kolab Systems AG |
+ | Licensed under the GNU GPL |
+ | |
+ | PURPOSE: |
+ | MIME message parsing utilities |
+ | |
+ +-----------------------------------------------------------------------+
+ | Author: Thomas Bruederli <roundcube@gmail.com> |
+ | Author: Aleksander Machniak <alec@alec.pl> |
+ +-----------------------------------------------------------------------+
+
+ $Id$
+
+*/
+
+
+/**
+ * Class for parsing MIME messages
+ *
+ * @package Mail
+ * @author Thomas Bruederli <roundcube@gmail.com>
+ * @author Aleksander Machniak <alec@alec.pl>
+ */
+class rcube_mime
+{
+ private static $default_charset = RCMAIL_CHARSET;
+
+
+ /**
+ * Object constructor.
+ */
+ function __construct($default_charset = null)
+ {
+ if ($default_charset) {
+ self::$default_charset = $default_charset;
+ }
+ else {
+ self::$default_charset = rcmail::get_instance()->config->get('default_charset', RCMAIL_CHARSET);
+ }
+ }
+
+
+ /**
+ * Split an address list into a structured array list
+ *
+ * @param string $input Input string
+ * @param int $max List only this number of addresses
+ * @param boolean $decode Decode address strings
+ * @param string $fallback Fallback charset if none specified
+ *
+ * @return array Indexed list of addresses
+ */
+ static function decode_address_list($input, $max = null, $decode = true, $fallback = null)
+ {
+ $a = self::parse_address_list($input, $decode, $fallback);
+ $out = array();
+ $j = 0;
+
+ // Special chars as defined by RFC 822 need to in quoted string (or escaped).
+ $special_chars = '[\(\)\<\>\\\.\[\]@,;:"]';
+
+ if (!is_array($a))
+ return $out;
+
+ foreach ($a as $val) {
+ $j++;
+ $address = trim($val['address']);
+ $name = trim($val['name']);
+
+ if ($name && $address && $name != $address)
+ $string = sprintf('%s <%s>', preg_match("/$special_chars/", $name) ? '"'.addcslashes($name, '"').'"' : $name, $address);
+ else if ($address)
+ $string = $address;
+ else if ($name)
+ $string = $name;
+
+ $out[$j] = array(
+ 'name' => $name,
+ 'mailto' => $address,
+ 'string' => $string
+ );
+
+ if ($max && $j==$max)
+ break;
+ }
+
+ return $out;
+ }
+
+
+ /**
+ * Decode a message header value
+ *
+ * @param string $input Header value
+ * @param string $fallback Fallback charset if none specified
+ *
+ * @return string Decoded string
+ */
+ public static function decode_header($input, $fallback = null)
+ {
+ $str = self::decode_mime_string((string)$input, $fallback);
+
+ return $str;
+ }
+
+
+ /**
+ * Decode a mime-encoded string to internal charset
+ *
+ * @param string $input Header value
+ * @param string $fallback Fallback charset if none specified
+ *
+ * @return string Decoded string
+ */
+ public static function decode_mime_string($input, $fallback = null)
+ {
+ $default_charset = !empty($fallback) ? $fallback : self::$default_charset;
+
+ // rfc: all line breaks or other characters not found
+ // in the Base64 Alphabet must be ignored by decoding software
+ // delete all blanks between MIME-lines, differently we can
+ // receive unnecessary blanks and broken utf-8 symbols
+ $input = preg_replace("/\?=\s+=\?/", '?==?', $input);
+
+ // encoded-word regexp
+ $re = '/=\?([^?]+)\?([BbQq])\?([^\n]*?)\?=/';
+
+ // Find all RFC2047's encoded words
+ if (preg_match_all($re, $input, $matches, PREG_OFFSET_CAPTURE | PREG_SET_ORDER)) {
+ // Initialize variables
+ $tmp = array();
+ $out = '';
+ $start = 0;
+
+ foreach ($matches as $idx => $m) {
+ $pos = $m[0][1];
+ $charset = $m[1][0];
+ $encoding = $m[2][0];
+ $text = $m[3][0];
+ $length = strlen($m[0][0]);
+
+ // Append everything that is before the text to be decoded
+ if ($start != $pos) {
+ $substr = substr($input, $start, $pos-$start);
+ $out .= rcube_charset_convert($substr, $default_charset);
+ $start = $pos;
+ }
+ $start += $length;
+
+ // Per RFC2047, each string part "MUST represent an integral number
+ // of characters . A multi-octet character may not be split across
+ // adjacent encoded-words." However, some mailers break this, so we
+ // try to handle characters spanned across parts anyway by iterating
+ // through and aggregating sequential encoded parts with the same
+ // character set and encoding, then perform the decoding on the
+ // aggregation as a whole.
+
+ $tmp[] = $text;
+ if ($next_match = $matches[$idx+1]) {
+ if ($next_match[0][1] == $start
+ && $next_match[1][0] == $charset
+ && $next_match[2][0] == $encoding
+ ) {
+ continue;
+ }
+ }
+
+ $count = count($tmp);
+ $text = '';
+
+ // Decode and join encoded-word's chunks
+ if ($encoding == 'B' || $encoding == 'b') {
+ // base64 must be decoded a segment at a time
+ for ($i=0; $i<$count; $i++)
+ $text .= base64_decode($tmp[$i]);
+ }
+ else { //if ($encoding == 'Q' || $encoding == 'q') {
+ // quoted printable can be combined and processed at once
+ for ($i=0; $i<$count; $i++)
+ $text .= $tmp[$i];
+
+ $text = str_replace('_', ' ', $text);
+ $text = quoted_printable_decode($text);
+ }
+
+ $out .= rcube_charset_convert($text, $charset);
+ $tmp = array();
+ }
+
+ // add the last part of the input string
+ if ($start != strlen($input)) {
+ $out .= rcube_charset_convert(substr($input, $start), $default_charset);
+ }
+
+ // return the results
+ return $out;
+ }
+
+ // no encoding information, use fallback
+ return rcube_charset_convert($input, $default_charset);
+ }
+
+
+ /**
+ * Decode a mime part
+ *
+ * @param string $input Input string
+ * @param string $encoding Part encoding
+ * @return string Decoded string
+ */
+ public static function decode($input, $encoding = '7bit')
+ {
+ switch (strtolower($encoding)) {
+ case 'quoted-printable':
+ return quoted_printable_decode($input);
+ case 'base64':
+ return base64_decode($input);
+ case 'x-uuencode':
+ case 'x-uue':
+ case 'uue':
+ case 'uuencode':
+ return convert_uudecode($input);
+ case '7bit':
+ default:
+ return $input;
+ }
+ }
+
+
+ /**
+ * Split RFC822 header string into an associative array
+ * @access private
+ */
+ public static function parse_headers($headers)
+ {
+ $a_headers = array();
+ $headers = preg_replace('/\r?\n(\t| )+/', ' ', $headers);
+ $lines = explode("\n", $headers);
+ $c = count($lines);
+
+ for ($i=0; $i<$c; $i++) {
+ if ($p = strpos($lines[$i], ': ')) {
+ $field = strtolower(substr($lines[$i], 0, $p));
+ $value = trim(substr($lines[$i], $p+1));
+ if (!empty($value))
+ $a_headers[$field] = $value;
+ }
+ }
+
+ return $a_headers;
+ }
+
+
+ /**
+ * @access private
+ */
+ private static function parse_address_list($str, $decode = true, $fallback = null)
+ {
+ // remove any newlines and carriage returns before
+ $str = preg_replace('/\r?\n(\s|\t)?/', ' ', $str);
+
+ // extract list items, remove comments
+ $str = self::explode_header_string(',;', $str, true);
+ $result = array();
+
+ // simplified regexp, supporting quoted local part
+ $email_rx = '(\S+|("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+"))@\S+';
+
+ foreach ($str as $key => $val) {
+ $name = '';
+ $address = '';
+ $val = trim($val);
+
+ if (preg_match('/(.*)<('.$email_rx.')>$/', $val, $m)) {
+ $address = $m[2];
+ $name = trim($m[1]);
+ }
+ else if (preg_match('/^('.$email_rx.')$/', $val, $m)) {
+ $address = $m[1];
+ $name = '';
+ }
+ else {
+ $name = $val;
+ }
+
+ // dequote and/or decode name
+ if ($name) {
+ if ($name[0] == '"' && $name[strlen($name)-1] == '"') {
+ $name = substr($name, 1, -1);
+ $name = stripslashes($name);
+ }
+ if ($decode) {
+ $name = self::decode_header($name, $fallback);
+ }
+ }
+
+ if (!$address && $name) {
+ $address = $name;
+ }
+
+ if ($address) {
+ $result[$key] = array('name' => $name, 'address' => $address);
+ }
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Explodes header (e.g. address-list) string into array of strings
+ * using specified separator characters with proper handling
+ * of quoted-strings and comments (RFC2822)
+ *
+ * @param string $separator String containing separator characters
+ * @param string $str Header string
+ * @param bool $remove_comments Enable to remove comments
+ *
+ * @return array Header items
+ */
+ public static function explode_header_string($separator, $str, $remove_comments = false)
+ {
+ $length = strlen($str);
+ $result = array();
+ $quoted = false;
+ $comment = 0;
+ $out = '';
+
+ for ($i=0; $i<$length; $i++) {
+ // we're inside a quoted string
+ if ($quoted) {
+ if ($str[$i] == '"') {
+ $quoted = false;
+ }
+ else if ($str[$i] == "\\") {
+ if ($comment <= 0) {
+ $out .= "\\";
+ }
+ $i++;
+ }
+ }
+ // we are inside a comment string
+ else if ($comment > 0) {
+ if ($str[$i] == ')') {
+ $comment--;
+ }
+ else if ($str[$i] == '(') {
+ $comment++;
+ }
+ else if ($str[$i] == "\\") {
+ $i++;
+ }
+ continue;
+ }
+ // separator, add to result array
+ else if (strpos($separator, $str[$i]) !== false) {
+ if ($out) {
+ $result[] = $out;
+ }
+ $out = '';
+ continue;
+ }
+ // start of quoted string
+ else if ($str[$i] == '"') {
+ $quoted = true;
+ }
+ // start of comment
+ else if ($remove_comments && $str[$i] == '(') {
+ $comment++;
+ }
+
+ if ($comment <= 0) {
+ $out .= $str[$i];
+ }
+ }
+
+ if ($out && $comment <= 0) {
+ $result[] = $out;
+ }
+
+ return $result;
+ }
+
+
+ /**
+ * Interpret a format=flowed message body according to RFC 2646
+ *
+ * @param string $text Raw body formatted as flowed text
+ *
+ * @return string Interpreted text with unwrapped lines and stuffed space removed
+ */
+ public static function unfold_flowed($text)
+ {
+ $text = preg_split('/\r?\n/', $text);
+ $last = -1;
+ $q_level = 0;
+
+ foreach ($text as $idx => $line) {
+ if ($line[0] == '>' && preg_match('/^(>+\s*)/', $line, $regs)) {
+ $q = strlen(str_replace(' ', '', $regs[0]));
+ $line = substr($line, strlen($regs[0]));
+
+ if ($q == $q_level && $line
+ && isset($text[$last])
+ && $text[$last][strlen($text[$last])-1] == ' '
+ ) {
+ $text[$last] .= $line;
+ unset($text[$idx]);
+ }
+ else {
+ $last = $idx;
+ }
+ }
+ else {
+ $q = 0;
+ if ($line == '-- ') {
+ $last = $idx;
+ }
+ else {
+ // remove space-stuffing
+ $line = preg_replace('/^\s/', '', $line);
+
+ if (isset($text[$last]) && $line
+ && $text[$last] != '-- '
+ && $text[$last][strlen($text[$last])-1] == ' '
+ ) {
+ $text[$last] .= $line;
+ unset($text[$idx]);
+ }
+ else {
+ $text[$idx] = $line;
+ $last = $idx;
+ }
+ }
+ }
+ $q_level = $q;
+ }
+
+ return implode("\r\n", $text);
+ }
+
+
+ /**
+ * Wrap the given text to comply with RFC 2646
+ *
+ * @param string $text Text to wrap
+ * @param int $length Length
+ *
+ * @return string Wrapped text
+ */
+ public static function format_flowed($text, $length = 72)
+ {
+ $text = preg_split('/\r?\n/', $text);
+
+ foreach ($text as $idx => $line) {
+ if ($line != '-- ') {
+ if ($line[0] == '>' && preg_match('/^(>+)/', $line, $regs)) {
+ $prefix = $regs[0];
+ $level = strlen($prefix);
+ $line = rtrim(substr($line, $level));
+ $line = $prefix . rc_wordwrap($line, $length - $level - 2, " \r\n$prefix ");
+ }
+ else if ($line) {
+ $line = rc_wordwrap(rtrim($line), $length - 2, " \r\n");
+ // space-stuffing
+ $line = preg_replace('/(^|\r\n)(From| |>)/', '\\1 \\2', $line);
+ }
+
+ $text[$idx] = $line;
+ }
+ }
+
+ return implode("\r\n", $text);
+ }
+
+}
diff --git a/program/steps/mail/addcontact.inc b/program/steps/mail/addcontact.inc
index 0120852a9..ab7bfc06f 100644
--- a/program/steps/mail/addcontact.inc
+++ b/program/steps/mail/addcontact.inc
@@ -1,94 +1,94 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/addcontact.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2009, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Add the submitted contact to the users address book |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
// only process ajax requests
if (!$OUTPUT->ajax_call)
return;
$abook = $RCMAIL->config->get('default_addressbook');
// Get configured addressbook
$CONTACTS = $RCMAIL->get_address_book($abook, true);
// Get first writeable addressbook if the configured doesn't exist
// This can happen when user deleted the addressbook (e.g. Kolab folder)
if ($abook == null || !is_object($CONTACTS)) {
$source = reset($RCMAIL->get_address_sources(true));
$CONTACTS = $RCMAIL->get_address_book($source['id'], true);
}
if (!empty($_POST['_address']) && is_object($CONTACTS))
{
- $contact_arr = $RCMAIL->imap->decode_address_list(get_input_value('_address', RCUBE_INPUT_POST, true), 1, false);
+ $contact_arr = rcube_mime::decode_address_list(get_input_value('_address', RCUBE_INPUT_POST, true), 1, false);
if (!empty($contact_arr[1]['mailto'])) {
$contact = array(
'email' => $contact_arr[1]['mailto'],
'name' => $contact_arr[1]['name']
);
// Validity checks
if (empty($contact['email'])) {
$OUTPUT->show_message('errorsavingcontact', 'error');
$OUTPUT->send();
}
$email = rcube_idn_to_ascii($contact['email']);
if (!check_email($email, false)) {
$OUTPUT->show_message('emailformaterror', 'error', array('email' => $contact['email']));
$OUTPUT->send();
}
$contact['email'] = rcube_idn_to_utf8($contact['email']);
$contact['name'] = rcube_addressbook::compose_display_name($contact);
// validate contact record
if (!$CONTACTS->validate($contact, true)) {
$error = $CONTACTS->get_error();
// TODO: show dialog to complete record
// if ($error['type'] == rcube_addressbook::ERROR_VALIDATE) { }
$OUTPUT->show_message($error['message'] ? $error['message'] : 'errorsavingcontact', 'error');
$OUTPUT->send();
}
// check for existing contacts
$existing = $CONTACTS->search('email', $contact['email'], 1, false);
if ($done = $existing->count)
$OUTPUT->show_message('contactexists', 'warning');
else {
$plugin = $RCMAIL->plugins->exec_hook('contact_create', array('record' => $contact, 'source' => null));
$contact = $plugin['record'];
$done = !$plugin['abort'] ? $CONTACTS->insert($contact) : $plugin['result'];
if ($done)
$OUTPUT->show_message('addedsuccessfully', 'confirmation');
}
}
}
if (!$done)
$OUTPUT->show_message($plugin['message'] ? $plugin['message'] : 'errorsavingcontact', 'error');
$OUTPUT->send();
diff --git a/program/steps/mail/compose.inc b/program/steps/mail/compose.inc
index 855fbab67..3095d2436 100644
--- a/program/steps/mail/compose.inc
+++ b/program/steps/mail/compose.inc
@@ -1,1491 +1,1491 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/compose.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Compose a new mail message with all headers and attachments |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
// define constants for message compose mode
define('RCUBE_COMPOSE_REPLY', 0x0106);
define('RCUBE_COMPOSE_FORWARD', 0x0107);
define('RCUBE_COMPOSE_DRAFT', 0x0108);
define('RCUBE_COMPOSE_EDIT', 0x0109);
$MESSAGE_FORM = null;
$MESSAGE = null;
$COMPOSE_ID = get_input_value('_id', RCUBE_INPUT_GET);
$COMPOSE = null;
if ($COMPOSE_ID && $_SESSION['compose_data_'.$COMPOSE_ID])
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
// give replicated session storage some time to synchronize
$retries = 0;
while ($COMPOSE_ID && !is_array($COMPOSE) && $RCMAIL->db->is_replicated() && $retries++ < 5) {
usleep(500000);
$RCMAIL->session->reload();
if ($_SESSION['compose_data_'.$COMPOSE_ID])
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
}
// Nothing below is called during message composition, only at "new/forward/reply/draft" initialization or
// if a compose-ID is given (i.e. when the compose step is opened in a new window/tab).
if (!is_array($COMPOSE))
{
// Infinite redirect prevention in case of broken session (#1487028)
if ($COMPOSE_ID)
raise_error(array('code' => 500, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid compose ID"), true, true);
$COMPOSE_ID = uniqid(mt_rand());
$_SESSION['compose_data_'.$COMPOSE_ID] = array(
'id' => $COMPOSE_ID,
'param' => request2param(RCUBE_INPUT_GET),
'mailbox' => $RCMAIL->imap->get_mailbox_name(),
);
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
// process values like "mailto:foo@bar.com?subject=new+message&cc=another"
if ($COMPOSE['param']['to']) {
// #1486037: remove "mailto:" prefix
$COMPOSE['param']['to'] = preg_replace('/^mailto:/i', '', $COMPOSE['param']['to']);
$mailto = explode('?', $COMPOSE['param']['to']);
if (count($mailto) > 1) {
$COMPOSE['param']['to'] = $mailto[0];
parse_str($mailto[1], $query);
foreach ($query as $f => $val)
$COMPOSE['param'][$f] = $val;
}
}
// select folder where to save the sent message
$COMPOSE['param']['sent_mbox'] = $RCMAIL->config->get('sent_mbox');
// pipe compose parameters thru plugins
$plugin = $RCMAIL->plugins->exec_hook('message_compose', $COMPOSE);
$COMPOSE['param'] = array_merge($COMPOSE['param'], $plugin['param']);
// add attachments listed by message_compose hook
if (is_array($plugin['attachments'])) {
foreach ($plugin['attachments'] as $attach) {
// we have structured data
if (is_array($attach)) {
$attachment = $attach;
}
// only a file path is given
else {
$filename = basename($attach);
$attachment = array(
'group' => $COMPOSE_ID,
'name' => $filename,
'mimetype' => rc_mime_content_type($attach, $filename),
'path' => $attach,
);
}
// save attachment if valid
if (($attachment['data'] && $attachment['name']) || ($attachment['path'] && file_exists($attachment['path']))) {
$attachment = rcmail::get_instance()->plugins->exec_hook('attachment_save', $attachment);
}
if ($attachment['status'] && !$attachment['abort']) {
unset($attachment['data'], $attachment['status'], $attachment['abort']);
$COMPOSE['attachments'][$attachment['id']] = $attachment;
}
}
}
// check if folder for saving sent messages exists and is subscribed (#1486802)
if ($sent_folder = $COMPOSE['param']['sent_mbox']) {
rcmail_check_sent_folder($sent_folder, true);
}
// redirect to a unique URL with all parameters stored in session
$OUTPUT->redirect(array('_action' => 'compose', '_id' => $COMPOSE['id']));
}
// add some labels to client
$OUTPUT->add_label('nosubject', 'nosenderwarning', 'norecipientwarning', 'nosubjectwarning', 'cancel',
'nobodywarning', 'notsentwarning', 'notuploadedwarning', 'savingmessage', 'sendingmessage',
'messagesaved', 'converting', 'editorwarning', 'searching', 'uploading', 'uploadingmany',
'fileuploaderror', 'sendmessage');
$OUTPUT->set_env('compose_id', $COMPOSE['id']);
// add config parameters to client script
if (!empty($CONFIG['drafts_mbox'])) {
$OUTPUT->set_env('drafts_mailbox', $CONFIG['drafts_mbox']);
$OUTPUT->set_env('draft_autosave', $CONFIG['draft_autosave']);
}
// set current mailbox in client environment
$OUTPUT->set_env('mailbox', $RCMAIL->imap->get_mailbox_name());
$OUTPUT->set_env('sig_above', $RCMAIL->config->get('sig_above', false));
$OUTPUT->set_env('top_posting', $RCMAIL->config->get('top_posting', false));
$OUTPUT->set_env('recipients_separator', trim($RCMAIL->config->get('recipients_separator', ',')));
// default font for HTML editor
$font = rcube_fontdefs($RCMAIL->config->get('default_font', 'Verdana'));
if ($font && !is_array($font)) {
$OUTPUT->set_env('default_font', $font);
}
// get reference message and set compose mode
if ($msg_uid = $COMPOSE['param']['draft_uid']) {
$RCMAIL->imap->set_mailbox($CONFIG['drafts_mbox']);
$compose_mode = RCUBE_COMPOSE_DRAFT;
}
else if ($msg_uid = $COMPOSE['param']['reply_uid'])
$compose_mode = RCUBE_COMPOSE_REPLY;
else if ($msg_uid = $COMPOSE['param']['forward_uid'])
$compose_mode = RCUBE_COMPOSE_FORWARD;
else if ($msg_uid = $COMPOSE['param']['uid'])
$compose_mode = RCUBE_COMPOSE_EDIT;
$config_show_sig = $RCMAIL->config->get('show_sig', 1);
if ($config_show_sig == 1)
$OUTPUT->set_env('show_sig', true);
else if ($config_show_sig == 2 && (empty($compose_mode) || $compose_mode == RCUBE_COMPOSE_EDIT || $compose_mode == RCUBE_COMPOSE_DRAFT))
$OUTPUT->set_env('show_sig', true);
else if ($config_show_sig == 3 && ($compose_mode == RCUBE_COMPOSE_REPLY || $compose_mode == RCUBE_COMPOSE_FORWARD))
$OUTPUT->set_env('show_sig', true);
else
$OUTPUT->set_env('show_sig', false);
// set line length for body wrapping
$LINE_LENGTH = $RCMAIL->config->get('line_length', 72);
if (!empty($msg_uid))
{
// similar as in program/steps/mail/show.inc
// re-set 'prefer_html' to have possibility to use html part for compose
$CONFIG['prefer_html'] = $CONFIG['prefer_html'] || $CONFIG['htmleditor'] || $compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT;
$MESSAGE = new rcube_message($msg_uid);
// make sure message is marked as read
if ($MESSAGE && $MESSAGE->headers && empty($MESSAGE->headers->flags['SEEN']))
$RCMAIL->imap->set_flag($msg_uid, 'SEEN');
if (!empty($MESSAGE->headers->charset))
$RCMAIL->imap->set_charset($MESSAGE->headers->charset);
if ($compose_mode == RCUBE_COMPOSE_REPLY)
{
$COMPOSE['reply_uid'] = $msg_uid;
$COMPOSE['reply_msgid'] = $MESSAGE->headers->messageID;
$COMPOSE['references'] = trim($MESSAGE->headers->references . " " . $MESSAGE->headers->messageID);
if (!empty($COMPOSE['param']['all']))
$MESSAGE->reply_all = $COMPOSE['param']['all'];
$OUTPUT->set_env('compose_mode', 'reply');
// Save the sent message in the same folder of the message being replied to
if ($RCMAIL->config->get('reply_same_folder') && ($sent_folder = $COMPOSE['mailbox'])
&& rcmail_check_sent_folder($sent_folder, false)
) {
$COMPOSE['param']['sent_mbox'] = $sent_folder;
}
}
else if ($compose_mode == RCUBE_COMPOSE_DRAFT)
{
if ($MESSAGE->headers->others['x-draft-info'])
{
// get reply_uid/forward_uid to flag the original message when sending
$info = rcmail_draftinfo_decode($MESSAGE->headers->others['x-draft-info']);
if ($info['type'] == 'reply')
$COMPOSE['reply_uid'] = $info['uid'];
else if ($info['type'] == 'forward')
$COMPOSE['forward_uid'] = $info['uid'];
$COMPOSE['mailbox'] = $info['folder'];
// Save the sent message in the same folder of the message being replied to
if ($RCMAIL->config->get('reply_same_folder') && ($sent_folder = $info['folder'])
&& rcmail_check_sent_folder($sent_folder, false)
) {
$COMPOSE['param']['sent_mbox'] = $sent_folder;
}
}
if ($MESSAGE->headers->in_reply_to)
$COMPOSE['reply_msgid'] = '<'.$MESSAGE->headers->in_reply_to.'>';
$COMPOSE['references'] = $MESSAGE->headers->references;
}
else if ($compose_mode == RCUBE_COMPOSE_FORWARD)
{
$COMPOSE['forward_uid'] = $msg_uid;
$OUTPUT->set_env('compose_mode', 'forward');
if (!empty($COMPOSE['param']['attachment']))
$MESSAGE->forward_attachment = true;
}
}
$MESSAGE->compose = array();
// get user's identities
$MESSAGE->identities = $RCMAIL->user->list_identities();
if (count($MESSAGE->identities))
{
foreach ($MESSAGE->identities as $idx => $ident) {
$email = mb_strtolower(rcube_idn_to_utf8($ident['email']));
$MESSAGE->identities[$idx]['email_ascii'] = $ident['email'];
$MESSAGE->identities[$idx]['ident'] = format_email_recipient($ident['email'], $ident['name']);
$MESSAGE->identities[$idx]['email'] = $email;
}
}
// Set From field value
if (!empty($_POST['_from'])) {
$MESSAGE->compose['from'] = get_input_value('_from', RCUBE_INPUT_POST);
}
else if (!empty($COMPOSE['param']['from'])) {
$MESSAGE->compose['from'] = $COMPOSE['param']['from'];
}
else if (count($MESSAGE->identities)) {
$a_recipients = array();
$a_names = array();
// extract all recipients of the reply-message
if (is_object($MESSAGE->headers) && in_array($compose_mode, array(RCUBE_COMPOSE_REPLY, RCUBE_COMPOSE_FORWARD)))
{
- $a_to = $RCMAIL->imap->decode_address_list($MESSAGE->headers->to);
+ $a_to = rcube_mime::decode_address_list($MESSAGE->headers->to, null, true, $MESSAGE->headers->charset);
foreach ($a_to as $addr) {
if (!empty($addr['mailto'])) {
$a_recipients[] = strtolower($addr['mailto']);
$a_names[] = $addr['name'];
}
}
if (!empty($MESSAGE->headers->cc)) {
- $a_cc = $RCMAIL->imap->decode_address_list($MESSAGE->headers->cc);
+ $a_cc = rcube_mime::decode_address_list($MESSAGE->headers->cc, null, true, $MESSAGE->headers->charset);
foreach ($a_cc as $addr) {
if (!empty($addr['mailto'])) {
$a_recipients[] = strtolower($addr['mailto']);
$a_names[] = $addr['name'];
}
}
}
}
$from_idx = null;
$default_identity = null;
$return_path = $MESSAGE->headers->others['return-path'];
// Select identity
foreach ($MESSAGE->identities as $idx => $ident) {
// save default identity ID
if ($ident['standard']) {
$default_identity = $idx;
}
// use From header
if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) {
if ($MESSAGE->headers->from == $ident['ident']) {
$from_idx = $idx;
break;
}
}
// reply to yourself
else if ($compose_mode == RCUBE_COMPOSE_REPLY && $MESSAGE->headers->from == $ident['ident']) {
$from_idx = $idx;
break;
}
// use replied message recipients
else if (($found = array_search($ident['email_ascii'], $a_recipients)) !== false) {
// match identity name, prefer default identity
if ($from_idx === null || ($a_names[$found] && $ident['name'] && $a_names[$found] == $ident['name'])) {
$from_idx = $idx;
}
}
}
// Fallback using Return-Path
if ($from_idx === null && $return_path) {
foreach ($MESSAGE->identities as $idx => $ident) {
if (strpos($return_path, str_replace('@', '=', $ident['email_ascii']).'@') !== false) {
$from_idx = $idx;
break;
}
}
}
// Still no ID, use default/first identity
if ($from_idx === null) {
$from_idx = $default_identity !== null ? $default_identity : key(reset($MESSAGE->identities));
}
$ident = $MESSAGE->identities[$from_idx];
$from_id = $ident['identity_id'];
$MESSAGE->compose['from_email'] = $ident['email'];
$MESSAGE->compose['from'] = $from_id;
}
// Set other headers
$a_recipients = array();
$parts = array('to', 'cc', 'bcc', 'replyto', 'followupto');
$separator = trim($RCMAIL->config->get('recipients_separator', ',')) . ' ';
foreach ($parts as $header) {
$fvalue = '';
$decode_header = true;
// we have a set of recipients stored is session
if ($header == 'to' && ($mailto_id = $COMPOSE['param']['mailto'])
&& $_SESSION['mailto'][$mailto_id]
) {
$fvalue = urldecode($_SESSION['mailto'][$mailto_id]);
$decode_header = false;
// make session to not grow up too much
unset($_SESSION['mailto'][$mailto_id]);
$COMPOSE['param']['to'] = $fvalue;
}
else if (!empty($_POST['_'.$header])) {
$fvalue = get_input_value('_'.$header, RCUBE_INPUT_POST, TRUE);
}
else if (!empty($COMPOSE['param'][$header])) {
$fvalue = $COMPOSE['param'][$header];
}
else if ($compose_mode == RCUBE_COMPOSE_REPLY) {
// get recipent address(es) out of the message headers
if ($header == 'to') {
$mailfollowup = $MESSAGE->headers->others['mail-followup-to'];
$mailreplyto = $MESSAGE->headers->others['mail-reply-to'];
// Reply to mailing list...
if ($MESSAGE->reply_all == 'list' && $mailfollowup)
$fvalue = $mailfollowup;
else if ($MESSAGE->reply_all == 'list'
&& preg_match('/<mailto:([^>]+)>/i', $MESSAGE->headers->others['list-post'], $m))
$fvalue = $m[1];
// Reply to...
else if ($MESSAGE->reply_all && $mailfollowup)
$fvalue = $mailfollowup;
else if ($mailreplyto)
$fvalue = $mailreplyto;
else if (!empty($MESSAGE->headers->replyto))
$fvalue = $MESSAGE->headers->replyto;
else if (!empty($MESSAGE->headers->from))
$fvalue = $MESSAGE->headers->from;
// Reply to message sent by yourself (#1487074)
if (!empty($ident) && $fvalue == $ident['ident']) {
$fvalue = $MESSAGE->headers->to;
}
}
// add recipient of original message if reply to all
else if ($header == 'cc' && !empty($MESSAGE->reply_all) && $MESSAGE->reply_all != 'list') {
if ($v = $MESSAGE->headers->to)
$fvalue .= $v;
if ($v = $MESSAGE->headers->cc)
$fvalue .= (!empty($fvalue) ? $separator : '') . $v;
}
}
else if (in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT))) {
// get drafted headers
if ($header=='to' && !empty($MESSAGE->headers->to))
$fvalue = $MESSAGE->get_header('to');
else if ($header=='cc' && !empty($MESSAGE->headers->cc))
$fvalue = $MESSAGE->get_header('cc');
else if ($header=='bcc' && !empty($MESSAGE->headers->bcc))
$fvalue = $MESSAGE->get_header('bcc');
else if ($header=='replyto' && !empty($MESSAGE->headers->others['mail-reply-to']))
$fvalue = $MESSAGE->get_header('mail-reply-to');
else if ($header=='replyto' && !empty($MESSAGE->headers->replyto))
$fvalue = $MESSAGE->get_header('reply-to');
else if ($header=='followupto' && !empty($MESSAGE->headers->others['mail-followup-to']))
$fvalue = $MESSAGE->get_header('mail-followup-to');
}
// split recipients and put them back together in a unique way
if (!empty($fvalue) && in_array($header, array('to', 'cc', 'bcc'))) {
- $to_addresses = $RCMAIL->imap->decode_address_list($fvalue, null, $decode_header);
+ $to_addresses = rcube_mime::decode_address_list($fvalue, null, $decode_header, $MESSAGE->headers->charset);
$fvalue = array();
foreach ($to_addresses as $addr_part) {
if (empty($addr_part['mailto']))
continue;
$mailto = mb_strtolower(rcube_idn_to_utf8($addr_part['mailto']));
if (!in_array($mailto, $a_recipients)
&& ($header == 'to' || empty($MESSAGE->compose['from_email']) || $mailto != $MESSAGE->compose['from_email'])
) {
if ($addr_part['name'] && $addr_part['mailto'] != $addr_part['name'])
$string = format_email_recipient($mailto, $addr_part['name']);
else
$string = $mailto;
$fvalue[] = $string;
$a_recipients[] = $addr_part['mailto'];
}
}
$fvalue = implode($separator, $fvalue);
}
$MESSAGE->compose[$header] = $fvalue;
}
unset($a_recipients);
// process $MESSAGE body/attachments, set $MESSAGE_BODY/$HTML_MODE vars and some session data
$MESSAGE_BODY = rcmail_prepare_message_body();
/****** compose mode functions ********/
function rcmail_compose_headers($attrib)
{
global $MESSAGE;
list($form_start, $form_end) = get_form_tags($attrib);
$out = '';
$part = strtolower($attrib['part']);
switch ($part)
{
case 'from':
return $form_start . rcmail_compose_header_from($attrib);
case 'to':
case 'cc':
case 'bcc':
$fname = '_' . $part;
$header = $param = $part;
$allow_attrib = array('id', 'class', 'style', 'cols', 'rows', 'tabindex');
$field_type = 'html_textarea';
break;
case 'replyto':
case 'reply-to':
$fname = '_replyto';
$param = 'replyto';
$header = 'reply-to';
case 'followupto':
case 'followup-to':
if (!$fname) {
$fname = '_followupto';
$param = 'followupto';
$header = 'mail-followup-to';
}
$allow_attrib = array('id', 'class', 'style', 'size', 'tabindex');
$field_type = 'html_inputfield';
break;
}
if ($fname && $field_type)
{
// pass the following attributes to the form class
$field_attrib = array('name' => $fname, 'spellcheck' => 'false');
foreach ($attrib as $attr => $value)
if (in_array($attr, $allow_attrib))
$field_attrib[$attr] = $value;
// create teaxtarea object
$input = new $field_type($field_attrib);
$out = $input->show($MESSAGE->compose[$param]);
}
if ($form_start)
$out = $form_start.$out;
// configure autocompletion
rcube_autocomplete_init();
return $out;
}
function rcmail_compose_header_from($attrib)
{
global $MESSAGE, $OUTPUT;
// pass the following attributes to the form class
$field_attrib = array('name' => '_from');
foreach ($attrib as $attr => $value)
if (in_array($attr, array('id', 'class', 'style', 'size', 'tabindex')))
$field_attrib[$attr] = $value;
if (count($MESSAGE->identities))
{
$a_signatures = array();
$field_attrib['onchange'] = JS_OBJECT_NAME.".change_identity(this)";
$select_from = new html_select($field_attrib);
// create SELECT element
foreach ($MESSAGE->identities as $sql_arr)
{
$identity_id = $sql_arr['identity_id'];
$select_from->add(format_email_recipient($sql_arr['email'], $sql_arr['name']), $identity_id);
// add signature to array
if (!empty($sql_arr['signature']) && empty($COMPOSE['param']['nosig']))
{
$a_signatures[$identity_id]['text'] = $sql_arr['signature'];
$a_signatures[$identity_id]['is_html'] = ($sql_arr['html_signature'] == 1) ? true : false;
if ($a_signatures[$identity_id]['is_html'])
{
$h2t = new html2text($a_signatures[$identity_id]['text'], false, false);
$a_signatures[$identity_id]['plain_text'] = trim($h2t->get_text());
}
}
}
$out = $select_from->show($MESSAGE->compose['from']);
// add signatures to client
$OUTPUT->set_env('signatures', $a_signatures);
}
// no identities, display text input field
else {
$field_attrib['class'] = 'from_address';
$input_from = new html_inputfield($field_attrib);
$out = $input_from->show($MESSAGE->compose['from']);
}
return $out;
}
function rcmail_compose_editor_mode()
{
global $RCMAIL, $MESSAGE, $compose_mode;
static $useHtml;
if ($useHtml !== null)
return $useHtml;
$html_editor = intval($RCMAIL->config->get('htmleditor'));
if ($compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT) {
$useHtml = $MESSAGE->has_html_part();
}
else if ($compose_mode == RCUBE_COMPOSE_REPLY) {
$useHtml = ($html_editor == 1 || ($html_editor == 2 && $MESSAGE->has_html_part()));
}
else { // RCUBE_COMPOSE_FORWARD or NEW
$useHtml = ($html_editor == 1);
}
return $useHtml;
}
function rcmail_prepare_message_body()
{
global $RCMAIL, $MESSAGE, $COMPOSE, $compose_mode, $LINE_LENGTH, $HTML_MODE;
// use posted message body
if (!empty($_POST['_message'])) {
$body = get_input_value('_message', RCUBE_INPUT_POST, true);
$isHtml = (bool) get_input_value('_is_html', RCUBE_INPUT_POST);
}
else if ($COMPOSE['param']['body']) {
$body = $COMPOSE['param']['body'];
$isHtml = false;
}
// forward as attachment
else if ($compose_mode == RCUBE_COMPOSE_FORWARD && $MESSAGE->forward_attachment) {
$isHtml = rcmail_compose_editor_mode();
$body = '';
if (empty($COMPOSE['attachments']))
rcmail_write_forward_attachment($MESSAGE);
}
// reply/edit/draft/forward
else if ($compose_mode) {
$has_html_part = $MESSAGE->has_html_part();
$isHtml = rcmail_compose_editor_mode();
if ($isHtml) {
if ($has_html_part) {
$body = $MESSAGE->first_html_part();
}
else {
$body = $MESSAGE->first_text_part();
// try to remove the signature
if ($RCMAIL->config->get('strip_existing_sig', true))
$body = rcmail_remove_signature($body);
// add HTML formatting
$body = rcmail_plain_body($body);
if ($body)
$body = '<pre>' . $body . '</pre>';
}
}
else {
if ($has_html_part) {
// use html part if it has been used for message (pre)viewing
// decrease line length for quoting
$len = $compose_mode == RCUBE_COMPOSE_REPLY ? $LINE_LENGTH-2 : $LINE_LENGTH;
$txt = new html2text($MESSAGE->first_html_part(), false, true, $len);
$body = $txt->get_text();
}
else {
$body = $MESSAGE->first_text_part($part);
if ($body && $part && $part->ctype_secondary == 'plain'
&& $part->ctype_parameters['format'] == 'flowed'
) {
- $body = rcube_message::unfold_flowed($body);
+ $body = rcube_mime::unfold_flowed($body);
}
}
}
// compose reply-body
if ($compose_mode == RCUBE_COMPOSE_REPLY)
$body = rcmail_create_reply_body($body, $isHtml);
// forward message body inline
else if ($compose_mode == RCUBE_COMPOSE_FORWARD)
$body = rcmail_create_forward_body($body, $isHtml);
// load draft message body
else if ($compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT)
$body = rcmail_create_draft_body($body, $isHtml);
}
else { // new message
$isHtml = rcmail_compose_editor_mode();
}
$plugin = $RCMAIL->plugins->exec_hook('message_compose_body',
array('body' => $body, 'html' => $isHtml, 'mode' => $compose_mode));
$body = $plugin['body'];
unset($plugin);
// add blocked.gif attachment (#1486516)
if ($isHtml && preg_match('#<img src="\./program/blocked\.gif"#', $body)) {
if ($attachment = rcmail_save_image('program/blocked.gif', 'image/gif')) {
$COMPOSE['attachments'][$attachment['id']] = $attachment;
$body = preg_replace('#\./program/blocked\.gif#',
$RCMAIL->comm_path.'&_action=display-attachment&_file=rcmfile'.$attachment['id'].'&_id='.$COMPOSE['id'],
$body);
}
}
$HTML_MODE = $isHtml;
return $body;
}
function rcmail_compose_body($attrib)
{
global $RCMAIL, $CONFIG, $OUTPUT, $MESSAGE, $compose_mode, $LINE_LENGTH, $HTML_MODE, $MESSAGE_BODY;
list($form_start, $form_end) = get_form_tags($attrib);
unset($attrib['form']);
if (empty($attrib['id']))
$attrib['id'] = 'rcmComposeBody';
$attrib['name'] = '_message';
$isHtml = $HTML_MODE;
$out = $form_start ? "$form_start\n" : '';
$saveid = new html_hiddenfield(array('name' => '_draft_saveid', 'value' => $compose_mode==RCUBE_COMPOSE_DRAFT ? str_replace(array('<','>'), "", $MESSAGE->headers->messageID) : ''));
$out .= $saveid->show();
$drafttoggle = new html_hiddenfield(array('name' => '_draft', 'value' => 'yes'));
$out .= $drafttoggle->show();
$msgtype = new html_hiddenfield(array('name' => '_is_html', 'value' => ($isHtml?"1":"0")));
$out .= $msgtype->show();
// If desired, set this textarea to be editable by TinyMCE
if ($isHtml) {
$attrib['class'] = 'mce_editor';
$textarea = new html_textarea($attrib);
$out .= $textarea->show($MESSAGE_BODY);
}
else {
$textarea = new html_textarea($attrib);
$out .= $textarea->show('');
// quote plain text, inject into textarea
$table = get_html_translation_table(HTML_SPECIALCHARS);
$MESSAGE_BODY = strtr($MESSAGE_BODY, $table);
$out = substr($out, 0, -11) . $MESSAGE_BODY . '</textarea>';
}
$out .= $form_end ? "\n$form_end" : '';
$OUTPUT->set_env('composebody', $attrib['id']);
// include HTML editor
rcube_html_editor();
// include GoogieSpell
if (!empty($CONFIG['enable_spellcheck'])) {
$engine = $RCMAIL->config->get('spellcheck_engine','googie');
$dictionary = (bool) $RCMAIL->config->get('spellcheck_dictionary');
$spellcheck_langs = (array) $RCMAIL->config->get('spellcheck_languages',
array('da'=>'Dansk', 'de'=>'Deutsch', 'en' => 'English', 'es'=>'Español',
'fr'=>'Français', 'it'=>'Italiano', 'nl'=>'Nederlands', 'pl'=>'Polski',
'pt'=>'Português', 'fi'=>'Suomi', 'sv'=>'Svenska'));
// googie works only with two-letter codes
if ($engine == 'googie') {
$lang = strtolower(substr($_SESSION['language'], 0, 2));
$spellcheck_langs_googie = array();
foreach ($spellcheck_langs as $key => $name)
$spellcheck_langs_googie[strtolower(substr($key,0,2))] = $name;
$spellcheck_langs = $spellcheck_langs_googie;
}
else {
$lang = $_SESSION['language'];
// if not found in the list, try with two-letter code
if (!$spellcheck_langs[$lang])
$lang = strtolower(substr($lang, 0, 2));
}
if (!$spellcheck_langs[$lang])
$lang = 'en';
$editor_lang_set = array();
foreach ($spellcheck_langs as $key => $name) {
$editor_lang_set[] = ($key == $lang ? '+' : '') . JQ($name).'='.JQ($key);
}
$OUTPUT->include_script('googiespell.js');
$OUTPUT->add_script(sprintf(
"var googie = new GoogieSpell('\$__skin_path/images/googiespell/','?_task=utils&_action=spell&lang=', %s);\n".
"googie.lang_chck_spell = \"%s\";\n".
"googie.lang_rsm_edt = \"%s\";\n".
"googie.lang_close = \"%s\";\n".
"googie.lang_revert = \"%s\";\n".
"googie.lang_no_error_found = \"%s\";\n".
"googie.lang_learn_word = \"%s\";\n".
"googie.setLanguages(%s);\n".
"googie.setCurrentLanguage('%s');\n".
"googie.setSpellContainer('spellcheck-control');\n".
"googie.decorateTextarea('%s');\n".
"%s.set_env('spellcheck', googie);",
!empty($dictionary) ? 'true' : 'false',
JQ(Q(rcube_label('checkspelling'))),
JQ(Q(rcube_label('resumeediting'))),
JQ(Q(rcube_label('close'))),
JQ(Q(rcube_label('revertto'))),
JQ(Q(rcube_label('nospellerrors'))),
JQ(Q(rcube_label('addtodict'))),
json_serialize($spellcheck_langs),
$lang,
$attrib['id'],
JS_OBJECT_NAME), 'foot');
$OUTPUT->add_label('checking');
$OUTPUT->set_env('spellcheck_langs', join(',', $editor_lang_set));
}
$out .= "\n".'<iframe name="savetarget" src="program/blank.gif" style="width:0;height:0;border:none;visibility:hidden;"></iframe>';
return $out;
}
function rcmail_create_reply_body($body, $bodyIsHtml)
{
global $RCMAIL, $MESSAGE, $LINE_LENGTH;
// build reply prefix
- $from = array_pop($RCMAIL->imap->decode_address_list($MESSAGE->get_header('from'), 1, false));
+ $from = array_pop(rcube_mime::decode_address_list($MESSAGE->get_header('from'), 1, false, $MESSAGE->headers->charset));
$prefix = rcube_label(array(
'name' => 'mailreplyintro',
'vars' => array(
'date' => format_date($MESSAGE->headers->date, $RCMAIL->config->get('date_long')),
'sender' => $from['name'] ? $from['name'] : rcube_idn_to_utf8($from['mailto']),
)
));
if (!$bodyIsHtml) {
$body = preg_replace('/\r?\n/', "\n", $body);
// try to remove the signature
if ($RCMAIL->config->get('strip_existing_sig', true))
$body = rcmail_remove_signature($body);
// soft-wrap and quote message text
$body = rcmail_wrap_and_quote(rtrim($body, "\n"), $LINE_LENGTH);
$prefix .= "\n";
$suffix = '';
if ($RCMAIL->config->get('top_posting'))
$prefix = "\n\n\n" . $prefix;
}
else {
// save inline images to files
$cid_map = rcmail_write_inline_attachments($MESSAGE);
// set is_safe flag (we need this for html body washing)
rcmail_check_safe($MESSAGE);
// clean up html tags
$body = rcmail_wash_html($body, array('safe' => $MESSAGE->is_safe), $cid_map);
// build reply (quote content)
$prefix = '<p>' . Q($prefix) . "</p>\n";
$prefix .= '<blockquote>';
if ($RCMAIL->config->get('top_posting')) {
$prefix = '<br>' . $prefix;
$suffix = '</blockquote>';
}
else {
$suffix = '</blockquote><p></p>';
}
}
return $prefix.$body.$suffix;
}
function rcmail_create_forward_body($body, $bodyIsHtml)
{
global $RCMAIL, $MESSAGE, $COMPOSE;
// add attachments
if (!isset($COMPOSE['forward_attachments']) && is_array($MESSAGE->mime_parts))
$cid_map = rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml);
$date = format_date($MESSAGE->headers->date, $RCMAIL->config->get('date_long'));
$charset = $RCMAIL->output->get_charset();
if (!$bodyIsHtml)
{
$prefix = "\n\n\n-------- " . rcube_label('originalmessage') . " --------\n";
$prefix .= rcube_label('subject') . ': ' . $MESSAGE->subject . "\n";
$prefix .= rcube_label('date') . ': ' . $date . "\n";
$prefix .= rcube_label('from') . ': ' . $MESSAGE->get_header('from') . "\n";
$prefix .= rcube_label('to') . ': ' . $MESSAGE->get_header('to') . "\n";
if ($MESSAGE->headers->cc)
$prefix .= rcube_label('cc') . ': ' . $MESSAGE->get_header('cc') . "\n";
if ($MESSAGE->headers->replyto && $MESSAGE->headers->replyto != $MESSAGE->headers->from)
$prefix .= rcube_label('replyto') . ': ' . $MESSAGE->get_header('replyto') . "\n";
$prefix .= "\n";
}
else
{
// set is_safe flag (we need this for html body washing)
rcmail_check_safe($MESSAGE);
// clean up html tags
$body = rcmail_wash_html($body, array('safe' => $MESSAGE->is_safe), $cid_map);
$prefix = sprintf(
"<br /><p>-------- " . rcube_label('originalmessage') . " --------</p>" .
"<table border=\"0\" cellpadding=\"0\" cellspacing=\"0\"><tbody>" .
"<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>" .
"<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>" .
"<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>" .
"<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>",
rcube_label('subject'), Q($MESSAGE->subject),
rcube_label('date'), Q($date),
rcube_label('from'), htmlspecialchars(Q($MESSAGE->get_header('from'), 'replace'), ENT_COMPAT, $charset),
rcube_label('to'), htmlspecialchars(Q($MESSAGE->get_header('to'), 'replace'), ENT_COMPAT, $charset));
if ($MESSAGE->headers->cc)
$prefix .= sprintf("<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>",
rcube_label('cc'),
htmlspecialchars(Q($MESSAGE->get_header('cc'), 'replace'), ENT_COMPAT, $charset));
if ($MESSAGE->headers->replyto && $MESSAGE->headers->replyto != $MESSAGE->headers->from)
$prefix .= sprintf("<tr><th align=\"right\" nowrap=\"nowrap\" valign=\"baseline\">%s: </th><td>%s</td></tr>",
rcube_label('replyto'),
htmlspecialchars(Q($MESSAGE->get_header('replyto'), 'replace'), ENT_COMPAT, $charset));
$prefix .= "</tbody></table><br>";
}
return $prefix.$body;
}
function rcmail_create_draft_body($body, $bodyIsHtml)
{
global $MESSAGE, $OUTPUT, $COMPOSE;
/**
* add attachments
* sizeof($MESSAGE->mime_parts can be 1 - e.g. attachment, but no text!
*/
if (empty($COMPOSE['forward_attachments'])
&& is_array($MESSAGE->mime_parts)
&& count($MESSAGE->mime_parts) > 0)
{
$cid_map = rcmail_write_compose_attachments($MESSAGE, $bodyIsHtml);
// replace cid with href in inline images links
if ($cid_map)
$body = str_replace(array_keys($cid_map), array_values($cid_map), $body);
}
return $body;
}
function rcmail_remove_signature($body)
{
global $RCMAIL;
$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-1));
}
break;
}
}
return $body;
}
function rcmail_write_compose_attachments(&$message, $bodyIsHtml)
{
global $RCMAIL, $COMPOSE;
$cid_map = $messages = array();
foreach ((array)$message->mime_parts as $pid => $part)
{
if (($part->ctype_primary != 'message' || !$bodyIsHtml) && $part->ctype_primary != 'multipart' &&
($part->disposition == 'attachment' || ($part->disposition == 'inline' && $bodyIsHtml) || $part->filename)
&& $part->mimetype != 'application/ms-tnef'
) {
$skip = false;
if ($part->mimetype == 'message/rfc822') {
$messages[] = $part->mime_id;
} else if ($messages) {
// skip attachments included in message/rfc822 attachment (#1486487)
foreach ($messages as $mimeid)
if (strpos($part->mime_id, $mimeid.'.') === 0) {
$skip = true;
break;
}
}
if (!$skip && ($attachment = rcmail_save_attachment($message, $pid))) {
$COMPOSE['attachments'][$attachment['id']] = $attachment;
if ($bodyIsHtml && ($part->content_id || $part->content_location)) {
$url = $RCMAIL->comm_path.'&_action=display-attachment&_file=rcmfile'.$attachment['id'].'&_id='.$COMPOSE['id'];
if ($part->content_id)
$cid_map['cid:'.$part->content_id] = $url;
else
$cid_map[$part->content_location] = $url;
}
}
}
}
$COMPOSE['forward_attachments'] = true;
return $cid_map;
}
function rcmail_write_inline_attachments(&$message)
{
global $RCMAIL, $COMPOSE;
$cid_map = array();
foreach ((array)$message->mime_parts as $pid => $part) {
if (($part->content_id || $part->content_location) && $part->filename) {
if ($attachment = rcmail_save_attachment($message, $pid)) {
$COMPOSE['attachments'][$attachment['id']] = $attachment;
$url = $RCMAIL->comm_path.'&_action=display-attachment&_file=rcmfile'.$attachment['id'].'&_id='.$COMPOSE['id'];
if ($part->content_id)
$cid_map['cid:'.$part->content_id] = $url;
else
$cid_map[$part->content_location] = $url;
}
}
}
return $cid_map;
}
// Creates an attachment from the forwarded message
function rcmail_write_forward_attachment(&$message)
{
global $RCMAIL, $COMPOSE;
if (strlen($message->subject)) {
$name = mb_substr($message->subject, 0, 64) . '.eml';
}
else {
$name = 'message_rfc822.eml';
}
$mem_limit = parse_bytes(ini_get('memory_limit'));
$curr_mem = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
$data = $path = null;
// don't load too big attachments into memory
if ($mem_limit > 0 && $message->size > $mem_limit - $curr_mem) {
$temp_dir = unslashify($RCMAIL->config->get('temp_dir'));
$path = tempnam($temp_dir, 'rcmAttmnt');
if ($fp = fopen($path, 'w')) {
$RCMAIL->imap->get_raw_body($message->uid, $fp);
fclose($fp);
} else
return false;
} else {
$data = $RCMAIL->imap->get_raw_body($message->uid);
}
$attachment = array(
'group' => $COMPOSE['id'],
'name' => $name,
'mimetype' => 'message/rfc822',
'data' => $data,
'path' => $path,
'size' => $path ? filesize($path) : strlen($data),
);
$attachment = $RCMAIL->plugins->exec_hook('attachment_save', $attachment);
if ($attachment['status']) {
unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']);
$COMPOSE['attachments'][$attachment['id']] = $attachment;
return true;
} else if ($path) {
@unlink($path);
}
return false;
}
function rcmail_save_attachment(&$message, $pid)
{
global $COMPOSE;
$rcmail = rcmail::get_instance();
$part = $message->mime_parts[$pid];
$mem_limit = parse_bytes(ini_get('memory_limit'));
$curr_mem = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
$data = $path = null;
// don't load too big attachments into memory
if ($mem_limit > 0 && $part->size > $mem_limit - $curr_mem) {
$temp_dir = unslashify($rcmail->config->get('temp_dir'));
$path = tempnam($temp_dir, 'rcmAttmnt');
if ($fp = fopen($path, 'w')) {
$message->get_part_content($pid, $fp);
fclose($fp);
} else
return false;
} else {
$data = $message->get_part_content($pid);
}
$attachment = array(
'group' => $COMPOSE['id'],
'name' => $part->filename ? $part->filename : 'Part_'.$pid.'.'.$part->ctype_secondary,
'mimetype' => $part->ctype_primary . '/' . $part->ctype_secondary,
'content_id' => $part->content_id,
'data' => $data,
'path' => $path,
'size' => $path ? filesize($path) : strlen($data),
);
$attachment = $rcmail->plugins->exec_hook('attachment_save', $attachment);
if ($attachment['status']) {
unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']);
return $attachment;
} else if ($path) {
@unlink($path);
}
return false;
}
function rcmail_save_image($path, $mimetype='')
{
global $COMPOSE;
// handle attachments in memory
$data = file_get_contents($path);
$attachment = array(
'group' => $COMPOSE['id'],
'name' => rcmail_basename($path),
'mimetype' => $mimetype ? $mimetype : rc_mime_content_type($path, $name),
'data' => $data,
'size' => strlen($data),
);
$attachment = rcmail::get_instance()->plugins->exec_hook('attachment_save', $attachment);
if ($attachment['status']) {
unset($attachment['data'], $attachment['status'], $attachment['content_id'], $attachment['abort']);
return $attachment;
}
return false;
}
function rcmail_basename($filename)
{
// basename() is not unicode safe and locale dependent
if (stristr(PHP_OS, 'win') || stristr(PHP_OS, 'netware')) {
return preg_replace('/^.*[\\\\\\/]/', '', $filename);
} else {
return preg_replace('/^.*[\/]/', '', $filename);
}
}
function rcmail_compose_subject($attrib)
{
global $MESSAGE, $COMPOSE, $compose_mode;
list($form_start, $form_end) = get_form_tags($attrib);
unset($attrib['form']);
$attrib['name'] = '_subject';
$attrib['spellcheck'] = 'true';
$textfield = new html_inputfield($attrib);
$subject = '';
// use subject from post
if (isset($_POST['_subject'])) {
$subject = get_input_value('_subject', RCUBE_INPUT_POST, TRUE);
}
// create a reply-subject
else if ($compose_mode == RCUBE_COMPOSE_REPLY) {
if (preg_match('/^re:/i', $MESSAGE->subject))
$subject = $MESSAGE->subject;
else
$subject = 'Re: '.$MESSAGE->subject;
}
// create a forward-subject
else if ($compose_mode == RCUBE_COMPOSE_FORWARD) {
if (preg_match('/^fwd:/i', $MESSAGE->subject))
$subject = $MESSAGE->subject;
else
$subject = 'Fwd: '.$MESSAGE->subject;
}
// creeate a draft-subject
else if ($compose_mode == RCUBE_COMPOSE_DRAFT || $compose_mode == RCUBE_COMPOSE_EDIT) {
$subject = $MESSAGE->subject;
}
else if (!empty($COMPOSE['param']['subject'])) {
$subject = $COMPOSE['param']['subject'];
}
$out = $form_start ? "$form_start\n" : '';
$out .= $textfield->show($subject);
$out .= $form_end ? "\n$form_end" : '';
return $out;
}
function rcmail_compose_attachment_list($attrib)
{
global $OUTPUT, $CONFIG, $COMPOSE;
// add ID if not given
if (!$attrib['id'])
$attrib['id'] = 'rcmAttachmentList';
$out = "\n";
$jslist = array();
if (is_array($COMPOSE['attachments'])) {
if ($attrib['deleteicon']) {
$button = html::img(array(
'src' => $CONFIG['skin_path'] . $attrib['deleteicon'],
'alt' => rcube_label('delete')
));
}
else
$button = Q(rcube_label('delete'));
foreach ($COMPOSE['attachments'] as $id => $a_prop) {
if (empty($a_prop))
continue;
$out .= html::tag('li', array('id' => 'rcmfile'.$id, 'class' => rcmail_filetype2classname($a_prop['mimetype'], $a_prop['name'])),
html::a(array(
'href' => "#delete",
'title' => rcube_label('delete'),
'onclick' => sprintf("return %s.command('remove-attachment','rcmfile%s', this)", JS_OBJECT_NAME, $id),
'class' => 'delete'),
$button) . Q($a_prop['name']));
$jslist['rcmfile'.$id] = array('name' => $a_prop['name'], 'complete' => true, 'mimetype' => $a_prop['mimetype']);
}
}
if ($attrib['deleteicon'])
$COMPOSE['deleteicon'] = $CONFIG['skin_path'] . $attrib['deleteicon'];
if ($attrib['cancelicon'])
$OUTPUT->set_env('cancelicon', $CONFIG['skin_path'] . $attrib['cancelicon']);
if ($attrib['loadingicon'])
$OUTPUT->set_env('loadingicon', $CONFIG['skin_path'] . $attrib['loadingicon']);
$OUTPUT->set_env('attachments', $jslist);
$OUTPUT->add_gui_object('attachmentlist', $attrib['id']);
return html::tag('ul', $attrib, $out, html::$common_attrib);
}
function rcmail_compose_attachment_form($attrib)
{
global $OUTPUT;
// set defaults
$attrib += array('id' => 'rcmUploadbox', 'buttons' => 'yes');
// Get filesize, enable upload progress bar
$max_filesize = rcube_upload_init();
$button = new html_inputfield(array('type' => 'button'));
$out = html::div($attrib,
$OUTPUT->form_tag(array('id' => $attrib['id'].'Frm', 'name' => 'uploadform', 'method' => 'post', 'enctype' => 'multipart/form-data'),
html::div(null, rcmail_compose_attachment_field(array('size' => $attrib['attachmentfieldsize']))) .
html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize)))) .
(get_boolean($attrib['buttons']) ? html::div('buttons',
$button->show(rcube_label('close'), array('class' => 'button', 'onclick' => "$('#$attrib[id]').hide()")) . ' ' .
$button->show(rcube_label('upload'), array('class' => 'button mainaction', 'onclick' => JS_OBJECT_NAME . ".command('send-attachment', this.form)"))
) : '')
)
);
$OUTPUT->add_gui_object('uploadform', $attrib['id'].'Frm');
return $out;
}
function rcmail_compose_attachment_field($attrib)
{
$attrib['type'] = 'file';
$attrib['name'] = '_attachments[]';
$attrib['multiple'] = 'multiple';
$field = new html_inputfield($attrib);
return $field->show();
}
function rcmail_priority_selector($attrib)
{
global $MESSAGE;
list($form_start, $form_end) = get_form_tags($attrib);
unset($attrib['form']);
$attrib['name'] = '_priority';
$selector = new html_select($attrib);
$selector->add(array(rcube_label('lowest'),
rcube_label('low'),
rcube_label('normal'),
rcube_label('high'),
rcube_label('highest')),
array(5, 4, 0, 2, 1));
if (isset($_POST['_priority']))
$sel = $_POST['_priority'];
else if (intval($MESSAGE->headers->priority) != 3)
$sel = intval($MESSAGE->headers->priority);
else
$sel = 0;
$out = $form_start ? "$form_start\n" : '';
$out .= $selector->show($sel);
$out .= $form_end ? "\n$form_end" : '';
return $out;
}
function rcmail_receipt_checkbox($attrib)
{
global $RCMAIL, $MESSAGE, $compose_mode;
list($form_start, $form_end) = get_form_tags($attrib);
unset($attrib['form']);
if (!isset($attrib['id']))
$attrib['id'] = 'receipt';
$attrib['name'] = '_receipt';
$attrib['value'] = '1';
$checkbox = new html_checkbox($attrib);
if ($MESSAGE && in_array($compose_mode, array(RCUBE_COMPOSE_DRAFT, RCUBE_COMPOSE_EDIT)))
$mdn_default = (bool) $MESSAGE->headers->mdn_to;
else
$mdn_default = $RCMAIL->config->get('mdn_default');
$out = $form_start ? "$form_start\n" : '';
$out .= $checkbox->show($mdn_default);
$out .= $form_end ? "\n$form_end" : '';
return $out;
}
function rcmail_dsn_checkbox($attrib)
{
global $RCMAIL;
list($form_start, $form_end) = get_form_tags($attrib);
unset($attrib['form']);
if (!isset($attrib['id']))
$attrib['id'] = 'dsn';
$attrib['name'] = '_dsn';
$attrib['value'] = '1';
$checkbox = new html_checkbox($attrib);
$out = $form_start ? "$form_start\n" : '';
$out .= $checkbox->show($RCMAIL->config->get('dsn_default'));
$out .= $form_end ? "\n$form_end" : '';
return $out;
}
function rcmail_editor_selector($attrib)
{
// determine whether HTML or plain text should be checked
$useHtml = rcmail_compose_editor_mode();
if (empty($attrib['editorid']))
$attrib['editorid'] = 'rcmComposeBody';
if (empty($attrib['name']))
$attrib['name'] = 'editorSelect';
$attrib['onchange'] = "return rcmail_toggle_editor(this, '".$attrib['editorid']."', '_is_html')";
$select = new html_select($attrib);
$select->add(Q(rcube_label('htmltoggle')), 'html');
$select->add(Q(rcube_label('plaintoggle')), 'plain');
return $select->show($useHtml ? 'html' : 'plain');
foreach ($choices as $value => $text) {
$attrib['id'] = '_' . $value;
$attrib['value'] = $value;
$selector .= $radio->show($chosenvalue, $attrib) . html::label($attrib['id'], Q(rcube_label($text)));
}
return $selector;
}
function rcmail_store_target_selection($attrib)
{
global $COMPOSE;
$attrib['name'] = '_store_target';
$select = rcmail_mailbox_select(array_merge($attrib, array(
'noselection' => '- '.rcube_label('dontsave').' -',
'folder_filter' => 'mail',
'folder_rights' => 'w',
)));
return $select->show($COMPOSE['param']['sent_mbox'], $attrib);
}
function rcmail_check_sent_folder($folder, $create=false)
{
global $RCMAIL;
if ($RCMAIL->imap->mailbox_exists($folder, true)) {
return true;
}
// folder may exist but isn't subscribed (#1485241)
if ($create) {
if (!$RCMAIL->imap->mailbox_exists($folder))
return $RCMAIL->imap->create_mailbox($folder, true);
else
return $RCMAIL->imap->subscribe($folder);
}
return false;
}
function get_form_tags($attrib)
{
global $RCMAIL, $MESSAGE_FORM, $COMPOSE;
$form_start = '';
if (!$MESSAGE_FORM)
{
$hiddenfields = new html_hiddenfield(array('name' => '_task', 'value' => $RCMAIL->task));
$hiddenfields->add(array('name' => '_action', 'value' => 'send'));
$hiddenfields->add(array('name' => '_id', 'value' => $COMPOSE['id']));
$form_start = empty($attrib['form']) ? $RCMAIL->output->form_tag(array('name' => "form", 'method' => "post")) : '';
$form_start .= $hiddenfields->show();
}
$form_end = ($MESSAGE_FORM && !strlen($attrib['form'])) ? '</form>' : '';
$form_name = !empty($attrib['form']) ? $attrib['form'] : 'form';
if (!$MESSAGE_FORM)
$RCMAIL->output->add_gui_object('messageform', $form_name);
$MESSAGE_FORM = $form_name;
return array($form_start, $form_end);
}
// register UI objects
$OUTPUT->add_handlers(array(
'composeheaders' => 'rcmail_compose_headers',
'composesubject' => 'rcmail_compose_subject',
'composebody' => 'rcmail_compose_body',
'composeattachmentlist' => 'rcmail_compose_attachment_list',
'composeattachmentform' => 'rcmail_compose_attachment_form',
'composeattachment' => 'rcmail_compose_attachment_field',
'priorityselector' => 'rcmail_priority_selector',
'editorselector' => 'rcmail_editor_selector',
'receiptcheckbox' => 'rcmail_receipt_checkbox',
'dsncheckbox' => 'rcmail_dsn_checkbox',
'storetarget' => 'rcmail_store_target_selection',
));
$OUTPUT->send('compose');
diff --git a/program/steps/mail/func.inc b/program/steps/mail/func.inc
index f791f7033..e17b19615 100644
--- a/program/steps/mail/func.inc
+++ b/program/steps/mail/func.inc
@@ -1,1640 +1,1639 @@
<?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');
+ $cont = Q(rcmail_address_string($header->$col, 3, false, null, $header->charset), 'show');
else if ($col=='subject') {
- $cont = trim($RCMAIL->imap->decode_header($header->$col));
+ $cont = trim(rcube_mime::decode_header($header->$col, $header->charset));
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">&nbsp;</span>';
break;
case 'attachment':
case 'priority':
case 'status':
$col_name = '<span class="' . $col .'">&nbsp;</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);
else if ($col_name[0] != '<')
$col_name = '<span class="' . $col .'">' . $col_name . '</span>';
$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'.' &nbsp; '.'\\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, $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|javascript:|import[^a]/i', $stripped)) {
if (!$washtml->get_config('allow_remote') && stripos($stripped, 'url('))
$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');
$exclude_headers = $attrib['exclude'] ? explode(',', $attrib['exclude']) : array();
$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 (in_array($hkey, $exclude_headers))
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']);
+ $header_value = rcmail_address_string($value, null, true, $attrib['addicon'], $headers['charset']);
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']);
+ $header_value = rcmail_address_string($value, null, true, $attrib['addicon'], $headers['charset']);
else
continue;
}
else if ($hkey == 'mail-followup-to') {
- $header_value = rcmail_address_string($value, null, true, $attrib['addicon']);
+ $header_value = rcmail_address_string($value, null, true, $attrib['addicon'], $headers['charset']);
}
else if (in_array($hkey, array('from', 'to', 'cc', 'bcc')))
- $header_value = rcmail_address_string($value, null, true, $attrib['addicon']);
+ $header_value = rcmail_address_string($value, null, true, $attrib['addicon'], $headers['charset']);
else if ($hkey == 'subject' && empty($value))
$header_value = rcube_label('nosubject');
else
- $header_value = trim($RCMAIL->imap->decode_header($value));
+ $header_value = trim(rcube_mime::decode_header($value, $headers['charset']));
$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, 'exclude' => $exclude_headers));
// single header value is requested
if (!empty($attrib['valueof']))
return Q($plugin['output'][$attrib['valueof']]['value'], ($hkey == 'subject' ? 'strict' : 'show'));
// 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, $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) {
// skip inline images
if ($attach_prop->content_id && $attach_prop->disposition == 'inline') {
continue;
}
// 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]);
$body = $replacer->replace($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, $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, $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-->',
'&lt;?',
'?&gt;',
'<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)
if ($attrib['href'])
$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['name']) {
$attrib['href'] = './#NOP';
$attrib['onclick'] = 'return false';
}
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)
+function rcmail_address_string($input, $max=null, $linked=false, $addicon=null, $default_charset=null)
{
global $RCMAIL, $PRINT_MODE, $CONFIG;
- $a_parts = $RCMAIL->imap->decode_address_list($input);
+ $a_parts = rcube_mime::decode_address_list($input, null, true, $default_charset);
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 &lt;%s&gt;', 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'),
'class' => 'rcmaddcontact',
),
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('&lt;%s&gt;', Q($mailto));
}
if ($c>$j)
$out .= ','.($max ? '&nbsp;' : ' ');
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($attrib)
{
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('header', Q($part->filename));
$table->add('download-link', html::a(array('href' => './?'.str_replace('_frame=', '_download=', $_SERVER['QUERY_STRING'])), Q(rcube_label('download'))));
}
if (!empty($part->size)) {
$table->add('title', Q(rcube_label('filesize')));
$table->add('header', 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));
+ $recipient = array_shift(rcube_mime::decode_address_list(
+ $message->headers->mdn_to, 1, true, $message->headers->charset));
$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',
));
diff --git a/program/steps/mail/sendmail.inc b/program/steps/mail/sendmail.inc
index 9d4d42562..953c7521a 100644
--- a/program/steps/mail/sendmail.inc
+++ b/program/steps/mail/sendmail.inc
@@ -1,761 +1,761 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/sendmail.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2011, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Compose a new mail message with all headers and attachments |
| and send it using the PEAR::Net_SMTP class or with PHP mail() |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
// remove all scripts and act as called in frame
$OUTPUT->reset();
$OUTPUT->framed = TRUE;
$savedraft = !empty($_POST['_draft']) ? true : false;
$COMPOSE_ID = get_input_value('_id', RCUBE_INPUT_GPC);
$COMPOSE =& $_SESSION['compose_data_'.$COMPOSE_ID];
/****** checks ********/
if (!isset($COMPOSE['id'])) {
raise_error(array('code' => 500, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Invalid compose ID"), true, false);
$OUTPUT->show_message('internalerror', 'error');
$OUTPUT->send('iframe');
}
if (!$savedraft) {
if (empty($_POST['_to']) && empty($_POST['_cc']) && empty($_POST['_bcc'])
&& empty($_POST['_subject']) && $_POST['_message']) {
$OUTPUT->show_message('sendingfailed', 'error');
$OUTPUT->send('iframe');
}
if(!empty($CONFIG['sendmail_delay'])) {
$wait_sec = time() - intval($CONFIG['sendmail_delay']) - intval($CONFIG['last_message_time']);
if($wait_sec < 0) {
$OUTPUT->show_message('senttooquickly', 'error', array('sec' => $wait_sec * -1));
$OUTPUT->send('iframe');
}
}
}
/****** message sending functions ********/
// encrypt parts of the header
function rcmail_encrypt_header($what)
{
global $CONFIG, $RCMAIL;
if (!$CONFIG['http_received_header_encrypt']) {
return $what;
}
return $RCMAIL->encrypt($what);
}
// get identity record
function rcmail_get_identity($id)
{
global $RCMAIL, $OUTPUT;
if ($sql_arr = $RCMAIL->user->get_identity($id)) {
$out = $sql_arr;
$out['mailto'] = $sql_arr['email'];
$out['string'] = format_email_recipient($sql_arr['email'],
rcube_charset_convert($sql_arr['name'], RCMAIL_CHARSET, $OUTPUT->get_charset()));
return $out;
}
return FALSE;
}
/**
* go from this:
* <img src="http[s]://.../tiny_mce/plugins/emotions/images/smiley-cool.gif" border="0" alt="Cool" title="Cool" />
*
* to this:
*
* <img src="/path/on/server/.../tiny_mce/plugins/emotions/images/smiley-cool.gif" border="0" alt="Cool" title="Cool" />
* ...
*/
function rcmail_fix_emoticon_paths(&$mime_message)
{
global $CONFIG;
$body = $mime_message->getHTMLBody();
// remove any null-byte characters before parsing
$body = preg_replace('/\x00/', '', $body);
$searchstr = 'program/js/tiny_mce/plugins/emotions/img/';
$offset = 0;
// keep track of added images, so they're only added once
$included_images = array();
if (preg_match_all('# src=[\'"]([^\'"]+)#', $body, $matches, PREG_OFFSET_CAPTURE)) {
foreach ($matches[1] as $m) {
// find emoticon image tags
if (preg_match('#'.$searchstr.'(.*)$#', $m[0], $imatches)) {
$image_name = $imatches[1];
// sanitize image name so resulting attachment doesn't leave images dir
$image_name = preg_replace('/[^a-zA-Z0-9_\.\-]/i', '', $image_name);
$img_file = INSTALL_PATH . '/' . $searchstr . $image_name;
if (! in_array($image_name, $included_images)) {
// add the image to the MIME message
if (! $mime_message->addHTMLImage($img_file, 'image/gif', '', true, $image_name))
$OUTPUT->show_message("emoticonerror", 'error');
array_push($included_images, $image_name);
}
$body = substr_replace($body, $img_file, $m[1] + $offset, strlen($m[0]));
$offset += strlen($img_file) - strlen($m[0]);
}
}
}
$mime_message->setHTMLBody($body);
return $body;
}
/**
* Parse and cleanup email address input (and count addresses)
*
* @param string Address input
* @param boolean Do count recipients (saved in global $RECIPIENT_COUNT)
* @param boolean Validate addresses (errors saved in global $EMAIL_FORMAT_ERROR)
* @return string Canonical recipients string separated by comma
*/
function rcmail_email_input_format($mailto, $count=false, $check=true)
{
global $RCMAIL, $EMAIL_FORMAT_ERROR, $RECIPIENT_COUNT;
// simplified email regexp, supporting quoted local part
$email_regexp = '(\S+|("[^"]+"))@\S+';
$delim = trim($RCMAIL->config->get('recipients_separator', ','));
$regexp = array("/[,;$delim]\s*[\r\n]+/", '/[\r\n]+/', "/[,;$delim]\s*\$/m", '/;/', '/(\S{1})(<'.$email_regexp.'>)/U');
$replace = array($delim.' ', ', ', '', $delim, '\\1 \\2');
// replace new lines and strip ending ', ', make address input more valid
$mailto = trim(preg_replace($regexp, $replace, $mailto));
$result = array();
$items = rcube_explode_quoted_string($delim, $mailto);
foreach($items as $item) {
$item = trim($item);
// address in brackets without name (do nothing)
if (preg_match('/^<'.$email_regexp.'>$/', $item)) {
$item = rcube_idn_to_ascii(trim($item, '<>'));
$result[] = '<' . $item . '>';
// address without brackets and without name (add brackets)
} else if (preg_match('/^'.$email_regexp.'$/', $item)) {
$item = rcube_idn_to_ascii($item);
$result[] = '<' . $item . '>';
// address with name (handle name)
} else if (preg_match('/<*'.$email_regexp.'>*$/', $item, $matches)) {
$address = $matches[0];
$name = trim(str_replace($address, '', $item), '" ');
$address = rcube_idn_to_ascii(trim($address, '<>'));
$result[] = format_email_recipient($address, $name);
$item = $address;
} else if (trim($item)) {
continue;
}
// check address format
$item = trim($item, '<>');
if ($item && $check && !check_email($item)) {
$EMAIL_FORMAT_ERROR = $item;
return;
}
}
if ($count) {
$RECIPIENT_COUNT += count($result);
}
return implode(', ', $result);
}
/****** compose message ********/
if (strlen($_POST['_draft_saveid']) > 3)
$olddraftmessageid = get_input_value('_draft_saveid', RCUBE_INPUT_POST);
$message_id = rcmail_gen_message_id();
// set default charset
$input_charset = $OUTPUT->get_charset();
$message_charset = isset($_POST['_charset']) ? $_POST['_charset'] : $input_charset;
$EMAIL_FORMAT_ERROR = NULL;
$RECIPIENT_COUNT = 0;
$mailto = rcmail_email_input_format(get_input_value('_to', RCUBE_INPUT_POST, TRUE, $message_charset), true);
$mailcc = rcmail_email_input_format(get_input_value('_cc', RCUBE_INPUT_POST, TRUE, $message_charset), true);
$mailbcc = rcmail_email_input_format(get_input_value('_bcc', RCUBE_INPUT_POST, TRUE, $message_charset), true);
if ($EMAIL_FORMAT_ERROR) {
$OUTPUT->show_message('emailformaterror', 'error', array('email' => $EMAIL_FORMAT_ERROR));
$OUTPUT->send('iframe');
}
if (empty($mailto) && !empty($mailcc)) {
$mailto = $mailcc;
$mailcc = null;
}
else if (empty($mailto))
$mailto = 'undisclosed-recipients:;';
// Get sender name and address...
$from = get_input_value('_from', RCUBE_INPUT_POST, true, $message_charset);
// ... from identity...
if (is_numeric($from)) {
if (is_array($identity_arr = rcmail_get_identity($from))) {
if ($identity_arr['mailto'])
$from = $identity_arr['mailto'];
if ($identity_arr['string'])
$from_string = $identity_arr['string'];
}
else {
$from = null;
}
}
// ... if there is no identity record, this might be a custom from
else if ($from_string = rcmail_email_input_format($from)) {
if (preg_match('/(\S+@\S+)/', $from_string, $m))
$from = trim($m[1], '<>');
else
$from = null;
}
if (!$from_string && $from)
$from_string = $from;
// compose headers array
$headers = array();
// if configured, the Received headers goes to top, for good measure
if ($CONFIG['http_received_header'])
{
$nldlm = "\r\n\t";
// FROM/VIA
$http_header = 'from ';
if (isset($_SERVER['HTTP_X_FORWARDED_FOR'])) {
$host = $_SERVER['HTTP_X_FORWARDED_FOR'];
$hostname = gethostbyaddr($host);
if ($CONFIG['http_received_header_encrypt']) {
$http_header .= rcmail_encrypt_header($hostname);
if ($host != $hostname)
$http_header .= ' ('. rcmail_encrypt_header($host) . ')';
} else {
$http_header .= (($host != $hostname) ? $hostname : '[' . $host . ']');
if ($host != $hostname)
$http_header .= ' (['. $host .'])';
}
$http_header .= $nldlm . ' via ';
}
$host = $_SERVER['REMOTE_ADDR'];
$hostname = gethostbyaddr($host);
if ($CONFIG['http_received_header_encrypt']) {
$http_header .= rcmail_encrypt_header($hostname);
if ($host != $hostname)
$http_header .= ' ('. rcmail_encrypt_header($host) . ')';
} else {
$http_header .= (($host != $hostname) ? $hostname : '[' . $host . ']');
if ($host != $hostname)
$http_header .= ' (['. $host .'])';
}
// BY
$http_header .= $nldlm . 'by ' . $_SERVER['HTTP_HOST'];
// WITH
$http_header .= $nldlm . 'with HTTP (' . $_SERVER['SERVER_PROTOCOL'] .
' '.$_SERVER['REQUEST_METHOD'] . '); ' . date('r');
$http_header = wordwrap($http_header, 69, $nldlm);
$headers['Received'] = $http_header;
}
$headers['Date'] = rcmail_user_date();
$headers['From'] = rcube_charset_convert($from_string, RCMAIL_CHARSET, $message_charset);
$headers['To'] = $mailto;
// additional recipients
if (!empty($mailcc)) {
$headers['Cc'] = $mailcc;
}
if (!empty($mailbcc)) {
$headers['Bcc'] = $mailbcc;
}
if (!empty($identity_arr['bcc'])) {
$headers['Bcc'] = ($headers['Bcc'] ? $headers['Bcc'].', ' : '') . $identity_arr['bcc'];
$RECIPIENT_COUNT ++;
}
if (($max_recipients = (int) $RCMAIL->config->get('max_recipients')) > 0) {
if ($RECIPIENT_COUNT > $max_recipients) {
$OUTPUT->show_message('toomanyrecipients', 'error', array('max' => $max_recipients));
$OUTPUT->send('iframe');
}
}
// add subject
$headers['Subject'] = trim(get_input_value('_subject', RCUBE_INPUT_POST, TRUE, $message_charset));
if (!empty($identity_arr['organization'])) {
$headers['Organization'] = $identity_arr['organization'];
}
if (!empty($_POST['_replyto'])) {
$headers['Reply-To'] = rcmail_email_input_format(get_input_value('_replyto', RCUBE_INPUT_POST, TRUE, $message_charset));
}
else if (!empty($identity_arr['reply-to'])) {
$headers['Reply-To'] = rcmail_email_input_format($identity_arr['reply-to'], false, true);
}
if (!empty($headers['Reply-To'])) {
$headers['Mail-Reply-To'] = $headers['Reply-To'];
}
if (!empty($_POST['_followupto'])) {
$headers['Mail-Followup-To'] = rcmail_email_input_format(get_input_value('_followupto', RCUBE_INPUT_POST, TRUE, $message_charset));
}
if (!empty($COMPOSE['reply_msgid'])) {
$headers['In-Reply-To'] = $COMPOSE['reply_msgid'];
}
// remember reply/forward UIDs in special headers
if (!empty($COMPOSE['reply_uid']) && $savedraft) {
$headers['X-Draft-Info'] = array('type' => 'reply', 'uid' => $COMPOSE['reply_uid']);
}
else if (!empty($COMPOSE['forward_uid']) && $savedraft) {
$headers['X-Draft-Info'] = array('type' => 'forward', 'uid' => $COMPOSE['forward_uid']);
}
if (!empty($COMPOSE['references'])) {
$headers['References'] = $COMPOSE['references'];
}
if (!empty($_POST['_priority'])) {
$priority = intval($_POST['_priority']);
$a_priorities = array(1=>'highest', 2=>'high', 4=>'low', 5=>'lowest');
if ($str_priority = $a_priorities[$priority]) {
$headers['X-Priority'] = sprintf("%d (%s)", $priority, ucfirst($str_priority));
}
}
if (!empty($_POST['_receipt'])) {
$headers['Return-Receipt-To'] = $from_string;
$headers['Disposition-Notification-To'] = $from_string;
}
// additional headers
$headers['Message-ID'] = $message_id;
$headers['X-Sender'] = $from;
if (is_array($headers['X-Draft-Info'])) {
$headers['X-Draft-Info'] = rcmail_draftinfo_encode($headers['X-Draft-Info'] + array('folder' => $COMPOSE['mailbox']));
}
if (!empty($CONFIG['useragent'])) {
$headers['User-Agent'] = $CONFIG['useragent'];
}
// exec hook for header checking and manipulation
$data = $RCMAIL->plugins->exec_hook('message_outgoing_headers', array('headers' => $headers));
// sending aborted by plugin
if ($data['abort'] && !$savedraft) {
$OUTPUT->show_message($data['message'] ? $data['message'] : 'sendingfailed');
$OUTPUT->send('iframe');
}
else
$headers = $data['headers'];
$isHtml = (bool) get_input_value('_is_html', RCUBE_INPUT_POST);
// fetch message body
$message_body = get_input_value('_message', RCUBE_INPUT_POST, TRUE, $message_charset);
if ($isHtml) {
$font = rcube_fontdefs($RCMAIL->config->get('default_font', 'Verdana'));
$bstyle = $font && is_string($font) ? " style='font-family: $font'" : '';
// append doctype and html/body wrappers
$message_body = '<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN">' .
"\r\n<html><body$bstyle>\r\n" . $message_body;
}
if (!$savedraft) {
if ($isHtml) {
// remove signature's div ID
$message_body = preg_replace('/\s*id="_rc_sig"/', '', $message_body);
// add inline css for blockquotes
$bstyle = 'padding-left:5px; border-left:#1010ff 2px solid; margin-left:5px; width:100%';
$message_body = preg_replace('/<blockquote>/',
'<blockquote type="cite" style="'.$bstyle.'">', $message_body);
}
// Check spelling before send
if ($CONFIG['spellcheck_before_send'] && $CONFIG['enable_spellcheck']
&& empty($COMPOSE['spell_checked']) && !empty($message_body)
) {
$spellchecker = new rcube_spellchecker(get_input_value('_lang', RCUBE_INPUT_GPC));
$spell_result = $spellchecker->check($message_body, $isHtml);
$COMPOSE['spell_checked'] = true;
if (!$spell_result) {
$result = $isHtml ? $spellchecker->get_words() : $spellchecker->get_xml();
$OUTPUT->show_message('mispellingsfound', 'error');
$OUTPUT->command('spellcheck_resume', $isHtml, $result);
$OUTPUT->send('iframe');
}
}
// generic footer for all messages
if ($isHtml && !empty($CONFIG['generic_message_footer_html'])) {
$footer = file_get_contents(realpath($CONFIG['generic_message_footer_html']));
$footer = rcube_charset_convert($footer, RCMAIL_CHARSET, $message_charset);
}
else if (!empty($CONFIG['generic_message_footer'])) {
$footer = file_get_contents(realpath($CONFIG['generic_message_footer']));
$footer = rcube_charset_convert($footer, RCMAIL_CHARSET, $message_charset);
if ($isHtml)
$footer = '<pre>'.$footer.'</pre>';
}
if ($footer)
$message_body .= "\r\n" . $footer;
}
if ($isHtml) {
$message_body .= "\r\n</body></html>\r\n";
}
// set line length for body wrapping
$LINE_LENGTH = $RCMAIL->config->get('line_length', 72);
// Since we can handle big messages with disk usage, we need more time to work
@set_time_limit(0);
// create PEAR::Mail_mime instance
$MAIL_MIME = new Mail_mime("\r\n");
// Check if we have enough memory to handle the message in it
// It's faster than using files, so we'll do this if we only can
if (is_array($COMPOSE['attachments']) && $CONFIG['smtp_server']
&& ($mem_limit = parse_bytes(ini_get('memory_limit'))))
{
$memory = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
foreach ($COMPOSE['attachments'] as $id => $attachment)
$memory += $attachment['size'];
// Yeah, Net_SMTP needs up to 12x more memory, 1.33 is for base64
if ($memory * 1.33 * 12 > $mem_limit)
$MAIL_MIME->setParam('delay_file_io', true);
}
// For HTML-formatted messages, construct the MIME message with both
// the HTML part and the plain-text part
if ($isHtml) {
$plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
array('body' => $message_body, 'type' => 'html', 'message' => $MAIL_MIME));
$MAIL_MIME->setHTMLBody($plugin['body']);
// replace emoticons
$plugin['body'] = rcmail_replace_emoticons($plugin['body']);
// add a plain text version of the e-mail as an alternative part.
$h2t = new html2text($plugin['body'], false, true, 0);
$plainTextPart = rc_wordwrap($h2t->get_text(), $LINE_LENGTH, "\r\n");
$plainTextPart = wordwrap($plainTextPart, 998, "\r\n", true);
if (!$plainTextPart) {
// empty message body breaks attachment handling in drafts
$plainTextPart = "\r\n";
}
else {
// make sure all line endings are CRLF (#1486712)
$plainTextPart = preg_replace('/\r?\n/', "\r\n", $plainTextPart);
}
$plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
array('body' => $plainTextPart, 'type' => 'alternative', 'message' => $MAIL_MIME));
$MAIL_MIME->setTXTBody($plugin['body']);
// look for "emoticon" images from TinyMCE and change their src paths to
// be file paths on the server instead of URL paths.
$message_body = rcmail_fix_emoticon_paths($MAIL_MIME);
}
else {
$plugin = $RCMAIL->plugins->exec_hook('message_outgoing_body',
array('body' => $message_body, 'type' => 'plain', 'message' => $MAIL_MIME));
$message_body = $plugin['body'];
// compose format=flowed content if enabled
if ($flowed = $RCMAIL->config->get('send_format_flowed', true))
- $message_body = rcube_message::format_flowed($message_body, min($LINE_LENGTH+2, 79));
+ $message_body = rcube_mime::format_flowed($message_body, min($LINE_LENGTH+2, 79));
else
$message_body = rc_wordwrap($message_body, $LINE_LENGTH, "\r\n");
$message_body = wordwrap($message_body, 998, "\r\n", true);
if (!strlen($message_body)) {
// empty message body breaks attachment handling in drafts
$message_body = "\r\n";
}
$MAIL_MIME->setTXTBody($message_body, false, true);
}
// add stored attachments, if any
if (is_array($COMPOSE['attachments']))
{
foreach ($COMPOSE['attachments'] as $id => $attachment) {
// This hook retrieves the attachment contents from the file storage backend
$attachment = $RCMAIL->plugins->exec_hook('attachment_get', $attachment);
$dispurl = '/\ssrc\s*=\s*[\'"]*\S+display-attachment\S+file=rcmfile' . preg_quote($attachment['id']) . '[\s\'"]*/';
$message_body = $MAIL_MIME->getHTMLBody();
if ($isHtml && (preg_match($dispurl, $message_body) > 0)) {
$message_body = preg_replace($dispurl, ' src="'.$attachment['name'].'" ', $message_body);
$MAIL_MIME->setHTMLBody($message_body);
if ($attachment['data'])
$MAIL_MIME->addHTMLImage($attachment['data'], $attachment['mimetype'], $attachment['name'], false);
else
$MAIL_MIME->addHTMLImage($attachment['path'], $attachment['mimetype'], $attachment['name'], true);
}
else {
$ctype = str_replace('image/pjpeg', 'image/jpeg', $attachment['mimetype']); // #1484914
$file = $attachment['data'] ? $attachment['data'] : $attachment['path'];
// .eml attachments send inline
$MAIL_MIME->addAttachment($file,
$ctype,
$attachment['name'],
($attachment['data'] ? false : true),
($ctype == 'message/rfc822' ? '8bit' : 'base64'),
($ctype == 'message/rfc822' ? 'inline' : 'attachment'),
'', '', '',
$CONFIG['mime_param_folding'] ? 'quoted-printable' : NULL,
$CONFIG['mime_param_folding'] == 2 ? 'quoted-printable' : NULL,
'', RCMAIL_CHARSET
);
}
}
}
// choose transfer encoding for plain/text body
if (preg_match('/[^\x00-\x7F]/', $MAIL_MIME->getTXTBody()))
$transfer_encoding = $RCMAIL->config->get('force_7bit') ? 'quoted-printable' : '8bit';
else
$transfer_encoding = '7bit';
// encoding settings for mail composing
$MAIL_MIME->setParam('text_encoding', $transfer_encoding);
$MAIL_MIME->setParam('html_encoding', 'quoted-printable');
$MAIL_MIME->setParam('head_encoding', 'quoted-printable');
$MAIL_MIME->setParam('head_charset', $message_charset);
$MAIL_MIME->setParam('html_charset', $message_charset);
$MAIL_MIME->setParam('text_charset', $message_charset . ($flowed ? ";\r\n format=flowed" : ''));
// encoding subject header with mb_encode provides better results with asian characters
if (function_exists('mb_encode_mimeheader')) {
mb_internal_encoding($message_charset);
$headers['Subject'] = mb_encode_mimeheader($headers['Subject'],
$message_charset, 'Q', "\r\n", 8);
mb_internal_encoding(RCMAIL_CHARSET);
}
// pass headers to message object
$MAIL_MIME->headers($headers);
// Begin SMTP Delivery Block
if (!$savedraft)
{
// check 'From' address (identity may be incomplete)
if (empty($from)) {
$OUTPUT->show_message('nofromaddress', 'error');
$OUTPUT->send('iframe');
}
// Handle Delivery Status Notification request
if (!empty($_POST['_dsn'])) {
$smtp_opts['dsn'] = true;
}
$sent = rcmail_deliver_message($MAIL_MIME, $from, $mailto,
$smtp_error, $mailbody_file, $smtp_opts);
// return to compose page if sending failed
if (!$sent)
{
// remove temp file
if ($mailbody_file) {
unlink($mailbody_file);
}
if ($smtp_error)
$OUTPUT->show_message($smtp_error['label'], 'error', $smtp_error['vars']);
else
$OUTPUT->show_message('sendingfailed', 'error');
$OUTPUT->send('iframe');
}
// save message sent time
if (!empty($CONFIG['sendmail_delay']))
$RCMAIL->user->save_prefs(array('last_message_time' => time()));
// set replied/forwarded flag
if ($COMPOSE['reply_uid'])
$RCMAIL->imap->set_flag($COMPOSE['reply_uid'], 'ANSWERED', $COMPOSE['mailbox']);
else if ($COMPOSE['forward_uid'])
$RCMAIL->imap->set_flag($COMPOSE['forward_uid'], 'FORWARDED', $COMPOSE['mailbox']);
} // End of SMTP Delivery Block
// Determine which folder to save message
if ($savedraft)
$store_target = $CONFIG['drafts_mbox'];
else
$store_target = isset($_POST['_store_target']) ? get_input_value('_store_target', RCUBE_INPUT_POST) : $CONFIG['sent_mbox'];
if ($store_target) {
// check if folder is subscribed
if ($RCMAIL->imap->mailbox_exists($store_target, true))
$store_folder = true;
// folder may be existing but not subscribed (#1485241)
else if (!$RCMAIL->imap->mailbox_exists($store_target))
$store_folder = $RCMAIL->imap->create_mailbox($store_target, true);
else if ($RCMAIL->imap->subscribe($store_target))
$store_folder = true;
// append message to sent box
if ($store_folder) {
// message body in file
if ($mailbody_file || $MAIL_MIME->getParam('delay_file_io')) {
$headers = $MAIL_MIME->txtHeaders();
// file already created
if ($mailbody_file)
$msg = $mailbody_file;
else {
$temp_dir = $RCMAIL->config->get('temp_dir');
$mailbody_file = tempnam($temp_dir, 'rcmMsg');
if (!PEAR::isError($msg = $MAIL_MIME->saveMessageBody($mailbody_file)))
$msg = $mailbody_file;
}
}
else {
$msg = $MAIL_MIME->getMessage();
$headers = '';
}
if (PEAR::isError($msg))
raise_error(array('code' => 650, 'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not create message: ".$msg->getMessage()),
TRUE, FALSE);
else {
$saved = $RCMAIL->imap->save_message($store_target, $msg, $headers, $mailbody_file ? true : false);
}
if ($mailbody_file) {
unlink($mailbody_file);
$mailbody_file = null;
}
// raise error if saving failed
if (!$saved) {
raise_error(array('code' => 800, 'type' => 'imap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not save message in $store_target"), TRUE, FALSE);
if ($savedraft) {
$OUTPUT->show_message('errorsaving', 'error');
$OUTPUT->send('iframe');
}
}
}
if ($olddraftmessageid) {
// delete previous saved draft
// @TODO: use message UID (remember to check UIDVALIDITY) to skip this SEARCH
$delete_idx = $RCMAIL->imap->search_once($CONFIG['drafts_mbox'],
'HEADER Message-ID '.$olddraftmessageid);
if ($del_uid = $delete_idx->getElement('FIRST')) {
$deleted = $RCMAIL->imap->delete_message($del_uid, $CONFIG['drafts_mbox']);
// raise error if deletion of old draft failed
if (!$deleted)
raise_error(array('code' => 800, 'type' => 'imap',
'file' => __FILE__, 'line' => __LINE__,
'message' => "Could not delete message from ".$CONFIG['drafts_mbox']), TRUE, FALSE);
}
}
}
// remove temp file
else if ($mailbody_file) {
unlink($mailbody_file);
}
if ($savedraft) {
$msgid = strtr($message_id, array('>' => '', '<' => ''));
// remember new draft-uid ($saved could be an UID or TRUE here)
if (is_bool($saved)) {
$draft_idx = $RCMAIL->imap->search_once($CONFIG['drafts_mbox'], 'HEADER Message-ID '.$msgid);
$saved = $draft_idx->getElement('FIRST');
}
$COMPOSE['param']['draft_uid'] = $saved;
// display success
$OUTPUT->show_message('messagesaved', 'confirmation');
// update "_draft_saveid" and the "cmp_hash" to prevent "Unsaved changes" warning
$OUTPUT->command('set_draft_id', $msgid);
$OUTPUT->command('compose_field_hash', true);
// start the auto-save timer again
$OUTPUT->command('auto_save_start');
$OUTPUT->send('iframe');
}
else {
rcmail_compose_cleanup($COMPOSE_ID);
if ($store_folder && !$saved)
$OUTPUT->command('sent_successfully', 'error', rcube_label('errorsavingsent'));
else
$OUTPUT->command('sent_successfully', 'confirmation', rcube_label('messagesent'));
$OUTPUT->send('iframe');
}
diff --git a/program/steps/mail/viewsource.inc b/program/steps/mail/viewsource.inc
index d951fbead..e2b4e1b61 100644
--- a/program/steps/mail/viewsource.inc
+++ b/program/steps/mail/viewsource.inc
@@ -1,59 +1,60 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/viewsource.inc |
| |
| This file is part of the Roundcube Webmail client |
| Copyright (C) 2005-2009, The Roundcube Dev Team |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Display a mail message similar as a usual mail application does |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
ob_end_clean();
// similar code as in program/steps/mail/get.inc
if ($uid = get_input_value('_uid', RCUBE_INPUT_GET))
{
$headers = $RCMAIL->imap->get_headers($uid);
$charset = $headers->charset ? $headers->charset : $CONFIG['default_charset'];
header("Content-Type: text/plain; charset={$charset}");
if (!empty($_GET['_save'])) {
- $filename = ($headers->subject ? $RCMAIL->imap->decode_header($headers->subject) : 'roundcube') . '.eml';
+ $subject = rcube_mime::decode_header($headers->subject, $headers->charset);
+ $filename = ($subject ? $subject : $RCMAIL->config->get('product_name', 'email')) . '.eml';
$browser = $RCMAIL->output->browser;
if ($browser->ie && $browser->ver < 7)
$filename = rawurlencode(abbreviate_string($filename, 55));
else if ($browser->ie)
$filename = rawurlencode($filename);
else
$filename = addcslashes($filename, '"');
header("Content-Length: {$headers->size}");
header("Content-Disposition: attachment; filename=\"$filename\"");
}
$RCMAIL->imap->print_raw_body($uid);
}
else
{
raise_error(array(
'code' => 500,
'type' => 'php',
'file' => __FILE__, 'line' => __LINE__,
'message' => 'Message UID '.$uid.' not found'),
true, true);
}
exit;
diff --git a/tests/maildecode.php b/tests/maildecode.php
index 664161cce..4ac499360 100644
--- a/tests/maildecode.php
+++ b/tests/maildecode.php
@@ -1,133 +1,130 @@
<?php
/**
* Test class to test messages decoding functions
*
* @package Tests
*/
class rcube_test_maildecode extends UnitTestCase
{
private $app;
function __construct()
{
$this->UnitTestCase('Mail headers decoding tests');
-
- $this->app = rcmail::get_instance();
- $this->app->imap_init(false);
}
/**
* Test decoding of single e-mail address strings
- * Uses rcube_imap::decode_address_list()
+ * Uses rcube_mime::decode_address_list()
*/
function test_decode_single_address()
{
$headers = array(
0 => 'test@domain.tld',
1 => '<test@domain.tld>',
2 => 'Test <test@domain.tld>',
3 => 'Test Test <test@domain.tld>',
4 => 'Test Test<test@domain.tld>',
5 => '"Test Test" <test@domain.tld>',
6 => '"Test Test"<test@domain.tld>',
7 => '"Test \\" Test" <test@domain.tld>',
8 => '"Test<Test" <test@domain.tld>',
9 => '=?ISO-8859-1?B?VGVzdAo=?= <test@domain.tld>',
10 => '=?ISO-8859-1?B?VGVzdAo=?=<test@domain.tld>', // #1487068
// comments in address (#1487673)
11 => 'Test (comment) <test@domain.tld>',
12 => '"Test" (comment) <test@domain.tld>',
13 => '"Test (comment)" (comment) <test@domain.tld>',
14 => '(comment) <test@domain.tld>',
15 => 'Test <test@(comment)domain.tld>',
16 => 'Test Test ((comment)) <test@domain.tld>',
17 => 'test@domain.tld (comment)',
18 => '"Test,Test" <test@domain.tld>',
// 1487939
19 => 'Test <"test test"@domain.tld>',
20 => '<"test test"@domain.tld>',
21 => '"test test"@domain.tld',
);
$results = array(
0 => array(1, '', 'test@domain.tld'),
1 => array(1, '', 'test@domain.tld'),
2 => array(1, 'Test', 'test@domain.tld'),
3 => array(1, 'Test Test', 'test@domain.tld'),
4 => array(1, 'Test Test', 'test@domain.tld'),
5 => array(1, 'Test Test', 'test@domain.tld'),
6 => array(1, 'Test Test', 'test@domain.tld'),
7 => array(1, 'Test " Test', 'test@domain.tld'),
8 => array(1, 'Test<Test', 'test@domain.tld'),
9 => array(1, 'Test', 'test@domain.tld'),
10 => array(1, 'Test', 'test@domain.tld'),
11 => array(1, 'Test', 'test@domain.tld'),
12 => array(1, 'Test', 'test@domain.tld'),
13 => array(1, 'Test (comment)', 'test@domain.tld'),
14 => array(1, '', 'test@domain.tld'),
15 => array(1, 'Test', 'test@domain.tld'),
16 => array(1, 'Test Test', 'test@domain.tld'),
17 => array(1, '', 'test@domain.tld'),
18 => array(1, 'Test,Test', 'test@domain.tld'),
19 => array(1, 'Test', '"test test"@domain.tld'),
20 => array(1, '', '"test test"@domain.tld'),
21 => array(1, '', '"test test"@domain.tld'),
);
foreach ($headers as $idx => $header) {
- $res = $this->app->imap->decode_address_list($header);
+ $res = rcube_mime::decode_address_list($header);
$this->assertEqual($results[$idx][0], count($res), "Rows number in result for header: " . $header);
$this->assertEqual($results[$idx][1], $res[1]['name'], "Name part decoding for header: " . $header);
$this->assertEqual($results[$idx][2], $res[1]['mailto'], "Email part decoding for header: " . $header);
}
}
/**
* Test decoding of header values
- * Uses rcube_imap::decode_mime_string()
+ * Uses rcube_mime::decode_mime_string()
*/
function test_header_decode_qp()
{
$test = array(
// #1488232: invalid character "?"
'quoted-printable (1)' => array(
'in' => '=?utf-8?Q?Certifica=C3=A7=C3=A3??=',
'out' => 'Certifica=C3=A7=C3=A3?',
),
'quoted-printable (2)' => array(
'in' => '=?utf-8?Q?Certifica=?= =?utf-8?Q?C3=A7=C3=A3?=',
'out' => 'Certifica=C3=A7=C3=A3',
),
'quoted-printable (3)' => array(
'in' => '=?utf-8?Q??= =?utf-8?Q??=',
'out' => '',
),
'quoted-printable (4)' => array(
'in' => '=?utf-8?Q??= a =?utf-8?Q??=',
'out' => ' a ',
),
'quoted-printable (5)' => array(
'in' => '=?utf-8?Q?a?= =?utf-8?Q?b?=',
'out' => 'ab',
),
'quoted-printable (6)' => array(
'in' => '=?utf-8?Q? ?= =?utf-8?Q?a?=',
'out' => ' a',
),
'quoted-printable (7)' => array(
'in' => '=?utf-8?Q?___?= =?utf-8?Q?a?=',
'out' => ' a',
),
);
foreach ($test as $idx => $item) {
- $res = $this->app->imap->decode_mime_string($item['in'], 'UTF-8');
+ $res = rcube_mime::decode_mime_string($item['in'], 'UTF-8');
$res = quoted_printable_encode($res);
$this->assertEqual($item['out'], $res, "Header decoding for: " . $idx);
}
}
}

File Metadata

Mime Type
text/x-diff
Expires
Tue, Aug 18, 4:07 AM (1 d, 12 h)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
1259428
Default Alt Text
(328 KB)

Event Timeline