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

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