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

Last change on this file since 4216 was 4216, checked in by alec, 3 years ago
  • Fix handling of folders with name "0" (#1487119)
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 48.4 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-2009, Roundcube Dev, - Switzerland                 |
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('lib/utf7.inc');
30require_once('include/rcube_shared.inc');
31
32// fallback if not PHP modules are available
33@include_once('lib/utf8.class.php');
34
35// define constannts for input reading
36define('RCUBE_INPUT_GET', 0x0101);
37define('RCUBE_INPUT_POST', 0x0102);
38define('RCUBE_INPUT_GPC', 0x0103);
39
40
41
42/**
43 * Return correct name for a specific database table
44 *
45 * @param string Table name
46 * @return string Translated table name
47 */
48function get_table_name($table)
49  {
50  global $CONFIG;
51
52  // return table name if configured
53  $config_key = 'db_table_'.$table;
54
55  if (strlen($CONFIG[$config_key]))
56    return $CONFIG[$config_key];
57
58  return $table;
59  }
60
61
62/**
63 * Return correct name for a specific database sequence
64 * (used for Postgres only)
65 *
66 * @param string Secuence name
67 * @return string Translated sequence name
68 */
69function get_sequence_name($sequence)
70  {
71  // return sequence name if configured
72  $config_key = 'db_sequence_'.$sequence;
73  $opt = rcmail::get_instance()->config->get($config_key);
74
75  if (!empty($opt))
76    return $opt;
77   
78  return $sequence;
79  }
80
81
82/**
83 * Get localized text in the desired language
84 * It's a global wrapper for rcmail::gettext()
85 *
86 * @param mixed Named parameters array or label name
87 * @return string Localized text
88 * @see rcmail::gettext()
89 */
90function rcube_label($p, $domain=null)
91{
92  return rcmail::get_instance()->gettext($p, $domain);
93}
94
95
96/**
97 * Overwrite action variable
98 *
99 * @param string New action value
100 */
101function rcmail_overwrite_action($action)
102  {
103  $app = rcmail::get_instance();
104  $app->action = $action;
105  $app->output->set_env('action', $action);
106  }
107
108
109/**
110 * Compose an URL for a specific action
111 *
112 * @param string  Request action
113 * @param array   More URL parameters
114 * @param string  Request task (omit if the same)
115 * @return The application URL
116 */
117function rcmail_url($action, $p=array(), $task=null)
118{
119  $app = rcmail::get_instance();
120  return $app->url((array)$p + array('_action' => $action, 'task' => $task));
121}
122
123
124/**
125 * Garbage collector function for temp files.
126 * Remove temp files older than two days
127 */
128function rcmail_temp_gc()
129  {
130  $rcmail = rcmail::get_instance();
131
132  $tmp = unslashify($rcmail->config->get('temp_dir'));
133  $expire = mktime() - 172800;  // expire in 48 hours
134
135  if ($dir = opendir($tmp))
136    {
137    while (($fname = readdir($dir)) !== false)
138      {
139      if ($fname{0} == '.')
140        continue;
141
142      if (filemtime($tmp.'/'.$fname) < $expire)
143        @unlink($tmp.'/'.$fname);
144      }
145
146    closedir($dir);
147    }
148  }
149
150
151/**
152 * Garbage collector for cache entries.
153 * Remove all expired message cache records
154 * @return void
155 */
156function rcmail_cache_gc()
157  {
158  $rcmail = rcmail::get_instance();
159  $db = $rcmail->get_dbh();
160 
161  // get target timestamp
162  $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
163 
164  $db->query("DELETE FROM ".get_table_name('messages')."
165             WHERE  created < " . $db->fromunixtime($ts));
166
167  $db->query("DELETE FROM ".get_table_name('cache')."
168              WHERE  created < " . $db->fromunixtime($ts));
169  }
170
171
172/**
173 * Catch an error and throw an exception.
174 *
175 * @param  int    Level of the error
176 * @param  string Error message
177 */
178function rcube_error_handler($errno, $errstr)
179  {
180  throw new ErrorException($errstr, 0, $errno);
181  }
182
183
184/**
185 * Convert a string from one charset to another.
186 * Uses mbstring and iconv functions if possible
187 *
188 * @param  string Input string
189 * @param  string Suspected charset of the input string
190 * @param  string Target charset to convert to; defaults to RCMAIL_CHARSET
191 * @return string Converted string
192 */
193function rcube_charset_convert($str, $from, $to=NULL)
194  {
195  static $iconv_options = null;
196  static $mbstring_loaded = null;
197  static $mbstring_list = null;
198  static $convert_warning = false;
199  static $conv = null;
200
201  $error = false;
202
203  $to = empty($to) ? strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
204  $from = rcube_parse_charset($from);
205
206  if ($from == $to || empty($str) || empty($from))
207    return $str;
208
209  // convert charset using iconv module
210  if (function_exists('iconv') && $from != 'UTF7-IMAP' && $to != 'UTF7-IMAP') {
211    if ($iconv_options === null) {
212      // ignore characters not available in output charset
213      $iconv_options = '//IGNORE';
214      if (iconv('', $iconv_options, '') === false) {
215        // iconv implementation does not support options
216        $iconv_options = '';
217      }
218    }
219
220    // throw an exception if iconv reports an illegal character in input
221    // it means that input string has been truncated
222    set_error_handler('rcube_error_handler', E_NOTICE);
223    try {
224      $_iconv = iconv($from, $to . $iconv_options, $str);
225    } catch (ErrorException $e) {
226      $_iconv = false;
227    }
228    restore_error_handler();
229    if ($_iconv !== false) {
230      return $_iconv;
231    }
232  }
233
234  if ($mbstring_loaded === null)
235    $mbstring_loaded = extension_loaded('mbstring');
236   
237  // convert charset using mbstring module
238  if ($mbstring_loaded) {
239    $aliases['WINDOWS-1257'] = 'ISO-8859-13';
240   
241    if ($mbstring_list === null) {
242      $mbstring_list = mb_list_encodings();
243      $mbstring_list = array_map('strtoupper', $mbstring_list);
244    }
245
246    $mb_from = $aliases[$from] ? $aliases[$from] : $from;
247    $mb_to = $aliases[$to] ? $aliases[$to] : $to;
248   
249    // return if encoding found, string matches encoding and convert succeeded
250    if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
251      if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
252        return $out;
253    }
254  }
255
256  // convert charset using bundled classes/functions
257  if ($to == 'UTF-8') {
258    if ($from == 'UTF7-IMAP') {
259      if ($_str = utf7_to_utf8($str))
260        return $_str;
261    }
262    else if ($from == 'UTF-7') {
263      if ($_str = rcube_utf7_to_utf8($str))
264        return $_str;
265    }
266    else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
267      return utf8_encode($str);
268    }
269    else if (class_exists('utf8')) {
270      if (!$conv)
271        $conv = new utf8($from);
272      else
273        $conv->loadCharset($from);
274
275      if($_str = $conv->strToUtf8($str))
276        return $_str;
277    }
278    $error = true;
279  }
280 
281  // encode string for output
282  if ($from == 'UTF-8') {
283    // @TODO: we need a function for UTF-7 (RFC2152) conversion
284    if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
285      if ($_str = utf8_to_utf7($str))
286        return $_str;
287    }
288    else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
289      return utf8_decode($str);
290    }
291    else if (class_exists('utf8')) {
292      if (!$conv)
293        $conv = new utf8($to);
294      else
295        $conv->loadCharset($from);
296
297      if ($_str = $conv->strToUtf8($str))
298        return $_str;
299    }
300    $error = true;
301  }
302 
303  // report error
304  if ($error && !$convert_warning) {
305    raise_error(array(
306      'code' => 500,
307      'type' => 'php',
308      'file' => __FILE__,
309      'line' => __LINE__,
310      'message' => "Could not convert string from $from to $to. Make sure iconv/mbstring is installed or lib/utf8.class is available."
311      ), true, false);
312   
313    $convert_warning = 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    // commented out, because this breaks displaying of text with entity strings
564    // in text messages.
565    //$out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
566
567    return $newlines ? nl2br($out) : $out;
568    }
569
570  // if the replace tables for XML and JS are not yet defined
571  if ($js_rep_table===false)
572    {
573    $js_rep_table = $xml_rep_table = array();
574    $xml_rep_table['&'] = '&amp;';
575
576    for ($c=160; $c<256; $c++)  // can be increased to support more charsets
577      $xml_rep_table[chr($c)] = "&#$c;";
578
579    $xml_rep_table['"'] = '&quot;';
580    $js_rep_table['"'] = '\\"';
581    $js_rep_table["'"] = "\\'";
582    $js_rep_table["\\"] = "\\\\";
583    // Unicode line and paragraph separators (#1486310)
584    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
585    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
586    }
587
588  // encode for javascript use
589  if ($enctype=='js')
590    return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
591
592  // encode for plaintext
593  if ($enctype=='text')
594    return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
595
596  if ($enctype=='url')
597    return rawurlencode($str);
598
599  // encode for XML
600  if ($enctype=='xml')
601    return strtr($str, $xml_rep_table);
602
603  // no encoding given -> return original string
604  return $str;
605  }
606 
607/**
608 * Quote a given string.
609 * Shortcut function for rep_specialchars_output
610 *
611 * @return string HTML-quoted string
612 * @see rep_specialchars_output()
613 */
614function Q($str, $mode='strict', $newlines=TRUE)
615  {
616  return rep_specialchars_output($str, 'html', $mode, $newlines);
617  }
618
619/**
620 * Quote a given string for javascript output.
621 * Shortcut function for rep_specialchars_output
622 *
623 * @return string JS-quoted string
624 * @see rep_specialchars_output()
625 */
626function JQ($str)
627  {
628  return rep_specialchars_output($str, 'js');
629  }
630
631
632/**
633 * Read input value and convert it for internal use
634 * Performs stripslashes() and charset conversion if necessary
635 *
636 * @param  string   Field name to read
637 * @param  int      Source to get value from (GPC)
638 * @param  boolean  Allow HTML tags in field value
639 * @param  string   Charset to convert into
640 * @return string   Field value or NULL if not available
641 */
642function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
643{
644  $value = NULL;
645 
646  if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
647    $value = $_GET[$fname];
648  else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
649    $value = $_POST[$fname];
650  else if ($source==RCUBE_INPUT_GPC)
651    {
652    if (isset($_POST[$fname]))
653      $value = $_POST[$fname];
654    else if (isset($_GET[$fname]))
655      $value = $_GET[$fname];
656    else if (isset($_COOKIE[$fname]))
657      $value = $_COOKIE[$fname];
658    }
659
660  return parse_input_value($value, $allow_html, $charset);
661}
662
663/**
664 * Parse/validate input value. See get_input_value()
665 * Performs stripslashes() and charset conversion if necessary
666 *
667 * @param  string   Input value
668 * @param  boolean  Allow HTML tags in field value
669 * @param  string   Charset to convert into
670 * @return string   Parsed value
671 */
672function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
673{
674  global $OUTPUT;
675
676  if (empty($value))
677    return $value;
678
679  if (is_array($value)) {
680    foreach ($value as $idx => $val)
681      $value[$idx] = parse_input_value($val, $allow_html, $charset);
682    return $value;
683  }
684
685  // strip single quotes if magic_quotes_sybase is enabled
686  if (ini_get('magic_quotes_sybase'))
687    $value = str_replace("''", "'", $value);
688  // strip slashes if magic_quotes enabled
689  else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
690    $value = stripslashes($value);
691
692  // remove HTML tags if not allowed   
693  if (!$allow_html)
694    $value = strip_tags($value);
695 
696  // convert to internal charset
697  if (is_object($OUTPUT) && $charset)
698    return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
699  else
700    return $value;
701}
702
703/**
704 * Convert array of request parameters (prefixed with _)
705 * to a regular array with non-prefixed keys.
706 *
707 * @param  int   Source to get value from (GPC)
708 * @return array Hash array with all request parameters
709 */
710function request2param($mode = RCUBE_INPUT_GPC)
711{
712  $out = array();
713  $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
714  foreach ($src as $key => $value) {
715    $fname = $key[0] == '_' ? substr($key, 1) : $key;
716    $out[$fname] = get_input_value($key, $mode);
717  }
718 
719  return $out;
720}
721
722/**
723 * Remove all non-ascii and non-word chars
724 * except ., -, _
725 */
726function asciiwords($str, $css_id = false, $replace_with = '')
727{
728  $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
729  return preg_replace("/[^$allowed]/i", $replace_with, $str);
730}
731
732/**
733 * Remove single and double quotes from given string
734 *
735 * @param string Input value
736 * @return string Dequoted string
737 */
738function strip_quotes($str)
739{
740  return str_replace(array("'", '"'), '', $str);
741}
742
743
744/**
745 * Remove new lines characters from given string
746 *
747 * @param string Input value
748 * @return string Stripped string
749 */
750function strip_newlines($str)
751{
752  return preg_replace('/[\r\n]/', '', $str);
753}
754
755
756/**
757 * Create a HTML table based on the given data
758 *
759 * @param  array  Named table attributes
760 * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
761 * @param  array  List of cols to show
762 * @param  string Name of the identifier col
763 * @return string HTML table code
764 */
765function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
766  {
767  global $RCMAIL;
768 
769  $table = new html_table(/*array('cols' => count($a_show_cols))*/);
770   
771  // add table header
772  if (!$attrib['noheader'])
773    foreach ($a_show_cols as $col)
774      $table->add_header($col, Q(rcube_label($col)));
775 
776  $c = 0;
777  if (!is_array($table_data))
778  {
779    $db = $RCMAIL->get_dbh();
780    while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
781    {
782      $zebra_class = $c % 2 ? 'even' : 'odd';
783      $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class));
784
785      // format each col
786      foreach ($a_show_cols as $col)
787        $table->add($col, Q($sql_arr[$col]));
788     
789      $c++;
790    }
791  }
792  else
793  {
794    foreach ($table_data as $row_data)
795    {
796      $zebra_class = $c % 2 ? 'even' : 'odd';
797      if (!empty($row_data['class']))
798        $zebra_class .= ' '.$row_data['class'];
799
800      $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class));
801
802      // format each col
803      foreach ($a_show_cols as $col)
804        $table->add($col, Q($row_data[$col]));
805       
806      $c++;
807    }
808  }
809
810  return $table->show($attrib);
811  }
812
813
814/**
815 * Create an edit field for inclusion on a form
816 *
817 * @param string col field name
818 * @param string value field value
819 * @param array attrib HTML element attributes for field
820 * @param string type HTML element type (default 'text')
821 * @return string HTML field definition
822 */
823function rcmail_get_edit_field($col, $value, $attrib, $type='text')
824  {
825  $fname = '_'.$col;
826  $attrib['name'] = $fname;
827 
828  if ($type=='checkbox')
829    {
830    $attrib['value'] = '1';
831    $input = new html_checkbox($attrib);
832    }
833  else if ($type=='textarea')
834    {
835    $attrib['cols'] = $attrib['size'];
836    $input = new html_textarea($attrib);
837    }
838  else
839    $input = new html_inputfield($attrib);
840
841  // use value from post
842  if (!empty($_POST[$fname]))
843    $value = get_input_value($fname, RCUBE_INPUT_POST,
844            $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
845
846  $out = $input->show($value);
847         
848  return $out;
849  }
850
851
852/**
853 * Replace all css definitions with #container [def]
854 * and remove css-inlined scripting
855 *
856 * @param string CSS source code
857 * @param string Container ID to use as prefix
858 * @return string Modified CSS source
859 */
860function rcmail_mod_css_styles($source, $container_id)
861  {
862  $last_pos = 0;
863  $replacements = new rcube_string_replacer;
864
865  // ignore the whole block if evil styles are detected
866  $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entity_decode($source));
867  if (preg_match('/expression|behavior|url\(|import/', $stripped))
868    return '/* evil! */';
869
870  // remove css comments (sometimes used for some ugly hacks)
871  $source = preg_replace('!/\*(.+)\*/!Ums', '', $source);
872
873  // cut out all contents between { and }
874  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
875  {
876    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
877    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
878    $last_pos = $pos+2;
879  }
880
881  // remove html comments and add #container to each tag selector.
882  // also replace body definition because we also stripped off the <body> tag
883  $styles = preg_replace(
884    array(
885      '/(^\s*<!--)|(-->\s*$)/',
886      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
887      '/'.preg_quote($container_id, '/').'\s+body/i',
888    ),
889    array(
890      '',
891      "\\1#$container_id \\2",
892      $container_id,
893    ),
894    $source);
895
896  // put block contents back in
897  $styles = $replacements->resolve($styles);
898
899  return $styles;
900  }
901
902
903/**
904 * Decode escaped entities used by known XSS exploits.
905 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
906 *
907 * @param string CSS content to decode
908 * @return string Decoded string
909 */
910function rcmail_xss_entity_decode($content)
911{
912  $out = html_entity_decode(html_entity_decode($content));
913  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
914  $out = preg_replace('#/\*.*\*/#Um', '', $out);
915  return $out;
916}
917
918
919/**
920 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
921 *
922 * @param array matches result from preg_replace_callback
923 * @return string decoded entity
924 */
925function rcmail_xss_entity_decode_callback($matches)
926{
927  return chr(hexdec($matches[1]));
928}
929
930/**
931 * Compose a valid attribute string for HTML tags
932 *
933 * @param array Named tag attributes
934 * @param array List of allowed attributes
935 * @return string HTML formatted attribute string
936 */
937function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
938  {
939  // allow the following attributes to be added to the <iframe> tag
940  $attrib_str = '';
941  foreach ($allowed_attribs as $a)
942    if (isset($attrib[$a]))
943      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
944
945  return $attrib_str;
946  }
947
948
949/**
950 * Convert a HTML attribute string attributes to an associative array (name => value)
951 *
952 * @param string Input string
953 * @return array Key-value pairs of parsed attributes
954 */
955function parse_attrib_string($str)
956  {
957  $attrib = array();
958  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
959
960  // convert attributes to an associative array (name => value)
961  if ($regs) {
962    foreach ($regs as $attr) {
963      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
964    }
965  }
966
967  return $attrib;
968  }
969
970
971/**
972 * Convert the given date to a human readable form
973 * This uses the date formatting properties from config
974 *
975 * @param mixed Date representation (string or timestamp)
976 * @param string Date format to use
977 * @return string Formatted date string
978 */
979function format_date($date, $format=NULL)
980  {
981  global $CONFIG;
982 
983  $ts = NULL;
984
985  if (is_numeric($date))
986    $ts = $date;
987  else if (!empty($date))
988    {
989    // support non-standard "GMTXXXX" literal
990    $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
991    // if date parsing fails, we have a date in non-rfc format.
992    // remove token from the end and try again
993    while ((($ts = @strtotime($date))===false) || ($ts < 0))
994      {
995        $d = explode(' ', $date);
996        array_pop($d);
997        if (!$d) break;
998        $date = implode(' ', $d);
999      }
1000    }
1001
1002  if (empty($ts))
1003    return '';
1004   
1005  // get user's timezone
1006  if ($CONFIG['timezone'] === 'auto')
1007    $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
1008  else {
1009    $tz = $CONFIG['timezone'];
1010    if ($CONFIG['dst_active'])
1011      $tz++;
1012  }
1013
1014  // convert time to user's timezone
1015  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
1016 
1017  // get current timestamp in user's timezone
1018  $now = time();  // local time
1019  $now -= (int)date('Z'); // make GMT time
1020  $now += ($tz * 3600); // user's time
1021  $now_date = getdate($now);
1022
1023  $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1024  $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
1025
1026  // define date format depending on current time
1027  if (!$format) {
1028    if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now)
1029      return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
1030    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1031      $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
1032    else
1033      $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
1034    }
1035
1036  // strftime() format
1037  if (preg_match('/%[a-z]+/i', $format))
1038    return strftime($format, $timestamp);
1039
1040  // parse format string manually in order to provide localized weekday and month names
1041  // an alternative would be to convert the date() format string to fit with strftime()
1042  $out = '';
1043  for($i=0; $i<strlen($format); $i++)
1044    {
1045    if ($format{$i}=='\\')  // skip escape chars
1046      continue;
1047   
1048    // write char "as-is"
1049    if ($format{$i}==' ' || $format{$i-1}=='\\')
1050      $out .= $format{$i};
1051    // weekday (short)
1052    else if ($format{$i}=='D')
1053      $out .= rcube_label(strtolower(date('D', $timestamp)));
1054    // weekday long
1055    else if ($format{$i}=='l')
1056      $out .= rcube_label(strtolower(date('l', $timestamp)));
1057    // month name (short)
1058    else if ($format{$i}=='M')
1059      $out .= rcube_label(strtolower(date('M', $timestamp)));
1060    // month name (long)
1061    else if ($format{$i}=='F')
1062      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1063    else if ($format{$i}=='x')
1064      $out .= strftime('%x %X', $timestamp);
1065    else
1066      $out .= date($format{$i}, $timestamp);
1067    }
1068 
1069  return $out;
1070  }
1071
1072
1073/**
1074 * Compose a valid representation of name and e-mail address
1075 *
1076 * @param string E-mail address
1077 * @param string Person name
1078 * @return string Formatted string
1079 */
1080function format_email_recipient($email, $name='')
1081  {
1082  if ($name && $name != $email)
1083    {
1084    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1085    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1086    }
1087  else
1088    return trim($email);
1089  }
1090
1091
1092
1093/****** debugging functions ********/
1094
1095
1096/**
1097 * Print or write debug messages
1098 *
1099 * @param mixed Debug message or data
1100 * @return void
1101 */
1102function console()
1103  {
1104  $args = func_get_args();
1105
1106  if (class_exists('rcmail', false)) {
1107    $rcmail = rcmail::get_instance();
1108    if (is_object($rcmail->plugins))
1109      $rcmail->plugins->exec_hook('console', $args);
1110  }
1111
1112  $msg = array();
1113  foreach ($args as $arg)
1114    $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1115
1116  if (!($GLOBALS['CONFIG']['debug_level'] & 4))
1117    write_log('console', join(";\n", $msg));
1118  else if ($GLOBALS['OUTPUT']->ajax_call)
1119    print "/*\n " . join(";\n", $msg) . " \n*/\n";
1120  else
1121    {
1122    print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
1123    print join(";<br/>\n", $msg);
1124    print "</pre></div>\n";
1125    }
1126  }
1127
1128
1129/**
1130 * Append a line to a logfile in the logs directory.
1131 * Date will be added automatically to the line.
1132 *
1133 * @param $name name of log file
1134 * @param line Line to append
1135 * @return void
1136 */
1137function write_log($name, $line)
1138  {
1139  global $CONFIG, $RCMAIL;
1140
1141  if (!is_string($line))
1142    $line = var_export($line, true);
1143 
1144  if (empty($CONFIG['log_date_format']))
1145    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1146 
1147  $date = date($CONFIG['log_date_format']);
1148 
1149  // trigger logging hook
1150  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1151    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1152    $name = $log['name'];
1153    $line = $log['line'];
1154    $date = $log['date'];
1155    if ($log['abort'])
1156      return true;
1157  }
1158 
1159  if ($CONFIG['log_driver'] == 'syslog') {
1160    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1161    syslog($prio, $line);
1162    return true;
1163  }
1164  else {
1165    $line = sprintf("[%s]: %s\n", $date, $line);
1166
1167    // log_driver == 'file' is assumed here
1168    if (empty($CONFIG['log_dir']))
1169      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1170
1171    // try to open specific log file for writing
1172    $logfile = $CONFIG['log_dir'].'/'.$name;
1173    if ($fp = @fopen($logfile, 'a')) {
1174      fwrite($fp, $line);
1175      fflush($fp);
1176      fclose($fp);
1177      return true;
1178    }
1179    else
1180      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1181  }
1182  return false;
1183}
1184
1185
1186/**
1187 * Write login data (name, ID, IP address) to the 'userlogins' log file.
1188 *
1189 * @return void
1190 */
1191function rcmail_log_login()
1192{
1193  global $RCMAIL;
1194
1195  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1196    return;
1197
1198  $address = $_SERVER['REMOTE_ADDR'];
1199  // append the NGINX X-Real-IP header, if set
1200  if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1201    $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1202  }
1203  // append the X-Forwarded-For header, if set
1204  if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1205    $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1206  }
1207
1208  if (!empty($remote_ip))
1209    $address .= '(' . implode(',', $remote_ip) . ')';
1210
1211  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1212    $RCMAIL->user->get_username(), $RCMAIL->user->ID, $address));
1213}
1214
1215
1216/**
1217 * @access private
1218 * @return mixed
1219 */
1220function rcube_timer()
1221{
1222  return microtime(true);
1223}
1224 
1225
1226/**
1227 * @access private
1228 * @return void
1229 */
1230function rcube_print_time($timer, $label='Timer', $dest='console')
1231{
1232  static $print_count = 0;
1233 
1234  $print_count++;
1235  $now = rcube_timer();
1236  $diff = $now-$timer;
1237 
1238  if (empty($label))
1239    $label = 'Timer '.$print_count;
1240 
1241  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1242}
1243
1244
1245/**
1246 * Return the mailboxlist in HTML
1247 *
1248 * @param array Named parameters
1249 * @return string HTML code for the gui object
1250 */
1251function rcmail_mailbox_list($attrib)
1252{
1253  global $RCMAIL;
1254  static $a_mailboxes;
1255 
1256  $attrib += array('maxlength' => 100, 'realnames' => false);
1257
1258  // add some labels to client
1259  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1260 
1261  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1262  unset($attrib['type']);
1263
1264  if ($type=='ul' && !$attrib['id'])
1265    $attrib['id'] = 'rcmboxlist';
1266
1267  // get mailbox list
1268  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1269 
1270  // build the folders tree
1271  if (empty($a_mailboxes)) {
1272    // get mailbox list
1273    $a_folders = $RCMAIL->imap->list_mailboxes();
1274    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1275    $a_mailboxes = array();
1276
1277    foreach ($a_folders as $folder)
1278      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1279  }
1280
1281  // allow plugins to alter the folder tree or to localize folder names
1282  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1283
1284  if ($type=='select') {
1285    $select = new html_select($attrib);
1286   
1287    // add no-selection option
1288    if ($attrib['noselection'])
1289      $select->add(rcube_label($attrib['noselection']), '0');
1290   
1291    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1292    $out = $select->show();
1293  }
1294  else {
1295    $js_mailboxlist = array();
1296    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1297   
1298    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1299    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1300    $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1301  }
1302
1303  return $out;
1304}
1305
1306
1307/**
1308 * Return the mailboxlist as html_select object
1309 *
1310 * @param array Named parameters
1311 * @return html_select HTML drop-down object
1312 */
1313function rcmail_mailbox_select($p = array())
1314{
1315  global $RCMAIL;
1316 
1317  $p += array('maxlength' => 100, 'realnames' => false);
1318  $a_mailboxes = array();
1319 
1320  foreach ($RCMAIL->imap->list_mailboxes() as $folder)
1321    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1322      rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1323
1324  $select = new html_select($p);
1325 
1326  if ($p['noselection'])
1327    $select->add($p['noselection'], '');
1328   
1329  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1330 
1331  return $select;
1332}
1333
1334
1335/**
1336 * Create a hierarchical array of the mailbox list
1337 * @access private
1338 * @return void
1339 */
1340function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1341{
1342  global $RCMAIL;
1343
1344  $pos = strpos($folder, $delm);
1345
1346  if ($pos !== false) {
1347    $subFolders = substr($folder, $pos+1);
1348    $currentFolder = substr($folder, 0, $pos);
1349
1350    // sometimes folder has a delimiter as the last character
1351    if (!strlen($subFolders))
1352      $virtual = false;
1353    else if (!isset($arrFolders[$currentFolder]))
1354      $virtual = true;
1355    else
1356      $virtual = $arrFolders[$currentFolder]['virtual'];
1357  }
1358  else {
1359    $subFolders = false;
1360    $currentFolder = $folder;
1361    $virtual = false;
1362  }
1363
1364  $path .= $currentFolder;
1365
1366  // Check \Noselect option (if options are in cache)
1367  if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
1368    $virtual = in_array('\\Noselect', $opts);
1369  }
1370
1371  if (!isset($arrFolders[$currentFolder])) {
1372    $arrFolders[$currentFolder] = array(
1373      'id' => $path,
1374      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1375      'virtual' => $virtual,
1376      'folders' => array());
1377  }
1378  else
1379    $arrFolders[$currentFolder]['virtual'] = $virtual;
1380
1381  if (strlen($subFolders))
1382    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1383}
1384 
1385
1386/**
1387 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1388 * @access private
1389 * @return string
1390 */
1391function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1392{
1393  global $RCMAIL, $CONFIG;
1394 
1395  $maxlength = intval($attrib['maxlength']);
1396  $realnames = (bool)$attrib['realnames'];
1397  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1398
1399  $idx = 0;
1400  $out = '';
1401  foreach ($arrFolders as $key => $folder) {
1402    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1403    $title = null;
1404
1405    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1406      $foldername = rcube_label($folder_class);
1407    }
1408    else {
1409      $foldername = $folder['name'];
1410
1411      // shorten the folder name to a given length
1412      if ($maxlength && $maxlength > 1) {
1413        $fname = abbreviate_string($foldername, $maxlength);
1414        if ($fname != $foldername)
1415          $title = $foldername;
1416        $foldername = $fname;
1417      }
1418    }
1419
1420    // make folder name safe for ids and class names
1421    $folder_id = asciiwords($folder['id'], true, '_');
1422    $classes = array('mailbox');
1423
1424    // set special class for Sent, Drafts, Trash and Junk
1425    if ($folder['id']==$CONFIG['sent_mbox'])
1426      $classes[] = 'sent';
1427    else if ($folder['id']==$CONFIG['drafts_mbox'])
1428      $classes[] = 'drafts';
1429    else if ($folder['id']==$CONFIG['trash_mbox'])
1430      $classes[] = 'trash';
1431    else if ($folder['id']==$CONFIG['junk_mbox'])
1432      $classes[] = 'junk';
1433    else if ($folder['id']=='INBOX')
1434      $classes[] = 'inbox';
1435    else
1436      $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1437     
1438    $classes[] = $zebra_class;
1439   
1440    if ($folder['id'] == $mbox_name)
1441      $classes[] = 'selected';
1442
1443    $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1444    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1445   
1446    if ($folder['virtual'])
1447      $classes[] = 'virtual';
1448    else if ($unread)
1449      $classes[] = 'unread';
1450
1451    $js_name = JQ($folder['id']);
1452    $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1453    $link_attrib = $folder['virtual'] ? array() : array(
1454      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1455      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1456      'title' => $title,
1457    );
1458
1459    $out .= html::tag('li', array(
1460        'id' => "rcmli".$folder_id,
1461        'class' => join(' ', $classes),
1462        'noclose' => true),
1463      html::a($link_attrib, $html_name) .
1464      (!empty($folder['folders']) ? html::div(array(
1465        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1466        'style' => "position:absolute",
1467        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1468      ), '&nbsp;') : ''));
1469   
1470    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1471   
1472    if (!empty($folder['folders'])) {
1473      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1474        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1475    }
1476
1477    $out .= "</li>\n";
1478    $idx++;
1479  }
1480
1481  return $out;
1482}
1483
1484
1485/**
1486 * Return html for a flat list <select> for the mailbox tree
1487 * @access private
1488 * @return string
1489 */
1490function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1491  {
1492  $idx = 0;
1493  $out = '';
1494  foreach ($arrFolders as $key=>$folder)
1495    {
1496    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1497      $foldername = rcube_label($folder_class);
1498    else
1499      {
1500      $foldername = $folder['name'];
1501     
1502      // shorten the folder name to a given length
1503      if ($maxlength && $maxlength>1)
1504        $foldername = abbreviate_string($foldername, $maxlength);
1505      }
1506
1507    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1508
1509    if (!empty($folder['folders']))
1510      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1511
1512    $idx++;
1513    }
1514
1515  return $out;
1516  }
1517
1518
1519/**
1520 * Return internal name for the given folder if it matches the configured special folders
1521 * @access private
1522 * @return string
1523 */
1524function rcmail_folder_classname($folder_id)
1525{
1526  global $CONFIG;
1527
1528  if ($folder_id == 'INBOX')
1529    return 'inbox';
1530
1531  // for these mailboxes we have localized labels and css classes
1532  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1533  {
1534    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1535      return $smbx;
1536  }
1537}
1538
1539
1540/**
1541 * Try to localize the given IMAP folder name.
1542 * UTF-7 decode it in case no localized text was found
1543 *
1544 * @param string Folder name
1545 * @return string Localized folder name in UTF-8 encoding
1546 */
1547function rcmail_localize_foldername($name)
1548{
1549  if ($folder_class = rcmail_folder_classname($name))
1550    return rcube_label($folder_class);
1551  else
1552    return rcube_charset_convert($name, 'UTF7-IMAP');
1553}
1554
1555
1556/**
1557 * Output HTML editor scripts
1558 *
1559 * @param string Editor mode
1560 * @return void
1561 */
1562function rcube_html_editor($mode='')
1563{
1564  global $RCMAIL, $CONFIG;
1565
1566  $hook = $RCMAIL->plugins->exec_hook('hmtl_editor', array('mode' => $mode));
1567
1568  if ($hook['abort'])
1569    return; 
1570
1571  $lang = strtolower($_SESSION['language']);
1572
1573  // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1574  $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1575
1576  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1577    $lang = 'en';
1578
1579  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1580  $RCMAIL->output->include_script('editor.js');
1581  $RCMAIL->output->add_script('rcmail_editor_init("$__skin_path",
1582    "'.JQ($lang).'", '.intval($CONFIG['enable_spellcheck']).', "'.$mode.'");');
1583}
1584
1585
1586/**
1587 * Check if working in SSL mode
1588 *
1589 * @param integer HTTPS port number
1590 * @param boolean Enables 'use_https' option checking
1591 * @return boolean
1592 */
1593function rcube_https_check($port=null, $use_https=true)
1594{
1595  global $RCMAIL;
1596
1597  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1598    return true;
1599  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1600    return true;
1601  if ($port && $_SERVER['SERVER_PORT'] == $port)
1602    return true;
1603  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1604    return true;
1605
1606  return false;
1607}
1608
1609
1610/**
1611 * For backward compatibility.
1612 *
1613 * @global rcmail $RCMAIL
1614 * @param string $var_name Variable name.
1615 * @return void
1616 */
1617function rcube_sess_unset($var_name=null)
1618{
1619  global $RCMAIL;
1620
1621  $RCMAIL->session->remove($var_name);
1622}
1623
1624
1625
1626/**
1627 * Replaces hostname variables
1628 *
1629 * @param string $name Hostname
1630 * @return string
1631 */
1632function rcube_parse_host($name)
1633{
1634  // %n - host
1635  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1636  // %d - domain name without first part, e.g. %d=mail.domain.tld, %m=domain.tld
1637  $d = preg_replace('/^[^\.]+\./', '', $n);
1638  // %h - IMAP host
1639  $h = $_SESSION['imap_host'];
1640  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1641  $z = preg_replace('/^[^\.]+\./', '', $h);
1642
1643  $name = str_replace(array('%n', '%d', '%h', '%z'), array($n, $d, $h, $z), $name);
1644  return $name;
1645}
1646
1647
1648/**
1649 * E-mail address validation
1650 *
1651 * @param string $email Email address
1652 * @param boolean $dns_check True to check dns
1653 * @return boolean
1654 */
1655function check_email($email, $dns_check=true)
1656{
1657  // Check for invalid characters
1658  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1659    return false;
1660
1661  // Check for length limit specified by RFC 5321 (#1486453)
1662  if (strlen($email) > 254)
1663    return false;
1664
1665  $email_array = explode('@', $email);
1666
1667  // Check that there's one @ symbol
1668  if (count($email_array) < 2)
1669    return false;
1670
1671  $domain_part = array_pop($email_array);
1672  $local_part = implode('@', $email_array);
1673
1674  // from PEAR::Validate
1675  $regexp = '&^(?:
1676        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1677        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
1678        $&xi';
1679
1680  if (!preg_match($regexp, $local_part))
1681    return false;
1682
1683  // Check domain part
1684  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))
1685    return true; // IP address
1686  else {
1687    // If not an IP address
1688    $domain_array = explode('.', $domain_part);
1689    if (sizeof($domain_array) < 2)
1690      return false; // Not enough parts to be a valid domain
1691
1692    foreach ($domain_array as $part)
1693      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1694        return false;
1695
1696    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1697      return true;
1698
1699    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1700      $lookup = array();
1701      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1702      foreach ($lookup as $line) {
1703        if (strpos($line, 'MX preference'))
1704          return true;
1705      }
1706      return false;
1707    }
1708
1709    // find MX record(s)
1710    if (getmxrr($domain_part, $mx_records))
1711      return true;
1712
1713    // find any DNS record
1714    if (checkdnsrr($domain_part, 'ANY'))
1715      return true;
1716  }
1717
1718  return false;
1719}
1720
1721
1722/**
1723 * Helper class to turn relative urls into absolute ones
1724 * using a predefined base
1725 */
1726class rcube_base_replacer
1727{
1728  private $base_url;
1729
1730  public function __construct($base)
1731  {
1732    $this->base_url = $base;
1733  }
1734
1735  public function callback($matches)
1736  {
1737    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1738  }
1739}
1740
1741
1742/**
1743 * Throw system error and show error page
1744 *
1745 * @param array Named parameters
1746 *  - code: Error code (required)
1747 *  - type: Error type [php|db|imap|javascript] (required)
1748 *  - message: Error message
1749 *  - file: File where error occured
1750 *  - line: Line where error occured
1751 * @param boolean True to log the error
1752 * @param boolean Terminate script execution
1753 */
1754// may be defined in Installer
1755if (!function_exists('raise_error')) {
1756function raise_error($arg=array(), $log=false, $terminate=false)
1757{
1758    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
1759
1760    // report bug (if not incompatible browser)
1761    if ($log && $arg['type'] && $arg['message'])
1762        log_bug($arg);
1763
1764    // display error page and terminate script
1765    if ($terminate) {
1766        $ERROR_CODE = $arg['code'];
1767        $ERROR_MESSAGE = $arg['message'];
1768        include('program/steps/utils/error.inc');
1769        exit;
1770    }
1771}
1772}
1773
1774
1775/**
1776 * Report error according to configured debug_level
1777 *
1778 * @param array Named parameters
1779 * @return void
1780 * @see raise_error()
1781 */
1782function log_bug($arg_arr)
1783{
1784    global $CONFIG;
1785    $program = strtoupper($arg_arr['type']);
1786
1787    // write error to local log file
1788    if ($CONFIG['debug_level'] & 1) {
1789        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
1790        $log_entry = sprintf("%s Error: %s%s (%s %s)",
1791            $program,
1792            $arg_arr['message'],
1793            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
1794            $_SERVER['REQUEST_METHOD'],
1795            $_SERVER['REQUEST_URI'] . $post_query);
1796
1797        if (!write_log('errors', $log_entry)) {
1798            // send error to PHPs error handler if write_log didn't succeed
1799            trigger_error($arg_arr['message']);
1800        }
1801    }
1802
1803    // resport the bug to the global bug reporting system
1804    if ($CONFIG['debug_level'] & 2) {
1805        // TODO: Send error via HTTP
1806    }
1807
1808    // show error if debug_mode is on
1809    if ($CONFIG['debug_level'] & 4) {
1810        print "<b>$program Error";
1811
1812        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
1813            print " in $arg_arr[file] ($arg_arr[line])";
1814
1815        print ':</b>&nbsp;';
1816        print nl2br($arg_arr['message']);
1817        print '<br />';
1818        flush();
1819    }
1820}
Note: See TracBrowser for help on using the repository browser.