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