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

Last change on this file since 4705 was 4705, checked in by netbit, 2 years ago
  • Added the %s variable in 'default_host' and 'smtp_server' option (%s variable is the domain name after the '@' from e-mail address provided at login screen). The %s just returns a value if the provided e-mail is valid to avoid unnecessary lookups and reduce the possibility of connections to undesirable hosts.
  • Small fix to the code comment of rcube_parse_host()
  • Property svn:eol-style set to native
  • Property svn:keywords set to Id
File size: 55.2 KB
Line 
1<?php
2
3/*
4 +-----------------------------------------------------------------------+
5 | program/include/main.inc                                              |
6 |                                                                       |
7 | This file is part of the Roundcube Webmail client                     |
8 | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
9 | Licensed under the GNU GPL                                            |
10 |                                                                       |
11 | PURPOSE:                                                              |
12 |   Provide basic functions for the webmail package                     |
13 |                                                                       |
14 +-----------------------------------------------------------------------+
15 | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16 +-----------------------------------------------------------------------+
17
18 $Id$
19
20*/
21
22/**
23 * Roundcube Webmail common functions
24 *
25 * @package Core
26 * @author Thomas Bruederli <roundcube@gmail.com>
27 */
28
29require_once 'utf7.inc';
30require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
31
32// define constannts for input reading
33define('RCUBE_INPUT_GET', 0x0101);
34define('RCUBE_INPUT_POST', 0x0102);
35define('RCUBE_INPUT_GPC', 0x0103);
36
37
38
39/**
40 * Return correct name for a specific database table
41 *
42 * @param string Table name
43 * @return string Translated table name
44 */
45function get_table_name($table)
46  {
47  global $CONFIG;
48
49  // return table name if configured
50  $config_key = 'db_table_'.$table;
51
52  if (strlen($CONFIG[$config_key]))
53    return $CONFIG[$config_key];
54
55  return $table;
56  }
57
58
59/**
60 * Return correct name for a specific database sequence
61 * (used for Postgres only)
62 *
63 * @param string Secuence name
64 * @return string Translated sequence name
65 */
66function get_sequence_name($sequence)
67  {
68  // return sequence name if configured
69  $config_key = 'db_sequence_'.$sequence;
70  $opt = rcmail::get_instance()->config->get($config_key);
71
72  if (!empty($opt))
73    return $opt;
74   
75  return $sequence;
76  }
77
78
79/**
80 * Get localized text in the desired language
81 * It's a global wrapper for rcmail::gettext()
82 *
83 * @param mixed Named parameters array or label name
84 * @param string Domain to search in (e.g. plugin name)
85 * @return string Localized text
86 * @see rcmail::gettext()
87 */
88function rcube_label($p, $domain=null)
89{
90  return rcmail::get_instance()->gettext($p, $domain);
91}
92
93
94/**
95 * Global wrapper of rcmail::text_exists()
96 * to check whether a text label is defined
97 *
98 * @see rcmail::text_exists()
99 */
100function rcube_label_exists($name, $domain=null)
101{
102  return rcmail::get_instance()->text_exists($name, $domain);
103}
104
105
106/**
107 * Overwrite action variable
108 *
109 * @param string New action value
110 */
111function rcmail_overwrite_action($action)
112  {
113  $app = rcmail::get_instance();
114  $app->action = $action;
115  $app->output->set_env('action', $action);
116  }
117
118
119/**
120 * Compose an URL for a specific action
121 *
122 * @param string  Request action
123 * @param array   More URL parameters
124 * @param string  Request task (omit if the same)
125 * @return The application URL
126 */
127function rcmail_url($action, $p=array(), $task=null)
128{
129  $app = rcmail::get_instance();
130  return $app->url((array)$p + array('_action' => $action, 'task' => $task));
131}
132
133
134/**
135 * Garbage collector function for temp files.
136 * Remove temp files older than two days
137 */
138function rcmail_temp_gc()
139  {
140  $rcmail = rcmail::get_instance();
141
142  $tmp = unslashify($rcmail->config->get('temp_dir'));
143  $expire = mktime() - 172800;  // expire in 48 hours
144
145  if ($dir = opendir($tmp))
146    {
147    while (($fname = readdir($dir)) !== false)
148      {
149      if ($fname{0} == '.')
150        continue;
151
152      if (filemtime($tmp.'/'.$fname) < $expire)
153        @unlink($tmp.'/'.$fname);
154      }
155
156    closedir($dir);
157    }
158  }
159
160
161/**
162 * Garbage collector for cache entries.
163 * Remove all expired message cache records
164 * @return void
165 */
166function rcmail_cache_gc()
167  {
168  $rcmail = rcmail::get_instance();
169  $db = $rcmail->get_dbh();
170 
171  // get target timestamp
172  $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
173 
174  $db->query("DELETE FROM ".get_table_name('messages')."
175             WHERE  created < " . $db->fromunixtime($ts));
176
177  $db->query("DELETE FROM ".get_table_name('cache')."
178              WHERE  created < " . $db->fromunixtime($ts));
179  }
180
181
182/**
183 * Catch an error and throw an exception.
184 *
185 * @param  int    Level of the error
186 * @param  string Error message
187 */
188function rcube_error_handler($errno, $errstr)
189  {
190  throw new ErrorException($errstr, 0, $errno);
191  }
192
193
194/**
195 * Convert a string from one charset to another.
196 * Uses mbstring and iconv functions if possible
197 *
198 * @param  string Input string
199 * @param  string Suspected charset of the input string
200 * @param  string Target charset to convert to; defaults to RCMAIL_CHARSET
201 * @return string Converted string
202 */
203function rcube_charset_convert($str, $from, $to=NULL)
204  {
205  static $iconv_options = null;
206  static $mbstring_loaded = null;
207  static $mbstring_list = null;
208  static $convert_warning = false;
209  static $conv = null;
210
211  $error = false;
212
213  $to = empty($to) ? strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
214  $from = rcube_parse_charset($from);
215
216  if ($from == $to || empty($str) || empty($from))
217    return $str;
218
219  // convert charset using iconv module
220  if (function_exists('iconv') && $from != 'UTF7-IMAP' && $to != 'UTF7-IMAP') {
221    if ($iconv_options === null) {
222      // ignore characters not available in output charset
223      $iconv_options = '//IGNORE';
224      if (iconv('', $iconv_options, '') === false) {
225        // iconv implementation does not support options
226        $iconv_options = '';
227      }
228    }
229
230    // throw an exception if iconv reports an illegal character in input
231    // it means that input string has been truncated
232    set_error_handler('rcube_error_handler', E_NOTICE);
233    try {
234      $_iconv = iconv($from, $to . $iconv_options, $str);
235    } catch (ErrorException $e) {
236      $_iconv = false;
237    }
238    restore_error_handler();
239    if ($_iconv !== false) {
240      return $_iconv;
241    }
242  }
243
244  if ($mbstring_loaded === null)
245    $mbstring_loaded = extension_loaded('mbstring');
246   
247  // convert charset using mbstring module
248  if ($mbstring_loaded) {
249    $aliases['WINDOWS-1257'] = 'ISO-8859-13';
250   
251    if ($mbstring_list === null) {
252      $mbstring_list = mb_list_encodings();
253      $mbstring_list = array_map('strtoupper', $mbstring_list);
254    }
255
256    $mb_from = $aliases[$from] ? $aliases[$from] : $from;
257    $mb_to = $aliases[$to] ? $aliases[$to] : $to;
258   
259    // return if encoding found, string matches encoding and convert succeeded
260    if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
261      if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
262        return $out;
263    }
264  }
265
266  // convert charset using bundled classes/functions
267  if ($to == 'UTF-8') {
268    if ($from == 'UTF7-IMAP') {
269      if ($_str = utf7_to_utf8($str))
270        return $_str;
271    }
272    else if ($from == 'UTF-7') {
273      if ($_str = rcube_utf7_to_utf8($str))
274        return $_str;
275    }
276    else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
277      return utf8_encode($str);
278    }
279    else if (class_exists('utf8')) {
280      if (!$conv)
281        $conv = new utf8($from);
282      else
283        $conv->loadCharset($from);
284
285      if($_str = $conv->strToUtf8($str))
286        return $_str;
287    }
288    $error = true;
289  }
290
291  // encode string for output
292  if ($from == 'UTF-8') {
293    // @TODO: we need a function for UTF-7 (RFC2152) conversion
294    if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
295      if ($_str = utf8_to_utf7($str))
296        return $_str;
297    }
298    else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
299      return utf8_decode($str);
300    }
301    else if (class_exists('utf8')) {
302      if (!$conv)
303        $conv = new utf8($to);
304      else
305        $conv->loadCharset($from);
306
307      if ($_str = $conv->strToUtf8($str))
308        return $_str;
309    }
310    $error = true;
311  }
312
313  // report error
314  if ($error && !$convert_warning) {
315    raise_error(array(
316      'code' => 500,
317      'type' => 'php',
318      'file' => __FILE__,
319      'line' => __LINE__,
320      'message' => "Could not convert string from $from to $to. Make sure iconv/mbstring is installed or lib/utf8.class is available."
321      ), true, false);
322
323    $convert_warning = true;
324  }
325
326  // return UTF-8 or original string
327  return $str;
328  }
329
330
331/**
332 * Parse and validate charset name string (see #1485758).
333 * Sometimes charset string is malformed, there are also charset aliases
334 * but we need strict names for charset conversion (specially utf8 class)
335 *
336 * @param  string Input charset name
337 * @return string The validated charset name
338 */
339function rcube_parse_charset($input)
340  {
341  static $charsets = array();
342  $charset = strtoupper($input);
343
344  if (isset($charsets[$input]))
345    return $charsets[$input];
346
347  $charset = preg_replace(array(
348    '/^[^0-9A-Z]+/',    // e.g. _ISO-8859-JP$SIO
349    '/\$.*$/',          // e.g. _ISO-8859-JP$SIO
350    '/UNICODE-1-1-*/',  // RFC1641/1642
351    '/^X-/',            // X- prefix (e.g. X-ROMAN8 => ROMAN8)
352    ), '', $charset);
353
354  if ($charset == 'BINARY')
355    return $charsets[$input] = null;
356
357  # Aliases: some of them from HTML5 spec.
358  $aliases = array(
359    'USASCII'       => 'WINDOWS-1252',
360    'ANSIX31101983' => 'WINDOWS-1252',
361    'ANSIX341968'   => 'WINDOWS-1252',
362    'UNKNOWN8BIT'   => 'ISO-8859-15',
363    'UNKNOWN'       => 'ISO-8859-15',
364    'USERDEFINED'   => 'ISO-8859-15',
365    'KSC56011987'   => 'EUC-KR',
366    'GB2312'        => 'GBK',
367    'GB231280'      => 'GBK',
368    'UNICODE'       => 'UTF-8',
369    'UTF7IMAP'      => 'UTF7-IMAP',
370    'TIS620'        => 'WINDOWS-874',
371    'ISO88599'      => 'WINDOWS-1254',
372    'ISO885911'     => 'WINDOWS-874',
373    'MACROMAN'      => 'MACINTOSH',
374    '77'            => 'MAC',
375    '128'           => 'SHIFT-JIS',
376    '129'           => 'CP949',
377    '130'           => 'CP1361',
378    '134'           => 'GBK',
379    '136'           => 'BIG5',
380    '161'           => 'WINDOWS-1253',
381    '162'           => 'WINDOWS-1254',
382    '163'           => 'WINDOWS-1258',
383    '177'           => 'WINDOWS-1255',
384    '178'           => 'WINDOWS-1256',
385    '186'           => 'WINDOWS-1257',
386    '204'           => 'WINDOWS-1251',
387    '222'           => 'WINDOWS-874',
388    '238'           => 'WINDOWS-1250',
389    'MS950'         => 'CP950',
390    'WINDOWS949'    => 'UHC',
391  );
392
393  // allow A-Z and 0-9 only
394  $str = preg_replace('/[^A-Z0-9]/', '', $charset);
395
396  if (isset($aliases[$str]))
397    $result = $aliases[$str];
398  // UTF
399  else if (preg_match('/U[A-Z][A-Z](7|8|16|32)(BE|LE)*/', $str, $m))
400    $result = 'UTF-' . $m[1] . $m[2];
401  // ISO-8859
402  else if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
403    $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
404    // some clients sends windows-1252 text as latin1,
405    // it is safe to use windows-1252 for all latin1
406    $result = $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
407    }
408  // handle broken charset names e.g. WINDOWS-1250HTTP-EQUIVCONTENT-TYPE
409  else if (preg_match('/(WIN|WINDOWS)([0-9]+)/', $str, $m)) {
410    $result = 'WINDOWS-' . $m[2];
411    }
412  // LATIN
413  else if (preg_match('/LATIN(.*)/', $str, $m)) {
414    $aliases = array('2' => 2, '3' => 3, '4' => 4, '5' => 9, '6' => 10,
415        '7' => 13, '8' => 14, '9' => 15, '10' => 16,
416        'ARABIC' => 6, 'CYRILLIC' => 5, 'GREEK' => 7, 'GREEK1' => 7, 'HEBREW' => 8);
417
418    // some clients sends windows-1252 text as latin1,
419    // it is safe to use windows-1252 for all latin1
420    if ($m[1] == 1) {
421      $result = 'WINDOWS-1252';
422      }
423    // if iconv is not supported we need ISO labels, it's also safe for iconv
424    else if (!empty($aliases[$m[1]])) {
425      $result = 'ISO-8859-'.$aliases[$m[1]];
426      }
427    // iconv requires convertion of e.g. LATIN-1 to LATIN1
428    else {
429      $result = $str;
430      }
431    }
432  else {
433    $result = $charset;
434    }
435
436  $charsets[$input] = $result;
437
438  return $result;
439  }
440
441
442/**
443 * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
444 *
445 * @param  string  Input string
446 * @return string  The converted string
447 */
448function rcube_utf7_to_utf8($str)
449{
450  $Index_64 = array(
451    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
452    0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
453    0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
454    1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
455    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
456    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
457    0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
458    1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
459  );
460
461  $u7len = strlen($str);
462  $str = strval($str);
463  $res = '';
464
465  for ($i=0; $u7len > 0; $i++, $u7len--)
466  {
467    $u7 = $str[$i];
468    if ($u7 == '+')
469    {
470      $i++;
471      $u7len--;
472      $ch = '';
473
474      for (; $u7len > 0; $i++, $u7len--)
475      {
476        $u7 = $str[$i];
477
478        if (!$Index_64[ord($u7)])
479          break;
480
481        $ch .= $u7;
482      }
483
484      if ($ch == '') {
485        if ($u7 == '-')
486          $res .= '+';
487        continue;
488      }
489
490      $res .= rcube_utf16_to_utf8(base64_decode($ch));
491    }
492    else
493    {
494      $res .= $u7;
495    }
496  }
497
498  return $res;
499}
500
501/**
502 * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
503 *
504 * @param  string  Input string
505 * @return string  The converted string
506 */
507function rcube_utf16_to_utf8($str)
508{
509  $len = strlen($str);
510  $dec = '';
511
512  for ($i = 0; $i < $len; $i += 2) {
513    $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
514    if ($c >= 0x0001 && $c <= 0x007F) {
515      $dec .= chr($c);
516    } else if ($c > 0x07FF) {
517      $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
518      $dec .= chr(0x80 | (($c >>  6) & 0x3F));
519      $dec .= chr(0x80 | (($c >>  0) & 0x3F));
520    } else {
521      $dec .= chr(0xC0 | (($c >>  6) & 0x1F));
522      $dec .= chr(0x80 | (($c >>  0) & 0x3F));
523    }
524  }
525  return $dec;
526}
527
528
529/**
530 * Replacing specials characters to a specific encoding type
531 *
532 * @param  string  Input string
533 * @param  string  Encoding type: text|html|xml|js|url
534 * @param  string  Replace mode for tags: show|replace|remove
535 * @param  boolean Convert newlines
536 * @return string  The quoted string
537 */
538function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
539  {
540  static $html_encode_arr = false;
541  static $js_rep_table = false;
542  static $xml_rep_table = false;
543
544  if (!$enctype)
545    $enctype = $OUTPUT->type;
546
547  // encode for HTML output
548  if ($enctype=='html')
549    {
550    if (!$html_encode_arr)
551      {
552      $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
553      unset($html_encode_arr['?']);
554      }
555
556    $ltpos = strpos($str, '<');
557    $encode_arr = $html_encode_arr;
558
559    // don't replace quotes and html tags
560    if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
561      {
562      unset($encode_arr['"']);
563      unset($encode_arr['<']);
564      unset($encode_arr['>']);
565      unset($encode_arr['&']);
566      }
567    else if ($mode=='remove')
568      $str = strip_tags($str);
569
570    $out = strtr($str, $encode_arr);
571
572    // avoid douple quotation of &
573    $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
574
575    return $newlines ? nl2br($out) : $out;
576    }
577
578  // if the replace tables for XML and JS are not yet defined
579  if ($js_rep_table===false)
580    {
581    $js_rep_table = $xml_rep_table = array();
582    $xml_rep_table['&'] = '&amp;';
583
584    for ($c=160; $c<256; $c++)  // can be increased to support more charsets
585      $xml_rep_table[chr($c)] = "&#$c;";
586
587    $xml_rep_table['"'] = '&quot;';
588    $js_rep_table['"'] = '\\"';
589    $js_rep_table["'"] = "\\'";
590    $js_rep_table["\\"] = "\\\\";
591    // Unicode line and paragraph separators (#1486310)
592    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
593    $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
594    }
595
596  // encode for javascript use
597  if ($enctype=='js')
598    return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
599
600  // encode for plaintext
601  if ($enctype=='text')
602    return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
603
604  if ($enctype=='url')
605    return rawurlencode($str);
606
607  // encode for XML
608  if ($enctype=='xml')
609    return strtr($str, $xml_rep_table);
610
611  // no encoding given -> return original string
612  return $str;
613  }
614 
615/**
616 * Quote a given string.
617 * Shortcut function for rep_specialchars_output
618 *
619 * @return string HTML-quoted string
620 * @see rep_specialchars_output()
621 */
622function Q($str, $mode='strict', $newlines=TRUE)
623  {
624  return rep_specialchars_output($str, 'html', $mode, $newlines);
625  }
626
627/**
628 * Quote a given string for javascript output.
629 * Shortcut function for rep_specialchars_output
630 *
631 * @return string JS-quoted string
632 * @see rep_specialchars_output()
633 */
634function JQ($str)
635  {
636  return rep_specialchars_output($str, 'js');
637  }
638
639
640/**
641 * Read input value and convert it for internal use
642 * Performs stripslashes() and charset conversion if necessary
643 *
644 * @param  string   Field name to read
645 * @param  int      Source to get value from (GPC)
646 * @param  boolean  Allow HTML tags in field value
647 * @param  string   Charset to convert into
648 * @return string   Field value or NULL if not available
649 */
650function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
651{
652  $value = NULL;
653 
654  if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
655    $value = $_GET[$fname];
656  else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
657    $value = $_POST[$fname];
658  else if ($source==RCUBE_INPUT_GPC)
659    {
660    if (isset($_POST[$fname]))
661      $value = $_POST[$fname];
662    else if (isset($_GET[$fname]))
663      $value = $_GET[$fname];
664    else if (isset($_COOKIE[$fname]))
665      $value = $_COOKIE[$fname];
666    }
667
668  return parse_input_value($value, $allow_html, $charset);
669}
670
671/**
672 * Parse/validate input value. See get_input_value()
673 * Performs stripslashes() and charset conversion if necessary
674 *
675 * @param  string   Input value
676 * @param  boolean  Allow HTML tags in field value
677 * @param  string   Charset to convert into
678 * @return string   Parsed value
679 */
680function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
681{
682  global $OUTPUT;
683
684  if (empty($value))
685    return $value;
686
687  if (is_array($value)) {
688    foreach ($value as $idx => $val)
689      $value[$idx] = parse_input_value($val, $allow_html, $charset);
690    return $value;
691  }
692
693  // strip single quotes if magic_quotes_sybase is enabled
694  if (ini_get('magic_quotes_sybase'))
695    $value = str_replace("''", "'", $value);
696  // strip slashes if magic_quotes enabled
697  else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
698    $value = stripslashes($value);
699
700  // remove HTML tags if not allowed   
701  if (!$allow_html)
702    $value = strip_tags($value);
703 
704  // convert to internal charset
705  if (is_object($OUTPUT) && $charset)
706    return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
707  else
708    return $value;
709}
710
711/**
712 * Convert array of request parameters (prefixed with _)
713 * to a regular array with non-prefixed keys.
714 *
715 * @param  int   Source to get value from (GPC)
716 * @return array Hash array with all request parameters
717 */
718function request2param($mode = RCUBE_INPUT_GPC)
719{
720  $out = array();
721  $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
722  foreach ($src as $key => $value) {
723    $fname = $key[0] == '_' ? substr($key, 1) : $key;
724    $out[$fname] = get_input_value($key, $mode);
725  }
726 
727  return $out;
728}
729
730/**
731 * Remove all non-ascii and non-word chars
732 * except ., -, _
733 */
734function asciiwords($str, $css_id = false, $replace_with = '')
735{
736  $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
737  return preg_replace("/[^$allowed]/i", $replace_with, $str);
738}
739
740/**
741 * Convert the given string into a valid HTML identifier
742 * Same functionality as done in app.js with this.identifier_expr
743 *
744 */
745function html_identifier($str)
746{
747  return asciiwords($str, true, '_');
748}
749
750/**
751 * Remove single and double quotes from given string
752 *
753 * @param string Input value
754 * @return string Dequoted string
755 */
756function strip_quotes($str)
757{
758  return str_replace(array("'", '"'), '', $str);
759}
760
761
762/**
763 * Remove new lines characters from given string
764 *
765 * @param string Input value
766 * @return string Stripped string
767 */
768function strip_newlines($str)
769{
770  return preg_replace('/[\r\n]/', '', $str);
771}
772
773
774/**
775 * Create a HTML table based on the given data
776 *
777 * @param  array  Named table attributes
778 * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
779 * @param  array  List of cols to show
780 * @param  string Name of the identifier col
781 * @return string HTML table code
782 */
783function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
784  {
785  global $RCMAIL;
786 
787  $table = new html_table(/*array('cols' => count($a_show_cols))*/);
788   
789  // add table header
790  if (!$attrib['noheader'])
791    foreach ($a_show_cols as $col)
792      $table->add_header($col, Q(rcube_label($col)));
793 
794  $c = 0;
795  if (!is_array($table_data))
796  {
797    $db = $RCMAIL->get_dbh();
798    while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
799    {
800      $zebra_class = $c % 2 ? 'even' : 'odd';
801      $table->add_row(array('id' => 'rcmrow' . html_identifier($sql_arr[$id_col]), 'class' => $zebra_class));
802
803      // format each col
804      foreach ($a_show_cols as $col)
805        $table->add($col, Q($sql_arr[$col]));
806     
807      $c++;
808    }
809  }
810  else
811  {
812    foreach ($table_data as $row_data)
813    {
814      $zebra_class = $c % 2 ? 'even' : 'odd';
815      if (!empty($row_data['class']))
816        $zebra_class .= ' '.$row_data['class'];
817
818      $table->add_row(array('id' => 'rcmrow' . html_identifier($row_data[$id_col]), 'class' => $zebra_class));
819
820      // format each col
821      foreach ($a_show_cols as $col)
822        $table->add($col, Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
823       
824      $c++;
825    }
826  }
827
828  return $table->show($attrib);
829  }
830
831
832/**
833 * Create an edit field for inclusion on a form
834 *
835 * @param string col field name
836 * @param string value field value
837 * @param array attrib HTML element attributes for field
838 * @param string type HTML element type (default 'text')
839 * @return string HTML field definition
840 */
841function rcmail_get_edit_field($col, $value, $attrib, $type='text')
842{
843  static $colcounts = array();
844 
845  $fname = '_'.$col;
846  $attrib['name'] = $fname . ($attrib['array'] ? '[]' : '');
847  $attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
848 
849  if ($type == 'checkbox') {
850    $attrib['value'] = '1';
851    $input = new html_checkbox($attrib);
852  }
853  else if ($type == 'textarea') {
854    $attrib['cols'] = $attrib['size'];
855    $input = new html_textarea($attrib);
856  }
857  else if ($type == 'select') {
858    $input = new html_select($attrib);
859    $input->add('---', '');
860    $input->add(array_values($attrib['options']), array_keys($attrib['options']));
861  }
862  else {
863    if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden')
864        $attrib['type'] = 'text';
865    $input = new html_inputfield($attrib);
866  }
867
868  // use value from post
869  if (isset($_POST[$fname])) {
870    $postvalue = get_input_value($fname, RCUBE_INPUT_POST,
871      $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
872    $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
873  }
874
875  $out = $input->show($value);
876
877  return $out;
878}
879
880
881/**
882 * Replace all css definitions with #container [def]
883 * and remove css-inlined scripting
884 *
885 * @param string CSS source code
886 * @param string Container ID to use as prefix
887 * @return string Modified CSS source
888 */
889function rcmail_mod_css_styles($source, $container_id)
890  {
891  $last_pos = 0;
892  $replacements = new rcube_string_replacer;
893
894  // ignore the whole block if evil styles are detected
895  $stripped = preg_replace('/[^a-z\(:;]/', '', rcmail_xss_entity_decode($source));
896  if (preg_match('/expression|behavior|url\(|import[^a]/', $stripped))
897    return '/* evil! */';
898
899  // remove css comments (sometimes used for some ugly hacks)
900  $source = preg_replace('!/\*(.+)\*/!Ums', '', $source);
901
902  // cut out all contents between { and }
903  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
904  {
905    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
906    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
907    $last_pos = $pos+2;
908  }
909
910  // remove html comments and add #container to each tag selector.
911  // also replace body definition because we also stripped off the <body> tag
912  $styles = preg_replace(
913    array(
914      '/(^\s*<!--)|(-->\s*$)/',
915      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
916      '/'.preg_quote($container_id, '/').'\s+body/i',
917    ),
918    array(
919      '',
920      "\\1#$container_id \\2",
921      $container_id,
922    ),
923    $source);
924
925  // put block contents back in
926  $styles = $replacements->resolve($styles);
927
928  return $styles;
929  }
930
931
932/**
933 * Decode escaped entities used by known XSS exploits.
934 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
935 *
936 * @param string CSS content to decode
937 * @return string Decoded string
938 */
939function rcmail_xss_entity_decode($content)
940{
941  $out = html_entity_decode(html_entity_decode($content));
942  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
943  $out = preg_replace('#/\*.*\*/#Um', '', $out);
944  return $out;
945}
946
947
948/**
949 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
950 *
951 * @param array matches result from preg_replace_callback
952 * @return string decoded entity
953 */
954function rcmail_xss_entity_decode_callback($matches)
955{
956  return chr(hexdec($matches[1]));
957}
958
959/**
960 * Compose a valid attribute string for HTML tags
961 *
962 * @param array Named tag attributes
963 * @param array List of allowed attributes
964 * @return string HTML formatted attribute string
965 */
966function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
967  {
968  // allow the following attributes to be added to the <iframe> tag
969  $attrib_str = '';
970  foreach ($allowed_attribs as $a)
971    if (isset($attrib[$a]))
972      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
973
974  return $attrib_str;
975  }
976
977
978/**
979 * Convert a HTML attribute string attributes to an associative array (name => value)
980 *
981 * @param string Input string
982 * @return array Key-value pairs of parsed attributes
983 */
984function parse_attrib_string($str)
985  {
986  $attrib = array();
987  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
988
989  // convert attributes to an associative array (name => value)
990  if ($regs) {
991    foreach ($regs as $attr) {
992      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
993    }
994  }
995
996  return $attrib;
997  }
998
999
1000/**
1001 * Improved equivalent to strtotime()
1002 *
1003 * @param string Date string
1004 * @return int
1005 */
1006function rcube_strtotime($date)
1007{
1008  // check for MS Outlook vCard date format YYYYMMDD
1009  if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
1010    return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
1011  }
1012  else if (is_numeric($date))
1013    return $date;
1014
1015  // support non-standard "GMTXXXX" literal
1016  $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
1017
1018  // if date parsing fails, we have a date in non-rfc format.
1019  // remove token from the end and try again
1020  while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
1021    $d = explode(' ', $date);
1022    array_pop($d);
1023    if (!$d) break;
1024    $date = implode(' ', $d);
1025  }
1026
1027  return $ts;
1028}
1029
1030
1031/**
1032 * Convert the given date to a human readable form
1033 * This uses the date formatting properties from config
1034 *
1035 * @param mixed Date representation (string or timestamp)
1036 * @param string Date format to use
1037 * @return string Formatted date string
1038 */
1039function format_date($date, $format=NULL)
1040{
1041  global $CONFIG;
1042 
1043  $ts = NULL;
1044
1045  if (!empty($date))
1046    $ts = rcube_strtotime($date);
1047
1048  if (empty($ts))
1049    return '';
1050
1051  // get user's timezone
1052  if ($CONFIG['timezone'] === 'auto')
1053    $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
1054  else {
1055    $tz = $CONFIG['timezone'];
1056    if ($CONFIG['dst_active'])
1057      $tz++;
1058  }
1059
1060  // convert time to user's timezone
1061  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
1062
1063  // get current timestamp in user's timezone
1064  $now = time();  // local time
1065  $now -= (int)date('Z'); // make GMT time
1066  $now += ($tz * 3600); // user's time
1067  $now_date = getdate($now);
1068
1069  $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1070  $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
1071
1072  // define date format depending on current time
1073  if (!$format) {
1074    if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
1075      $format = $CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i';
1076      $today  = true;
1077    }
1078    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1079      $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
1080    else
1081      $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
1082  }
1083
1084  // strftime() format
1085  if (preg_match('/%[a-z]+/i', $format)) {
1086    $format = strftime($format, $timestamp);
1087    return $today ? (rcube_label('today') . ' ' . $format) : $format;
1088  }
1089
1090  // parse format string manually in order to provide localized weekday and month names
1091  // an alternative would be to convert the date() format string to fit with strftime()
1092  $out = '';
1093  for($i=0; $i<strlen($format); $i++) {
1094    if ($format{$i}=='\\')  // skip escape chars
1095      continue;
1096
1097    // write char "as-is"
1098    if ($format{$i}==' ' || $format{$i-1}=='\\')
1099      $out .= $format{$i};
1100    // weekday (short)
1101    else if ($format{$i}=='D')
1102      $out .= rcube_label(strtolower(date('D', $timestamp)));
1103    // weekday long
1104    else if ($format{$i}=='l')
1105      $out .= rcube_label(strtolower(date('l', $timestamp)));
1106    // month name (short)
1107    else if ($format{$i}=='M')
1108      $out .= rcube_label(strtolower(date('M', $timestamp)));
1109    // month name (long)
1110    else if ($format{$i}=='F')
1111      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1112    else if ($format{$i}=='x')
1113      $out .= strftime('%x %X', $timestamp);
1114    else
1115      $out .= date($format{$i}, $timestamp);
1116  }
1117
1118  if ($today) {
1119    $label = rcube_label('today');
1120    // replcae $ character with "Today" label (#1486120)
1121    if (strpos($out, '$') !== false) {
1122      $out = preg_replace('/\$/', $label, $out, 1);
1123    }
1124    else {
1125      $out = $label . ' ' . $out;
1126    }
1127  }
1128
1129  return $out;
1130}
1131
1132
1133/**
1134 * Compose a valid representation of name and e-mail address
1135 *
1136 * @param string E-mail address
1137 * @param string Person name
1138 * @return string Formatted string
1139 */
1140function format_email_recipient($email, $name='')
1141{
1142  if ($name && $name != $email) {
1143    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1144    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1145  }
1146
1147  return trim($email);
1148}
1149
1150
1151/**
1152 * Return the mailboxlist in HTML
1153 *
1154 * @param array Named parameters
1155 * @return string HTML code for the gui object
1156 */
1157function rcmail_mailbox_list($attrib)
1158{
1159  global $RCMAIL;
1160  static $a_mailboxes;
1161 
1162  $attrib += array('maxlength' => 100, 'realnames' => false);
1163
1164  // add some labels to client
1165  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1166 
1167  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1168  unset($attrib['type']);
1169
1170  if ($type=='ul' && !$attrib['id'])
1171    $attrib['id'] = 'rcmboxlist';
1172
1173  // get mailbox list
1174  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1175 
1176  // build the folders tree
1177  if (empty($a_mailboxes)) {
1178    // get mailbox list
1179    $a_folders = $RCMAIL->imap->list_mailboxes();
1180    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1181    $a_mailboxes = array();
1182
1183    foreach ($a_folders as $folder)
1184      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1185  }
1186
1187  // allow plugins to alter the folder tree or to localize folder names
1188  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1189
1190  if ($type=='select') {
1191    $select = new html_select($attrib);
1192   
1193    // add no-selection option
1194    if ($attrib['noselection'])
1195      $select->add(rcube_label($attrib['noselection']), '0');
1196   
1197    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1198    $out = $select->show();
1199  }
1200  else {
1201    $js_mailboxlist = array();
1202    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1203   
1204    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1205    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1206    $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1207  }
1208
1209  return $out;
1210}
1211
1212
1213/**
1214 * Return the mailboxlist as html_select object
1215 *
1216 * @param array Named parameters
1217 * @return html_select HTML drop-down object
1218 */
1219function rcmail_mailbox_select($p = array())
1220{
1221  global $RCMAIL;
1222 
1223  $p += array('maxlength' => 100, 'realnames' => false);
1224  $a_mailboxes = array();
1225
1226  if ($p['unsubscribed'])
1227    $list = $RCMAIL->imap->list_unsubscribed();
1228  else
1229    $list = $RCMAIL->imap->list_mailboxes();
1230
1231  foreach ($list as $folder)
1232    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1233      rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1234
1235  $select = new html_select($p);
1236 
1237  if ($p['noselection'])
1238    $select->add($p['noselection'], '');
1239   
1240  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1241 
1242  return $select;
1243}
1244
1245
1246/**
1247 * Create a hierarchical array of the mailbox list
1248 * @access private
1249 * @return void
1250 */
1251function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1252{
1253  global $RCMAIL;
1254
1255  $pos = strpos($folder, $delm);
1256
1257  if ($pos !== false) {
1258    $subFolders = substr($folder, $pos+1);
1259    $currentFolder = substr($folder, 0, $pos);
1260
1261    // sometimes folder has a delimiter as the last character
1262    if (!strlen($subFolders))
1263      $virtual = false;
1264    else if (!isset($arrFolders[$currentFolder]))
1265      $virtual = true;
1266    else
1267      $virtual = $arrFolders[$currentFolder]['virtual'];
1268  }
1269  else {
1270    $subFolders = false;
1271    $currentFolder = $folder;
1272    $virtual = false;
1273  }
1274
1275  $path .= $currentFolder;
1276
1277  // Check \Noselect option (if options are in cache)
1278  if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
1279    $virtual = in_array('\\Noselect', $opts);
1280  }
1281
1282  if (!isset($arrFolders[$currentFolder])) {
1283    $arrFolders[$currentFolder] = array(
1284      'id' => $path,
1285      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1286      'virtual' => $virtual,
1287      'folders' => array());
1288  }
1289  else
1290    $arrFolders[$currentFolder]['virtual'] = $virtual;
1291
1292  if (strlen($subFolders))
1293    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1294}
1295 
1296
1297/**
1298 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1299 * @access private
1300 * @return string
1301 */
1302function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1303{
1304  global $RCMAIL, $CONFIG;
1305 
1306  $maxlength = intval($attrib['maxlength']);
1307  $realnames = (bool)$attrib['realnames'];
1308  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1309
1310  $idx = 0;
1311  $out = '';
1312  foreach ($arrFolders as $key => $folder) {
1313    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1314    $title = null;
1315
1316    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1317      $foldername = rcube_label($folder_class);
1318    }
1319    else {
1320      $foldername = $folder['name'];
1321
1322      // shorten the folder name to a given length
1323      if ($maxlength && $maxlength > 1) {
1324        $fname = abbreviate_string($foldername, $maxlength);
1325        if ($fname != $foldername)
1326          $title = $foldername;
1327        $foldername = $fname;
1328      }
1329    }
1330
1331    // make folder name safe for ids and class names
1332    $folder_id = html_identifier($folder['id']);
1333    $classes = array('mailbox');
1334
1335    // set special class for Sent, Drafts, Trash and Junk
1336    if ($folder['id']==$CONFIG['sent_mbox'])
1337      $classes[] = 'sent';
1338    else if ($folder['id']==$CONFIG['drafts_mbox'])
1339      $classes[] = 'drafts';
1340    else if ($folder['id']==$CONFIG['trash_mbox'])
1341      $classes[] = 'trash';
1342    else if ($folder['id']==$CONFIG['junk_mbox'])
1343      $classes[] = 'junk';
1344    else if ($folder['id']=='INBOX')
1345      $classes[] = 'inbox';
1346    else
1347      $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1348     
1349    $classes[] = $zebra_class;
1350   
1351    if ($folder['id'] == $mbox_name)
1352      $classes[] = 'selected';
1353
1354    $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1355    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1356   
1357    if ($folder['virtual'])
1358      $classes[] = 'virtual';
1359    else if ($unread)
1360      $classes[] = 'unread';
1361
1362    $js_name = JQ($folder['id']);
1363    $html_name = Q($foldername) . ($unread ? html::span('unreadcount', " ($unread)") : '');
1364    $link_attrib = $folder['virtual'] ? array() : array(
1365      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1366      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1367      'rel' => $folder['id'],
1368      'title' => $title,
1369    );
1370
1371    $out .= html::tag('li', array(
1372        'id' => "rcmli".$folder_id,
1373        'class' => join(' ', $classes),
1374        'noclose' => true),
1375      html::a($link_attrib, $html_name) .
1376      (!empty($folder['folders']) ? html::div(array(
1377        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1378        'style' => "position:absolute",
1379        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1380      ), '&nbsp;') : ''));
1381   
1382    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1383   
1384    if (!empty($folder['folders'])) {
1385      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1386        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1387    }
1388
1389    $out .= "</li>\n";
1390    $idx++;
1391  }
1392
1393  return $out;
1394}
1395
1396
1397/**
1398 * Return html for a flat list <select> for the mailbox tree
1399 * @access private
1400 * @return string
1401 */
1402function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1403  {
1404  $idx = 0;
1405  $out = '';
1406  foreach ($arrFolders as $key=>$folder)
1407    {
1408    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1409      $foldername = rcube_label($folder_class);
1410    else
1411      {
1412      $foldername = $folder['name'];
1413     
1414      // shorten the folder name to a given length
1415      if ($maxlength && $maxlength>1)
1416        $foldername = abbreviate_string($foldername, $maxlength);
1417      }
1418
1419    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1420
1421    if (!empty($folder['folders']))
1422      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1423
1424    $idx++;
1425    }
1426
1427  return $out;
1428  }
1429
1430
1431/**
1432 * Return internal name for the given folder if it matches the configured special folders
1433 * @access private
1434 * @return string
1435 */
1436function rcmail_folder_classname($folder_id)
1437{
1438  global $CONFIG;
1439
1440  if ($folder_id == 'INBOX')
1441    return 'inbox';
1442
1443  // for these mailboxes we have localized labels and css classes
1444  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1445  {
1446    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1447      return $smbx;
1448  }
1449}
1450
1451
1452/**
1453 * Try to localize the given IMAP folder name.
1454 * UTF-7 decode it in case no localized text was found
1455 *
1456 * @param string Folder name
1457 * @return string Localized folder name in UTF-8 encoding
1458 */
1459function rcmail_localize_foldername($name)
1460{
1461  if ($folder_class = rcmail_folder_classname($name))
1462    return rcube_label($folder_class);
1463  else
1464    return rcube_charset_convert($name, 'UTF7-IMAP');
1465}
1466
1467
1468function rcmail_quota_display($attrib)
1469{
1470  global $OUTPUT;
1471
1472  if (!$attrib['id'])
1473    $attrib['id'] = 'rcmquotadisplay';
1474
1475  if(isset($attrib['display']))
1476    $_SESSION['quota_display'] = $attrib['display'];
1477
1478  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1479
1480  $quota = rcmail_quota_content($attrib);
1481
1482  $OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready');
1483
1484  return html::span($attrib, '');
1485}
1486
1487
1488function rcmail_quota_content($attrib=NULL)
1489{
1490  global $RCMAIL;
1491
1492  $quota = $RCMAIL->imap->get_quota();
1493  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1494
1495  $quota_result = (array) $quota;
1496  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1497
1498  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1499    $quota_result['title'] = rcube_label('unlimited');
1500    $quota_result['percent'] = 0;
1501  }
1502  else if ($quota['total']) {
1503    if (!isset($quota['percent']))
1504      $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1505
1506    $title = sprintf('%s / %s (%.0f%%)',
1507        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1508        $quota_result['percent']);
1509
1510    $quota_result['title'] = $title;
1511
1512    if ($attrib['width'])
1513      $quota_result['width'] = $attrib['width'];
1514    if ($attrib['height'])
1515      $quota_result['height']   = $attrib['height'];
1516  }
1517  else {
1518    $quota_result['title'] = rcube_label('unknown');
1519    $quota_result['percent'] = 0;
1520  }
1521
1522  return $quota_result;
1523}
1524
1525
1526/**
1527 * Outputs error message according to server error/response codes
1528 *
1529 * @param string Fallback message label
1530 * @param string Fallback message label arguments
1531 *
1532 * @return void
1533 */
1534function rcmail_display_server_error($fallback=null, $fallback_args=null)
1535{
1536    global $RCMAIL;
1537
1538    $err_code = $RCMAIL->imap->get_error_code();
1539    $res_code = $RCMAIL->imap->get_response_code();
1540
1541    if ($res_code == rcube_imap::NOPERM) {
1542        $RCMAIL->output->show_message('errornoperm', 'error');
1543    }
1544    else if ($res_code == rcube_imap::READONLY) {
1545        $RCMAIL->output->show_message('errorreadonly', 'error');
1546    }
1547    else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1548        $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1549    }
1550    else if ($fallback) {
1551        $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1552    }
1553
1554    return true;
1555}
1556
1557
1558/**
1559 * Output HTML editor scripts
1560 *
1561 * @param string Editor mode
1562 * @return void
1563 */
1564function rcube_html_editor($mode='')
1565{
1566  global $RCMAIL, $CONFIG;
1567
1568  $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
1569
1570  if ($hook['abort'])
1571    return; 
1572
1573  $lang = strtolower($_SESSION['language']);
1574
1575  // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1576  $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1577
1578  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1579    $lang = 'en';
1580
1581  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1582  $RCMAIL->output->include_script('editor.js');
1583  $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
1584    JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
1585    'foot');
1586}
1587
1588
1589/**
1590 * Replaces TinyMCE's emoticon images with plain-text representation
1591 *
1592 * @param string HTML content
1593 * @return string HTML content
1594 */
1595function rcmail_replace_emoticons($html)
1596{
1597  $emoticons = array(
1598    '8-)' => 'smiley-cool',
1599    ':-#' => 'smiley-foot-in-mouth',
1600    ':-*' => 'smiley-kiss',
1601    ':-X' => 'smiley-sealed',
1602    ':-P' => 'smiley-tongue-out',
1603    ':-@' => 'smiley-yell',
1604    ":'(" => 'smiley-cry',
1605    ':-(' => 'smiley-frown',
1606    ':-D' => 'smiley-laughing',
1607    ':-)' => 'smiley-smile',
1608    ':-S' => 'smiley-undecided',
1609    ':-$' => 'smiley-embarassed',
1610    'O:-)' => 'smiley-innocent',
1611    ':-|' => 'smiley-money-mouth',
1612    ':-O' => 'smiley-surprised',
1613    ';-)' => 'smiley-wink',
1614  );
1615
1616  foreach ($emoticons as $idx => $file) {
1617    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1618    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1619    $replace[] = $idx;
1620  }
1621
1622  return preg_replace($search, $replace, $html);
1623}
1624
1625
1626/**
1627 * Check if working in SSL mode
1628 *
1629 * @param integer HTTPS port number
1630 * @param boolean Enables 'use_https' option checking
1631 * @return boolean
1632 */
1633function rcube_https_check($port=null, $use_https=true)
1634{
1635  global $RCMAIL;
1636
1637  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1638    return true;
1639  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1640    return true;
1641  if ($port && $_SERVER['SERVER_PORT'] == $port)
1642    return true;
1643  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1644    return true;
1645
1646  return false;
1647}
1648
1649
1650/**
1651 * For backward compatibility.
1652 *
1653 * @global rcmail $RCMAIL
1654 * @param string $var_name Variable name.
1655 * @return void
1656 */
1657function rcube_sess_unset($var_name=null)
1658{
1659  global $RCMAIL;
1660
1661  $RCMAIL->session->remove($var_name);
1662}
1663
1664
1665/**
1666 * Replaces hostname variables
1667 *
1668 * @param string $name Hostname
1669 * @param string $host Optional IMAP hostname
1670 * @return string
1671 */
1672function rcube_parse_host($name, $host='')
1673{
1674  // %n - host
1675  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1676  // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
1677  $d = preg_replace('/^[^\.]+\./', '', $n);
1678  // %h - IMAP host
1679  $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
1680  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1681  $z = preg_replace('/^[^\.]+\./', '', $h);
1682  // %s - domain name after the '@' from e-mail address provided at login screen
1683  if ( filter_var(get_input_value('_user', RCUBE_INPUT_POST), FILTER_VALIDATE_EMAIL) !== FALSE )
1684    preg_match('/[^@]+$/', get_input_value('_user', RCUBE_INPUT_POST), $s);
1685
1686  $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[0]), $name);
1687  return $name;
1688}
1689
1690
1691/**
1692 * E-mail address validation
1693 *
1694 * @param string $email Email address
1695 * @param boolean $dns_check True to check dns
1696 * @return boolean
1697 */
1698function check_email($email, $dns_check=true)
1699{
1700  // Check for invalid characters
1701  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1702    return false;
1703
1704  // Check for length limit specified by RFC 5321 (#1486453)
1705  if (strlen($email) > 254)
1706    return false;
1707
1708  $email_array = explode('@', $email);
1709
1710  // Check that there's one @ symbol
1711  if (count($email_array) < 2)
1712    return false;
1713
1714  $domain_part = array_pop($email_array);
1715  $local_part = implode('@', $email_array);
1716
1717  // from PEAR::Validate
1718  $regexp = '&^(?:
1719        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1720        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
1721        $&xi';
1722
1723  if (!preg_match($regexp, $local_part))
1724    return false;
1725
1726  // Check domain part
1727  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))
1728    return true; // IP address
1729  else {
1730    // If not an IP address
1731    $domain_array = explode('.', $domain_part);
1732    if (sizeof($domain_array) < 2)
1733      return false; // Not enough parts to be a valid domain
1734
1735    foreach ($domain_array as $part)
1736      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1737        return false;
1738
1739    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1740      return true;
1741
1742    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1743      $lookup = array();
1744      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1745      foreach ($lookup as $line) {
1746        if (strpos($line, 'MX preference'))
1747          return true;
1748      }
1749      return false;
1750    }
1751
1752    // find MX record(s)
1753    if (getmxrr($domain_part, $mx_records))
1754      return true;
1755
1756    // find any DNS record
1757    if (checkdnsrr($domain_part, 'ANY'))
1758      return true;
1759  }
1760
1761  return false;
1762}
1763
1764/*
1765 * Idn_to_ascii wrapper.
1766 * Intl/Idn modules version of this function doesn't work with e-mail address
1767 */
1768function rcube_idn_to_ascii($str)
1769{
1770  return rcube_idn_convert($str, true);
1771}
1772
1773/*
1774 * Idn_to_ascii wrapper.
1775 * Intl/Idn modules version of this function doesn't work with e-mail address
1776 */
1777function rcube_idn_to_utf8($str)
1778{
1779  return rcube_idn_convert($str, false);
1780}
1781
1782function rcube_idn_convert($input, $is_utf=false)
1783{
1784  if ($at = strpos($input, '@')) {
1785    $user   = substr($input, 0, $at);
1786    $domain = substr($input, $at+1);
1787  }
1788  else {
1789    $domain = $input;
1790  }
1791
1792  $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
1793
1794  return $at ? $user . '@' . $domain : $domain;
1795}
1796
1797
1798/**
1799 * Helper class to turn relative urls into absolute ones
1800 * using a predefined base
1801 */
1802class rcube_base_replacer
1803{
1804  private $base_url;
1805
1806  public function __construct($base)
1807  {
1808    $this->base_url = $base;
1809  }
1810
1811  public function callback($matches)
1812  {
1813    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1814  }
1815}
1816
1817
1818/****** debugging and logging functions ********/
1819
1820/**
1821 * Print or write debug messages
1822 *
1823 * @param mixed Debug message or data
1824 * @return void
1825 */
1826function console()
1827{
1828    $args = func_get_args();
1829
1830    if (class_exists('rcmail', false)) {
1831        $rcmail = rcmail::get_instance();
1832        if (is_object($rcmail->plugins)) {
1833            $plugin = $rcmail->plugins->exec_hook('console', array('args' => $args));
1834            if ($plugin['abort'])
1835                return;
1836            $args = $plugin['args'];
1837        }
1838    }
1839
1840    $msg = array();
1841    foreach ($args as $arg)
1842        $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1843
1844    write_log('console', join(";\n", $msg));
1845}
1846
1847
1848/**
1849 * Append a line to a logfile in the logs directory.
1850 * Date will be added automatically to the line.
1851 *
1852 * @param $name name of log file
1853 * @param line Line to append
1854 * @return void
1855 */
1856function write_log($name, $line)
1857{
1858  global $CONFIG, $RCMAIL;
1859
1860  if (!is_string($line))
1861    $line = var_export($line, true);
1862 
1863  if (empty($CONFIG['log_date_format']))
1864    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1865 
1866  $date = date($CONFIG['log_date_format']);
1867 
1868  // trigger logging hook
1869  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1870    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1871    $name = $log['name'];
1872    $line = $log['line'];
1873    $date = $log['date'];
1874    if ($log['abort'])
1875      return true;
1876  }
1877 
1878  if ($CONFIG['log_driver'] == 'syslog') {
1879    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1880    syslog($prio, $line);
1881    return true;
1882  }
1883  else {
1884    $line = sprintf("[%s]: %s\n", $date, $line);
1885
1886    // log_driver == 'file' is assumed here
1887    if (empty($CONFIG['log_dir']))
1888      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1889
1890    // try to open specific log file for writing
1891    $logfile = $CONFIG['log_dir'].'/'.$name;
1892    if ($fp = @fopen($logfile, 'a')) {
1893      fwrite($fp, $line);
1894      fflush($fp);
1895      fclose($fp);
1896      return true;
1897    }
1898    else
1899      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1900  }
1901
1902  return false;
1903}
1904
1905
1906/**
1907 * Write login data (name, ID, IP address) to the 'userlogins' log file.
1908 *
1909 * @return void
1910 */
1911function rcmail_log_login()
1912{
1913  global $RCMAIL;
1914
1915  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1916    return;
1917
1918  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1919    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip()));
1920}
1921
1922
1923/**
1924 * Returns remote IP address and forwarded addresses if found
1925 *
1926 * @return string Remote IP address(es)
1927 */
1928function rcmail_remote_ip()
1929{
1930    $address = $_SERVER['REMOTE_ADDR'];
1931
1932    // append the NGINX X-Real-IP header, if set
1933    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1934        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1935    }
1936    // append the X-Forwarded-For header, if set
1937    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1938        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1939    }
1940
1941    if (!empty($remote_ip))
1942        $address .= '(' . implode(',', $remote_ip) . ')';
1943
1944    return $address;
1945}
1946
1947
1948/**
1949 * Check whether the HTTP referer matches the current request
1950 *
1951 * @return boolean True if referer is the same host+path, false if not
1952 */
1953function rcube_check_referer()
1954{
1955  $uri = parse_url($_SERVER['REQUEST_URI']);
1956  $referer = parse_url(rc_request_header('Referer'));
1957  return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
1958}
1959
1960
1961/**
1962 * @access private
1963 * @return mixed
1964 */
1965function rcube_timer()
1966{
1967  return microtime(true);
1968}
1969
1970
1971/**
1972 * @access private
1973 * @return void
1974 */
1975function rcube_print_time($timer, $label='Timer', $dest='console')
1976{
1977  static $print_count = 0;
1978
1979  $print_count++;
1980  $now = rcube_timer();
1981  $diff = $now-$timer;
1982
1983  if (empty($label))
1984    $label = 'Timer '.$print_count;
1985
1986  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1987}
1988
1989
1990/**
1991 * Throw system error and show error page
1992 *
1993 * @param array Named parameters
1994 *  - code: Error code (required)
1995 *  - type: Error type [php|db|imap|javascript] (required)
1996 *  - message: Error message
1997 *  - file: File where error occured
1998 *  - line: Line where error occured
1999 * @param boolean True to log the error
2000 * @param boolean Terminate script execution
2001 */
2002// may be defined in Installer
2003if (!function_exists('raise_error')) {
2004function raise_error($arg=array(), $log=false, $terminate=false)
2005{
2006    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
2007
2008    // report bug (if not incompatible browser)
2009    if ($log && $arg['type'] && $arg['message'])
2010        rcube_log_bug($arg);
2011
2012    // display error page and terminate script
2013    if ($terminate) {
2014        $ERROR_CODE = $arg['code'];
2015        $ERROR_MESSAGE = $arg['message'];
2016        include INSTALL_PATH . 'program/steps/utils/error.inc';
2017        exit;
2018    }
2019}
2020}
2021
2022
2023/**
2024 * Report error according to configured debug_level
2025 *
2026 * @param array Named parameters
2027 * @return void
2028 * @see raise_error()
2029 */
2030function rcube_log_bug($arg_arr)
2031{
2032    global $CONFIG;
2033
2034    $program = strtoupper($arg_arr['type']);
2035    $level   = $CONFIG['debug_level'];
2036
2037    // disable errors for ajax requests, write to log instead (#1487831)
2038    if (($level & 4) && !empty($_REQUEST['_remote'])) {
2039        $level = ($level ^ 4) | 1;
2040    }
2041
2042    // write error to local log file
2043    if ($level & 1) {
2044        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
2045        $log_entry = sprintf("%s Error: %s%s (%s %s)",
2046            $program,
2047            $arg_arr['message'],
2048            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
2049            $_SERVER['REQUEST_METHOD'],
2050            $_SERVER['REQUEST_URI'] . $post_query);
2051
2052        if (!write_log('errors', $log_entry)) {
2053            // send error to PHPs error handler if write_log didn't succeed
2054            trigger_error($arg_arr['message']);
2055        }
2056    }
2057
2058    // report the bug to the global bug reporting system
2059    if ($level & 2) {
2060        // TODO: Send error via HTTP
2061    }
2062
2063    // show error if debug_mode is on
2064    if ($level & 4) {
2065        print "<b>$program Error";
2066
2067        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2068            print " in $arg_arr[file] ($arg_arr[line])";
2069
2070        print ':</b>&nbsp;';
2071        print nl2br($arg_arr['message']);
2072        print '<br />';
2073        flush();
2074    }
2075}
2076
Note: See TracBrowser for help on using the repository browser.