Thomas Bruederli
2013-10-21 f06aa8058b7e32ba32d4551074b6e0b8a300f751
program/include/main.inc
@@ -6,7 +6,10 @@
 |                                                                       |
 | This file is part of the Roundcube Webmail client                     |
 | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
 | Licensed under the GNU GPL                                            |
 |                                                                       |
 | Licensed under the GNU General Public License version 3 or            |
 | any later version with exceptions for skins & plugins.                |
 | See the README file for a full license statement.                     |
 |                                                                       |
 | PURPOSE:                                                              |
 |   Provide basic functions for the webmail package                     |
@@ -26,7 +29,6 @@
 * @author Thomas Bruederli <roundcube@gmail.com>
 */
require_once 'utf7.inc';
require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
// define constannts for input reading
@@ -156,363 +158,40 @@
}
/**
 * Garbage collector for cache entries.
 * Remove all expired message cache records
 * @return void
 */
function rcmail_cache_gc()
{
  $rcmail = rcmail::get_instance();
  $db = $rcmail->get_dbh();
  // get target timestamp
  $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
  $db->query("DELETE FROM ".get_table_name('cache_messages')
        ." WHERE changed < " . $db->fromunixtime($ts));
  $db->query("DELETE FROM ".get_table_name('cache_index')
        ." WHERE changed < " . $db->fromunixtime($ts));
  $db->query("DELETE FROM ".get_table_name('cache_thread')
        ." WHERE changed < " . $db->fromunixtime($ts));
  $db->query("DELETE FROM ".get_table_name('cache')
        ." WHERE created < " . $db->fromunixtime($ts));
}
/**
 * Catch an error and throw an exception.
 *
 * @param  int    Level of the error
 * @param  string Error message
 */
function rcube_error_handler($errno, $errstr)
{
  throw new ErrorException($errstr, 0, $errno);
}
/**
 * Convert a string from one charset to another.
 * Uses mbstring and iconv functions if possible
 *
 * @param  string Input string
 * @param  string Suspected charset of the input string
 * @param  string Target charset to convert to; defaults to RCMAIL_CHARSET
 * @return string Converted string
 */
// Deprecated
function rcube_charset_convert($str, $from, $to=NULL)
{
  static $iconv_options = null;
  static $mbstring_loaded = null;
  static $mbstring_list = null;
  static $conv = null;
    return rcube_charset::convert($str, $from, $to);
}
  $error = false;
  $to = empty($to) ? strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
  $from = rcube_parse_charset($from);
// Deprecated
function rc_detect_encoding($string, $failover='')
{
    return rcube_charset::detect($string, $failover);
}
  if ($from == $to || empty($str) || empty($from))
    return $str;
  // convert charset using iconv module
  if (function_exists('iconv') && $from != 'UTF7-IMAP' && $to != 'UTF7-IMAP') {
    if ($iconv_options === null) {
      // ignore characters not available in output charset
      $iconv_options = '//IGNORE';
      if (iconv('', $iconv_options, '') === false) {
        // iconv implementation does not support options
        $iconv_options = '';
      }
    }
    // throw an exception if iconv reports an illegal character in input
    // it means that input string has been truncated
    set_error_handler('rcube_error_handler', E_NOTICE);
    try {
      $_iconv = iconv($from, $to . $iconv_options, $str);
    } catch (ErrorException $e) {
      $_iconv = false;
    }
    restore_error_handler();
    if ($_iconv !== false) {
      return $_iconv;
    }
  }
  if ($mbstring_loaded === null)
    $mbstring_loaded = extension_loaded('mbstring');
  // convert charset using mbstring module
  if ($mbstring_loaded) {
    $aliases['WINDOWS-1257'] = 'ISO-8859-13';
    if ($mbstring_list === null) {
      $mbstring_list = mb_list_encodings();
      $mbstring_list = array_map('strtoupper', $mbstring_list);
    }
    $mb_from = $aliases[$from] ? $aliases[$from] : $from;
    $mb_to = $aliases[$to] ? $aliases[$to] : $to;
    // return if encoding found, string matches encoding and convert succeeded
    if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
      if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
        return $out;
    }
  }
  // convert charset using bundled classes/functions
  if ($to == 'UTF-8') {
    if ($from == 'UTF7-IMAP') {
      if ($_str = utf7_to_utf8($str))
        return $_str;
    }
    else if ($from == 'UTF-7') {
      if ($_str = rcube_utf7_to_utf8($str))
        return $_str;
    }
    else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
      return utf8_encode($str);
    }
    else if (class_exists('utf8')) {
      if (!$conv)
        $conv = new utf8($from);
      else
        $conv->loadCharset($from);
      if($_str = $conv->strToUtf8($str))
        return $_str;
    }
    $error = true;
  }
  // encode string for output
  if ($from == 'UTF-8') {
    // @TODO: we need a function for UTF-7 (RFC2152) conversion
    if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
      if ($_str = utf8_to_utf7($str))
        return $_str;
    }
    else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
      return utf8_decode($str);
    }
    else if (class_exists('utf8')) {
      if (!$conv)
        $conv = new utf8($to);
      else
        $conv->loadCharset($from);
      if ($_str = $conv->strToUtf8($str))
        return $_str;
    }
    $error = true;
  }
  // return UTF-8 or original string
  return $str;
