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

Last change on this file since 3525 was 3525, checked in by alec, 3 years ago
  • make sure INBOX is allways displayed as Inbox (per recent discussion)
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 42.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 */
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  foreach ($a_show_cols as $col)
736    $table->add_header($col, Q(rcube_label($col)));
737 
738  $c = 0;
739  if (!is_array($table_data))
740  {
741    $db = $RCMAIL->get_dbh();
742    while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
743    {
744      $zebra_class = $c % 2 ? 'even' : 'odd';
745      $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class));
746
747      // format each col
748      foreach ($a_show_cols as $col)
749        $table->add($col, Q($sql_arr[$col]));
750     
751      $c++;
752    }
753  }
754  else
755  {
756    foreach ($table_data as $row_data)
757    {
758      $zebra_class = $c % 2 ? 'even' : 'odd';
759      if (!empty($row_data['class']))
760        $zebra_class .= ' '.$row_data['class'];
761
762      $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class));
763
764      // format each col
765      foreach ($a_show_cols as $col)
766        $table->add($col, Q($row_data[$col]));
767       
768      $c++;
769    }
770  }
771
772  return $table->show($attrib);
773  }
774
775
776/**
777 * Create an edit field for inclusion on a form
778 *
779 * @param string col field name
780 * @param string value field value
781 * @param array attrib HTML element attributes for field
782 * @param string type HTML element type (default 'text')
783 * @return string HTML field definition
784 */
785function rcmail_get_edit_field($col, $value, $attrib, $type='text')
786  {
787  $fname = '_'.$col;
788  $attrib['name'] = $fname;
789 
790  if ($type=='checkbox')
791    {
792    $attrib['value'] = '1';
793    $input = new html_checkbox($attrib);
794    }
795  else if ($type=='textarea')
796    {
797    $attrib['cols'] = $attrib['size'];
798    $input = new html_textarea($attrib);
799    }
800  else
801    $input = new html_inputfield($attrib);
802
803  // use value from post
804  if (!empty($_POST[$fname]))
805    $value = get_input_value($fname, RCUBE_INPUT_POST,
806            $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
807
808  $out = $input->show($value);
809         
810  return $out;
811  }
812
813
814/**
815 * Replace all css definitions with #container [def]
816 * and remove css-inlined scripting
817 *
818 * @param string CSS source code
819 * @param string Container ID to use as prefix
820 * @return string Modified CSS source
821 */
822function rcmail_mod_css_styles($source, $container_id)
823  {
824  $last_pos = 0;
825  $replacements = new rcube_string_replacer;
826 
827  // ignore the whole block if evil styles are detected
828  $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entity_decode($source));
829  if (preg_match('/expression|behavior|url\(|import/', $stripped))
830    return '/* evil! */';
831
832  // cut out all contents between { and }
833  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
834  {
835    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
836    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
837    $last_pos = $pos+2;
838  }
839 
840  // remove html comments and add #container to each tag selector.
841  // also replace body definition because we also stripped off the <body> tag
842  $styles = preg_replace(
843    array(
844      '/(^\s*<!--)|(-->\s*$)/',
845      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#][a-z0-9\.\-_]*)/im',
846      "/$container_id\s+body/i",
847    ),
848    array(
849      '',
850      "\\1#$container_id \\2",
851      "$container_id div.rcmBody",
852    ),
853    $source);
854 
855  // put block contents back in
856  $styles = $replacements->resolve($styles);
857
858  return $styles;
859  }
860
861
862/**
863 * Decode escaped entities used by known XSS exploits.
864 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
865 *
866 * @param string CSS content to decode
867 * @return string Decoded string
868 */
869function rcmail_xss_entity_decode($content)
870{
871  $out = html_entity_decode(html_entity_decode($content));
872  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
873  $out = preg_replace('#/\*.*\*/#Um', '', $out);
874  return $out;
875}
876
877
878/**
879 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
880 *
881 * @param array matches result from preg_replace_callback
882 * @return string decoded entity
883 */
884function rcmail_xss_entity_decode_callback($matches)
885{
886  return chr(hexdec($matches[1]));
887}
888
889/**
890 * Compose a valid attribute string for HTML tags
891 *
892 * @param array Named tag attributes
893 * @param array List of allowed attributes
894 * @return string HTML formatted attribute string
895 */
896function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
897  {
898  // allow the following attributes to be added to the <iframe> tag
899  $attrib_str = '';
900  foreach ($allowed_attribs as $a)
901    if (isset($attrib[$a]))
902      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
903
904  return $attrib_str;
905  }
906
907
908/**
909 * Convert a HTML attribute string attributes to an associative array (name => value)
910 *
911 * @param string Input string
912 * @return array Key-value pairs of parsed attributes
913 */
914function parse_attrib_string($str)
915  {
916  $attrib = array();
917  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
918
919  // convert attributes to an associative array (name => value)
920  if ($regs) {
921    foreach ($regs as $attr) {
922      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
923    }
924  }
925
926  return $attrib;
927  }
928
929
930/**
931 * Convert the given date to a human readable form
932 * This uses the date formatting properties from config
933 *
934 * @param mixed Date representation (string or timestamp)
935 * @param string Date format to use
936 * @return string Formatted date string
937 */
938function format_date($date, $format=NULL)
939  {
940  global $CONFIG;
941 
942  $ts = NULL;
943
944  if (is_numeric($date))
945    $ts = $date;
946  else if (!empty($date))
947    {
948    // support non-standard "GMTXXXX" literal
949    $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
950    // if date parsing fails, we have a date in non-rfc format.
951    // remove token from the end and try again
952    while ((($ts = @strtotime($date))===false) || ($ts < 0))
953      {
954        $d = explode(' ', $date);
955        array_pop($d);
956        if (!$d) break;
957        $date = implode(' ', $d);
958      }
959    }
960
961  if (empty($ts))
962    return '';
963   
964  // get user's timezone
965  if ($CONFIG['timezone'] === 'auto')
966    $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
967  else {
968    $tz = $CONFIG['timezone'];
969    if ($CONFIG['dst_active'])
970      $tz++;
971  }
972
973  // convert time to user's timezone
974  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
975 
976  // get current timestamp in user's timezone
977  $now = time();  // local time
978  $now -= (int)date('Z'); // make GMT time
979  $now += ($tz * 3600); // user's time
980  $now_date = getdate($now);
981
982  $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
983  $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
984
985  // define date format depending on current time
986  if (!$format) {
987    if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now)
988      return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
989    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
990      $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
991    else
992      $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
993    }
994
995  // strftime() format
996  if (preg_match('/%[a-z]+/i', $format))
997    return strftime($format, $timestamp);
998
999  // parse format string manually in order to provide localized weekday and month names
1000  // an alternative would be to convert the date() format string to fit with strftime()
1001  $out = '';
1002  for($i=0; $i<strlen($format); $i++)
1003    {
1004    if ($format{$i}=='\\')  // skip escape chars
1005      continue;
1006   
1007    // write char "as-is"
1008    if ($format{$i}==' ' || $format{$i-1}=='\\')
1009      $out .= $format{$i};
1010    // weekday (short)
1011    else if ($format{$i}=='D')
1012      $out .= rcube_label(strtolower(date('D', $timestamp)));
1013    // weekday long
1014    else if ($format{$i}=='l')
1015      $out .= rcube_label(strtolower(date('l', $timestamp)));
1016    // month name (short)
1017    else if ($format{$i}=='M')
1018      $out .= rcube_label(strtolower(date('M', $timestamp)));
1019    // month name (long)
1020    else if ($format{$i}=='F')
1021      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1022    else if ($format{$i}=='x')
1023      $out .= strftime('%x %X', $timestamp);
1024    else
1025      $out .= date($format{$i}, $timestamp);
1026    }
1027 
1028  return $out;
1029  }
1030
1031
1032/**
1033 * Compose a valid representation of name and e-mail address
1034 *
1035 * @param string E-mail address
1036 * @param string Person name
1037 * @return string Formatted string
1038 */
1039function format_email_recipient($email, $name='')
1040  {
1041  if ($name && $name != $email)
1042    {
1043    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1044    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1045    }
1046  else
1047    return trim($email);
1048  }
1049
1050
1051
1052/****** debugging functions ********/
1053
1054
1055/**
1056 * Print or write debug messages
1057 *
1058 * @param mixed Debug message or data
1059 */
1060function console()
1061  {
1062  $args = func_get_args();
1063
1064  if (class_exists('rcmail', false)) {
1065    $rcmail = rcmail::get_instance();
1066    if (is_object($rcmail->plugins))
1067      $rcmail->plugins->exec_hook('console', $args);
1068  }
1069
1070  $msg = array();
1071  foreach ($args as $arg)
1072    $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1073
1074  if (!($GLOBALS['CONFIG']['debug_level'] & 4))
1075    write_log('console', join(";\n", $msg));
1076  else if ($GLOBALS['OUTPUT']->ajax_call)
1077    print "/*\n " . join(";\n", $msg) . " \n*/\n";
1078  else
1079    {
1080    print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
1081    print join(";<br/>\n", $msg);
1082    print "</pre></div>\n";
1083    }
1084  }
1085
1086
1087/**
1088 * Append a line to a logfile in the logs directory.
1089 * Date will be added automatically to the line.
1090 *
1091 * @param $name name of log file
1092 * @param line Line to append
1093 */
1094function write_log($name, $line)
1095  {
1096  global $CONFIG, $RCMAIL;
1097
1098  if (!is_string($line))
1099    $line = var_export($line, true);
1100 
1101  if (empty($CONFIG['log_date_format']))
1102    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1103 
1104  $date = date($CONFIG['log_date_format']);
1105 
1106  // trigger logging hook
1107  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1108    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1109    $name = $log['name'];
1110    $line = $log['line'];
1111    $date = $log['date'];
1112    if ($log['abort'])
1113      return true;
1114  }
1115 
1116  $log_entry = sprintf("[%s]: %s\n", $date, $line);
1117
1118  if ($CONFIG['log_driver'] == 'syslog') {
1119    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1120    syslog($prio, $log_entry);
1121    return true;
1122  }
1123  else {
1124    // log_driver == 'file' is assumed here
1125    if (empty($CONFIG['log_dir']))
1126      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1127
1128    // try to open specific log file for writing
1129    $logfile = $CONFIG['log_dir'].'/'.$name;
1130    if ($fp = @fopen($logfile, 'a')) {
1131      fwrite($fp, $log_entry);
1132      fflush($fp);
1133      fclose($fp);
1134      return true;
1135    }
1136    else
1137      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1138  }
1139  return false;
1140}
1141
1142
1143/**
1144 * Write login data (name, ID, IP address) to the 'userlogins' log file.
1145 */
1146function rcmail_log_login()
1147{
1148  global $RCMAIL;
1149
1150  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1151    return;
1152
1153  $address = $_SERVER['REMOTE_ADDR'];
1154  // append the NGINX X-Real-IP header, if set
1155  if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1156    $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1157  }
1158  // append the X-Forwarded-For header, if set
1159  if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1160    $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1161  }
1162
1163  if (!empty($remote_ip))
1164    $address .= '(' . implode(',', $remote_ip) . ')';
1165
1166  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1167    $RCMAIL->user->get_username(), $RCMAIL->user->ID, $address));
1168}
1169
1170
1171/**
1172 * @access private
1173 */
1174function rcube_timer()
1175{
1176  return microtime(true);
1177}
1178 
1179
1180/**
1181 * @access private
1182 */
1183function rcube_print_time($timer, $label='Timer', $dest='console')
1184{
1185  static $print_count = 0;
1186 
1187  $print_count++;
1188  $now = rcube_timer();
1189  $diff = $now-$timer;
1190 
1191  if (empty($label))
1192    $label = 'Timer '.$print_count;
1193 
1194  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1195}
1196
1197
1198/**
1199 * Return the mailboxlist in HTML
1200 *
1201 * @param array Named parameters
1202 * @return string HTML code for the gui object
1203 */
1204function rcmail_mailbox_list($attrib)
1205{
1206  global $RCMAIL;
1207  static $a_mailboxes;
1208 
1209  $attrib += array('maxlength' => 100, 'realnames' => false);
1210
1211  // add some labels to client
1212  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1213 
1214  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1215  unset($attrib['type']);
1216
1217  if ($type=='ul' && !$attrib['id'])
1218    $attrib['id'] = 'rcmboxlist';
1219
1220  // get mailbox list
1221  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1222 
1223  // build the folders tree
1224  if (empty($a_mailboxes)) {
1225    // get mailbox list
1226    $a_folders = $RCMAIL->imap->list_mailboxes();
1227    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1228    $a_mailboxes = array();
1229
1230    foreach ($a_folders as $folder)
1231      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1232  }
1233 
1234  // allow plugins to alter the folder tree or to localize folder names
1235  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1236
1237  if ($type=='select') {
1238    $select = new html_select($attrib);
1239   
1240    // add no-selection option
1241    if ($attrib['noselection'])
1242      $select->add(rcube_label($attrib['noselection']), '0');
1243   
1244    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1245    $out = $select->show();
1246  }
1247  else {
1248    $js_mailboxlist = array();
1249    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1250   
1251    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1252    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1253    $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1254  }
1255
1256  return $out;
1257}
1258
1259
1260/**
1261 * Return the mailboxlist as html_select object
1262 *
1263 * @param array Named parameters
1264 * @return object html_select HTML drop-down object
1265 */
1266function rcmail_mailbox_select($p = array())
1267{
1268  global $RCMAIL;
1269 
1270  $p += array('maxlength' => 100, 'realnames' => false);
1271  $a_mailboxes = array();
1272 
1273  foreach ($RCMAIL->imap->list_mailboxes() as $folder)
1274    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1275      rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1276
1277  $select = new html_select($p);
1278 
1279  if ($p['noselection'])
1280    $select->add($p['noselection'], '');
1281   
1282  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1283 
1284  return $select;
1285}
1286
1287
1288/**
1289 * Create a hierarchical array of the mailbox list
1290 * @access private
1291 */
1292function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1293{
1294  $pos = strpos($folder, $delm);
1295  if ($pos !== false) {
1296    $subFolders = substr($folder, $pos+1);
1297    $currentFolder = substr($folder, 0, $pos);
1298    $virtual = !isset($arrFolders[$currentFolder]);
1299  }
1300  else {
1301    $subFolders = false;
1302    $currentFolder = $folder;
1303    $virtual = false;
1304  }
1305
1306  $path .= $currentFolder;
1307
1308  if (!isset($arrFolders[$currentFolder])) {
1309    $arrFolders[$currentFolder] = array(
1310      'id' => $path,
1311      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1312      'virtual' => $virtual,
1313      'folders' => array());
1314  }
1315  else
1316    $arrFolders[$currentFolder]['virtual'] = $virtual;
1317
1318  if (!empty($subFolders))
1319    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1320}
1321 
1322
1323/**
1324 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1325 * @access private
1326 */
1327function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1328{
1329  global $RCMAIL, $CONFIG;
1330 
1331  $maxlength = intval($attrib['maxlength']);
1332  $realnames = (bool)$attrib['realnames'];
1333  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1334
1335  $idx = 0;
1336  $out = '';
1337  foreach ($arrFolders as $key => $folder) {
1338    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1339    $title = null;
1340
1341    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1342      $foldername = rcube_label($folder_class);
1343    }
1344    else {
1345      $foldername = $folder['name'];
1346
1347      // shorten the folder name to a given length
1348      if ($maxlength && $maxlength > 1) {
1349        $fname = abbreviate_string($foldername, $maxlength);
1350        if ($fname != $foldername)
1351          $title = $foldername;
1352        $foldername = $fname;
1353      }
1354    }
1355
1356    // make folder name safe for ids and class names
1357    $folder_id = asciiwords($folder['id'], true, '_');
1358    $classes = array('mailbox');
1359
1360    // set special class for Sent, Drafts, Trash and Junk
1361    if ($folder['id']==$CONFIG['sent_mbox'])
1362      $classes[] = 'sent';
1363    else if ($folder['id']==$CONFIG['drafts_mbox'])
1364      $classes[] = 'drafts';
1365    else if ($folder['id']==$CONFIG['trash_mbox'])
1366      $classes[] = 'trash';
1367    else if ($folder['id']==$CONFIG['junk_mbox'])
1368      $classes[] = 'junk';
1369    else if ($folder['id']=='INBOX')
1370      $classes[] = 'inbox';
1371    else
1372      $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1373     
1374    $classes[] = $zebra_class;
1375   
1376    if ($folder['id'] == $mbox_name)
1377      $classes[] = 'selected';
1378
1379    $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1380    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1381   
1382    if ($folder['virtual'])
1383      $classes[] = 'virtual';
1384    else if ($unread)
1385      $classes[] = 'unread';
1386
1387    $js_name = JQ($folder['id']);
1388    $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1389    $link_attrib = $folder['virtual'] ? array() : array(
1390      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1391      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1392      'title' => $title,
1393    );
1394
1395    $out .= html::tag('li', array(
1396        'id' => "rcmli".$folder_id,
1397        'class' => join(' ', $classes),
1398        'noclose' => true),
1399      html::a($link_attrib, $html_name) .
1400      (!empty($folder['folders']) ? html::div(array(
1401        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1402        'style' => "position:absolute",
1403        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1404      ), '&nbsp;') : ''));
1405   
1406    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1407   
1408    if (!empty($folder['folders'])) {
1409      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1410        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1411    }
1412
1413    $out .= "</li>\n";
1414    $idx++;
1415  }
1416
1417  return $out;
1418}
1419
1420
1421/**
1422 * Return html for a flat list <select> for the mailbox tree
1423 * @access private
1424 */
1425function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1426  {
1427  $idx = 0;
1428  $out = '';
1429  foreach ($arrFolders as $key=>$folder)
1430    {
1431    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1432      $foldername = rcube_label($folder_class);
1433    else
1434      {
1435      $foldername = $folder['name'];
1436     
1437      // shorten the folder name to a given length
1438      if ($maxlength && $maxlength>1)
1439        $foldername = abbreviate_string($foldername, $maxlength);
1440      }
1441
1442    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1443
1444    if (!empty($folder['folders']))
1445      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1446
1447    $idx++;
1448    }
1449
1450  return $out;
1451  }
1452
1453
1454/**
1455 * Return internal name for the given folder if it matches the configured special folders
1456 * @access private
1457 */
1458function rcmail_folder_classname($folder_id)
1459{
1460  global $CONFIG;
1461
1462  if ($folder_id == 'INBOX')
1463    return 'inbox';
1464
1465  // for these mailboxes we have localized labels and css classes
1466  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1467  {
1468    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1469      return $smbx;
1470  }
1471}
1472
1473
1474/**
1475 * Try to localize the given IMAP folder name.
1476 * UTF-7 decode it in case no localized text was found
1477 *
1478 * @param string Folder name
1479 * @return string Localized folder name in UTF-8 encoding
1480 */
1481function rcmail_localize_foldername($name)
1482{
1483  if ($folder_class = rcmail_folder_classname($name))
1484    return rcube_label($folder_class);
1485  else
1486    return rcube_charset_convert($name, 'UTF7-IMAP');
1487}
1488
1489
1490/**
1491 * Output HTML editor scripts
1492 *
1493 * @param string Editor mode
1494 */
1495function rcube_html_editor($mode='')
1496{
1497  global $RCMAIL, $CONFIG;
1498
1499  $hook = $RCMAIL->plugins->exec_hook('hmtl_editor', array('mode' => $mode));
1500
1501  if ($hook['abort'])
1502    return; 
1503
1504  $lang = strtolower(substr($_SESSION['language'], 0, 2));
1505  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1506    $lang = 'en';
1507
1508  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1509  $RCMAIL->output->include_script('editor.js');
1510  $RCMAIL->output->add_script('rcmail_editor_init("$__skin_path",
1511    "'.JQ($lang).'", '.intval($CONFIG['enable_spellcheck']).', "'.$mode.'");');
1512}
1513
1514
1515/**
1516 * Check if working in SSL mode
1517 *
1518 * @param integer HTTPS port number
1519 * @param boolean Enables 'use_https' option checking
1520 */
1521function rcube_https_check($port=null, $use_https=true)
1522{
1523  global $RCMAIL;
1524 
1525  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1526    return true;
1527  if ($port && $_SERVER['SERVER_PORT'] == $port)
1528    return true;
1529  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1530    return true;
1531
1532  return false;
1533}
1534
1535// for backward compatibility
1536function rcube_sess_unset($var_name=null)
1537{
1538  global $RCMAIL;
1539
1540  $RCMAIL->session->remove($var_name);
1541}
1542
1543
1544/**
1545 * E-mail address validation
1546 */
1547function check_email($email, $dns_check=true)
1548{
1549  // Check for invalid characters
1550  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1551    return false;
1552
1553  // Check for length limit specified by RFC 5321 (#1486453)
1554  if (strlen($email) > 254)
1555    return false;
1556
1557  $email_array = explode('@', $email);
1558
1559  // Check that there's one @ symbol
1560  if (count($email_array) < 2)
1561    return false;
1562
1563  $domain_part = array_pop($email_array);
1564  $local_part = implode('@', $email_array);
1565
1566  // from PEAR::Validate
1567  $regexp = '&^(?:
1568        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1569        ([-\w!\#\$%\&\'*+~/^`|{}]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}]+)*))    #2 OR dot-atom
1570        $&xi';
1571
1572  if (!preg_match($regexp, $local_part))
1573    return false;
1574
1575  // Check domain part
1576  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))
1577    return true; // IP address
1578  else {
1579    // If not an IP address
1580    $domain_array = explode('.', $domain_part);
1581    if (sizeof($domain_array) < 2)
1582      return false; // Not enough parts to be a valid domain
1583
1584    foreach ($domain_array as $part)
1585      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1586        return false;
1587
1588    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1589      return true;
1590
1591    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<'))
1592      return true;
1593
1594    // find MX record(s)
1595    if (getmxrr($domain_part, $mx_records))
1596      return true;
1597
1598    // find any DNS record
1599    if (checkdnsrr($domain_part, 'ANY'))
1600      return true;
1601  }
1602
1603  return false;
1604}
1605
1606
1607/**
1608 * Helper class to turn relative urls into absolute ones
1609 * using a predefined base
1610 */
1611class rcube_base_replacer
1612{
1613  private $base_url;
1614 
1615  public function __construct($base)
1616  {
1617    $this->base_url = $base;
1618  }
1619 
1620  public function callback($matches)
1621  {
1622    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1623  }
1624}
1625
1626?>
Note: See TracBrowser for help on using the repository browser.