source: github/program/include/main.inc @ da71783

HEADcourier-fixdev-browser-capabilitiespdorelease-0.7release-0.8
Last change on this file since da71783 was da71783, checked in by thomascube <thomas@…>, 21 months ago

Move timezone computation to rcube_config; don't override SESSIONtimezone? when saving prefs

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