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

Last change on this file since 5586 was 5586, checked in by thomasb, 18 months ago

Allow clean background:url(...) styles in safe mode. This will make Roundcube pass the Email Standards Acid Test

  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 66.6 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) {
645    if (isset($_GET[$fname]))
646      $value = $_GET[$fname];
647  }
648  else if ($source == RCUBE_INPUT_POST) {
649    if (isset($_POST[$fname]))
650      $value = $_POST[$fname];
651  }
652  else if ($source == RCUBE_INPUT_GPC) {
653    if (isset($_POST[$fname]))
654      $value = $_POST[$fname];
655    else if (isset($_GET[$fname]))
656      $value = $_GET[$fname];
657    else if (isset($_COOKIE[$fname]))
658      $value = $_COOKIE[$fname];
659  }
660
661  return parse_input_value($value, $allow_html, $charset);
662}
663
664/**
665 * Parse/validate input value. See get_input_value()
666 * Performs stripslashes() and charset conversion if necessary
667 *
668 * @param  string   Input value
669 * @param  boolean  Allow HTML tags in field value
670 * @param  string   Charset to convert into
671 * @return string   Parsed value
672 */
673function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
674{
675  global $OUTPUT;
676
677  if (empty($value))
678    return $value;
679
680  if (is_array($value)) {
681    foreach ($value as $idx => $val)
682      $value[$idx] = parse_input_value($val, $allow_html, $charset);
683    return $value;
684  }
685
686  // strip single quotes if magic_quotes_sybase is enabled
687  if (ini_get('magic_quotes_sybase'))
688    $value = str_replace("''", "'", $value);
689  // strip slashes if magic_quotes enabled
690  else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
691    $value = stripslashes($value);
692
693  // remove HTML tags if not allowed
694  if (!$allow_html)
695    $value = strip_tags($value);
696
697  $output_charset = is_object($OUTPUT) ? $OUTPUT->get_charset() : null;
698
699  // remove invalid characters (#1488124)
700  if ($output_charset == 'UTF-8')
701    $value = rc_utf8_clean($value);
702
703  // convert to internal charset
704  if ($charset && $output_charset)
705    $value = rcube_charset_convert($value, $output_charset, $charset);
706
707  return $value;
708}
709
710/**
711 * Convert array of request parameters (prefixed with _)
712 * to a regular array with non-prefixed keys.
713 *
714 * @param  int   Source to get value from (GPC)
715 * @return array Hash array with all request parameters
716 */
717function request2param($mode = RCUBE_INPUT_GPC, $ignore = 'task|action')
718{
719  $out = array();
720  $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
721  foreach ($src as $key => $value) {
722    $fname = $key[0] == '_' ? substr($key, 1) : $key;
723    if ($ignore && !preg_match('/^(' . $ignore . ')$/', $fname))
724      $out[$fname] = get_input_value($key, $mode);
725  }
726
727  return $out;
728}
729
730/**
731 * Remove all non-ascii and non-word chars
732 * except ., -, _
733 */
734function asciiwords($str, $css_id = false, $replace_with = '')
735{
736  $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
737  return preg_replace("/[^$allowed]/i", $replace_with, $str);
738}
739
740/**
741 * Convert the given string into a valid HTML identifier
742 * Same functionality as done in app.js with rcube_webmail.html_identifier()
743 */
744function html_identifier($str, $encode=false)
745{
746  if ($encode)
747    return rtrim(strtr(base64_encode($str), '+/', '-_'), '=');
748  else
749    return asciiwords($str, true, '_');
750}
751
752/**
753 * Remove single and double quotes from given string
754 *
755 * @param string Input value
756 * @return string Dequoted string
757 */
758function strip_quotes($str)
759{
760  return str_replace(array("'", '"'), '', $str);
761}
762
763
764/**
765 * Remove new lines characters from given string
766 *
767 * @param string Input value
768 * @return string Stripped string
769 */
770function strip_newlines($str)
771{
772  return preg_replace('/[\r\n]/', '', $str);
773}
774
775
776/**
777 * Create a HTML table based on the given data
778 *
779 * @param  array  Named table attributes
780 * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
781 * @param  array  List of cols to show
782 * @param  string Name of the identifier col
783 * @return string HTML table code
784 */
785function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
786{
787  global $RCMAIL;
788
789  $table = new html_table(/*array('cols' => count($a_show_cols))*/);
790
791  // add table header
792  if (!$attrib['noheader'])
793    foreach ($a_show_cols as $col)
794      $table->add_header($col, Q(rcube_label($col)));
795
796  $c = 0;
797  if (!is_array($table_data))
798  {
799    $db = $RCMAIL->get_dbh();
800    while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
801    {
802      $table->add_row(array('id' => 'rcmrow' . html_identifier($sql_arr[$id_col])));
803
804      // format each col
805      foreach ($a_show_cols as $col)
806        $table->add($col, Q($sql_arr[$col]));
807
808      $c++;
809    }
810  }
811  else {
812    foreach ($table_data as $row_data)
813    {
814      $class = !empty($row_data['class']) ? $row_data['class'] : '';
815
816      $table->add_row(array('id' => 'rcmrow' . html_identifier($row_data[$id_col]), 'class' => $class));
817
818      // format each col
819      foreach ($a_show_cols as $col)
820        $table->add($col, Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
821
822      $c++;
823    }
824  }
825
826  return $table->show($attrib);
827}
828
829
830/**
831 * Create an edit field for inclusion on a form
832 *
833 * @param string col field name
834 * @param string value field value
835 * @param array attrib HTML element attributes for field
836 * @param string type HTML element type (default 'text')
837 * @return string HTML field definition
838 */
839function rcmail_get_edit_field($col, $value, $attrib, $type='text')
840{
841  static $colcounts = array();
842 
843  $fname = '_'.$col;
844  $attrib['name'] = $fname . ($attrib['array'] ? '[]' : '');
845  $attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
846 
847  if ($type == 'checkbox') {
848    $attrib['value'] = '1';
849    $input = new html_checkbox($attrib);
850  }
851  else if ($type == 'textarea') {
852    $attrib['cols'] = $attrib['size'];
853    $input = new html_textarea($attrib);
854  }
855  else if ($type == 'select') {
856    $input = new html_select($attrib);
857    $input->add('---', '');
858    $input->add(array_values($attrib['options']), array_keys($attrib['options']));
859  }
860  else {
861    if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden')
862        $attrib['type'] = 'text';
863    $input = new html_inputfield($attrib);
864  }
865
866  // use value from post
867  if (isset($_POST[$fname])) {
868    $postvalue = get_input_value($fname, RCUBE_INPUT_POST, true);
869    $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
870  }
871
872  $out = $input->show($value);
873
874  return $out;
875}
876
877
878/**
879 * Replace all css definitions with #container [def]
880 * and remove css-inlined scripting
881 *
882 * @param string CSS source code
883 * @param string Container ID to use as prefix
884 * @return string Modified CSS source
885 */
886function rcmail_mod_css_styles($source, $container_id, $allow_remote=false)
887  {
888  $last_pos = 0;
889  $replacements = new rcube_string_replacer;
890
891  // ignore the whole block if evil styles are detected
892  $source = rcmail_xss_entity_decode($source);
893  $stripped = preg_replace('/[^a-z\(:;]/i', '', $source);
894  $evilexpr = 'expression|behavior' . (!$allow_remote ? '|url\(|import[^a]' : '');
895  if (preg_match("/$evilexpr/i", $stripped) // don't accept No-Gos
896      || (strpos($stripped, 'url(') && !preg_match('!url\s*\([ "\'](https?:)//[a-z0-9/._+-]+["\' ]\)!Ui', $source))) // only allow clean urls
897    return '/* evil! */';
898
899  // cut out all contents between { and }
900  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
901  {
902    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
903    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
904    $last_pos = $pos+2;
905  }
906
907  // remove html comments and add #container to each tag selector.
908  // also replace body definition because we also stripped off the <body> tag
909  $styles = preg_replace(
910    array(
911      '/(^\s*<!--)|(-->\s*$)/',
912      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
913      '/'.preg_quote($container_id, '/').'\s+body/i',
914    ),
915    array(
916      '',
917      "\\1#$container_id \\2",
918      $container_id,
919    ),
920    $source);
921
922  // put block contents back in
923  $styles = $replacements->resolve($styles);
924
925  return $styles;
926  }
927
928
929/**
930 * Decode escaped entities used by known XSS exploits.
931 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
932 *
933 * @param string CSS content to decode
934 * @return string Decoded string
935 */
936function rcmail_xss_entity_decode($content)
937{
938  $out = html_entity_decode(html_entity_decode($content));
939  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
940  $out = preg_replace('#/\*.*\*/#Ums', '', $out);
941  return $out;
942}
943
944
945/**
946 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
947 *
948 * @param array matches result from preg_replace_callback
949 * @return string decoded entity
950 */
951function rcmail_xss_entity_decode_callback($matches)
952{
953  return chr(hexdec($matches[1]));
954}
955
956/**
957 * Compose a valid attribute string for HTML tags
958 *
959 * @param array Named tag attributes
960 * @param array List of allowed attributes
961 * @return string HTML formatted attribute string
962 */
963function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
964  {
965  // allow the following attributes to be added to the <iframe> tag
966  $attrib_str = '';
967  foreach ($allowed_attribs as $a)
968    if (isset($attrib[$a]))
969      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
970
971  return $attrib_str;
972  }
973
974
975/**
976 * Convert a HTML attribute string attributes to an associative array (name => value)
977 *
978 * @param string Input string
979 * @return array Key-value pairs of parsed attributes
980 */
981function parse_attrib_string($str)
982  {
983  $attrib = array();
984  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
985
986  // convert attributes to an associative array (name => value)
987  if ($regs) {
988    foreach ($regs as $attr) {
989      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
990    }
991  }
992
993  return $attrib;
994  }
995
996
997/**
998 * Improved equivalent to strtotime()
999 *
1000 * @param string Date string
1001 * @return int
1002 */
1003function rcube_strtotime($date)
1004{
1005  // check for MS Outlook vCard date format YYYYMMDD
1006  if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
1007    return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
1008  }
1009  else if (is_numeric($date))
1010    return $date;
1011
1012  // support non-standard "GMTXXXX" literal
1013  $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
1014
1015  // if date parsing fails, we have a date in non-rfc format.
1016  // remove token from the end and try again
1017  while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
1018    $d = explode(' ', $date);
1019    array_pop($d);
1020    if (!$d) break;
1021    $date = implode(' ', $d);
1022  }
1023
1024  return $ts;
1025}
1026
1027
1028/**
1029 * Convert the given date to a human readable form
1030 * This uses the date formatting properties from config
1031 *
1032 * @param mixed  Date representation (string or timestamp)
1033 * @param string Date format to use
1034 * @param bool   Enables date convertion according to user timezone
1035 *
1036 * @return string Formatted date string
1037 */
1038function format_date($date, $format=NULL, $convert=true)
1039{
1040  global $RCMAIL, $CONFIG;
1041
1042  if (!empty($date))
1043    $ts = rcube_strtotime($date);
1044
1045  if (empty($ts))
1046    return '';
1047
1048  if ($convert) {
1049    // get user's timezone offset
1050    $tz = $RCMAIL->config->get_timezone();
1051
1052    // convert time to user's timezone
1053    $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
1054
1055    // get current timestamp in user's timezone
1056    $now = time();  // local time
1057    $now -= (int)date('Z'); // make GMT time
1058    $now += ($tz * 3600); // user's time
1059  }
1060  else {
1061    $now       = time();
1062    $timestamp = $ts;
1063  }
1064
1065  // define date format depending on current time
1066  if (!$format) {
1067    $now_date    = getdate($now);
1068    $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1069    $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
1070
1071    if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
1072      $format = $RCMAIL->config->get('date_today', $RCMAIL->config->get('time_format', 'H:i'));
1073      $today  = true;
1074    }
1075    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1076      $format = $RCMAIL->config->get('date_short', 'D H:i');
1077    else
1078      $format = $RCMAIL->config->get('date_long', 'Y-m-d H:i');
1079  }
1080
1081  // strftime() format
1082  if (preg_match('/%[a-z]+/i', $format)) {
1083    $format = strftime($format, $timestamp);
1084    return $today ? (rcube_label('today') . ' ' . $format) : $format;
1085  }
1086
1087  // parse format string manually in order to provide localized weekday and month names
1088  // an alternative would be to convert the date() format string to fit with strftime()
1089  $out = '';
1090  for($i=0; $i<strlen($format); $i++) {
1091    if ($format[$i]=='\\')  // skip escape chars
1092      continue;
1093
1094    // write char "as-is"
1095    if ($format[$i]==' ' || $format{$i-1}=='\\')
1096      $out .= $format[$i];
1097    // weekday (short)
1098    else if ($format[$i]=='D')
1099      $out .= rcube_label(strtolower(date('D', $timestamp)));
1100    // weekday long
1101    else if ($format[$i]=='l')
1102      $out .= rcube_label(strtolower(date('l', $timestamp)));
1103    // month name (short)
1104    else if ($format[$i]=='M')
1105      $out .= rcube_label(strtolower(date('M', $timestamp)));
1106    // month name (long)
1107    else if ($format[$i]=='F')
1108      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1109    else if ($format[$i]=='x')
1110      $out .= strftime('%x %X', $timestamp);
1111    else
1112      $out .= date($format[$i], $timestamp);
1113  }
1114
1115  if ($today) {
1116    $label = rcube_label('today');
1117    // replcae $ character with "Today" label (#1486120)
1118    if (strpos($out, '$') !== false) {
1119      $out = preg_replace('/\$/', $label, $out, 1);
1120    }
1121    else {
1122      $out = $label . ' ' . $out;
1123    }
1124  }
1125
1126  return $out;
1127}
1128
1129
1130/**
1131 * Compose a valid representation of name and e-mail address
1132 *
1133 * @param string E-mail address
1134 * @param string Person name
1135 * @return string Formatted string
1136 */
1137function format_email_recipient($email, $name='')
1138{
1139  if ($name && $name != $email) {
1140    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1141    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1142  }
1143
1144  return trim($email);
1145}
1146
1147
1148/**
1149 * Return the mailboxlist in HTML
1150 *
1151 * @param array Named parameters
1152 * @return string HTML code for the gui object
1153 */
1154function rcmail_mailbox_list($attrib)
1155{
1156  global $RCMAIL;
1157  static $a_mailboxes;
1158
1159  $attrib += array('maxlength' => 100, 'realnames' => false);
1160
1161  // add some labels to client
1162  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1163
1164  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1165  unset($attrib['type']);
1166
1167  if ($type=='ul' && !$attrib['id'])
1168    $attrib['id'] = 'rcmboxlist';
1169
1170  if (empty($attrib['folder_name']))
1171    $attrib['folder_name'] = '*';
1172
1173  // get mailbox list
1174  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1175
1176  // build the folders tree
1177  if (empty($a_mailboxes)) {
1178    // get mailbox list
1179    $a_folders = $RCMAIL->imap->list_mailboxes('', $attrib['folder_name'], $attrib['folder_filter']);
1180    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1181    $a_mailboxes = array();
1182
1183    foreach ($a_folders as $folder)
1184      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1185  }
1186
1187  // allow plugins to alter the folder tree or to localize folder names
1188  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1189
1190  if ($type == 'select') {
1191    $select = new html_select($attrib);
1192
1193    // add no-selection option
1194    if ($attrib['noselection'])
1195      $select->add(rcube_label($attrib['noselection']), '');
1196
1197    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1198    $out = $select->show();
1199  }
1200  else {
1201    $js_mailboxlist = array();
1202    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1203
1204    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1205    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1206    $RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders'));
1207  }
1208
1209  return $out;
1210}
1211
1212
1213/**
1214 * Return the mailboxlist as html_select object
1215 *
1216 * @param array Named parameters
1217 * @return html_select HTML drop-down object
1218 */
1219function rcmail_mailbox_select($p = array())
1220{
1221  global $RCMAIL;
1222
1223  $p += array('maxlength' => 100, 'realnames' => false);
1224  $a_mailboxes = array();
1225
1226  if (empty($p['folder_name']))
1227    $p['folder_name'] = '*';
1228
1229  if ($p['unsubscribed'])
1230    $list = $RCMAIL->imap->list_unsubscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
1231  else
1232    $list = $RCMAIL->imap->list_mailboxes('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
1233
1234  $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1235
1236  foreach ($list as $folder) {
1237    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1238      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1239  }
1240
1241  $select = new html_select($p);
1242
1243  if ($p['noselection'])
1244    $select->add($p['noselection'], '');
1245
1246  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
1247
1248  return $select;
1249}
1250
1251
1252/**
1253 * Create a hierarchical array of the mailbox list
1254 * @access private
1255 * @return void
1256 */
1257function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1258{
1259  global $RCMAIL;
1260
1261  // Handle namespace prefix
1262  $prefix = '';
1263  if (!$path) {
1264    $n_folder = $folder;
1265    $folder = $RCMAIL->imap->mod_mailbox($folder);
1266
1267    if ($n_folder != $folder) {
1268      $prefix = substr($n_folder, 0, -strlen($folder));
1269    }
1270  }
1271
1272  $pos = strpos($folder, $delm);
1273
1274  if ($pos !== false) {
1275    $subFolders = substr($folder, $pos+1);
1276    $currentFolder = substr($folder, 0, $pos);
1277
1278    // sometimes folder has a delimiter as the last character
1279    if (!strlen($subFolders))
1280      $virtual = false;
1281    else if (!isset($arrFolders[$currentFolder]))
1282      $virtual = true;
1283    else
1284      $virtual = $arrFolders[$currentFolder]['virtual'];
1285  }
1286  else {
1287    $subFolders = false;
1288    $currentFolder = $folder;
1289    $virtual = false;
1290  }
1291
1292  $path .= $prefix.$currentFolder;
1293
1294  if (!isset($arrFolders[$currentFolder])) {
1295    $arrFolders[$currentFolder] = array(
1296      'id' => $path,
1297      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1298      'virtual' => $virtual,
1299      'folders' => array());
1300  }
1301  else
1302    $arrFolders[$currentFolder]['virtual'] = $virtual;
1303
1304  if (strlen($subFolders))
1305    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1306}
1307
1308
1309/**
1310 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1311 * @access private
1312 * @return string
1313 */
1314function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1315{
1316  global $RCMAIL, $CONFIG;
1317
1318  $maxlength = intval($attrib['maxlength']);
1319  $realnames = (bool)$attrib['realnames'];
1320  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1321
1322  $out = '';
1323  foreach ($arrFolders as $key => $folder) {
1324    $title        = null;
1325    $folder_class = rcmail_folder_classname($folder['id']);
1326    $collapsed    = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false;
1327    $unread       = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1328
1329    if ($folder_class && !$realnames) {
1330      $foldername = rcube_label($folder_class);
1331    }
1332    else {
1333      $foldername = $folder['name'];
1334
1335      // shorten the folder name to a given length
1336      if ($maxlength && $maxlength > 1) {
1337        $fname = abbreviate_string($foldername, $maxlength);
1338        if ($fname != $foldername)
1339          $title = $foldername;
1340        $foldername = $fname;
1341      }
1342    }
1343
1344    // make folder name safe for ids and class names
1345    $folder_id = html_identifier($folder['id'], true);
1346    $classes = array('mailbox');
1347
1348    // set special class for Sent, Drafts, Trash and Junk
1349    if ($folder_class)
1350      $classes[] = $folder_class;
1351
1352    if ($folder['id'] == $mbox_name)
1353      $classes[] = 'selected';
1354
1355    if ($folder['virtual'])
1356      $classes[] = 'virtual';
1357    else if ($unread)
1358      $classes[] = 'unread';
1359
1360    $js_name = JQ($folder['id']);
1361    $html_name = Q($foldername) . ($unread ? html::span('unreadcount', " ($unread)") : '');
1362    $link_attrib = $folder['virtual'] ? array() : array(
1363      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1364      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1365      'rel' => $folder['id'],
1366      'title' => $title,
1367    );
1368
1369    $out .= html::tag('li', array(
1370        'id' => "rcmli".$folder_id,
1371        'class' => join(' ', $classes),
1372        'noclose' => true),
1373      html::a($link_attrib, $html_name) .
1374      (!empty($folder['folders']) ? html::div(array(
1375        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1376        'style' => "position:absolute",
1377        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1378      ), '&nbsp;') : ''));
1379
1380    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1381
1382    if (!empty($folder['folders'])) {
1383      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1384        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1385    }
1386
1387    $out .= "</li>\n";
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, $opts=array())
1400{
1401  global $RCMAIL;
1402
1403  $out = '';
1404
1405  foreach ($arrFolders as $key => $folder) {
1406    // skip exceptions (and its subfolders)
1407    if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
1408      continue;
1409    }
1410
1411    // skip folders in which it isn't possible to create subfolders
1412    if (!empty($opts['skip_noinferiors']) && ($attrs = $RCMAIL->imap->mailbox_attributes($folder['id']))
1413        && in_array('\\Noinferiors', $attrs)
1414    ) {
1415      continue;
1416    }
1417
1418    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1419      $foldername = rcube_label($folder_class);
1420    else {
1421      $foldername = $folder['name'];
1422
1423      // shorten the folder name to a given length
1424      if ($maxlength && $maxlength>1)
1425        $foldername = abbreviate_string($foldername, $maxlength);
1426    }
1427
1428    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1429
1430    if (!empty($folder['folders']))
1431      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
1432        $select, $realnames, $nestLevel+1, $opts);
1433  }
1434
1435  return $out;
1436}
1437
1438
1439/**
1440 * Return internal name for the given folder if it matches the configured special folders
1441 * @access private
1442 * @return string
1443 */
1444function rcmail_folder_classname($folder_id)
1445{
1446  global $CONFIG;
1447
1448  if ($folder_id == 'INBOX')
1449    return 'inbox';
1450
1451  // for these mailboxes we have localized labels and css classes
1452  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1453  {
1454    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1455      return $smbx;
1456  }
1457}
1458
1459
1460/**
1461 * Try to localize the given IMAP folder name.
1462 * UTF-7 decode it in case no localized text was found
1463 *
1464 * @param string Folder name
1465 * @return string Localized folder name in UTF-8 encoding
1466 */
1467function rcmail_localize_foldername($name)
1468{
1469  if ($folder_class = rcmail_folder_classname($name))
1470    return rcube_label($folder_class);
1471  else
1472    return rcube_charset_convert($name, 'UTF7-IMAP');
1473}
1474
1475
1476function rcmail_localize_folderpath($path)
1477{
1478    global $RCMAIL;
1479
1480    $protect_folders = $RCMAIL->config->get('protect_default_folders');
1481    $default_folders = (array) $RCMAIL->config->get('default_imap_folders');
1482    $delimiter       = $RCMAIL->imap->get_hierarchy_delimiter();
1483    $path            = explode($delimiter, $path);
1484    $result          = array();
1485
1486    foreach ($path as $idx => $dir) {
1487        $directory = implode($delimiter, array_slice($path, 0, $idx+1));
1488        if ($protect_folders && in_array($directory, $default_folders)) {
1489            unset($result);
1490            $result[] = rcmail_localize_foldername($directory);
1491        }
1492        else {
1493            $result[] = rcube_charset_convert($dir, 'UTF7-IMAP');
1494        }
1495    }
1496
1497    return implode($delimiter, $result);
1498}
1499
1500
1501function rcmail_quota_display($attrib)
1502{
1503  global $OUTPUT;
1504
1505  if (!$attrib['id'])
1506    $attrib['id'] = 'rcmquotadisplay';
1507
1508  if(isset($attrib['display']))
1509    $_SESSION['quota_display'] = $attrib['display'];
1510
1511  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1512
1513  $quota = rcmail_quota_content($attrib);
1514
1515  $OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready');
1516
1517  return html::span($attrib, '');
1518}
1519
1520
1521function rcmail_quota_content($attrib=NULL)
1522{
1523  global $RCMAIL;
1524
1525  $quota = $RCMAIL->imap->get_quota();
1526  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1527
1528  $quota_result = (array) $quota;
1529  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1530
1531  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1532    $quota_result['title'] = rcube_label('unlimited');
1533    $quota_result['percent'] = 0;
1534  }
1535  else if ($quota['total']) {
1536    if (!isset($quota['percent']))
1537      $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1538
1539    $title = sprintf('%s / %s (%.0f%%)',
1540        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1541        $quota_result['percent']);
1542
1543    $quota_result['title'] = $title;
1544
1545    if ($attrib['width'])
1546      $quota_result['width'] = $attrib['width'];
1547    if ($attrib['height'])
1548      $quota_result['height']   = $attrib['height'];
1549  }
1550  else {
1551    $quota_result['title'] = rcube_label('unknown');
1552    $quota_result['percent'] = 0;
1553  }
1554
1555  return $quota_result;
1556}
1557
1558
1559/**
1560 * Outputs error message according to server error/response codes
1561 *
1562 * @param string Fallback message label
1563 * @param string Fallback message label arguments
1564 *
1565 * @return void
1566 */
1567function rcmail_display_server_error($fallback=null, $fallback_args=null)
1568{
1569    global $RCMAIL;
1570
1571    $err_code = $RCMAIL->imap->get_error_code();
1572    $res_code = $RCMAIL->imap->get_response_code();
1573
1574    if ($res_code == rcube_imap::NOPERM) {
1575        $RCMAIL->output->show_message('errornoperm', 'error');
1576    }
1577    else if ($res_code == rcube_imap::READONLY) {
1578        $RCMAIL->output->show_message('errorreadonly', 'error');
1579    }
1580    else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1581        // try to detect access rights problem and display appropriate message
1582        if (stripos($err_str, 'Permission denied') !== false)
1583            $RCMAIL->output->show_message('errornoperm', 'error');
1584        else
1585            $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1586    }
1587    else if ($fallback) {
1588        $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1589    }
1590
1591    return true;
1592}
1593
1594
1595/**
1596 * Output HTML editor scripts
1597 *
1598 * @param string Editor mode
1599 * @return void
1600 */
1601function rcube_html_editor($mode='')
1602{
1603  global $RCMAIL;
1604
1605  $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
1606
1607  if ($hook['abort'])
1608    return;
1609
1610  $lang = strtolower($_SESSION['language']);
1611
1612  // TinyMCE uses two-letter lang codes, with exception of Chinese
1613  if (strpos($lang, 'zh_') === 0)
1614    $lang = str_replace('_', '-', $lang);
1615  else
1616    $lang = substr($lang, 0, 2);
1617
1618  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1619    $lang = 'en';
1620
1621  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1622  $RCMAIL->output->include_script('editor.js');
1623  $RCMAIL->output->add_script(sprintf("rcmail_editor_init(%s)",
1624    json_encode(array(
1625        'mode'       => $mode,
1626        'skin_path'  => '$__skin_path',
1627        'lang'       => $lang,
1628        'spellcheck' => intval($RCMAIL->config->get('enable_spellcheck')),
1629        'spelldict'  => intval($RCMAIL->config->get('spellcheck_dictionary')),
1630    ))), 'foot');
1631}
1632
1633
1634/**
1635 * Replaces TinyMCE's emoticon images with plain-text representation
1636 *
1637 * @param string HTML content
1638 * @return string HTML content
1639 */
1640function rcmail_replace_emoticons($html)
1641{
1642  $emoticons = array(
1643    '8-)' => 'smiley-cool',
1644    ':-#' => 'smiley-foot-in-mouth',
1645    ':-*' => 'smiley-kiss',
1646    ':-X' => 'smiley-sealed',
1647    ':-P' => 'smiley-tongue-out',
1648    ':-@' => 'smiley-yell',
1649    ":'(" => 'smiley-cry',
1650    ':-(' => 'smiley-frown',
1651    ':-D' => 'smiley-laughing',
1652    ':-)' => 'smiley-smile',
1653    ':-S' => 'smiley-undecided',
1654    ':-$' => 'smiley-embarassed',
1655    'O:-)' => 'smiley-innocent',
1656    ':-|' => 'smiley-money-mouth',
1657    ':-O' => 'smiley-surprised',
1658    ';-)' => 'smiley-wink',
1659  );
1660
1661  foreach ($emoticons as $idx => $file) {
1662    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1663    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1664    $replace[] = $idx;
1665  }
1666
1667  return preg_replace($search, $replace, $html);
1668}
1669
1670
1671/**
1672 * Send the given message using the configured method
1673 *
1674 * @param object $message    Reference to Mail_MIME object
1675 * @param string $from       Sender address string
1676 * @param array  $mailto     Array of recipient address strings
1677 * @param array  $smtp_error SMTP error array (reference)
1678 * @param string $body_file  Location of file with saved message body (reference),
1679 *                           used when delay_file_io is enabled
1680 * @param array  $smtp_opts  SMTP options (e.g. DSN request)
1681 *
1682 * @return boolean Send status.
1683 */
1684function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file=null, $smtp_opts=null)
1685{
1686  global $CONFIG, $RCMAIL;
1687
1688  $headers = $message->headers();
1689
1690  // send thru SMTP server using custom SMTP library
1691  if ($CONFIG['smtp_server']) {
1692    // generate list of recipients
1693    $a_recipients = array($mailto);
1694
1695    if (strlen($headers['Cc']))
1696      $a_recipients[] = $headers['Cc'];
1697    if (strlen($headers['Bcc']))
1698      $a_recipients[] = $headers['Bcc'];
1699
1700    // clean Bcc from header for recipients
1701    $send_headers = $headers;
1702    unset($send_headers['Bcc']);
1703    // here too, it because txtHeaders() below use $message->_headers not only $send_headers
1704    unset($message->_headers['Bcc']);
1705
1706    $smtp_headers = $message->txtHeaders($send_headers, true);
1707
1708    if ($message->getParam('delay_file_io')) {
1709      // use common temp dir
1710      $temp_dir = $RCMAIL->config->get('temp_dir');
1711      $body_file = tempnam($temp_dir, 'rcmMsg');
1712      if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
1713        raise_error(array('code' => 650, 'type' => 'php',
1714            'file' => __FILE__, 'line' => __LINE__,
1715            'message' => "Could not create message: ".$mime_result->getMessage()),
1716            TRUE, FALSE);
1717        return false;
1718      }
1719      $msg_body = fopen($body_file, 'r');
1720    } else {
1721      $msg_body = $message->get();
1722    }
1723
1724    // send message
1725    if (!is_object($RCMAIL->smtp))
1726      $RCMAIL->smtp_init(true);
1727
1728    $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
1729    $smtp_response = $RCMAIL->smtp->get_response();
1730    $smtp_error = $RCMAIL->smtp->get_error();
1731
1732    // log error
1733    if (!$sent)
1734      raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__,
1735                        'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
1736  }
1737  // send mail using PHP's mail() function
1738  else {
1739    // unset some headers because they will be added by the mail() function
1740    $headers_enc = $message->headers($headers);
1741    $headers_php = $message->_headers;
1742    unset($headers_php['To'], $headers_php['Subject']);
1743
1744    // reset stored headers and overwrite
1745    $message->_headers = array();
1746    $header_str = $message->txtHeaders($headers_php);
1747
1748    // #1485779
1749    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1750      if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
1751        $headers_enc['To'] = implode(', ', $m[1]);
1752      }
1753    }
1754
1755    $msg_body = $message->get();
1756
1757    if (PEAR::isError($msg_body))
1758      raise_error(array('code' => 650, 'type' => 'php',
1759            'file' => __FILE__, 'line' => __LINE__,
1760            'message' => "Could not create message: ".$msg_body->getMessage()),
1761            TRUE, FALSE);
1762    else {
1763      $delim   = $RCMAIL->config->header_delimiter();
1764      $to      = $headers_enc['To'];
1765      $subject = $headers_enc['Subject'];
1766      $header_str = rtrim($header_str);
1767
1768      if ($delim != "\r\n") {
1769        $header_str = str_replace("\r\n", $delim, $header_str);
1770        $msg_body   = str_replace("\r\n", $delim, $msg_body);
1771        $to         = str_replace("\r\n", $delim, $to);
1772        $subject    = str_replace("\r\n", $delim, $subject);
1773      }
1774
1775      if (ini_get('safe_mode'))
1776        $sent = mail($to, $subject, $msg_body, $header_str);
1777      else
1778        $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
1779    }
1780  }
1781
1782  if ($sent) {
1783    $RCMAIL->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
1784
1785    // remove MDN headers after sending
1786    unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
1787
1788    // get all recipients
1789    if ($headers['Cc'])
1790      $mailto .= $headers['Cc'];
1791    if ($headers['Bcc'])
1792      $mailto .= $headers['Bcc'];
1793    if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
1794      $mailto = implode(', ', array_unique($m[1]));
1795
1796    if ($CONFIG['smtp_log']) {
1797      write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
1798        $RCMAIL->user->get_username(),
1799        $_SERVER['REMOTE_ADDR'],
1800        $mailto,
1801        !empty($smtp_response) ? join('; ', $smtp_response) : ''));
1802    }
1803  }
1804
1805  if (is_resource($msg_body)) {
1806    fclose($msg_body);
1807  }
1808
1809  $message->_headers = array();
1810  $message->headers($headers);
1811
1812  return $sent;
1813}
1814
1815
1816// Returns unique Message-ID
1817function rcmail_gen_message_id()
1818{
1819  global $RCMAIL;
1820
1821  $local_part  = md5(uniqid('rcmail'.mt_rand(),true));
1822  $domain_part = $RCMAIL->user->get_username('domain');
1823
1824  // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
1825  if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
1826    if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']))
1827      && preg_match('/\.[a-z]+$/i', $host)) {
1828        $domain_part = $host;
1829    }
1830    else if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['SERVER_NAME']))
1831      && preg_match('/\.[a-z]+$/i', $host)) {
1832        $domain_part = $host;
1833    }
1834  }
1835
1836  return sprintf('<%s@%s>', $local_part, $domain_part);
1837}
1838
1839
1840// Returns RFC2822 formatted current date in user's timezone
1841function rcmail_user_date()
1842{
1843  global $RCMAIL, $CONFIG;
1844
1845  // get user's timezone
1846  $tz = $RCMAIL->config->get_timezone();
1847
1848  $date = time() + $tz * 60 * 60;
1849  $date = gmdate('r', $date);
1850  $tz   = sprintf('%+05d', intval($tz) * 100 + ($tz - intval($tz)) * 60);
1851  $date = preg_replace('/[+-][0-9]{4}$/', $tz, $date);
1852
1853  return $date;
1854}
1855
1856
1857/**
1858 * Check if we can process not exceeding memory_limit
1859 *
1860 * @param integer Required amount of memory
1861 * @return boolean
1862 */
1863function rcmail_mem_check($need)
1864{
1865  $mem_limit = parse_bytes(ini_get('memory_limit'));
1866  $memory    = function_exists('memory_get_usage') ? memory_get_usage() : 16*1024*1024; // safe value: 16MB
1867
1868  return $mem_limit && $memory + $need > $mem_limit ? false : true;
1869}
1870
1871
1872/**
1873 * Check if working in SSL mode
1874 *
1875 * @param integer HTTPS port number
1876 * @param boolean Enables 'use_https' option checking
1877 * @return boolean
1878 */
1879function rcube_https_check($port=null, $use_https=true)
1880{
1881  global $RCMAIL;
1882
1883  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1884    return true;
1885  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1886    return true;
1887  if ($port && $_SERVER['SERVER_PORT'] == $port)
1888    return true;
1889  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1890    return true;
1891
1892  return false;
1893}
1894
1895
1896/**
1897 * For backward compatibility.
1898 *
1899 * @global rcmail $RCMAIL
1900 * @param string $var_name Variable name.
1901 * @return void
1902 */
1903function rcube_sess_unset($var_name=null)
1904{
1905  global $RCMAIL;
1906
1907  $RCMAIL->session->remove($var_name);
1908}
1909
1910
1911/**
1912 * Replaces hostname variables
1913 *
1914 * @param string $name Hostname
1915 * @param string $host Optional IMAP hostname
1916 * @return string
1917 */
1918function rcube_parse_host($name, $host='')
1919{
1920  // %n - host
1921  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1922  // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
1923  $d = preg_replace('/^[^\.]+\./', '', $n);
1924  // %h - IMAP host
1925  $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
1926  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1927  $z = preg_replace('/^[^\.]+\./', '', $h);
1928  // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
1929  if ( strpos($name, '%s') !== false ){
1930    $user_email = rcube_idn_convert(get_input_value('_user', RCUBE_INPUT_POST), true);
1931    if ( preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s) < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false )
1932      return false;
1933  }
1934
1935  $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
1936  return $name;
1937}
1938
1939
1940/**
1941 * E-mail address validation
1942 *
1943 * @param string $email Email address
1944 * @param boolean $dns_check True to check dns
1945 * @return boolean
1946 */
1947function check_email($email, $dns_check=true)
1948{
1949  // Check for invalid characters
1950  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1951    return false;
1952
1953  // Check for length limit specified by RFC 5321 (#1486453)
1954  if (strlen($email) > 254)
1955    return false;
1956
1957  $email_array = explode('@', $email);
1958
1959  // Check that there's one @ symbol
1960  if (count($email_array) < 2)
1961    return false;
1962
1963  $domain_part = array_pop($email_array);
1964  $local_part = implode('@', $email_array);
1965
1966  // from PEAR::Validate
1967  $regexp = '&^(?:
1968        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1969        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
1970        $&xi';
1971
1972  if (!preg_match($regexp, $local_part))
1973    return false;
1974
1975  // Check domain part
1976  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))
1977    return true; // IP address
1978  else {
1979    // If not an IP address
1980    $domain_array = explode('.', $domain_part);
1981    if (sizeof($domain_array) < 2)
1982      return false; // Not enough parts to be a valid domain
1983
1984    foreach ($domain_array as $part)
1985      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1986        return false;
1987
1988    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1989      return true;
1990
1991    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1992      $lookup = array();
1993      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1994      foreach ($lookup as $line) {
1995        if (strpos($line, 'MX preference'))
1996          return true;
1997      }
1998      return false;
1999    }
2000
2001    // find MX record(s)
2002    if (getmxrr($domain_part, $mx_records))
2003      return true;
2004
2005    // find any DNS record
2006    if (checkdnsrr($domain_part, 'ANY'))
2007      return true;
2008  }
2009
2010  return false;
2011}
2012
2013/*
2014 * Idn_to_ascii wrapper.
2015 * Intl/Idn modules version of this function doesn't work with e-mail address
2016 */
2017function rcube_idn_to_ascii($str)
2018{
2019  return rcube_idn_convert($str, true);
2020}
2021
2022/*
2023 * Idn_to_ascii wrapper.
2024 * Intl/Idn modules version of this function doesn't work with e-mail address
2025 */
2026function rcube_idn_to_utf8($str)
2027{
2028  return rcube_idn_convert($str, false);
2029}
2030
2031function rcube_idn_convert($input, $is_utf=false)
2032{
2033  if ($at = strpos($input, '@')) {
2034    $user   = substr($input, 0, $at);
2035    $domain = substr($input, $at+1);
2036  }
2037  else {
2038    $domain = $input;
2039  }
2040
2041  $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
2042
2043  if ($domain === false) {
2044    return '';
2045  }
2046
2047  return $at ? $user . '@' . $domain : $domain;
2048}
2049
2050
2051/**
2052 * Helper class to turn relative urls into absolute ones
2053 * using a predefined base
2054 */
2055class rcube_base_replacer
2056{
2057  private $base_url;
2058
2059  public function __construct($base)
2060  {
2061    $this->base_url = $base;
2062  }
2063
2064  public function callback($matches)
2065  {
2066    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
2067  }
2068}
2069
2070
2071/****** debugging and logging functions ********/
2072
2073/**
2074 * Print or write debug messages
2075 *
2076 * @param mixed Debug message or data
2077 * @return void
2078 */
2079function console()
2080{
2081    $args = func_get_args();
2082
2083    if (class_exists('rcmail', false)) {
2084        $rcmail = rcmail::get_instance();
2085        if (is_object($rcmail->plugins)) {
2086            $plugin = $rcmail->plugins->exec_hook('console', array('args' => $args));
2087            if ($plugin['abort'])
2088                return;
2089            $args = $plugin['args'];
2090        }
2091    }
2092
2093    $msg = array();
2094    foreach ($args as $arg)
2095        $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
2096
2097    write_log('console', join(";\n", $msg));
2098}
2099
2100
2101/**
2102 * Append a line to a logfile in the logs directory.
2103 * Date will be added automatically to the line.
2104 *
2105 * @param $name name of log file
2106 * @param line Line to append
2107 * @return void
2108 */
2109function write_log($name, $line)
2110{
2111  global $CONFIG, $RCMAIL;
2112
2113  if (!is_string($line))
2114    $line = var_export($line, true);
2115 
2116  if (empty($CONFIG['log_date_format']))
2117    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
2118 
2119  $date = date($CONFIG['log_date_format']);
2120 
2121  // trigger logging hook
2122  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
2123    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
2124    $name = $log['name'];
2125    $line = $log['line'];
2126    $date = $log['date'];
2127    if ($log['abort'])
2128      return true;
2129  }
2130 
2131  if ($CONFIG['log_driver'] == 'syslog') {
2132    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
2133    syslog($prio, $line);
2134    return true;
2135  }
2136  else {
2137    $line = sprintf("[%s]: %s\n", $date, $line);
2138
2139    // log_driver == 'file' is assumed here
2140    if (empty($CONFIG['log_dir']))
2141      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
2142
2143    // try to open specific log file for writing
2144    $logfile = $CONFIG['log_dir'].'/'.$name;
2145    if ($fp = @fopen($logfile, 'a')) {
2146      fwrite($fp, $line);
2147      fflush($fp);
2148      fclose($fp);
2149      return true;
2150    }
2151    else
2152      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
2153  }
2154
2155  return false;
2156}
2157
2158
2159/**
2160 * Write login data (name, ID, IP address) to the 'userlogins' log file.
2161 *
2162 * @return void
2163 */
2164function rcmail_log_login()
2165{
2166  global $RCMAIL;
2167
2168  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
2169    return;
2170
2171  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s in session %s',
2172    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip(), session_id()));
2173}
2174
2175
2176/**
2177 * Returns remote IP address and forwarded addresses if found
2178 *
2179 * @return string Remote IP address(es)
2180 */
2181function rcmail_remote_ip()
2182{
2183    $address = $_SERVER['REMOTE_ADDR'];
2184
2185    // append the NGINX X-Real-IP header, if set
2186    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
2187        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
2188    }
2189    // append the X-Forwarded-For header, if set
2190    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2191        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
2192    }
2193
2194    if (!empty($remote_ip))
2195        $address .= '(' . implode(',', $remote_ip) . ')';
2196
2197    return $address;
2198}
2199
2200
2201/**
2202 * Check whether the HTTP referer matches the current request
2203 *
2204 * @return boolean True if referer is the same host+path, false if not
2205 */
2206function rcube_check_referer()
2207{
2208  $uri = parse_url($_SERVER['REQUEST_URI']);
2209  $referer = parse_url(rc_request_header('Referer'));
2210  return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
2211}
2212
2213
2214/**
2215 * @access private
2216 * @return mixed
2217 */
2218function rcube_timer()
2219{
2220  return microtime(true);
2221}
2222
2223
2224/**
2225 * @access private
2226 * @return void
2227 */
2228function rcube_print_time($timer, $label='Timer', $dest='console')
2229{
2230  static $print_count = 0;
2231
2232  $print_count++;
2233  $now = rcube_timer();
2234  $diff = $now-$timer;
2235
2236  if (empty($label))
2237    $label = 'Timer '.$print_count;
2238
2239  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
2240}
2241
2242
2243/**
2244 * Throw system error and show error page
2245 *
2246 * @param array Named parameters
2247 *  - code: Error code (required)
2248 *  - type: Error type [php|db|imap|javascript] (required)
2249 *  - message: Error message
2250 *  - file: File where error occured
2251 *  - line: Line where error occured
2252 * @param boolean True to log the error
2253 * @param boolean Terminate script execution
2254 */
2255// may be defined in Installer
2256if (!function_exists('raise_error')) {
2257function raise_error($arg=array(), $log=false, $terminate=false)
2258{
2259    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
2260
2261    // report bug (if not incompatible browser)
2262    if ($log && $arg['type'] && $arg['message'])
2263        rcube_log_bug($arg);
2264
2265    // display error page and terminate script
2266    if ($terminate) {
2267        $ERROR_CODE = $arg['code'];
2268        $ERROR_MESSAGE = $arg['message'];
2269        include INSTALL_PATH . 'program/steps/utils/error.inc';
2270        exit;
2271    }
2272}
2273}
2274
2275
2276/**
2277 * Report error according to configured debug_level
2278 *
2279 * @param array Named parameters
2280 * @return void
2281 * @see raise_error()
2282 */
2283function rcube_log_bug($arg_arr)
2284{
2285    global $CONFIG;
2286
2287    $program = strtoupper($arg_arr['type']);
2288    $level   = $CONFIG['debug_level'];
2289
2290    // disable errors for ajax requests, write to log instead (#1487831)
2291    if (($level & 4) && !empty($_REQUEST['_remote'])) {
2292        $level = ($level ^ 4) | 1;
2293    }
2294
2295    // write error to local log file
2296    if ($level & 1) {
2297        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
2298        $log_entry = sprintf("%s Error: %s%s (%s %s)",
2299            $program,
2300            $arg_arr['message'],
2301            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
2302            $_SERVER['REQUEST_METHOD'],
2303            $_SERVER['REQUEST_URI'] . $post_query);
2304
2305        if (!write_log('errors', $log_entry)) {
2306            // send error to PHPs error handler if write_log didn't succeed
2307            trigger_error($arg_arr['message']);
2308        }
2309    }
2310
2311    // report the bug to the global bug reporting system
2312    if ($level & 2) {
2313        // TODO: Send error via HTTP
2314    }
2315
2316    // show error if debug_mode is on
2317    if ($level & 4) {
2318        print "<b>$program Error";
2319
2320        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2321            print " in $arg_arr[file] ($arg_arr[line])";
2322
2323        print ':</b>&nbsp;';
2324        print nl2br($arg_arr['message']);
2325        print '<br />';
2326        flush();
2327    }
2328}
2329
2330function rcube_upload_progress()
2331{
2332    global $RCMAIL;
2333
2334    $prefix = ini_get('apc.rfc1867_prefix');
2335    $params = array(
2336        'action' => $RCMAIL->action,
2337        'name' => get_input_value('_progress', RCUBE_INPUT_GET),
2338    );
2339
2340    if (function_exists('apc_fetch')) {
2341        $status = apc_fetch($prefix . $params['name']);
2342
2343        if (!empty($status)) {
2344            $status['percent'] = round($status['current']/$status['total']*100);
2345            $params = array_merge($status, $params);
2346        }
2347    }
2348
2349    if (isset($params['percent']))
2350        $params['text'] = rcube_label(array('name' => 'uploadprogress', 'vars' => array(
2351            'percent' => $params['percent'] . '%',
2352            'current' => show_bytes($params['current']),
2353            'total'   => show_bytes($params['total'])
2354        )));
2355
2356    $RCMAIL->output->command('upload_progress_update', $params);
2357    $RCMAIL->output->send();
2358}
2359
2360function rcube_upload_init()
2361{
2362    global $RCMAIL;
2363
2364    // Enable upload progress bar
2365    if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
2366        if ($field_name = ini_get('apc.rfc1867_name')) {
2367            $RCMAIL->output->set_env('upload_progress_name', $field_name);
2368            $RCMAIL->output->set_env('upload_progress_time', (int) $seconds);
2369        }
2370    }
2371
2372    // find max filesize value
2373    $max_filesize = parse_bytes(ini_get('upload_max_filesize'));
2374    $max_postsize = parse_bytes(ini_get('post_max_size'));
2375    if ($max_postsize && $max_postsize < $max_filesize)
2376        $max_filesize = $max_postsize;
2377
2378    $RCMAIL->output->set_env('max_filesize', $max_filesize);
2379    $max_filesize = show_bytes($max_filesize);
2380    $RCMAIL->output->set_env('filesizeerror', rcube_label(array(
2381        'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
2382
2383    return $max_filesize;
2384}
2385
2386/**
2387 * Initializes client-side autocompletion
2388 */
2389function rcube_autocomplete_init()
2390{
2391    global $RCMAIL;
2392    static $init;
2393
2394    if ($init)
2395        return;
2396
2397    $init = 1;
2398
2399    if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) {
2400      $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql');
2401      if (count($book_types) > 1) {
2402        $RCMAIL->output->set_env('autocomplete_threads', $threads);
2403        $RCMAIL->output->set_env('autocomplete_sources', $book_types);
2404      }
2405    }
2406
2407    $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15));
2408    $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length'));
2409    $RCMAIL->output->add_label('autocompletechars', 'autocompletemore');
2410}
2411
2412function rcube_fontdefs($font = null)
2413{
2414  $fonts = array(
2415    'Andale Mono'   => '"Andale Mono",Times,monospace',
2416    'Arial'         => 'Arial,Helvetica,sans-serif',
2417    'Arial Black'   => '"Arial Black","Avant Garde",sans-serif',
2418    'Book Antiqua'  => '"Book Antiqua",Palatino,serif',
2419    'Courier New'   => '"Courier New",Courier,monospace',
2420    'Georgia'       => 'Georgia,Palatino,serif',
2421    'Helvetica'     => 'Helvetica,Arial,sans-serif',
2422    'Impact'        => 'Impact,Chicago,sans-serif',
2423    'Tahoma'        => 'Tahoma,Arial,Helvetica,sans-serif',
2424    'Terminal'      => 'Terminal,Monaco,monospace',
2425    'Times New Roman' => '"Times New Roman",Times,serif',
2426    'Trebuchet MS'  => '"Trebuchet MS",Geneva,sans-serif',
2427    'Verdana'       => 'Verdana,Geneva,sans-serif',
2428  );
2429
2430  if ($font)
2431    return $fonts[$font];
2432
2433  return $fonts;
2434}
Note: See TracBrowser for help on using the repository browser.