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

Last change on this file since 4977 was 4977, checked in by thomasb, 22 months ago

Move mail sending functions from mail task to core for general usage

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