From f06aa8058b7e32ba32d4551074b6e0b8a300f751 Mon Sep 17 00:00:00 2001
From: Thomas Bruederli <thomas@roundcube.net>
Date: Mon, 21 Oct 2013 15:02:40 -0400
Subject: [PATCH] Bump version after security fix

---
 program/include/main.inc |  874 ++++++++++++++++++++++++---------------------------------
 1 files changed, 372 insertions(+), 502 deletions(-)

diff --git a/program/include/main.inc b/program/include/main.inc
index 100feb6..bf56cb2 100644
--- a/program/include/main.inc
+++ b/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
@@ -97,9 +99,9 @@
  *
  * @see rcmail::text_exists()
  */
-function rcube_label_exists($name, $domain=null)
+function rcube_label_exists($name, $domain=null, &$ref_domain = null)
 {
-  return rcmail::get_instance()->text_exists($name, $domain);
+  return rcmail::get_instance()->text_exists($name, $domain, $ref_domain);
 }
 
 
@@ -156,357 +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('messages')."
-             WHERE  created < " . $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);
 }
 
 
@@ -634,20 +319,23 @@
 function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
 {
   $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 ($source == RCUBE_INPUT_GET) {
+    if (isset($_GET[$fname]))
+      $value = $_GET[$fname];
+  }
+  else if ($source == RCUBE_INPUT_POST) {
+    if (isset($_POST[$fname]))
+      $value = $_POST[$fname];
+  }
+  else if ($source == RCUBE_INPUT_GPC) {
     if (isset($_POST[$fname]))
       $value = $_POST[$fname];
     else if (isset($_GET[$fname]))
       $value = $_GET[$fname];
     else if (isset($_COOKIE[$fname]))
       $value = $_COOKIE[$fname];
-    }
+  }
 
   return parse_input_value($value, $allow_html, $charset);
 }
@@ -655,7 +343,7 @@
 /**
  * Parse/validate input value. See get_input_value()
  * Performs stripslashes() and charset conversion if necessary
- * 
+ *
  * @param  string   Input value
  * @param  boolean  Allow HTML tags in field value
  * @param  string   Charset to convert into
@@ -681,15 +369,21 @@
   else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
     $value = stripslashes($value);
 
-  // remove HTML tags if not allowed    
+  // remove HTML tags if not allowed
   if (!$allow_html)
     $value = strip_tags($value);
-  
+
+  $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null;
+
+  // remove invalid characters (#1488124)
+  if ($output_charset == 'UTF-8')
+    $value = rc_utf8_clean($value);
+
   // convert to internal charset
-  if (is_object($OUTPUT) && $charset)
-    return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
-  else
-    return $value;
+  if ($charset && $output_charset)
+    $value = rcube_charset_convert($value, $output_charset, $charset);
+
+  return $value;
 }
 
 /**
@@ -699,15 +393,16 @@
  * @param  int   Source to get value from (GPC)
  * @return array Hash array with all request parameters
  */
-function request2param($mode = RCUBE_INPUT_GPC)
+function request2param($mode = RCUBE_INPUT_GPC, $ignore = 'task|action')
 {
   $out = array();
   $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
   foreach ($src as $key => $value) {
     $fname = $key[0] == '_' ? substr($key, 1) : $key;
-    $out[$fname] = get_input_value($key, $mode);
+    if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname))
+      $out[$fname] = get_input_value($key, $mode);
   }
-  
+
   return $out;
 }
 
@@ -723,12 +418,14 @@
 
 /**
  * Convert the given string into a valid HTML identifier
- * Same functionality as done in app.js with this.identifier_expr
- *
+ * Same functionality as done in app.js with rcube_webmail.html_identifier()
  */
-function html_identifier($str)
+function html_identifier($str, $encode=false)
 {
-  return asciiwords($str, true, '_');
+  if ($encode)
+    return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
+  else
+    return asciiwords($str, true, '_');
 }
 
 /**
@@ -765,52 +462,48 @@
  * @return string HTML table code
  */
 function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