// Deprecated
function rc_utf8_clean($input)
{
    return rcube_charset::clean($input);
}
/**
 * Parse and validate charset name string (see #1485758).
 * Sometimes charset string is malformed, there are also charset aliases
 * but we need strict names for charset conversion (specially utf8 class)
 * Convert a variable into a javascript object notation
 *
 * @param  string Input charset name
 * @return string The validated charset name
 * @param mixed Input value
 * @return string Serialized JSON string
 */
function rcube_parse_charset($input)
function json_serialize($input)
{
  static $charsets = array();
  $charset = strtoupper($input);
    $input = rcube_charset::clean($input);
  if (isset($charsets[$input]))
    return $charsets[$input];
  $charset = preg_replace(array(
    '/^[^0-9A-Z]+/',    // e.g. _ISO-8859-JP$SIO
    '/\$.*$/',          // e.g. _ISO-8859-JP$SIO
    '/UNICODE-1-1-*/',  // RFC1641/1642
    '/^X-/',            // X- prefix (e.g. X-ROMAN8 => ROMAN8)
    ), '', $charset);
  if ($charset == 'BINARY')
    return $charsets[$input] = null;
  # Aliases: some of them from HTML5 spec.
  $aliases = array(
    'USASCII'       => 'WINDOWS-1252',
    'ANSIX31101983' => 'WINDOWS-1252',
    'ANSIX341968'   => 'WINDOWS-1252',
    'UNKNOWN8BIT'   => 'ISO-8859-15',
    'UNKNOWN'       => 'ISO-8859-15',
    'USERDEFINED'   => 'ISO-8859-15',
    'KSC56011987'   => 'EUC-KR',
    'GB2312'        => 'GBK',
    'GB231280'       => 'GBK',
    'UNICODE'       => 'UTF-8',
    'UTF7IMAP'       => 'UTF7-IMAP',
    'TIS620'       => 'WINDOWS-874',
    'ISO88599'       => 'WINDOWS-1254',
    'ISO885911'       => 'WINDOWS-874',
    'MACROMAN'       => 'MACINTOSH',
    '77'            => 'MAC',
    '128'           => 'SHIFT-JIS',
    '129'           => 'CP949',
    '130'           => 'CP1361',
    '134'           => 'GBK',
    '136'           => 'BIG5',
    '161'           => 'WINDOWS-1253',
    '162'           => 'WINDOWS-1254',
    '163'           => 'WINDOWS-1258',
    '177'           => 'WINDOWS-1255',
    '178'           => 'WINDOWS-1256',
    '186'           => 'WINDOWS-1257',
    '204'           => 'WINDOWS-1251',
    '222'           => 'WINDOWS-874',
    '238'           => 'WINDOWS-1250',
    'MS950'         => 'CP950',
    'WINDOWS949'    => 'UHC',
  );
  // allow A-Z and 0-9 only
  $str = preg_replace('/[^A-Z0-9]/', '', $charset);
  if (isset($aliases[$str]))
    $result = $aliases[$str];
  // UTF
  else if (preg_match('/U[A-Z][A-Z](7|8|16|32)(BE|LE)*/', $str, $m))
    $result = 'UTF-' . $m[1] . $m[2];
  // ISO-8859
  else if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
    $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
    // some clients sends windows-1252 text as latin1,
    // it is safe to use windows-1252 for all latin1
    $result = $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
  }
  // handle broken charset names e.g. WINDOWS-1250HTTP-EQUIVCONTENT-TYPE
  else if (preg_match('/(WIN|WINDOWS)([0-9]+)/', $str, $m)) {
    $result = 'WINDOWS-' . $m[2];
  }
  // LATIN
  else if (preg_match('/LATIN(.*)/', $str, $m)) {
    $aliases = array('2' => 2, '3' => 3, '4' => 4, '5' => 9, '6' => 10,
        '7' => 13, '8' => 14, '9' => 15, '10' => 16,
        'ARABIC' => 6, 'CYRILLIC' => 5, 'GREEK' => 7, 'GREEK1' => 7, 'HEBREW' => 8);
    // some clients sends windows-1252 text as latin1,
    // it is safe to use windows-1252 for all latin1
    if ($m[1] == 1) {
      $result = 'WINDOWS-1252';
    }
    // if iconv is not supported we need ISO labels, it's also safe for iconv
    else if (!empty($aliases[$m[1]])) {
      $result = 'ISO-8859-'.$aliases[$m[1]];
    }
    // iconv requires convertion of e.g. LATIN-1 to LATIN1
    else {
      $result = $str;
    }
  }
  else {
    $result = $charset;
  }
  $charsets[$input] = $result;
  return $result;
}
/**
 * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
 *
 * @param  string  Input string
 * @return string  The converted string
 */
