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

HEADcourier-fixdev-browser-capabilitiespdorelease-0.6release-0.7release-0.8
Last change on this file since fde361e was fde361e, checked in by alecpl <alec@…>, 2 years ago
  • Replace preg_match() with faster strpos()
  • Property mode set to 100644
File size: 55.7 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, true);
871    $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
872  }
873
874  $out = $input->show($value);
875
876  return $out;
877}
878
879
880/**
881 * Replace all css definitions with #container [def]
882 * and remove css-inlined scripting
883 *
884 * @param string CSS source code
885 * @param string Container ID to use as prefix
886 * @return string Modified CSS source
887 */
888function rcmail_mod_css_styles($source, $container_id)
889  {
890  $last_pos = 0;
891  $replacements = new rcube_string_replacer;
892
893  // ignore the whole block if evil styles are detected
894  $stripped = preg_replace('/[^a-z\(:;]/', '', rcmail_xss_entity_decode($source));
895  if (preg_match('/expression|behavior|url\(|import[^a]/', $stripped))
896    return '/* evil! */';
897
898  // remove css comments (sometimes used for some ugly hacks)
899  $source = preg_replace('!/\*(.+)\*/!Ums', '', $source);
900
901  // cut out all contents between { and }
902  while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
903  {
904    $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
905    $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
906    $last_pos = $pos+2;
907  }
908
909  // remove html comments and add #container to each tag selector.
910  // also replace body definition because we also stripped off the <body> tag
911  $styles = preg_replace(
912    array(
913      '/(^\s*<!--)|(-->\s*$)/',
914      '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
915      '/'.preg_quote($container_id, '/').'\s+body/i',
916    ),
917    array(
918      '',
919      "\\1#$container_id \\2",
920      $container_id,
921    ),
922    $source);
923
924  // put block contents back in
925  $styles = $replacements->resolve($styles);
926
927  return $styles;
928  }
929
930
931/**
932 * Decode escaped entities used by known XSS exploits.
933 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
934 *
935 * @param string CSS content to decode
936 * @return string Decoded string
937 */
938function rcmail_xss_entity_decode($content)
939{
940  $out = html_entity_decode(html_entity_decode($content));
941  $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
942  $out = preg_replace('#/\*.*\*/#Um', '', $out);
943  return $out;
944}
945
946
947/**
948 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
949 *
950 * @param array matches result from preg_replace_callback
951 * @return string decoded entity
952 */
953function rcmail_xss_entity_decode_callback($matches)
954{
955  return chr(hexdec($matches[1]));
956}
957
958/**
959 * Compose a valid attribute string for HTML tags
960 *
961 * @param array Named tag attributes
962 * @param array List of allowed attributes
963 * @return string HTML formatted attribute string
964 */
965function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
966  {
967  // allow the following attributes to be added to the <iframe> tag
968  $attrib_str = '';
969  foreach ($allowed_attribs as $a)
970    if (isset($attrib[$a]))
971      $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
972
973  return $attrib_str;
974  }
975
976
977/**
978 * Convert a HTML attribute string attributes to an associative array (name => value)
979 *
980 * @param string Input string
981 * @return array Key-value pairs of parsed attributes
982 */
983function parse_attrib_string($str)
984  {
985  $attrib = array();
986  preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
987
988  // convert attributes to an associative array (name => value)
989  if ($regs) {
990    foreach ($regs as $attr) {
991      $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
992    }
993  }
994
995  return $attrib;
996  }
997
998
999/**
1000 * Improved equivalent to strtotime()
1001 *
1002 * @param string Date string
1003 * @return int
1004 */
1005function rcube_strtotime($date)
1006{
1007  // check for MS Outlook vCard date format YYYYMMDD
1008  if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
1009    return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
1010  }
1011  else if (is_numeric($date))
1012    return $date;
1013
1014  // support non-standard "GMTXXXX" literal
1015  $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
1016
1017  // if date parsing fails, we have a date in non-rfc format.
1018  // remove token from the end and try again
1019  while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
1020    $d = explode(' ', $date);
1021    array_pop($d);
1022    if (!$d) break;
1023    $date = implode(' ', $d);
1024  }
1025
1026  return $ts;
1027}
1028
1029
1030/**
1031 * Convert the given date to a human readable form
1032 * This uses the date formatting properties from config
1033 *
1034 * @param mixed Date representation (string or timestamp)
1035 * @param string Date format to use
1036 * @return string Formatted date string
1037 */
1038function format_date($date, $format=NULL)
1039{
1040  global $CONFIG;
1041 
1042  $ts = NULL;
1043
1044  if (!empty($date))
1045    $ts = rcube_strtotime($date);
1046
1047  if (empty($ts))
1048    return '';
1049
1050  // get user's timezone
1051  if ($CONFIG['timezone'] === 'auto')
1052    $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
1053  else {
1054    $tz = $CONFIG['timezone'];
1055    if ($CONFIG['dst_active'])
1056      $tz++;
1057  }
1058
1059  // convert time to user's timezone
1060  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
1061
1062  // get current timestamp in user's timezone
1063  $now = time();  // local time
1064  $now -= (int)date('Z'); // make GMT time
1065  $now += ($tz * 3600); // user's time
1066  $now_date = getdate($now);
1067
1068  $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1069  $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
1070
1071  // define date format depending on current time
1072  if (!$format) {
1073    if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
1074      $format = $CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i';
1075      $today  = true;
1076    }
1077    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1078      $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
1079    else
1080      $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
1081  }
1082
1083  // strftime() format
1084  if (preg_match('/%[a-z]+/i', $format)) {
1085    $format = strftime($format, $timestamp);
1086    return $today ? (rcube_label('today') . ' ' . $format) : $format;
1087  }
1088
1089  // parse format string manually in order to provide localized weekday and month names
1090  // an alternative would be to convert the date() format string to fit with strftime()
1091  $out = '';
1092  for($i=0; $i<strlen($format); $i++) {
1093    if ($format[$i]=='\\')  // skip escape chars
1094      continue;
1095
1096    // write char "as-is"
1097    if ($format[$i]==' ' || $format{$i-1}=='\\')
1098      $out .= $format[$i];
1099    // weekday (short)
1100    else if ($format[$i]=='D')
1101      $out .= rcube_label(strtolower(date('D', $timestamp)));
1102    // weekday long
1103    else if ($format[$i]=='l')
1104      $out .= rcube_label(strtolower(date('l', $timestamp)));
1105    // month name (short)
1106    else if ($format[$i]=='M')
1107      $out .= rcube_label(strtolower(date('M', $timestamp)));
1108    // month name (long)
1109    else if ($format[$i]=='F')
1110      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1111    else if ($format[$i]=='x')
1112      $out .= strftime('%x %X', $timestamp);
1113    else
1114      $out .= date($format[$i], $timestamp);
1115  }
1116
1117  if ($today) {
1118    $label = rcube_label('today');
1119    // replcae $ character with "Today" label (#1486120)
1120    if (strpos($out, '$') !== false) {
1121      $out = preg_replace('/\$/', $label, $out, 1);
1122    }
1123    else {
1124      $out = $label . ' ' . $out;
1125    }
1126  }
1127
1128  return $out;
1129}
1130
1131
1132/**
1133 * Compose a valid representation of name and e-mail address
1134 *
1135 * @param string E-mail address
1136 * @param string Person name
1137 * @return string Formatted string
1138 */
1139function format_email_recipient($email, $name='')
1140{
1141  if ($name && $name != $email) {
1142    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1143    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1144  }
1145
1146  return trim($email);
1147}
1148
1149
1150/**
1151 * Return the mailboxlist in HTML
1152 *
1153 * @param array Named parameters
1154 * @return string HTML code for the gui object
1155 */
1156function rcmail_mailbox_list($attrib)
1157{
1158  global $RCMAIL;
1159  static $a_mailboxes;
1160
1161  $attrib += array('maxlength' => 100, 'realnames' => false);
1162
1163  // add some labels to client
1164  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1165
1166  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1167  unset($attrib['type']);
1168
1169  if ($type=='ul' && !$attrib['id'])
1170    $attrib['id'] = 'rcmboxlist';
1171
1172  if (empty($attrib['folder_name']))
1173    $attrib['folder_name'] = '*';
1174
1175  // get mailbox list
1176  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1177
1178  // build the folders tree
1179  if (empty($a_mailboxes)) {
1180    // get mailbox list
1181    $a_folders = $RCMAIL->imap->list_mailboxes('', $attrib['folder_name'], $attrib['folder_filter']);
1182    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1183    $a_mailboxes = array();
1184
1185    foreach ($a_folders as $folder)
1186      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1187  }
1188
1189  // allow plugins to alter the folder tree or to localize folder names
1190  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1191
1192  if ($type == 'select') {
1193    $select = new html_select($attrib);
1194
1195    // add no-selection option
1196    if ($attrib['noselection'])
1197      $select->add(rcube_label($attrib['noselection']), '');
1198
1199    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1200    $out = $select->show();
1201  }
1202  else {
1203    $js_mailboxlist = array();
1204    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1205
1206    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1207    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1208    $RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders'));
1209  }
1210
1211  return $out;
1212}
1213
1214
1215/**
1216 * Return the mailboxlist as html_select object
1217 *
1218 * @param array Named parameters
1219 * @return html_select HTML drop-down object
1220 */
1221function rcmail_mailbox_select($p = array())
1222{
1223  global $RCMAIL;
1224
1225  $p += array('maxlength' => 100, 'realnames' => false);
1226  $a_mailboxes = array();
1227
1228  if (empty($p['folder_name']))
1229    $p['folder_name'] = '*';
1230
1231  if ($p['unsubscribed'])
1232    $list = $RCMAIL->imap->list_unsubscribed('', $p['folder_name'], $p['folder_filter']);
1233  else
1234    $list = $RCMAIL->imap->list_mailboxes('', $p['folder_name'], $p['folder_filter']);
1235
1236  $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1237
1238  foreach ($list as $folder) {
1239    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1240      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1241  }
1242
1243  $select = new html_select($p);
1244
1245  if ($p['noselection'])
1246    $select->add($p['noselection'], '');
1247
1248  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1249
1250  return $select;
1251}
1252
1253
1254/**
1255 * Create a hierarchical array of the mailbox list
1256 * @access private
1257 * @return void
1258 */
1259function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1260{
1261  global $RCMAIL;
1262
1263  // Handle namespace prefix
1264  $prefix = '';
1265  if (!$path) {
1266    $n_folder = $folder;
1267    $folder = $RCMAIL->imap->mod_mailbox($folder);
1268
1269    if ($n_folder != $folder) {
1270      $prefix = substr($n_folder, 0, -strlen($folder));
1271    }
1272  }
1273
1274  $pos = strpos($folder, $delm);
1275
1276  if ($pos !== false) {
1277    $subFolders = substr($folder, $pos+1);
1278    $currentFolder = substr($folder, 0, $pos);
1279
1280    // sometimes folder has a delimiter as the last character
1281    if (!strlen($subFolders))
1282      $virtual = false;
1283    else if (!isset($arrFolders[$currentFolder]))
1284      $virtual = true;
1285    else
1286      $virtual = $arrFolders[$currentFolder]['virtual'];
1287  }
1288  else {
1289    $subFolders = false;
1290    $currentFolder = $folder;
1291    $virtual = false;
1292  }
1293
1294  $path .= $prefix.$currentFolder;
1295
1296  if (!isset($arrFolders[$currentFolder])) {
1297    // Check \Noselect option (if options are in cache)
1298    if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
1299      $virtual = in_array('\\Noselect', $opts);
1300    }
1301
1302    $arrFolders[$currentFolder] = array(
1303      'id' => $path,
1304      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1305      'virtual' => $virtual,
1306      'folders' => array());
1307  }
1308  else
1309    $arrFolders[$currentFolder]['virtual'] = $virtual;
1310
1311  if (strlen($subFolders))
1312    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1313}
1314
1315
1316/**
1317 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1318 * @access private
1319 * @return string
1320 */
1321function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1322{
1323  global $RCMAIL, $CONFIG;
1324
1325  $maxlength = intval($attrib['maxlength']);
1326  $realnames = (bool)$attrib['realnames'];
1327  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1328
1329  $idx = 0;
1330  $out = '';
1331  foreach ($arrFolders as $key => $folder) {
1332    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1333    $title = null;
1334
1335    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1336      $foldername = rcube_label($folder_class);
1337    }
1338    else {
1339      $foldername = $folder['name'];
1340
1341      // shorten the folder name to a given length
1342      if ($maxlength && $maxlength > 1) {
1343        $fname = abbreviate_string($foldername, $maxlength);
1344        if ($fname != $foldername)
1345          $title = $foldername;
1346        $foldername = $fname;
1347      }
1348    }
1349
1350    // make folder name safe for ids and class names
1351    $folder_id = html_identifier($folder['id']);
1352    $classes = array('mailbox');
1353
1354    // set special class for Sent, Drafts, Trash and Junk
1355    if ($folder['id'] == $CONFIG['sent_mbox'])
1356      $classes[] = 'sent';
1357    else if ($folder['id'] == $CONFIG['drafts_mbox'])
1358      $classes[] = 'drafts';
1359    else if ($folder['id'] == $CONFIG['trash_mbox'])
1360      $classes[] = 'trash';
1361    else if ($folder['id'] == $CONFIG['junk_mbox'])
1362      $classes[] = 'junk';
1363    else if ($folder['id'] == 'INBOX')
1364      $classes[] = 'inbox';
1365    else
1366      $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1367
1368    $classes[] = $zebra_class;
1369
1370    if ($folder['id'] == $mbox_name)
1371      $classes[] = 'selected';
1372
1373    $collapsed = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false;
1374    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1375
1376    if ($folder['virtual'])
1377      $classes[] = 'virtual';
1378    else if ($unread)
1379      $classes[] = 'unread';
1380
1381    $js_name = JQ($folder['id']);
1382    $html_name = Q($foldername) . ($unread ? html::span('unreadcount', " ($unread)") : '');
1383    $link_attrib = $folder['virtual'] ? array() : array(
1384      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1385      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1386      'rel' => $folder['id'],
1387      'title' => $title,
1388    );
1389
1390    $out .= html::tag('li', array(
1391        'id' => "rcmli".$folder_id,
1392        'class' => join(' ', $classes),
1393        'noclose' => true),
1394      html::a($link_attrib, $html_name) .
1395      (!empty($folder['folders']) ? html::div(array(
1396        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1397        'style' => "position:absolute",
1398        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1399      ), '&nbsp;') : ''));
1400
1401    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1402
1403    if (!empty($folder['folders'])) {
1404      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1405        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1406    }
1407
1408    $out .= "</li>\n";
1409    $idx++;
1410  }
1411
1412  return $out;
1413}
1414
1415
1416/**
1417 * Return html for a flat list <select> for the mailbox tree
1418 * @access private
1419 * @return string
1420 */
1421function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1422{
1423  $out = '';
1424
1425  foreach ($arrFolders as $key=>$folder) {
1426    if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1427      $foldername = rcube_label($folder_class);
1428    else {
1429      $foldername = $folder['name'];
1430
1431      // shorten the folder name to a given length
1432      if ($maxlength && $maxlength>1)
1433        $foldername = abbreviate_string($foldername, $maxlength);
1434    }
1435
1436    $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1437
1438    if (!empty($folder['folders']))
1439      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1440  }
1441
1442  return $out;
1443}
1444
1445
1446/**
1447 * Return internal name for the given folder if it matches the configured special folders
1448 * @access private
1449 * @return string
1450 */
1451function rcmail_folder_classname($folder_id)
1452{
1453  global $CONFIG;
1454
1455  if ($folder_id == 'INBOX')
1456    return 'inbox';
1457
1458  // for these mailboxes we have localized labels and css classes
1459  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1460  {
1461    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1462      return $smbx;
1463  }
1464}
1465
1466
1467/**
1468 * Try to localize the given IMAP folder name.
1469 * UTF-7 decode it in case no localized text was found
1470 *
1471 * @param string Folder name
1472 * @return string Localized folder name in UTF-8 encoding
1473 */
1474function rcmail_localize_foldername($name)
1475{
1476  if ($folder_class = rcmail_folder_classname($name))
1477    return rcube_label($folder_class);
1478  else
1479    return rcube_charset_convert($name, 'UTF7-IMAP');
1480}
1481
1482
1483function rcmail_quota_display($attrib)
1484{
1485  global $OUTPUT;
1486
1487  if (!$attrib['id'])
1488    $attrib['id'] = 'rcmquotadisplay';
1489
1490  if(isset($attrib['display']))
1491    $_SESSION['quota_display'] = $attrib['display'];
1492
1493  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1494
1495  $quota = rcmail_quota_content($attrib);
1496
1497  $OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready');
1498
1499  return html::span($attrib, '');
1500}
1501
1502
1503function rcmail_quota_content($attrib=NULL)
1504{
1505  global $RCMAIL;
1506
1507  $quota = $RCMAIL->imap->get_quota();
1508  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1509
1510  $quota_result = (array) $quota;
1511  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1512
1513  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1514    $quota_result['title'] = rcube_label('unlimited');
1515    $quota_result['percent'] = 0;
1516  }
1517  else if ($quota['total']) {
1518    if (!isset($quota['percent']))
1519      $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1520
1521    $title = sprintf('%s / %s (%.0f%%)',
1522        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1523        $quota_result['percent']);
1524
1525    $quota_result['title'] = $title;
1526
1527    if ($attrib['width'])
1528      $quota_result['width'] = $attrib['width'];
1529    if ($attrib['height'])
1530      $quota_result['height']   = $attrib['height'];
1531  }
1532  else {
1533    $quota_result['title'] = rcube_label('unknown');
1534    $quota_result['percent'] = 0;
1535  }
1536
1537  return $quota_result;
1538}
1539
1540
1541/**
1542 * Outputs error message according to server error/response codes
1543 *
1544 * @param string Fallback message label
1545 * @param string Fallback message label arguments
1546 *
1547 * @return void
1548 */
1549function rcmail_display_server_error($fallback=null, $fallback_args=null)
1550{
1551    global $RCMAIL;
1552
1553    $err_code = $RCMAIL->imap->get_error_code();
1554    $res_code = $RCMAIL->imap->get_response_code();
1555
1556    if ($res_code == rcube_imap::NOPERM) {
1557        $RCMAIL->output->show_message('errornoperm', 'error');
1558    }
1559    else if ($res_code == rcube_imap::READONLY) {
1560        $RCMAIL->output->show_message('errorreadonly', 'error');
1561    }
1562    else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1563        $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1564    }
1565    else if ($fallback) {
1566        $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1567    }
1568
1569    return true;
1570}
1571
1572
1573/**
1574 * Output HTML editor scripts
1575 *
1576 * @param string Editor mode
1577 * @return void
1578 */
1579function rcube_html_editor($mode='')
1580{
1581  global $RCMAIL, $CONFIG;
1582
1583  $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
1584
1585  if ($hook['abort'])
1586    return; 
1587
1588  $lang = strtolower($_SESSION['language']);
1589
1590  // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1591  $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1592
1593  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1594    $lang = 'en';
1595
1596  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1597  $RCMAIL->output->include_script('editor.js');
1598  $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
1599    JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
1600    'foot');
1601}
1602
1603
1604/**
1605 * Replaces TinyMCE's emoticon images with plain-text representation
1606 *
1607 * @param string HTML content
1608 * @return string HTML content
1609 */
1610function rcmail_replace_emoticons($html)
1611{
1612  $emoticons = array(
1613    '8-)' => 'smiley-cool',
1614    ':-#' => 'smiley-foot-in-mouth',
1615    ':-*' => 'smiley-kiss',
1616    ':-X' => 'smiley-sealed',
1617    ':-P' => 'smiley-tongue-out',
1618    ':-@' => 'smiley-yell',
1619    ":'(" => 'smiley-cry',
1620    ':-(' => 'smiley-frown',
1621    ':-D' => 'smiley-laughing',
1622    ':-)' => 'smiley-smile',
1623    ':-S' => 'smiley-undecided',
1624    ':-$' => 'smiley-embarassed',
1625    'O:-)' => 'smiley-innocent',
1626    ':-|' => 'smiley-money-mouth',
1627    ':-O' => 'smiley-surprised',
1628    ';-)' => 'smiley-wink',
1629  );
1630
1631  foreach ($emoticons as $idx => $file) {
1632    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1633    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1634    $replace[] = $idx;
1635  }
1636
1637  return preg_replace($search, $replace, $html);
1638}
1639
1640
1641/**
1642 * Check if working in SSL mode
1643 *
1644 * @param integer HTTPS port number
1645 * @param boolean Enables 'use_https' option checking
1646 * @return boolean
1647 */
1648function rcube_https_check($port=null, $use_https=true)
1649{
1650  global $RCMAIL;
1651
1652  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1653    return true;
1654  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1655    return true;
1656  if ($port && $_SERVER['SERVER_PORT'] == $port)
1657    return true;
1658  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1659    return true;
1660
1661  return false;
1662}
1663
1664
1665/**
1666 * For backward compatibility.
1667 *
1668 * @global rcmail $RCMAIL
1669 * @param string $var_name Variable name.
1670 * @return void
1671 */
1672function rcube_sess_unset($var_name=null)
1673{
1674  global $RCMAIL;
1675
1676  $RCMAIL->session->remove($var_name);
1677}
1678
1679
1680/**
1681 * Replaces hostname variables
1682 *
1683 * @param string $name Hostname
1684 * @param string $host Optional IMAP hostname
1685 * @return string
1686 */
1687function rcube_parse_host($name, $host='')
1688{
1689  // %n - host
1690  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1691  // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
1692  $d = preg_replace('/^[^\.]+\./', '', $n);
1693  // %h - IMAP host
1694  $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
1695  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1696  $z = preg_replace('/^[^\.]+\./', '', $h);
1697  // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
1698  if ( strpos($name, '%s') !== false ){
1699    $user_email = rcube_idn_convert(get_input_value('_user', RCUBE_INPUT_POST), true);
1700    if ( preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s) < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false )
1701      return false;
1702  }
1703
1704  $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
1705  return $name;
1706}
1707
1708
1709/**
1710 * E-mail address validation
1711 *
1712 * @param string $email Email address
1713 * @param boolean $dns_check True to check dns
1714 * @return boolean
1715 */
1716function check_email($email, $dns_check=true)
1717{
1718  // Check for invalid characters
1719  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1720    return false;
1721
1722  // Check for length limit specified by RFC 5321 (#1486453)
1723  if (strlen($email) > 254)
1724    return false;
1725
1726  $email_array = explode('@', $email);
1727
1728  // Check that there's one @ symbol
1729  if (count($email_array) < 2)
1730    return false;
1731
1732  $domain_part = array_pop($email_array);
1733  $local_part = implode('@', $email_array);
1734
1735  // from PEAR::Validate
1736  $regexp = '&^(?:
1737        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1738        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
1739        $&xi';
1740
1741  if (!preg_match($regexp, $local_part))
1742    return false;
1743
1744  // Check domain part
1745  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))
1746    return true; // IP address
1747  else {
1748    // If not an IP address
1749    $domain_array = explode('.', $domain_part);
1750    if (sizeof($domain_array) < 2)
1751      return false; // Not enough parts to be a valid domain
1752
1753    foreach ($domain_array as $part)
1754      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1755        return false;
1756
1757    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1758      return true;
1759
1760    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1761      $lookup = array();
1762      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1763      foreach ($lookup as $line) {
1764        if (strpos($line, 'MX preference'))
1765          return true;
1766      }
1767      return false;
1768    }
1769
1770    // find MX record(s)
1771    if (getmxrr($domain_part, $mx_records))
1772      return true;
1773
1774    // find any DNS record
1775    if (checkdnsrr($domain_part, 'ANY'))
1776      return true;
1777  }
1778
1779  return false;
1780}
1781
1782/*
1783 * Idn_to_ascii wrapper.
1784 * Intl/Idn modules version of this function doesn't work with e-mail address
1785 */
1786function rcube_idn_to_ascii($str)
1787{
1788  return rcube_idn_convert($str, true);
1789}
1790
1791/*
1792 * Idn_to_ascii wrapper.
1793 * Intl/Idn modules version of this function doesn't work with e-mail address
1794 */
1795function rcube_idn_to_utf8($str)
1796{
1797  return rcube_idn_convert($str, false);
1798}
1799
1800function rcube_idn_convert($input, $is_utf=false)
1801{
1802  if ($at = strpos($input, '@')) {
1803    $user   = substr($input, 0, $at);
1804    $domain = substr($input, $at+1);
1805  }
1806  else {
1807    $domain = $input;
1808  }
1809
1810  $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
1811
1812  return $at ? $user . '@' . $domain : $domain;
1813}
1814
1815
1816/**
1817 * Helper class to turn relative urls into absolute ones
1818 * using a predefined base
1819 */
1820class rcube_base_replacer
1821{
1822  private $base_url;
1823
1824  public function __construct($base)
1825  {
1826    $this->base_url = $base;
1827  }
1828
1829  public function callback($matches)
1830  {
1831    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1832  }
1833}
1834
1835
1836/****** debugging and logging functions ********/
1837
1838/**
1839 * Print or write debug messages
1840 *
1841 * @param mixed Debug message or data
1842 * @return void
1843 */
1844function console()
1845{
1846    $args = func_get_args();
1847
1848    if (class_exists('rcmail', false)) {
1849        $rcmail = rcmail::get_instance();
1850        if (is_object($rcmail->plugins)) {
1851            $plugin = $rcmail->plugins->exec_hook('console', array('args' => $args));
1852            if ($plugin['abort'])
1853                return;
1854            $args = $plugin['args'];
1855        }
1856    }
1857
1858    $msg = array();
1859    foreach ($args as $arg)
1860        $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1861
1862    write_log('console', join(";\n", $msg));
1863}
1864
1865
1866/**
1867 * Append a line to a logfile in the logs directory.
1868 * Date will be added automatically to the line.
1869 *
1870 * @param $name name of log file
1871 * @param line Line to append
1872 * @return void
1873 */
1874function write_log($name, $line)
1875{
1876  global $CONFIG, $RCMAIL;
1877
1878  if (!is_string($line))
1879    $line = var_export($line, true);
1880 
1881  if (empty($CONFIG['log_date_format']))
1882    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1883 
1884  $date = date($CONFIG['log_date_format']);
1885 
1886  // trigger logging hook
1887  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1888    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1889    $name = $log['name'];
1890    $line = $log['line'];
1891    $date = $log['date'];
1892    if ($log['abort'])
1893      return true;
1894  }
1895 
1896  if ($CONFIG['log_driver'] == 'syslog') {
1897    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1898    syslog($prio, $line);
1899    return true;
1900  }
1901  else {
1902    $line = sprintf("[%s]: %s\n", $date, $line);
1903
1904    // log_driver == 'file' is assumed here
1905    if (empty($CONFIG['log_dir']))
1906      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1907
1908    // try to open specific log file for writing
1909    $logfile = $CONFIG['log_dir'].'/'.$name;
1910    if ($fp = @fopen($logfile, 'a')) {
1911      fwrite($fp, $line);
1912      fflush($fp);
1913      fclose($fp);
1914      return true;
1915    }
1916    else
1917      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1918  }
1919
1920  return false;
1921}
1922
1923
1924/**
1925 * Write login data (name, ID, IP address) to the 'userlogins' log file.
1926 *
1927 * @return void
1928 */
1929function rcmail_log_login()
1930{
1931  global $RCMAIL;
1932
1933  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1934    return;
1935
1936  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1937    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip()));
1938}
1939
1940
1941/**
1942 * Returns remote IP address and forwarded addresses if found
1943 *
1944 * @return string Remote IP address(es)
1945 */
1946function rcmail_remote_ip()
1947{
1948    $address = $_SERVER['REMOTE_ADDR'];
1949
1950    // append the NGINX X-Real-IP header, if set
1951    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1952        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1953    }
1954    // append the X-Forwarded-For header, if set
1955    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1956        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1957    }
1958
1959    if (!empty($remote_ip))
1960        $address .= '(' . implode(',', $remote_ip) . ')';
1961
1962    return $address;
1963}
1964
1965
1966/**
1967 * Check whether the HTTP referer matches the current request
1968 *
1969 * @return boolean True if referer is the same host+path, false if not
1970 */
1971function rcube_check_referer()
1972{
1973  $uri = parse_url($_SERVER['REQUEST_URI']);
1974  $referer = parse_url(rc_request_header('Referer'));
1975  return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
1976}
1977
1978
1979/**
1980 * @access private
1981 * @return mixed
1982 */
1983function rcube_timer()
1984{
1985  return microtime(true);
1986}
1987
1988
1989/**
1990 * @access private
1991 * @return void
1992 */
1993function rcube_print_time($timer, $label='Timer', $dest='console')
1994{
1995  static $print_count = 0;
1996
1997  $print_count++;
1998  $now = rcube_timer();
1999  $diff = $now-$timer;
2000
2001  if (empty($label))
2002    $label = 'Timer '.$print_count;
2003
2004  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
2005}
2006
2007
2008/**
2009 * Throw system error and show error page
2010 *
2011 * @param array Named parameters
2012 *  - code: Error code (required)
2013 *  - type: Error type [php|db|imap|javascript] (required)
2014 *  - message: Error message
2015 *  - file: File where error occured
2016 *  - line: Line where error occured
2017 * @param boolean True to log the error
2018 * @param boolean Terminate script execution
2019 */
2020// may be defined in Installer
2021if (!function_exists('raise_error')) {
2022function raise_error($arg=array(), $log=false, $terminate=false)
2023{
2024    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
2025
2026    // report bug (if not incompatible browser)
2027    if ($log && $arg['type'] && $arg['message'])
2028        rcube_log_bug($arg);
2029
2030    // display error page and terminate script
2031    if ($terminate) {
2032        $ERROR_CODE = $arg['code'];
2033        $ERROR_MESSAGE = $arg['message'];
2034        include INSTALL_PATH . 'program/steps/utils/error.inc';
2035        exit;
2036    }
2037}
2038}
2039
2040
2041/**
2042 * Report error according to configured debug_level
2043 *
2044 * @param array Named parameters
2045 * @return void
2046 * @see raise_error()
2047 */
2048function rcube_log_bug($arg_arr)
2049{
2050    global $CONFIG;
2051
2052    $program = strtoupper($arg_arr['type']);
2053    $level   = $CONFIG['debug_level'];
2054
2055    // disable errors for ajax requests, write to log instead (#1487831)
2056    if (($level & 4) && !empty($_REQUEST['_remote'])) {
2057        $level = ($level ^ 4) | 1;
2058    }
2059
2060    // write error to local log file
2061    if ($level & 1) {
2062        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
2063        $log_entry = sprintf("%s Error: %s%s (%s %s)",
2064            $program,
2065            $arg_arr['message'],
2066            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
2067            $_SERVER['REQUEST_METHOD'],
2068            $_SERVER['REQUEST_URI'] . $post_query);
2069
2070        if (!write_log('errors', $log_entry)) {
2071            // send error to PHPs error handler if write_log didn't succeed
2072            trigger_error($arg_arr['message']);
2073        }
2074    }
2075
2076    // report the bug to the global bug reporting system
2077    if ($level & 2) {
2078        // TODO: Send error via HTTP
2079    }
2080
2081    // show error if debug_mode is on
2082    if ($level & 4) {
2083        print "<b>$program Error";
2084
2085        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2086            print " in $arg_arr[file] ($arg_arr[line])";
2087
2088        print ':</b>&nbsp;';
2089        print nl2br($arg_arr['message']);
2090        print '<br />';
2091        flush();
2092    }
2093}
2094
Note: See TracBrowser for help on using the repository browser.