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

Last change on this file since 5423 was 5423, checked in by alec, 19 months ago
  • Don't consider \Noselect flag when building folders tree (#1488004)
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 64.9 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    $arrFolders[$currentFolder] = array(
1285      'id' => $path,
1286      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1287      'virtual' => $virtual,
1288      'folders' => array());
1289  }
1290  else
1291    $arrFolders[$currentFolder]['virtual'] = $virtual;
1292
1293  if (strlen($subFolders))
1294    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1295}
1296
1297
1298/**
1299 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1300 * @access private
1301 * @return string
1302 */
1303function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1304{
1305  global $RCMAIL, $CONFIG;
1306
1307  $maxlength = intval($attrib['maxlength']);
1308  $realnames = (bool)$attrib['realnames'];
1309  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1310
1311  $out = '';
1312  foreach ($arrFolders as $key => $folder) {
1313    $title        = null;
1314    $folder_class = rcmail_folder_classname($folder['id']);
1315    $collapsed    = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false;
1316    $unread       = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1317
1318    if ($folder_class && !$realnames) {
1319      $foldername = rcube_label($folder_class);
1320    }
1321    else {
1322      $foldername = $folder['name'];
1323
1324      // shorten the folder name to a given length
1325      if ($maxlength && $maxlength > 1) {
1326        $fname = abbreviate_string($foldername, $maxlength);
1327        if ($fname != $foldername)
1328          $title = $foldername;
1329        $foldername = $fname;
1330      }
1331    }
1332
1333    // make folder name safe for ids and class names
1334    $folder_id = html_identifier($folder['id']);
1335    $classes = array('mailbox');
1336
1337    // set special class for Sent, Drafts, Trash and Junk
1338    if ($folder_class)
1339      $classes[] = $folder_class;
1340
1341    if ($folder['id'] == $mbox_name)
1342      $classes[] = 'selected';
1343
1344    if ($folder['virtual'])
1345      $classes[] = 'virtual';
1346    else if ($unread)
1347      $classes[] = 'unread';
1348
1349    $js_name = JQ($folder['id']);
1350    $html_name = Q($foldername) . ($unread ? html::span('unreadcount', " ($unread)") : '');
1351    $link_attrib = $folder['virtual'] ? array() : array(
1352      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1353      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1354      'rel' => $folder['id'],
1355      'title' => $title,
1356    );
1357
1358    $out .= html::tag('li', array(
1359        'id' => "rcmli".$folder_id,
1360        'class' => join(' ', $classes),
1361        'noclose' => true),
1362      html::a($link_attrib, $html_name) .
1363      (!empty($folder['folders']) ? html::div(array(
1364        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1365        'style' => "position:absolute",
1366        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1367      ), '&nbsp;') : ''));
1368
1369    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1370
1371    if (!empty($folder['folders'])) {
1372      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1373        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1374    }
1375
1376    $out .= "</li>\n";
1377  }
1378
1379  return $out;
1380}
1381
1382
1383/**
1384 * Return html for a flat list <select> for the mailbox tree
1385 * @access private
1386 * @return string
1387 */
1388function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0, $opts=array())
1389{
1390  global $RCMAIL;
1391
1392  $out = '';
1393
1394  foreach ($arrFolders as $key => $folder) {
1395    // skip exceptions (and its subfolders)
1396    if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
1397      continue;
1398    }
1399
1400    // skip folders in which it isn't possible to create subfolders
1401    if (!empty($opts['skip_noinferiors']) && ($attrs = $RCMAIL->imap->mailbox_attributes($folder['id']))
1402        && in_array('\\Noinferiors', $attrs)
1403    ) {
1404      continue;
1405    }
1406
1407    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1408      $foldername = rcube_label($folder_class);
1409    else {
1410      $foldername = $folder['name'];
1411
1412      // shorten the folder name to a given length
1413      if ($maxlength && $maxlength>1)
1414        $foldername = abbreviate_string($foldername, $maxlength);
1415    }
1416
1417    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1418
1419    if (!empty($folder['folders']))
1420      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
1421        $select, $realnames, $nestLevel+1, $opts);
1422  }
1423
1424  return $out;
1425}
1426
1427
1428/**
1429 * Return internal name for the given folder if it matches the configured special folders
1430 * @access private
1431 * @return string
1432 */
1433function rcmail_folder_classname($folder_id)
1434{
1435  global $CONFIG;
1436
1437  if ($folder_id == 'INBOX')
1438    return 'inbox';
1439
1440  // for these mailboxes we have localized labels and css classes
1441  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1442  {
1443    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1444      return $smbx;
1445  }
1446}
1447
1448
1449/**
1450 * Try to localize the given IMAP folder name.
1451 * UTF-7 decode it in case no localized text was found
1452 *
1453 * @param string Folder name
1454 * @return string Localized folder name in UTF-8 encoding
1455 */
1456function rcmail_localize_foldername($name)
1457{
1458  if ($folder_class = rcmail_folder_classname($name))
1459    return rcube_label($folder_class);
1460  else
1461    return rcube_charset_convert($name, 'UTF7-IMAP');
1462}
1463
1464
1465function rcmail_localize_folderpath($path)
1466{
1467    global $RCMAIL;
1468
1469    $protect_folders = $RCMAIL->config->get('protect_default_folders');
1470    $default_folders = (array) $RCMAIL->config->get('default_imap_folders');
1471    $delimiter       = $RCMAIL->imap->get_hierarchy_delimiter();
1472    $path            = explode($delimiter, $path);
1473    $result          = array();
1474
1475    foreach ($path as $idx => $dir) {
1476        $directory = implode($delimiter, array_slice($path, 0, $idx+1));
1477        if ($protect_folders && in_array($directory, $default_folders)) {
1478            unset($result);
1479            $result[] = rcmail_localize_foldername($directory);
1480        }
1481        else {
1482            $result[] = rcube_charset_convert($dir, 'UTF7-IMAP');
1483        }
1484    }
1485
1486    return implode($delimiter, $result);
1487}
1488
1489
1490function rcmail_quota_display($attrib)
1491{
1492  global $OUTPUT;
1493
1494  if (!$attrib['id'])
1495    $attrib['id'] = 'rcmquotadisplay';
1496
1497  if(isset($attrib['display']))
1498    $_SESSION['quota_display'] = $attrib['display'];
1499
1500  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1501
1502  $quota = rcmail_quota_content($attrib);
1503
1504  $OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready');
1505
1506  return html::span($attrib, '');
1507}
1508
1509
1510function rcmail_quota_content($attrib=NULL)
1511{
1512  global $RCMAIL;
1513
1514  $quota = $RCMAIL->imap->get_quota();
1515  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1516
1517  $quota_result = (array) $quota;
1518  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1519
1520  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1521    $quota_result['title'] = rcube_label('unlimited');
1522    $quota_result['percent'] = 0;
1523  }
1524  else if ($quota['total']) {
1525    if (!isset($quota['percent']))
1526      $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1527
1528    $title = sprintf('%s / %s (%.0f%%)',
1529        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1530        $quota_result['percent']);
1531
1532    $quota_result['title'] = $title;
1533
1534    if ($attrib['width'])
1535      $quota_result['width'] = $attrib['width'];
1536    if ($attrib['height'])
1537      $quota_result['height']   = $attrib['height'];
1538  }
1539  else {
1540    $quota_result['title'] = rcube_label('unknown');
1541    $quota_result['percent'] = 0;
1542  }
1543
1544  return $quota_result;
1545}
1546
1547
1548/**
1549 * Outputs error message according to server error/response codes
1550 *
1551 * @param string Fallback message label
1552 * @param string Fallback message label arguments
1553 *
1554 * @return void
1555 */
1556function rcmail_display_server_error($fallback=null, $fallback_args=null)
1557{
1558    global $RCMAIL;
1559
1560    $err_code = $RCMAIL->imap->get_error_code();
1561    $res_code = $RCMAIL->imap->get_response_code();
1562
1563    if ($res_code == rcube_imap::NOPERM) {
1564        $RCMAIL->output->show_message('errornoperm', 'error');
1565    }
1566    else if ($res_code == rcube_imap::READONLY) {
1567        $RCMAIL->output->show_message('errorreadonly', 'error');
1568    }
1569    else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1570        // try to detect access rights problem and display appropriate message
1571        if (stripos($err_str, 'Permission denied') !== false)
1572            $RCMAIL->output->show_message('errornoperm', 'error');
1573        else
1574            $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1575    }
1576    else if ($fallback) {
1577        $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1578    }
1579
1580    return true;
1581}
1582
1583
1584/**
1585 * Output HTML editor scripts
1586 *
1587 * @param string Editor mode
1588 * @return void
1589 */
1590function rcube_html_editor($mode='')
1591{
1592  global $RCMAIL, $CONFIG;
1593
1594  $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
1595
1596  if ($hook['abort'])
1597    return;
1598
1599  $lang = strtolower($_SESSION['language']);
1600
1601  // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1602  $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1603
1604  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1605    $lang = 'en';
1606
1607  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1608  $RCMAIL->output->include_script('editor.js');
1609  $RCMAIL->output->add_script(sprintf("rcmail_editor_init(%s)",
1610    json_encode(array(
1611        'mode'       => $mode,
1612        'skin_path'  => '$__skin_path',
1613        'lang'       => $lang,
1614        'spellcheck' => intval($CONFIG['enable_spellcheck']),
1615        'spelldict'  => intval($CONFIG['spellcheck_dictionary']),
1616    ))), 'foot');
1617}
1618
1619
1620/**
1621 * Replaces TinyMCE's emoticon images with plain-text representation
1622 *
1623 * @param string HTML content
1624 * @return string HTML content
1625 */
1626function rcmail_replace_emoticons($html)
1627{
1628  $emoticons = array(
1629    '8-)' => 'smiley-cool',
1630    ':-#' => 'smiley-foot-in-mouth',
1631    ':-*' => 'smiley-kiss',
1632    ':-X' => 'smiley-sealed',
1633    ':-P' => 'smiley-tongue-out',
1634    ':-@' => 'smiley-yell',
1635    ":'(" => 'smiley-cry',
1636    ':-(' => 'smiley-frown',
1637    ':-D' => 'smiley-laughing',
1638    ':-)' => 'smiley-smile',
1639    ':-S' => 'smiley-undecided',
1640    ':-$' => 'smiley-embarassed',
1641    'O:-)' => 'smiley-innocent',
1642    ':-|' => 'smiley-money-mouth',
1643    ':-O' => 'smiley-surprised',
1644    ';-)' => 'smiley-wink',
1645  );
1646
1647  foreach ($emoticons as $idx => $file) {
1648    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1649    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1650    $replace[] = $idx;
1651  }
1652
1653  return preg_replace($search, $replace, $html);
1654}
1655
1656
1657/**
1658 * Send the given message using the configured method
1659 *
1660 * @param object $message    Reference to Mail_MIME object
1661 * @param string $from       Sender address string
1662 * @param array  $mailto     Array of recipient address strings
1663 * @param array  $smtp_error SMTP error array (reference)
1664 * @param string $body_file  Location of file with saved message body (reference),
1665 *                           used when delay_file_io is enabled
1666 * @param array  $smtp_opts  SMTP options (e.g. DSN request)
1667 *
1668 * @return boolean Send status.
1669 */
1670function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file=null, $smtp_opts=null)
1671{
1672  global $CONFIG, $RCMAIL;
1673
1674  $headers = $message->headers();
1675
1676  // send thru SMTP server using custom SMTP library
1677  if ($CONFIG['smtp_server']) {
1678    // generate list of recipients
1679    $a_recipients = array($mailto);
1680
1681    if (strlen($headers['Cc']))
1682      $a_recipients[] = $headers['Cc'];
1683    if (strlen($headers['Bcc']))
1684      $a_recipients[] = $headers['Bcc'];
1685
1686    // clean Bcc from header for recipients
1687    $send_headers = $headers;
1688    unset($send_headers['Bcc']);
1689    // here too, it because txtHeaders() below use $message->_headers not only $send_headers
1690    unset($message->_headers['Bcc']);
1691
1692    $smtp_headers = $message->txtHeaders($send_headers, true);
1693
1694    if ($message->getParam('delay_file_io')) {
1695      // use common temp dir
1696      $temp_dir = $RCMAIL->config->get('temp_dir');
1697      $body_file = tempnam($temp_dir, 'rcmMsg');
1698      if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
1699        raise_error(array('code' => 650, 'type' => 'php',
1700            'file' => __FILE__, 'line' => __LINE__,
1701            'message' => "Could not create message: ".$mime_result->getMessage()),
1702            TRUE, FALSE);
1703        return false;
1704      }
1705      $msg_body = fopen($body_file, 'r');
1706    } else {
1707      $msg_body = $message->get();
1708    }
1709
1710    // send message
1711    if (!is_object($RCMAIL->smtp))
1712      $RCMAIL->smtp_init(true);
1713
1714    $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
1715    $smtp_response = $RCMAIL->smtp->get_response();
1716    $smtp_error = $RCMAIL->smtp->get_error();
1717
1718    // log error
1719    if (!$sent)
1720      raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__,
1721                        'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
1722  }
1723  // send mail using PHP's mail() function
1724  else {
1725    // unset some headers because they will be added by the mail() function
1726    $headers_enc = $message->headers($headers);
1727    $headers_php = $message->_headers;
1728    unset($headers_php['To'], $headers_php['Subject']);
1729
1730    // reset stored headers and overwrite
1731    $message->_headers = array();
1732    $header_str = $message->txtHeaders($headers_php);
1733
1734    // #1485779
1735    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1736      if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
1737        $headers_enc['To'] = implode(', ', $m[1]);
1738      }
1739    }
1740
1741    $msg_body = $message->get();
1742
1743    if (PEAR::isError($msg_body))
1744      raise_error(array('code' => 650, 'type' => 'php',
1745            'file' => __FILE__, 'line' => __LINE__,
1746            'message' => "Could not create message: ".$msg_body->getMessage()),
1747            TRUE, FALSE);
1748    else {
1749      $delim   = $RCMAIL->config->header_delimiter();
1750      $to      = $headers_enc['To'];
1751      $subject = $headers_enc['Subject'];
1752      $header_str = rtrim($header_str);
1753
1754      if ($delim != "\r\n") {
1755        $header_str = str_replace("\r\n", $delim, $header_str);
1756        $msg_body   = str_replace("\r\n", $delim, $msg_body);
1757        $to         = str_replace("\r\n", $delim, $to);
1758        $subject    = str_replace("\r\n", $delim, $subject);
1759      }
1760
1761      if (ini_get('safe_mode'))
1762        $sent = mail($to, $subject, $msg_body, $header_str);
1763      else
1764        $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
1765    }
1766  }
1767
1768  if ($sent) {
1769    $RCMAIL->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
1770
1771    // remove MDN headers after sending
1772    unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
1773
1774    // get all recipients
1775    if ($headers['Cc'])
1776      $mailto .= $headers['Cc'];
1777    if ($headers['Bcc'])
1778      $mailto .= $headers['Bcc'];
1779    if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
1780      $mailto = implode(', ', array_unique($m[1]));
1781
1782    if ($CONFIG['smtp_log']) {
1783      write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
1784        $RCMAIL->user->get_username(),
1785        $_SERVER['REMOTE_ADDR'],
1786        $mailto,
1787        !empty($smtp_response) ? join('; ', $smtp_response) : ''));
1788    }
1789  }
1790
1791  if (is_resource($msg_body)) {
1792    fclose($msg_body);
1793  }
1794
1795  $message->_headers = array();
1796  $message->headers($headers);
1797
1798  return $sent;
1799}
1800
1801
1802// Returns unique Message-ID
1803function rcmail_gen_message_id()
1804{
1805  global $RCMAIL;
1806
1807  $local_part  = md5(uniqid('rcmail'.mt_rand(),true));
1808  $domain_part = $RCMAIL->user->get_username('domain');
1809
1810  // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
1811  if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
1812    if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']))
1813      && preg_match('/\.[a-z]+$/i', $host)) {
1814        $domain_part = $host;
1815    }
1816    else if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['SERVER_NAME']))
1817      && preg_match('/\.[a-z]+$/i', $host)) {
1818        $domain_part = $host;
1819    }
1820  }
1821
1822  return sprintf('<%s@%s>', $local_part, $domain_part);
1823}
1824
1825
1826// Returns RFC2822 formatted current date in user's timezone
1827function rcmail_user_date()
1828{
1829  global $RCMAIL, $CONFIG;
1830
1831  // get user's timezone
1832  $tz = $RCMAIL->config->get_timezone();
1833
1834  $date = time() + $tz * 60 * 60;
1835  $date = gmdate('r', $date);
1836  $tz   = sprintf('%+05d', intval($tz) * 100 + ($tz - intval($tz)) * 60);
1837  $date = preg_replace('/[+-][0-9]{4}$/', $tz, $date);
1838
1839  return $date;
1840}
1841
1842
1843/**
1844 * Check if working in SSL mode
1845 *
1846 * @param integer HTTPS port number
1847 * @param boolean Enables 'use_https' option checking
1848 * @return boolean
1849 */
1850function rcube_https_check($port=null, $use_https=true)
1851{
1852  global $RCMAIL;
1853
1854  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1855    return true;
1856  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1857    return true;
1858  if ($port && $_SERVER['SERVER_PORT'] == $port)
1859    return true;
1860  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1861    return true;
1862
1863  return false;
1864}
1865
1866
1867/**
1868 * For backward compatibility.
1869 *
1870 * @global rcmail $RCMAIL
1871 * @param string $var_name Variable name.
1872 * @return void
1873 */
1874function rcube_sess_unset($var_name=null)
1875{
1876  global $RCMAIL;
1877
1878  $RCMAIL->session->remove($var_name);
1879}
1880
1881
1882/**
1883 * Replaces hostname variables
1884 *
1885 * @param string $name Hostname
1886 * @param string $host Optional IMAP hostname
1887 * @return string
1888 */
1889function rcube_parse_host($name, $host='')
1890{
1891  // %n - host
1892  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1893  // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
1894  $d = preg_replace('/^[^\.]+\./', '', $n);
1895  // %h - IMAP host
1896  $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
1897  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1898  $z = preg_replace('/^[^\.]+\./', '', $h);
1899  // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
1900  if ( strpos($name, '%s') !== false ){
1901    $user_email = rcube_idn_convert(get_input_value('_user', RCUBE_INPUT_POST), true);
1902    if ( preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s) < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false )
1903      return false;
1904  }
1905
1906  $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
1907  return $name;
1908}
1909
1910
1911/**
1912 * E-mail address validation
1913 *
1914 * @param string $email Email address
1915 * @param boolean $dns_check True to check dns
1916 * @return boolean
1917 */
1918function check_email($email, $dns_check=true)
1919{
1920  // Check for invalid characters
1921  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1922    return false;
1923
1924  // Check for length limit specified by RFC 5321 (#1486453)
1925  if (strlen($email) > 254)
1926    return false;
1927
1928  $email_array = explode('@', $email);
1929
1930  // Check that there's one @ symbol
1931  if (count($email_array) < 2)
1932    return false;
1933
1934  $domain_part = array_pop($email_array);
1935  $local_part = implode('@', $email_array);
1936
1937  // from PEAR::Validate
1938  $regexp = '&^(?:
1939        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1940        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
1941        $&xi';
1942
1943  if (!preg_match($regexp, $local_part))
1944    return false;
1945
1946  // Check domain part
1947  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))
1948    return true; // IP address
1949  else {
1950    // If not an IP address
1951    $domain_array = explode('.', $domain_part);
1952    if (sizeof($domain_array) < 2)
1953      return false; // Not enough parts to be a valid domain
1954
1955    foreach ($domain_array as $part)
1956      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1957        return false;
1958
1959    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1960      return true;
1961
1962    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1963      $lookup = array();
1964      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1965      foreach ($lookup as $line) {
1966        if (strpos($line, 'MX preference'))
1967          return true;
1968      }
1969      return false;
1970    }
1971
1972    // find MX record(s)
1973    if (getmxrr($domain_part, $mx_records))
1974      return true;
1975
1976    // find any DNS record
1977    if (checkdnsrr($domain_part, 'ANY'))
1978      return true;
1979  }
1980
1981  return false;
1982}
1983
1984/*
1985 * Idn_to_ascii wrapper.
1986 * Intl/Idn modules version of this function doesn't work with e-mail address
1987 */
1988function rcube_idn_to_ascii($str)
1989{
1990  return rcube_idn_convert($str, true);
1991}
1992
1993/*
1994 * Idn_to_ascii wrapper.
1995 * Intl/Idn modules version of this function doesn't work with e-mail address
1996 */
1997function rcube_idn_to_utf8($str)
1998{
1999  return rcube_idn_convert($str, false);
2000}
2001
2002function rcube_idn_convert($input, $is_utf=false)
2003{
2004  if ($at = strpos($input, '@')) {
2005    $user   = substr($input, 0, $at);
2006    $domain = substr($input, $at+1);
2007  }
2008  else {
2009    $domain = $input;
2010  }
2011
2012  $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
2013
2014  if ($domain === false) {
2015    return '';
2016  }
2017
2018  return $at ? $user . '@' . $domain : $domain;
2019}
2020
2021
2022/**
2023 * Helper class to turn relative urls into absolute ones
2024 * using a predefined base
2025 */
2026class rcube_base_replacer
2027{
2028  private $base_url;
2029
2030  public function __construct($base)
2031  {
2032    $this->base_url = $base;
2033  }
2034
2035  public function callback($matches)
2036  {
2037    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
2038  }
2039}
2040
2041
2042/****** debugging and logging functions ********/
2043
2044/**
2045 * Print or write debug messages
2046 *
2047 * @param mixed Debug message or data
2048 * @return void
2049 */
2050function console()
2051{
2052    $args = func_get_args();
2053
2054    if (class_exists('rcmail', false)) {
2055        $rcmail = rcmail::get_instance();
2056        if (is_object($rcmail->plugins)) {
2057            $plugin = $rcmail->plugins->exec_hook('console', array('args' => $args));
2058            if ($plugin['abort'])
2059                return;
2060            $args = $plugin['args'];
2061        }
2062    }
2063
2064    $msg = array();
2065    foreach ($args as $arg)
2066        $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
2067
2068    write_log('console', join(";\n", $msg));
2069}
2070
2071
2072/**
2073 * Append a line to a logfile in the logs directory.
2074 * Date will be added automatically to the line.
2075 *
2076 * @param $name name of log file
2077 * @param line Line to append
2078 * @return void
2079 */
2080function write_log($name, $line)
2081{
2082  global $CONFIG, $RCMAIL;
2083
2084  if (!is_string($line))
2085    $line = var_export($line, true);
2086 
2087  if (empty($CONFIG['log_date_format']))
2088    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
2089 
2090  $date = date($CONFIG['log_date_format']);
2091 
2092  // trigger logging hook
2093  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
2094    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
2095    $name = $log['name'];
2096    $line = $log['line'];
2097    $date = $log['date'];
2098    if ($log['abort'])
2099      return true;
2100  }
2101 
2102  if ($CONFIG['log_driver'] == 'syslog') {
2103    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
2104    syslog($prio, $line);
2105    return true;
2106  }
2107  else {
2108    $line = sprintf("[%s]: %s\n", $date, $line);
2109
2110    // log_driver == 'file' is assumed here
2111    if (empty($CONFIG['log_dir']))
2112      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
2113
2114    // try to open specific log file for writing
2115    $logfile = $CONFIG['log_dir'].'/'.$name;
2116    if ($fp = @fopen($logfile, 'a')) {
2117      fwrite($fp, $line);
2118      fflush($fp);
2119      fclose($fp);
2120      return true;
2121    }
2122    else
2123      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
2124  }
2125
2126  return false;
2127}
2128
2129
2130/**
2131 * Write login data (name, ID, IP address) to the 'userlogins' log file.
2132 *
2133 * @return void
2134 */
2135function rcmail_log_login()
2136{
2137  global $RCMAIL;
2138
2139  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
2140    return;
2141
2142  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s in session %s',
2143    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip(), session_id()));
2144}
2145
2146
2147/**
2148 * Returns remote IP address and forwarded addresses if found
2149 *
2150 * @return string Remote IP address(es)
2151 */
2152function rcmail_remote_ip()
2153{
2154    $address = $_SERVER['REMOTE_ADDR'];
2155
2156    // append the NGINX X-Real-IP header, if set
2157    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
2158        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
2159    }
2160    // append the X-Forwarded-For header, if set
2161    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2162        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
2163    }
2164
2165    if (!empty($remote_ip))
2166        $address .= '(' . implode(',', $remote_ip) . ')';
2167
2168    return $address;
2169}
2170
2171
2172/**
2173 * Check whether the HTTP referer matches the current request
2174 *
2175 * @return boolean True if referer is the same host+path, false if not
2176 */
2177function rcube_check_referer()
2178{
2179  $uri = parse_url($_SERVER['REQUEST_URI']);
2180  $referer = parse_url(rc_request_header('Referer'));
2181  return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
2182}
2183
2184
2185/**
2186 * @access private
2187 * @return mixed
2188 */
2189function rcube_timer()
2190{
2191  return microtime(true);
2192}
2193
2194
2195/**
2196 * @access private
2197 * @return void
2198 */
2199function rcube_print_time($timer, $label='Timer', $dest='console')
2200{
2201  static $print_count = 0;
2202
2203  $print_count++;
2204  $now = rcube_timer();
2205  $diff = $now-$timer;
2206
2207  if (empty($label))
2208    $label = 'Timer '.$print_count;
2209
2210  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
2211}
2212
2213
2214/**
2215 * Throw system error and show error page
2216 *
2217 * @param array Named parameters
2218 *  - code: Error code (required)
2219 *  - type: Error type [php|db|imap|javascript] (required)
2220 *  - message: Error message
2221 *  - file: File where error occured
2222 *  - line: Line where error occured
2223 * @param boolean True to log the error
2224 * @param boolean Terminate script execution
2225 */
2226// may be defined in Installer
2227if (!function_exists('raise_error')) {
2228function raise_error($arg=array(), $log=false, $terminate=false)
2229{
2230    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
2231
2232    // report bug (if not incompatible browser)
2233    if ($log && $arg['type'] && $arg['message'])
2234        rcube_log_bug($arg);
2235
2236    // display error page and terminate script
2237    if ($terminate) {
2238        $ERROR_CODE = $arg['code'];
2239        $ERROR_MESSAGE = $arg['message'];
2240        include INSTALL_PATH . 'program/steps/utils/error.inc';
2241        exit;
2242    }
2243}
2244}
2245
2246
2247/**
2248 * Report error according to configured debug_level
2249 *
2250 * @param array Named parameters
2251 * @return void
2252 * @see raise_error()
2253 */
2254function rcube_log_bug($arg_arr)
2255{
2256    global $CONFIG;
2257
2258    $program = strtoupper($arg_arr['type']);
2259    $level   = $CONFIG['debug_level'];
2260
2261    // disable errors for ajax requests, write to log instead (#1487831)
2262    if (($level & 4) && !empty($_REQUEST['_remote'])) {
2263        $level = ($level ^ 4) | 1;
2264    }
2265
2266    // write error to local log file
2267    if ($level & 1) {
2268        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
2269        $log_entry = sprintf("%s Error: %s%s (%s %s)",
2270            $program,
2271            $arg_arr['message'],
2272            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
2273            $_SERVER['REQUEST_METHOD'],
2274            $_SERVER['REQUEST_URI'] . $post_query);
2275
2276        if (!write_log('errors', $log_entry)) {
2277            // send error to PHPs error handler if write_log didn't succeed
2278            trigger_error($arg_arr['message']);
2279        }
2280    }
2281
2282    // report the bug to the global bug reporting system
2283    if ($level & 2) {
2284        // TODO: Send error via HTTP
2285    }
2286
2287    // show error if debug_mode is on
2288    if ($level & 4) {
2289        print "<b>$program Error";
2290
2291        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2292            print " in $arg_arr[file] ($arg_arr[line])";
2293
2294        print ':</b>&nbsp;';
2295        print nl2br($arg_arr['message']);
2296        print '<br />';
2297        flush();
2298    }
2299}
2300
2301function rcube_upload_progress()
2302{
2303    global $RCMAIL;
2304
2305    $prefix = ini_get('apc.rfc1867_prefix');
2306    $params = array(
2307        'action' => $RCMAIL->action,
2308        'name' => get_input_value('_progress', RCUBE_INPUT_GET),
2309    );
2310
2311    if (function_exists('apc_fetch')) {
2312        $status = apc_fetch($prefix . $params['name']);
2313
2314        if (!empty($status)) {
2315            $status['percent'] = round($status['current']/$status['total']*100);
2316            $params = array_merge($status, $params);
2317        }
2318    }
2319
2320    if (isset($params['percent']))
2321        $params['text'] = rcube_label(array('name' => 'uploadprogress', 'vars' => array(
2322            'percent' => $params['percent'] . '%',
2323            'current' => show_bytes($params['current']),
2324            'total'   => show_bytes($params['total'])
2325        )));
2326
2327    $RCMAIL->output->command('upload_progress_update', $params);
2328    $RCMAIL->output->send();
2329}
2330
2331function rcube_upload_init()
2332{
2333    global $RCMAIL;
2334
2335    // Enable upload progress bar
2336    if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
2337        if ($field_name = ini_get('apc.rfc1867_name')) {
2338            $RCMAIL->output->set_env('upload_progress_name', $field_name);
2339            $RCMAIL->output->set_env('upload_progress_time', (int) $seconds);
2340        }
2341    }
2342
2343    // find max filesize value
2344    $max_filesize = parse_bytes(ini_get('upload_max_filesize'));
2345    $max_postsize = parse_bytes(ini_get('post_max_size'));
2346    if ($max_postsize && $max_postsize < $max_filesize)
2347        $max_filesize = $max_postsize;
2348
2349    $RCMAIL->output->set_env('max_filesize', $max_filesize);
2350    $max_filesize = show_bytes($max_filesize);
2351    $RCMAIL->output->set_env('filesizeerror', rcube_label(array(
2352        'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
2353
2354    return $max_filesize;
2355}
2356
2357/**
2358 * Initializes client-side autocompletion
2359 */
2360function rcube_autocomplete_init()
2361{
2362    global $RCMAIL;
2363    static $init;
2364
2365    if ($init)
2366        return;
2367
2368    $init = 1;
2369
2370    if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) {
2371      $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql');
2372      if (count($book_types) > 1) {
2373        $RCMAIL->output->set_env('autocomplete_threads', $threads);
2374        $RCMAIL->output->set_env('autocomplete_sources', $book_types);
2375      }
2376    }
2377
2378    $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15));
2379    $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length'));
2380    $RCMAIL->output->add_label('autocompletechars', 'autocompletemore');
2381}
Note: See TracBrowser for help on using the repository browser.