function rcube_utf7_to_utf8($str)
{
  $Index_64 = array(
    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
    0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
    1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
  );
  $u7len = strlen($str);
  $str = strval($str);
  $res = '';
  for ($i=0; $u7len > 0; $i++, $u7len--)
  {
    $u7 = $str[$i];
    if ($u7 == '+')
    {
      $i++;
      $u7len--;
      $ch = '';
      for (; $u7len > 0; $i++, $u7len--)
      {
        $u7 = $str[$i];
        if (!$Index_64[ord($u7)])
          break;
   $ch .= $u7;
      }
      if ($ch == '') {
        if ($u7 == '-')
          $res .= '+';
        continue;
      }
      $res .= rcube_utf16_to_utf8(base64_decode($ch));
    }
    else
    {
      $res .= $u7;
    }
  }
  return $res;
}
/**
 * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
 *
 * @param  string  Input string
 * @return string  The converted string
 */
function rcube_utf16_to_utf8($str)
{
  $len = strlen($str);
  $dec = '';
  for ($i = 0; $i < $len; $i += 2) {
    $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
    if ($c >= 0x0001 && $c <= 0x007F) {
      $dec .= chr($c);
    } else if ($c > 0x07FF) {
      $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
      $dec .= chr(0x80 | (($c >>  6) & 0x3F));
      $dec .= chr(0x80 | (($c >>  0) & 0x3F));
    } else {
      $dec .= chr(0xC0 | (($c >>  6) & 0x1F));
      $dec .= chr(0x80 | (($c >>  0) & 0x3F));
    }
  }
  return $dec;
    // sometimes even using rcube_charset::clean() the input contains invalid UTF-8 sequences
    // that's why we have @ here
    return @json_encode($input);
}
@@ -839,11 +518,11 @@
function rcmail_get_edit_field($col, $value, $attrib, $type='text')
{
  static $colcounts = array();
  $fname = '_'.$col;
  $attrib['name'] = $fname . ($attrib['array'] ? '[]' : '');
  $attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
  if ($type == 'checkbox') {
    $attrib['value'] = '1';
    $input = new html_checkbox($attrib);
@@ -856,6 +535,9 @@
    $input = new html_select($attrib);
    $input->add('---', '');
    $input->add(array_values($attrib['options']), array_keys($attrib['options']));
  }
  else if ($attrib['type'] == 'password') {
    $input = new html_passwordfield($attrib);
  }
  else {
    if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden')
@@ -1043,7 +725,7 @@
 * Convert the given date to a human readable form
 * This uses the date formatting properties from config
 *
 * @param mixed  Date representation (string or timestamp)
 * @param mixed  Date representation (string, timestamp or DateTime object)
 * @param string Date format to use
 * @param bool   Enables date convertion according to user timezone
 *
@@ -1053,31 +735,41 @@
{
  global $RCMAIL, $CONFIG;
  if (!empty($date))
    $ts = rcube_strtotime($date);
  if (empty($ts))
    return '';
  if ($convert) {
    // get user's timezone offset
    $tz = $RCMAIL->config->get_timezone();
    // convert time to user's timezone
    $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
    // get current timestamp in user's timezone
    $now = time();  // local time
    $now -= (int)date('Z'); // make GMT time
    $now += ($tz * 3600); // user's time
  if (is_object($date) && is_a($date, 'DateTime')) {
    $timestamp = $date->format('U');
  }
  else {
    $now       = time();
    $timestamp = $ts;
    if (!empty($date))
      $timestamp = rcube_strtotime($date);
    if (empty($timestamp))
      return '';
    try {
      $date = new DateTime("@".$timestamp);
    }
    catch (Exception $e) {
      return '';
    }
  }
  if ($convert) {
    try {
      // convert to the right timezone
      $stz = date_default_timezone_get();
      $tz = new DateTimeZone($RCMAIL->config->get('timezone'));
      $date->setTimezone($tz);
      date_default_timezone_set($tz->getName());
      $timestamp = $date->format('U');
    }
    catch (Exception $e) {
    }
  }
  // define date format depending on current time
  if (!$format) {
    $now         = 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']);
@@ -1095,13 +787,18 @@
  // strftime() format
  if (preg_match('/%[a-z]+/i', $format)) {
    $format = strftime($format, $timestamp);
    if ($convert && $stz) {
      date_default_timezone_set($stz);
    }
    return $today ? (rcube_label('today') . ' ' . $format) : $format;
  }
  // parse format string manually in order to provide localized weekday and month names
  // an alternative would be to convert the date() format string to fit with strftime()
  $out = '';
  for($i=0; $i<strlen($format); $i++) {
  for ($i=0; $i<strlen($format); $i++) {
    if ($format[$i]=='\\')  // skip escape chars
      continue;
@@ -1135,6 +832,10 @@
    else {
      $out = $label . ' ' . $out;
    }
  }
  if ($convert && $stz) {
    date_default_timezone_set($stz);
  }
  return $out;
@@ -1185,13 +886,14 @@
    $attrib['folder_name'] = '*';
  // get mailbox list
  $mbox_name = $RCMAIL->imap->get_mailbox_name();
  $storage = $RCMAIL->get_storage();
  $mbox_name = $storage->get_folder();
  // build the folders tree
  if (empty($a_mailboxes)) {
    // get mailbox list
    $a_folders = $RCMAIL->imap->list_mailboxes('', $attrib['folder_name'], $attrib['folder_filter']);
    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
    $a_folders = $storage->list_folders_subscribed('', $attrib['folder_name'], $attrib['folder_filter']);
    $delimiter = $storage->get_hierarchy_delimiter();
    $a_mailboxes = array();
    foreach ($a_folders as $folder)
@@ -1199,7 +901,15 @@
  }
  // allow plugins to alter the folder tree or to localize folder names
  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array(
    'list'      => $a_mailboxes,
    'delimiter' => $delimiter,
    'type'      => $type,
    'attribs'   => $attrib,
  ));
  $a_mailboxes = $hook['list'];
  $attrib      = $hook['attribs'];
  if ($type == 'select') {
    $select = new html_select($attrib);
@@ -1208,12 +918,12 @@
    if ($attrib['noselection'])
      $select->add(rcube_label($attrib['noselection']), '');
    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
    $out = $select->show();
    rcmail_render_folder_tree_select($a_mailboxes, $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
    $out = $select->show($attrib['default']);
  }
  else {
    $js_mailboxlist = array();
    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
    $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);
@@ -1237,16 +947,18 @@
  $p += array('maxlength' => 100, 'realnames' => false);
  $a_mailboxes = array();
  $storage = $RCMAIL->get_storage();
  if (empty($p['folder_name']))
  if (empty($p['folder_name'])) {
    $p['folder_name'] = '*';
  }
  if ($p['unsubscribed'])
    $list = $RCMAIL->imap->list_unsubscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
    $list = $storage->list_folders('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
  else
    $list = $RCMAIL->imap->list_mailboxes('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
    $list = $storage->list_folders_subscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
  $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
  $delimiter = $storage->get_hierarchy_delimiter();
  foreach ($list as $folder) {
    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
@@ -1277,7 +989,7 @@
  $prefix = '';
  if (!$path) {
    $n_folder = $folder;
    $folder = $RCMAIL->imap->mod_mailbox($folder);
    $folder = $RCMAIL->storage->mod_folder($folder);
    if ($n_folder != $folder) {
      $prefix = substr($n_folder, 0, -strlen($folder));
@@ -1332,7 +1044,7 @@
  $maxlength = intval($attrib['maxlength']);
  $realnames = (bool)$attrib['realnames'];
  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
  $msgcounts = $RCMAIL->storage->get_cache('messagecount');
  $out = '';
  foreach ($arrFolders as $key => $folder) {
@@ -1424,7 +1136,7 @@
    }
    // skip folders in which it isn't possible to create subfolders
    if (!empty($opts['skip_noinferiors']) && ($attrs = $RCMAIL->imap->mailbox_attributes($folder['id']))
    if (!empty($opts['skip_noinferiors']) && ($attrs = $RCMAIL->storage->folder_attributes($folder['id']))
        && in_array('\\Noinferiors', $attrs)
    ) {
      continue;
@@ -1493,8 +1205,8 @@
    global $RCMAIL;
    $protect_folders = $RCMAIL->config->get('protect_default_folders');
    $default_folders = (array) $RCMAIL->config->get('default_imap_folders');
    $delimiter       = $RCMAIL->imap->get_hierarchy_delimiter();
    $default_folders = (array) $RCMAIL->config->get('default_folders');
    $delimiter       = $RCMAIL->storage->get_hierarchy_delimiter();
    $path            = explode($delimiter, $path);
    $result          = array();
@@ -1520,8 +1232,7 @@
  if (!$attrib['id'])
    $attrib['id'] = 'rcmquotadisplay';
  if(isset($attrib['display']))
    $_SESSION['quota_display'] = $attrib['display'];
  $_SESSION['quota_display'] = !empty($attrib['display']) ? $attrib['display'] : 'text';
  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
@@ -1537,7 +1248,7 @@
{
  global $RCMAIL;
  $quota = $RCMAIL->imap->get_quota();
  $quota = $RCMAIL->storage->get_quota();
  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
  $quota_result = (array) $quota;
@@ -1583,16 +1294,19 @@
{
    global $RCMAIL;
    $err_code = $RCMAIL->imap->get_error_code();
    $res_code = $RCMAIL->imap->get_response_code();
    $err_code = $RCMAIL->storage->get_error_code();
    $res_code = $RCMAIL->storage->get_response_code();
    if ($res_code == rcube_imap::NOPERM) {
    if ($err_code < 0) {
        $RCMAIL->output->show_message('storageerror', 'error');
    }
    else if ($res_code == rcube_storage::NOPERM) {
        $RCMAIL->output->show_message('errornoperm', 'error');
    }
    else if ($res_code == rcube_imap::READONLY) {
    else if ($res_code == rcube_storage::READONLY) {
        $RCMAIL->output->show_message('errorreadonly', 'error');
    }
    else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
    else if ($err_code && ($err_str = $RCMAIL->storage->get_error_str())) {
        // try to detect access rights problem and display appropriate message
        if (stripos($err_str, 'Permission denied') !== false)
            $RCMAIL->output->show_message('errornoperm', 'error');
@@ -1619,12 +1333,14 @@
  list($primary, $secondary) = explode('/', $mimetype);
  $classes = array($primary ? $primary : 'unknown');
  if ($secondary)
  if ($secondary) {
    $classes[] = $secondary;
  if (preg_match('/\.([a-z0-9]+)$/', $filename, $m))
  }
  if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) {
    $classes[] = $m[1];
  }
  return join(" ", $classes);
  return strtolower(join(" ", $classes));
}
/**
@@ -1658,11 +1374,11 @@
  $RCMAIL->output->add_script(sprintf("rcmail_editor_init(%s)",
    json_encode(array(
        'mode'       => $mode,
        'skin_path'  => '$__skin_path',
        'lang'       => $lang,
        'skin_path'  => $RCMAIL->output->get_skin_path(),
        'spellcheck' => intval($RCMAIL->config->get('enable_spellcheck')),
        'spelldict'  => intval($RCMAIL->config->get('spellcheck_dictionary')),
    ))), 'foot');
    ))), 'docready');
}
@@ -1875,17 +1591,18 @@
// Returns RFC2822 formatted current date in user's timezone
function rcmail_user_date()
{
  global $RCMAIL, $CONFIG;
  global $RCMAIL;
  // get user's timezone
  $tz = $RCMAIL->config->get_timezone();
  try {
    $tz   = new DateTimeZone($RCMAIL->config->get('timezone'));
    $date = new DateTime('now', $tz);
  }
  catch (Exception $e) {
    $date = new DateTime();
  }
  $date = time() + $tz * 60 * 60;
  $date = gmdate('r', $date);
  $tz   = sprintf('%+05d', intval($tz) * 100 + ($tz - intval($tz)) * 60);
  $date = preg_replace('/[+-][0-9]{4}$/', $tz, $date);
  return $date;
  return $date->format('r');
}
@@ -1900,7 +1617,7 @@
  $mem_limit = parse_bytes(ini_get('memory_limit'));
  $memory    = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
  return $mem_limit && $memory + $need > $mem_limit ? false : true;
  return $mem_limit > 0 && $memory + $need > $mem_limit ? false : true;
}
@@ -1957,7 +1674,7 @@
  // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
  $d = preg_replace('/^[^\.]+\./', '', $n);
  // %h - IMAP host
  $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
  $h = $_SESSION['storage_host'] ? $_SESSION['storage_host'] : $host;
  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
  $z = preg_replace('/^[^\.]+\./', '', $h);
  // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
@@ -2098,7 +1815,68 @@
  public function callback($matches)
  {
    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
    return $matches[1] . '="' . self::absolute_url($matches[3], $this->base_url) . '"';
  }
  public function replace($body)
  {
    return preg_replace_callback(array(
      '/(src|background|href)=(["\']?)([^"\'\s]+)(\2|\s|>)/Ui',
      '/(url\s*\()(["\']?)([^"\'\)\s]+)(\2)\)/Ui',
      ),
      array($this, 'callback'), $body);
  }
  /**
   * Convert paths like ../xxx to an absolute path using a base url
   *
   * @param string $path     Relative path
   * @param string $base_url Base URL
   *
   * @return string Absolute URL
   */
  public static function absolute_url($path, $base_url)
  {
    $host_url = $base_url;
    $abs_path = $path;
    // check if path is an absolute URL
    if (preg_match('/^[fhtps]+:\/\//', $path)) {
      return $path;
    }
    // check if path is a content-id scheme
    if (strpos($path, 'cid:') === 0) {
      return $path;
    }
    // cut base_url to the last directory
    if (strrpos($base_url, '/') > 7) {
      $host_url = substr($base_url, 0, strpos($base_url, '/', 7));
      $base_url = substr($base_url, 0, strrpos($base_url, '/'));
    }
    // $path is absolute
    if ($path[0] == '/') {
      $abs_path = $host_url.$path;
    }
    else {
      // strip './' because its the same as ''
      $path = preg_replace('/^\.\//', '', $path);
      if (preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER)) {
        foreach ($matches as $a_match) {
          if (strrpos($base_url, '/')) {
            $base_url = substr($base_url, 0, strrpos($base_url, '/'));
          }
          $path = substr($path, 3);
        }
      }
      $abs_path = $base_url.'/'.$path;
    }
    return $abs_path;
  }
}