-  {
+{
   global $RCMAIL;
-  
+
   $table = new html_table(/*array('cols' => count($a_show_cols))*/);
-    
+
   // add table header
   if (!$attrib['noheader'])
     foreach ($a_show_cols as $col)
       $table->add_header($col, Q(rcube_label($col)));
-  
+
   $c = 0;
-  if (!is_array($table_data)) 
+  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' . html_identifier($sql_arr[$id_col]), 'class' => $zebra_class));
+      $table->add_row(array('id' => 'rcmrow' . html_identifier($sql_arr[$id_col])));
 
       // format each col
       foreach ($a_show_cols as $col)
         $table->add($col, Q($sql_arr[$col]));
-      
+
       $c++;
     }
   }
-  else 
-  {
+  else {
     foreach ($table_data as $row_data)
     {
-      $zebra_class = $c % 2 ? 'even' : 'odd';
-      if (!empty($row_data['class']))
-        $zebra_class .= ' '.$row_data['class'];
+      $class = !empty($row_data['class']) ? $row_data['class'] : '';
 
-      $table->add_row(array('id' => 'rcmrow' . html_identifier($row_data[$id_col]), 'class' => $zebra_class));
+      $table->add_row(array('id' => 'rcmrow' . html_identifier($row_data[$id_col]), 'class' => $class));
 
       // format each col
       foreach ($a_show_cols as $col)
         $table->add($col, Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
-        
+
       $c++;
     }
   }
 
   return $table->show($attrib);
-  }
+}
 
 
 /**
@@ -825,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);
@@ -842,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')
@@ -869,23 +565,37 @@
  * @param string Container ID to use as prefix
  * @return string Modified CSS source
  */
-function rcmail_mod_css_styles($source, $container_id)
+function rcmail_mod_css_styles($source, $container_id, $allow_remote=false)
   {
   $last_pos = 0;
   $replacements = new rcube_string_replacer;
 
   // ignore the whole block if evil styles are detected
-  $stripped = preg_replace('/[^a-z\(:;]/', '', rcmail_xss_entity_decode($source));
-  if (preg_match('/expression|behavior|url\(|import[^a]/', $stripped))
+  $source = rcmail_xss_entity_decode($source);
+  $stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
+  $evilexpr = 'expression|behavior|javascript:|import[^a]' . (!$allow_remote ? '|url\(' : '');
+  if (preg_match("/$evilexpr/i", $stripped))
     return '/* evil! */';
 
-  // remove css comments (sometimes used for some ugly hacks)
-  $source = preg_replace('!/\*(.+)\*/!Ums', '', $source);
-
   // cut out all contents between { and }
-  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
-  {
-    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
+  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos))) {
+    $styles = substr($source, $pos+1, $pos2-($pos+1));
+
+    // check every line of a style block...
+    if ($allow_remote) {
+      $a_styles = preg_split('/;[\r\n]*/', $styles, -1, PREG_SPLIT_NO_EMPTY);
+      foreach ($a_styles as $line) {
+        $stripped = preg_replace('/[^a-z\(:;]/i', '', $line);
+        // ... and only allow strict url() values
+        if (stripos($stripped, 'url(') && !preg_match('!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Uims', $line)) {
+          $a_styles = array('/* evil! */');
+          break;
+        }
+      }
+      $styles = join(";\n", $a_styles);
+    }
+
+    $key = $replacements->add($styles);
     $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
     $last_pos = $pos+2;
   }
@@ -923,7 +633,7 @@
 {
   $out = html_entity_decode(html_entity_decode($content));
   $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
-  $out = preg_replace('#/\*.*\*/#Um', '', $out);
+  $out = preg_replace('#/\*.*\*/#Ums', '', $out);
   return $out;
 }
 
@@ -1015,59 +725,80 @@
  * 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
+ *
  * @return string Formatted date string
  */
-function format_date($date, $format=NULL)
+function format_date($date, $format=NULL, $convert=true)
 {
   global $RCMAIL, $CONFIG;
-  
-  $ts = NULL;
 
-  if (!empty($date))
-    $ts = rcube_strtotime($date);
+  if (is_object($date) && is_a($date, 'DateTime')) {
+    $timestamp = $date->format('U');
+  }
+  else {
+    if (!empty($date))
+      $timestamp = rcube_strtotime($date);
 
-  if (empty($ts))
-    return '';
+    if (empty($timestamp))
+      return '';
 
-  // get user's timezone
-  $tz = $RCMAIL->config->get_timezone();
+    try {
+      $date = new DateTime("@".$timestamp);
+    }
+    catch (Exception $e) {
+      return '';
+    }
+  }
 
-  // convert time to user's timezone
-  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
+  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());
 
