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

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