source: github/program/include/main.inc @ 9d5d7a8

HEADcourier-fixdev-browser-capabilitiespdorelease-0.7release-0.8
Last change on this file since 9d5d7a8 was 9d5d7a8, checked in by thomascube <thomas@…>, 21 months ago

Log session_id with logins

  • Property mode set to 100644
File size: 64.8 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'], 0, $p['exceptions']);
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, $exceptions=array())
1406{
1407  $out = '';
1408
1409  foreach ($arrFolders as $key => $folder) {
1410    if (empty($exceptions) || !in_array($folder['id'], $exceptions)) {
1411      if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1412        $foldername = rcube_label($folder_class);
1413      else {
1414        $foldername = $folder['name'];
1415
1416        // shorten the folder name to a given length
1417        if ($maxlength && $maxlength>1)
1418          $foldername = abbreviate_string($foldername, $maxlength);
1419      }
1420
1421      $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1422    }
1423    else if ($nestLevel)
1424      continue;
1425
1426    if (!empty($folder['folders']))
1427      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
1428        $select, $realnames, $nestLevel+1, $exceptions);
1429  }
1430
1431  return $out;
1432}
1433
1434
1435/**
1436 * Return internal name for the given folder if it matches the configured special folders
1437 * @access private
1438 * @return string
1439 */
1440function rcmail_folder_classname($folder_id)
1441{
1442  global $CONFIG;
1443
1444  if ($folder_id == 'INBOX')
1445    return 'inbox';
1446
1447  // for these mailboxes we have localized labels and css classes
1448  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1449  {
1450    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1451      return $smbx;
1452  }
1453}
1454
1455
1456/**
1457 * Try to localize the given IMAP folder name.
1458 * UTF-7 decode it in case no localized text was found
1459 *
1460 * @param string Folder name
1461 * @return string Localized folder name in UTF-8 encoding
1462 */
1463function rcmail_localize_foldername($name)
1464{
1465  if ($folder_class = rcmail_folder_classname($name))
1466    return rcube_label($folder_class);
1467  else
1468    return rcube_charset_convert($name, 'UTF7-IMAP');
1469}
1470
1471
1472function rcmail_localize_folderpath($path)
1473{
1474    global $RCMAIL;
1475
1476    $protect_folders = $RCMAIL->config->get('protect_default_folders');
1477    $default_folders = (array) $RCMAIL->config->get('default_imap_folders');
1478    $delimiter       = $RCMAIL->imap->get_hierarchy_delimiter();
1479    $path            = explode($delimiter, $path);
1480    $result          = array();
1481
1482    foreach ($path as $idx => $dir) {
1483        $directory = implode($delimiter, array_slice($path, 0, $idx+1));
1484        if ($protect_folders && in_array($directory, $default_folders)) {
1485            unset($result);
1486            $result[] = rcmail_localize_foldername($directory);
1487        }
1488        else {
1489            $result[] = rcube_charset_convert($dir, 'UTF7-IMAP');
1490        }
1491    }
1492
1493    return implode($delimiter, $result);
1494}
1495
1496
1497function rcmail_quota_display($attrib)
1498{
1499  global $OUTPUT;
1500
1501  if (!$attrib['id'])
1502    $attrib['id'] = 'rcmquotadisplay';
1503
1504  if(isset($attrib['display']))
1505    $_SESSION['quota_display'] = $attrib['display'];
1506
1507  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1508
1509  $quota = rcmail_quota_content($attrib);
1510
1511  $OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready');
1512
1513  return html::span($attrib, '');
1514}
1515
1516
1517function rcmail_quota_content($attrib=NULL)
1518{
1519  global $RCMAIL;
1520
1521  $quota = $RCMAIL->imap->get_quota();
1522  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1523
1524  $quota_result = (array) $quota;
1525  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1526
1527  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1528    $quota_result['title'] = rcube_label('unlimited');
1529    $quota_result['percent'] = 0;
1530  }
1531  else if ($quota['total']) {
1532    if (!isset($quota['percent']))
1533      $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1534
1535    $title = sprintf('%s / %s (%.0f%%)',
1536        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1537        $quota_result['percent']);
1538
1539    $quota_result['title'] = $title;
1540
1541    if ($attrib['width'])
1542      $quota_result['width'] = $attrib['width'];
1543    if ($attrib['height'])
1544      $quota_result['height']   = $attrib['height'];
1545  }
1546  else {
1547    $quota_result['title'] = rcube_label('unknown');
1548    $quota_result['percent'] = 0;
1549  }
1550
1551  return $quota_result;
1552}
1553
1554
1555/**
1556 * Outputs error message according to server error/response codes
1557 *
1558 * @param string Fallback message label
1559 * @param string Fallback message label arguments
1560 *
1561 * @return void
1562 */
1563function rcmail_display_server_error($fallback=null, $fallback_args=null)
1564{
1565    global $RCMAIL;
1566
1567    $err_code = $RCMAIL->imap->get_error_code();
1568    $res_code = $RCMAIL->imap->get_response_code();
1569
1570    if ($res_code == rcube_imap::NOPERM) {
1571        $RCMAIL->output->show_message('errornoperm', 'error');
1572    }
1573    else if ($res_code == rcube_imap::READONLY) {
1574        $RCMAIL->output->show_message('errorreadonly', 'error');
1575    }
1576    else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1577        $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1578    }
1579    else if ($fallback) {
1580        $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1581    }
1582
1583    return true;
1584}
1585
1586
1587/**
1588 * Output HTML editor scripts
1589 *
1590 * @param string Editor mode
1591 * @return void
1592 */
1593function rcube_html_editor($mode='')
1594{
1595  global $RCMAIL, $CONFIG;
1596
1597  $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
1598
1599  if ($hook['abort'])
1600    return; 
1601
1602  $lang = strtolower($_SESSION['language']);
1603
1604  // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1605  $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1606
1607  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1608    $lang = 'en';
1609
1610  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1611  $RCMAIL->output->include_script('editor.js');
1612  $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
1613    JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
1614    'foot');
1615}
1616
1617
1618/**
1619 * Replaces TinyMCE's emoticon images with plain-text representation
1620 *
1621 * @param string HTML content
1622 * @return string HTML content
1623 */
1624function rcmail_replace_emoticons($html)
1625{
1626  $emoticons = array(
1627    '8-)' => 'smiley-cool',
1628    ':-#' => 'smiley-foot-in-mouth',
1629    ':-*' => 'smiley-kiss',
1630    ':-X' => 'smiley-sealed',
1631    ':-P' => 'smiley-tongue-out',
1632    ':-@' => 'smiley-yell',
1633    ":'(" => 'smiley-cry',
1634    ':-(' => 'smiley-frown',
1635    ':-D' => 'smiley-laughing',
1636    ':-)' => 'smiley-smile',
1637    ':-S' => 'smiley-undecided',
1638    ':-$' => 'smiley-embarassed',
1639    'O:-)' => 'smiley-innocent',
1640    ':-|' => 'smiley-money-mouth',
1641    ':-O' => 'smiley-surprised',
1642    ';-)' => 'smiley-wink',
1643  );
1644
1645  foreach ($emoticons as $idx => $file) {
1646    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1647    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1648    $replace[] = $idx;
1649  }
1650
1651  return preg_replace($search, $replace, $html);
1652}
1653
1654
1655/**
1656 * Send the given message using the configured method
1657 *
1658 * @param object $message    Reference to Mail_MIME object
1659 * @param string $from       Sender address string
1660 * @param array  $mailto     Array of recipient address strings
1661 * @param array  $smtp_error SMTP error array (reference)
1662 * @param string $body_file  Location of file with saved message body (reference)
1663 * @param array  $smtp_opts  SMTP options (e.g. DSN request)
1664 *
1665 * @return boolean Send status.
1666 */
1667function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file, $smtp_opts=null)
1668{
1669  global $CONFIG, $RCMAIL;
1670
1671  $headers = $message->headers();
1672
1673  // send thru SMTP server using custom SMTP library
1674  if ($CONFIG['smtp_server']) {
1675    // generate list of recipients
1676    $a_recipients = array($mailto);
1677
1678    if (strlen($headers['Cc']))
1679      $a_recipients[] = $headers['Cc'];
1680    if (strlen($headers['Bcc']))
1681      $a_recipients[] = $headers['Bcc'];
1682
1683    // clean Bcc from header for recipients
1684    $send_headers = $headers;
1685    unset($send_headers['Bcc']);
1686    // here too, it because txtHeaders() below use $message->_headers not only $send_headers
1687    unset($message->_headers['Bcc']);
1688
1689    $smtp_headers = $message->txtHeaders($send_headers, true);
1690
1691    if ($message->getParam('delay_file_io')) {
1692      // use common temp dir
1693      $temp_dir = $RCMAIL->config->get('temp_dir');
1694      $body_file = tempnam($temp_dir, 'rcmMsg');
1695      if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
1696        raise_error(array('code' => 650, 'type' => 'php',
1697            'file' => __FILE__, 'line' => __LINE__,
1698            'message' => "Could not create message: ".$mime_result->getMessage()),
1699            TRUE, FALSE);
1700        return false;
1701      }
1702      $msg_body = fopen($body_file, 'r');
1703    } else {
1704      $msg_body = $message->get();
1705    }
1706
1707    // send message
1708    if (!is_object($RCMAIL->smtp))
1709      $RCMAIL->smtp_init(true);
1710
1711    $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
1712    $smtp_response = $RCMAIL->smtp->get_response();
1713    $smtp_error = $RCMAIL->smtp->get_error();
1714
1715    // log error
1716    if (!$sent)
1717      raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__,
1718                        'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
1719  }
1720  // send mail using PHP's mail() function
1721  else {
1722    // unset some headers because they will be added by the mail() function
1723    $headers_enc = $message->headers($headers);
1724    $headers_php = $message->_headers;
1725    unset($headers_php['To'], $headers_php['Subject']);
1726
1727    // reset stored headers and overwrite
1728    $message->_headers = array();
1729    $header_str = $message->txtHeaders($headers_php);
1730
1731    // #1485779
1732    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1733      if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
1734        $headers_enc['To'] = implode(', ', $m[1]);
1735      }
1736    }
1737
1738    $msg_body = $message->get();
1739
1740    if (PEAR::isError($msg_body))
1741      raise_error(array('code' => 650, 'type' => 'php',
1742            'file' => __FILE__, 'line' => __LINE__,
1743            'message' => "Could not create message: ".$msg_body->getMessage()),
1744            TRUE, FALSE);
1745    else {
1746      $delim   = $RCMAIL->config->header_delimiter();
1747      $to      = $headers_enc['To'];
1748      $subject = $headers_enc['Subject'];
1749      $header_str = rtrim($header_str);
1750
1751      if ($delim != "\r\n") {
1752        $header_str = str_replace("\r\n", $delim, $header_str);
1753        $msg_body   = str_replace("\r\n", $delim, $msg_body);
1754        $to         = str_replace("\r\n", $delim, $to);
1755        $subject    = str_replace("\r\n", $delim, $subject);
1756      }
1757
1758      if (ini_get('safe_mode'))
1759        $sent = mail($to, $subject, $msg_body, $header_str);
1760      else
1761        $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
1762    }
1763  }
1764
1765  if ($sent) {
1766    $RCMAIL->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
1767
1768    // remove MDN headers after sending
1769    unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
1770
1771    // get all recipients
1772    if ($headers['Cc'])
1773      $mailto .= $headers['Cc'];
1774    if ($headers['Bcc'])
1775      $mailto .= $headers['Bcc'];
1776    if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
1777      $mailto = implode(', ', array_unique($m[1]));
1778
1779    if ($CONFIG['smtp_log']) {
1780      write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
1781        $RCMAIL->user->get_username(),
1782        $_SERVER['REMOTE_ADDR'],
1783        $mailto,
1784        !empty($smtp_response) ? join('; ', $smtp_response) : ''));
1785    }
1786  }
1787
1788  if (is_resource($msg_body)) {
1789    fclose($msg_body);
1790  }
1791
1792  $message->_headers = array();
1793  $message->headers($headers);
1794
1795  return $sent;
1796}
1797
1798
1799// Returns unique Message-ID
1800function rcmail_gen_message_id()
1801{
1802  global $RCMAIL;
1803
1804  $local_part  = md5(uniqid('rcmail'.mt_rand(),true));
1805  $domain_part = $RCMAIL->user->get_username('domain');
1806
1807  // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
1808  if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
1809    if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']))
1810      && preg_match('/\.[a-z]+$/i', $host)) {
1811        $domain_part = $host;
1812    }
1813    else if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['SERVER_NAME']))
1814      && preg_match('/\.[a-z]+$/i', $host)) {
1815        $domain_part = $host;
1816    }
1817  }
1818
1819  return sprintf('<%s@%s>', $local_part, $domain_part);
1820}
1821
1822
1823// Returns RFC2822 formatted current date in user's timezone
1824function rcmail_user_date()
1825{
1826  global $CONFIG;
1827
1828  // get user's timezone
1829  if ($CONFIG['timezone'] === 'auto') {
1830    $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
1831  }
1832  else {
1833    $tz = $CONFIG['timezone'];
1834    if ($CONFIG['dst_active'])
1835      $tz++;
1836  }
1837
1838  $date = time() + $tz * 60 * 60;
1839  $date = gmdate('r', $date);
1840  $tz   = sprintf('%+05d', intval($tz) * 100 + ($tz - intval($tz)) * 60);
1841  $date = preg_replace('/[+-][0-9]{4}$/', $tz, $date);
1842
1843  return $date;
1844}
1845
1846
1847/**
1848 * Check if working in SSL mode
1849 *
1850 * @param integer HTTPS port number
1851 * @param boolean Enables 'use_https' option checking
1852 * @return boolean
1853 */
1854function rcube_https_check($port=null, $use_https=true)
1855{
1856  global $RCMAIL;
1857
1858  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1859    return true;
1860  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1861    return true;
1862  if ($port && $_SERVER['SERVER_PORT'] == $port)
1863    return true;
1864  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1865    return true;
1866
1867  return false;
1868}
1869
1870
1871/**
1872 * For backward compatibility.
1873 *
1874 * @global rcmail $RCMAIL
1875 * @param string $var_name Variable name.
1876 * @return void
1877 */
1878function rcube_sess_unset($var_name=null)
1879{
1880  global $RCMAIL;
1881
1882  $RCMAIL->session->remove($var_name);
1883}
1884
1885
1886/**
1887 * Replaces hostname variables
1888 *
1889 * @param string $name Hostname
1890 * @param string $host Optional IMAP hostname
1891 * @return string
1892 */
1893function rcube_parse_host($name, $host='')
1894{
1895  // %n - host
1896  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1897  // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
1898  $d = preg_replace('/^[^\.]+\./', '', $n);
1899  // %h - IMAP host
1900  $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
1901  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1902  $z = preg_replace('/^[^\.]+\./', '', $h);
1903  // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
1904  if ( strpos($name, '%s') !== false ){
1905    $user_email = rcube_idn_convert(get_input_value('_user', RCUBE_INPUT_POST), true);
1906    if ( preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s) < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false )
1907      return false;
1908  }
1909
1910  $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
1911  return $name;
1912}
1913
1914
1915/**
1916 * E-mail address validation
1917 *
1918 * @param string $email Email address
1919 * @param boolean $dns_check True to check dns
1920 * @return boolean
1921 */
1922function check_email($email, $dns_check=true)
1923{
1924  // Check for invalid characters
1925  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1926    return false;
1927
1928  // Check for length limit specified by RFC 5321 (#1486453)
1929  if (strlen($email) > 254)
1930    return false;
1931
1932  $email_array = explode('@', $email);
1933
1934  // Check that there's one @ symbol
1935  if (count($email_array) < 2)
1936    return false;
1937
1938  $domain_part = array_pop($email_array);
1939  $local_part = implode('@', $email_array);
1940
1941  // from PEAR::Validate
1942  $regexp = '&^(?:
1943        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1944        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
1945        $&xi';
1946
1947  if (!preg_match($regexp, $local_part))
1948    return false;
1949
1950  // Check domain part
1951  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))
1952    return true; // IP address
1953  else {
1954    // If not an IP address
1955    $domain_array = explode('.', $domain_part);
1956    if (sizeof($domain_array) < 2)
1957      return false; // Not enough parts to be a valid domain
1958
1959    foreach ($domain_array as $part)
1960      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1961        return false;
1962
1963    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1964      return true;
1965
1966    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1967      $lookup = array();
1968      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1969      foreach ($lookup as $line) {
1970        if (strpos($line, 'MX preference'))
1971          return true;
1972      }
1973      return false;
1974    }
1975
1976    // find MX record(s)
1977    if (getmxrr($domain_part, $mx_records))
1978      return true;
1979
1980    // find any DNS record
1981    if (checkdnsrr($domain_part, 'ANY'))
1982      return true;
1983  }
1984
1985  return false;
1986}
1987
1988/*
1989 * Idn_to_ascii wrapper.
1990 * Intl/Idn modules version of this function doesn't work with e-mail address
1991 */
1992function rcube_idn_to_ascii($str)
1993{
1994  return rcube_idn_convert($str, true);
1995}
1996
1997/*
1998 * Idn_to_ascii wrapper.
1999 * Intl/Idn modules version of this function doesn't work with e-mail address
2000 */
2001function rcube_idn_to_utf8($str)
2002{
2003  return rcube_idn_convert($str, false);
2004}
2005
2006function rcube_idn_convert($input, $is_utf=false)
2007{
2008  if ($at = strpos($input, '@')) {
2009    $user   = substr($input, 0, $at);
2010    $domain = substr($input, $at+1);
2011  }
2012  else {
2013    $domain = $input;
2014  }
2015
2016  $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
2017
2018  if ($domain === false) {
2019    return '';
2020  }
2021
2022  return $at ? $user . '@' . $domain : $domain;
2023}
2024
2025
2026/**
2027 * Helper class to turn relative urls into absolute ones
2028 * using a predefined base
2029 */
2030class rcube_base_replacer
2031{
2032  private $base_url;
2033
2034  public function __construct($base)
2035  {
2036    $this->base_url = $base;
2037  }
2038
2039  public function callback($matches)
2040  {
2041    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
2042  }
2043}
2044
2045
2046/****** debugging and logging functions ********/
2047
2048/**
2049 * Print or write debug messages
2050 *
2051 * @param mixed Debug message or data
2052 * @return void
2053 */
2054function console()
2055{
2056    $args = func_get_args();
2057
2058    if (class_exists('rcmail', false)) {
2059        $rcmail = rcmail::get_instance();
2060        if (is_object($rcmail->plugins)) {
2061            $plugin = $rcmail->plugins->exec_hook('console', array('args' => $args));
2062            if ($plugin['abort'])
2063                return;
2064            $args = $plugin['args'];
2065        }
2066    }
2067
2068    $msg = array();
2069    foreach ($args as $arg)
2070        $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
2071
2072    write_log('console', join(";\n", $msg));
2073}
2074
2075
2076/**
2077 * Append a line to a logfile in the logs directory.
2078 * Date will be added automatically to the line.
2079 *
2080 * @param $name name of log file
2081 * @param line Line to append
2082 * @return void
2083 */
2084function write_log($name, $line)
2085{
2086  global $CONFIG, $RCMAIL;
2087
2088  if (!is_string($line))
2089    $line = var_export($line, true);
2090 
2091  if (empty($CONFIG['log_date_format']))
2092    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
2093 
2094  $date = date($CONFIG['log_date_format']);
2095 
2096  // trigger logging hook
2097  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
2098    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
2099    $name = $log['name'];
2100    $line = $log['line'];
2101    $date = $log['date'];
2102    if ($log['abort'])
2103      return true;
2104  }
2105 
2106  if ($CONFIG['log_driver'] == 'syslog') {
2107    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
2108    syslog($prio, $line);
2109    return true;
2110  }
2111  else {
2112    $line = sprintf("[%s]: %s\n", $date, $line);
2113
2114    // log_driver == 'file' is assumed here
2115    if (empty($CONFIG['log_dir']))
2116      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
2117
2118    // try to open specific log file for writing
2119    $logfile = $CONFIG['log_dir'].'/'.$name;
2120    if ($fp = @fopen($logfile, 'a')) {
2121      fwrite($fp, $line);
2122      fflush($fp);
2123      fclose($fp);
2124      return true;
2125    }
2126    else
2127      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
2128  }
2129
2130  return false;
2131}
2132
2133
2134/**
2135 * Write login data (name, ID, IP address) to the 'userlogins' log file.
2136 *
2137 * @return void
2138 */
2139function rcmail_log_login()
2140{
2141  global $RCMAIL;
2142
2143  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
2144    return;
2145
2146  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s in session %s',
2147    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip(), session_id()));
2148}
2149
2150
2151/**
2152 * Returns remote IP address and forwarded addresses if found
2153 *
2154 * @return string Remote IP address(es)
2155 */
2156function rcmail_remote_ip()
2157{
2158    $address = $_SERVER['REMOTE_ADDR'];
2159
2160    // append the NGINX X-Real-IP header, if set
2161    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
2162        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
2163    }
2164    // append the X-Forwarded-For header, if set
2165    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2166        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
2167    }
2168
2169    if (!empty($remote_ip))
2170        $address .= '(' . implode(',', $remote_ip) . ')';
2171
2172    return $address;
2173}
2174
2175
2176/**
2177 * Check whether the HTTP referer matches the current request
2178 *
2179 * @return boolean True if referer is the same host+path, false if not
2180 */
2181function rcube_check_referer()
2182{
2183  $uri = parse_url($_SERVER['REQUEST_URI']);
2184  $referer = parse_url(rc_request_header('Referer'));
2185  return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
2186}
2187
2188
2189/**
2190 * @access private
2191 * @return mixed
2192 */
2193function rcube_timer()
2194{
2195  return microtime(true);
2196}
2197
2198
2199/**
2200 * @access private
2201 * @return void
2202 */
2203function rcube_print_time($timer, $label='Timer', $dest='console')
2204{
2205  static $print_count = 0;
2206
2207  $print_count++;
2208  $now = rcube_timer();
2209  $diff = $now-$timer;
2210
2211  if (empty($label))
2212    $label = 'Timer '.$print_count;
2213
2214  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
2215}
2216
2217
2218/**
2219 * Throw system error and show error page
2220 *
2221 * @param array Named parameters
2222 *  - code: Error code (required)
2223 *  - type: Error type [php|db|imap|javascript] (required)
2224 *  - message: Error message
2225 *  - file: File where error occured
2226 *  - line: Line where error occured
2227 * @param boolean True to log the error
2228 * @param boolean Terminate script execution
2229 */
2230// may be defined in Installer
2231if (!function_exists('raise_error')) {
2232function raise_error($arg=array(), $log=false, $terminate=false)
2233{
2234    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
2235
2236    // report bug (if not incompatible browser)
2237    if ($log && $arg['type'] && $arg['message'])
2238        rcube_log_bug($arg);
2239
2240    // display error page and terminate script
2241    if ($terminate) {
2242        $ERROR_CODE = $arg['code'];
2243        $ERROR_MESSAGE = $arg['message'];
2244        include INSTALL_PATH . 'program/steps/utils/error.inc';
2245        exit;
2246    }
2247}
2248}
2249
2250
2251/**
2252 * Report error according to configured debug_level
2253 *
2254 * @param array Named parameters
2255 * @return void
2256 * @see raise_error()
2257 */
2258function rcube_log_bug($arg_arr)
2259{
2260    global $CONFIG;
2261
2262    $program = strtoupper($arg_arr['type']);
2263    $level   = $CONFIG['debug_level'];
2264
2265    // disable errors for ajax requests, write to log instead (#1487831)
2266    if (($level & 4) && !empty($_REQUEST['_remote'])) {
2267        $level = ($level ^ 4) | 1;
2268    }
2269
2270    // write error to local log file
2271    if ($level & 1) {
2272        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
2273        $log_entry = sprintf("%s Error: %s%s (%s %s)",
2274            $program,
2275            $arg_arr['message'],
2276            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
2277            $_SERVER['REQUEST_METHOD'],
2278            $_SERVER['REQUEST_URI'] . $post_query);
2279
2280        if (!write_log('errors', $log_entry)) {
2281            // send error to PHPs error handler if write_log didn't succeed
2282            trigger_error($arg_arr['message']);
2283        }
2284    }
2285
2286    // report the bug to the global bug reporting system
2287    if ($level & 2) {
2288        // TODO: Send error via HTTP
2289    }
2290
2291    // show error if debug_mode is on
2292    if ($level & 4) {
2293        print "<b>$program Error";
2294
2295        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2296            print " in $arg_arr[file] ($arg_arr[line])";
2297
2298        print ':</b>&nbsp;';
2299        print nl2br($arg_arr['message']);
2300        print '<br />';
2301        flush();
2302    }
2303}
2304
2305function rcube_upload_progress()
2306{
2307    global $RCMAIL;
2308
2309    $prefix = ini_get('apc.rfc1867_prefix');
2310    $params = array(
2311        'action' => $RCMAIL->action,
2312        'name' => get_input_value('_progress', RCUBE_INPUT_GET),
2313    );
2314
2315    if (function_exists('apc_fetch')) {
2316        $status = apc_fetch($prefix . $params['name']);
2317
2318        if (!empty($status)) {
2319            $status['percent'] = round($status['current']/$status['total']*100);
2320            $params = array_merge($status, $params);
2321        }
2322    }
2323
2324    if (isset($params['percent']))
2325        $params['text'] = rcube_label(array('name' => 'uploadprogress', 'vars' => array(
2326            'percent' => $params['percent'] . '%',
2327            'current' => show_bytes($params['current']),
2328            'total'   => show_bytes($params['total'])
2329        )));
2330
2331    $RCMAIL->output->command('upload_progress_update', $params);
2332    $RCMAIL->output->send();
2333}
2334
2335function rcube_upload_init()
2336{
2337    global $RCMAIL;
2338
2339    // Enable upload progress bar
2340    if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
2341        if ($field_name = ini_get('apc.rfc1867_name')) {
2342            $RCMAIL->output->set_env('upload_progress_name', $field_name);
2343            $RCMAIL->output->set_env('upload_progress_time', (int) $seconds);
2344        }
2345    }
2346
2347    // find max filesize value
2348    $max_filesize = parse_bytes(ini_get('upload_max_filesize'));
2349    $max_postsize = parse_bytes(ini_get('post_max_size'));
2350    if ($max_postsize && $max_postsize < $max_filesize)
2351        $max_filesize = $max_postsize;
2352
2353    $RCMAIL->output->set_env('max_filesize', $max_filesize);
2354    $max_filesize = show_bytes($max_filesize);
2355    $RCMAIL->output->set_env('filesizeerror', rcube_label(array(
2356        'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
2357
2358    return $max_filesize;
2359}
2360
2361/**
2362 * Initializes client-side autocompletion
2363 */
2364function rcube_autocomplete_init()
2365{
2366    global $RCMAIL;
2367    static $init;
2368
2369    if ($init)
2370        return;
2371
2372    $init = 1;
2373
2374    if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) {
2375      $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql');
2376      if (count($book_types) > 1) {
2377        $RCMAIL->output->set_env('autocomplete_threads', $threads);
2378        $RCMAIL->output->set_env('autocomplete_sources', $book_types);
2379      }
2380    }
2381
2382    $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15));
2383    $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length'));
2384    $RCMAIL->output->add_label('autocompletechars');
2385}
Note: See TracBrowser for help on using the repository browser.