-  // 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']);
+      $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']);
+
     if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
-      $format = $CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i';
+      $format = $RCMAIL->config->get('date_today', $RCMAIL->config->get('time_format', 'H:i'));
       $today  = true;
     }
     else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
-      $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
+      $format = $RCMAIL->config->get('date_short', 'D H:i');
     else
-      $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
+      $format = $RCMAIL->config->get('date_long', 'Y-m-d H:i');
   }
 
   // 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;
 
@@ -1103,6 +834,10 @@
     }
   }
 
+  if ($convert && $stz) {
+    date_default_timezone_set($stz);
+  }
+
   return $out;
 }
 
@@ -1136,7 +871,7 @@
   global $RCMAIL;
   static $a_mailboxes;
 
-  $attrib += array('maxlength' => 100, 'realnames' => false);
+  $attrib += array('maxlength' => 100, 'realnames' => false, 'unreadwrap' => ' (%s)');
 
   // add some labels to client
   $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
@@ -1151,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)
@@ -1165,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);
@@ -1174,15 +918,16 @@
     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);
+    $RCMAIL->output->set_env('unreadwrap', $attrib['unreadwrap']);
     $RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders'));
   }
 
@@ -1202,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']);
+    $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']);
+    $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']))
@@ -1223,7 +970,7 @@
   if ($p['noselection'])
     $select->add($p['noselection'], '');
 
-  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p['exceptions']);
+  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
 
   return $select;
 }
@@ -1242,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));
@@ -1272,11 +1019,6 @@
   $path .= $prefix.$currentFolder;
 
   if (!isset($arrFolders[$currentFolder])) {
-    // Check \Noselect option (if options are in cache)
-    if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
-      $virtual = in_array('\\Noselect', $opts);
-    }
-
     $arrFolders[$currentFolder] = array(
       'id' => $path,
       'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
@@ -1302,15 +1044,16 @@
 
   $maxlength = intval($attrib['maxlength']);
   $realnames = (bool)$attrib['realnames'];
-  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
+  $msgcounts = $RCMAIL->storage->get_cache('messagecount');
 
-  $idx = 0;
   $out = '';
   foreach ($arrFolders as $key => $folder) {
-    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
-    $title = null;
+    $title        = null;
+    $folder_class = rcmail_folder_classname($folder['id']);
+    $collapsed    = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false;
+    $unread       = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
 
-    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
+    if ($folder_class && !$realnames) {
       $foldername = rcube_label($folder_class);
     }
     else {
@@ -1326,30 +1069,15 @@
     }
 
     // make folder name safe for ids and class names
-    $folder_id = html_identifier($folder['id']);
+    $folder_id = html_identifier($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_class)
+      $classes[] = $folder_class;
 
     if ($folder['id'] == $mbox_name)
       $classes[] = 'selected';
-
-    $collapsed = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false;
-    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
 
     if ($folder['virtual'])
       $classes[] = 'virtual';
@@ -1357,7 +1085,7 @@
       $classes[] = 'unread';
 
     $js_name = JQ($folder['id']);
-    $html_name = Q($foldername) . ($unread ? html::span('unreadcount', " ($unread)") : '');
+    $html_name = Q($foldername) . ($unread ? html::span('unreadcount', sprintf($attrib['unreadwrap'], $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),
@@ -1384,7 +1112,6 @@
     }
 
     $out .= "</li>\n";
-    $idx++;
   }
 
   return $out;
@@ -1396,30 +1123,40 @@
  * @access private
  * @return string
  */
-function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0, $exceptions=array())
+function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0, $opts=array())
 {
+  global $RCMAIL;
+
   $out = '';
 
   foreach ($arrFolders as $key => $folder) {
-    if (empty($exceptions) || !in_array($folder['id'], $exceptions)) {
-      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']);
-    }
-    else if ($nestLevel)
+    // skip exceptions (and its subfolders)
+    if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
       continue;
+    }
+
+    // skip folders in which it isn't possible to create subfolders
+    if (!empty($opts['skip_noinferiors']) && ($attrs = $RCMAIL->storage->folder_attributes($folder['id']))
+        && in_array('\\Noinferiors', $attrs)
+    ) {
+      continue;
+    }
+
+    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
+      $foldername = rcube_label($folder_class);
+    else {
+      $foldername = $folder['name'];
+
+      // shorten the folder name to a given length
+      if ($maxlength && $maxlength>1)
+        $foldername = abbreviate_string($foldername, $maxlength);
+    }
+
+    $select->add(str_repeat('&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, $exceptions);
+        $select, $realnames, $nestLevel+1, $opts);
   }
 
   return $out;
@@ -1468,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();
 
@@ -1495,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']);
 
@@ -1512,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;
@@ -1558,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');
@@ -1583,6 +1322,28 @@
 
 
 /**
+ * Generate CSS classes from mimetype and filename extension
+ *
+ * @param string Mimetype
+ * @param string The filename
+ * @return string CSS classes separated by space
+ */
+function rcmail_filetype2classname($mimetype, $filename)
+{
+  list($primary, $secondary) = explode('/', $mimetype);
+
+  $classes = array($primary ? $primary : 'unknown');
+  if ($secondary) {
+    $classes[] = $secondary;
+  }
+  if (preg_match('/\.([a-z0-9]+)$/i', $filename, $m)) {
+    $classes[] = $m[1];
+  }
+
+  return strtolower(join(" ", $classes));
+}
+
+/**
  * Output HTML editor scripts
  *
  * @param string Editor mode
@@ -1590,26 +1351,34 @@
  */
 function rcube_html_editor($mode='')
 {
-  global $RCMAIL, $CONFIG;
+  global $RCMAIL;
 
   $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
 
   if ($hook['abort'])
-    return;  
+    return;
 
   $lang = strtolower($_SESSION['language']);
 
-  // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
-  $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
+  // TinyMCE uses two-letter lang codes, with exception of Chinese
+  if (strpos($lang, 'zh_') === 0)
+    $lang = str_replace('_', '-', $lang);
+  else
+    $lang = substr($lang, 0, 2);
 
   if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
     $lang = 'en';
 
   $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
   $RCMAIL->output->include_script('editor.js');
-  $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
-    JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
-    'foot');
+  $RCMAIL->output->add_script(sprintf("rcmail_editor_init(%s)",
+    json_encode(array(
+        'mode'       => $mode,
+        'lang'       => $lang,
+        'skin_path'  => $RCMAIL->output->get_skin_path(),
+        'spellcheck' => intval($RCMAIL->config->get('enable_spellcheck')),
+        'spelldict'  => intval($RCMAIL->config->get('spellcheck_dictionary')),
+    ))), 'docready');
 }
 
 
