source: github/program/include/main.inc @ 427e3a4

HEADcourier-fixdev-browser-capabilitiespdorelease-0.7release-0.8
Last change on this file since 427e3a4 was 427e3a4, checked in by alecpl <alec@…>, 21 months ago
  • Check for "Permission denied" string in IMAP error messages to display more appropriate message to the user
  • 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 $RCMAIL, $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  $tz = $RCMAIL->config->get_timezone();
1036
1037  // convert time to user's timezone
1038  $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
1039
1040  // get current timestamp in user's timezone
1041  $now = time();  // local time
1042  $now -= (int)date('Z'); // make GMT time
1043  $now += ($tz * 3600); // user's time
1044  $now_date = getdate($now);
1045
1046  $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1047  $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
1048
1049  // define date format depending on current time
1050  if (!$format) {
1051    if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
1052      $format = $CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i';
1053      $today  = true;
1054    }
1055    else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1056      $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
1057    else
1058      $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
1059  }
1060
1061  // strftime() format
1062  if (preg_match('/%[a-z]+/i', $format)) {
1063    $format = strftime($format, $timestamp);
1064    return $today ? (rcube_label('today') . ' ' . $format) : $format;
1065  }
1066
1067  // parse format string manually in order to provide localized weekday and month names
1068  // an alternative would be to convert the date() format string to fit with strftime()
1069  $out = '';
1070  for($i=0; $i<strlen($format); $i++) {
1071    if ($format[$i]=='\\')  // skip escape chars
1072      continue;
1073
1074    // write char "as-is"
1075    if ($format[$i]==' ' || $format{$i-1}=='\\')
1076      $out .= $format[$i];
1077    // weekday (short)
1078    else if ($format[$i]=='D')
1079      $out .= rcube_label(strtolower(date('D', $timestamp)));
1080    // weekday long
1081    else if ($format[$i]=='l')
1082      $out .= rcube_label(strtolower(date('l', $timestamp)));
1083    // month name (short)
1084    else if ($format[$i]=='M')
1085      $out .= rcube_label(strtolower(date('M', $timestamp)));
1086    // month name (long)
1087    else if ($format[$i]=='F')
1088      $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1089    else if ($format[$i]=='x')
1090      $out .= strftime('%x %X', $timestamp);
1091    else
1092      $out .= date($format[$i], $timestamp);
1093  }
1094
1095  if ($today) {
1096    $label = rcube_label('today');
1097    // replcae $ character with "Today" label (#1486120)
1098    if (strpos($out, '$') !== false) {
1099      $out = preg_replace('/\$/', $label, $out, 1);
1100    }
1101    else {
1102      $out = $label . ' ' . $out;
1103    }
1104  }
1105
1106  return $out;
1107}
1108
1109
1110/**
1111 * Compose a valid representation of name and e-mail address
1112 *
1113 * @param string E-mail address
1114 * @param string Person name
1115 * @return string Formatted string
1116 */
1117function format_email_recipient($email, $name='')
1118{
1119  if ($name && $name != $email) {
1120    // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1121    return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1122  }
1123
1124  return trim($email);
1125}
1126
1127
1128/**
1129 * Return the mailboxlist in HTML
1130 *
1131 * @param array Named parameters
1132 * @return string HTML code for the gui object
1133 */
1134function rcmail_mailbox_list($attrib)
1135{
1136  global $RCMAIL;
1137  static $a_mailboxes;
1138
1139  $attrib += array('maxlength' => 100, 'realnames' => false);
1140
1141  // add some labels to client
1142  $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1143
1144  $type = $attrib['type'] ? $attrib['type'] : 'ul';
1145  unset($attrib['type']);
1146
1147  if ($type=='ul' && !$attrib['id'])
1148    $attrib['id'] = 'rcmboxlist';
1149
1150  if (empty($attrib['folder_name']))
1151    $attrib['folder_name'] = '*';
1152
1153  // get mailbox list
1154  $mbox_name = $RCMAIL->imap->get_mailbox_name();
1155
1156  // build the folders tree
1157  if (empty($a_mailboxes)) {
1158    // get mailbox list
1159    $a_folders = $RCMAIL->imap->list_mailboxes('', $attrib['folder_name'], $attrib['folder_filter']);
1160    $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1161    $a_mailboxes = array();
1162
1163    foreach ($a_folders as $folder)
1164      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1165  }
1166
1167  // allow plugins to alter the folder tree or to localize folder names
1168  $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1169
1170  if ($type == 'select') {
1171    $select = new html_select($attrib);
1172
1173    // add no-selection option
1174    if ($attrib['noselection'])
1175      $select->add(rcube_label($attrib['noselection']), '');
1176
1177    rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1178    $out = $select->show();
1179  }
1180  else {
1181    $js_mailboxlist = array();
1182    $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1183
1184    $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1185    $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1186    $RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders'));
1187  }
1188
1189  return $out;
1190}
1191
1192
1193/**
1194 * Return the mailboxlist as html_select object
1195 *
1196 * @param array Named parameters
1197 * @return html_select HTML drop-down object
1198 */
1199function rcmail_mailbox_select($p = array())
1200{
1201  global $RCMAIL;
1202
1203  $p += array('maxlength' => 100, 'realnames' => false);
1204  $a_mailboxes = array();
1205
1206  if (empty($p['folder_name']))
1207    $p['folder_name'] = '*';
1208
1209  if ($p['unsubscribed'])
1210    $list = $RCMAIL->imap->list_unsubscribed('', $p['folder_name'], $p['folder_filter']);
1211  else
1212    $list = $RCMAIL->imap->list_mailboxes('', $p['folder_name'], $p['folder_filter']);
1213
1214  $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1215
1216  foreach ($list as $folder) {
1217    if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1218      rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1219  }
1220
1221  $select = new html_select($p);
1222
1223  if ($p['noselection'])
1224    $select->add($p['noselection'], '');
1225
1226  rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p['exceptions']);
1227
1228  return $select;
1229}
1230
1231
1232/**
1233 * Create a hierarchical array of the mailbox list
1234 * @access private
1235 * @return void
1236 */
1237function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1238{
1239  global $RCMAIL;
1240
1241  // Handle namespace prefix
1242  $prefix = '';
1243  if (!$path) {
1244    $n_folder = $folder;
1245    $folder = $RCMAIL->imap->mod_mailbox($folder);
1246
1247    if ($n_folder != $folder) {
1248      $prefix = substr($n_folder, 0, -strlen($folder));
1249    }
1250  }
1251
1252  $pos = strpos($folder, $delm);
1253
1254  if ($pos !== false) {
1255    $subFolders = substr($folder, $pos+1);
1256    $currentFolder = substr($folder, 0, $pos);
1257
1258    // sometimes folder has a delimiter as the last character
1259    if (!strlen($subFolders))
1260      $virtual = false;
1261    else if (!isset($arrFolders[$currentFolder]))
1262      $virtual = true;
1263    else
1264      $virtual = $arrFolders[$currentFolder]['virtual'];
1265  }
1266  else {
1267    $subFolders = false;
1268    $currentFolder = $folder;
1269    $virtual = false;
1270  }
1271
1272  $path .= $prefix.$currentFolder;
1273
1274  if (!isset($arrFolders[$currentFolder])) {
1275    // Check \Noselect option (if options are in cache)
1276    if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
1277      $virtual = in_array('\\Noselect', $opts);
1278    }
1279
1280    $arrFolders[$currentFolder] = array(
1281      'id' => $path,
1282      'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1283      'virtual' => $virtual,
1284      'folders' => array());
1285  }
1286  else
1287    $arrFolders[$currentFolder]['virtual'] = $virtual;
1288
1289  if (strlen($subFolders))
1290    rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1291}
1292
1293
1294/**
1295 * Return html for a structured list &lt;ul&gt; for the mailbox tree
1296 * @access private
1297 * @return string
1298 */
1299function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1300{
1301  global $RCMAIL, $CONFIG;
1302
1303  $maxlength = intval($attrib['maxlength']);
1304  $realnames = (bool)$attrib['realnames'];
1305  $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1306
1307  $idx = 0;
1308  $out = '';
1309  foreach ($arrFolders as $key => $folder) {
1310    $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1311    $title = null;
1312
1313    if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1314      $foldername = rcube_label($folder_class);
1315    }
1316    else {
1317      $foldername = $folder['name'];
1318
1319      // shorten the folder name to a given length
1320      if ($maxlength && $maxlength > 1) {
1321        $fname = abbreviate_string($foldername, $maxlength);
1322        if ($fname != $foldername)
1323          $title = $foldername;
1324        $foldername = $fname;
1325      }
1326    }
1327
1328    // make folder name safe for ids and class names
1329    $folder_id = html_identifier($folder['id']);
1330    $classes = array('mailbox');
1331
1332    // set special class for Sent, Drafts, Trash and Junk
1333    if ($folder['id'] == $CONFIG['sent_mbox'])
1334      $classes[] = 'sent';
1335    else if ($folder['id'] == $CONFIG['drafts_mbox'])
1336      $classes[] = 'drafts';
1337    else if ($folder['id'] == $CONFIG['trash_mbox'])
1338      $classes[] = 'trash';
1339    else if ($folder['id'] == $CONFIG['junk_mbox'])
1340      $classes[] = 'junk';
1341    else if ($folder['id'] == 'INBOX')
1342      $classes[] = 'inbox';
1343    else
1344      $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1345
1346    $classes[] = $zebra_class;
1347
1348    if ($folder['id'] == $mbox_name)
1349      $classes[] = 'selected';
1350
1351    $collapsed = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false;
1352    $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1353
1354    if ($folder['virtual'])
1355      $classes[] = 'virtual';
1356    else if ($unread)
1357      $classes[] = 'unread';
1358
1359    $js_name = JQ($folder['id']);
1360    $html_name = Q($foldername) . ($unread ? html::span('unreadcount', " ($unread)") : '');
1361    $link_attrib = $folder['virtual'] ? array() : array(
1362      'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1363      'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1364      'rel' => $folder['id'],
1365      'title' => $title,
1366    );
1367
1368    $out .= html::tag('li', array(
1369        'id' => "rcmli".$folder_id,
1370        'class' => join(' ', $classes),
1371        'noclose' => true),
1372      html::a($link_attrib, $html_name) .
1373      (!empty($folder['folders']) ? html::div(array(
1374        'class' => ($collapsed ? 'collapsed' : 'expanded'),
1375        'style' => "position:absolute",
1376        'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1377      ), '&nbsp;') : ''));
1378
1379    $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1380
1381    if (!empty($folder['folders'])) {
1382      $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1383        rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1384    }
1385
1386    $out .= "</li>\n";
1387    $idx++;
1388  }
1389
1390  return $out;
1391}
1392
1393
1394/**
1395 * Return html for a flat list <select> for the mailbox tree
1396 * @access private
1397 * @return string
1398 */
1399function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0, $exceptions=array())
1400{
1401  $out = '';
1402
1403  foreach ($arrFolders as $key => $folder) {
1404    if (empty($exceptions) || !in_array($folder['id'], $exceptions)) {
1405      if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1406        $foldername = rcube_label($folder_class);
1407      else {
1408        $foldername = $folder['name'];
1409
1410        // shorten the folder name to a given length
1411        if ($maxlength && $maxlength>1)
1412          $foldername = abbreviate_string($foldername, $maxlength);
1413      }
1414
1415      $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1416    }
1417    else if ($nestLevel)
1418      continue;
1419
1420    if (!empty($folder['folders']))
1421      $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
1422        $select, $realnames, $nestLevel+1, $exceptions);
1423  }
1424
1425  return $out;
1426}
1427
1428
1429/**
1430 * Return internal name for the given folder if it matches the configured special folders
1431 * @access private
1432 * @return string
1433 */
1434function rcmail_folder_classname($folder_id)
1435{
1436  global $CONFIG;
1437
1438  if ($folder_id == 'INBOX')
1439    return 'inbox';
1440
1441  // for these mailboxes we have localized labels and css classes
1442  foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1443  {
1444    if ($folder_id == $CONFIG[$smbx.'_mbox'])
1445      return $smbx;
1446  }
1447}
1448
1449
1450/**
1451 * Try to localize the given IMAP folder name.
1452 * UTF-7 decode it in case no localized text was found
1453 *
1454 * @param string Folder name
1455 * @return string Localized folder name in UTF-8 encoding
1456 */
1457function rcmail_localize_foldername($name)
1458{
1459  if ($folder_class = rcmail_folder_classname($name))
1460    return rcube_label($folder_class);
1461  else
1462    return rcube_charset_convert($name, 'UTF7-IMAP');
1463}
1464
1465
1466function rcmail_localize_folderpath($path)
1467{
1468    global $RCMAIL;
1469
1470    $protect_folders = $RCMAIL->config->get('protect_default_folders');
1471    $default_folders = (array) $RCMAIL->config->get('default_imap_folders');
1472    $delimiter       = $RCMAIL->imap->get_hierarchy_delimiter();
1473    $path            = explode($delimiter, $path);
1474    $result          = array();
1475
1476    foreach ($path as $idx => $dir) {
1477        $directory = implode($delimiter, array_slice($path, 0, $idx+1));
1478        if ($protect_folders && in_array($directory, $default_folders)) {
1479            unset($result);
1480            $result[] = rcmail_localize_foldername($directory);
1481        }
1482        else {
1483            $result[] = rcube_charset_convert($dir, 'UTF7-IMAP');
1484        }
1485    }
1486
1487    return implode($delimiter, $result);
1488}
1489
1490
1491function rcmail_quota_display($attrib)
1492{
1493  global $OUTPUT;
1494
1495  if (!$attrib['id'])
1496    $attrib['id'] = 'rcmquotadisplay';
1497
1498  if(isset($attrib['display']))
1499    $_SESSION['quota_display'] = $attrib['display'];
1500
1501  $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1502
1503  $quota = rcmail_quota_content($attrib);
1504
1505  $OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready');
1506
1507  return html::span($attrib, '');
1508}
1509
1510
1511function rcmail_quota_content($attrib=NULL)
1512{
1513  global $RCMAIL;
1514
1515  $quota = $RCMAIL->imap->get_quota();
1516  $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1517
1518  $quota_result = (array) $quota;
1519  $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1520
1521  if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1522    $quota_result['title'] = rcube_label('unlimited');
1523    $quota_result['percent'] = 0;
1524  }
1525  else if ($quota['total']) {
1526    if (!isset($quota['percent']))
1527      $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1528
1529    $title = sprintf('%s / %s (%.0f%%)',
1530        show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1531        $quota_result['percent']);
1532
1533    $quota_result['title'] = $title;
1534
1535    if ($attrib['width'])
1536      $quota_result['width'] = $attrib['width'];
1537    if ($attrib['height'])
1538      $quota_result['height']   = $attrib['height'];
1539  }
1540  else {
1541    $quota_result['title'] = rcube_label('unknown');
1542    $quota_result['percent'] = 0;
1543  }
1544
1545  return $quota_result;
1546}
1547
1548
1549/**
1550 * Outputs error message according to server error/response codes
1551 *
1552 * @param string Fallback message label
1553 * @param string Fallback message label arguments
1554 *
1555 * @return void
1556 */
1557function rcmail_display_server_error($fallback=null, $fallback_args=null)
1558{
1559    global $RCMAIL;
1560
1561    $err_code = $RCMAIL->imap->get_error_code();
1562    $res_code = $RCMAIL->imap->get_response_code();
1563
1564    if ($res_code == rcube_imap::NOPERM) {
1565        $RCMAIL->output->show_message('errornoperm', 'error');
1566    }
1567    else if ($res_code == rcube_imap::READONLY) {
1568        $RCMAIL->output->show_message('errorreadonly', 'error');
1569    }
1570    else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1571        // try to detect access rights problem and display appropriate message
1572        if (stripos($err_str, 'Permission denied') !== false)
1573            $RCMAIL->output->show_message('errornoperm', 'error');
1574        else
1575            $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1576    }
1577    else if ($fallback) {
1578        $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1579    }
1580
1581    return true;
1582}
1583
1584
1585/**
1586 * Output HTML editor scripts
1587 *
1588 * @param string Editor mode
1589 * @return void
1590 */
1591function rcube_html_editor($mode='')
1592{
1593  global $RCMAIL, $CONFIG;
1594
1595  $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
1596
1597  if ($hook['abort'])
1598    return; 
1599
1600  $lang = strtolower($_SESSION['language']);
1601
1602  // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1603  $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1604
1605  if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1606    $lang = 'en';
1607
1608  $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1609  $RCMAIL->output->include_script('editor.js');
1610  $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
1611    JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
1612    'foot');
1613}
1614
1615
1616/**
1617 * Replaces TinyMCE's emoticon images with plain-text representation
1618 *
1619 * @param string HTML content
1620 * @return string HTML content
1621 */
1622function rcmail_replace_emoticons($html)
1623{
1624  $emoticons = array(
1625    '8-)' => 'smiley-cool',
1626    ':-#' => 'smiley-foot-in-mouth',
1627    ':-*' => 'smiley-kiss',
1628    ':-X' => 'smiley-sealed',
1629    ':-P' => 'smiley-tongue-out',
1630    ':-@' => 'smiley-yell',
1631    ":'(" => 'smiley-cry',
1632    ':-(' => 'smiley-frown',
1633    ':-D' => 'smiley-laughing',
1634    ':-)' => 'smiley-smile',
1635    ':-S' => 'smiley-undecided',
1636    ':-$' => 'smiley-embarassed',
1637    'O:-)' => 'smiley-innocent',
1638    ':-|' => 'smiley-money-mouth',
1639    ':-O' => 'smiley-surprised',
1640    ';-)' => 'smiley-wink',
1641  );
1642
1643  foreach ($emoticons as $idx => $file) {
1644    // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1645    $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1646    $replace[] = $idx;
1647  }
1648
1649  return preg_replace($search, $replace, $html);
1650}
1651
1652
1653/**
1654 * Send the given message using the configured method
1655 *
1656 * @param object $message    Reference to Mail_MIME object
1657 * @param string $from       Sender address string
1658 * @param array  $mailto     Array of recipient address strings
1659 * @param array  $smtp_error SMTP error array (reference)
1660 * @param string $body_file  Location of file with saved message body (reference),
1661 *                           used when delay_file_io is enabled
1662 * @param array  $smtp_opts  SMTP options (e.g. DSN request)
1663 *
1664 * @return boolean Send status.
1665 */
1666function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file=null, $smtp_opts=null)
1667{
1668  global $CONFIG, $RCMAIL;
1669
1670  $headers = $message->headers();
1671
1672  // send thru SMTP server using custom SMTP library
1673  if ($CONFIG['smtp_server']) {
1674    // generate list of recipients
1675    $a_recipients = array($mailto);
1676
1677    if (strlen($headers['Cc']))
1678      $a_recipients[] = $headers['Cc'];
1679    if (strlen($headers['Bcc']))
1680      $a_recipients[] = $headers['Bcc'];
1681
1682    // clean Bcc from header for recipients
1683    $send_headers = $headers;
1684    unset($send_headers['Bcc']);
1685    // here too, it because txtHeaders() below use $message->_headers not only $send_headers
1686    unset($message->_headers['Bcc']);
1687
1688    $smtp_headers = $message->txtHeaders($send_headers, true);
1689
1690    if ($message->getParam('delay_file_io')) {
1691      // use common temp dir
1692      $temp_dir = $RCMAIL->config->get('temp_dir');
1693      $body_file = tempnam($temp_dir, 'rcmMsg');
1694      if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
1695        raise_error(array('code' => 650, 'type' => 'php',
1696            'file' => __FILE__, 'line' => __LINE__,
1697            'message' => "Could not create message: ".$mime_result->getMessage()),
1698            TRUE, FALSE);
1699        return false;
1700      }
1701      $msg_body = fopen($body_file, 'r');
1702    } else {
1703      $msg_body = $message->get();
1704    }
1705
1706    // send message
1707    if (!is_object($RCMAIL->smtp))
1708      $RCMAIL->smtp_init(true);
1709
1710    $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
1711    $smtp_response = $RCMAIL->smtp->get_response();
1712    $smtp_error = $RCMAIL->smtp->get_error();
1713
1714    // log error
1715    if (!$sent)
1716      raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__,
1717                        'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
1718  }
1719  // send mail using PHP's mail() function
1720  else {
1721    // unset some headers because they will be added by the mail() function
1722    $headers_enc = $message->headers($headers);
1723    $headers_php = $message->_headers;
1724    unset($headers_php['To'], $headers_php['Subject']);
1725
1726    // reset stored headers and overwrite
1727    $message->_headers = array();
1728    $header_str = $message->txtHeaders($headers_php);
1729
1730    // #1485779
1731    if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1732      if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
1733        $headers_enc['To'] = implode(', ', $m[1]);
1734      }
1735    }
1736
1737    $msg_body = $message->get();
1738
1739    if (PEAR::isError($msg_body))
1740      raise_error(array('code' => 650, 'type' => 'php',
1741            'file' => __FILE__, 'line' => __LINE__,
1742            'message' => "Could not create message: ".$msg_body->getMessage()),
1743            TRUE, FALSE);
1744    else {
1745      $delim   = $RCMAIL->config->header_delimiter();
1746      $to      = $headers_enc['To'];
1747      $subject = $headers_enc['Subject'];
1748      $header_str = rtrim($header_str);
1749
1750      if ($delim != "\r\n") {
1751        $header_str = str_replace("\r\n", $delim, $header_str);
1752        $msg_body   = str_replace("\r\n", $delim, $msg_body);
1753        $to         = str_replace("\r\n", $delim, $to);
1754        $subject    = str_replace("\r\n", $delim, $subject);
1755      }
1756
1757      if (ini_get('safe_mode'))
1758        $sent = mail($to, $subject, $msg_body, $header_str);
1759      else
1760        $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
1761    }
1762  }
1763
1764  if ($sent) {
1765    $RCMAIL->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
1766
1767    // remove MDN headers after sending
1768    unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
1769
1770    // get all recipients
1771    if ($headers['Cc'])
1772      $mailto .= $headers['Cc'];
1773    if ($headers['Bcc'])
1774      $mailto .= $headers['Bcc'];
1775    if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
1776      $mailto = implode(', ', array_unique($m[1]));
1777
1778    if ($CONFIG['smtp_log']) {
1779      write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
1780        $RCMAIL->user->get_username(),
1781        $_SERVER['REMOTE_ADDR'],
1782        $mailto,
1783        !empty($smtp_response) ? join('; ', $smtp_response) : ''));
1784    }
1785  }
1786
1787  if (is_resource($msg_body)) {
1788    fclose($msg_body);
1789  }
1790
1791  $message->_headers = array();
1792  $message->headers($headers);
1793
1794  return $sent;
1795}
1796
1797
1798// Returns unique Message-ID
1799function rcmail_gen_message_id()
1800{
1801  global $RCMAIL;
1802
1803  $local_part  = md5(uniqid('rcmail'.mt_rand(),true));
1804  $domain_part = $RCMAIL->user->get_username('domain');
1805
1806  // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
1807  if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
1808    if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']))
1809      && preg_match('/\.[a-z]+$/i', $host)) {
1810        $domain_part = $host;
1811    }
1812    else if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['SERVER_NAME']))
1813      && preg_match('/\.[a-z]+$/i', $host)) {
1814        $domain_part = $host;
1815    }
1816  }
1817
1818  return sprintf('<%s@%s>', $local_part, $domain_part);
1819}
1820
1821
1822// Returns RFC2822 formatted current date in user's timezone
1823function rcmail_user_date()
1824{
1825  global $RCMAIL, $CONFIG;
1826
1827  // get user's timezone
1828  $tz = $RCMAIL->config->get_timezone();
1829
1830  $date = time() + $tz * 60 * 60;
1831  $date = gmdate('r', $date);
1832  $tz   = sprintf('%+05d', intval($tz) * 100 + ($tz - intval($tz)) * 60);
1833  $date = preg_replace('/[+-][0-9]{4}$/', $tz, $date);
1834
1835  return $date;
1836}
1837
1838
1839/**
1840 * Check if working in SSL mode
1841 *
1842 * @param integer HTTPS port number
1843 * @param boolean Enables 'use_https' option checking
1844 * @return boolean
1845 */
1846function rcube_https_check($port=null, $use_https=true)
1847{
1848  global $RCMAIL;
1849
1850  if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1851    return true;
1852  if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1853    return true;
1854  if ($port && $_SERVER['SERVER_PORT'] == $port)
1855    return true;
1856  if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1857    return true;
1858
1859  return false;
1860}
1861
1862
1863/**
1864 * For backward compatibility.
1865 *
1866 * @global rcmail $RCMAIL
1867 * @param string $var_name Variable name.
1868 * @return void
1869 */
1870function rcube_sess_unset($var_name=null)
1871{
1872  global $RCMAIL;
1873
1874  $RCMAIL->session->remove($var_name);
1875}
1876
1877
1878/**
1879 * Replaces hostname variables
1880 *
1881 * @param string $name Hostname
1882 * @param string $host Optional IMAP hostname
1883 * @return string
1884 */
1885function rcube_parse_host($name, $host='')
1886{
1887  // %n - host
1888  $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1889  // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
1890  $d = preg_replace('/^[^\.]+\./', '', $n);
1891  // %h - IMAP host
1892  $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
1893  // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1894  $z = preg_replace('/^[^\.]+\./', '', $h);
1895  // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
1896  if ( strpos($name, '%s') !== false ){
1897    $user_email = rcube_idn_convert(get_input_value('_user', RCUBE_INPUT_POST), true);
1898    if ( preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s) < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false )
1899      return false;
1900  }
1901
1902  $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
1903  return $name;
1904}
1905
1906
1907/**
1908 * E-mail address validation
1909 *
1910 * @param string $email Email address
1911 * @param boolean $dns_check True to check dns
1912 * @return boolean
1913 */
1914function check_email($email, $dns_check=true)
1915{
1916  // Check for invalid characters
1917  if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1918    return false;
1919
1920  // Check for length limit specified by RFC 5321 (#1486453)
1921  if (strlen($email) > 254)
1922    return false;
1923
1924  $email_array = explode('@', $email);
1925
1926  // Check that there's one @ symbol
1927  if (count($email_array) < 2)
1928    return false;
1929
1930  $domain_part = array_pop($email_array);
1931  $local_part = implode('@', $email_array);
1932
1933  // from PEAR::Validate
1934  $regexp = '&^(?:
1935        ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                             #1 quoted name
1936        ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))  #2 OR dot-atom (RFC5322)
1937        $&xi';
1938
1939  if (!preg_match($regexp, $local_part))
1940    return false;
1941
1942  // Check domain part
1943  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))
1944    return true; // IP address
1945  else {
1946    // If not an IP address
1947    $domain_array = explode('.', $domain_part);
1948    if (sizeof($domain_array) < 2)
1949      return false; // Not enough parts to be a valid domain
1950
1951    foreach ($domain_array as $part)
1952      if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1953        return false;
1954
1955    if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1956      return true;
1957
1958    if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1959      $lookup = array();
1960      @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1961      foreach ($lookup as $line) {
1962        if (strpos($line, 'MX preference'))
1963          return true;
1964      }
1965      return false;
1966    }
1967
1968    // find MX record(s)
1969    if (getmxrr($domain_part, $mx_records))
1970      return true;
1971
1972    // find any DNS record
1973    if (checkdnsrr($domain_part, 'ANY'))
1974      return true;
1975  }
1976
1977  return false;
1978}
1979
1980/*
1981 * Idn_to_ascii wrapper.
1982 * Intl/Idn modules version of this function doesn't work with e-mail address
1983 */
1984function rcube_idn_to_ascii($str)
1985{
1986  return rcube_idn_convert($str, true);
1987}
1988
1989/*
1990 * Idn_to_ascii wrapper.
1991 * Intl/Idn modules version of this function doesn't work with e-mail address
1992 */
1993function rcube_idn_to_utf8($str)
1994{
1995  return rcube_idn_convert($str, false);
1996}
1997
1998function rcube_idn_convert($input, $is_utf=false)
1999{
2000  if ($at = strpos($input, '@')) {
2001    $user   = substr($input, 0, $at);
2002    $domain = substr($input, $at+1);
2003  }
2004  else {
2005    $domain = $input;
2006  }
2007
2008  $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
2009
2010  if ($domain === false) {
2011    return '';
2012  }
2013
2014  return $at ? $user . '@' . $domain : $domain;
2015}
2016
2017
2018/**
2019 * Helper class to turn relative urls into absolute ones
2020 * using a predefined base
2021 */
2022class rcube_base_replacer
2023{
2024  private $base_url;
2025
2026  public function __construct($base)
2027  {
2028    $this->base_url = $base;
2029  }
2030
2031  public function callback($matches)
2032  {
2033    return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
2034  }
2035}
2036
2037
2038/****** debugging and logging functions ********/
2039
2040/**
2041 * Print or write debug messages
2042 *
2043 * @param mixed Debug message or data
2044 * @return void
2045 */
2046function console()
2047{
2048    $args = func_get_args();
2049
2050    if (class_exists('rcmail', false)) {
2051        $rcmail = rcmail::get_instance();
2052        if (is_object($rcmail->plugins)) {
2053            $plugin = $rcmail->plugins->exec_hook('console', array('args' => $args));
2054            if ($plugin['abort'])
2055                return;
2056            $args = $plugin['args'];
2057        }
2058    }
2059
2060    $msg = array();
2061    foreach ($args as $arg)
2062        $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
2063
2064    write_log('console', join(";\n", $msg));
2065}
2066
2067
2068/**
2069 * Append a line to a logfile in the logs directory.
2070 * Date will be added automatically to the line.
2071 *
2072 * @param $name name of log file
2073 * @param line Line to append
2074 * @return void
2075 */
2076function write_log($name, $line)
2077{
2078  global $CONFIG, $RCMAIL;
2079
2080  if (!is_string($line))
2081    $line = var_export($line, true);
2082 
2083  if (empty($CONFIG['log_date_format']))
2084    $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
2085 
2086  $date = date($CONFIG['log_date_format']);
2087 
2088  // trigger logging hook
2089  if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
2090    $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
2091    $name = $log['name'];
2092    $line = $log['line'];
2093    $date = $log['date'];
2094    if ($log['abort'])
2095      return true;
2096  }
2097 
2098  if ($CONFIG['log_driver'] == 'syslog') {
2099    $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
2100    syslog($prio, $line);
2101    return true;
2102  }
2103  else {
2104    $line = sprintf("[%s]: %s\n", $date, $line);
2105
2106    // log_driver == 'file' is assumed here
2107    if (empty($CONFIG['log_dir']))
2108      $CONFIG['log_dir'] = INSTALL_PATH.'logs';
2109
2110    // try to open specific log file for writing
2111    $logfile = $CONFIG['log_dir'].'/'.$name;
2112    if ($fp = @fopen($logfile, 'a')) {
2113      fwrite($fp, $line);
2114      fflush($fp);
2115      fclose($fp);
2116      return true;
2117    }
2118    else
2119      trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
2120  }
2121
2122  return false;
2123}
2124
2125
2126/**
2127 * Write login data (name, ID, IP address) to the 'userlogins' log file.
2128 *
2129 * @return void
2130 */
2131function rcmail_log_login()
2132{
2133  global $RCMAIL;
2134
2135  if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
2136    return;
2137
2138  write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s in session %s',
2139    $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip(), session_id()));
2140}
2141
2142
2143/**
2144 * Returns remote IP address and forwarded addresses if found
2145 *
2146 * @return string Remote IP address(es)
2147 */
2148function rcmail_remote_ip()
2149{
2150    $address = $_SERVER['REMOTE_ADDR'];
2151
2152    // append the NGINX X-Real-IP header, if set
2153    if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
2154        $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
2155    }
2156    // append the X-Forwarded-For header, if set
2157    if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2158        $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
2159    }
2160
2161    if (!empty($remote_ip))
2162        $address .= '(' . implode(',', $remote_ip) . ')';
2163
2164    return $address;
2165}
2166
2167
2168/**
2169 * Check whether the HTTP referer matches the current request
2170 *
2171 * @return boolean True if referer is the same host+path, false if not
2172 */
2173function rcube_check_referer()
2174{
2175  $uri = parse_url($_SERVER['REQUEST_URI']);
2176  $referer = parse_url(rc_request_header('Referer'));
2177  return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
2178}
2179
2180
2181/**
2182 * @access private
2183 * @return mixed
2184 */
2185function rcube_timer()
2186{
2187  return microtime(true);
2188}
2189
2190
2191/**
2192 * @access private
2193 * @return void
2194 */
2195function rcube_print_time($timer, $label='Timer', $dest='console')
2196{
2197  static $print_count = 0;
2198
2199  $print_count++;
2200  $now = rcube_timer();
2201  $diff = $now-$timer;
2202
2203  if (empty($label))
2204    $label = 'Timer '.$print_count;
2205
2206  write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
2207}
2208
2209
2210/**
2211 * Throw system error and show error page
2212 *
2213 * @param array Named parameters
2214 *  - code: Error code (required)
2215 *  - type: Error type [php|db|imap|javascript] (required)
2216 *  - message: Error message
2217 *  - file: File where error occured
2218 *  - line: Line where error occured
2219 * @param boolean True to log the error
2220 * @param boolean Terminate script execution
2221 */
2222// may be defined in Installer
2223if (!function_exists('raise_error')) {
2224function raise_error($arg=array(), $log=false, $terminate=false)
2225{
2226    global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
2227
2228    // report bug (if not incompatible browser)
2229    if ($log && $arg['type'] && $arg['message'])
2230        rcube_log_bug($arg);
2231
2232    // display error page and terminate script
2233    if ($terminate) {
2234        $ERROR_CODE = $arg['code'];
2235        $ERROR_MESSAGE = $arg['message'];
2236        include INSTALL_PATH . 'program/steps/utils/error.inc';
2237        exit;
2238    }
2239}
2240}
2241
2242
2243/**
2244 * Report error according to configured debug_level
2245 *
2246 * @param array Named parameters
2247 * @return void
2248 * @see raise_error()
2249 */
2250function rcube_log_bug($arg_arr)
2251{
2252    global $CONFIG;
2253
2254    $program = strtoupper($arg_arr['type']);
2255    $level   = $CONFIG['debug_level'];
2256
2257    // disable errors for ajax requests, write to log instead (#1487831)
2258    if (($level & 4) && !empty($_REQUEST['_remote'])) {
2259        $level = ($level ^ 4) | 1;
2260    }
2261
2262    // write error to local log file
2263    if ($level & 1) {
2264        $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
2265        $log_entry = sprintf("%s Error: %s%s (%s %s)",
2266            $program,
2267            $arg_arr['message'],
2268            $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
2269            $_SERVER['REQUEST_METHOD'],
2270            $_SERVER['REQUEST_URI'] . $post_query);
2271
2272        if (!write_log('errors', $log_entry)) {
2273            // send error to PHPs error handler if write_log didn't succeed
2274            trigger_error($arg_arr['message']);
2275        }
2276    }
2277
2278    // report the bug to the global bug reporting system
2279    if ($level & 2) {
2280        // TODO: Send error via HTTP
2281    }
2282
2283    // show error if debug_mode is on
2284    if ($level & 4) {
2285        print "<b>$program Error";
2286
2287        if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2288            print " in $arg_arr[file] ($arg_arr[line])";
2289
2290        print ':</b>&nbsp;';
2291        print nl2br($arg_arr['message']);
2292        print '<br />';
2293        flush();
2294    }
2295}
2296
2297function rcube_upload_progress()
2298{
2299    global $RCMAIL;
2300
2301    $prefix = ini_get('apc.rfc1867_prefix');
2302    $params = array(
2303        'action' => $RCMAIL->action,
2304        'name' => get_input_value('_progress', RCUBE_INPUT_GET),
2305    );
2306
2307    if (function_exists('apc_fetch')) {
2308        $status = apc_fetch($prefix . $params['name']);
2309
2310        if (!empty($status)) {
2311            $status['percent'] = round($status['current']/$status['total']*100);
2312            $params = array_merge($status, $params);
2313        }
2314    }
2315
2316    if (isset($params['percent']))
2317        $params['text'] = rcube_label(array('name' => 'uploadprogress', 'vars' => array(
2318            'percent' => $params['percent'] . '%',
2319            'current' => show_bytes($params['current']),
2320            'total'   => show_bytes($params['total'])
2321        )));
2322
2323    $RCMAIL->output->command('upload_progress_update', $params);
2324    $RCMAIL->output->send();
2325}
2326
2327function rcube_upload_init()
2328{
2329    global $RCMAIL;
2330
2331    // Enable upload progress bar
2332    if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
2333        if ($field_name = ini_get('apc.rfc1867_name')) {
2334            $RCMAIL->output->set_env('upload_progress_name', $field_name);
2335            $RCMAIL->output->set_env('upload_progress_time', (int) $seconds);
2336        }
2337    }
2338
2339    // find max filesize value
2340    $max_filesize = parse_bytes(ini_get('upload_max_filesize'));
2341    $max_postsize = parse_bytes(ini_get('post_max_size'));
2342    if ($max_postsize && $max_postsize < $max_filesize)
2343        $max_filesize = $max_postsize;
2344
2345    $RCMAIL->output->set_env('max_filesize', $max_filesize);
2346    $max_filesize = show_bytes($max_filesize);
2347    $RCMAIL->output->set_env('filesizeerror', rcube_label(array(
2348        'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
2349
2350    return $max_filesize;
2351}
2352
2353/**
2354 * Initializes client-side autocompletion
2355 */
2356function rcube_autocomplete_init()
2357{
2358    global $RCMAIL;
2359    static $init;
2360
2361    if ($init)
2362        return;
2363
2364    $init = 1;
2365
2366    if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) {
2367      $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql');
2368      if (count($book_types) > 1) {
2369        $RCMAIL->output->set_env('autocomplete_threads', $threads);
2370        $RCMAIL->output->set_env('autocomplete_sources', $book_types);
2371      }
2372    }
2373
2374    $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15));
2375    $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length'));
2376    $RCMAIL->output->add_label('autocompletechars');
2377}
Note: See TracBrowser for help on using the repository browser.