Page MenuHomePhorge

No OneTemporary

diff --git a/index.php b/index.php
index adab4118c..a080ef585 100644
--- a/index.php
+++ b/index.php
@@ -1,245 +1,243 @@
<?php
/*
+-------------------------------------------------------------------------+
| RoundCube Webmail IMAP Client |
| Version 0.2-20080829 |
| |
| Copyright (C) 2005-2008, RoundCube Dev. - Switzerland |
| |
| This program is free software; you can redistribute it and/or modify |
| it under the terms of the GNU General Public License version 2 |
| as published by the Free Software Foundation. |
| |
| This program is distributed in the hope that it will be useful, |
| but WITHOUT ANY WARRANTY; without even the implied warranty of |
| MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| GNU General Public License for more details. |
| |
| You should have received a copy of the GNU General Public License along |
| with this program; if not, write to the Free Software Foundation, Inc., |
| 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. |
| |
+-------------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-------------------------------------------------------------------------+
$Id$
*/
// include environment
require_once 'program/include/iniset.php';
// init application and start session with requested task
$RCMAIL = rcmail::get_instance();
// init output class
$OUTPUT = !empty($_REQUEST['_remote']) ? $RCMAIL->init_json() : $RCMAIL->load_gui(!empty($_REQUEST['_framed']));
// set output buffering
if ($RCMAIL->action != 'get' && $RCMAIL->action != 'viewsource') {
// use gzip compression if supported
if (function_exists('ob_gzhandler')
&& !ini_get('zlib.output_compression')
&& ini_get('output_handler') != 'ob_gzhandler') {
ob_start('ob_gzhandler');
}
else {
ob_start();
}
}
-
// check if config files had errors
if ($err_str = $RCMAIL->config->get_error()) {
raise_error(array(
'code' => 601,
'type' => 'php',
'message' => $err_str), false, true);
}
// check DB connections and exit on failure
if ($err_str = $DB->is_error()) {
raise_error(array(
'code' => 603,
'type' => 'db',
'message' => $err_str), FALSE, TRUE);
}
-
// error steps
if ($RCMAIL->action=='error' && !empty($_GET['_code'])) {
raise_error(array('code' => hexdec($_GET['_code'])), FALSE, TRUE);
}
// try to log in
if ($RCMAIL->action=='login' && $RCMAIL->task=='mail') {
$host = $RCMAIL->autoselect_host();
// check if client supports cookies
if (empty($_COOKIE)) {
$OUTPUT->show_message("cookiesdisabled", 'warning');
}
else if ($_SESSION['temp'] && !empty($_POST['_user']) && isset($_POST['_pass']) &&
$RCMAIL->login(trim(get_input_value('_user', RCUBE_INPUT_POST), ' '),
get_input_value('_pass', RCUBE_INPUT_POST, true, 'ISO-8859-1'), $host)) {
// create new session ID
unset($_SESSION['temp']);
rcube_sess_regenerate_id();
// send auth cookie if necessary
$RCMAIL->authenticate_session();
// log successful login
if ($RCMAIL->config->get('log_logins')) {
write_log('userlogins', sprintf('Successful login for %s (id %d) from %s',
$RCMAIL->user->get_username(),
$RCMAIL->user->ID,
$_SERVER['REMOTE_ADDR']));
}
// send redirect
$OUTPUT->redirect();
}
else {
$OUTPUT->show_message($IMAP->error_code == -1 ? 'imaperror' : 'loginfailed', 'warning');
$RCMAIL->kill_session();
}
}
// end session
else if (($RCMAIL->task=='logout' || $RCMAIL->action=='logout') && isset($_SESSION['user_id'])) {
$OUTPUT->show_message('loggedout');
$RCMAIL->logout_actions();
$RCMAIL->kill_session();
}
// check session and auth cookie
else if ($RCMAIL->action != 'login' && $_SESSION['user_id'] && $RCMAIL->action != 'send') {
if (!$RCMAIL->authenticate_session()) {
$OUTPUT->show_message('sessionerror', 'error');
$RCMAIL->kill_session();
}
}
// log in to imap server
if (!empty($RCMAIL->user->ID) && $RCMAIL->task == 'mail') {
if (!$RCMAIL->imap_connect()) {
$RCMAIL->kill_session();
}
}
// check client X-header to verify request origin
if ($OUTPUT->ajax_call) {
if (!$RCMAIL->config->get('devel_mode') && !rc_request_header('X-RoundCube-Referer')) {
header('HTTP/1.1 404 Not Found');
die("Invalid Request");
}
}
// not logged in -> show login page
if (empty($RCMAIL->user->ID)) {
if ($OUTPUT->ajax_call)
$OUTPUT->redirect(array(), 2000);
// check if installer is still active
if ($RCMAIL->config->get('enable_installer') && is_readable('./installer/index.php')) {
$OUTPUT->add_footer(html::div(array('style' => "background:#ef9398; border:2px solid #dc5757; padding:0.5em; margin:2em auto; width:50em"),
html::tag('h2', array('style' => "margin-top:0.2em"), "Installer script is still accessible") .
html::p(null, "The install script of your RoundCube installation is still stored in its default location!") .
html::p(null, "Please <b>remove</b> the whole <tt>installer</tt> folder from the RoundCube directory because .
these files may expose sensitive configuration data like server passwords and encryption keys
to the public. Make sure you cannot access the <a href=\"./installer/\">installer script</a> from your browser.")
)
);
}
$OUTPUT->set_env('task', 'login');
$OUTPUT->send('login');
}
// handle keep-alive signal
if ($RCMAIL->action == 'keep-alive') {
$OUTPUT->reset();
$OUTPUT->send();
}
// save preference value
else if ($RCMAIL->action == 'save-pref') {
$RCMAIL->user->save_prefs(array(get_input_value('_name', RCUBE_INPUT_POST) => get_input_value('_value', RCUBE_INPUT_POST)));
$OUTPUT->reset();
$OUTPUT->send();
}
// map task/action to a certain include file
$action_map = array(
'mail' => 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' => 'compose.inc',
'display-attachment' => 'compose.inc',
),
'addressbook' => array(
'add' => 'edit.inc',
),
'settings' => array(
'folders' => 'manage_folders.inc',
'create-folder' => 'manage_folders.inc',
'rename-folder' => 'manage_folders.inc',
'delete-folder' => 'manage_folders.inc',
'subscribe' => 'manage_folders.inc',
'unsubscribe' => 'manage_folders.inc',
'add-identity' => 'edit_identity.inc',
)
);
// include task specific functions
include_once 'program/steps/'.$RCMAIL->task.'/func.inc';
// allow 5 "redirects" to another action
$redirects = 0; $incstep = null;
while ($redirects < 5) {
$stepfile = !empty($action_map[$RCMAIL->task][$RCMAIL->action]) ?
$action_map[$RCMAIL->task][$RCMAIL->action] : strtr($RCMAIL->action, '-', '_') . '.inc';
// try to include the step file
if (is_file(($incfile = 'program/steps/'.$RCMAIL->task.'/'.$stepfile))) {
include($incfile);
$redirects++;
}
else {
break;
}
}
// make sure the message count is refreshed (for default view)
if ($RCMAIL->task == 'mail') {
$IMAP->messagecount($_SESSION['mbox'], 'ALL', true);
}
// parse main template (default)
$OUTPUT->send($RCMAIL->task);
// if we arrive here, something went wrong
raise_error(array(
'code' => 404,
'type' => 'php',
'line' => __LINE__,
'file' => __FILE__,
'message' => "Invalid request"), true, true);
?>
diff --git a/program/include/main.inc b/program/include/main.inc
index 75ad2b30b..8eb2c81f5 100644
--- a/program/include/main.inc
+++ b/program/include/main.inc
@@ -1,1167 +1,1170 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/main.inc |
| |
| This file is part of the RoundCube Webmail client |
| Copyright (C) 2005-2008, RoundCube Dev, - Switzerland |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Provide basic functions for the webmail package |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
/**
* RoundCube Webmail common functions
*
* @package Core
* @author Thomas Bruederli <roundcube@gmail.com>
*/
require_once('lib/utf7.inc');
require_once('include/rcube_shared.inc');
// fallback if not PHP modules are available
@include_once('lib/des.inc');
@include_once('lib/utf8.class.php');
// define constannts for input reading
define('RCUBE_INPUT_GET', 0x0101);
define('RCUBE_INPUT_POST', 0x0102);
define('RCUBE_INPUT_GPC', 0x0103);
/**
* Return correct name for a specific database table
*
* @param string Table name
* @return string Translated table name
*/
function get_table_name($table)
{
global $CONFIG;
// return table name if configured
$config_key = 'db_table_'.$table;
if (strlen($CONFIG[$config_key]))
return $CONFIG[$config_key];
return $table;
}
/**
* Return correct name for a specific database sequence
* (used for Postgres only)
*
* @param string Secuence name
* @return string Translated sequence name
*/
function get_sequence_name($sequence)
{
// return table name if configured
$config_key = 'db_sequence_'.$sequence;
$opt = rcmail::get_instance()->config->get($config_key);
if (!empty($opt))
return $opt;
return $sequence;
}
/**
* Get localized text in the desired language
* It's a global wrapper for rcmail::gettext()
*
* @param mixed Named parameters array or label name
* @return string Localized text
* @see rcmail::gettext()
*/
function rcube_label($p)
{
return rcmail::get_instance()->gettext($p);
}
/**
* Overwrite action variable
*
* @param string New action value
*/
function rcmail_overwrite_action($action)
{
$app = rcmail::get_instance();
$app->action = $action;
$app->output->set_env('action', $action);
}
/**
* Compose an URL for a specific action
*
* @param string Request action
* @param array More URL parameters
* @param string Request task (omit if the same)
* @return The application URL
*/
function rcmail_url($action, $p=array(), $task=null)
{
$app = rcmail::get_instance();
return $app->url((array)$p + array('_action' => $action, 'task' => $task));
}
/**
* Garbage collector function for temp files.
* Remove temp files older than two days
*/
function rcmail_temp_gc()
{
$tmp = unslashify($CONFIG['temp_dir']);
$expire = mktime() - 172800; // expire in 48 hours
if ($dir = opendir($tmp))
{
while (($fname = readdir($dir)) !== false)
{
if ($fname{0} == '.')
continue;
if (filemtime($tmp.'/'.$fname) < $expire)
@unlink($tmp.'/'.$fname);
}
closedir($dir);
}
}
/**
* Garbage collector for cache entries.
* Remove all expired message cache records
*/
function rcmail_message_cache_gc()
{
global $DB, $CONFIG;
// no cache lifetime configured
if (empty($CONFIG['message_cache_lifetime']))
return;
// get target timestamp
$ts = get_offset_time($CONFIG['message_cache_lifetime'], -1);
$DB->query("DELETE FROM ".get_table_name('messages')."
WHERE created < ".$DB->fromunixtime($ts));
}
/**
* Convert a string from one charset to another.
* Uses mbstring and iconv functions if possible
*
* @param string Input string
* @param string Suspected charset of the input string
* @param string Target charset to convert to; defaults to RCMAIL_CHARSET
* @return Converted string
*/
function rcube_charset_convert($str, $from, $to=NULL)
{
static $mbstring_loaded = null, $convert_warning = false;
$from = strtoupper($from);
$to = $to==NULL ? strtoupper(RCMAIL_CHARSET) : strtoupper($to);
$error = false; $conv = null;
if ($from==$to || $str=='' || empty($from))
return $str;
$aliases = array(
- 'US-ASCII' => 'ISO-8859-1',
- 'UNKNOWN-8BIT' => 'ISO-8859-15',
- 'X-UNKNOWN' => 'ISO-8859-15',
- 'X-USER-DEFINED' => 'ISO-8859-15',
- 'ISO-8859-8-I' => 'ISO-8859-8',
- 'KS_C_5601-1987' => 'EUC-KR',
+ 'US-ASCII' => 'ISO-8859-1',
+ 'ANSI_X3.110-1983' => 'ISO-8859-1',
+ 'ANSI_X3.4-1968' => 'ISO-8859-1',
+ 'UNKNOWN-8BIT' => 'ISO-8859-15',
+ 'X-UNKNOWN' => 'ISO-8859-15',
+ 'X-USER-DEFINED' => 'ISO-8859-15',
+ 'ISO-8859-8-I' => 'ISO-8859-8',
+ 'KS_C_5601-1987' => 'EUC-KR',
);
// convert charset using iconv module
if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7')
{
$aliases['GB2312'] = 'GB18030';
$_iconv = iconv(($aliases[$from] ? $aliases[$from] : $from), ($aliases[$to] ? $aliases[$to] : $to) . "//IGNORE", $str);
if ($_iconv !== false)
{
return $_iconv;
}
}
if (is_null($mbstring_loaded))
$mbstring_loaded = extension_loaded('mbstring');
// convert charset using mbstring module
if ($mbstring_loaded)
{
$aliases['UTF-7'] = 'UTF7-IMAP';
$aliases['WINDOWS-1257'] = 'ISO-8859-13';
// return if convert succeeded
if (($out = mb_convert_encoding($str, ($aliases[$to] ? $aliases[$to] : $to), ($aliases[$from] ? $aliases[$from] : $from))) != '')
return $out;
}
if (class_exists('utf8'))
$conv = new utf8();
// convert string to UTF-8
if ($from == 'UTF-7')
$str = utf7_to_utf8($str);
else if (($from == 'ISO-8859-1') && function_exists('utf8_encode'))
$str = utf8_encode($str);
else if ($from != 'UTF-8' && $conv)
{
$conv->loadCharset($from);
$str = $conv->strToUtf8($str);
}
else if ($from != 'UTF-8')
$error = true;
// encode string for output
if ($to == 'UTF-7')
return utf8_to_utf7($str);
else if ($to == 'ISO-8859-1' && function_exists('utf8_decode'))
return utf8_decode($str);
else if ($to != 'UTF-8' && $conv)
{
$conv->loadCharset($to);
return $conv->utf8ToStr($str);
}
else if ($to != 'UTF-8')
$error = true;
// report error
if ($error && !$convert_warning)
{
raise_error(array(
'code' => 500,
'type' => 'php',
'file' => __FILE__,
'message' => "Could not convert string charset. Make sure iconv is installed or lib/utf8.class is available"
), true, false);
$convert_warning = true;
}
// return UTF-8 string
return $str;
}
/**
* Replacing specials characters to a specific encoding type
*
* @param string Input string
* @param string Encoding type: text|html|xml|js|url
* @param string Replace mode for tags: show|replace|remove
* @param boolean Convert newlines
* @return The quoted string
*/
function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
{
global $OUTPUT;
static $html_encode_arr = false;
static $js_rep_table = false;
static $xml_rep_table = false;
$charset = $OUTPUT->get_charset();
$is_iso_8859_1 = false;
if ($charset == 'ISO-8859-1') {
$is_iso_8859_1 = true;
}
if (!$enctype)
$enctype = $OUTPUT->type;
// encode for plaintext
if ($enctype=='text')
return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
// encode for HTML output
if ($enctype=='html')
{
if (!$html_encode_arr)
{
$html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
unset($html_encode_arr['?']);
}
$ltpos = strpos($str, '<');
$encode_arr = $html_encode_arr;
// don't replace quotes and html tags
if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
{
unset($encode_arr['"']);
unset($encode_arr['<']);
unset($encode_arr['>']);
unset($encode_arr['&']);
}
else if ($mode=='remove')
$str = strip_tags($str);
// avoid douple quotation of &
$out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', strtr($str, $encode_arr));
return $newlines ? nl2br($out) : $out;
}
if ($enctype=='url')
return rawurlencode($str);
// if the replace tables for XML and JS are not yet defined
if ($js_rep_table===false)
{
$js_rep_table = $xml_rep_table = array();
$xml_rep_table['&'] = '&amp;';
for ($c=160; $c<256; $c++) // can be increased to support more charsets
{
$xml_rep_table[Chr($c)] = "&#$c;";
if ($is_iso_8859_1)
$js_rep_table[Chr($c)] = sprintf("\\u%04x", $c);
}
$xml_rep_table['"'] = '&quot;';
}
// encode for XML
if ($enctype=='xml')
return strtr($str, $xml_rep_table);
// encode for javascript use
if ($enctype=='js')
{
if ($charset!='UTF-8')
$str = rcube_charset_convert($str, RCMAIL_CHARSET,$charset);
return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), addslashes(strtr($str, $js_rep_table)));
}
// no encoding given -> return original string
return $str;
}
/**
* Quote a given string.
* Shortcut function for rep_specialchars_output
*
* @return string HTML-quoted string
* @see rep_specialchars_output()
*/
function Q($str, $mode='strict', $newlines=TRUE)
{
return rep_specialchars_output($str, 'html', $mode, $newlines);
}
/**
* Quote a given string for javascript output.
* Shortcut function for rep_specialchars_output
*
* @return string JS-quoted string
* @see rep_specialchars_output()
*/
function JQ($str)
{
return rep_specialchars_output($str, 'js');
}
/**
* Read input value and convert it for internal use
* Performs stripslashes() and charset conversion if necessary
*
* @param string Field name to read
* @param int Source to get value from (GPC)
* @param boolean Allow HTML tags in field value
* @param string Charset to convert into
* @return string Field value or NULL if not available
*/
function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
{
global $OUTPUT;
$value = NULL;
if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
$value = $_GET[$fname];
else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
$value = $_POST[$fname];
else if ($source==RCUBE_INPUT_GPC)
{
if (isset($_POST[$fname]))
$value = $_POST[$fname];
else if (isset($_GET[$fname]))
$value = $_GET[$fname];
else if (isset($_COOKIE[$fname]))
$value = $_COOKIE[$fname];
}
// strip slashes if magic_quotes enabled
if ((bool)get_magic_quotes_gpc())
$value = stripslashes($value);
// remove HTML tags if not allowed
if (!$allow_html)
$value = strip_tags($value);
// convert to internal charset
if (is_object($OUTPUT))
return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
else
return $value;
}
/**
* Remove all non-ascii and non-word chars
* except . and -
*/
function asciiwords($str, $css_id = false)
{
$allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
return preg_replace("/[^$allowed]/i", '', $str);
}
/**
* Remove single and double quotes from given string
*
* @param string Input value
* @return string Dequoted string
*/
function strip_quotes($str)
{
return preg_replace('/[\'"]/', '', $str);
}
/**
* Remove new lines characters from given string
*
* @param string Input value
* @return string Stripped string
*/
function strip_newlines($str)
{
return preg_replace('/[\r\n]/', '', $str);
}
/**
* Create a HTML table based on the given data
*
* @param array Named table attributes
* @param mixed Table row data. Either a two-dimensional array or a valid SQL result set
* @param array List of cols to show
* @param string Name of the identifier col
* @return string HTML table code
*/
function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
{
global $RCMAIL;
$table = new html_table(/*array('cols' => count($a_show_cols))*/);
// add table header
foreach ($a_show_cols as $col)
$table->add_header($col, Q(rcube_label($col)));
$c = 0;
if (!is_array($table_data))
{
$db = $RCMAIL->get_dbh();
while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
{
$zebra_class = $c % 2 ? 'even' : 'odd';
$table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => "contact $zebra_class"));
// format each col
foreach ($a_show_cols as $col)
$table->add($col, Q($sql_arr[$col]));
$c++;
}
}
else
{
foreach ($table_data as $row_data)
{
$zebra_class = $c % 2 ? 'even' : 'odd';
$table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => "contact $zebra_class"));
// format each col
foreach ($a_show_cols as $col)
$table->add($col, Q($row_data[$col]));
$c++;
}
}
return $table->show($attrib);
}
/**
* Create an edit field for inclusion on a form
*
* @param string col field name
* @param string value field value
* @param array attrib HTML element attributes for field
* @param string type HTML element type (default 'text')
* @return string HTML field definition
*/
function rcmail_get_edit_field($col, $value, $attrib, $type='text')
{
$fname = '_'.$col;
$attrib['name'] = $fname;
if ($type=='checkbox')
{
$attrib['value'] = '1';
$input = new html_checkbox($attrib);
}
else if ($type=='textarea')
{
$attrib['cols'] = $attrib['size'];
$input = new html_textarea($attrib);
}
else
$input = new html_inputfield($attrib);
// use value from post
if (!empty($_POST[$fname]))
$value = get_input_value($fname, RCUBE_INPUT_POST,
$type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
$out = $input->show($value);
return $out;
}
/**
* Replace all css definitions with #container [def]
* and remove css-inlined scripting
*
* @param string CSS source code
* @param string Container ID to use as prefix
* @return string Modified CSS source
*/
function rcmail_mod_css_styles($source, $container_id, $base_url = '')
{
$a_css_values = array();
$last_pos = 0;
// ignore the whole block if evil styles are detected
$stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entitiy_decode($source));
if (preg_match('/expression|behavior|url\(|import/', $stripped))
return '';
// cut out all contents between { and }
while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
{
$key = sizeof($a_css_values);
$a_css_values[$key] = substr($source, $pos+1, $pos2-($pos+1));
$source = substr($source, 0, $pos+1) . "<<str_replacement[$key]>>" . substr($source, $pos2, strlen($source)-$pos2);
$last_pos = $pos+2;
}
// remove html comments and add #container to each tag selector.
// also replace body definition because we also stripped off the <body> tag
$styles = preg_replace(
array(
'/(^\s*<!--)|(-->\s*$)/',
'/(^\s*|,\s*|\}\s*)([a-z0-9\._#][a-z0-9\.\-_]*)/im',
'/@import\s+(url\()?[\'"]?([^\)\'"]+)[\'"]?(\))?/ime',
'/<<str_replacement\[([0-9]+)\]>>/e',
"/$container_id\s+body/i"
),
array(
'',
"\\1#$container_id \\2",
"sprintf(\"@import url('./bin/modcss.php?u=%s&c=%s')\", urlencode(make_absolute_url('\\2','$base_url')), urlencode($container_id))",
"\$a_css_values[\\1]",
"$container_id div.rcmBody"
),
$source);
return $styles;
}
/**
* Decode escaped entities used by known XSS exploits.
* See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
*
* @param string CSS content to decode
* @return string Decoded string
*/
function rcmail_xss_entitiy_decode($content)
{
$out = html_entity_decode(html_entity_decode($content));
$out = preg_replace('/\\\([0-9a-f]{4})/ie', "chr(hexdec('\\1'))", $out);
$out = preg_replace('#/\*.*\*/#Um', '', $out);
return $out;
}
/**
* Compose a valid attribute string for HTML tags
*
* @param array Named tag attributes
* @param array List of allowed attributes
* @return string HTML formatted attribute string
*/
function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
{
// allow the following attributes to be added to the <iframe> tag
$attrib_str = '';
foreach ($allowed_attribs as $a)
if (isset($attrib[$a]))
$attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
return $attrib_str;
}
/**
* Convert a HTML attribute string attributes to an associative array (name => value)
*
* @param string Input string
* @return array Key-value pairs of parsed attributes
*/
function parse_attrib_string($str)
{
$attrib = array();
preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]+)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
// convert attributes to an associative array (name => value)
if ($regs)
foreach ($regs as $attr)
{
$attrib[strtolower($attr[1])] = $attr[3] . $attr[4];
}
return $attrib;
}
/**
* Convert the given date to a human readable form
* This uses the date formatting properties from config
*
* @param mixed Date representation (string or timestamp)
* @param string Date format to use
* @return string Formatted date string
*/
function format_date($date, $format=NULL)
{
global $CONFIG;
$ts = NULL;
if (is_numeric($date))
$ts = $date;
else if (!empty($date))
{
- while (($ts = @strtotime($date))===false)
+ // if date parsing fails, we have a date in non-rfc format.
+ // remove token from the end and try again
+ while ((($ts = @strtotime($date))===false) || ($ts < 0))
{
- // if we have a date in non-rfc format
- // remove token from the end and try again
$d = explode(' ', $date);
array_pop($d);
if (!$d) break;
$date = implode(' ', $d);
}
}
if (empty($ts))
return '';
// get user's timezone
if ($CONFIG['timezone'] === 'auto')
$tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
else {
$tz = $CONFIG['timezone'];
if ($CONFIG['dst_active'])
$tz++;
}
// convert time to user's timezone
$timestamp = $ts - date('Z', $ts) + ($tz * 3600);
// get current timestamp in user's timezone
$now = time(); // local time
$now -= (int)date('Z'); // make GMT time
$now += ($tz * 3600); // user's time
$now_date = getdate($now);
$today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
$week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
// define date format depending on current time
if ($CONFIG['prettydate'] && !$format && $timestamp > $today_limit && $timestamp < $now)
return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
else if ($CONFIG['prettydate'] && !$format && $timestamp > $week_limit && $timestamp < $now)
$format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
else if (!$format)
$format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
// parse format string manually in order to provide localized weekday and month names
// an alternative would be to convert the date() format string to fit with strftime()
$out = '';
for($i=0; $i<strlen($format); $i++)
{
if ($format{$i}=='\\') // skip escape chars
continue;
// write char "as-is"
if ($format{$i}==' ' || $format{$i-1}=='\\')
$out .= $format{$i};
// weekday (short)
else if ($format{$i}=='D')
$out .= rcube_label(strtolower(date('D', $timestamp)));
// weekday long
else if ($format{$i}=='l')
$out .= rcube_label(strtolower(date('l', $timestamp)));
// month name (short)
else if ($format{$i}=='M')
$out .= rcube_label(strtolower(date('M', $timestamp)));
// month name (long)
else if ($format{$i}=='F')
$out .= rcube_label('long'.strtolower(date('M', $timestamp)));
else if ($format{$i}=='x')
$out .= strftime('%x %X', $timestamp);
else
$out .= date($format{$i}, $timestamp);
}
return $out;
}
/**
* Compose a valid representaion of name and e-mail address
*
* @param string E-mail address
* @param string Person name
* @return string Formatted string
*/
function format_email_recipient($email, $name='')
{
if ($name && $name != $email)
{
// Special chars as defined by RFC 822 need to in quoted string (or escaped).
return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, $email);
}
else
return $email;
}
/****** debugging functions ********/
/**
* Print or write debug messages
*
* @param mixed Debug message or data
*/
function console()
{
$msg = array();
foreach (func_get_args() as $arg)
$msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
if (!($GLOBALS['CONFIG']['debug_level'] & 4))
write_log('console', join(";\n", $msg));
else if ($GLOBALS['OUTPUT']->ajax_call)
print "/*\n " . join(";\n", $msg) . " \n*/\n";
else
{
print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
print join(";<br/>\n", $msg);
print "</pre></div>\n";
}
}
/**
* Append a line to a logfile in the logs directory.
* Date will be added automatically to the line.
*
* @param $name name of log file
* @param line Line to append
*/
function write_log($name, $line)
{
global $CONFIG;
if (!is_string($line))
$line = var_export($line, true);
$log_entry = sprintf("[%s]: %s\n",
date("d-M-Y H:i:s O", mktime()),
$line);
-
+
if ($CONFIG['log_driver'] == 'syslog') {
if ($name == 'errors')
$prio = LOG_ERR;
else
$prio = LOG_INFO;
syslog($prio, $log_entry);
} else {
// log_driver == 'file' is assumed here
if (empty($CONFIG['log_dir']))
$CONFIG['log_dir'] = INSTALL_PATH.'logs';
-
+
// try to open specific log file for writing
if ($fp = @fopen($CONFIG['log_dir'].'/'.$name, 'a')) {
fwrite($fp, $log_entry);
+ fflush($fp);
fclose($fp);
}
}
}
/**
* @access private
*/
function rcube_timer()
{
list($usec, $sec) = explode(" ", microtime());
return ((float)$usec + (float)$sec);
}
/**
* @access private
*/
function rcube_print_time($timer, $label='Timer')
{
static $print_count = 0;
$print_count++;
$now = rcube_timer();
$diff = $now-$timer;
if (empty($label))
$label = 'Timer '.$print_count;
console(sprintf("%s: %0.4f sec", $label, $diff));
}
/**
* Return the mailboxlist in HTML
*
* @param array Named parameters
* @return string HTML code for the gui object
*/
function rcmail_mailbox_list($attrib)
{
global $RCMAIL;
static $a_mailboxes;
$attrib += array('maxlength' => 100, 'relanames' => false);
// add some labels to client
$RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
$type = $attrib['type'] ? $attrib['type'] : 'ul';
unset($attrib['type']);
if ($type=='ul' && !$attrib['id'])
$attrib['id'] = 'rcmboxlist';
// get mailbox list
$mbox_name = $RCMAIL->imap->get_mailbox_name();
// build the folders tree
if (empty($a_mailboxes)) {
// get mailbox list
$a_folders = $RCMAIL->imap->list_mailboxes();
$delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
$a_mailboxes = array();
foreach ($a_folders as $folder)
rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
}
if ($type=='select') {
$select = new html_select($attrib);
// add no-selection option
if ($attrib['noselection'])
$select->add(rcube_label($attrib['noselection']), '0');
rcmail_render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
$out = $select->show();
}
else {
$js_mailboxlist = array();
$out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($a_mailboxes, $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
$RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
$RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
$RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
}
return $out;
}
/**
* Return the mailboxlist as html_select object
*
* @param array Named parameters
* @return object html_select HTML drop-down object
*/
function rcmail_mailbox_select($p = array())
{
global $RCMAIL;
$p += array('maxlength' => 100, 'relanames' => false);
$a_mailboxes = array();
foreach ($RCMAIL->imap->list_mailboxes() as $folder)
rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
$select = new html_select($p);
if ($p['noselection'])
$select->add($p['noselection'], '');
rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
return $select;
}
/**
* Create a hierarchical array of the mailbox list
* @access private
*/
function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
{
$pos = strpos($folder, $delm);
if ($pos !== false) {
$subFolders = substr($folder, $pos+1);
$currentFolder = substr($folder, 0, $pos);
$virtual = !isset($arrFolders[$currentFolder]);
}
else {
$subFolders = false;
$currentFolder = $folder;
$virtual = false;
}
$path .= $currentFolder;
if (!isset($arrFolders[$currentFolder])) {
$arrFolders[$currentFolder] = array(
'id' => $path,
'name' => rcube_charset_convert($currentFolder, 'UTF-7'),
'virtual' => $virtual,
'folders' => array());
}
else
$arrFolders[$currentFolder]['virtual'] = $virtual;
if (!empty($subFolders))
rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
}
/**
* Return html for a structured list &lt;ul&gt; for the mailbox tree
* @access private
*/
function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
{
global $RCMAIL, $CONFIG;
$maxlength = intval($attrib['maxlength']);
$realnames = (bool)$attrib['realnames'];
$msgcounts = $RCMAIL->imap->get_cache('messagecount');
$idx = 0;
$out = '';
foreach ($arrFolders as $key => $folder) {
$zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
$title = null;
if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
$foldername = rcube_label($folder_class);
}
else {
$foldername = $folder['name'];
// shorten the folder name to a given length
if ($maxlength && $maxlength > 1) {
$fname = abbreviate_string($foldername, $maxlength);
if ($fname != $foldername)
$title = $foldername;
$foldername = $fname;
}
}
// make folder name safe for ids and class names
$folder_id = asciiwords($folder['id'], true);
$classes = array('mailbox');
// set special class for Sent, Drafts, Trash and Junk
if ($folder['id']==$CONFIG['sent_mbox'])
$classes[] = 'sent';
else if ($folder['id']==$CONFIG['drafts_mbox'])
$classes[] = 'drafts';
else if ($folder['id']==$CONFIG['trash_mbox'])
$classes[] = 'trash';
else if ($folder['id']==$CONFIG['junk_mbox'])
$classes[] = 'junk';
else if ($folder['id']=='INBOX')
$classes[] = 'inbox';
else
$classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
$classes[] = $zebra_class;
if ($folder['id'] == $mbox_name)
$classes[] = 'selected';
$collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
$unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
if ($folder['virtual'])
$classes[] = 'virtual';
else if ($unread)
$classes[] = 'unread';
$js_name = JQ($folder['id']);
$html_name = Q($foldername . ($unread ? " ($unread)" : ''));
$link_attrib = $folder['virtual'] ? array() : array(
'href' => rcmail_url('', array('_mbox' => $folder['id'])),
'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
'title' => $title,
);
$out .= html::tag('li', array(
'id' => "rcmli".$folder_id,
'class' => join(' ', $classes),
'noclose' => true),
html::a($link_attrib, $html_name) .
(!empty($folder['folders']) ? html::div(array(
'class' => ($collapsed ? 'collapsed' : 'expanded'),
'style' => "position:absolute",
'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
), '&nbsp;') : ''));
$jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
if (!empty($folder['folders'])) {
$out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
}
$out .= "</li>\n";
$idx++;
}
return $out;
}
/**
* Return html for a flat list <select> for the mailbox tree
* @access private
*/
function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
{
$idx = 0;
$out = '';
foreach ($arrFolders as $key=>$folder)
{
if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
$foldername = rcube_label($folder_class);
else
{
$foldername = $folder['name'];
// shorten the folder name to a given length
if ($maxlength && $maxlength>1)
$foldername = abbreviate_string($foldername, $maxlength);
}
$select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
if (!empty($folder['folders']))
$out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
$idx++;
}
return $out;
}
/**
* Return internal name for the given folder if it matches the configured special folders
* @access private
*/
function rcmail_folder_classname($folder_id)
{
global $CONFIG;
$cname = null;
$folder_lc = strtolower($folder_id);
// for these mailboxes we have localized labels and css classes
foreach (array('inbox', 'sent', 'drafts', 'trash', 'junk') as $smbx)
{
if ($folder_lc == $smbx || $folder_id == $CONFIG[$smbx.'_mbox'])
$cname = $smbx;
}
return $cname;
}
/**
* Try to localize the given IMAP folder name.
* UTF-7 decode it in case no localized text was found
*
* @param string Folder name
* @return string Localized folder name in UTF-8 encoding
*/
function rcmail_localize_foldername($name)
{
if ($folder_class = rcmail_folder_classname($name))
return rcube_label($folder_class);
else
return rcube_charset_convert($name, 'UTF-7');
}
?>
diff --git a/program/include/rcube_imap.php b/program/include/rcube_imap.php
index 4a71c974f..e18c14c2e 100644
--- a/program/include/rcube_imap.php
+++ b/program/include/rcube_imap.php
@@ -1,3037 +1,3039 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcube_imap.php |
| |
| This file is part of the RoundCube Webmail client |
| Copyright (C) 2005-2008, RoundCube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| IMAP wrapper that implements the Iloha IMAP Library (IIL) |
| See http://ilohamail.org/ for details |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
/*
* Obtain classes from the Iloha IMAP library
*/
require_once('lib/imap.inc');
require_once('lib/mime.inc');
/**
* Interface class for accessing an IMAP server
*
* This is a wrapper that implements the Iloha IMAP Library (IIL)
*
* @package Mail
* @author Thomas Bruederli <roundcube@gmail.com>
* @version 1.40
* @link http://ilohamail.org
*/
class rcube_imap
{
var $db;
var $conn;
var $root_ns = '';
var $root_dir = '';
var $mailbox = 'INBOX';
var $list_page = 1;
var $page_size = 10;
var $sort_field = 'date';
var $sort_order = 'DESC';
var $delimiter = NULL;
var $caching_enabled = FALSE;
var $default_charset = 'ISO-8859-1';
var $default_folders = array('INBOX');
var $default_folders_lc = array('inbox');
var $cache = array();
var $cache_keys = array();
var $cache_changes = array();
var $uid_id_map = array();
var $msg_headers = array();
var $skip_deleted = FALSE;
var $search_set = NULL;
var $search_subject = '';
var $search_string = '';
var $search_charset = '';
var $debug_level = 1;
var $error_code = 0;
/**
* Object constructor
*
* @param object DB Database connection
*/
function __construct($db_conn)
{
$this->db = $db_conn;
}
/**
* Connect to an IMAP server
*
* @param string Host to connect
* @param string Username for IMAP account
* @param string Password for IMAP account
* @param number Port to connect to
* @param string 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, $auth_type=null)
{
global $ICL_SSL, $ICL_PORT, $IMAP_USE_INTERNAL_DATE;
// check for Open-SSL support in PHP build
if ($use_ssl && in_array('openssl', get_loaded_extensions()))
$ICL_SSL = $use_ssl == 'imaps' ? 'ssl' : $use_ssl;
else if ($use_ssl)
{
raise_error(array('code' => 403, 'type' => 'imap', 'file' => __FILE__,
'message' => 'Open SSL not available;'), TRUE, FALSE);
$port = 143;
}
$ICL_PORT = $port;
$IMAP_USE_INTERNAL_DATE = false;
$this->conn = iil_Connect($host, $user, $pass, array('imap' => $auth_type ? $auth_type : 'check'));
$this->host = $host;
$this->user = $user;
$this->pass = $pass;
$this->port = $port;
$this->ssl = $use_ssl;
// print trace mesages
if ($this->conn && ($this->debug_level & 8))
console($this->conn->message);
// write error log
else if (!$this->conn && $GLOBALS['iil_error'])
{
$this->error_code = $GLOBALS['iil_errornum'];
raise_error(array('code' => 403,
'type' => 'imap',
'message' => $GLOBALS['iil_error']), TRUE, FALSE);
}
// get server properties
if ($this->conn)
{
if (!empty($this->conn->delimiter))
$this->delimiter = $this->conn->delimiter;
if (!empty($this->conn->rootdir))
{
$this->set_rootdir($this->conn->rootdir);
$this->root_ns = ereg_replace('[\.\/]$', '', $this->conn->rootdir);
}
}
return $this->conn ? TRUE : FALSE;
}
/**
* Close IMAP connection
* Usually done on script shutdown
*
* @access public
*/
function close()
{
if ($this->conn)
iil_Close($this->conn);
}
/**
* 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->close();
$this->connect($this->host, $this->user, $this->pass, $this->port, $this->ssl);
// issue SELECT command to restore connection status
if ($this->mailbox)
iil_C_Select($this->conn, $this->mailbox);
}
/**
* Set a root folder for the IMAP connection.
*
* Only folders within this root folder will be displayed
* and all folder paths will be translated using this folder name
*
* @param string Root folder
* @access public
*/
function set_rootdir($root)
{
if (ereg('[\.\/]$', $root)) //(substr($root, -1, 1)==='/')
$root = substr($root, 0, -1);
$this->root_dir = $root;
if (empty($this->delimiter))
$this->get_hierarchy_delimiter();
}
/**
* Set default message charset
*
* This will be used for message decoding if a charset specification is not available
*
* @param string 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 Indexed list of folder names
* @access public
*/
function set_default_mailboxes($arr)
{
if (is_array($arr))
{
$this->default_folders = $arr;
$this->default_folders_lc = array();
// add inbox if not included
if (!in_array_nocase('INBOX', $this->default_folders))
array_unshift($this->default_folders, 'INBOX');
// create a second list with lower cased names
foreach ($this->default_folders as $mbox)
$this->default_folders_lc[] = strtolower($mbox);
}
}
/**
* Set internal mailbox reference.
*
* All operations will be perfomed on this mailbox/folder
*
* @param string Mailbox/Folder name
* @access public
*/
function set_mailbox($new_mbox)
{
$mailbox = $this->_mod_mailbox($new_mbox);
if ($this->mailbox == $mailbox)
return;
$this->mailbox = $mailbox;
// clear messagecount cache for this mailbox
$this->_clear_messagecount($mailbox);
}
/**
* Set internal list page
*
* @param number Page number to list
* @access public
*/
function set_page($page)
{
$this->list_page = (int)$page;
}
/**
* Set internal page size
*
* @param number 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
*
* @param array List of IMAP fields to search in
* @param string Search string
* @param array List of message ids or NULL if empty
*/
function set_search_set($subject, $str=null, $msgs=null, $charset=null)
{
if (is_array($subject) && $str == null && $msgs == null)
list($subject, $str, $msgs, $charset) = $subject;
if ($msgs != null && !is_array($msgs))
$msgs = split(',', $msgs);
$this->search_subject = $subject;
$this->search_string = $str;
$this->search_set = (array)$msgs;
$this->search_charset = $charset;
}
/**
* Return the saved search set as hash array
* @return array Search set
*/
function get_search_set()
{
return array($this->search_subject, $this->search_string, $this->search_set, $this->search_charset);
}
/**
* Returns the currently used mailbox name
*
* @return string Name of the mailbox/folder
* @access public
*/
function get_mailbox_name()
{
return $this->conn ? $this->_mod_mailbox($this->mailbox, 'out') : '';
}
/**
* Returns the IMAP server's capability
*
* @param string Capability name
* @return mixed Capability value or TRUE if supported, FALSE if not
* @access public
*/
function get_capability($cap)
{
return iil_C_GetCapability($this->conn, strtoupper($cap));
}
/**
* Checks the PERMANENTFLAGS capability of the current mailbox
* and returns true if the given flag is supported by the IMAP server
*
* @param string Permanentflag name
* @return mixed True if this flag is supported
* @access public
*/
function check_permflag($flag)
{
$flagsmap = $GLOBALS['IMAP_FLAGS'];
return (($imap_flag = $flagsmap[strtoupper($flag)]) && in_array_nocase($imap_flag, $this->conn->permanentflags));
}
/**
* Returns the delimiter that is used by the IMAP server for folder separation
*
* @return string Delimiter string
* @access public
*/
function get_hierarchy_delimiter()
{
if ($this->conn && empty($this->delimiter))
$this->delimiter = iil_C_GetHierarchyDelimiter($this->conn);
if (empty($this->delimiter))
$this->delimiter = '/';
return $this->delimiter;
}
/**
* Public method for mailbox listing.
*
* Converts mailbox name with root dir first
*
* @param string Optional root folder
* @param string Optional filter for mailbox listing
* @return array List of mailboxes/folders
* @access public
*/
function list_mailboxes($root='', $filter='*')
{
$a_out = array();
$a_mboxes = $this->_list_mailboxes($root, $filter);
foreach ($a_mboxes as $mbox_row)
{
$name = $this->_mod_mailbox($mbox_row, 'out');
if (strlen($name))
$a_out[] = $name;
}
// INBOX should always be available
if (!in_array_nocase('INBOX', $a_out))
array_unshift($a_out, 'INBOX');
// sort mailboxes
$a_out = $this->_sort_mailbox_list($a_out);
return $a_out;
}
/**
* Private method for mailbox listing
*
* @return array List of mailboxes/folders
* @see rcube_imap::list_mailboxes()
* @access private
*/
function _list_mailboxes($root='', $filter='*')
{
$a_defaults = $a_out = array();
// get cached folder list
$a_mboxes = $this->get_cache('mailboxes');
if (is_array($a_mboxes))
return $a_mboxes;
// retrieve list of folders from IMAP server
$a_folders = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox($root), $filter);
if (!is_array($a_folders) || !sizeof($a_folders))
$a_folders = array();
// write mailboxlist to cache
$this->update_cache('mailboxes', $a_folders);
return $a_folders;
}
/**
* Get message count for a specific mailbox
*
* @param string Mailbox/folder name
* @param string Mode for count [ALL|UNSEEN|RECENT]
* @param boolean Force reading from server and update cache
* @return int Number of messages
* @access public
*/
function messagecount($mbox_name='', $mode='ALL', $force=FALSE)
{
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
return $this->_messagecount($mailbox, $mode, $force);
}
/**
* Private method for getting nr of messages
*
* @access private
* @see rcube_imap::messagecount()
*/
function _messagecount($mailbox='', $mode='ALL', $force=FALSE)
{
$a_mailbox_cache = FALSE;
$mode = strtoupper($mode);
if (empty($mailbox))
$mailbox = $this->mailbox;
// count search set
if ($this->search_string && $mailbox == $this->mailbox && $mode == 'ALL' && !$force)
return count((array)$this->search_set);
$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];
// RECENT count is fetched a bit different
if ($mode == 'RECENT')
$count = iil_C_CheckForRecent($this->conn, $mailbox);
// use SEARCH for message counting
else if ($this->skip_deleted)
{
$search_str = "ALL UNDELETED";
// get message count and store in cache
if ($mode == 'UNSEEN')
$search_str .= " UNSEEN";
// get message count using SEARCH
// not very performant but more precise (using UNDELETED)
$count = 0;
$index = $this->_search_index($mailbox, $search_str);
if (is_array($index))
{
$str = implode(",", $index);
if (!empty($str))
$count = count($index);
}
}
else
{
if ($mode == 'UNSEEN')
$count = iil_C_CountUnseen($this->conn, $mailbox);
else
$count = iil_C_CountMessages($this->conn, $mailbox);
}
if (!is_array($a_mailbox_cache[$mailbox]))
$a_mailbox_cache[$mailbox] = array();
$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/folder name
* @param int Current page to list
* @param string Header field to sort by
* @param string Sort order [ASC|DESC]
* @return array Indexed array with message header objects
* @access public
*/
function list_headers($mbox_name='', $page=NULL, $sort_field=NULL, $sort_order=NULL)
{
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
return $this->_list_headers($mailbox, $page, $sort_field, $sort_order);
}
/**
* Private method for listing message headers
*
* @access private
* @see rcube_imap::list_headers
*/
function _list_headers($mailbox='', $page=NULL, $sort_field=NULL, $sort_order=NULL, $recursive=FALSE)
{
if (!strlen($mailbox))
return array();
// use saved message set
if ($this->search_string && $mailbox == $this->mailbox)
return $this->_list_header_set($mailbox, $this->search_set, $page, $sort_field, $sort_order);
$this->_set_sort_order($sort_field, $sort_order);
$max = $this->_messagecount($mailbox);
$start_msg = ($this->list_page-1) * $this->page_size;
list($begin, $end) = $this->_get_message_range($max, $page);
// mailbox is empty
if ($begin >= $end)
return array();
$headers_sorted = FALSE;
$cache_key = $mailbox.'.msg';
$cache_status = $this->check_cache_status($mailbox, $cache_key);
// cache is OK, we can get all messages from local cache
if ($cache_status>0)
{
$a_msg_headers = $this->get_message_cache($cache_key, $start_msg, $start_msg+$this->page_size, $this->sort_field, $this->sort_order);
$headers_sorted = TRUE;
}
// cache is dirty, sync it
else if ($this->caching_enabled && $cache_status==-1 && !$recursive)
{
$this->sync_header_index($mailbox);
return $this->_list_headers($mailbox, $page, $this->sort_field, $this->sort_order, TRUE);
}
else
{
// retrieve headers from IMAP
if ($this->get_capability('sort') && ($msg_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, $this->skip_deleted ? 'UNDELETED' : '')))
{
$mymsgidx = array_slice ($msg_index, $begin, $end-$begin);
$msgs = join(",", $mymsgidx);
}
else
{
$msgs = sprintf("%d:%d", $begin+1, $end);
$msg_index = range($begin, $end);
}
// fetch reuested headers from server
$a_msg_headers = array();
$deleted_count = $this->_fetch_headers($mailbox, $msgs, $a_msg_headers, $cache_key);
if ($this->sort_order == 'DESC' && $headers_sorted) {
//since the sort order is not used in the iil_c_sort function we have to do it here
$a_msg_headers = array_reverse($a_msg_headers);
}
// delete cached messages with a higher index than $max+1
// Changed $max to $max+1 to fix this bug : #1484295
$this->clear_message_cache($cache_key, $max + 1);
// kick child process to sync cache
// ...
}
// return empty array if no messages found
if (!is_array($a_msg_headers) || empty($a_msg_headers)) {
return array();
}
// if not already sorted
if (!$headers_sorted)
{
// use this class for message sorting
$sorter = new rcube_header_sorter();
$sorter->set_sequence_numbers($msg_index);
$sorter->sort_headers($a_msg_headers);
if ($this->sort_order == 'DESC')
$a_msg_headers = array_reverse($a_msg_headers);
}
return array_values($a_msg_headers);
}
/**
* Public method for listing a specific set of headers
* convert mailbox name with root dir first
*
* @param string Mailbox/folder name
* @param array List of message ids to list
* @param int Current page to list
* @param string Header field to sort by
* @param string Sort order [ASC|DESC]
* @return array Indexed array with message header objects
* @access public
*/
function list_header_set($mbox_name='', $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
{
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
return $this->_list_header_set($mailbox, $msgs, $page, $sort_field, $sort_order);
}
/**
* Private method for listing a set of message headers
*
* @access private
* @see rcube_imap::list_header_set()
*/
function _list_header_set($mailbox, $msgs, $page=NULL, $sort_field=NULL, $sort_order=NULL)
{
if (!strlen($mailbox) || empty($msgs))
return array();
// also accept a comma-separated list of message ids
if (is_array ($msgs)) {
$max = count ($msgs);
$msgs = join (',', $msgs);
} else {
$max = count(split(',', $msgs));
}
$this->_set_sort_order($sort_field, $sort_order);
$start_msg = ($this->list_page-1) * $this->page_size;
// fetch reuested headers from server
$a_msg_headers = array();
$this->_fetch_headers($mailbox, $msgs, $a_msg_headers, NULL);
// 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 = iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order);
// only return the requested part of the set
return array_slice(array_values($a_msg_headers), $start_msg, min($max-$start_msg, $this->page_size));
}
/**
* Helper function to get first and last index of the requested set
*
* @param int message count
* @param mixed page number to show, or string 'all'
* @return array array with two values: first index, last index
* @access private
*/
function _get_message_range($max, $page)
{
$start_msg = ($this->list_page-1) * $this->page_size;
if ($page=='all')
{
$begin = 0;
$end = $max;
}
else if ($this->sort_order=='DESC')
{
$begin = $max - $this->page_size - $start_msg;
$end = $max - $start_msg;
}
else
{
$begin = $start_msg;
$end = $start_msg + $this->page_size;
}
if ($begin < 0) $begin = 0;
if ($end < 0) $end = $max;
if ($end > $max) $end = $max;
return array($begin, $end);
}
/**
* Fetches message headers
* Used for loop
*
* @param string Mailbox name
* @param string Message index to fetch
* @param array Reference to message headers array
* @param array Array with cache index
* @return int Number of deleted messages
* @access private
*/
function _fetch_headers($mailbox, $msgs, &$a_msg_headers, $cache_key)
{
// cache is incomplete
$cache_index = $this->get_message_cache_index($cache_key);
// fetch reuested headers from server
$a_header_index = iil_C_FetchHeaders($this->conn, $mailbox, $msgs);
$deleted_count = 0;
if (!empty($a_header_index))
{
foreach ($a_header_index as $i => $headers)
{
if ($headers->deleted && $this->skip_deleted)
{
// delete from cache
if ($cache_index[$headers->id] && $cache_index[$headers->id] == $headers->uid)
$this->remove_message_cache($cache_key, $headers->id);
$deleted_count++;
continue;
}
// add message to cache
if ($this->caching_enabled && $cache_index[$headers->id] != $headers->uid)
$this->add_message_cache($cache_key, $headers->id, $headers);
$a_msg_headers[$headers->uid] = $headers;
}
}
return $deleted_count;
}
/**
* Return sorted array of message UIDs
*
* @param string Mailbox to get index from
* @param string Sort column
* @param string Sort order [ASC, DESC]
* @return array Indexed array with message ids
*/
function message_index($mbox_name='', $sort_field=NULL, $sort_order=NULL)
{
$this->_set_sort_order($sort_field, $sort_order);
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
$key = "{$mailbox}:{$this->sort_field}:{$this->sort_order}:{$this->search_string}.msgi";
// we have a saved search result. get index from there
if (!isset($this->cache[$key]) && $this->search_string && $mailbox == $this->mailbox)
{
$this->cache[$key] = $a_msg_headers = array();
$this->_fetch_headers($mailbox, join(',', $this->search_set), $a_msg_headers, NULL);
foreach (iil_SortHeaders($a_msg_headers, $this->sort_field, $this->sort_order) as $i => $msg)
$this->cache[$key][] = $msg->uid;
}
// have stored it in RAM
if (isset($this->cache[$key]))
return $this->cache[$key];
// check local cache
$cache_key = $mailbox.'.msg';
$cache_status = $this->check_cache_status($mailbox, $cache_key);
// cache is OK
if ($cache_status>0)
{
$a_index = $this->get_message_cache_index($cache_key, TRUE, $this->sort_field, $this->sort_order);
return array_values($a_index);
}
// fetch complete message index
$msg_count = $this->_messagecount($mailbox);
if ($this->get_capability('sort') && ($a_index = iil_C_Sort($this->conn, $mailbox, $this->sort_field, '', TRUE)))
{
if ($this->sort_order == 'DESC')
$a_index = array_reverse($a_index);
$this->cache[$key] = $a_index;
}
else
{
$a_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", $this->sort_field);
$a_uids = iil_C_FetchUIDs($this->conn, $mailbox);
if ($this->sort_order=="ASC")
asort($a_index);
else if ($this->sort_order=="DESC")
arsort($a_index);
$i = 0;
$this->cache[$key] = array();
foreach ($a_index as $index => $value)
$this->cache[$key][$i++] = $a_uids[$index];
}
return $this->cache[$key];
}
/**
* @access private
*/
function sync_header_index($mailbox)
{
$cache_key = $mailbox.'.msg';
$cache_index = $this->get_message_cache_index($cache_key);
$msg_count = $this->_messagecount($mailbox);
// fetch complete message index
$a_message_index = iil_C_FetchHeaderIndex($this->conn, $mailbox, "1:$msg_count", 'UID');
foreach ($a_message_index as $id => $uid)
{
// message in cache at correct position
if ($cache_index[$id] == $uid)
{
unset($cache_index[$id]);
continue;
}
// message in cache but in wrong position
if (in_array((string)$uid, $cache_index, TRUE))
{
unset($cache_index[$id]);
}
// other message at this position
if (isset($cache_index[$id]))
{
$this->remove_message_cache($cache_key, $id);
unset($cache_index[$id]);
}
// fetch complete headers and add to cache
$headers = iil_C_FetchHeader($this->conn, $mailbox, $id);
$this->add_message_cache($cache_key, $headers->id, $headers);
}
// those ids that are still in cache_index have been deleted
if (!empty($cache_index))
{
foreach ($cache_index as $id => $uid)
$this->remove_message_cache($cache_key, $id);
}
}
/**
* Invoke search request to IMAP server
*
* @param string mailbox name to search in
* @param string search criteria (ALL, TO, FROM, SUBJECT, etc)
* @param string search string
* @return array search results as list of message ids
* @access public
*/
function search($mbox_name='', $criteria='ALL', $str=NULL, $charset=NULL)
{
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
// have an array of criterias => execute multiple searches
if (is_array($criteria) && $str)
{
$results = array();
foreach ($criteria as $crit)
if ($search_result = $this->search($mbox_name, $crit, $str, $charset))
$results = array_merge($results, $search_result);
$results = array_unique($results);
$this->set_search_set($criteria, $str, $results, $charset);
return $results;
}
else if ($str && $criteria)
{
$search = (!empty($charset) ? "CHARSET $charset " : '') . sprintf("%s {%d}\r\n%s", $criteria, strlen($str), $str);
$results = $this->_search_index($mailbox, $search);
// try search with ISO charset (should be supported by server)
if (empty($results) && !empty($charset) && $charset!='ISO-8859-1')
$results = $this->search($mbox_name, $criteria, rcube_charset_convert($str, $charset, 'ISO-8859-1'), 'ISO-8859-1');
$this->set_search_set($criteria, $str, $results, $charset);
return $results;
}
else
return $this->_search_index($mailbox, $criteria);
}
/**
* Private search method
*
* @return array search results as list of message ids
* @access private
* @see rcube_imap::search()
*/
function _search_index($mailbox, $criteria='ALL')
{
$a_messages = iil_C_Search($this->conn, $mailbox, $criteria);
// clean message list (there might be some empty entries)
if (is_array($a_messages))
{
foreach ($a_messages as $i => $val)
if (empty($val))
unset($a_messages[$i]);
}
return $a_messages;
}
/**
* Refresh saved search set
*
* @return array Current search set
*/
function refresh_search()
{
if (!empty($this->search_subject) && !empty($this->search_string))
$this->search_set = $this->search('', $this->search_subject, $this->search_string, $this->search_charset);
return $this->get_search_set();
}
/**
* Check if the given message ID is part of the current search set
*
* @return boolean True on match or if no search request is stored
*/
function in_searchset($msgid)
{
if (!empty($this->search_string))
return in_array("$msgid", (array)$this->search_set, true);
else
return true;
}
/**
* Return message headers object of a specific message
*
* @param int Message ID
* @param string Mailbox to read from
* @param boolean True if $id is the message UID
* @return object Message headers representation
*/
function get_headers($id, $mbox_name=NULL, $is_uid=TRUE)
{
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
$uid = $is_uid ? $id : $this->_id2uid($id);
// get cached headers
if ($uid && ($headers = &$this->get_cached_message($mailbox.'.msg', $uid)))
return $headers;
$headers = iil_C_FetchHeader($this->conn, $mailbox, $id, $is_uid);
// write headers cache
if ($headers)
{
if ($headers->uid && $headers->id)
$this->uid_id_map[$mailbox][$headers->uid] = $headers->id;
$this->add_message_cache($mailbox.'.msg', $headers->id, $headers);
}
return $headers;
}
/**
* Fetch body structure from the IMAP server and build
* an object structure similar to the one generated by PEAR::Mail_mimeDecode
*
* @param int Message UID to fetch
* @return object rcube_message_part Message part tree or False on failure
*/
function &get_structure($uid)
{
$cache_key = $this->mailbox.'.msg';
$headers = &$this->get_cached_message($cache_key, $uid, true);
// return cached message structure
- if (is_object($headers) && is_object($headers->structure))
+ if (is_object($headers) && is_object($headers->structure)) {
return $headers->structure;
-
+ }
+
// resolve message sequence number
- if (!($msg_id = $this->_uid2id($uid)))
+ if (!($msg_id = $this->_uid2id($uid))) {
return FALSE;
+ }
- $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
+ $structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
$structure = iml_GetRawStructureArray($structure_str);
$struct = false;
// parse structure and add headers
if (!empty($structure))
{
$this->_msg_id = $msg_id;
$headers = $this->get_headers($uid);
$struct = &$this->_structure_part($structure);
$struct->headers = get_object_vars($headers);
// don't trust given content-type
if (empty($struct->parts) && !empty($struct->headers['ctype']))
{
$struct->mime_id = '1';
$struct->mimetype = strtolower($struct->headers['ctype']);
list($struct->ctype_primary, $struct->ctype_secondary) = explode('/', $struct->mimetype);
}
// write structure to cache
if ($this->caching_enabled)
$this->add_message_cache($cache_key, $msg_id, $headers, $struct);
}
return $struct;
}
/**
* Build message part object
*
* @access private
*/
function &_structure_part($part, $count=0, $parent='')
{
$struct = new rcube_message_part;
$struct->mime_id = empty($parent) ? (string)$count : "$parent.$count";
// multipart
if (is_array($part[0]))
{
$struct->ctype_primary = 'multipart';
// 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;
$struct->parts = array();
for ($i=0, $count=0; $i<count($part); $i++)
if (is_array($part[$i]) && count($part[$i]) > 3)
$struct->parts[] = $this->_structure_part($part[$i], ++$count, $struct->mime_id);
return $struct;
}
// 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'];
}
// read content encoding
if (!empty($part[5]) && $part[5]!='NIL')
{
$struct->encoding = strtolower($part[5]);
$struct->headers['content-transfer-encoding'] = $struct->encoding;
}
// get part size
if (!empty($part[6]) && $part[6]!='NIL')
$struct->size = intval($part[6]);
// read part disposition
$di = count($part) - 2;
if ((is_array($part[$di]) && count($part[$di]) == 2 && is_array($part[$di][1])) ||
(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 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]) && count($part[8][$i]) > 5)
$struct->parts[] = $this->_structure_part($part[8][$i], ++$count, $struct->mime_id);
}
// get part ID
if (!empty($part[3]) && $part[3]!='NIL')
{
$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)) {
$part_headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $struct->mime_id);
$struct->headers = $this->_parse_headers($part_headers) + $struct->headers;
}
if ($struct->ctype_primary=='message') {
if (is_array($part[8]) && empty($struct->parts))
$struct->parts[] = $this->_structure_part($part[8], ++$count, $struct->mime_id);
}
// normalize filename property
$this->_set_part_filename($struct);
return $struct;
}
/**
* Set attachment filename from message part structure
*
* @access private
* @param object rcube_message_part Part object
*/
function _set_part_filename(&$part)
{
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"
//TODO: fetching headers for a second time is not effecient, this code should be moved somewhere earlier --tensor
if ($i<2) {
// TODO: fetch only Content-Type/Content-Disposition header
$headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $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) {
$headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $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) {
$headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $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) {
$headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $this->_msg_id, $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)) {
$part->filename = rcube_imap::decode_mime_string($filename_mime,
$part->charset ? $part->charset : rc_detect_encoding($filename_mime, $this->default_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);
}
}
/**
* Fetch message body of a specific message from the server
*
* @param int Message UID
* @param string Part number
* @param object rcube_message_part Part object created by get_structure()
* @param mixed True to print part, ressource to write part contents in
* @param resource File pointer to save the message part
* @return string Message/part body if not printed
*/
function &get_message_part($uid, $part=1, $o_part=NULL, $print=NULL, $fp=NULL)
{
if (!($msg_id = $this->_uid2id($uid)))
return FALSE;
// get part encoding if not provided
if (!is_object($o_part))
{
$structure_str = iil_C_FetchStructureString($this->conn, $this->mailbox, $msg_id);
$structure = iml_GetRawStructureArray($structure_str);
$part_type = iml_GetPartTypeCode($structure, $part);
$o_part = new rcube_message_part;
$o_part->ctype_primary = $part_type==0 ? 'text' : ($part_type==2 ? 'message' : 'other');
$o_part->encoding = strtolower(iml_GetPartEncodingString($structure, $part));
$o_part->charset = iml_GetPartCharset($structure, $part);
}
// TODO: Add caching for message parts
if ($print)
{
$mode = $o_part->encoding == 'base64' ? 3 : ($o_part->encoding == 'quoted-printable' ? 1 : 2);
$body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, $mode);
// we have to decode the part manually before printing
if ($mode == 1)
{
echo $this->mime_decode($body, $o_part->encoding);
$body = true;
}
}
else
{
if ($fp && $o_part->encoding == 'base64')
return iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, 3, $fp);
else
$body = iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, $part, 1);
// decode part body
if ($o_part->encoding)
$body = $this->mime_decode($body, $o_part->encoding);
// convert charset (if text or message part)
if ($o_part->ctype_primary=='text' || $o_part->ctype_primary=='message')
{
// assume default if no charset specified
if (empty($o_part->charset))
$o_part->charset = $this->default_charset;
$body = rcube_charset_convert($body, $o_part->charset);
}
if ($fp)
{
fwrite($fp, $body);
return true;
}
}
return $body;
}
/**
* Fetch message body of a specific message from the server
*
* @param int Message UID
* @return string Message/part body
* @see rcube_imap::get_message_part()
*/
function &get_body($uid, $part=1)
{
$headers = $this->get_headers($uid);
return rcube_charset_convert(
$this->mime_decode($this->get_message_part($uid, $part), 'quoted-printable'),
$headers->charset ? $headers->charset : $this->default_charset);
}
/**
* Returns the whole message source as string
*
* @param int Message UID
* @return string Message source string
*/
function &get_raw_body($uid)
{
if (!($msg_id = $this->_uid2id($uid)))
return FALSE;
$body = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
$body .= iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 1);
return $body;
}
/**
* Returns the message headers as string
*
* @param int Message UID
* @return string Message headers string
*/
function &get_raw_headers($uid)
{
if (!($msg_id = $this->_uid2id($uid)))
return FALSE;
$headers = iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
return $headers;
}
/**
* Sends the whole message source to stdout
*
* @param int Message UID
*/
function print_raw_body($uid)
{
if (!($msg_id = $this->_uid2id($uid)))
return FALSE;
print iil_C_FetchPartHeader($this->conn, $this->mailbox, $msg_id, NULL);
flush();
iil_C_HandlePartBody($this->conn, $this->mailbox, $msg_id, NULL, 2);
}
/**
* Set message flag to one or several messages
*
* @param mixed Message UIDs as array or as comma-separated string
* @param string Flag to set: SEEN, UNDELETED, DELETED, RECENT, ANSWERED, DRAFT, MDNSENT
* @return boolean True on success, False on failure
*/
function set_flag($uids, $flag)
{
$flag = strtoupper($flag);
$msg_ids = array();
if (!is_array($uids))
$uids = explode(',',$uids);
foreach ($uids as $uid) {
$msg_ids[$uid] = $this->_uid2id($uid);
}
if ($flag=='UNDELETED')
$result = iil_C_Undelete($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
else if ($flag=='UNSEEN')
$result = iil_C_Unseen($this->conn, $this->mailbox, join(',', array_values($msg_ids)));
else if ($flag=='UNFLAGGED')
$result = iil_C_UnFlag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), 'FLAGGED');
else
$result = iil_C_Flag($this->conn, $this->mailbox, join(',', array_values($msg_ids)), $flag);
// reload message headers if cached
$cache_key = $this->mailbox.'.msg';
if ($this->caching_enabled)
{
foreach ($msg_ids as $uid => $id)
{
if ($cached_headers = $this->get_cached_message($cache_key, $uid))
{
$this->remove_message_cache($cache_key, $id);
//$this->get_headers($uid);
}
}
// close and re-open connection
// this prevents connection problems with Courier
$this->reconnect();
}
// set nr of messages that were flaged
$count = count($msg_ids);
// clear message count cache
if ($result && $flag=='SEEN')
$this->_set_messagecount($this->mailbox, 'UNSEEN', $count*(-1));
else if ($result && $flag=='UNSEEN')
$this->_set_messagecount($this->mailbox, 'UNSEEN', $count);
else if ($result && $flag=='DELETED')
$this->_set_messagecount($this->mailbox, 'ALL', $count*(-1));
return $result;
}
/**
* Append a mail message (source) to a specific mailbox
*
* @param string Target mailbox
* @param string Message source
* @return boolean True on success, False on error
*/
function save_message($mbox_name, &$message)
{
$mbox_name = stripslashes($mbox_name);
$mailbox = $this->_mod_mailbox($mbox_name);
// make sure mailbox exists
if (($mailbox == 'INBOX') || in_array($mailbox, $this->_list_mailboxes()))
$saved = iil_C_Append($this->conn, $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 string List of UIDs to move, separated by comma
* @param string Target mailbox
* @param string Source mailbox
* @return boolean True on success, False on error
*/
function move_message($uids, $to_mbox, $from_mbox='')
{
$to_mbox_in = stripslashes($to_mbox);
$from_mbox = stripslashes($from_mbox);
$to_mbox = $this->_mod_mailbox($to_mbox_in);
$from_mbox = $from_mbox ? $this->_mod_mailbox($from_mbox) : $this->mailbox;
// make sure mailbox exists
if (!in_array($to_mbox, $this->_list_mailboxes()))
{
if (in_array($to_mbox_in, $this->default_folders))
$this->create_mailbox($to_mbox_in, TRUE);
else
return FALSE;
}
// convert the list of uids to array
$a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
// exit if no message uids are specified
if (!is_array($a_uids))
return false;
// convert uids to message ids
$a_mids = array();
foreach ($a_uids as $uid)
$a_mids[] = $this->_uid2id($uid, $from_mbox);
$iil_move = iil_C_Move($this->conn, join(',', $a_mids), $from_mbox, $to_mbox);
$moved = !($iil_move === false || $iil_move < 0);
// send expunge command in order to have the moved message
// really deleted from the source mailbox
if ($moved) {
// but only when flag_for_deletion is set to false
if (!rcmail::get_instance()->config->get('flag_for_deletion', false))
{
$this->_expunge($from_mbox, FALSE);
$this->_clear_messagecount($from_mbox);
$this->_clear_messagecount($to_mbox);
}
}
// moving failed
else if (rcmail::get_instance()->config->get('delete_always', false)) {
return iil_C_Delete($this->conn, $from_mbox, join(',', $a_mids));
}
// remove message ids from search set
if ($moved && $this->search_set && $from_mbox == $this->mailbox)
$this->search_set = array_diff($this->search_set, $a_mids);
// update cached message headers
$cache_key = $from_mbox.'.msg';
if ($moved && ($a_cache_index = $this->get_message_cache_index($cache_key)))
{
$start_index = 100000;
foreach ($a_uids as $uid)
{
if (($index = array_search($uid, $a_cache_index)) !== FALSE)
$start_index = min($index, $start_index);
}
// clear cache from the lowest index on
$this->clear_message_cache($cache_key, $start_index);
}
return $moved;
}
/**
* Mark messages as deleted and expunge mailbox
*
* @param string List of UIDs to move, separated by comma
* @param string Source mailbox
* @return boolean True on success, False on error
*/
function delete_message($uids, $mbox_name='')
{
$mbox_name = stripslashes($mbox_name);
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
// convert the list of uids to array
$a_uids = is_string($uids) ? explode(',', $uids) : (is_array($uids) ? $uids : NULL);
// exit if no message uids are specified
if (!is_array($a_uids))
return false;
// convert uids to message ids
$a_mids = array();
foreach ($a_uids as $uid)
$a_mids[] = $this->_uid2id($uid, $mailbox);
$deleted = iil_C_Delete($this->conn, $mailbox, join(',', $a_mids));
// send expunge command in order to have the deleted message
// really deleted from the mailbox
if ($deleted)
{
$this->_expunge($mailbox, FALSE);
$this->_clear_messagecount($mailbox);
unset($this->uid_id_map[$mailbox]);
}
// remove message ids from search set
if ($deleted && $this->search_set && $mailbox == $this->mailbox)
$this->search_set = array_diff($this->search_set, $a_mids);
// remove deleted messages from cache
$cache_key = $mailbox.'.msg';
if ($deleted && ($a_cache_index = $this->get_message_cache_index($cache_key)))
{
$start_index = 100000;
foreach ($a_uids as $uid)
{
if (($index = array_search($uid, $a_cache_index)) !== FALSE)
$start_index = min($index, $start_index);
}
// clear cache from the lowest index on
$this->clear_message_cache($cache_key, $start_index);
}
return $deleted;
}
/**
* Clear all messages in a specific mailbox
*
* @param string Mailbox name
* @return int Above 0 on success
*/
function clear_mailbox($mbox_name=NULL)
{
$mbox_name = stripslashes($mbox_name);
$mailbox = !empty($mbox_name) ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
$msg_count = $this->_messagecount($mailbox, 'ALL');
if ($msg_count>0)
{
$cleared = iil_C_ClearFolder($this->conn, $mailbox);
// make sure the message count cache is cleared as well
if ($cleared)
{
$this->clear_message_cache($mailbox.'.msg');
$a_mailbox_cache = $this->get_cache('messagecount');
unset($a_mailbox_cache[$mailbox]);
$this->update_cache('messagecount', $a_mailbox_cache);
}
return $cleared;
}
else
return 0;
}
/**
* Send IMAP expunge command and clear cache
*
* @param string Mailbox name
* @param boolean False if cache should not be cleared
* @return boolean True on success
*/
function expunge($mbox_name='', $clear_cache=TRUE)
{
$mbox_name = stripslashes($mbox_name);
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
return $this->_expunge($mailbox, $clear_cache);
}
/**
* Send IMAP expunge command and clear cache
*
* @see rcube_imap::expunge()
* @access private
*/
function _expunge($mailbox, $clear_cache=TRUE)
{
$result = iil_C_Expunge($this->conn, $mailbox);
if ($result>=0 && $clear_cache)
{
$this->clear_message_cache($mailbox.'.msg');
$this->_clear_messagecount($mailbox);
}
return $result;
}
/* --------------------------------
* folder managment
* --------------------------------*/
/**
* Get a list of all folders available on the IMAP server
*
* @param string IMAP root dir
* @return array Indexed array with folder names
*/
function list_unsubscribed($root='')
{
static $sa_unsubscribed;
if (is_array($sa_unsubscribed))
return $sa_unsubscribed;
// retrieve list of folders from IMAP server
$a_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
// modify names with root dir
foreach ($a_mboxes as $mbox_name)
{
$name = $this->_mod_mailbox($mbox_name, 'out');
if (strlen($name))
$a_folders[] = $name;
}
// filter folders and sort them
$sa_unsubscribed = $this->_sort_mailbox_list($a_folders);
return $sa_unsubscribed;
}
/**
* 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 iil_C_GetQuota($this->conn);
return FALSE;
}
/**
* Subscribe to a specific mailbox(es)
*
* @param array 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 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 New mailbox name (as utf-7 string)
* @param boolean True if the new mailbox should be subscribed
* @param string Name of the created mailbox, false on error
*/
function create_mailbox($name, $subscribe=FALSE)
{
$result = FALSE;
// replace backslashes
$name = preg_replace('/[\\\]+/', '-', $name);
// reduce mailbox name to 100 chars
$name = substr($name, 0, 100);
$abs_name = $this->_mod_mailbox($name);
$a_mailbox_cache = $this->get_cache('mailboxes');
if (strlen($abs_name) && (!is_array($a_mailbox_cache) || !in_array($abs_name, $a_mailbox_cache)))
$result = iil_C_CreateFolder($this->conn, $abs_name);
// try to subscribe it
if ($result && $subscribe)
$this->subscribe($name);
return $result ? $name : FALSE;
}
/**
* Set a new name to an existing mailbox
*
* @param string Mailbox to rename (as utf-7 string)
* @param string New mailbox name (as utf-7 string)
* @return string Name of the renames mailbox, False on error
*/
function rename_mailbox($mbox_name, $new_name)
{
$result = FALSE;
// replace backslashes
$name = preg_replace('/[\\\]+/', '-', $new_name);
// encode mailbox name and reduce it to 100 chars
$name = substr($new_name, 0, 100);
// make absolute path
$mailbox = $this->_mod_mailbox($mbox_name);
$abs_name = $this->_mod_mailbox($name);
// check if mailbox is subscribed
$a_subscribed = $this->_list_mailboxes();
$subscribed = in_array($mailbox, $a_subscribed);
// unsubscribe folder
if ($subscribed)
iil_C_UnSubscribe($this->conn, $mailbox);
if (strlen($abs_name))
$result = iil_C_RenameFolder($this->conn, $mailbox, $abs_name);
if ($result)
{
$delm = $this->get_hierarchy_delimiter();
// check if mailbox children are subscribed
foreach ($a_subscribed as $c_subscribed)
if (preg_match('/^'.preg_quote($mailbox.$delm, '/').'/', $c_subscribed))
{
iil_C_UnSubscribe($this->conn, $c_subscribed);
iil_C_Subscribe($this->conn, preg_replace('/^'.preg_quote($mailbox, '/').'/', $abs_name, $c_subscribed));
}
// clear cache
$this->clear_message_cache($mailbox.'.msg');
$this->clear_cache('mailboxes');
}
// try to subscribe it
if ($result && $subscribed)
iil_C_Subscribe($this->conn, $abs_name);
return $result ? $name : FALSE;
}
/**
* Remove mailboxes from server
*
* @param string Mailbox name
* @return boolean True on success
*/
function delete_mailbox($mbox_name)
{
$deleted = FALSE;
if (is_array($mbox_name))
$a_mboxes = $mbox_name;
else if (is_string($mbox_name) && strlen($mbox_name))
$a_mboxes = explode(',', $mbox_name);
$all_mboxes = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox($root), '*');
if (is_array($a_mboxes))
foreach ($a_mboxes as $mbox_name)
{
$mailbox = $this->_mod_mailbox($mbox_name);
// unsubscribe mailbox before deleting
iil_C_UnSubscribe($this->conn, $mailbox);
// send delete command to server
$result = iil_C_DeleteFolder($this->conn, $mailbox);
if ($result>=0)
$deleted = TRUE;
foreach ($all_mboxes as $c_mbox)
{
$regex = preg_quote($mailbox . $this->delimiter, '/');
$regex = '/^' . $regex . '/';
if (preg_match($regex, $c_mbox))
{
iil_C_UnSubscribe($this->conn, $c_mbox);
$result = iil_C_DeleteFolder($this->conn, $c_mbox);
if ($result>=0)
$deleted = TRUE;
}
}
}
// clear mailboxlist cache
if ($deleted)
{
$this->clear_message_cache($mailbox.'.msg');
$this->clear_cache('mailboxes');
}
return $deleted;
}
/**
* Create all folders specified as default
*/
function create_default_folders()
{
$a_folders = iil_C_ListMailboxes($this->conn, $this->_mod_mailbox(''), '*');
$a_subscribed = iil_C_ListSubscribed($this->conn, $this->_mod_mailbox(''), '*');
// create default folders if they do not exist
foreach ($this->default_folders as $folder)
{
$abs_name = $this->_mod_mailbox($folder);
if (!in_array_nocase($abs_name, $a_folders))
$this->create_mailbox($folder, TRUE);
else if (!in_array_nocase($abs_name, $a_subscribed))
$this->subscribe($folder);
}
}
/* --------------------------------
* internal caching methods
* --------------------------------*/
/**
* @access private
*/
function set_caching($set)
{
if ($set && is_object($this->db))
$this->caching_enabled = TRUE;
else
$this->caching_enabled = FALSE;
}
/**
* @access private
*/
function get_cache($key)
{
// read cache
if (!isset($this->cache[$key]) && $this->caching_enabled)
{
$cache_data = $this->_read_cache_record('IMAP.'.$key);
$this->cache[$key] = strlen($cache_data) ? unserialize($cache_data) : FALSE;
}
return $this->cache[$key];
}
/**
* @access private
*/
function update_cache($key, $data)
{
$this->cache[$key] = $data;
$this->cache_changed = TRUE;
$this->cache_changes[$key] = TRUE;
}
/**
* @access private
*/
function write_cache()
{
if ($this->caching_enabled && $this->cache_changed)
{
foreach ($this->cache as $key => $data)
{
if ($this->cache_changes[$key])
$this->_write_cache_record('IMAP.'.$key, serialize($data));
}
}
}
/**
* @access private
*/
function clear_cache($key=NULL)
{
if (!$this->caching_enabled)
return;
if ($key===NULL)
{
foreach ($this->cache as $key => $data)
$this->_clear_cache_record('IMAP.'.$key);
$this->cache = array();
$this->cache_changed = FALSE;
$this->cache_changes = array();
}
else
{
$this->_clear_cache_record('IMAP.'.$key);
$this->cache_changes[$key] = FALSE;
unset($this->cache[$key]);
}
}
/**
* @access private
*/
function _read_cache_record($key)
{
$cache_data = FALSE;
if ($this->db)
{
// get cached data from DB
$sql_result = $this->db->query(
"SELECT cache_id, data
FROM ".get_table_name('cache')."
WHERE user_id=?
AND cache_key=?",
$_SESSION['user_id'],
$key);
if ($sql_arr = $this->db->fetch_assoc($sql_result))
{
$cache_data = $sql_arr['data'];
$this->cache_keys[$key] = $sql_arr['cache_id'];
}
}
return $cache_data;
}
/**
* @access private
*/
function _write_cache_record($key, $data)
{
if (!$this->db)
return FALSE;
// check if we already have a cache entry for this key
if (!isset($this->cache_keys[$key]))
{
$sql_result = $this->db->query(
"SELECT cache_id
FROM ".get_table_name('cache')."
WHERE user_id=?
AND cache_key=?",
$_SESSION['user_id'],
$key);
if ($sql_arr = $this->db->fetch_assoc($sql_result))
$this->cache_keys[$key] = $sql_arr['cache_id'];
else
$this->cache_keys[$key] = FALSE;
}
// update existing cache record
if ($this->cache_keys[$key])
{
$this->db->query(
"UPDATE ".get_table_name('cache')."
SET created=".$this->db->now().",
data=?
WHERE user_id=?
AND cache_key=?",
$data,
$_SESSION['user_id'],
$key);
}
// add new cache record
else
{
$this->db->query(
"INSERT INTO ".get_table_name('cache')."
(created, user_id, cache_key, data)
VALUES (".$this->db->now().", ?, ?, ?)",
$_SESSION['user_id'],
$key,
$data);
}
}
/**
* @access private
*/
function _clear_cache_record($key)
{
$this->db->query(
"DELETE FROM ".get_table_name('cache')."
WHERE user_id=?
AND cache_key=?",
$_SESSION['user_id'],
$key);
}
/* --------------------------------
* message caching methods
* --------------------------------*/
/**
* Checks if the cache is up-to-date
*
* @param string Mailbox name
* @param string Internal cache key
* @return int -3 = off, -2 = incomplete, -1 = dirty
*/
function check_cache_status($mailbox, $cache_key)
{
if (!$this->caching_enabled)
return -3;
$cache_index = $this->get_message_cache_index($cache_key, TRUE);
$msg_count = $this->_messagecount($mailbox);
$cache_count = count($cache_index);
// console("Cache check: $msg_count !== ".count($cache_index));
if ($cache_count==$msg_count)
{
// get highest index
$header = iil_C_FetchHeader($this->conn, $mailbox, "$msg_count");
$cache_uid = array_pop($cache_index);
// uids of highest message matches -> cache seems OK
if ($cache_uid == $header->uid)
return 1;
// cache is dirty
return -1;
}
// if cache count differs less than 10% report as dirty
else if (abs($msg_count - $cache_count) < $msg_count/10)
return -1;
else
return -2;
}
/**
* @access private
*/
function get_message_cache($key, $from, $to, $sort_field, $sort_order)
{
$cache_key = "$key:$from:$to:$sort_field:$sort_order";
$db_header_fields = array('idx', 'uid', 'subject', 'from', 'to', 'cc', 'date', 'size');
if (!in_array($sort_field, $db_header_fields))
$sort_field = 'idx';
if ($this->caching_enabled && !isset($this->cache[$cache_key]))
{
$this->cache[$cache_key] = array();
$sql_result = $this->db->limitquery(
"SELECT idx, uid, headers
FROM ".get_table_name('messages')."
WHERE user_id=?
AND cache_key=?
ORDER BY ".$this->db->quoteIdentifier($sort_field)." ".
strtoupper($sort_order),
$from,
$to-$from,
$_SESSION['user_id'],
$key);
while ($sql_arr = $this->db->fetch_assoc($sql_result))
{
$uid = $sql_arr['uid'];
$this->cache[$cache_key][$uid] = unserialize($sql_arr['headers']);
// featch headers if unserialize failed
if (empty($this->cache[$cache_key][$uid]))
$this->cache[$cache_key][$uid] = iil_C_FetchHeader($this->conn, preg_replace('/.msg$/', '', $key), $uid, true);
}
}
return $this->cache[$cache_key];
}
/**
* @access private
*/
function &get_cached_message($key, $uid, $struct=false)
{
$internal_key = '__single_msg';
if ($this->caching_enabled && (!isset($this->cache[$internal_key][$uid]) ||
($struct && empty($this->cache[$internal_key][$uid]->structure))))
{
$sql_select = "idx, uid, headers" . ($struct ? ", structure" : '');
$sql_result = $this->db->query(
"SELECT $sql_select
FROM ".get_table_name('messages')."
WHERE user_id=?
AND cache_key=?
AND uid=?",
$_SESSION['user_id'],
$key,
$uid);
if ($sql_arr = $this->db->fetch_assoc($sql_result))
{
$this->cache[$internal_key][$uid] = unserialize($sql_arr['headers']);
if (is_object($this->cache[$internal_key][$uid]) && !empty($sql_arr['structure']))
$this->cache[$internal_key][$uid]->structure = unserialize($sql_arr['structure']);
}
}
return $this->cache[$internal_key][$uid];
}
/**
* @access private
*/
function get_message_cache_index($key, $force=FALSE, $sort_col='idx', $sort_order='ASC')
{
static $sa_message_index = array();
// empty key -> empty array
if (!$this->caching_enabled || empty($key))
return array();
if (!empty($sa_message_index[$key]) && !$force)
return $sa_message_index[$key];
$sa_message_index[$key] = array();
$sql_result = $this->db->query(
"SELECT idx, uid
FROM ".get_table_name('messages')."
WHERE user_id=?
AND cache_key=?
ORDER BY ".$this->db->quote_identifier($sort_col)." ".$sort_order,
$_SESSION['user_id'],
$key);
while ($sql_arr = $this->db->fetch_assoc($sql_result))
$sa_message_index[$key][$sql_arr['idx']] = $sql_arr['uid'];
return $sa_message_index[$key];
}
/**
* @access private
*/
function add_message_cache($key, $index, $headers, $struct=null)
{
if (empty($key) || !is_object($headers) || empty($headers->uid))
return;
// add to internal (fast) cache
$this->cache['__single_msg'][$headers->uid] = $headers;
$this->cache['__single_msg'][$headers->uid]->structure = $struct;
// no further caching
if (!$this->caching_enabled)
return;
// check for an existing record (probly headers are cached but structure not)
$sql_result = $this->db->query(
"SELECT message_id
FROM ".get_table_name('messages')."
WHERE user_id=?
AND cache_key=?
AND uid=?
AND del<>1",
$_SESSION['user_id'],
$key,
$headers->uid);
// update cache record
if ($sql_arr = $this->db->fetch_assoc($sql_result))
{
$this->db->query(
"UPDATE ".get_table_name('messages')."
SET idx=?, headers=?, structure=?
WHERE message_id=?",
$index,
serialize($headers),
is_object($struct) ? serialize($struct) : NULL,
$sql_arr['message_id']
);
}
else // insert new record
{
$this->db->query(
"INSERT INTO ".get_table_name('messages')."
(user_id, del, cache_key, created, idx, uid, subject, ".$this->db->quoteIdentifier('from').", ".$this->db->quoteIdentifier('to').", cc, date, size, headers, structure)
VALUES (?, 0, ?, ".$this->db->now().", ?, ?, ?, ?, ?, ?, ".$this->db->fromunixtime($headers->timestamp).", ?, ?, ?)",
$_SESSION['user_id'],
$key,
$index,
$headers->uid,
(string)substr($this->decode_header($headers->subject, TRUE), 0, 128),
(string)substr($this->decode_header($headers->from, TRUE), 0, 128),
(string)substr($this->decode_header($headers->to, TRUE), 0, 128),
(string)substr($this->decode_header($headers->cc, TRUE), 0, 128),
(int)$headers->size,
serialize($headers),
is_object($struct) ? serialize($struct) : NULL
);
}
}
/**
* @access private
*/
function remove_message_cache($key, $index)
{
if (!$this->caching_enabled)
return;
$this->db->query(
"DELETE FROM ".get_table_name('messages')."
WHERE user_id=?
AND cache_key=?
AND idx=?",
$_SESSION['user_id'],
$key,
$index);
}
/**
* @access private
*/
function clear_message_cache($key, $start_index=1)
{
if (!$this->caching_enabled)
return;
$this->db->query(
"DELETE FROM ".get_table_name('messages')."
WHERE user_id=?
AND cache_key=?
AND idx>=?",
$_SESSION['user_id'],
$key,
$start_index);
}
/* --------------------------------
* encoding/decoding methods
* --------------------------------*/
/**
* Split an address list into a structured array list
*
* @param string Input string
* @param int List only this number of addresses
* @param boolean 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 = $val['address'];
$name = preg_replace(array('/^[\'"]/', '/[\'"]$/'), '', 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 Header value
* @param boolean 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 Header value
* @param string Fallback charset if none specified
* @return string Decoded string
* @static
*/
function decode_mime_string($input, $fallback=null)
{
// Initialize variable
$out = '';
// Iterate instead of recursing, this way if there are too many values we don't have stack overflows
// 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);
// Check if there is stuff to decode
if (strpos($input, '=?') !== false) {
// Loop through the string to decode all occurences of =? ?= into the variable $out
while(($pos = strpos($input, '=?')) !== false) {
// Append everything that is before the text to be decoded
$out .= substr($input, 0, $pos);
// Get the location of the text to decode
$end_cs_pos = strpos($input, "?", $pos+2);
$end_en_pos = strpos($input, "?", $end_cs_pos+1);
$end_pos = strpos($input, "?=", $end_en_pos+1);
// Extract the encoded string
$encstr = substr($input, $pos+2, ($end_pos-$pos-2));
// Extract the remaining string
$input = substr($input, $end_pos+2);
// Decode the string fragement
$out .= rcube_imap::_decode_mime_string_part($encstr);
}
// Deocde the rest (if any)
if (strlen($input) != 0)
$out .= rcube_imap::decode_mime_string($input, $fallback);
// return the results
return $out;
}
// no encoding information, use fallback
return rcube_charset_convert($input,
!empty($fallback) ? $fallback : rcmail::get_instance()->config->get('default_charset', 'ISO-8859-1'));
}
/**
* Decode a part of a mime-encoded string
*
* @access private
*/
function _decode_mime_string_part($str)
{
$a = explode('?', $str);
$count = count($a);
// should be in format "charset?encoding?base64_string"
if ($count >= 3)
{
for ($i=2; $i<$count; $i++)
$rest.=$a[$i];
if (($a[1]=="B")||($a[1]=="b"))
$rest = base64_decode($rest);
else if (($a[1]=="Q")||($a[1]=="q"))
{
$rest = str_replace("_", " ", $rest);
$rest = quoted_printable_decode($rest);
}
return rcube_charset_convert($rest, $a[0]);
}
else
return $str; // we dont' know what to do with this
}
/**
* Decode a mime part
*
* @param string Input string
* @param string Part encoding
* @return string Decoded string
* @access private
*/
function mime_decode($input, $encoding='7bit')
{
switch (strtolower($encoding))
{
case '7bit':
return $input;
break;
case 'quoted-printable':
return quoted_printable_decode($input);
break;
case 'base64':
return base64_decode($input);
break;
default:
return $input;
}
}
/**
* Convert body charset to UTF-8 according to the ctype_parameters
*
* @param string Part body to decode
* @param string Charset to convert from
* @return string Content converted to internal charset
*/
function charset_decode($body, $ctype_param)
{
if (is_array($ctype_param) && !empty($ctype_param['charset']))
return rcube_charset_convert($body, $ctype_param['charset']);
// defaults to what is specified in the class header
return rcube_charset_convert($body, $this->default_charset);
}
/**
* Translate UID to message ID
*
* @param int Message UID
* @param string Mailbox name
* @return int Message ID
*/
function get_id($uid, $mbox_name=NULL)
{
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
return $this->_uid2id($uid, $mailbox);
}
/**
* Translate message number to UID
*
* @param int Message ID
* @param string Mailbox name
* @return int Message UID
*/
function get_uid($id,$mbox_name=NULL)
{
$mailbox = $mbox_name ? $this->_mod_mailbox($mbox_name) : $this->mailbox;
return $this->_id2uid($id, $mailbox);
}
/* --------------------------------
* private methods
* --------------------------------*/
/**
* @access private
*/
function _mod_mailbox($mbox_name, $mode='in')
{
if ((!empty($this->root_ns) && $this->root_ns == $mbox_name) || $mbox_name == 'INBOX')
return $mbox_name;
if (!empty($this->root_dir) && $mode=='in')
$mbox_name = $this->root_dir.$this->delimiter.$mbox_name;
else if (strlen($this->root_dir) && $mode=='out')
$mbox_name = substr($mbox_name, strlen($this->root_dir)+1);
return $mbox_name;
}
/**
* Validate the given input and save to local properties
* @access 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
* @access 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(strtolower($folder), $this->default_folders_lc)) !== false && !$a_defaults[$p])
$a_defaults[$p] = $folder;
else
$folders[$folder] = rc_strtolower(rcube_charset_convert($folder, 'UTF-7'));
}
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)) {
$a_out[] = $folder;
unset($folders[$key]);
foreach ($folders as $idx => $f) {
if (strpos($f, $folder.$delimiter) === 0) {
$a_out[] = $f;
unset($folders[$idx]);
}
}
reset($folders);
}
return $a_out;
}
/**
* @access private
*/
function _uid2id($uid, $mbox_name=NULL)
{
if (!$mbox_name)
$mbox_name = $this->mailbox;
if (!isset($this->uid_id_map[$mbox_name][$uid]))
$this->uid_id_map[$mbox_name][$uid] = iil_C_UID2ID($this->conn, $mbox_name, $uid);
return $this->uid_id_map[$mbox_name][$uid];
}
/**
* @access private
*/
function _id2uid($id, $mbox_name=NULL)
{
if (!$mbox_name)
$mbox_name = $this->mailbox;
$index = array_flip((array)$this->uid_id_map[$mbox_name]);
if (isset($index[$id]))
$uid = $index[$id];
else
{
$uid = iil_C_ID2UID($this->conn, $mbox_name, $id);
$this->uid_id_map[$mbox_name][$uid] = $id;
}
return $uid;
}
/**
* Subscribe/unsubscribe a list of mailboxes and update local cache
* @access private
*/
function _change_subscription($a_mboxes, $mode)
{
$updated = FALSE;
if (is_array($a_mboxes))
foreach ($a_mboxes as $i => $mbox_name)
{
$mailbox = $this->_mod_mailbox($mbox_name);
$a_mboxes[$i] = $mailbox;
if ($mode=='subscribe')
$result = iil_C_Subscribe($this->conn, $mailbox);
else if ($mode=='unsubscribe')
$result = iil_C_UnSubscribe($this->conn, $mailbox);
if ($result>=0)
$updated = TRUE;
}
// get cached mailbox list
if ($updated)
{
$a_mailbox_cache = $this->get_cache('mailboxes');
if (!is_array($a_mailbox_cache))
return $updated;
// modify cached list
if ($mode=='subscribe')
$a_mailbox_cache = array_merge($a_mailbox_cache, $a_mboxes);
else if ($mode=='unsubscribe')
$a_mailbox_cache = array_diff($a_mailbox_cache, $a_mboxes);
// write mailboxlist to cache
$this->update_cache('mailboxes', $this->_sort_mailbox_list($a_mailbox_cache));
}
return $updated;
}
/**
* Increde/decrese messagecount for a specific mailbox
* @access private
*/
function _set_messagecount($mbox_name, $mode, $increment)
{
$a_mailbox_cache = FALSE;
$mailbox = $mbox_name ? $mbox_name : $this->mailbox;
$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
*/
function _clear_messagecount($mbox_name='')
{
$a_mailbox_cache = FALSE;
$mailbox = $mbox_name ? $mbox_name : $this->mailbox;
$a_mailbox_cache = $this->get_cache('messagecount');
if (is_array($a_mailbox_cache[$mailbox]))
{
unset($a_mailbox_cache[$mailbox]);
$this->update_cache('messagecount', $a_mailbox_cache);
}
}
/**
* Split RFC822 header string into an associative array
* @access private
*/
function _parse_headers($headers)
{
$a_headers = array();
$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
*/
function _parse_address_list($str, $decode=true)
{
// remove any newlines and carriage returns before
$a = $this->_explode_quoted_string('[,;]', preg_replace( "/[\r\n]/", " ", $str));
$result = array();
foreach ($a as $key => $val)
{
$val = preg_replace("/([\"\w])</", "$1 <", $val);
$sub_a = $this->_explode_quoted_string(' ', $decode ? $this->decode_header($val) : $val);
$result[$key]['name'] = '';
foreach ($sub_a as $k => $v)
{
if (strpos($v, '@') > 0)
$result[$key]['address'] = str_replace('<', '', str_replace('>', '', $v));
else
$result[$key]['name'] .= (empty($result[$key]['name'])?'':' ').str_replace("\"",'',stripslashes($v));
}
if (empty($result[$key]['name']))
$result[$key]['name'] = $result[$key]['address'];
}
return $result;
}
/**
* @access private
*/
function _explode_quoted_string($delimiter, $string)
{
$result = array();
$strlen = strlen($string);
for ($q=$p=$i=0; $i < $strlen; $i++)
{
if ($string{$i} == "\"" && $string{$i-1} != "\\")
$q = $q ? false : true;
else if (!$q && preg_match("/$delimiter/", $string{$i}))
{
$result[] = substr($string, $p, $i - $p);
$p = $i + 1;
}
}
$result[] = substr($string, $p);
return $result;
}
} // 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();
}
/**
* Class for sorting an array of iilBasicHeader objects in a predetermined order.
*
* @package Mail
* @author Eric Stadtherr
*/
class rcube_header_sorter
{
var $sequence_numbers = array();
/**
* Set the predetermined sort order.
*
* @param array Numerically indexed array of IMAP message sequence numbers
*/
function set_sequence_numbers($seqnums)
{
$this->sequence_numbers = array_flip($seqnums);
}
/**
* Sort the array of header objects
*
* @param array Array of iilBasicHeader objects indexed by UID
*/
function sort_headers(&$headers)
{
/*
* uksort would work if the keys were the sequence number, but unfortunately
* the keys are the UIDs. We'll use uasort instead and dereference the value
* to get the sequence number (in the "id" field).
*
* uksort($headers, array($this, "compare_seqnums"));
*/
uasort($headers, array($this, "compare_seqnums"));
}
/**
* Sort method called by uasort()
*/
function compare_seqnums($a, $b)
{
// First get the sequence number from the header object (the 'id' field).
$seqa = $a->id;
$seqb = $b->id;
// then find each sequence number in my ordered list
$posa = isset($this->sequence_numbers[$seqa]) ? intval($this->sequence_numbers[$seqa]) : -1;
$posb = isset($this->sequence_numbers[$seqb]) ? intval($this->sequence_numbers[$seqb]) : -1;
// return the relative position as the comparison value
return $posa - $posb;
}
}
/**
* Add quoted-printable encoding to a given string
*
* @param string String to encode
* @param int Add new line after this number of characters
* @param boolean True if spaces should be converted into =20
* @return string Encoded string
*/
function quoted_printable_encode($input, $line_max=76, $space_conv=false)
{
$hex = array('0','1','2','3','4','5','6','7','8','9','A','B','C','D','E','F');
$lines = preg_split("/(?:\r\n|\r|\n)/", $input);
$eol = "\r\n";
$escape = "=";
$output = "";
while( list(, $line) = each($lines))
{
//$line = rtrim($line); // remove trailing white space -> no =20\r\n necessary
$linlen = strlen($line);
$newline = "";
for($i = 0; $i < $linlen; $i++)
{
$c = substr( $line, $i, 1 );
$dec = ord( $c );
if ( ( $i == 0 ) && ( $dec == 46 ) ) // convert first point in the line into =2E
{
$c = "=2E";
}
if ( $dec == 32 )
{
if ( $i == ( $linlen - 1 ) ) // convert space at eol only
{
$c = "=20";
}
else if ( $space_conv )
{
$c = "=20";
}
}
else if ( ($dec == 61) || ($dec < 32 ) || ($dec > 126) ) // always encode "\t", which is *not* required
{
$h2 = floor($dec/16);
$h1 = floor($dec%16);
$c = $escape.$hex["$h2"].$hex["$h1"];
}
if ( (strlen($newline) + strlen($c)) >= $line_max ) // CRLF is not counted
{
$output .= $newline.$escape.$eol; // soft line break; " =\r\n" is okay
$newline = "";
// check if newline first character will be point or not
if ( $dec == 46 )
{
$c = "=2E";
}
}
$newline .= $c;
} // end of for
$output .= $newline.$eol;
} // end of while
return trim($output);
}
diff --git a/program/include/rcube_message.php b/program/include/rcube_message.php
index 10b9e8e41..d59c9448a 100644
--- a/program/include/rcube_message.php
+++ b/program/include/rcube_message.php
@@ -1,427 +1,427 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/include/rcube_message.php |
| |
| This file is part of the RoundCube Webmail client |
| Copyright (C) 2008, RoundCube Dev. - Switzerland |
| 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: rcube_imap.php 1344 2008-04-30 08:21:42Z thomasb $
*/
/**
* Logical representation of a mail message with all its data
* and related functions
*
* @package Mail
* @author Thomas Bruederli <roundcube@gmail.com>
*/
class rcube_message
{
private $app;
private $imap;
private $opt = array();
private $inline_parts = array();
private $parse_alternative = false;
public $uid = null;
public $headers;
public $structure;
public $parts = array();
public $mime_parts = array();
public $attachments = array();
public $subject = '';
public $sender = null;
public $is_safe = false;
function __construct($uid)
{
$this->app = rcmail::get_instance();
$this->imap = $this->app->imap;
$this->uid = $uid;
$this->headers = $this->imap->get_headers($uid);
$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->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 ($this->structure = $this->imap->get_structure($uid)) {
$this->get_mime_numbers($this->structure);
$this->parse_structure($this->structure);
}
else {
$this->body = $this->imap->get_body($uid);
}
}
/**
* Return a (decoded) message header
*
* @param string Header name
* @param bool Don't mime-decode the value
* @return string Header value
*/
public function get_header($name, $raw = false)
{
$value = $this->headers->$name;
return $raw ? $value : $this->imap->decode_header($value);
}
/**
* Set is_safe var and session data
*
* @param bool 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 Part MIME-ID
* @return string URL or false if part does not exist
*/
public function get_part_url($mime_id)
{
if ($this->mime_parts[$mime_id])
return $this->opt['get_url'] . "&_part=" . $mime_id;
else
return false;
}
/**
* Get content of a specific part of this message
*
* @param string Part MIME-ID
* @param resource 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])
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()
{
$html_part = null;
// check all message parts
foreach ($this->mime_parts as $mime_id => $part) {
$mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary);
if ($mimetype == 'text/html') {
$html_part = $this->imap->get_message_part($this->uid, $mime_id, $part);
}
}
return $html_part;
}
/**
* Return the first text part of this message
*
* @return string Plain text message/part content
*/
function first_text_part()
{
// no message structure, return complete body
if (empty($this->parts))
return $this->body;
$out = null;
// check all message parts
foreach ($this->mime_parts as $mime_id => $part) {
$mimetype = strtolower($part->ctype_primary . '/' . $part->ctype_secondary);
if ($mimetype == 'text/plain') {
$out = $this->imap->get_message_part($this->uid, $mime_id, $part);
break;
}
else if ($mimetype == 'text/html') {
$html_part = $this->imap->get_message_part($this->uid, $mime_id, $part);
// remove special chars encoding
$trans = array_flip(get_html_translation_table(HTML_ENTITIES));
$html_part = strtr($html_part, $trans);
// create instance of html2text class
$txt = new html2text($html_part);
$out = $txt->get_text();
break;
}
}
return $out;
}
/**
* Raad the message structure returend by the IMAP server
* and build flat lists of content parts and attachments
*
* @param object rcube_message_part Message structure node
* @param bool True when called recursively
*/
private function parse_structure($structure, $recursive = false)
{
$message_ctype_primary = strtolower($structure->ctype_primary);
$message_ctype_secondary = strtolower($structure->ctype_secondary);
// 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;
}
// print body if message doesn't have multiple parts
if ($message_ctype_primary == 'text' && !$recursive) {
$structure->type = 'content';
$this->parts[] = &$structure;
}
// the same for pgp signed messages
else if ($message_ctype_primary == 'application' && $message_ctype_secondary == 'pgp' && !$recursive) {
$structure->type = 'content';
$this->parts[] = &$structure;
}
// message contains alternative parts
else if ($message_ctype_primary == 'multipart' && ($message_ctype_secondary == 'alternative') && is_array($structure->parts)) {
// get html/plaintext parts
$plain_part = $html_part = $print_part = $related_part = null;
foreach ($structure->parts as $p => $sub_part) {
$rel_parts = $attachmnts = null;
$sub_ctype_primary = strtolower($sub_part->ctype_primary);
$sub_ctype_secondary = strtolower($sub_part->ctype_secondary);
// check if sub part is
if ($sub_ctype_primary=='text' && $sub_ctype_secondary=='plain')
$plain_part = $p;
else if ($sub_ctype_primary=='text' && $sub_ctype_secondary=='html')
$html_part = $p;
else if ($sub_ctype_primary=='text' && $sub_ctype_secondary=='enriched')
$enriched_part = $p;
else if ($sub_ctype_primary=='multipart' && ($sub_ctype_secondary=='related' || $sub_ctype_secondary=='mixed'))
$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->body = rcube_label('htmlmessage');
$c->ctype_primary = 'text';
$c->ctype_secondary = 'plain';
$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 ($message_ctype_primary == 'multipart' && $message_ctype_secondary == 'encrypted') {
$p = new stdClass;
$p->type = 'content';
$p->ctype_primary = 'text';
$p->ctype_secondary = 'plain';
$p->body = rcube_label('encryptedmessage');
$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 = strtolower($mail_part->ctype_primary);
$secondary_type = strtolower($mail_part->ctype_secondary);
// multipart/alternative
if ($primary_type=='multipart') {
$this->parse_structure($mail_part, true);
}
// part text/[plain|html] OR message/delivery-status
else if (($primary_type == 'text' && ($secondary_type == 'plain' || $secondary_type == 'html') && $mail_part->disposition != 'attachment') ||
($primary_type == 'message' && ($secondary_type == 'delivery-status' || $secondary_type == 'disposition-notification'))) {
// add text part if we're not in alternative mode or 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 file/attachment
else if ($mail_part->disposition == 'attachment' || $mail_part->disposition == 'inline' ||
$mail_part->headers['content-id'] || (empty($mail_part->disposition) && $mail_part->filename)) {
// skip apple resource forks
if ($message_ctype_secondary == 'appledouble' && $secondary_type == 'applefile')
continue;
// part belongs to a related message
if ($message_ctype_secondary == 'related' && $mail_part->headers['content-id']) {
$mail_part->content_id = preg_replace(array('/^</', '/>$/'), '', $mail_part->headers['content-id']);
$this->inline_parts[] = $mail_part;
}
else if ($message_ctype_secondary == 'related' && $mail_part->headers['content-location']) {
$mail_part->content_location = $mail_part->headers['content-base'] . $mail_part->headers['content-location'];
$this->inline_parts[] = $mail_part;
}
// is regular attachment
else {
if (!$mail_part->filename)
$mail_part->filename = 'Part '.$mail_part->mime_id;
$this->attachments[] = $mail_part;
}
}
}
// if this was a related part try to resolve references
if ($message_ctype_secondary == 'related' && sizeof($this->inline_parts)) {
$a_replaces = array();
foreach ($this->inline_parts as $inline_object) {
$part_url = $this->get_part_url($inline_object->mime_id);
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;
}
// 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 single part non-text
else if ($structure->filename) {
$this->attachments[] = $structure;
}
}
/**
* Fill aflat array with references to all parts, indexed by part numbers
*
* @param object rcube_message_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]);
}
}
diff --git a/program/lib/Mail/mime.php b/program/lib/Mail/mime.php
index 688c326a5..975ca48ce 100644
--- a/program/lib/Mail/mime.php
+++ b/program/lib/Mail/mime.php
@@ -1,1151 +1,1150 @@
<?php
/**
* The Mail_Mime class is used to create MIME E-mail messages
*
* The Mail_Mime class provides an OO interface to create MIME
* enabled email messages. This way you can create emails that
* contain plain-text bodies, HTML bodies, attachments, inline
* images and specific headers.
*
* Compatible with PHP versions 4 and 5
*
* LICENSE: This LICENSE is in the BSD license style.
* Copyright (c) 2002-2003, Richard Heyes <richard@phpguru.org>
* Copyright (c) 2003-2006, PEAR <pear-group@php.net>
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the following
* conditions are met:
*
* - Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* - Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* - Neither the name of the authors, nor the names of its contributors
* may be used to endorse or promote products derived from this
* software without specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF
* THE POSSIBILITY OF SUCH DAMAGE.
*
* @category Mail
* @package Mail_Mime
* @author Richard Heyes <richard@phpguru.org>
* @author Tomas V.V. Cox <cox@idecnet.com>
* @author Cipriano Groenendal <cipri@php.net>
* @author Sean Coates <sean@php.net>
* @copyright 2003-2006 PEAR <pear-group@php.net>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @version CVS: $Id$
* @link http://pear.php.net/package/Mail_mime
*
* This class is based on HTML Mime Mail class from
* Richard Heyes <richard@phpguru.org> which was based also
* in the mime_mail.class by Tobias Ratschiller <tobias@dnet.it>
* and Sascha Schumann <sascha@schumann.cx>
*/
/**
* require PEAR
*
* This package depends on PEAR to raise errors.
*/
require_once 'PEAR.php';
/**
* require Mail_mimePart
*
* Mail_mimePart contains the code required to
* create all the different parts a mail can
* consist of.
*/
require_once 'Mail/mimePart.php';
/**
* The Mail_Mime class provides an OO interface to create MIME
* enabled email messages. This way you can create emails that
* contain plain-text bodies, HTML bodies, attachments, inline
* images and specific headers.
*
* @category Mail
* @package Mail_Mime
* @author Richard Heyes <richard@phpguru.org>
* @author Tomas V.V. Cox <cox@idecnet.com>
* @author Cipriano Groenendal <cipri@php.net>
* @author Sean Coates <sean@php.net>
* @copyright 2003-2006 PEAR <pear-group@php.net>
* @license http://www.opensource.org/licenses/bsd-license.php BSD License
* @version Release: @package_version@
* @link http://pear.php.net/package/Mail_mime
*/
class Mail_mime
{
/**
* Contains the plain text part of the email
*
* @var string
* @access private
*/
var $_txtbody;
/**
* Contains the html part of the email
*
* @var string
* @access private
*/
var $_htmlbody;
/**
* contains the mime encoded text
*
* @var string
* @access private
*/
var $_mime;
/**
* contains the multipart content
*
* @var string
* @access private
*/
var $_multipart;
/**
* list of the attached images
*
* @var array
* @access private
*/
var $_html_images = array();
/**
* list of the attachements
*
* @var array
* @access private
*/
var $_parts = array();
/**
* Build parameters
*
* @var array
* @access private
*/
var $_build_params = array();
/**
* Headers for the mail
*
* @var array
* @access private
*/
var $_headers = array();
/**
* End Of Line sequence (for serialize)
*
* @var string
* @access private
*/
var $_eol;
/**
* Constructor function.
*
* @param string $crlf what type of linebreak to use.
* Defaults to "\r\n"
*
* @return void
*
* @access public
*/
function Mail_mime($crlf = "\r\n")
{
$this->_setEOL($crlf);
$this->_build_params = array(
'head_encoding' => 'quoted-printable',
'text_encoding' => '7bit',
'html_encoding' => 'quoted-printable',
'7bit_wrap' => 998,
'html_charset' => 'ISO-8859-1',
'text_charset' => 'ISO-8859-1',
'head_charset' => 'ISO-8859-1'
);
}
/**
* wakeup function called by unserialize. It re-sets the EOL constant
*
* @access private
* @return void
*/
function __wakeup()
{
$this->_setEOL($this->_eol);
}
/**
* Accessor function to set the body text. Body text is used if
* it's not an html mail being sent or else is used to fill the
* text/plain part that emails clients who don't support
* html should show.
*
* @param string $data Either a string or
* the file name with the contents
* @param bool $isfile If true the first param should be treated
* as a file name, else as a string (default)
* @param bool $append If true the text or file is appended to
* the existing body, else the old body is
* overwritten
*
* @return mixed true on success or PEAR_Error object
* @access public
*/
function setTXTBody($data, $isfile = false, $append = false)
{
if (!$isfile) {
if (!$append) {
$this->_txtbody = $data;
} else {
$this->_txtbody .= $data;
}
} else {
$cont = $this->_file2str($data);
if (PEAR::isError($cont)) {
return $cont;
}
if (!$append) {
$this->_txtbody = $cont;
} else {
$this->_txtbody .= $cont;
}
}
return true;
}
/**
* Adds a html part to the mail.
*
* @param string $data either a string or the file name with the
* contents
* @param bool $isfile a flag that determines whether $data is a
* filename, or a string(false, default)
*
* @return bool true on success
* @access public
*/
function setHTMLBody($data, $isfile = false)
{
if (!$isfile) {
$this->_htmlbody = $data;
} else {
$cont = $this->_file2str($data);
if (PEAR::isError($cont)) {
return $cont;
}
$this->_htmlbody = $cont;
}
return true;
}
/**
* Adds an image to the list of embedded images.
*
* @param string $file the image file name OR image data itself
* @param string $c_type the content type
* @param string $name the filename of the image.
* Only used if $file is the image data.
* @param bool $isfile whether $file is a filename or not.
* Defaults to true
*
* @return bool true on success
* @access public
*/
function addHTMLImage($file, $c_type='application/octet-stream',
$name = '', $isfile = true)
{
$filedata = ($isfile === true) ? $this->_file2str($file)
: $file;
if ($isfile === true) {
$filename = ($name == '' ? $file : $name);
} else {
$filename = $name;
}
if (PEAR::isError($filedata)) {
return $filedata;
}
$this->_html_images[] = array(
'body' => $filedata,
'name' => $filename,
'c_type' => $c_type,
'cid' => md5(uniqid(time()))
);
return true;
}
/**
* Adds a file to the list of attachments.
*
* @param string $file The file name of the file to attach
* OR the file contents itself
* @param string $c_type The content type
* @param string $name The filename of the attachment
* Only use if $file is the contents
* @param bool $isfile Whether $file is a filename or not
* Defaults to true
* @param string $encoding The type of encoding to use.
* Defaults to base64.
* Possible values: 7bit, 8bit, base64,
* or quoted-printable.
* @param string $disposition The content-disposition of this file
* Defaults to attachment.
* Possible values: attachment, inline.
* @param string $charset The character set used in the filename
* of this attachment.
* @param string $language The language of the attachment
* @param string $location The RFC 2557.4 location of the attachment
* @param string $n_encoding Use RFC 2047 for attachment name (Content-Type) encoding
* @param string $f_encoding Use RFC 2047 for attachment filename (Content-Disposition) encoding
*
* @return mixed true on success or PEAR_Error object
* @access public
*/
function addAttachment($file,
$c_type = 'application/octet-stream',
$name = '',
$isfile = true,
$encoding = 'base64',
$disposition = 'attachment',
$charset = '',
$language = '',
$location = '',
$n_encoding = NULL,
$f_encoding = NULL)
{
$filedata = ($isfile === true) ? $this->_file2str($file)
: $file;
if ($isfile === true) {
// Force the name the user supplied, otherwise use $file
$filename = (strlen($name)) ? $name : $file;
} else {
$filename = $name;
}
if (!strlen($filename)) {
$msg = "The supplied filename for the attachment can't be empty";
$err = PEAR::raiseError($msg);
return $err;
}
$filename = $this->_basename($filename);
if (PEAR::isError($filedata)) {
return $filedata;
}
$this->_parts[] = array(
'body' => $filedata,
'name' => $filename,
'c_type' => $c_type,
'encoding' => $encoding,
'charset' => $charset,
'language' => $language,
'location' => $location,
'disposition' => $disposition,
'name-encoding' => $n_encoding,
'filename-encoding'=> $f_encoding
);
return true;
}
/**
* Get the contents of the given file name as string
*
* @param string $file_name path of file to process
*
* @return string contents of $file_name
* @access private
*/
function &_file2str($file_name)
{
//Check state of file and raise an error properly
if (!file_exists($file_name)) {
$err = PEAR::raiseError('File not found: ' . $file_name);
return $err;
}
if (!is_file($file_name)) {
$err = PEAR::raiseError('Not a regular file: ' . $file_name);
return $err;
}
if (!is_readable($file_name)) {
$err = PEAR::raiseError('File is not readable: ' . $file_name);
return $err;
}
//Temporarily reset magic_quotes_runtime and read file contents
if ($magic_quote_setting = get_magic_quotes_runtime()) {
set_magic_quotes_runtime(0);
}
$cont = file_get_contents($file_name);
if ($magic_quote_setting) {
set_magic_quotes_runtime($magic_quote_setting);
}
return $cont;
}
/**
* Adds a text subpart to the mimePart object and
* returns it during the build process.
*
* @param mixed &$obj The object to add the part to, or
* null if a new object is to be created.
* @param string $text The text to add.
*
* @return object The text mimePart object
* @access private
*/
function &_addTextPart(&$obj, $text)
{
$params['content_type'] = 'text/plain';
$params['encoding'] = $this->_build_params['text_encoding'];
$params['charset'] = $this->_build_params['text_charset'];
if (is_object($obj)) {
$ret = $obj->addSubpart($text, $params);
return $ret;
} else {
$ret = new Mail_mimePart($text, $params);
return $ret;
}
}
/**
* Adds a html subpart to the mimePart object and
* returns it during the build process.
*
* @param mixed &$obj The object to add the part to, or
* null if a new object is to be created.
*
* @return object The html mimePart object
* @access private
*/
function &_addHtmlPart(&$obj)
{
$params['content_type'] = 'text/html';
$params['encoding'] = $this->_build_params['html_encoding'];
$params['charset'] = $this->_build_params['html_charset'];
if (is_object($obj)) {
$ret = $obj->addSubpart($this->_htmlbody, $params);
return $ret;
} else {
$ret = new Mail_mimePart($this->_htmlbody, $params);
return $ret;
}
}
/**
* Creates a new mimePart object, using multipart/mixed as
* the initial content-type and returns it during the
* build process.
*
* @return object The multipart/mixed mimePart object
* @access private
*/
function &_addMixedPart()
{
$params = array();
$params['content_type'] = 'multipart/mixed';
//Create empty multipart/mixed Mail_mimePart object to return
$ret = new Mail_mimePart('', $params);
return $ret;
}
/**
* Adds a multipart/alternative part to a mimePart
* object (or creates one), and returns it during
* the build process.
*
* @param mixed &$obj The object to add the part to, or
* null if a new object is to be created.
*
* @return object The multipart/mixed mimePart object
* @access private
*/
function &_addAlternativePart(&$obj)
{
$params['content_type'] = 'multipart/alternative';
if (is_object($obj)) {
return $obj->addSubpart('', $params);
} else {
$ret = new Mail_mimePart('', $params);
return $ret;
}
}
/**
* Adds a multipart/related part to a mimePart
* object (or creates one), and returns it during
* the build process.
*
* @param mixed &$obj The object to add the part to, or
* null if a new object is to be created
*
* @return object The multipart/mixed mimePart object
* @access private
*/
function &_addRelatedPart(&$obj)
{
$params['content_type'] = 'multipart/related';
if (is_object($obj)) {
return $obj->addSubpart('', $params);
} else {
$ret = new Mail_mimePart('', $params);
return $ret;
}
}
/**
* Adds an html image subpart to a mimePart object
* and returns it during the build process.
*
* @param object &$obj The mimePart to add the image to
* @param array $value The image information
*
* @return object The image mimePart object
* @access private
*/
function &_addHtmlImagePart(&$obj, $value)
{
$params['content_type'] = $value['c_type'];
$params['encoding'] = 'base64';
$params['disposition'] = 'inline';
$params['dfilename'] = $value['name'];
$params['cid'] = $value['cid'];
if ($value['name-encoding']) {
$params['name-encoding'] = $value['name-encoding'];
}
if ($value['filename-encoding']) {
$params['filename-encoding'] = $value['filename-encoding'];
}
$ret = $obj->addSubpart($value['body'], $params);
return $ret;
}
/**
* Adds an attachment subpart to a mimePart object
* and returns it during the build process.
*
* @param object &$obj The mimePart to add the image to
* @param array $value The attachment information
*
* @return object The image mimePart object
* @access private
*/
function &_addAttachmentPart(&$obj, $value)
{
$params['dfilename'] = $value['name'];
$params['encoding'] = $value['encoding'];
if ($value['charset']) {
$params['charset'] = $value['charset'];
}
if ($value['language']) {
$params['language'] = $value['language'];
}
if ($value['location']) {
$params['location'] = $value['location'];
}
if ($value['name-encoding']) {
$params['name-encoding'] = $value['name-encoding'];
}
if ($value['filename-encoding']) {
$params['filename-encoding'] = $value['filename-encoding'];
}
$params['content_type'] = $value['c_type'];
$params['disposition'] = isset($value['disposition']) ?
$value['disposition'] : 'attachment';
$ret = $obj->addSubpart($value['body'], $params);
return $ret;
}
/**
* Returns the complete e-mail, ready to send using an alternative
* mail delivery method. Note that only the mailpart that is made
* with Mail_Mime is created. This means that,
* YOU WILL HAVE NO TO: HEADERS UNLESS YOU SET IT YOURSELF
* using the $xtra_headers parameter!
*
* @param string $separation The separation etween these two parts.
* @param array $build_params The Build parameters passed to the
* &get() function. See &get for more info.
* @param array $xtra_headers The extra headers that should be passed
* to the &headers() function.
* See that function for more info.
* @param bool $overwrite Overwrite the existing headers with new.
*
* @return string The complete e-mail.
* @access public
*/
function getMessage(
$separation = null,
$build_params = null,
$xtra_headers = null,
$overwrite = false
)
{
if ($separation === null) {
$separation = MAIL_MIME_CRLF;
}
$body = $this->get($build_params);
$head = $this->txtHeaders($xtra_headers, $overwrite);
$mail = $head . $separation . $body;
return $mail;
}
/**
* Builds the multipart message from the list ($this->_parts) and
* returns the mime content.
*
* @param array $build_params Build parameters that change the way the email
* is built. Should be associative. Can contain:
* head_encoding - What encoding to use for the headers.
* Options: quoted-printable or base64
* Default is quoted-printable
* text_encoding - What encoding to use for plain text
* Options: 7bit, 8bit,
* base64, or quoted-printable
* Default is 7bit
* html_encoding - What encoding to use for html
* Options: 7bit, 8bit,
* base64, or quoted-printable
* Default is quoted-printable
* 7bit_wrap - Number of characters before text is
* wrapped in 7bit encoding
* Default is 998
* html_charset - The character set to use for html.
* Default is iso-8859-1
* text_charset - The character set to use for text.
* Default is iso-8859-1
* head_charset - The character set to use for headers.
* Default is iso-8859-1
*
* @return string The mime content
* @access public
*/
function &get($build_params = null)
{
if (isset($build_params)) {
while (list($key, $value) = each($build_params)) {
$this->_build_params[$key] = $value;
}
}
if (isset($this->_headers['From'])){
//Bug #11381: Illegal characters in domain ID
if (preg_match("|(@[0-9a-zA-Z\-\.]+)|", $this->_headers['From'], $matches)){
$domainID = $matches[1];
}else{
$domainID = "@localhost";
}
foreach($this->_html_images as $i => $img){
$this->_html_images[$i]['cid'] = $this->_html_images[$i]['cid'] . $domainID;
}
}
if (count($this->_html_images) AND isset($this->_htmlbody)) {
foreach ($this->_html_images as $key => $value) {
$regex = array();
$regex[] = '#(\s)((?i)src|background|href(?-i))\s*=\s*(["\']?)' .
preg_quote($value['name'], '#') . '\3#';
$regex[] = '#(?i)url(?-i)\(\s*(["\']?)' .
preg_quote($value['name'], '#') . '\1\s*\)#';
$rep = array();
$rep[] = '\1\2=\3cid:' . $value['cid'] .'\3';
$rep[] = 'url(\1cid:' . $value['cid'] . '\1)';
$this->_htmlbody = preg_replace($regex, $rep, $this->_htmlbody);
$this->_html_images[$key]['name'] =
$this->_basename($this->_html_images[$key]['name']);
}
}
$null = null;
$attachments = count($this->_parts) ? true : false;
$html_images = count($this->_html_images) ? true : false;
$html = strlen($this->_htmlbody) ? true : false;
$text = (!$html AND strlen($this->_txtbody)) ? true : false;
switch (true) {
case $text AND !$attachments:
$message =& $this->_addTextPart($null, $this->_txtbody);
break;
case !$text AND !$html AND $attachments:
$message =& $this->_addMixedPart();
for ($i = 0; $i < count($this->_parts); $i++) {
$this->_addAttachmentPart($message, $this->_parts[$i]);
}
break;
case $text AND $attachments:
$message =& $this->_addMixedPart();
$this->_addTextPart($message, $this->_txtbody);
for ($i = 0; $i < count($this->_parts); $i++) {
$this->_addAttachmentPart($message, $this->_parts[$i]);
}
break;
case $html AND !$attachments AND !$html_images:
if (isset($this->_txtbody)) {
$message =& $this->_addAlternativePart($null);
$this->_addTextPart($message, $this->_txtbody);
$this->_addHtmlPart($message);
} else {
$message =& $this->_addHtmlPart($null);
}
break;
case $html AND !$attachments AND $html_images:
$message =& $this->_addRelatedPart($null);
if (isset($this->_txtbody)) {
$alt =& $this->_addAlternativePart($message);
$this->_addTextPart($alt, $this->_txtbody);
$this->_addHtmlPart($alt);
} else {
$this->_addHtmlPart($message);
}
for ($i = 0; $i < count($this->_html_images); $i++) {
$this->_addHtmlImagePart($message, $this->_html_images[$i]);
}
break;
case $html AND $attachments AND !$html_images:
$message =& $this->_addMixedPart();
if (isset($this->_txtbody)) {
$alt =& $this->_addAlternativePart($message);
$this->_addTextPart($alt, $this->_txtbody);
$this->_addHtmlPart($alt);
} else {
$this->_addHtmlPart($message);
}
for ($i = 0; $i < count($this->_parts); $i++) {
$this->_addAttachmentPart($message, $this->_parts[$i]);
}
break;
case $html AND $attachments AND $html_images:
$message =& $this->_addMixedPart();
if (isset($this->_txtbody)) {
$alt =& $this->_addAlternativePart($message);
$this->_addTextPart($alt, $this->_txtbody);
$rel =& $this->_addRelatedPart($alt);
} else {
$rel =& $this->_addRelatedPart($message);
}
$this->_addHtmlPart($rel);
for ($i = 0; $i < count($this->_html_images); $i++) {
$this->_addHtmlImagePart($rel, $this->_html_images[$i]);
}
for ($i = 0; $i < count($this->_parts); $i++) {
$this->_addAttachmentPart($message, $this->_parts[$i]);
}
break;
}
if (isset($message)) {
$output = $message->encode();
$this->_headers = array_merge($this->_headers,
$output['headers']);
$body = $output['body'];
return $body;
} else {
$ret = false;
return $ret;
}
}
/**
* Returns an array with the headers needed to prepend to the email
* (MIME-Version and Content-Type). Format of argument is:
* $array['header-name'] = 'header-value';
*
* @param array $xtra_headers Assoc array with any extra headers.
* Optional.
* @param bool $overwrite Overwrite already existing headers.
*
* @return array Assoc array with the mime headers
* @access public
*/
function &headers($xtra_headers = null, $overwrite = false)
{
// Content-Type header should already be present,
// So just add mime version header
$headers['MIME-Version'] = '1.0';
if (isset($xtra_headers)) {
$headers = array_merge($headers, $xtra_headers);
}
if ($overwrite) {
$this->_headers = array_merge($this->_headers, $headers);
} else {
$this->_headers = array_merge($headers, $this->_headers);
}
$encodedHeaders = $this->_encodeHeaders($this->_headers);
return $encodedHeaders;
}
/**
* Get the text version of the headers
* (usefull if you want to use the PHP mail() function)
*
* @param array $xtra_headers Assoc array with any extra headers.
* Optional.
* @param bool $overwrite Overwrite the existing heaers with new.
*
* @return string Plain text headers
* @access public
*/
function txtHeaders($xtra_headers = null, $overwrite = false)
{
$headers = $this->headers($xtra_headers, $overwrite);
$ret = '';
foreach ($headers as $key => $val) {
$ret .= "$key: $val" . MAIL_MIME_CRLF;
}
return $ret;
}
/**
* Sets the Subject header
*
* @param string $subject String to set the subject to.
*
* @return void
* @access public
*/
function setSubject($subject)
{
$this->_headers['Subject'] = $subject;
}
/**
* Set an email to the From (the sender) header
*
* @param string $email The email address to use
*
* @return void
* @access public
*/
function setFrom($email)
{
$this->_headers['From'] = $email;
}
/**
* Add an email to the Cc (carbon copy) header
* (multiple calls to this method are allowed)
*
* @param string $email The email direction to add
*
* @return void
* @access public
*/
function addCc($email)
{
if (isset($this->_headers['Cc'])) {
$this->_headers['Cc'] .= ", $email";
} else {
$this->_headers['Cc'] = $email;
}
}
/**
* Add an email to the Bcc (blank carbon copy) header
* (multiple calls to this method are allowed)
*
* @param string $email The email direction to add
*
* @return void
* @access public
*/
function addBcc($email)
{
if (isset($this->_headers['Bcc'])) {
$this->_headers['Bcc'] .= ", $email";
} else {
$this->_headers['Bcc'] = $email;
}
}
/**
* Since the PHP send function requires you to specifiy
* recipients (To: header) separately from the other
* headers, the To: header is not properly encoded.
* To fix this, you can use this public method to
* encode your recipients before sending to the send
* function
*
* @param string $recipients A comma-delimited list of recipients
*
* @return string Encoded data
* @access public
*/
function encodeRecipients($recipients)
{
$input = array("To" => $recipients);
$retval = $this->_encodeHeaders($input);
return $retval["To"] ;
}
/**
* Encodes a header as per RFC2047
*
* @param array $input The header data to encode
* @param array $params Extra build parameters
*
* @return array Encoded data
* @access private
*/
function _encodeHeaders($input, $params = array())
{
$build_params = $this->_build_params;
while (list($key, $value) = each($params)) {
$build_params[$key] = $value;
}
//$hdr_name: Name of the heaer
//$hdr_value: Full line of header value.
//$atoms: The $hdr_value split into atoms*
//$atom: A single atom to encode.*
//$hdr_value_out: The recombined $hdr_val-atoms, or the encoded string.
//Note: Atom as specified here is not exactly the same as an RFC822 atom,
//as $atom's may contain just a single space.
$useIconv = true;
if (isset($build_params['ignore-iconv'])) {
$useIconv = !$build_params['ignore-iconv'];
}
foreach ($input as $hdr_name => $hdr_value) {
/*
$parts = preg_split('/([ ])/', $hdr_value, -1, PREG_SPLIT_DELIM_CAPTURE);
$atoms = array();
foreach ($parts as $part){
$atom .= $part;
$quoteMatch = preg_match_all('|"|', $atom, $matches) % 2;
if (!$quoteMatch){
$atoms[] = $atom;
$atom = null;
}
}
if ($atom){
$atoms[] = $atom;
}
foreach ($atoms as $atom){
*/
if (preg_match('#([\x80-\xFF]){1}#', $hdr_value)) {
if (function_exists('iconv_mime_encode') && $useIconv) {
$imePrefs = array();
if ($build_params['head_encoding'] == 'base64') {
$imePrefs['scheme'] = 'B';
} else {
$imePrefs['scheme'] = 'Q';
}
$imePrefs['input-charset'] = $build_params['head_charset'];
$imePrefs['output-charset'] = $build_params['head_charset'];
$imePrefs['line-length'] = 74;
$imePrefs['line-break-chars'] = "\r\n"; //Specified in RFC2047
$hdr_value = iconv_mime_encode($hdr_name, $hdr_value, $imePrefs);
$hdr_value = preg_replace("#^{$hdr_name}\:\ #", "", $hdr_value);
} elseif ($build_params['head_encoding'] == 'base64') {
//Base64 encoding has been selected.
//Base64 encode the entire string
$hdr_value = base64_encode($hdr_value);
//Generate the header using the specified params and dynamicly
//determine the maximum length of such strings.
//75 is the value specified in the RFC. The first -2 is there so
//the later regexp doesn't break any of the translated chars.
//The -2 on the first line-regexp is to compensate for the ": "
//between the header-name and the header value
$prefix = '=?' . $build_params['head_charset'] . '?B?';
$suffix = '?=';
$maxLength = 75 - strlen($prefix . $suffix) - 2;
$maxLength1stLine = $maxLength - strlen($hdr_name) - 2;
//We can cut base4 every 4 characters, so the real max
//we can get must be rounded down.
$maxLength = $maxLength - ($maxLength % 4);
$maxLength1stLine = $maxLength1stLine - ($maxLength1stLine % 4);
$cutpoint = $maxLength1stLine;
$hdr_value_out = $hdr_value;
$output = "";
while ($hdr_value_out) {
//Split translated string at every $maxLength
$part = substr($hdr_value_out, 0, $cutpoint);
$hdr_value_out = substr($hdr_value_out, $cutpoint);
$cutpoint = $maxLength;
//RFC 2047 specifies that any split header should
//be seperated by a CRLF SPACE.
if ($output) {
$output .= "\r\n ";
}
$output .= $prefix . $part . $suffix;
}
$hdr_value = $output;
} else {
//quoted-printable encoding has been selected
//Fix for Bug #10298, Ota Mares <om@viazenetti.de>
//Check if there is a double quote at beginning or end of
//the string to prevent that an open or closing quote gets
//ignored because it is encapsuled by an encoding pre/suffix.
//Remove the double quote and set the specific prefix or
//suffix variable so that we can concat the encoded string and
//the double quotes back together to get the intended string.
$quotePrefix = $quoteSuffix = '';
if ($hdr_value{0} == '"') {
$hdr_value = substr($hdr_value, 1);
$quotePrefix = '"';
}
if ($hdr_value{strlen($hdr_value)-1} == '"') {
$hdr_value = substr($hdr_value, 0, -1);
$quoteSuffix = '"';
}
//Generate the header using the specified params and dynamicly
//determine the maximum length of such strings.
//75 is the value specified in the RFC. The -2 is there so
//the later regexp doesn't break any of the translated chars.
//The -2 on the first line-regexp is to compensate for the ": "
//between the header-name and the header value
$prefix = '=?' . $build_params['head_charset'] . '?Q?';
$suffix = '?=';
$maxLength = 75 - strlen($prefix . $suffix) - 2 - 1;
$maxLength1stLine = $maxLength - strlen($hdr_name) - 2;
$maxLength = $maxLength - 1;
//Replace all special characters used by the encoder.
$search = array('=', '_', '?', ' ');
$replace = array('=3D', '=5F', '=3F', '_');
$hdr_value = str_replace($search, $replace, $hdr_value);
//Replace all extended characters (\x80-xFF) with their
//ASCII values.
$hdr_value = preg_replace('#([\x80-\xFF])#e',
'"=" . strtoupper(dechex(ord("\1")))',
$hdr_value);
//This regexp will break QP-encoded text at every $maxLength
//but will not break any encoded letters.
$reg1st = "|(.{0,$maxLength1stLine}[^\=][^\=])|";
$reg2nd = "|(.{0,$maxLength}[^\=][^\=])|";
//Fix for Bug #10298, Ota Mares <om@viazenetti.de>
//Concat the double quotes and encoded string together
$hdr_value = $quotePrefix . $hdr_value . $quoteSuffix;
-
$hdr_value_out = $hdr_value;
$realMax = $maxLength1stLine + strlen($prefix . $suffix);
if (strlen($hdr_value_out) >= $realMax) {
//Begin with the regexp for the first line.
$reg = $reg1st;
$output = "";
while ($hdr_value_out) {
//Split translated string at every $maxLength
//But make sure not to break any translated chars.
$found = preg_match($reg, $hdr_value_out, $matches);
//After this first line, we need to use a different
//regexp for the first line.
$reg = $reg2nd;
//Save the found part and encapsulate it in the
//prefix & suffix. Then remove the part from the
//$hdr_value_out variable.
if ($found) {
$part = $matches[0];
$len = strlen($matches[0]);
$hdr_value_out = substr($hdr_value_out, $len);
} else {
$part = $hdr_value_out;
$hdr_value_out = "";
}
//RFC 2047 specifies that any split header should
//be seperated by a CRLF SPACE
if ($output) {
$output .= "\r\n ";
}
$output .= $prefix . $part . $suffix;
}
$hdr_value_out = $output;
} else {
$hdr_value_out = $prefix . $hdr_value_out . $suffix;
}
$hdr_value = $hdr_value_out;
}
}
$input[$hdr_name] = $hdr_value;
}
return $input;
}
/**
* Set the object's end-of-line and define the constant if applicable.
*
* @param string $eol End Of Line sequence
*
* @return void
* @access private
*/
function _setEOL($eol)
{
$this->_eol = $eol;
if (!defined('MAIL_MIME_CRLF')) {
define('MAIL_MIME_CRLF', $this->_eol, true);
}
}
/**
* Get file's basename (locale independent)
*
* @param string Filename
*
* @return string Basename
* @access private
*/
function _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);
}
} // End of class
diff --git a/program/steps/mail/show.inc b/program/steps/mail/show.inc
index 4f5253883..4f0a3b032 100644
--- a/program/steps/mail/show.inc
+++ b/program/steps/mail/show.inc
@@ -1,223 +1,223 @@
<?php
/*
+-----------------------------------------------------------------------+
| program/steps/mail/show.inc |
| |
| This file is part of the RoundCube Webmail client |
| Copyright (C) 2005-2008, RoundCube Dev. - Switzerland |
| Licensed under the GNU GPL |
| |
| PURPOSE: |
| Display a mail message similar as a usual mail application does |
| |
+-----------------------------------------------------------------------+
| Author: Thomas Bruederli <roundcube@gmail.com> |
+-----------------------------------------------------------------------+
$Id$
*/
$PRINT_MODE = $RCMAIL->action=='print' ? TRUE : FALSE;
// similar code as in program/steps/mail/get.inc
if ($_GET['_uid']) {
$MESSAGE = new rcube_message(get_input_value('_uid', RCUBE_INPUT_GET));
-
+
// set message charset as default
if (!empty($MESSAGE->headers->charset))
$IMAP->set_charset($MESSAGE->headers->charset);
// go back to list if message not found (wrong UID)
if (empty($MESSAGE->headers)) {
$OUTPUT->show_message('messageopenerror', 'error');
if ($RCMAIL->action=='preview' && $OUTPUT->template_exists('messagepreview'))
$OUTPUT->send('messagepreview');
else {
rcmail_overwrite_action('');
return;
}
}
$mbox_name = $IMAP->get_mailbox_name();
// check known senders to display images
if (!$MESSAGE->is_safe
&& !empty($MESSAGE->sender['mailto'])
&& $RCMAIL->config->get('addrbook_show_images')
&& $MESSAGE->has_html_part()) {
$CONTACTS = new rcube_contacts($DB, $_SESSION['user_id']);
if ($CONTACTS->search('email', $MESSAGE->sender['mailto'], true, false)->count) {
$MESSAGE->set_safe(true);
}
}
// calculate Etag for this request
$etag = md5($MESSAGE->uid.$mbox_name.session_id()
.intval($MESSAGE->headers->mdn_sent)
.intval($MESSAGE->is_safe)
.(!empty($MESSAGE->attachments) ? intval($CONFIG['inline_images']) : '')
.intval($PRINT_MODE));
// allow caching, unless remote images are present
if ((bool)$MESSAGE->is_safe)
send_nocacheing_headers();
else if (empty($CONFIG['devel_mode']))
send_modified_header($_SESSION['login_time'], $etag, !$MESSAGE->headers->seen);
$OUTPUT->set_pagetitle($MESSAGE->subject);
// mark message as read
if (!$MESSAGE->headers->seen)
$IMAP->set_flag($MESSAGE->uid, 'SEEN');
// give message uid to the client
$OUTPUT->set_env('uid', $MESSAGE->uid);
// set environement
$OUTPUT->set_env('safemode', $MESSAGE->is_safe);
$OUTPUT->set_env('sender', $MESSAGE->sender['string']);
$OUTPUT->set_env('permaurl', rcmail_url('show', array('_uid' => $MESSAGE->uid, '_mbox' => $mbox_name)));
$OUTPUT->set_env('mailbox', $mbox_name);
if ($CONFIG['trash_mbox'])
$OUTPUT->set_env('trash_mailbox', $CONFIG['trash_mbox']);
if (!$OUTPUT->ajax_call)
$OUTPUT->add_label('checkingmail', 'deletemessage', 'movemessagetotrash', 'movingmessage');
// check for unset disposition notification
if ($MESSAGE->headers->mdn_to &&
!$MESSAGE->headers->mdn_sent &&
$IMAP->check_permflag('MDNSENT') &&
$mbox_name != $CONFIG['drafts_mbox'] &&
$mbox_name != $CONFIG['sent_mbox'])
{
if (intval($CONFIG['mdn_requests']) === 1)
{
if (rcmail_send_mdn($MESSAGE->uid))
$OUTPUT->show_message('receiptsent', 'confirmation');
else
$OUTPUT->show_message('errorsendingreceipt', 'error');
}
else if (empty($CONFIG['mdn_requests']))
{
$OUTPUT->add_label('mdnrequest');
$OUTPUT->set_env('mdn_request', true);
}
}
$next = $prev = $first = $last = -1;
// get previous, first, next and last message UID
if ((!($_SESSION['sort_col'] == 'date' && $_SESSION['sort_order'] == 'DESC') &&
$IMAP->get_capability('sort')) || !empty($_REQUEST['_search']))
{
// Only if we use custom sorting
$a_msg_index = $IMAP->message_index(NULL, $_SESSION['sort_col'], $_SESSION['sort_order']);
$MESSAGE->index = array_search((string)$MESSAGE->uid, $a_msg_index, TRUE);
$prev = isset($a_msg_index[$MESSAGE->index-1]) ? $a_msg_index[$MESSAGE->index-1] : -1 ;
$first = count($a_msg_index)>0 ? $a_msg_index[0] : -1;
$next = isset($a_msg_index[$MESSAGE->index+1]) ? $a_msg_index[$MESSAGE->index+1] : -1 ;
$last = count($a_msg_index)>0 ? $a_msg_index[count($a_msg_index)-1] : -1;
}
else
{
// this assumes that we are sorted by date_DESC
$seq = $IMAP->get_id($MESSAGE->uid);
$prev = $IMAP->get_uid($seq + 1);
$first = $IMAP->get_uid($IMAP->messagecount());
$next = $IMAP->get_uid($seq - 1);
$last = $IMAP->get_uid(1);
$MESSAGE->index = $IMAP->messagecount() - $seq;
}
if ($prev > 0)
$OUTPUT->set_env('prev_uid', $prev);
if ($first >0)
$OUTPUT->set_env('first_uid', $first);
if ($next > 0)
$OUTPUT->set_env('next_uid', $next);
if ($last >0)
$OUTPUT->set_env('last_uid', $last);
}
function rcmail_message_attachments($attrib)
{
global $PRINT_MODE, $MESSAGE;
$out = $ol = '';
if (sizeof($MESSAGE->attachments)) {
foreach ($MESSAGE->attachments as $attach_prop) {
if ($PRINT_MODE) {
$ol .= html::tag('li', null, sprintf("%s (%s)", Q($attach_prop->filename), Q(show_bytes($attach_prop->size))));
}
else {
if (rc_strlen($attach_prop->filename) > 50) {
$filename = abbreviate_string($attach_prop->filename, 50);
$title = $attach_prop->filename;
}
else {
$filename = $attach_prop->filename;
$title = '';
}
$ol .= html::tag('li', null,
html::a(array(
'href' => $MESSAGE->get_part_url($attach_prop->mime_id),
'onclick' => sprintf(
'return %s.command(\'load-attachment\',{part:\'%s\', mimetype:\'%s\'},this)',
JS_OBJECT_NAME,
$attach_prop->mime_id,
$attach_prop->mimetype),
'title' => Q($title),
),
Q($filename)));
}
}
$out = html::tag('ul', $attrib, $ol, html::$common_attrib);
}
return $out;
}
function rcmail_remote_objects_msg($attrib)
{
global $MESSAGE, $RCMAIL;
if (!$attrib['id'])
$attrib['id'] = 'rcmremoteobjmsg';
$msg = Q(rcube_label('blockedimages')) . '&nbsp;';
$msg .= html::a(array('href' => "#loadimages", 'onclick' => JS_OBJECT_NAME.".command('load-images')"), Q(rcube_label('showimages')));
// add link to save sender in addressbook and reload message
if ($MESSAGE->sender['mailto'] && $RCMAIL->config->get('addrbook_show_images')) {
$msg .= ' ' . html::a(array('href' => "#alwaysload", 'onclick' => JS_OBJECT_NAME.".command('always-load')", 'style' => "white-space:nowrap"),
Q(rcube_label(array('name' => 'alwaysshow', 'vars' => array('sender' => $MESSAGE->sender['mailto'])))));
}
$RCMAIL->output->add_gui_object('remoteobjectsmsg', $attrib['id']);
return html::div($attrib, $msg);
}
$OUTPUT->add_handlers(array(
'messageattachments' => 'rcmail_message_attachments',
'mailboxname' => 'rcmail_mailbox_name_display',
'blockedobjects' => 'rcmail_remote_objects_msg'));
if ($RCMAIL->action=='print' && $OUTPUT->template_exists('printmessage'))
$OUTPUT->send('printmessage');
else if ($RCMAIL->action=='preview' && $OUTPUT->template_exists('messagepreview'))
- $OUTPUT->send('messagepreview');
+ $OUTPUT->send('messagepreview');
else
$OUTPUT->send('message');
?>

File Metadata

Mime Type
text/x-diff
Expires
Sat, Mar 1, 11:00 AM (8 h, 8 m)
Storage Engine
blob
Storage Format
Raw Data
Storage Handle
166792
Default Alt Text
(193 KB)

Event Timeline