@@ -1822,17 +1591,33 @@
 // 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->format('r');
+}
 
-  return $date;
+
+/**
+ * Check if we can process not exceeding memory_limit
+ *
+ * @param integer Required amount of memory
+ * @return boolean
+ */
+function rcmail_mem_check($need)
+{
+  $mem_limit = parse_bytes(ini_get('memory_limit'));
+  $memory    = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
+
+  return $mem_limit > 0 && $memory + $need > $mem_limit ? false : true;
 }
 
 
@@ -1889,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
@@ -2030,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;
   }
 }
 
@@ -2373,5 +2219,29 @@
 
     $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15));
     $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length'));
-    $RCMAIL->output->add_label('autocompletechars');
+    $RCMAIL->output->add_label('autocompletechars', 'autocompletemore');
+}
+
+function rcube_fontdefs($font = null)
+{
+  $fonts = array(
+    'Andale Mono'   => '"Andale Mono",Times,monospace',
+    'Arial'         => 'Arial,Helvetica,sans-serif',
+    'Arial Black'   => '"Arial Black","Avant Garde",sans-serif',
+    'Book Antiqua'  => '"Book Antiqua",Palatino,serif',
+    'Courier New'   => '"Courier New",Courier,monospace',
+    'Georgia'       => 'Georgia,Palatino,serif',
+    'Helvetica'     => 'Helvetica,Arial,sans-serif',
+    'Impact'        => 'Impact,Chicago,sans-serif',
+    'Tahoma'        => 'Tahoma,Arial,Helvetica,sans-serif',
+    'Terminal'      => 'Terminal,Monaco,monospace',
+    'Times New Roman' => '"Times New Roman",Times,serif',
+    'Trebuchet MS'  => '"Trebuchet MS",Geneva,sans-serif',
+    'Verdana'       => 'Verdana,Geneva,sans-serif',
+  );
+
+  if ($font)
+    return $fonts[$font];
+
+  return $fonts;
 }

--
Gitblit v1.9.1