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

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