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

Last change on this file since 4119 was 4119, checked in by alec, 3 years ago
  • Add charset alias: WINDOWS-949 -> UHC (#1487072)
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 47.9 KB
Line 
1<?php
2
3/*
4 +-----------------------------------------------------------------------+
5 | program/include/main.inc                                              |
6 |                                                                       |
7 | This file is part of the Roundcube Webmail client                     |
8 | Copyright (C) 2005-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  # Aliases: some of them from HTML5 spec.
345  $aliases = array(
346    'USASCII'       => 'WINDOWS-1252',
347    'ANSIX31101983' => 'WINDOWS-1252',
348    'ANSIX341968'   => 'WINDOWS-1252',
349    'UNKNOWN8BIT'   => 'ISO-8859-15',
350    'UNKNOWN'       => 'ISO-8859-15',
351    'USERDEFINED'   => 'ISO-8859-15',
352    'KSC56011987'   => 'EUC-KR',
353    'GB2312'        => 'GBK',
354    'GB231280'      => 'GBK',
355    'UNICODE'       => 'UTF-8',
356    'UTF7IMAP'      => 'UTF7-IMAP',
357    'TIS620'        => 'WINDOWS-874',
358    'ISO88599'      => 'WINDOWS-1254',
359    'ISO885911'     => 'WINDOWS-874',
360    'MACROMAN'      => 'MACINTOSH',
361    '77'            => 'MAC',
362    '128'           => 'SHIFT-JIS',
363    '129'           => 'CP949',
364    '130'           => 'CP1361',
365    '134'           => 'GBK',
366    '136'           => 'BIG5',
367    '161'           => 'WINDOWS-1253',
368    '162'           => 'WINDOWS-1254',
369    '163'           => 'WINDOWS-1258',
370    '177'           => 'WINDOWS-1255',
371    '178'           => 'WINDOWS-1256',
372    '186'           => 'WINDOWS-1257',
373    '204'           => 'WINDOWS-1251',
374    '222'           => 'WINDOWS-874',
375    '238'           => 'WINDOWS-1250',
376    'MS950'         => 'CP950',
377    'WINDOWS949'    => 'UHC',
378  );
379
380  // allow A-Z and 0-9 only
381  $str = preg_replace('/[^A-Z0-9]/', '', $charset);
382
383  if (isset($aliases[$str]))
384    $result = $aliases[$str];
385  // UTF
386  else if (preg_match('/U[A-Z][A-Z](7|8|16|32)(BE|LE)*/', $str, $m))
387    $result = 'UTF-' . $m[1] . $m[2];
388  // ISO-8859
389  else if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
390    $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
391    // some clients sends windows-1252 text as latin1,
392    // it is safe to use windows-1252 for all latin1
393    $result = $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
394    }
395  // handle broken charset names e.g. WINDOWS-1250HTTP-EQUIVCONTENT-TYPE
396  else if (preg_match('/(WIN|WINDOWS)([0-9]+)/', $str, $m)) {
397    $result = 'WINDOWS-' . $m[2];
398    }
399  // LATIN
400  else if (preg_match('/LATIN(.*)/', $str, $m)) {
401    $aliases = array('2' => 2, '3' => 3, '4' => 4, '5' => 9, '6' => 10,
402        '7' => 13, '8' => 14, '9' => 15, '10' => 16,
403        'ARABIC' => 6, 'CYRILLIC' => 5, 'GREEK' => 7, 'GREEK1' => 7, 'HEBREW' => 8);
404
405    // some clients sends windows-1252 text as latin1,
406    // it is safe to use windows-1252 for all latin1
407    if ($m[1] == 1) {
408      $result = 'WINDOWS-1252';
409      }
410    // if iconv is not supported we need ISO labels, it's also safe for iconv
411    else if (!empty($aliases[$m[1]])) {
412      $result = 'ISO-8859-'.$aliases[$m[1]];
413      }
414    // iconv requires convertion of e.g. LATIN-1 to LATIN1
415    else {
416      $result = $str;
417      }
418    }
419  else {
420    $result = $charset;
421    }
422
423  $charsets[$input] = $result;
424
425  return $result;
426  }
427
428
429/**
430 * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
431 *
432 * @param  string  Input string
433 * @return string  The converted string
434 */
435function rcube_utf7_to_utf8($str)
436{
437  $Index_64 = array(
438    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
439    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
440    0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
441    1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
442    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
443    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
444    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
445    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
446  );
447
448  $u7len = strlen($str);
449  $str = strval($str);
450  $res = '';
451
452  for ($i=0; $u7len > 0; $i++, $u7len--)
453  {
454    $u7 = $str[$i];
455    if ($u7 == '+')
456    {
457      $i++;
458      $u7len--;
459      $ch = '';
460
461      for (; $u7len > 0; $i++, $u7len--)
462      {
463        $u7 = $str[$i];
464
465        if (!$Index_64[ord($u7)])
466          break;
467
468        $ch .= $u7;
469      }
470
471      if ($ch == '') {
472        if ($u7 == '-')
473          $res .= '+';
474        continue;
475      }
476
477      $res .= rcube_utf16_to_utf8(base64_decode($ch));
478    }
479    else
480    {
481      $res .= $u7;
482    }
483  }
484
485  return $res;
486}
487
488/**
489 * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
490 *
491 * @param  string  Input string
492 * @return string  The converted string
493 */
494function rcube_utf16_to_utf8($str)
495{
496  $len = strlen($str);
497  $dec = '';
498
499  for ($i = 0; $i < $len; $i += 2) {
500    $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
501    if ($c >= 0x0001 && $c <= 0x007F) {
502      $dec .= chr($c);
503    } else if ($c > 0x07FF) {
504      $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
505      $dec .= chr(0x80 | (($c >>  6) & 0x3F));
506      $dec .= chr(0x80 | (($c >>  0) & 0x3F));
507    } else {
508      $dec .= chr(0xC0 | (($c >>  6) & 0x1F));
509      $dec .= chr(0x80 | (($c >>  0) & 0x3F));
510    }
511  }
512  return $dec;
513}
514
515
516/**
517 * Replacing specials characters to a specific encoding type
518 *
519 * @param  string  Input string
520 * @param  string  Encoding type: text|html|xml|js|url
521 * @param  string  Replace mode for tags: show|replace|remove
522 * @param  boolean Convert newlines
523 * @return string  The quoted string
524 */
525function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
526  {
527  static $html_encode_arr = false;
528  static $js_rep_table = false;
529  static $xml_rep_table = false;
530
531  if (!$enctype)
532    $enctype = $OUTPUT->type;
533
534  // encode for HTML output
535  if ($enctype=='html')
536    {
537    if (!$html_encode_arr)
538      {
539      $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);       
540      unset($html_encode_arr['?']);
541      }
542
543    $ltpos = strpos($str, '<');
544    $encode_arr = $html_encode_arr;
545
546    // don't replace quotes and html tags
547    if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
548      {
549      unset($encode_arr['"']);
550      unset($encode_arr['<']);
551      unset($encode_arr['>']);
552      unset($encode_arr['&']);
553      }
554    else if ($mode=='remove')
555      $str = strip_tags($str);
556   
557    // avoid douple quotation of &
558    $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', strtr($str, $encode_arr));
559     
560    return $newlines ? nl2br($out) : $out;
561    }
562
563  // if the replace tables for XML and JS are not yet defined
564  if ($js_rep_table===false)
565    {
566    $js_rep_table = $xml_rep_table = array();
567    $xml_rep_table['&'] = '&amp;';
568
569    for ($c=160; $c<256; $c++)  // can be increased to support more charsets
570      $xml_rep_table[chr($c)] = "&#$c;";
571
572    $xml_rep_table['"'] = '&quot;';
573    $js_rep_table['"'] = '\\"';
574    $js_rep_table["'"] = "\\'";
575    $js_rep_table["\\"] = "\\\\";
576    // Unicode line and paragraph separators (#1486310)
577    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
578    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
579    }
580
581  // encode for javascript use
582  if ($enctype=='js')
583    return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
584
585  // encode for plaintext
586  if ($enctype=='text')
587    return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
588
589  if ($enctype=='url')
590    return rawurlencode($str);
591
592  // encode for XML
593  if ($enctype=='xml')
594    return strtr($str, $xml_rep_table);
595
596  // no encoding given -> return original string
597  return $str;
598  }
599 
600/**
601 * Quote a given string.
602 * Shortcut function for rep_specialchars_output
603 *
604 * @return string HTML-quoted string
605 * @see rep_specialchars_output()
606 */
607function Q($str, $mode='strict', $newlines=TRUE)
608  {
609  return rep_specialchars_output($str, 'html', $mode, $newlines);
610  }
611
612/**
613 * Quote a given string for javascript output.
614 * Shortcut function for rep_specialchars_output
615 *
616 * @return string JS-quoted string
617 * @see rep_specialchars_output()
618 */
619function JQ($str)
620  {
621  return rep_specialchars_output($str, 'js');
622  }
623
624
625/**
626 * Read input value and convert it for internal use
627 * Performs stripslashes() and charset conversion if necessary
628 *
629 * @param  string   Field name to read
630 * @param  int      Source to get value from (GPC)
631 * @param  boolean  Allow HTML tags in field value
632 * @param  string   Charset to convert into
633 * @return string   Field value or NULL if not available
634 */
635function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
636{
637  $value = NULL;
638 
639  if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
640    $value = $_GET[$fname];
641  else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
642    $value = $_POST[$fname];
643  else if ($source==RCUBE_INPUT_GPC)
644    {
645    if (isset($_POST[$fname]))
646      $value = $_POST[$fname];
647    else if (isset($_GET[$fname]))
648      $value = $_GET[$fname];
649    else if (isset($_COOKIE[$fname]))
650      $value = $_COOKIE[$fname];
651    }
652
653  return parse_input_value($value, $allow_html, $charset);
654}
655
656/**
657 * Parse/validate input value. See get_input_value()
658 * Performs stripslashes() and charset conversion if necessary
659 *
660 * @param  string   Input value
661 * @param  boolean  Allow HTML tags in field value
662 * @param  string   Charset to convert into
663 * @return string   Parsed value
664 */
665function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
666{
667  global $OUTPUT;
668
669  if (empty($value))
670    return $value;
671
672  if (is_array($value)) {
673    foreach ($value as $idx => $val)
674      $value[$idx] = parse_input_value($val, $allow_html, $charset);
675    return $value;
676  }
677
678  // strip single quotes if magic_quotes_sybase is enabled
679  if (ini_get('magic_quotes_sybase'))
680    $value = str_replace("''", "'", $value);
681  // strip slashes if magic_quotes enabled
682  else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
683    $value = stripslashes($value);
684
685  // remove HTML tags if not allowed   
686  if (!$allow_html)
687    $value = strip_tags($value);
688 
689  // convert to internal charset
690  if (is_object($OUTPUT) && $charset)
691    return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
692  else
693    return $value;
694}
695
696/**
697 * Convert array of request parameters (prefixed with _)
698 * to a regular array with non-prefixed keys.
699 *
700 * @param  int   Source to get value from (GPC)
701 * @return array Hash array with all request parameters
702 */
703function request2param($mode = RCUBE_INPUT_GPC)
704{
705  $out = array();
706  $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
707  foreach ($src as $key => $value) {
708    $fname = $key[0] == '_' ? substr($key, 1) : $key;
709    $out[$fname] = get_input_value($key, $mode);
710  }
711 
712  return $out;
713}
714
715/**
716 * Remove all non-ascii and non-word chars
717 * except ., -, _
718 */
719function asciiwords($str, $css_id = false, $replace_with = '')
720{
721  $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
722  return preg_replace("/[^$allowed]/i", $replace_with, $str);
723}
724
725/**
726 * Remove single and double quotes from given string
727 *
728 * @param string Input value
729 * @return string Dequoted string
730 */
731function strip_quotes($str)
732{
733  return str_replace(array("'", '"'), '', $str);
734}
735
736
737/**
738 * Remove new lines characters from given string
739 *
740 * @param string Input value
741 * @return string Stripped string
742 */
743function strip_newlines($str)
744{
745  return preg_replace('/[\r\n]/', '', $str);
746}
747
748
749/**
750 * Create a HTML table based on the given data
751 *
752 * @param  array  Named table attributes
753 * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
754 * @param  array  List of cols to show
755 * @param  string Name of the identifier col
756 * @return string HTML table code
757 */
758function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
759  {
760  global $RCMAIL;
761 
762  $table = new html_table(/*array('cols' => count($a_show_cols))*/);
763   
764  // add table header
765  if (!$attrib['noheader'])
766    foreach ($a_show_cols as $col)
767      $table->add_header($col, Q(rcube_label($col)));
768 
769  $c = 0;
770  if (!is_array($table_data))
771  {
772    $db = $RCMAIL->get_dbh();
773    while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
774    {
775      $zebra_class = $c % 2 ? 'even' : 'odd';
776      $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class));
777
778      // format each col
779      foreach ($a_show_cols as $col)
780        $table->add($col, Q($sql_arr[$col]));
781     
782      $c++;
783    }
784  }
785  else
786  {
787    foreach ($table_data as $row_data)
788    {
789      $zebra_class = $c % 2 ? 'even' : 'odd';
790      if (!empty($row_data['class']))
791        $zebra_class .= ' '.$row_data['class'];
792
793      $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class));
794
795      // format each col
796      foreach ($a_show_cols as $col)
797        $table->add($col, Q($row_data[$col]));
798       
799      $c++;
800    }
801  }
802
803  return $table->show($attrib);
804  }
805
806
807/**
808 * Create an edit field for inclusion on a form
809 *
810 * @param string col field name
811 * @param string value field value
812 * @param array attrib HTML element attributes for field
813 * @param string type HTML element type (default 'text')
814 * @return string HTML field definition
815 */
816function rcmail_get_edit_field($col, $value, $attrib, $type='text')
817  {
818  $fname = '_'.$col;
819  $attrib['name'] = $fname;
820 
821  if ($type=='checkbox')
822    {
823    $attrib['value'] = '1';
824    $input = new html_checkbox($attrib);
825    }
826  else if ($type=='textarea')
827    {
828    $attrib['cols'] = $attrib['size'];
829    $input = new html_textarea($attrib);
830    }
831  else
832    $input = new html_inputfield($attrib);
833
834  // use value from post
835  if (!empty($_POST[$fname]))
836    $value = get_input_value($fname, RCUBE_INPUT_POST,
837            $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
838
839  $out = $input->show($value);
840         
841  return $out;
842  }
843
844
845/**
846 * Replace all css definitions with #container [def]
847 * and remove css-inlined scripting
848 *
849 * @param string CSS source code
850 * @param string Container ID to use as prefix
851 * @return string Modified CSS source
852 */
853function rcmail_mod_css_styles($source, $container_id)
854  {
855  $last_pos = 0;
856  $replacements = new rcube_string_replacer;
857
858  // ignore the whole block if evil styles are detected
859  $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entity_decode($source));
860  if (preg_match('/expression|behavior|url\(|import/', $stripped))
861    return '/* evil! */';
862
863  // remove css comments (sometimes used for some ugly hacks)
864  $source = preg_replace('!/\*(.+)\*/!Ums', '', $source);
865
866  // cut out all contents between { and }
867  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
868  {
869    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
870    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
871    $last_pos = $pos+2;
872  }
873
874  // remove html comments and add #container to each tag selector.
875  // also replace body definition because we also stripped off the <body> tag
876  $styles = preg_replace(
877    array(
878      '/(^\s*<!--)|(-->\s*$)/',
879      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
880      '/'.preg_quote($container_id, '/').'\s+body/i',
881    ),
882    array(
883      '',
884      "\\1#$container_id \\2",
885      $container_id,
886    ),
887    $source);
888
889  // put block contents back in
890  $styles = $replacements->resolve($styles);
891
892  return $styles;
893  }
894
895
896/**
897 * Decode escaped entities used by known XSS exploits.
898 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
899 *
900 * @param string CSS content to decode
901 * @return string Decoded string
902 */
903function rcmail_xss_entity_decode($content)
904{
905  $out = html_entity_decode(html_entity_decode($content));
906  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
907  $out = preg_replace('#/\*.*\*/#Um', '', $out);
908  return $out;
909}
910
911
912/**
913 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
914 *
915 * @param array matches result from preg_replace_callback
916 * @return string decoded entity
917 */
918function rcmail_xss_entity_decode_callback($matches)
919{
920  return chr(hexdec($matches[1]));
921}
922
923/**
924 * Compose a valid attribute string for HTML tags
925 *
926 * @param array Named tag attributes
927 * @param array List of allowed attributes
928 * @return string HTML formatted attribute string
929 */
930function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
931  {
932  // allow the following attributes to be added to the <iframe> tag
933  $attrib_str = '';
934  foreach ($allowed_attribs as $a)
935    if (isset($attrib[$a]))
936      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
937
938  return $attrib_str;
939  }
940
941
942/**
943 * Convert a HTML attribute string attributes to an associative array (name => value)
944 *
945 * @param string Input string
946 * @return array Key-value pairs of parsed attributes
947 */
948function parse_attrib_string($str)
949  {
950  $attrib = array();
951  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
952
953  // convert attributes to an associative array (name => value)
954  if ($regs) {
955    foreach ($regs as $attr) {
956      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
957    }
958  }
959
960  return $attrib;
961  }
962
963
964/**
965 * Convert the given date to a human readable form
966 * This uses the date formatting properties from config
967 *
968 * @param mixed Date representation (string or timestamp)
969 * @param string Date format to use
970 * @return string Formatted date string
971 */
972function format_date($date, $format=NULL)
973  {
974  global $CONFIG;
975 
976  $ts = NULL;
977
978  if (is_numeric($date))
979    $ts = $date;
980  else if (!empty($date))
981    {
982    // support non-standard "GMTXXXX" literal
983    $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
984    // if date parsing fails, we have a date in non-rfc format.
985    // remove token from the end and try again
986    while ((($ts = @strtotime($date))===false) || ($ts < 0))
987      {
988        $d = explode(' ', $date);
989        array_pop($d);
990        if (!$d) break;
991        $date = implode(' ', $d);
992      }
993    }
994
995  if (empty($ts))
996    return '';
997   
998  // get user's timezone
999  if ($CONFIG['timezone'] === 'auto')
1000    $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
1001  else {
1002    $tz = $CONFIG['timezone'];
1003    if ($CONFIG['dst_active'])
1004      $tz++;
1005  }
1006
1007  // convert time to user's timezone
1008  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
1009 
1010  // get current timestamp in user's timezone
1011  $now = time();  // local time
1012  $now -= (int)date('Z'); // make GMT time
1013  $now += ($tz * 3600); // user's time
1014  $now_date = getdate($now);
1015
1016  $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1017  $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
1018
1019  // define date format depending on current time
1020  if (!$format) {
1021    if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now)
1022      return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
1023    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1024      $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
1025    else
1026      $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
1027    }
1028
1029  // strftime() format
1030  if (preg_match('/%[a-z]+/i', $format))
1031    return strftime($format, $timestamp);
1032
1033  // parse format string manually in order to provide localized weekday and month names
1034  // an alternative would be to convert the date() format string to fit with strftime()
1035  $out = '';
1036  for($i=0; $i<strlen($format); $i++)
1037    {
1038    if ($format{$i}=='\\')  // skip escape chars
1039      continue;
1040   
1041    // write char "as-is"
1042    if ($format{$i}==' ' || $format{$i-1}=='\\')
1043      $out .= $format{$i};
1044    // weekday (short)
1045    else if ($format{$i}=='D')
1046      $out .= rcube_label(strtolower(date('D', $timestamp)));
1047    // weekday long
1048    else if ($format{$i}=='l')
1049      $out .= rcube_label(strtolower(date('l', $timestamp)));
1050    // month name (short)
1051    else if ($format{$i}=='M')
1052      $out .= rcube_label(strtolower(date('M', $timestamp)));
1053    // month name (long)
1054    else if ($format{$i}=='F')
1055      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1056    else if ($format{$i}=='x')
1057      $out .= strftime('%x %X', $timestamp);
1058    else
1059      $out .= date($format{$i}, $timestamp);
1060    }
1061 
1062  return $out;
1063  }
1064
1065
1066/**
1067 * Compose a valid representation of name and e-mail address
1068 *
1069 * @param string E-mail address
1070 * @param string Person name
1071 * @return string Formatted string
1072 */
1073function format_email_recipient($email, $name='')
1074  {
1075  if ($name && $name != $email)
1076    {
1077    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1078    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1079    }
1080  else
1081    return trim($email);
1082  }
1083
1084
1085
1086/****** debugging functions ********/
1087
1088
1089/**
1090 * Print or write debug messages
1091 *
1092 * @param mixed Debug message or data
1093 * @return void
1094 */
1095function console()
1096  {
1097  $args = func_get_args();
1098
1099  if (class_exists('rcmail', false)) {
1100    $rcmail = rcmail::get_instance();
1101    if (is_object($rcmail->plugins))
1102      $rcmail->plugins->exec_hook('console', $args);
1103  }
1104
1105  $msg = array();
1106  foreach ($args as $arg)
1107    $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1108
1109  if (!($GLOBALS['CONFIG']['debug_level'] & 4))
1110    write_log('console', join(";\n", $msg));
1111  else if ($GLOBALS['OUTPUT']->ajax_call)
1112    print "/*\n " . join(";\n", $msg) . " \n*/\n";
1113  else
1114    {
1115    print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
1116    print join(";<br/>\n", $msg);
1117    print "</pre></div>\n";
1118    }
1119  }
1120
1121
1122/**
1123 * Append a line to a logfile in the logs directory.
1124 * Date will be added automatically to the line.
1125 *
1126 * @param $name name of log file
1127 * @param line Line to append
1128 * @return void
1129 */
1130function write_log($name, $line)
1131  {
1132  global $CONFIG, $RCMAIL;
1133
1134  if (!is_string($line))
1135    $line = var_export($line, true);
1136 
1137  if (empty($CONFIG['log_date_format']))
1138    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1139 
1140  $date = date($CONFIG['log_date_format']);
1141 
1142  // trigger logging hook
1143  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1144    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1145    $name = $log['name'];
1146    $line = $log['line'];
1147    $date = $log['date'];
1148    if ($log['abort'])
1149      return true;
1150  }
1151 
1152  if ($CONFIG['log_driver'] == 'syslog') {
1153    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1154    syslog($prio, $line);
1155    return true;
1156  }
1157  else {
1158    $line = sprintf("[%s]: %s\n", $date, $line);
1159
1160    // log_driver == 'file' is assumed here
1161    if (empty($CONFIG['log_dir']))
1162      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1163
1164    // try to open specific log file for writing
1165    $logfile = $CONFIG['log_dir'].'/'.$name;
1166    if ($fp = @fopen($logfile, 'a')) {
1167      fwrite($fp, $line);
1168      fflush($fp);
1169      fclose($fp);
1170      return true;
1171    }
1172    else
1173      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1174  }
1175  return false;
1176}
1177
1178
1179/**
1180 * Write login data (name, ID, IP address) to the 'userlogins' log file.
1181 *
1182 * @return void
1183 */
1184function rcmail_log_login()
1185{
1186  global $RCMAIL;
1187
1188  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1189    return;
1190
1191  $address = $_SERVER['REMOTE_ADDR'];
1192  // append the NGINX X-Real-IP header, if set
1193  if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1194    $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1195  }
1196  // append the X-Forwarded-For header, if set
1197  if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1198    $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1199  }
1200
1201  if (!empty($remote_ip))
1202    $address .= '(' . implode(',', $remote_ip) . ')';
1203
1204  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1205    $RCMAIL->user->get_username(), $RCMAIL->user->ID, $address));
1206}
1207
1208
1209/**
1210 * @access private
1211 * @return mixed
1212 */
1213function rcube_timer()
1214{
1215  return microtime(true);
1216}
1217 
1218
1219/**
1220 * @access private
1221 * @return void
1222 */
1223function rcube_print_time($timer, $label='Timer', $dest='console')
1224{
1225  static $print_count = 0;
1226 
1227  $print_count++;
1228  $now = rcube_timer();
1229  $diff = $now-$timer;
1230 
1231  if (empty($label))
1232    $label = 'Timer '.$print_count;
1233 
1234  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1235}
1236
1237
1238/**
1239 * Return the mailboxlist in HTML
1240 *
1241 * @param array Named parameters
1242 * @return string HTML code for the gui object
1243 */
1244function rcmail_mailbox_list($attrib)
1245{
1246  global $RCMAIL;
1247  static $a_mailboxes;
1248 
1249  $attrib += array('maxlength' => 100, 'realnames' => false);
1250
1251  // add some labels to client
1252  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1253 
1254  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1255  unset($attrib['type']);
1256
1257  if ($type=='ul' && !$attrib['id'])
1258    $attrib['id'] = 'rcmboxlist';
1259
1260  // get mailbox list
1261  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1262 
1263  // build the folders tree
1264  if (empty($a_mailboxes)) {
1265    // get mailbox list
1266    $a_folders = $RCMAIL->imap->list_mailboxes();
1267    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1268    $a_mailboxes = array();
1269
1270    foreach ($a_folders as $folder)
1271      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1272  }
1273 
1274  // allow plugins to alter the folder tree or to localize folder names
1275  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1276
1277  if ($type=='select') {
1278    $select = new html_select($attrib);
1279   
1280    // add no-selection option
1281    if ($attrib['noselection'])
1282      $select->add(rcube_label($attrib['noselection']), '0');
1283   
1284    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1285    $out = $select->show();
1286  }
1287  else {
1288    $js_mailboxlist = array();
1289    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1290   
1291    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1292    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1293    $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1294  }
1295
1296  return $out;
1297}
1298
1299
1300/**
1301 * Return the mailboxlist as html_select object
1302 *
1303 * @param array Named parameters
1304 * @return html_select HTML drop-down object
1305 */
1306function rcmail_mailbox_select($p = array())
1307{
1308  global $RCMAIL;
1309 
1310  $p += array('maxlength' => 100, 'realnames' => false);
1311  $a_mailboxes = array();
1312 
1313  foreach ($RCMAIL->imap->list_mailboxes() as $folder)
1314    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1315      rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1316
1317  $select = new html_select($p);
1318 
1319  if ($p['noselection'])
1320    $select->add($p['noselection'], '');
1321   
1322  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1323 
1324  return $select;
1325}
1326
1327
1328/**
1329 * Create a hierarchical array of the mailbox list
1330 * @access private
1331 * @return void
1332 */
1333function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1334{
1335  $pos = strpos($folder, $delm);
1336  if ($pos !== false) {
1337    $subFolders = substr($folder, $pos+1);
1338    $currentFolder = substr($folder, 0, $pos);
1339    $virtual = !isset($arrFolders[$currentFolder]);
1340  }
1341  else {
1342    $subFolders = false;
1343    $currentFolder = $folder;
1344    $virtual = false;
1345  }
1346
1347  $path .= $currentFolder;
1348
1349  if (!isset($arrFolders[$currentFolder])) {
1350    $arrFolders[$currentFolder] = array(
1351      'id' => $path,
1352      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1353      'virtual' => $virtual,
1354      'folders' => array());
1355  }
1356  else
1357    $arrFolders[$currentFolder]['virtual'] = $virtual;
1358
1359  if (!empty($subFolders))
1360    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1361}
1362 
1363
1364/**
1365 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1366 * @access private
1367 * @return string
1368 */
1369function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1370{
1371  global $RCMAIL, $CONFIG;
1372 
1373  $maxlength = intval($attrib['maxlength']);
1374  $realnames = (bool)$attrib['realnames'];
1375  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1376
1377  $idx = 0;
1378  $out = '';
1379  foreach ($arrFolders as $key => $folder) {
1380    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1381    $title = null;
1382
1383    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1384      $foldername = rcube_label($folder_class);
1385    }
1386    else {
1387      $foldername = $folder['name'];
1388
1389      // shorten the folder name to a given length
1390      if ($maxlength && $maxlength > 1) {
1391        $fname = abbreviate_string($foldername, $maxlength);
1392        if ($fname != $foldername)
1393          $title = $foldername;
1394        $foldername = $fname;
1395      }
1396    }
1397
1398    // make folder name safe for ids and class names
1399    $folder_id = asciiwords($folder['id'], true, '_');
1400    $classes = array('mailbox');
1401
1402    // set special class for Sent, Drafts, Trash and Junk
1403    if ($folder['id']==$CONFIG['sent_mbox'])
1404      $classes[] = 'sent';
1405    else if ($folder['id']==$CONFIG['drafts_mbox'])
1406      $classes[] = 'drafts';
1407    else if ($folder['id']==$CONFIG['trash_mbox'])
1408      $classes[] = 'trash';
1409    else if ($folder['id']==$CONFIG['junk_mbox'])
1410      $classes[] = 'junk';
1411    else if ($folder['id']=='INBOX')
1412      $classes[] = 'inbox';
1413    else
1414      $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1415     
1416    $classes[] = $zebra_class;
1417   
1418    if ($folder['id'] == $mbox_name)
1419      $classes[] = 'selected';
1420
1421    $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1422    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1423   
1424    if ($folder['virtual'])
1425      $classes[] = 'virtual';
1426    else if ($unread)
1427      $classes[] = 'unread';
1428
1429    $js_name = JQ($folder['id']);
1430    $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1431    $link_attrib = $folder['virtual'] ? array() : array(
1432      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1433      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1434      'title' => $title,
1435    );
1436
1437    $out .= html::tag('li', array(
1438        'id' => "rcmli".$folder_id,
1439        'class' => join(' ', $classes),
1440        'noclose' => true),
1441      html::a($link_attrib, $html_name) .
1442      (!empty($folder['folders']) ? html::div(array(
1443        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1444        'style' => "position:absolute",
1445        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1446      ), '&nbsp;') : ''));
1447   
1448    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1449   
1450    if (!empty($folder['folders'])) {
1451      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1452        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1453    }
1454
1455    $out .= "</li>\n";
1456    $idx++;
1457  }
1458
1459  return $out;
1460}
1461
1462
1463/**
1464 * Return html for a flat list <select> for the mailbox tree
1465 * @access private
1466 * @return string
1467 */
1468function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1469  {
1470  $idx = 0;
1471  $out = '';
1472  foreach ($arrFolders as $key=>$folder)
1473    {
1474    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1475      $foldername = rcube_label($folder_class);
1476    else
1477      {
1478      $foldername = $folder['name'];
1479     
1480      // shorten the folder name to a given length
1481      if ($maxlength && $maxlength>1)
1482        $foldername = abbreviate_string($foldername, $maxlength);
1483      }
1484
1485    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1486
1487    if (!empty($folder['folders']))
1488      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1489
1490    $idx++;
1491    }
1492
1493  return $out;
1494  }
1495
1496
1497/**
1498 * Return internal name for the given folder if it matches the configured special folders
1499 * @access private
1500 * @return string
1501 */
1502function rcmail_folder_classname($folder_id)
1503{
1504  global $CONFIG;
1505
1506  if ($folder_id == 'INBOX')
1507    return 'inbox';
1508
1509  // for these mailboxes we have localized labels and css classes
1510  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1511  {
1512    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1513      return $smbx;
1514  }
1515}
1516
1517
1518/**
1519 * Try to localize the given IMAP folder name.
1520 * UTF-7 decode it in case no localized text was found
1521 *
1522 * @param string Folder name
1523 * @return string Localized folder name in UTF-8 encoding
1524 */
1525function rcmail_localize_foldername($name)
1526{
1527  if ($folder_class = rcmail_folder_classname($name))
1528    return rcube_label($folder_class);
1529  else
1530    return rcube_charset_convert($name, 'UTF7-IMAP');
1531}
1532
1533
1534/**
1535 * Output HTML editor scripts
1536 *
1537 * @param string Editor mode
1538 * @return void
1539 */
1540function rcube_html_editor($mode='')
1541{
1542  global $RCMAIL, $CONFIG;
1543
1544  $hook = $RCMAIL->plugins->exec_hook('hmtl_editor', array('mode' => $mode));
1545
1546  if ($hook['abort'])
1547    return; 
1548
1549  $lang = strtolower($_SESSION['language']);
1550
1551  // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1552  $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1553
1554  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1555    $lang = 'en';
1556
1557  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1558  $RCMAIL->output->include_script('editor.js');
1559  $RCMAIL->output->add_script('rcmail_editor_init("$__skin_path",
1560    "'.JQ($lang).'", '.intval($CONFIG['enable_spellcheck']).', "'.$mode.'");');
1561}
1562
1563
1564/**
1565 * Check if working in SSL mode
1566 *
1567 * @param integer HTTPS port number
1568 * @param boolean Enables 'use_https' option checking
1569 * @return boolean
1570 */
1571function rcube_https_check($port=null, $use_https=true)
1572{
1573  global $RCMAIL;
1574
1575  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1576    return true;
1577  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1578    return true;
1579  if ($port && $_SERVER['SERVER_PORT'] == $port)
1580    return true;
1581  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1582    return true;
1583
1584  return false;
1585}
1586
1587
1588/**
1589 * For backward compatibility.
1590 *
1591 * @global rcmail $RCMAIL
1592 * @param string $var_name Variable name.
1593 * @return void
1594 */
1595function rcube_sess_unset($var_name=null)
1596{
1597  global $RCMAIL;
1598
1599  $RCMAIL->session->remove($var_name);
1600}
1601
1602
1603
1604/**
1605 * Replaces hostname variables
1606 *
1607 * @param string $name Hostname
1608 * @return string
1609 */
1610function rcube_parse_host($name)
1611{
1612  // %n - host
1613  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1614  // %d - domain name without first part, e.g. %d=mail.domain.tld, %m=domain.tld
1615  $d = preg_replace('/^[^\.]+\./', '', $n);
1616  // %h - IMAP host
1617  $h = $_SESSION['imap_host'];
1618  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1619  $z = preg_replace('/^[^\.]+\./', '', $h);
1620
1621  $name = str_replace(array('%n', '%d', '%h', '%z'), array($n, $d, $h, $z), $name);
1622  return $name;
1623}
1624
1625
1626/**
1627 * E-mail address validation
1628 *
1629 * @param string $email Email address
1630 * @param boolean $dns_check True to check dns
1631 * @return boolean
1632 */
1633function check_email($email, $dns_check=true)
1634{
1635  // Check for invalid characters
1636  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1637    return false;
1638
1639  // Check for length limit specified by RFC 5321 (#1486453)
1640  if (strlen($email) > 254)
1641    return false;
1642
1643  $email_array = explode('@', $email);
1644
1645  // Check that there's one @ symbol
1646  if (count($email_array) < 2)
1647    return false;
1648
1649  $domain_part = array_pop($email_array);
1650  $local_part = implode('@', $email_array);
1651
1652  // from PEAR::Validate
1653  $regexp = '&^(?:
1654        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1655        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
1656        $&xi';
1657
1658  if (!preg_match($regexp, $local_part))
1659    return false;
1660
1661  // Check domain part
1662  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))
1663    return true; // IP address
1664  else {
1665    // If not an IP address
1666    $domain_array = explode('.', $domain_part);
1667    if (sizeof($domain_array) < 2)
1668      return false; // Not enough parts to be a valid domain
1669
1670    foreach ($domain_array as $part)
1671      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1672        return false;
1673
1674    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1675      return true;
1676
1677    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1678      $lookup = array();
1679      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1680      foreach ($lookup as $line) {
1681        if (strpos($line, 'MX preference'))
1682          return true;
1683      }
1684      return false;
1685    }
1686
1687    // find MX record(s)
1688    if (getmxrr($domain_part, $mx_records))
1689      return true;
1690
1691    // find any DNS record
1692    if (checkdnsrr($domain_part, 'ANY'))
1693      return true;
1694  }
1695
1696  return false;
1697}
1698
1699
1700/**
1701 * Helper class to turn relative urls into absolute ones
1702 * using a predefined base
1703 */
1704class rcube_base_replacer
1705{
1706  private $base_url;
1707
1708  public function __construct($base)
1709  {
1710    $this->base_url = $base;
1711  }
1712
1713  public function callback($matches)
1714  {
1715    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1716  }
1717}
1718
1719
1720/**
1721 * Throw system error and show error page
1722 *
1723 * @param array Named parameters
1724 *  - code: Error code (required)
1725 *  - type: Error type [php|db|imap|javascript] (required)
1726 *  - message: Error message
1727 *  - file: File where error occured
1728 *  - line: Line where error occured
1729 * @param boolean True to log the error
1730 * @param boolean Terminate script execution
1731 */
1732// may be defined in Installer
1733if (!function_exists('raise_error')) {
1734function raise_error($arg=array(), $log=false, $terminate=false)
1735{
1736    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
1737
1738    // report bug (if not incompatible browser)
1739    if ($log && $arg['type'] && $arg['message'])
1740        log_bug($arg);
1741
1742    // display error page and terminate script
1743    if ($terminate) {
1744        $ERROR_CODE = $arg['code'];
1745        $ERROR_MESSAGE = $arg['message'];
1746        include('program/steps/utils/error.inc');
1747        exit;
1748    }
1749}
1750}
1751
1752
1753/**
1754 * Report error according to configured debug_level
1755 *
1756 * @param array Named parameters
1757 * @return void
1758 * @see raise_error()
1759 */
1760function log_bug($arg_arr)
1761{
1762    global $CONFIG;
1763    $program = strtoupper($arg_arr['type']);
1764
1765    // write error to local log file
1766    if ($CONFIG['debug_level'] & 1) {
1767        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
1768        $log_entry = sprintf("%s Error: %s%s (%s %s)",
1769            $program,
1770            $arg_arr['message'],
1771            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
1772            $_SERVER['REQUEST_METHOD'],
1773            $_SERVER['REQUEST_URI'] . $post_query);
1774
1775        if (!write_log('errors', $log_entry)) {
1776            // send error to PHPs error handler if write_log didn't succeed
1777            trigger_error($arg_arr['message']);
1778        }
1779    }
1780
1781    // resport the bug to the global bug reporting system
1782    if ($CONFIG['debug_level'] & 2) {
1783        // TODO: Send error via HTTP
1784    }
1785
1786    // show error if debug_mode is on
1787    if ($CONFIG['debug_level'] & 4) {
1788        print "<b>$program Error";
1789
1790        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
1791            print " in $arg_arr[file] ($arg_arr[line])";
1792
1793        print ':</b>&nbsp;';
1794        print nl2br($arg_arr['message']);
1795        print '<br />';
1796        flush();
1797    }
1798}
Note: See TracBrowser for help on using the repository browser.