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