source: subversion/trunk/roundcubemail/program/include/main.inc @ 4738

Last change on this file since 4738 was 4738, checked in by alec, 2 years ago
  • Fixed handling of folder with name "0" in folder selector
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 55.8 KB
Line 
1<?php
2
3/*
4 +-----------------------------------------------------------------------+
5 | program/include/main.inc                                              |
6 |                                                                       |
7 | This file is part of the Roundcube Webmail client                     |
8 | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
9 | Licensed under the GNU GPL                                            |
10 |                                                                       |
11 | PURPOSE:                                                              |
12 |   Provide basic functions for the webmail package                     |
13 |                                                                       |
14 +-----------------------------------------------------------------------+
15 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16 +-----------------------------------------------------------------------+
17
18 $Id$
19
20*/
21
22/**
23 * Roundcube Webmail common functions
24 *
25 * @package Core
26 * @author Thomas Bruederli <roundcube@gmail.com>
27 */
28
29require_once 'utf7.inc';
30require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
31
32// define constannts for input reading
33define('RCUBE_INPUT_GET', 0x0101);
34define('RCUBE_INPUT_POST', 0x0102);
35define('RCUBE_INPUT_GPC', 0x0103);
36
37
38
39/**
40 * Return correct name for a specific database table
41 *
42 * @param string Table name
43 * @return string Translated table name
44 */
45function get_table_name($table)
46  {
47  global $CONFIG;
48
49  // return table name if configured
50  $config_key = 'db_table_'.$table;
51
52  if (strlen($CONFIG[$config_key]))
53    return $CONFIG[$config_key];
54
55  return $table;
56  }
57
58
59/**
60 * Return correct name for a specific database sequence
61 * (used for Postgres only)
62 *
63 * @param string Secuence name
64 * @return string Translated sequence name
65 */
66function get_sequence_name($sequence)
67  {
68  // return sequence name if configured
69  $config_key = 'db_sequence_'.$sequence;
70  $opt = rcmail::get_instance()->config->get($config_key);
71
72  if (!empty($opt))
73    return $opt;
74   
75  return $sequence;
76  }
77
78
79/**
80 * Get localized text in the desired language
81 * It's a global wrapper for rcmail::gettext()
82 *
83 * @param mixed Named parameters array or label name
84 * @param string Domain to search in (e.g. plugin name)
85 * @return string Localized text
86 * @see rcmail::gettext()
87 */
88function rcube_label($p, $domain=null)
89{
90  return rcmail::get_instance()->gettext($p, $domain);
91}
92
93
94/**
95 * Global wrapper of rcmail::text_exists()
96 * to check whether a text label is defined
97 *
98 * @see rcmail::text_exists()
99 */
100function rcube_label_exists($name, $domain=null)
101{
102  return rcmail::get_instance()->text_exists($name, $domain);
103}
104
105
106/**
107 * Overwrite action variable
108 *
109 * @param string New action value
110 */
111function rcmail_overwrite_action($action)
112  {
113  $app = rcmail::get_instance();
114  $app->action = $action;
115  $app->output->set_env('action', $action);
116  }
117
118
119/**
120 * Compose an URL for a specific action
121 *
122 * @param string  Request action
123 * @param array   More URL parameters
124 * @param string  Request task (omit if the same)
125 * @return The application URL
126 */
127function rcmail_url($action, $p=array(), $task=null)
128{
129  $app = rcmail::get_instance();
130  return $app->url((array)$p + array('_action' => $action, 'task' => $task));
131}
132
133
134/**
135 * Garbage collector function for temp files.
136 * Remove temp files older than two days
137 */
138function rcmail_temp_gc()
139  {
140  $rcmail = rcmail::get_instance();
141
142  $tmp = unslashify($rcmail->config->get('temp_dir'));
143  $expire = mktime() - 172800;  // expire in 48 hours
144
145  if ($dir = opendir($tmp))
146    {
147    while (($fname = readdir($dir)) !== false)
148      {
149      if ($fname{0} == '.')
150        continue;
151
152      if (filemtime($tmp.'/'.$fname) < $expire)
153        @unlink($tmp.'/'.$fname);
154      }
155
156    closedir($dir);
157    }
158  }
159
160
161/**
162 * Garbage collector for cache entries.
163 * Remove all expired message cache records
164 * @return void
165 */
166function rcmail_cache_gc()
167  {
168  $rcmail = rcmail::get_instance();
169  $db = $rcmail->get_dbh();
170 
171  // get target timestamp
172  $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
173 
174  $db->query("DELETE FROM ".get_table_name('messages')."
175             WHERE  created < " . $db->fromunixtime($ts));
176
177  $db->query("DELETE FROM ".get_table_name('cache')."
178              WHERE  created < " . $db->fromunixtime($ts));
179  }
180
181
182/**
183 * Catch an error and throw an exception.
184 *
185 * @param  int    Level of the error
186 * @param  string Error message
187 */
188function rcube_error_handler($errno, $errstr)
189  {
190  throw new ErrorException($errstr, 0, $errno);
191  }
192
193
194/**
195 * Convert a string from one charset to another.
196 * Uses mbstring and iconv functions if possible
197 *
198 * @param  string Input string
199 * @param  string Suspected charset of the input string
200 * @param  string Target charset to convert to; defaults to RCMAIL_CHARSET
201 * @return string Converted string
202 */
203function rcube_charset_convert($str, $from, $to=NULL)
204  {
205  static $iconv_options = null;
206  static $mbstring_loaded = null;
207  static $mbstring_list = null;
208  static $convert_warning = false;
209  static $conv = null;
210
211  $error = false;
212
213  $to = empty($to) ? strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
214  $from = rcube_parse_charset($from);
215
216  if ($from == $to || empty($str) || empty($from))
217    return $str;
218
219  // convert charset using iconv module
220  if (function_exists('iconv') && $from != 'UTF7-IMAP' && $to != 'UTF7-IMAP') {
221    if ($iconv_options === null) {
222      // ignore characters not available in output charset
223      $iconv_options = '//IGNORE';
224      if (iconv('', $iconv_options, '') === false) {
225        // iconv implementation does not support options
226        $iconv_options = '';
227      }
228    }
229
230    // throw an exception if iconv reports an illegal character in input
231    // it means that input string has been truncated
232    set_error_handler('rcube_error_handler', E_NOTICE);
233    try {
234      $_iconv = iconv($from, $to . $iconv_options, $str);
235    } catch (ErrorException $e) {
236      $_iconv = false;
237    }
238    restore_error_handler();
239    if ($_iconv !== false) {
240      return $_iconv;
241    }
242  }
243
244  if ($mbstring_loaded === null)
245    $mbstring_loaded = extension_loaded('mbstring');
246   
247  // convert charset using mbstring module
248  if ($mbstring_loaded) {
249    $aliases['WINDOWS-1257'] = 'ISO-8859-13';
250   
251    if ($mbstring_list === null) {
252      $mbstring_list = mb_list_encodings();
253      $mbstring_list = array_map('strtoupper', $mbstring_list);
254    }
255
256    $mb_from = $aliases[$from] ? $aliases[$from] : $from;
257    $mb_to = $aliases[$to] ? $aliases[$to] : $to;
258   
259    // return if encoding found, string matches encoding and convert succeeded
260    if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
261      if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
262        return $out;
263    }
264  }
265
266  // convert charset using bundled classes/functions
267  if ($to == 'UTF-8') {
268    if ($from == 'UTF7-IMAP') {
269      if ($_str = utf7_to_utf8($str))
270        return $_str;
271    }
272    else if ($from == 'UTF-7') {
273      if ($_str = rcube_utf7_to_utf8($str))
274        return $_str;
275    }
276    else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
277      return utf8_encode($str);
278    }
279    else if (class_exists('utf8')) {
280      if (!$conv)
281        $conv = new utf8($from);
282      else
283        $conv->loadCharset($from);
284
285      if($_str = $conv->strToUtf8($str))
286        return $_str;
287    }
288    $error = true;
289  }
290
291  // encode string for output
292  if ($from == 'UTF-8') {
293    // @TODO: we need a function for UTF-7 (RFC2152) conversion
294    if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
295      if ($_str = utf8_to_utf7($str))
296        return $_str;
297    }
298    else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
299      return utf8_decode($str);
300    }
301    else if (class_exists('utf8')) {
302      if (!$conv)
303        $conv = new utf8($to);
304      else
305        $conv->loadCharset($from);
306
307      if ($_str = $conv->strToUtf8($str))
308        return $_str;
309    }
310    $error = true;
311  }
312
313  // report error
314  if ($error && !$convert_warning) {
315    raise_error(array(
316      'code' => 500,
317      'type' => 'php',
318      'file' => __FILE__,
319      'line' => __LINE__,
320      'message' => "Could not convert string from $from to $to. Make sure iconv/mbstring is installed or lib/utf8.class is available."
321      ), true, false);
322
323    $convert_warning = true;
324  }
325
326  // return UTF-8 or original string
327  return $str;
328  }
329
330
331/**
332 * Parse and validate charset name string (see #1485758).
333 * Sometimes charset string is malformed, there are also charset aliases
334 * but we need strict names for charset conversion (specially utf8 class)
335 *
336 * @param  string Input charset name
337 * @return string The validated charset name
338 */
339function rcube_parse_charset($input)
340  {
341  static $charsets = array();
342  $charset = strtoupper($input);
343
344  if (isset($charsets[$input]))
345    return $charsets[$input];
346
347  $charset = preg_replace(array(
348    '/^[^0-9A-Z]+/',    // e.g. _ISO-8859-JP$SIO
349    '/\$.*$/',          // e.g. _ISO-8859-JP$SIO
350    '/UNICODE-1-1-*/',  // RFC1641/1642
351    '/^X-/',            // X- prefix (e.g. X-ROMAN8 => ROMAN8)
352    ), '', $charset);
353
354  if ($charset == 'BINARY')
355    return $charsets[$input] = null;
356
357  # Aliases: some of them from HTML5 spec.
358  $aliases = array(
359    'USASCII'       => 'WINDOWS-1252',
360    'ANSIX31101983' => 'WINDOWS-1252',
361    'ANSIX341968'   => 'WINDOWS-1252',
362    'UNKNOWN8BIT'   => 'ISO-8859-15',
363    'UNKNOWN'       => 'ISO-8859-15',
364    'USERDEFINED'   => 'ISO-8859-15',
365    'KSC56011987'   => 'EUC-KR',
366    'GB2312'        => 'GBK',
367    'GB231280'      => 'GBK',
368    'UNICODE'       => 'UTF-8',
369    'UTF7IMAP'      => 'UTF7-IMAP',
370    'TIS620'        => 'WINDOWS-874',
371    'ISO88599'      => 'WINDOWS-1254',
372    'ISO885911'     => 'WINDOWS-874',
373    'MACROMAN'      => 'MACINTOSH',
374    '77'            => 'MAC',
375    '128'           => 'SHIFT-JIS',
376    '129'           => 'CP949',
377    '130'           => 'CP1361',
378    '134'           => 'GBK',
379    '136'           => 'BIG5',
380    '161'           => 'WINDOWS-1253',
381    '162'           => 'WINDOWS-1254',
382    '163'           => 'WINDOWS-1258',
383    '177'           => 'WINDOWS-1255',
384    '178'           => 'WINDOWS-1256',
385    '186'           => 'WINDOWS-1257',
386    '204'           => 'WINDOWS-1251',
387    '222'           => 'WINDOWS-874',
388    '238'           => 'WINDOWS-1250',
389    'MS950'         => 'CP950',
390    'WINDOWS949'    => 'UHC',
391  );
392
393  // allow A-Z and 0-9 only
394  $str = preg_replace('/[^A-Z0-9]/', '', $charset);
395
396  if (isset($aliases[$str]))
397    $result = $aliases[$str];
398  // UTF
399  else if (preg_match('/U[A-Z][A-Z](7|8|16|32)(BE|LE)*/', $str, $m))
400    $result = 'UTF-' . $m[1] . $m[2];
401  // ISO-8859
402  else if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
403    $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
404    // some clients sends windows-1252 text as latin1,
405    // it is safe to use windows-1252 for all latin1
406    $result = $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
407    }
408  // handle broken charset names e.g. WINDOWS-1250HTTP-EQUIVCONTENT-TYPE
409  else if (preg_match('/(WIN|WINDOWS)([0-9]+)/', $str, $m)) {
410    $result = 'WINDOWS-' . $m[2];
411    }
412  // LATIN
413  else if (preg_match('/LATIN(.*)/', $str, $m)) {
414    $aliases = array('2' => 2, '3' => 3, '4' => 4, '5' => 9, '6' => 10,
415        '7' => 13, '8' => 14, '9' => 15, '10' => 16,
416        'ARABIC' => 6, 'CYRILLIC' => 5, 'GREEK' => 7, 'GREEK1' => 7, 'HEBREW' => 8);
417
418    // some clients sends windows-1252 text as latin1,
419    // it is safe to use windows-1252 for all latin1
420    if ($m[1] == 1) {
421      $result = 'WINDOWS-1252';
422      }
423    // if iconv is not supported we need ISO labels, it's also safe for iconv
424    else if (!empty($aliases[$m[1]])) {
425      $result = 'ISO-8859-'.$aliases[$m[1]];
426      }
427    // iconv requires convertion of e.g. LATIN-1 to LATIN1
428    else {
429      $result = $str;
430      }
431    }
432  else {
433    $result = $charset;
434    }
435
436  $charsets[$input] = $result;
437
438  return $result;
439  }
440
441
442/**
443 * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
444 *
445 * @param  string  Input string
446 * @return string  The converted string
447 */
448function rcube_utf7_to_utf8($str)
449{
450  $Index_64 = array(
451    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
452    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
453    0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
454    1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
455    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
456    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
457    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
458    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
459  );
460
461  $u7len = strlen($str);
462  $str = strval($str);
463  $res = '';
464
465  for ($i=0; $u7len > 0; $i++, $u7len--)
466  {
467    $u7 = $str[$i];
468    if ($u7 == '+')
469    {
470      $i++;
471      $u7len--;
472      $ch = '';
473
474      for (; $u7len > 0; $i++, $u7len--)
475      {
476        $u7 = $str[$i];
477
478        if (!$Index_64[ord($u7)])
479          break;
480
481        $ch .= $u7;
482      }
483
484      if ($ch == '') {
485        if ($u7 == '-')
486          $res .= '+';
487        continue;
488      }
489
490      $res .= rcube_utf16_to_utf8(base64_decode($ch));
491    }
492    else
493    {
494      $res .= $u7;
495    }
496  }
497
498  return $res;
499}
500
501/**
502 * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
503 *
504 * @param  string  Input string
505 * @return string  The converted string
506 */
507function rcube_utf16_to_utf8($str)
508{
509  $len = strlen($str);
510  $dec = '';
511
512  for ($i = 0; $i < $len; $i += 2) {
513    $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
514    if ($c >= 0x0001 && $c <= 0x007F) {
515      $dec .= chr($c);
516    } else if ($c > 0x07FF) {
517      $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
518      $dec .= chr(0x80 | (($c >>  6) & 0x3F));
519      $dec .= chr(0x80 | (($c >>  0) & 0x3F));
520    } else {
521      $dec .= chr(0xC0 | (($c >>  6) & 0x1F));
522      $dec .= chr(0x80 | (($c >>  0) & 0x3F));
523    }
524  }
525  return $dec;
526}
527
528
529/**
530 * Replacing specials characters to a specific encoding type
531 *
532 * @param  string  Input string
533 * @param  string  Encoding type: text|html|xml|js|url
534 * @param  string  Replace mode for tags: show|replace|remove
535 * @param  boolean Convert newlines
536 * @return string  The quoted string
537 */
538function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
539  {
540  static $html_encode_arr = false;
541  static $js_rep_table = false;
542  static $xml_rep_table = false;
543
544  if (!$enctype)
545    $enctype = $OUTPUT->type;
546
547  // encode for HTML output
548  if ($enctype=='html')
549    {
550    if (!$html_encode_arr)
551      {
552      $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
553      unset($html_encode_arr['?']);
554      }
555
556    $ltpos = strpos($str, '<');
557    $encode_arr = $html_encode_arr;
558
559    // don't replace quotes and html tags
560    if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
561      {
562      unset($encode_arr['"']);
563      unset($encode_arr['<']);
564      unset($encode_arr['>']);
565      unset($encode_arr['&']);
566      }
567    else if ($mode=='remove')
568      $str = strip_tags($str);
569
570    $out = strtr($str, $encode_arr);
571
572    // avoid douple quotation of &
573    $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
574
575    return $newlines ? nl2br($out) : $out;
576    }
577
578  // if the replace tables for XML and JS are not yet defined
579  if ($js_rep_table===false)
580    {
581    $js_rep_table = $xml_rep_table = array();
582    $xml_rep_table['&'] = '&amp;';
583
584    for ($c=160; $c<256; $c++)  // can be increased to support more charsets
585      $xml_rep_table[chr($c)] = "&#$c;";
586
587    $xml_rep_table['"'] = '&quot;';
588    $js_rep_table['"'] = '\\"';
589    $js_rep_table["'"] = "\\'";
590    $js_rep_table["\\"] = "\\\\";
591    // Unicode line and paragraph separators (#1486310)
592    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
593    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
594    }
595
596  // encode for javascript use
597  if ($enctype=='js')
598    return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
599
600  // encode for plaintext
601  if ($enctype=='text')
602    return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
603
604  if ($enctype=='url')
605    return rawurlencode($str);
606
607  // encode for XML
608  if ($enctype=='xml')
609    return strtr($str, $xml_rep_table);
610
611  // no encoding given -> return original string
612  return $str;
613  }
614 
615/**
616 * Quote a given string.
617 * Shortcut function for rep_specialchars_output
618 *
619 * @return string HTML-quoted string
620 * @see rep_specialchars_output()
621 */
622function Q($str, $mode='strict', $newlines=TRUE)
623  {
624  return rep_specialchars_output($str, 'html', $mode, $newlines);
625  }
626
627/**
628 * Quote a given string for javascript output.
629 * Shortcut function for rep_specialchars_output
630 *
631 * @return string JS-quoted string
632 * @see rep_specialchars_output()
633 */
634function JQ($str)
635  {
636  return rep_specialchars_output($str, 'js');
637  }
638
639
640/**
641 * Read input value and convert it for internal use
642 * Performs stripslashes() and charset conversion if necessary
643 *
644 * @param  string   Field name to read
645 * @param  int      Source to get value from (GPC)
646 * @param  boolean  Allow HTML tags in field value
647 * @param  string   Charset to convert into
648 * @return string   Field value or NULL if not available
649 */
650function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
651{
652  $value = NULL;
653 
654  if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
655    $value = $_GET[$fname];
656  else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
657    $value = $_POST[$fname];
658  else if ($source==RCUBE_INPUT_GPC)
659    {
660    if (isset($_POST[$fname]))
661      $value = $_POST[$fname];
662    else if (isset($_GET[$fname]))
663      $value = $_GET[$fname];
664    else if (isset($_COOKIE[$fname]))
665      $value = $_COOKIE[$fname];
666    }
667
668  return parse_input_value($value, $allow_html, $charset);
669}
670
671/**
672 * Parse/validate input value. See get_input_value()
673 * Performs stripslashes() and charset conversion if necessary
674 *
675 * @param  string   Input value
676 * @param  boolean  Allow HTML tags in field value
677 * @param  string   Charset to convert into
678 * @return string   Parsed value
679 */
680function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
681{
682  global $OUTPUT;
683
684  if (empty($value))
685    return $value;
686
687  if (is_array($value)) {
688    foreach ($value as $idx => $val)
689      $value[$idx] = parse_input_value($val, $allow_html, $charset);
690    return $value;
691  }
692
693  // strip single quotes if magic_quotes_sybase is enabled
694  if (ini_get('magic_quotes_sybase'))
695    $value = str_replace("''", "'", $value);
696  // strip slashes if magic_quotes enabled
697  else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
698    $value = stripslashes($value);
699
700  // remove HTML tags if not allowed   
701  if (!$allow_html)
702    $value = strip_tags($value);
703 
704  // convert to internal charset
705  if (is_object($OUTPUT) && $charset)
706    return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
707  else
708    return $value;
709}
710
711/**
712 * Convert array of request parameters (prefixed with _)
713 * to a regular array with non-prefixed keys.
714 *
715 * @param  int   Source to get value from (GPC)
716 * @return array Hash array with all request parameters
717 */
718function request2param($mode = RCUBE_INPUT_GPC)
719{
720  $out = array();
721  $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
722  foreach ($src as $key => $value) {
723    $fname = $key[0] == '_' ? substr($key, 1) : $key;
724    $out[$fname] = get_input_value($key, $mode);
725  }
726 
727  return $out;
728}
729
730/**
731 * Remove all non-ascii and non-word chars
732 * except ., -, _
733 */
734function asciiwords($str, $css_id = false, $replace_with = '')
735{
736  $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
737  return preg_replace("/[^$allowed]/i", $replace_with, $str);
738}
739
740/**
741 * Convert the given string into a valid HTML identifier
742 * Same functionality as done in app.js with this.identifier_expr
743 *
744 */
745function html_identifier($str)
746{
747  return asciiwords($str, true, '_');
748}
749
750/**
751 * Remove single and double quotes from given string
752 *
753 * @param string Input value
754 * @return string Dequoted string
755 */
756function strip_quotes($str)
757{
758  return str_replace(array("'", '"'), '', $str);
759}
760
761
762/**
763 * Remove new lines characters from given string
764 *
765 * @param string Input value
766 * @return string Stripped string
767 */
768function strip_newlines($str)
769{
770  return preg_replace('/[\r\n]/', '', $str);
771}
772
773
774/**
775 * Create a HTML table based on the given data
776 *
777 * @param  array  Named table attributes
778 * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
779 * @param  array  List of cols to show
780 * @param  string Name of the identifier col
781 * @return string HTML table code
782 */
783function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
784  {
785  global $RCMAIL;
786 
787  $table = new html_table(/*array('cols' => count($a_show_cols))*/);
788   
789  // add table header
790  if (!$attrib['noheader'])
791    foreach ($a_show_cols as $col)
792      $table->add_header($col, Q(rcube_label($col)));
793 
794  $c = 0;
795  if (!is_array($table_data))
796  {
797    $db = $RCMAIL->get_dbh();
798    while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
799    {
800      $zebra_class = $c % 2 ? 'even' : 'odd';
801      $table->add_row(array('id' => 'rcmrow' . html_identifier($sql_arr[$id_col]), 'class' => $zebra_class));
802
803      // format each col
804      foreach ($a_show_cols as $col)
805        $table->add($col, Q($sql_arr[$col]));
806     
807      $c++;
808    }
809  }
810  else
811  {
812    foreach ($table_data as $row_data)
813    {
814      $zebra_class = $c % 2 ? 'even' : 'odd';
815      if (!empty($row_data['class']))
816        $zebra_class .= ' '.$row_data['class'];
817
818      $table->add_row(array('id' => 'rcmrow' . html_identifier($row_data[$id_col]), 'class' => $zebra_class));
819
820      // format each col
821      foreach ($a_show_cols as $col)
822        $table->add($col, Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
823       
824      $c++;
825    }
826  }
827
828  return $table->show($attrib);
829  }
830
831
832/**
833 * Create an edit field for inclusion on a form
834 *
835 * @param string col field name
836 * @param string value field value
837 * @param array attrib HTML element attributes for field
838 * @param string type HTML element type (default 'text')
839 * @return string HTML field definition
840 */
841function rcmail_get_edit_field($col, $value, $attrib, $type='text')
842{
843  static $colcounts = array();
844 
845  $fname = '_'.$col;
846  $attrib['name'] = $fname . ($attrib['array'] ? '[]' : '');
847  $attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
848 
849  if ($type == 'checkbox') {
850    $attrib['value'] = '1';
851    $input = new html_checkbox($attrib);
852  }
853  else if ($type == 'textarea') {
854    $attrib['cols'] = $attrib['size'];
855    $input = new html_textarea($attrib);
856  }
857  else if ($type == 'select') {
858    $input = new html_select($attrib);
859    $input->add('---', '');
860    $input->add(array_values($attrib['options']), array_keys($attrib['options']));
861  }
862  else {
863    if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden')
864        $attrib['type'] = 'text';
865    $input = new html_inputfield($attrib);
866  }
867
868  // use value from post
869  if (isset($_POST[$fname])) {
870    $postvalue = get_input_value($fname, RCUBE_INPUT_POST,
871      $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
872    $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
873  }
874
875  $out = $input->show($value);
876
877  return $out;
878}
879
880
881/**
882 * Replace all css definitions with #container [def]
883 * and remove css-inlined scripting
884 *
885 * @param string CSS source code
886 * @param string Container ID to use as prefix
887 * @return string Modified CSS source
888 */
889function rcmail_mod_css_styles($source, $container_id)
890  {
891  $last_pos = 0;
892  $replacements = new rcube_string_replacer;
893
894  // ignore the whole block if evil styles are detected
895  $stripped = preg_replace('/[^a-z\(:;]/', '', rcmail_xss_entity_decode($source));
896  if (preg_match('/expression|behavior|url\(|import[^a]/', $stripped))
897    return '/* evil! */';
898
899  // remove css comments (sometimes used for some ugly hacks)
900  $source = preg_replace('!/\*(.+)\*/!Ums', '', $source);
901
902  // cut out all contents between { and }
903  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
904  {
905    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
906    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
907    $last_pos = $pos+2;
908  }
909
910  // remove html comments and add #container to each tag selector.
911  // also replace body definition because we also stripped off the <body> tag
912  $styles = preg_replace(
913    array(
914      '/(^\s*<!--)|(-->\s*$)/',
915      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
916      '/'.preg_quote($container_id, '/').'\s+body/i',
917    ),
918    array(
919      '',
920      "\\1#$container_id \\2",
921      $container_id,
922    ),
923    $source);
924
925  // put block contents back in
926  $styles = $replacements->resolve($styles);
927
928  return $styles;
929  }
930
931
932/**
933 * Decode escaped entities used by known XSS exploits.
934 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
935 *
936 * @param string CSS content to decode
937 * @return string Decoded string
938 */
939function rcmail_xss_entity_decode($content)
940{
941  $out = html_entity_decode(html_entity_decode($content));
942  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
943  $out = preg_replace('#/\*.*\*/#Um', '', $out);
944  return $out;
945}
946
947
948/**
949 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
950 *
951 * @param array matches result from preg_replace_callback
952 * @return string decoded entity
953 */
954function rcmail_xss_entity_decode_callback($matches)
955{
956  return chr(hexdec($matches[1]));
957}
958
959/**
960 * Compose a valid attribute string for HTML tags
961 *
962 * @param array Named tag attributes
963 * @param array List of allowed attributes
964 * @return string HTML formatted attribute string
965 */
966function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
967  {
968  // allow the following attributes to be added to the <iframe> tag
969  $attrib_str = '';
970  foreach ($allowed_attribs as $a)
971    if (isset($attrib[$a]))
972      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
973
974  return $attrib_str;
975  }
976
977
978/**
979 * Convert a HTML attribute string attributes to an associative array (name => value)
980 *
981 * @param string Input string
982 * @return array Key-value pairs of parsed attributes
983 */
984function parse_attrib_string($str)
985  {
986  $attrib = array();
987  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
988
989  // convert attributes to an associative array (name => value)
990  if ($regs) {
991    foreach ($regs as $attr) {
992      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
993    }
994  }
995
996  return $attrib;
997  }
998
999
1000/**
1001 * Improved equivalent to strtotime()
1002 *
1003 * @param string Date string
1004 * @return int
1005 */
1006function rcube_strtotime($date)
1007{
1008  // check for MS Outlook vCard date format YYYYMMDD
1009  if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
1010    return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
1011  }
1012  else if (is_numeric($date))
1013    return $date;
1014
1015  // support non-standard "GMTXXXX" literal
1016  $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
1017
1018  // if date parsing fails, we have a date in non-rfc format.
1019  // remove token from the end and try again
1020  while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
1021    $d = explode(' ', $date);
1022    array_pop($d);
1023    if (!$d) break;
1024    $date = implode(' ', $d);
1025  }
1026
1027  return $ts;
1028}
1029
1030
1031/**
1032 * Convert the given date to a human readable form
1033 * This uses the date formatting properties from config
1034 *
1035 * @param mixed Date representation (string or timestamp)
1036 * @param string Date format to use
1037 * @return string Formatted date string
1038 */
1039function format_date($date, $format=NULL)
1040{
1041  global $CONFIG;
1042 
1043  $ts = NULL;
1044
1045  if (!empty($date))
1046    $ts = rcube_strtotime($date);
1047
1048  if (empty($ts))
1049    return '';
1050
1051  // get user's timezone
1052  if ($CONFIG['timezone'] === 'auto')
1053    $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
1054  else {
1055    $tz = $CONFIG['timezone'];
1056    if ($CONFIG['dst_active'])
1057      $tz++;
1058  }
1059
1060  // convert time to user's timezone
1061  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
1062
1063  // get current timestamp in user's timezone
1064  $now = time();  // local time
1065  $now -= (int)date('Z'); // make GMT time
1066  $now += ($tz * 3600); // user's time
1067  $now_date = getdate($now);
1068
1069  $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1070  $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
1071
1072  // define date format depending on current time
1073  if (!$format) {
1074    if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
1075      $format = $CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i';
1076      $today  = true;
1077    }
1078    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1079      $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
1080    else
1081      $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
1082  }
1083
1084  // strftime() format
1085  if (preg_match('/%[a-z]+/i', $format)) {
1086    $format = strftime($format, $timestamp);
1087    return $today ? (rcube_label('today') . ' ' . $format) : $format;
1088  }
1089
1090  // parse format string manually in order to provide localized weekday and month names
1091  // an alternative would be to convert the date() format string to fit with strftime()
1092  $out = '';
1093  for($i=0; $i<strlen($format); $i++) {
1094    if ($format{$i}=='\\')  // skip escape chars
1095      continue;
1096
1097    // write char "as-is"
1098    if ($format{$i}==' ' || $format{$i-1}=='\\')
1099      $out .= $format{$i};
1100    // weekday (short)
1101    else if ($format{$i}=='D')
1102      $out .= rcube_label(strtolower(date('D', $timestamp)));
1103    // weekday long
1104    else if ($format{$i}=='l')
1105      $out .= rcube_label(strtolower(date('l', $timestamp)));
1106    // month name (short)
1107    else if ($format{$i}=='M')
1108      $out .= rcube_label(strtolower(date('M', $timestamp)));
1109    // month name (long)
1110    else if ($format{$i}=='F')
1111      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1112    else if ($format{$i}=='x')
1113      $out .= strftime('%x %X', $timestamp);
1114    else
1115      $out .= date($format{$i}, $timestamp);
1116  }
1117
1118  if ($today) {
1119    $label = rcube_label('today');
1120    // replcae $ character with "Today" label (#1486120)
1121    if (strpos($out, '$') !== false) {
1122      $out = preg_replace('/\$/', $label, $out, 1);
1123    }
1124    else {
1125      $out = $label . ' ' . $out;
1126    }
1127  }
1128
1129  return $out;
1130}
1131
1132
1133/**
1134 * Compose a valid representation of name and e-mail address
1135 *
1136 * @param string E-mail address
1137 * @param string Person name
1138 * @return string Formatted string
1139 */
1140function format_email_recipient($email, $name='')
1141{
1142  if ($name && $name != $email) {
1143    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1144    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1145  }
1146
1147  return trim($email);
1148}
1149
1150
1151/**
1152 * Return the mailboxlist in HTML
1153 *
1154 * @param array Named parameters
1155 * @return string HTML code for the gui object
1156 */
1157function rcmail_mailbox_list($attrib)
1158{
1159  global $RCMAIL;
1160  static $a_mailboxes;
1161
1162  $attrib += array('maxlength' => 100, 'realnames' => false);
1163
1164  // add some labels to client
1165  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1166
1167  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1168  unset($attrib['type']);
1169
1170  if ($type=='ul' && !$attrib['id'])
1171    $attrib['id'] = 'rcmboxlist';
1172
1173  if (empty($attrib['folder_name']))
1174    $attrib['folder_name'] = '*';
1175
1176  // get mailbox list
1177  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1178
1179  // build the folders tree
1180  if (empty($a_mailboxes)) {
1181    // get mailbox list
1182    $a_folders = $RCMAIL->imap->list_mailboxes('', $attrib['folder_name'], $attrib['folder_filter']);
1183    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1184    $a_mailboxes = array();
1185
1186    foreach ($a_folders as $folder)
1187      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1188  }
1189
1190  // allow plugins to alter the folder tree or to localize folder names
1191  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1192
1193  if ($type == 'select') {
1194    $select = new html_select($attrib);
1195
1196    // add no-selection option
1197    if ($attrib['noselection'])
1198      $select->add(rcube_label($attrib['noselection']), '');
1199
1200    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1201    $out = $select->show();
1202  }
1203  else {
1204    $js_mailboxlist = array();
1205    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1206
1207    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1208    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1209    $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1210  }
1211
1212  return $out;
1213}
1214
1215
1216/**
1217 * Return the mailboxlist as html_select object
1218 *
1219 * @param array Named parameters
1220 * @return html_select HTML drop-down object
1221 */
1222function rcmail_mailbox_select($p = array())
1223{
1224  global $RCMAIL;
1225
1226  $p += array('maxlength' => 100, 'realnames' => false);
1227  $a_mailboxes = array();
1228
1229  if (empty($p['folder_name']))
1230    $p['folder_name'] = '*';
1231
1232  if ($p['unsubscribed'])
1233    $list = $RCMAIL->imap->list_unsubscribed('', $p['folder_name'], $p['folder_filter']);
1234  else
1235    $list = $RCMAIL->imap->list_mailboxes('', $p['folder_name'], $p['folder_filter']);
1236
1237  $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1238
1239  foreach ($list as $folder) {
1240    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1241      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1242  }
1243
1244  $select = new html_select($p);
1245
1246  if ($p['noselection'])
1247    $select->add($p['noselection'], '');
1248
1249  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1250
1251  return $select;
1252}
1253
1254
1255/**
1256 * Create a hierarchical array of the mailbox list
1257 * @access private
1258 * @return void
1259 */
1260function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1261{
1262  global $RCMAIL;
1263
1264  // Handle namespace prefix
1265  $prefix = '';
1266  if (!$path) {
1267    $n_folder = $folder;
1268    $folder = $RCMAIL->imap->mod_mailbox($folder);
1269
1270    if ($n_folder != $folder) {
1271      $prefix = substr($n_folder, 0, -strlen($folder));
1272    }
1273  }
1274
1275  $pos = strpos($folder, $delm);
1276
1277  if ($pos !== false) {
1278    $subFolders = substr($folder, $pos+1);
1279    $currentFolder = substr($folder, 0, $pos);
1280
1281    // sometimes folder has a delimiter as the last character
1282    if (!strlen($subFolders))
1283      $virtual = false;
1284    else if (!isset($arrFolders[$currentFolder]))
1285      $virtual = true;
1286    else
1287      $virtual = $arrFolders[$currentFolder]['virtual'];
1288  }
1289  else {
1290    $subFolders = false;
1291    $currentFolder = $folder;
1292    $virtual = false;
1293  }
1294
1295  $path .= $prefix.$currentFolder;
1296
1297  if (!isset($arrFolders[$currentFolder])) {
1298    // Check \Noselect option (if options are in cache)
1299    if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
1300      $virtual = in_array('\\Noselect', $opts);
1301    }
1302
1303    $arrFolders[$currentFolder] = array(
1304      'id' => $path,
1305      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1306      'virtual' => $virtual,
1307      'folders' => array());
1308  }
1309  else
1310    $arrFolders[$currentFolder]['virtual'] = $virtual;
1311
1312  if (strlen($subFolders))
1313    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1314}
1315
1316
1317/**
1318 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1319 * @access private
1320 * @return string
1321 */
1322function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1323{
1324  global $RCMAIL, $CONFIG;
1325
1326  $maxlength = intval($attrib['maxlength']);
1327  $realnames = (bool)$attrib['realnames'];
1328  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1329
1330  $idx = 0;
1331  $out = '';
1332  foreach ($arrFolders as $key => $folder) {
1333    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1334    $title = null;
1335
1336    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1337      $foldername = rcube_label($folder_class);
1338    }
1339    else {
1340      $foldername = $folder['name'];
1341
1342      // shorten the folder name to a given length
1343      if ($maxlength && $maxlength > 1) {
1344        $fname = abbreviate_string($foldername, $maxlength);
1345        if ($fname != $foldername)
1346          $title = $foldername;
1347        $foldername = $fname;
1348      }
1349    }
1350
1351    // make folder name safe for ids and class names
1352    $folder_id = html_identifier($folder['id']);
1353    $classes = array('mailbox');
1354
1355    // set special class for Sent, Drafts, Trash and Junk
1356    if ($folder['id']==$CONFIG['sent_mbox'])
1357      $classes[] = 'sent';
1358    else if ($folder['id']==$CONFIG['drafts_mbox'])
1359      $classes[] = 'drafts';
1360    else if ($folder['id']==$CONFIG['trash_mbox'])
1361      $classes[] = 'trash';
1362    else if ($folder['id']==$CONFIG['junk_mbox'])
1363      $classes[] = 'junk';
1364    else if ($folder['id']=='INBOX')
1365      $classes[] = 'inbox';
1366    else
1367      $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1368
1369    $classes[] = $zebra_class;
1370
1371    if ($folder['id'] == $mbox_name)
1372      $classes[] = 'selected';
1373
1374    $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1375    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1376
1377    if ($folder['virtual'])
1378      $classes[] = 'virtual';
1379    else if ($unread)
1380      $classes[] = 'unread';
1381
1382    $js_name = JQ($folder['id']);
1383    $html_name = Q($foldername) . ($unread ? html::span('unreadcount', " ($unread)") : '');
1384    $link_attrib = $folder['virtual'] ? array() : array(
1385      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1386      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1387      'rel' => $folder['id'],
1388      'title' => $title,
1389    );
1390
1391    $out .= html::tag('li', array(
1392        'id' => "rcmli".$folder_id,
1393        'class' => join(' ', $classes),
1394        'noclose' => true),
1395      html::a($link_attrib, $html_name) .
1396      (!empty($folder['folders']) ? html::div(array(
1397        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1398        'style' => "position:absolute",
1399        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1400      ), '&nbsp;') : ''));
1401
1402    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1403
1404    if (!empty($folder['folders'])) {
1405      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1406        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1407    }
1408
1409    $out .= "</li>\n";
1410    $idx++;
1411  }
1412
1413  return $out;
1414}
1415
1416
1417/**
1418 * Return html for a flat list <select> for the mailbox tree
1419 * @access private
1420 * @return string
1421 */
1422function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1423{
1424  $out = '';
1425
1426  foreach ($arrFolders as $key=>$folder) {
1427    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1428      $foldername = rcube_label($folder_class);
1429    else {
1430      $foldername = $folder['name'];
1431
1432      // shorten the folder name to a given length
1433      if ($maxlength && $maxlength>1)
1434        $foldername = abbreviate_string($foldername, $maxlength);
1435    }
1436
1437    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1438
1439    if (!empty($folder['folders']))
1440      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1441  }
1442
1443  return $out;
1444}
1445
1446
1447/**
1448 * Return internal name for the given folder if it matches the configured special folders
1449 * @access private
1450 * @return string
1451 */
1452function rcmail_folder_classname($folder_id)
1453{
1454  global $CONFIG;
1455
1456  if ($folder_id == 'INBOX')
1457    return 'inbox';
1458
1459  // for these mailboxes we have localized labels and css classes
1460  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1461  {
1462    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1463      return $smbx;
1464  }
1465}
1466
1467
1468/**
1469 * Try to localize the given IMAP folder name.
1470 * UTF-7 decode it in case no localized text was found
1471 *
1472 * @param string Folder name
1473 * @return string Localized folder name in UTF-8 encoding
1474 */
1475function rcmail_localize_foldername($name)
1476{
1477  if ($folder_class = rcmail_folder_classname($name))
1478    return rcube_label($folder_class);
1479  else
1480    return rcube_charset_convert($name, 'UTF7-IMAP');
1481}
1482
1483
1484function rcmail_quota_display($attrib)
1485{
1486  global $OUTPUT;
1487
1488  if (!$attrib['id'])
1489    $attrib['id'] = 'rcmquotadisplay';
1490
1491  if(isset($attrib['display']))
1492    $_SESSION['quota_display'] = $attrib['display'];
1493
1494  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1495
1496  $quota = rcmail_quota_content($attrib);
1497
1498  $OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready');
1499
1500  return html::span($attrib, '');
1501}
1502
1503
1504function rcmail_quota_content($attrib=NULL)
1505{
1506  global $RCMAIL;
1507
1508  $quota = $RCMAIL->imap->get_quota();
1509  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1510
1511  $quota_result = (array) $quota;
1512  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1513
1514  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1515    $quota_result['title'] = rcube_label('unlimited');
1516    $quota_result['percent'] = 0;
1517  }
1518  else if ($quota['total']) {
1519    if (!isset($quota['percent']))
1520      $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1521
1522    $title = sprintf('%s / %s (%.0f%%)',
1523        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1524        $quota_result['percent']);
1525
1526    $quota_result['title'] = $title;
1527
1528    if ($attrib['width'])
1529      $quota_result['width'] = $attrib['width'];
1530    if ($attrib['height'])
1531      $quota_result['height']   = $attrib['height'];
1532  }
1533  else {
1534    $quota_result['title'] = rcube_label('unknown');
1535    $quota_result['percent'] = 0;
1536  }
1537
1538  return $quota_result;
1539}
1540
1541
1542/**
1543 * Outputs error message according to server error/response codes
1544 *
1545 * @param string Fallback message label
1546 * @param string Fallback message label arguments
1547 *
1548 * @return void
1549 */
1550function rcmail_display_server_error($fallback=null, $fallback_args=null)
1551{
1552    global $RCMAIL;
1553
1554    $err_code = $RCMAIL->imap->get_error_code();
1555    $res_code = $RCMAIL->imap->get_response_code();
1556
1557    if ($res_code == rcube_imap::NOPERM) {
1558        $RCMAIL->output->show_message('errornoperm', 'error');
1559    }
1560    else if ($res_code == rcube_imap::READONLY) {
1561        $RCMAIL->output->show_message('errorreadonly', 'error');
1562    }
1563    else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1564        $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1565    }
1566    else if ($fallback) {
1567        $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1568    }
1569
1570    return true;
1571}
1572
1573
1574/**
1575 * Output HTML editor scripts
1576 *
1577 * @param string Editor mode
1578 * @return void
1579 */
1580function rcube_html_editor($mode='')
1581{
1582  global $RCMAIL, $CONFIG;
1583
1584  $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
1585
1586  if ($hook['abort'])
1587    return; 
1588
1589  $lang = strtolower($_SESSION['language']);
1590
1591  // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1592  $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1593
1594  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1595    $lang = 'en';
1596
1597  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1598  $RCMAIL->output->include_script('editor.js');
1599  $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
1600    JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
1601    'foot');
1602}
1603
1604
1605/**
1606 * Replaces TinyMCE's emoticon images with plain-text representation
1607 *
1608 * @param string HTML content
1609 * @return string HTML content
1610 */
1611function rcmail_replace_emoticons($html)
1612{
1613  $emoticons = array(
1614    '8-)' => 'smiley-cool',
1615    ':-#' => 'smiley-foot-in-mouth',
1616    ':-*' => 'smiley-kiss',
1617    ':-X' => 'smiley-sealed',
1618    ':-P' => 'smiley-tongue-out',
1619    ':-@' => 'smiley-yell',
1620    ":'(" => 'smiley-cry',
1621    ':-(' => 'smiley-frown',
1622    ':-D' => 'smiley-laughing',
1623    ':-)' => 'smiley-smile',
1624    ':-S' => 'smiley-undecided',
1625    ':-$' => 'smiley-embarassed',
1626    'O:-)' => 'smiley-innocent',
1627    ':-|' => 'smiley-money-mouth',
1628    ':-O' => 'smiley-surprised',
1629    ';-)' => 'smiley-wink',
1630  );
1631
1632  foreach ($emoticons as $idx => $file) {
1633    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1634    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1635    $replace[] = $idx;
1636  }
1637
1638  return preg_replace($search, $replace, $html);
1639}
1640
1641
1642/**
1643 * Check if working in SSL mode
1644 *
1645 * @param integer HTTPS port number
1646 * @param boolean Enables 'use_https' option checking
1647 * @return boolean
1648 */
1649function rcube_https_check($port=null, $use_https=true)
1650{
1651  global $RCMAIL;
1652
1653  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1654    return true;
1655  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1656    return true;
1657  if ($port && $_SERVER['SERVER_PORT'] == $port)
1658    return true;
1659  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1660    return true;
1661
1662  return false;
1663}
1664
1665
1666/**
1667 * For backward compatibility.
1668 *
1669 * @global rcmail $RCMAIL
1670 * @param string $var_name Variable name.
1671 * @return void
1672 */
1673function rcube_sess_unset($var_name=null)
1674{
1675  global $RCMAIL;
1676
1677  $RCMAIL->session->remove($var_name);
1678}
1679
1680
1681/**
1682 * Replaces hostname variables
1683 *
1684 * @param string $name Hostname
1685 * @param string $host Optional IMAP hostname
1686 * @return string
1687 */
1688function rcube_parse_host($name, $host='')
1689{
1690  // %n - host
1691  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1692  // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
1693  $d = preg_replace('/^[^\.]+\./', '', $n);
1694  // %h - IMAP host
1695  $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
1696  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1697  $z = preg_replace('/^[^\.]+\./', '', $h);
1698  // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
1699  if ( strpos($name, '%s') !== false ){
1700    $user_email = rcube_idn_convert(get_input_value('_user', RCUBE_INPUT_POST), true);
1701    if ( preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s) < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false )
1702      return false;
1703  }
1704
1705  $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
1706  return $name;
1707}
1708
1709
1710/**
1711 * E-mail address validation
1712 *
1713 * @param string $email Email address
1714 * @param boolean $dns_check True to check dns
1715 * @return boolean
1716 */
1717function check_email($email, $dns_check=true)
1718{
1719  // Check for invalid characters
1720  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1721    return false;
1722
1723  // Check for length limit specified by RFC 5321 (#1486453)
1724  if (strlen($email) > 254)
1725    return false;
1726
1727  $email_array = explode('@', $email);
1728
1729  // Check that there's one @ symbol
1730  if (count($email_array) < 2)
1731    return false;
1732
1733  $domain_part = array_pop($email_array);
1734  $local_part = implode('@', $email_array);
1735
1736  // from PEAR::Validate
1737  $regexp = '&^(?:
1738        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1739        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
1740        $&xi';
1741
1742  if (!preg_match($regexp, $local_part))
1743    return false;
1744
1745  // Check domain part
1746  if (preg_match('/^\[*(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])(\.(25[0-5]|2[0-4][0-9]|1[0-9][0-9]|[1-9]?[0-9])){3}\]*$/', $domain_part))
1747    return true; // IP address
1748  else {
1749    // If not an IP address
1750    $domain_array = explode('.', $domain_part);
1751    if (sizeof($domain_array) < 2)
1752      return false; // Not enough parts to be a valid domain
1753
1754    foreach ($domain_array as $part)
1755      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1756        return false;
1757
1758    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1759      return true;
1760
1761    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1762      $lookup = array();
1763      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1764      foreach ($lookup as $line) {
1765        if (strpos($line, 'MX preference'))
1766          return true;
1767      }
1768      return false;
1769    }
1770
1771    // find MX record(s)
1772    if (getmxrr($domain_part, $mx_records))
1773      return true;
1774
1775    // find any DNS record
1776    if (checkdnsrr($domain_part, 'ANY'))
1777      return true;
1778  }
1779
1780  return false;
1781}
1782
1783/*
1784 * Idn_to_ascii wrapper.
1785 * Intl/Idn modules version of this function doesn't work with e-mail address
1786 */
1787function rcube_idn_to_ascii($str)
1788{
1789  return rcube_idn_convert($str, true);
1790}
1791
1792/*
1793 * Idn_to_ascii wrapper.
1794 * Intl/Idn modules version of this function doesn't work with e-mail address
1795 */
1796function rcube_idn_to_utf8($str)
1797{
1798  return rcube_idn_convert($str, false);
1799}
1800
1801function rcube_idn_convert($input, $is_utf=false)
1802{
1803  if ($at = strpos($input, '@')) {
1804    $user   = substr($input, 0, $at);
1805    $domain = substr($input, $at+1);
1806  }
1807  else {
1808    $domain = $input;
1809  }
1810
1811  $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
1812
1813  return $at ? $user . '@' . $domain : $domain;
1814}
1815
1816
1817/**
1818 * Helper class to turn relative urls into absolute ones
1819 * using a predefined base
1820 */
1821class rcube_base_replacer
1822{
1823  private $base_url;
1824
1825  public function __construct($base)
1826  {
1827    $this->base_url = $base;
1828  }
1829
1830  public function callback($matches)
1831  {
1832    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1833  }
1834}
1835
1836
1837/****** debugging and logging functions ********/
1838
1839/**
1840 * Print or write debug messages
1841 *
1842 * @param mixed Debug message or data
1843 * @return void
1844 */
1845function console()
1846{
1847    $args = func_get_args();
1848
1849    if (class_exists('rcmail', false)) {
1850        $rcmail = rcmail::get_instance();
1851        if (is_object($rcmail->plugins)) {
1852            $plugin = $rcmail->plugins->exec_hook('console', array('args' => $args));
1853            if ($plugin['abort'])
1854                return;
1855            $args = $plugin['args'];
1856        }
1857    }
1858
1859    $msg = array();
1860    foreach ($args as $arg)
1861        $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1862
1863    write_log('console', join(";\n", $msg));
1864}
1865
1866
1867/**
1868 * Append a line to a logfile in the logs directory.
1869 * Date will be added automatically to the line.
1870 *
1871 * @param $name name of log file
1872 * @param line Line to append
1873 * @return void
1874 */
1875function write_log($name, $line)
1876{
1877  global $CONFIG, $RCMAIL;
1878
1879  if (!is_string($line))
1880    $line = var_export($line, true);
1881 
1882  if (empty($CONFIG['log_date_format']))
1883    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1884 
1885  $date = date($CONFIG['log_date_format']);
1886 
1887  // trigger logging hook
1888  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1889    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1890    $name = $log['name'];
1891    $line = $log['line'];
1892    $date = $log['date'];
1893    if ($log['abort'])
1894      return true;
1895  }
1896 
1897  if ($CONFIG['log_driver'] == 'syslog') {
1898    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1899    syslog($prio, $line);
1900    return true;
1901  }
1902  else {
1903    $line = sprintf("[%s]: %s\n", $date, $line);
1904
1905    // log_driver == 'file' is assumed here
1906    if (empty($CONFIG['log_dir']))
1907      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1908
1909    // try to open specific log file for writing
1910    $logfile = $CONFIG['log_dir'].'/'.$name;
1911    if ($fp = @fopen($logfile, 'a')) {
1912      fwrite($fp, $line);
1913      fflush($fp);
1914      fclose($fp);
1915      return true;
1916    }
1917    else
1918      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1919  }
1920
1921  return false;
1922}
1923
1924
1925/**
1926 * Write login data (name, ID, IP address) to the 'userlogins' log file.
1927 *
1928 * @return void
1929 */
1930function rcmail_log_login()
1931{
1932  global $RCMAIL;
1933
1934  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1935    return;
1936
1937  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1938    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip()));
1939}
1940
1941
1942/**
1943 * Returns remote IP address and forwarded addresses if found
1944 *
1945 * @return string Remote IP address(es)
1946 */
1947function rcmail_remote_ip()
1948{
1949    $address = $_SERVER['REMOTE_ADDR'];
1950
1951    // append the NGINX X-Real-IP header, if set
1952    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1953        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1954    }
1955    // append the X-Forwarded-For header, if set
1956    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1957        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1958    }
1959
1960    if (!empty($remote_ip))
1961        $address .= '(' . implode(',', $remote_ip) . ')';
1962
1963    return $address;
1964}
1965
1966
1967/**
1968 * Check whether the HTTP referer matches the current request
1969 *
1970 * @return boolean True if referer is the same host+path, false if not
1971 */
1972function rcube_check_referer()
1973{
1974  $uri = parse_url($_SERVER['REQUEST_URI']);
1975  $referer = parse_url(rc_request_header('Referer'));
1976  return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
1977}
1978
1979
1980/**
1981 * @access private
1982 * @return mixed
1983 */
1984function rcube_timer()
1985{
1986  return microtime(true);
1987}
1988
1989
1990/**
1991 * @access private
1992 * @return void
1993 */
1994function rcube_print_time($timer, $label='Timer', $dest='console')
1995{
1996  static $print_count = 0;
1997
1998  $print_count++;
1999  $now = rcube_timer();
2000  $diff = $now-$timer;
2001
2002  if (empty($label))
2003    $label = 'Timer '.$print_count;
2004
2005  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
2006}
2007
2008
2009/**
2010 * Throw system error and show error page
2011 *
2012 * @param array Named parameters
2013 *  - code: Error code (required)
2014 *  - type: Error type [php|db|imap|javascript] (required)
2015 *  - message: Error message
2016 *  - file: File where error occured
2017 *  - line: Line where error occured
2018 * @param boolean True to log the error
2019 * @param boolean Terminate script execution
2020 */
2021// may be defined in Installer
2022if (!function_exists('raise_error')) {
2023function raise_error($arg=array(), $log=false, $terminate=false)
2024{
2025    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
2026
2027    // report bug (if not incompatible browser)
2028    if ($log && $arg['type'] && $arg['message'])
2029        rcube_log_bug($arg);
2030
2031    // display error page and terminate script
2032    if ($terminate) {
2033        $ERROR_CODE = $arg['code'];
2034        $ERROR_MESSAGE = $arg['message'];
2035        include INSTALL_PATH . 'program/steps/utils/error.inc';
2036        exit;
2037    }
2038}
2039}
2040
2041
2042/**
2043 * Report error according to configured debug_level
2044 *
2045 * @param array Named parameters
2046 * @return void
2047 * @see raise_error()
2048 */
2049function rcube_log_bug($arg_arr)
2050{
2051    global $CONFIG;
2052
2053    $program = strtoupper($arg_arr['type']);
2054    $level   = $CONFIG['debug_level'];
2055
2056    // disable errors for ajax requests, write to log instead (#1487831)
2057    if (($level & 4) && !empty($_REQUEST['_remote'])) {
2058        $level = ($level ^ 4) | 1;
2059    }
2060
2061    // write error to local log file
2062    if ($level & 1) {
2063        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
2064        $log_entry = sprintf("%s Error: %s%s (%s %s)",
2065            $program,
2066            $arg_arr['message'],
2067            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
2068            $_SERVER['REQUEST_METHOD'],
2069            $_SERVER['REQUEST_URI'] . $post_query);
2070
2071        if (!write_log('errors', $log_entry)) {
2072            // send error to PHPs error handler if write_log didn't succeed
2073            trigger_error($arg_arr['message']);
2074        }
2075    }
2076
2077    // report the bug to the global bug reporting system
2078    if ($level & 2) {
2079        // TODO: Send error via HTTP
2080    }
2081
2082    // show error if debug_mode is on
2083    if ($level & 4) {
2084        print "<b>$program Error";
2085
2086        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2087            print " in $arg_arr[file] ($arg_arr[line])";
2088
2089        print ':</b>&nbsp;';
2090        print nl2br($arg_arr['message']);
2091        print '<br />';
2092        flush();
2093    }
2094}
2095
Note: See TracBrowser for help on using the repository browser.