thomascube
2011-08-13 a02cfa9b085fc875fc55f57029a8e5846771ed5e
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                     |
A 8  | Copyright (C) 2005-2009, Roundcube Dev, - Switzerland                 |
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
T 87  * @return string Localized text
88  * @see rcmail::gettext()
6d969b 89  */
cc97ea 90 function rcube_label($p, $domain=null)
1854c4 91 {
cc97ea 92   return rcmail::get_instance()->gettext($p, $domain);
1854c4 93 }
4e17e6 94
a02cfa 95 /**
T 96  * Global wrapper of rcmail::text_exists()
97  * to check whether a text label is defined
98  *
99  * @see rcmail::text_exists()
100  */
101 function rcube_label_exists($name, $domain=null)
102 {
103   return rcmail::get_instance()->text_exists($name, $domain);
104 }
4e17e6 105
6d969b 106 /**
T 107  * Overwrite action variable
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   }
a5897a 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   }
T 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);
T 322     
323     $convert_warning = true;
ae8a60 324   }
1a7f99 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 /**
T 741  * Remove single and double quotes from given string
6d969b 742  *
T 743  * @param string Input value
744  * @return string Dequoted string
e34ae1 745  */
T 746 function strip_quotes($str)
747 {
2aa2b3 748   return str_replace(array("'", '"'), '', $str);
e34ae1 749 }
10a699 750
6d969b 751
3cf664 752 /**
T 753  * Remove new lines characters from given string
6d969b 754  *
T 755  * @param string Input value
756  * @return string Stripped string
3cf664 757  */
T 758 function strip_newlines($str)
759 {
760   return preg_replace('/[\r\n]/', '', $str);
f11541 761 }
4e17e6 762
T 763
6d969b 764 /**
T 765  * Create a HTML table based on the given data
766  *
767  * @param  array  Named table attributes
768  * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
769  * @param  array  List of cols to show
770  * @param  string Name of the identifier col
771  * @return string HTML table code
772  */
d1d2c4 773 function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
4e17e6 774   {
83a763 775   global $RCMAIL;
4e17e6 776   
83a763 777   $table = new html_table(/*array('cols' => count($a_show_cols))*/);
4e17e6 778     
83a763 779   // add table header
54759c 780   if (!$attrib['noheader'])
A 781     foreach ($a_show_cols as $col)
782       $table->add_header($col, Q(rcube_label($col)));
4e17e6 783   
T 784   $c = 0;
d1d2c4 785   if (!is_array($table_data)) 
83a763 786   {
T 787     $db = $RCMAIL->get_dbh();
788     while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
4e17e6 789     {
83a763 790       $zebra_class = $c % 2 ? 'even' : 'odd';
1fcad1 791       $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class));
d1d2c4 792
S 793       // format each col
794       foreach ($a_show_cols as $col)
83a763 795         $table->add($col, Q($sql_arr[$col]));
T 796       
d1d2c4 797       $c++;
S 798     }
83a763 799   }
d1d2c4 800   else 
83a763 801   {
d1d2c4 802     foreach ($table_data as $row_data)
83a763 803     {
T 804       $zebra_class = $c % 2 ? 'even' : 'odd';
b69560 805       if (!empty($row_data['class']))
A 806         $zebra_class .= ' '.$row_data['class'];
807
1fcad1 808       $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class));
d1d2c4 809
S 810       // format each col
811       foreach ($a_show_cols as $col)
83a763 812         $table->add($col, Q($row_data[$col]));
T 813         
d1d2c4 814       $c++;
4e17e6 815     }
83a763 816   }
4e17e6 817
83a763 818   return $table->show($attrib);
4e17e6 819   }
T 820
821
a0109c 822 /**
S 823  * Create an edit field for inclusion on a form
824  * 
825  * @param string col field name
826  * @param string value field value
827  * @param array attrib HTML element attributes for field
828  * @param string type HTML element type (default 'text')
829  * @return string HTML field definition
830  */
4e17e6 831 function rcmail_get_edit_field($col, $value, $attrib, $type='text')
T 832   {
833   $fname = '_'.$col;
834   $attrib['name'] = $fname;
835   
836   if ($type=='checkbox')
837     {
838     $attrib['value'] = '1';
47124c 839     $input = new html_checkbox($attrib);
4e17e6 840     }
T 841   else if ($type=='textarea')
842     {
843     $attrib['cols'] = $attrib['size'];
47124c 844     $input = new html_textarea($attrib);
4e17e6 845     }
T 846   else
47124c 847     $input = new html_inputfield($attrib);
4e17e6 848
T 849   // use value from post
597170 850   if (!empty($_POST[$fname]))
407dcf 851     $value = get_input_value($fname, RCUBE_INPUT_POST,
A 852         $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
4e17e6 853
T 854   $out = $input->show($value);
855          
856   return $out;
f11541 857   }
T 858
859
6d969b 860 /**
97bd2c 861  * Replace all css definitions with #container [def]
a3e5b4 862  * and remove css-inlined scripting
97bd2c 863  *
T 864  * @param string CSS source code
865  * @param string Container ID to use as prefix
866  * @return string Modified CSS source
867  */
aa055c 868 function rcmail_mod_css_styles($source, $container_id)
97bd2c 869   {
T 870   $last_pos = 0;
aa055c 871   $replacements = new rcube_string_replacer;
cb3dfd 872
a3e5b4 873   // ignore the whole block if evil styles are detected
98cb0f 874   $stripped = preg_replace('/[^a-z\(:;]/', '', rcmail_xss_entity_decode($source));
T 875   if (preg_match('/expression|behavior|url\(|import[^a]/', $stripped))
aa055c 876     return '/* evil! */';
97bd2c 877
8e5ed7 878   // remove css comments (sometimes used for some ugly hacks)
T 879   $source = preg_replace('!/\*(.+)\*/!Ums', '', $source);
880
97bd2c 881   // cut out all contents between { and }
T 882   while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
883   {
aa055c 884     $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
T 885     $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
97bd2c 886     $last_pos = $pos+2;
T 887   }
8e5ed7 888
a3e5b4 889   // remove html comments and add #container to each tag selector.
97bd2c 890   // also replace body definition because we also stripped off the <body> tag
T 891   $styles = preg_replace(
892     array(
893       '/(^\s*<!--)|(-->\s*$)/',
d0b981 894       '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
cb3dfd 895       '/'.preg_quote($container_id, '/').'\s+body/i',
97bd2c 896     ),
T 897     array(
898       '',
899       "\\1#$container_id \\2",
cb3dfd 900       $container_id,
97bd2c 901     ),
T 902     $source);
cb3dfd 903
1608f4 904   // put block contents back in
aa055c 905   $styles = $replacements->resolve($styles);
97bd2c 906
T 907   return $styles;
908   }
fba1f5 909
97bd2c 910
T 911 /**
1c499a 912  * Decode escaped entities used by known XSS exploits.
T 913  * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
914  *
915  * @param string CSS content to decode
916  * @return string Decoded string
917  */
c5ee03 918 function rcmail_xss_entity_decode($content)
1c499a 919 {
T 920   $out = html_entity_decode(html_entity_decode($content));
c5ee03 921   $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
85a913 922   $out = preg_replace('#/\*.*\*/#Um', '', $out);
1c499a 923   return $out;
T 924 }
925
aa055c 926
T 927 /**
c5ee03 928  * preg_replace_callback callback for rcmail_xss_entity_decode_callback
aa055c 929  *
T 930  * @param array matches result from preg_replace_callback
931  * @return string decoded entity
932  */ 
c5ee03 933 function rcmail_xss_entity_decode_callback($matches)
aa055c 934
T 935   return chr(hexdec($matches[1]));
936 }
1c499a 937
T 938 /**
6d969b 939  * Compose a valid attribute string for HTML tags
T 940  *
941  * @param array Named tag attributes
942  * @param array List of allowed attributes
943  * @return string HTML formatted attribute string
944  */
4e17e6 945 function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
T 946   {
947   // allow the following attributes to be added to the <iframe> tag
948   $attrib_str = '';
949   foreach ($allowed_attribs as $a)
950     if (isset($attrib[$a]))
fe79b1 951       $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
4e17e6 952
T 953   return $attrib_str;
954   }
955
956
6d969b 957 /**
T 958  * Convert a HTML attribute string attributes to an associative array (name => value)
959  *
960  * @param string Input string
961  * @return array Key-value pairs of parsed attributes
962  */
fe79b1 963 function parse_attrib_string($str)
T 964   {
965   $attrib = array();
d59aaa 966   preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
fe79b1 967
T 968   // convert attributes to an associative array (name => value)
cc97ea 969   if ($regs) {
T 970     foreach ($regs as $attr) {
971       $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
972     }
973   }
fe79b1 974
T 975   return $attrib;
976   }
977
4e17e6 978
6d969b 979 /**
T 980  * Convert the given date to a human readable form
981  * This uses the date formatting properties from config
982  *
983  * @param mixed Date representation (string or timestamp)
984  * @param string Date format to use
985  * @return string Formatted date string
986  */
4e17e6 987 function format_date($date, $format=NULL)
1a2754 988 {
197601 989   global $CONFIG;
4e17e6 990   
4647e1 991   $ts = NULL;
ea090c 992
4e17e6 993   if (is_numeric($date))
T 994     $ts = $date;
b076a4 995   else if (!empty($date))
ea090c 996     {
33875d 997     // support non-standard "GMTXXXX" literal
A 998     $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
c9ca6a 999     // if date parsing fails, we have a date in non-rfc format.
S 1000     // remove token from the end and try again
1001     while ((($ts = @strtotime($date))===false) || ($ts < 0))
ea090c 1002       {
A 1003         $d = explode(' ', $date);
197601 1004         array_pop($d);
T 1005         if (!$d) break;
1006         $date = implode(' ', $d);
ea090c 1007       }
A 1008     }
1009
4647e1 1010   if (empty($ts))
b076a4 1011     return '';
1a2754 1012
4647e1 1013   // get user's timezone
62784a 1014   if ($CONFIG['timezone'] === 'auto')
T 1015     $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
c8ae24 1016   else {
T 1017     $tz = $CONFIG['timezone'];
1018     if ($CONFIG['dst_active'])
1019       $tz++;
1020   }
4e17e6 1021
T 1022   // convert time to user's timezone
4647e1 1023   $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
1a2754 1024
4e17e6 1025   // get current timestamp in user's timezone
T 1026   $now = time();  // local time
1027   $now -= (int)date('Z'); // make GMT time
4647e1 1028   $now += ($tz * 3600); // user's time
c45eb5 1029   $now_date = getdate($now);
4e17e6 1030
749b07 1031   $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1a2754 1032   $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
4e17e6 1033
539df6 1034   // define date format depending on current time
A 1035   if (!$format) {
1a2754 1036     if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
A 1037       $format = $CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i';
1038       $today  = true;
1039     }
539df6 1040     else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
A 1041       $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
1042     else
1043       $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
1a2754 1044   }
4e17e6 1045
b6b593 1046   // strftime() format
1a2754 1047   if (preg_match('/%[a-z]+/i', $format)) {
A 1048     $format = strftime($format, $timestamp);
1049     return $today ? (rcube_label('today') . ' ' . $format) : $format;
1050   }
4e17e6 1051
T 1052   // parse format string manually in order to provide localized weekday and month names
1053   // an alternative would be to convert the date() format string to fit with strftime()
1054   $out = '';
1a2754 1055   for($i=0; $i<strlen($format); $i++) {
4e17e6 1056     if ($format{$i}=='\\')  // skip escape chars
T 1057       continue;
1a2754 1058
4e17e6 1059     // write char "as-is"
T 1060     if ($format{$i}==' ' || $format{$i-1}=='\\')
1061       $out .= $format{$i};
1062     // weekday (short)
1063     else if ($format{$i}=='D')
1064       $out .= rcube_label(strtolower(date('D', $timestamp)));
1065     // weekday long
1066     else if ($format{$i}=='l')
1067       $out .= rcube_label(strtolower(date('l', $timestamp)));
1068     // month name (short)
1069     else if ($format{$i}=='M')
1070       $out .= rcube_label(strtolower(date('M', $timestamp)));
1071     // month name (long)
1072     else if ($format{$i}=='F')
7479cc 1073       $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
5b1de5 1074     else if ($format{$i}=='x')
A 1075       $out .= strftime('%x %X', $timestamp);
4e17e6 1076     else
T 1077       $out .= date($format{$i}, $timestamp);
1078   }
1079
9b624b 1080   if ($today) {
A 1081     $label = rcube_label('today');
1082     // replcae $ character with "Today" label (#1486120)
1083     if (strpos($out, '$') !== false) {
1084       $out = preg_replace('/\$/', $label, $out, 1);
1085     }
1086     else {
1087       $out = $label . ' ' . $out;
1088     }
1089   }
1090
1091   return $out;
1a2754 1092 }
A 1093
4e17e6 1094
6d969b 1095 /**
a9bfe2 1096  * Compose a valid representation of name and e-mail address
6d969b 1097  *
T 1098  * @param string E-mail address
1099  * @param string Person name
1100  * @return string Formatted string
1101  */
f11541 1102 function format_email_recipient($email, $name='')
T 1103   {
1104   if ($name && $name != $email)
0c6f4b 1105     {
T 1106     // Special chars as defined by RFC 822 need to in quoted string (or escaped).
a9bfe2 1107     return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
0c6f4b 1108     }
f11541 1109   else
a9bfe2 1110     return trim($email);
f11541 1111   }
T 1112
1113
1114
c39957 1115 /****** debugging functions ********/
T 1116
1117
1118 /**
1119  * Print or write debug messages
1120  *
1121  * @param mixed Debug message or data
5c461b 1122  * @return void
c39957 1123  */
ed132e 1124 function console()
c39957 1125   {
cc97ea 1126   $args = func_get_args();
76db10 1127
759696 1128   if (class_exists('rcmail', false)) {
T 1129     $rcmail = rcmail::get_instance();
1130     if (is_object($rcmail->plugins))
1131       $rcmail->plugins->exec_hook('console', $args);
1132   }
cc97ea 1133
ed132e 1134   $msg = array();
cc97ea 1135   foreach ($args as $arg)
76db10 1136     $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
c39957 1137
T 1138   if (!($GLOBALS['CONFIG']['debug_level'] & 4))
ed132e 1139     write_log('console', join(";\n", $msg));
197601 1140   else if ($GLOBALS['OUTPUT']->ajax_call)
ed132e 1141     print "/*\n " . join(";\n", $msg) . " \n*/\n";
c39957 1142   else
T 1143     {
1144     print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
ed132e 1145     print join(";<br/>\n", $msg);
c39957 1146     print "</pre></div>\n";
T 1147     }
1148   }
1149
1150
1151 /**
1152  * Append a line to a logfile in the logs directory.
1153  * Date will be added automatically to the line.
1154  *
653242 1155  * @param $name name of log file
S 1156  * @param line Line to append
5c461b 1157  * @return void
c39957 1158  */
T 1159 function write_log($name, $line)
1160   {
75fd64 1161   global $CONFIG, $RCMAIL;
e170b4 1162
T 1163   if (!is_string($line))
1164     $line = var_export($line, true);
0ad27c 1165  
A 1166   if (empty($CONFIG['log_date_format']))
1167     $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
75fd64 1168   
a0c4cb 1169   $date = date($CONFIG['log_date_format']);
T 1170   
75fd64 1171   // trigger logging hook
T 1172   if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
a0c4cb 1173     $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
75fd64 1174     $name = $log['name'];
T 1175     $line = $log['line'];
a0c4cb 1176     $date = $log['date'];
75fd64 1177     if ($log['abort'])
20e251 1178       return true;
75fd64 1179   }
0ad27c 1180  
b77d0d 1181   if ($CONFIG['log_driver'] == 'syslog') {
75fd64 1182     $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
5cf7aa 1183     syslog($prio, $line);
186938 1184     return true;
75fd64 1185   }
T 1186   else {
5cf7aa 1187     $line = sprintf("[%s]: %s\n", $date, $line);
A 1188
b77d0d 1189     // log_driver == 'file' is assumed here
A 1190     if (empty($CONFIG['log_dir']))
1191       $CONFIG['log_dir'] = INSTALL_PATH.'logs';
c9ca6a 1192
b77d0d 1193     // try to open specific log file for writing
cb8961 1194     $logfile = $CONFIG['log_dir'].'/'.$name;
T 1195     if ($fp = @fopen($logfile, 'a')) {
5cf7aa 1196       fwrite($fp, $line);
c9ca6a 1197       fflush($fp);
b77d0d 1198       fclose($fp);
186938 1199       return true;
c39957 1200     }
cb8961 1201     else
T 1202       trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
c39957 1203   }
186938 1204   return false;
b77d0d 1205 }
c39957 1206
cc9570 1207
6d969b 1208 /**
354455 1209  * Write login data (name, ID, IP address) to the 'userlogins' log file.
5c461b 1210  *
A 1211  * @return void
354455 1212  */
A 1213 function rcmail_log_login()
1214 {
1215   global $RCMAIL;
1216
1217   if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1218     return;
1219
1220   write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
ad399a 1221     $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip()));
A 1222 }
1223
1224
1225 /**
1226  * Returns remote IP address and forwarded addresses if found
1227  *
1228  * @return string Remote IP address(es)
1229  */
1230 function rcmail_remote_ip()
1231 {
1232     $address = $_SERVER['REMOTE_ADDR'];
1233
1234     // append the NGINX X-Real-IP header, if set
1235     if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1236         $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1237     }
1238     // append the X-Forwarded-For header, if set
1239     if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1240         $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1241     }
1242
1243     if (!empty($remote_ip))
1244         $address .= '(' . implode(',', $remote_ip) . ')';
1245
1246     return $address;
354455 1247 }
A 1248
1249
1250 /**
b46e5b 1251  * Check whether the HTTP referer matches the current request
T 1252  *
1253  * @return boolean True if referer is the same host+path, false if not
1254  */
1255 function rcube_check_referer()
1256 {
1257   $uri = parse_url($_SERVER['REQUEST_URI']);
1258   $referer = parse_url(rc_request_header('Referer'));
1259   return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
1260 }
1261
1262
1263 /**
6d969b 1264  * @access private
5c461b 1265  * @return mixed
6d969b 1266  */
15a9d1 1267 function rcube_timer()
533e86 1268 {
T 1269   return microtime(true);
1270 }
ad399a 1271
15a9d1 1272
6d969b 1273 /**
T 1274  * @access private
5c461b 1275  * @return void
6d969b 1276  */
8bc018 1277 function rcube_print_time($timer, $label='Timer', $dest='console')
533e86 1278 {
15a9d1 1279   static $print_count = 0;
T 1280   
1281   $print_count++;
1282   $now = rcube_timer();
1283   $diff = $now-$timer;
1284   
1285   if (empty($label))
1286     $label = 'Timer '.$print_count;
1287   
8bc018 1288   write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
533e86 1289 }
15a9d1 1290
93be5b 1291
6d969b 1292 /**
T 1293  * Return the mailboxlist in HTML
1294  *
1295  * @param array Named parameters
1296  * @return string HTML code for the gui object
1297  */
93be5b 1298 function rcmail_mailbox_list($attrib)
62e542 1299 {
25f80d 1300   global $RCMAIL;
93be5b 1301   static $a_mailboxes;
bff88d 1302
b822b6 1303   $attrib += array('maxlength' => 100, 'realnames' => false);
93be5b 1304
S 1305   // add some labels to client
112c91 1306   $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
bff88d 1307
93be5b 1308   $type = $attrib['type'] ? $attrib['type'] : 'ul';
6d6e06 1309   unset($attrib['type']);
T 1310
93be5b 1311   if ($type=='ul' && !$attrib['id'])
S 1312     $attrib['id'] = 'rcmboxlist';
1313
1314   // get mailbox list
25f80d 1315   $mbox_name = $RCMAIL->imap->get_mailbox_name();
bff88d 1316
93be5b 1317   // build the folders tree
62e542 1318   if (empty($a_mailboxes)) {
93be5b 1319     // get mailbox list
25f80d 1320     $a_folders = $RCMAIL->imap->list_mailboxes();
T 1321     $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
93be5b 1322     $a_mailboxes = array();
S 1323
1324     foreach ($a_folders as $folder)
1325       rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
62e542 1326   }
10f08e 1327
f62d5f 1328   // allow plugins to alter the folder tree or to localize folder names
4fa127 1329   $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
93be5b 1330
bff88d 1331   if ($type == 'select') {
6d6e06 1332     $select = new html_select($attrib);
bff88d 1333
6d6e06 1334     // add no-selection option
T 1335     if ($attrib['noselection'])
bff88d 1336       $select->add(rcube_label($attrib['noselection']), '');
A 1337
f62d5f 1338     rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
62e542 1339     $out = $select->show();
6d6e06 1340   }
T 1341   else {
f89f03 1342     $js_mailboxlist = array();
f62d5f 1343     $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
bff88d 1344
25f80d 1345     $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
f89f03 1346     $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
25f80d 1347     $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
T 1348   }
93be5b 1349
6d6e06 1350   return $out;
62e542 1351 }
93be5b 1352
S 1353
cb3538 1354 /**
T 1355  * Return the mailboxlist as html_select object
1356  *
1357  * @param array Named parameters
5c461b 1358  * @return html_select HTML drop-down object
cb3538 1359  */
T 1360 function rcmail_mailbox_select($p = array())
1361 {
1362   global $RCMAIL;
bff88d 1363
93a88c 1364   $p += array('maxlength' => 100, 'realnames' => false);
cb3538 1365   $a_mailboxes = array();
af3c04 1366
A 1367   if ($p['unsubscribed'])
1368     $list = $RCMAIL->imap->list_unsubscribed();
1369   else
1370     $list = $RCMAIL->imap->list_mailboxes();
1371
1372   foreach ($list as $folder)
93a88c 1373     if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
A 1374       rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
cb3538 1375
T 1376   $select = new html_select($p);
1377   
1378   if ($p['noselection'])
1379     $select->add($p['noselection'], '');
1380     
64f20d 1381   rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
cb3538 1382   
T 1383   return $select;
1384 }
93be5b 1385
S 1386
6d969b 1387 /**
T 1388  * Create a hierarchical array of the mailbox list
1389  * @access private
5c461b 1390  * @return void
6d969b 1391  */
93be5b 1392 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
f89f03 1393 {
a5a4bf 1394   global $RCMAIL;
A 1395
93be5b 1396   $pos = strpos($folder, $delm);
10f08e 1397
f89f03 1398   if ($pos !== false) {
93be5b 1399     $subFolders = substr($folder, $pos+1);
S 1400     $currentFolder = substr($folder, 0, $pos);
10f08e 1401
A 1402     // sometimes folder has a delimiter as the last character
448409 1403     if (!strlen($subFolders))
10f08e 1404       $virtual = false;
A 1405     else if (!isset($arrFolders[$currentFolder]))
1406       $virtual = true;
1407     else
1408       $virtual = $arrFolders[$currentFolder]['virtual'];
f89f03 1409   }
T 1410   else {
93be5b 1411     $subFolders = false;
S 1412     $currentFolder = $folder;
f89f03 1413     $virtual = false;
T 1414   }
93be5b 1415
S 1416   $path .= $currentFolder;
1417
3870be 1418   // Check \Noselect option (if options are in cache)
A 1419   if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
a5a4bf 1420     $virtual = in_array('\\Noselect', $opts);
A 1421   }
1422
f89f03 1423   if (!isset($arrFolders[$currentFolder])) {
6d6e06 1424     $arrFolders[$currentFolder] = array(
T 1425       'id' => $path,
a5897a 1426       'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
f89f03 1427       'virtual' => $virtual,
6d6e06 1428       'folders' => array());
f89f03 1429   }
T 1430   else
1431     $arrFolders[$currentFolder]['virtual'] = $virtual;
93be5b 1432
448409 1433   if (strlen($subFolders))
93be5b 1434     rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
f89f03 1435 }
bff88d 1436
93be5b 1437
6d969b 1438 /**
T 1439  * Return html for a structured list &lt;ul&gt; for the mailbox tree
1440  * @access private
5c461b 1441  * @return string
6d969b 1442  */
f89f03 1443 function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
T 1444 {
25f80d 1445   global $RCMAIL, $CONFIG;
bff88d 1446
f89f03 1447   $maxlength = intval($attrib['maxlength']);
T 1448   $realnames = (bool)$attrib['realnames'];
1449   $msgcounts = $RCMAIL->imap->get_cache('messagecount');
93be5b 1450
S 1451   $idx = 0;
1452   $out = '';
f89f03 1453   foreach ($arrFolders as $key => $folder) {
cb3538 1454     $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
6d6e06 1455     $title = null;
93be5b 1456
f89f03 1457     if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
cb3bad 1458       $foldername = rcube_label($folder_class);
f89f03 1459     }
T 1460     else {
93be5b 1461       $foldername = $folder['name'];
S 1462
1463       // shorten the folder name to a given length
f89f03 1464       if ($maxlength && $maxlength > 1) {
6f2f2d 1465         $fname = abbreviate_string($foldername, $maxlength);
93be5b 1466         if ($fname != $foldername)
6d6e06 1467           $title = $foldername;
93be5b 1468         $foldername = $fname;
S 1469       }
f89f03 1470     }
93be5b 1471
S 1472     // make folder name safe for ids and class names
8ca0c7 1473     $folder_id = asciiwords($folder['id'], true, '_');
6d6e06 1474     $classes = array('mailbox');
93be5b 1475
S 1476     // set special class for Sent, Drafts, Trash and Junk
1477     if ($folder['id']==$CONFIG['sent_mbox'])
6d6e06 1478       $classes[] = 'sent';
93be5b 1479     else if ($folder['id']==$CONFIG['drafts_mbox'])
6d6e06 1480       $classes[] = 'drafts';
93be5b 1481     else if ($folder['id']==$CONFIG['trash_mbox'])
6d6e06 1482       $classes[] = 'trash';
93be5b 1483     else if ($folder['id']==$CONFIG['junk_mbox'])
6d6e06 1484       $classes[] = 'junk';
d6497f 1485     else if ($folder['id']=='INBOX')
A 1486       $classes[] = 'inbox';
6d6e06 1487     else
d6497f 1488       $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
bff88d 1489
6d6e06 1490     $classes[] = $zebra_class;
bff88d 1491
6d6e06 1492     if ($folder['id'] == $mbox_name)
T 1493       $classes[] = 'selected';
93be5b 1494
f5aa16 1495     $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
f89f03 1496     $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
bff88d 1497
f89f03 1498     if ($folder['virtual'])
T 1499       $classes[] = 'virtual';
1500     else if ($unread)
1501       $classes[] = 'unread';
f5aa16 1502
6d6e06 1503     $js_name = JQ($folder['id']);
f89f03 1504     $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
T 1505     $link_attrib = $folder['virtual'] ? array() : array(
1506       'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1507       'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1508       'title' => $title,
1509     );
1510
6d6e06 1511     $out .= html::tag('li', array(
T 1512         'id' => "rcmli".$folder_id,
1513         'class' => join(' ', $classes),
1514         'noclose' => true),
f89f03 1515       html::a($link_attrib, $html_name) .
e1eb70 1516       (!empty($folder['folders']) ? html::div(array(
T 1517         'class' => ($collapsed ? 'collapsed' : 'expanded'),
1518         'style' => "position:absolute",
1519         'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1520       ), '&nbsp;') : ''));
bff88d 1521
f89f03 1522     $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
bff88d 1523
f89f03 1524     if (!empty($folder['folders'])) {
T 1525       $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1526         rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1527     }
93be5b 1528
S 1529     $out .= "</li>\n";
1530     $idx++;
f89f03 1531   }
93be5b 1532
S 1533   return $out;
f89f03 1534 }
93be5b 1535
S 1536
6d969b 1537 /**
T 1538  * Return html for a flat list <select> for the mailbox tree
1539  * @access private
5c461b 1540  * @return string
6d969b 1541  */
64f20d 1542 function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
bff88d 1543 {
93be5b 1544   $out = '';
bff88d 1545
A 1546   foreach ($arrFolders as $key=>$folder) {
64f20d 1547     if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
cb3bad 1548       $foldername = rcube_label($folder_class);
bff88d 1549     else {
93be5b 1550       $foldername = $folder['name'];
bff88d 1551
93be5b 1552       // shorten the folder name to a given length
S 1553       if ($maxlength && $maxlength>1)
6f2f2d 1554         $foldername = abbreviate_string($foldername, $maxlength);
bff88d 1555     }
93be5b 1556
6d6e06 1557     $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
93be5b 1558
S 1559     if (!empty($folder['folders']))
64f20d 1560       $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
bff88d 1561   }
93be5b 1562
S 1563   return $out;
bff88d 1564 }
93be5b 1565
cb3bad 1566
T 1567 /**
1568  * Return internal name for the given folder if it matches the configured special folders
1569  * @access private
5c461b 1570  * @return string
cb3bad 1571  */
T 1572 function rcmail_folder_classname($folder_id)
1573 {
1574   global $CONFIG;
1575
0a1921 1576   if ($folder_id == 'INBOX')
A 1577     return 'inbox';
1578
cb3bad 1579   // for these mailboxes we have localized labels and css classes
64c9b5 1580   foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
cb3bad 1581   {
f94629 1582     if ($folder_id == $CONFIG[$smbx.'_mbox'])
64c9b5 1583       return $smbx;
cb3bad 1584   }
T 1585 }
1586
1587
fed22f 1588 /**
T 1589  * Try to localize the given IMAP folder name.
1590  * UTF-7 decode it in case no localized text was found
1591  *
1592  * @param string Folder name
1593  * @return string Localized folder name in UTF-8 encoding
1594  */
1595 function rcmail_localize_foldername($name)
1596 {
1597   if ($folder_class = rcmail_folder_classname($name))
1598     return rcube_label($folder_class);
1599   else
a5897a 1600     return rcube_charset_convert($name, 'UTF7-IMAP');
fed22f 1601 }
T 1602
1603
af3c04 1604 function rcmail_quota_display($attrib)
A 1605 {
1606   global $OUTPUT;
1607
1608   if (!$attrib['id'])
1609     $attrib['id'] = 'rcmquotadisplay';
1610
1611   if(isset($attrib['display']))
1612     $_SESSION['quota_display'] = $attrib['display'];
1613
1614   $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1615
1616   $quota = rcmail_quota_content($attrib);
1617
1618   $OUTPUT->add_script('$(document).ready(function(){
1619     rcmail.set_quota('.json_serialize($quota).')});', 'foot');
1620
1621   return html::span($attrib, '');
1622 }
1623
1624
1625 function rcmail_quota_content($attrib=NULL)
1626 {
1627   global $RCMAIL;
1628
1629   $quota = $RCMAIL->imap->get_quota();
1630   $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1631
1632   $quota_result = (array) $quota;
1633   $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1634
1635   if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1636     $quota_result['title'] = rcube_label('unlimited');
1637     $quota_result['percent'] = 0;
1638   }
1639   else if ($quota['total']) {
1640     if (!isset($quota['percent']))
1641       $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1642
1643     $title = sprintf('%s / %s (%.0f%%)',
1644         show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1645         $quota_result['percent']);
1646
1647     $quota_result['title'] = $title;
1648
1649     if ($attrib['width'])
1650       $quota_result['width'] = $attrib['width'];
1651     if ($attrib['height'])
1652       $quota_result['height']    = $attrib['height'];
1653   }
1654   else {
1655     $quota_result['title'] = rcube_label('unknown');
1656     $quota_result['percent'] = 0;
1657   }
1658
1659   return $quota_result;
1660 }
1661
1662
b8ae50 1663 /**
90f81a 1664  * Outputs error message according to server error/response codes
A 1665  *
1666  * @param string Fallback message label
1667  * @param string Fallback message label arguments
1668  *
1669  * @return void
1670  */
1671 function rcmail_display_server_error($fallback=null, $fallback_args=null)
1672 {
1673     global $RCMAIL;
1674
1675     $err_code = $RCMAIL->imap->get_error_code();
1676     $res_code = $RCMAIL->imap->get_response_code();
1677
1678     if ($res_code == rcube_imap::NOPERM) {
1679         $RCMAIL->output->show_message('errornoperm', 'error');
1680     }
1681     else if ($res_code == rcube_imap::READONLY) {
1682         $RCMAIL->output->show_message('errorreadonly', 'error');
1683     }
1684     else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1685         $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1686     }
1687     else if ($fallback) {
1688         $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1689     }
1690
1691     return true;
1692 }
1693
1694
1695 /**
b8ae50 1696  * Output HTML editor scripts
A 1697  *
be5d4a 1698  * @param string Editor mode
5c461b 1699  * @return void
b8ae50 1700  */
A 1701 function rcube_html_editor($mode='')
1702 {
9ab7bc 1703   global $RCMAIL, $CONFIG;
b8ae50 1704
9b624b 1705   $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
9ab7bc 1706
A 1707   if ($hook['abort'])
1708     return;  
1709
1fb718 1710   $lang = strtolower($_SESSION['language']);
A 1711
1712   // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1713   $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1714
dc0040 1715   if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
A 1716     $lang = 'en';
1717
9ab7bc 1718   $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
A 1719   $RCMAIL->output->include_script('editor.js');
11a61a 1720   $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
A 1721     JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
4b410e 1722     'foot');
b8ae50 1723 }
aa055c 1724
T 1725
1726 /**
747289 1727  * Replaces TinyMCE's emoticon images with plain-text representation
A 1728  *
1729  * @param string HTML content
1730  * @return string HTML content
1731  */
1732 function rcmail_replace_emoticons($html)
1733 {
1734   $emoticons = array(
1735     '8-)' => 'smiley-cool',
1736     ':-#' => 'smiley-foot-in-mouth',
1737     ':-*' => 'smiley-kiss',
1738     ':-X' => 'smiley-sealed',
1739     ':-P' => 'smiley-tongue-out',
1740     ':-@' => 'smiley-yell',
1741     ":'(" => 'smiley-cry',
1742     ':-(' => 'smiley-frown',
1743     ':-D' => 'smiley-laughing',
1744     ':-)' => 'smiley-smile',
a06535 1745     ':-S' => 'smiley-undecided',
A 1746     ':-$' => 'smiley-embarassed',
1747     'O:-)' => 'smiley-innocent',
747289 1748     ':-|' => 'smiley-money-mouth',
a06535 1749     ':-O' => 'smiley-surprised',
747289 1750     ';-)' => 'smiley-wink',
A 1751   );
1752
1753   foreach ($emoticons as $idx => $file) {
1754     // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1755     $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1756     $replace[] = $idx;
1757   }
1758
1759   return preg_replace($search, $replace, $html);
1760 }
1761
1762
1763 /**
5818e4 1764  * Check if working in SSL mode
A 1765  *
1766  * @param integer HTTPS port number
1767  * @param boolean Enables 'use_https' option checking
5c461b 1768  * @return boolean
5818e4 1769  */
A 1770 function rcube_https_check($port=null, $use_https=true)
1771 {
1772   global $RCMAIL;
3a4c9f 1773
6c5aa6 1774   if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
5818e4 1775     return true;
3a4c9f 1776   if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
A 1777     return true;
5818e4 1778   if ($port && $_SERVER['SERVER_PORT'] == $port)
A 1779     return true;
929a50 1780   if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
5818e4 1781     return true;
A 1782
1783   return false;
1784 }
1785
bb8721 1786
5c461b 1787 /**
A 1788  * For backward compatibility.
1789  *
1790  * @global rcmail $RCMAIL
1791  * @param string $var_name Variable name.
1792  * @return void
1793  */
929a50 1794 function rcube_sess_unset($var_name=null)
A 1795 {
1796   global $RCMAIL;
1797
1798   $RCMAIL->session->remove($var_name);
1799 }
1800
5818e4 1801
5c461b 1802
A 1803 /**
1804  * Replaces hostname variables
1805  *
1806  * @param string $name Hostname
98cb0f 1807  * @param string $host Optional IMAP hostname
5c461b 1808  * @return string
A 1809  */
98cb0f 1810 function rcube_parse_host($name, $host='')
bb8721 1811 {
A 1812   // %n - host
1813   $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1814   // %d - domain name without first part, e.g. %d=mail.domain.tld, %m=domain.tld
1815   $d = preg_replace('/^[^\.]+\./', '', $n);
1816   // %h - IMAP host
98cb0f 1817   $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
11be93 1818   // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
A 1819   $z = preg_replace('/^[^\.]+\./', '', $h);
bb8721 1820
11be93 1821   $name = str_replace(array('%n', '%d', '%h', '%z'), array($n, $d, $h, $z), $name);
bb8721 1822   return $name;
A 1823 }
1824
1825
5818e4 1826 /**
e4acbb 1827  * E-mail address validation
5c461b 1828  *
A 1829  * @param string $email Email address
1830  * @param boolean $dns_check True to check dns
1831  * @return boolean
e4acbb 1832  */
1baeb6 1833 function check_email($email, $dns_check=true)
e4acbb 1834 {
A 1835   // Check for invalid characters
1836   if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1837     return false;
1838
aba092 1839   // Check for length limit specified by RFC 5321 (#1486453)
A 1840   if (strlen($email) > 254) 
1841     return false;
1842
1baeb6 1843   $email_array = explode('@', $email);
A 1844
aba092 1845   // Check that there's one @ symbol
1baeb6 1846   if (count($email_array) < 2)
e4acbb 1847     return false;
A 1848
1baeb6 1849   $domain_part = array_pop($email_array);
A 1850   $local_part = implode('@', $email_array);
1851
1852   // from PEAR::Validate
1853   $regexp = '&^(?:
1854     ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                  #1 quoted name
d7a5df 1855     ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))     #2 OR dot-atom (RFC5322)
1baeb6 1856     $&xi';
A 1857
1858   if (!preg_match($regexp, $local_part))
1859     return false;
e4acbb 1860
A 1861   // Check domain part
1baeb6 1862   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 1863     return true; // IP address
e4acbb 1864   else {
A 1865     // If not an IP address
1baeb6 1866     $domain_array = explode('.', $domain_part);
e4acbb 1867     if (sizeof($domain_array) < 2)
A 1868       return false; // Not enough parts to be a valid domain
1869
1baeb6 1870     foreach ($domain_array as $part)
A 1871       if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
e4acbb 1872         return false;
A 1873
1baeb6 1874     if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
e4acbb 1875       return true;
A 1876
0f3764 1877     if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
A 1878       $lookup = array();
1879       @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1880       foreach ($lookup as $line) {
1881         if (strpos($line, 'MX preference'))
1882           return true;
1883       }
1884       return false;
1885     }
e4acbb 1886
A 1887     // find MX record(s)
1baeb6 1888     if (getmxrr($domain_part, $mx_records))
e4acbb 1889       return true;
A 1890
1891     // find any DNS record
1baeb6 1892     if (checkdnsrr($domain_part, 'ANY'))
e4acbb 1893       return true;
A 1894   }
1895
1896   return false;
1897 }
1898
b46e5b 1899 /*
T 1900  * Idn_to_ascii wrapper.
1901  * Intl/Idn modules version of this function doesn't work with e-mail address
1902  */
1903 function rcube_idn_to_ascii($str)
1904 {
1905   return rcube_idn_convert($str, true);
1906 }
1907
1908 /*
1909  * Idn_to_ascii wrapper.
1910  * Intl/Idn modules version of this function doesn't work with e-mail address
1911  */
1912 function rcube_idn_to_utf8($str)
1913 {
1914   return rcube_idn_convert($str, false);
1915 }
1916
1917 function rcube_idn_convert($input, $is_utf=false)
1918 {
1919   if ($at = strpos($input, '@')) {
1920     $user   = substr($input, 0, $at);
1921     $domain = substr($input, $at+1);
1922   }
1923   else {
1924     $domain = $input;
1925   }
1926
1927   $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
1928
187833 1929   if ($domain === false) {
A 1930     return '';
1931   }
1932
b46e5b 1933   return $at ? $user . '@' . $domain : $domain;
T 1934 }
1935
e4acbb 1936
A 1937 /**
aa055c 1938  * Helper class to turn relative urls into absolute ones
T 1939  * using a predefined base
1940  */
1941 class rcube_base_replacer
1942 {
1943   private $base_url;
24c91e 1944
aa055c 1945   public function __construct($base)
T 1946   {
1947     $this->base_url = $base;
1948   }
24c91e 1949
aa055c 1950   public function callback($matches)
T 1951   {
1952     return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1953   }
1954 }
1955
874ff4 1956
24c91e 1957 /**
A 1958  * Throw system error and show error page
1959  *
1960  * @param array Named parameters
1961  *  - code: Error code (required)
1962  *  - type: Error type [php|db|imap|javascript] (required)
1963  *  - message: Error message
1964  *  - file: File where error occured
1965  *  - line: Line where error occured
1966  * @param boolean True to log the error
1967  * @param boolean Terminate script execution
1968  */
874ff4 1969 // may be defined in Installer
A 1970 if (!function_exists('raise_error')) {
24c91e 1971 function raise_error($arg=array(), $log=false, $terminate=false)
A 1972 {
1973     global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
1974
1975     // report bug (if not incompatible browser)
1976     if ($log && $arg['type'] && $arg['message'])
1977         log_bug($arg);
1978
1979     // display error page and terminate script
1980     if ($terminate) {
1981         $ERROR_CODE = $arg['code'];
1982         $ERROR_MESSAGE = $arg['message'];
1983         include('program/steps/utils/error.inc');
1984         exit;
1985     }
1986 }
874ff4 1987 }
24c91e 1988
A 1989
1990 /**
1991  * Report error according to configured debug_level
1992  *
1993  * @param array Named parameters
5c461b 1994  * @return void
24c91e 1995  * @see raise_error()
A 1996  */
1997 function log_bug($arg_arr)
1998 {
1999     global $CONFIG;
2000     $program = strtoupper($arg_arr['type']);
2001
2002     // write error to local log file
2003     if ($CONFIG['debug_level'] & 1) {
2004         $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
2005         $log_entry = sprintf("%s Error: %s%s (%s %s)",
2006             $program,
2007             $arg_arr['message'],
2008             $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
2009             $_SERVER['REQUEST_METHOD'],
2010             $_SERVER['REQUEST_URI'] . $post_query);
2011
2012         if (!write_log('errors', $log_entry)) {
2013             // send error to PHPs error handler if write_log didn't succeed
2014             trigger_error($arg_arr['message']);
2015         }
2016     }
2017
2018     // resport the bug to the global bug reporting system
2019     if ($CONFIG['debug_level'] & 2) {
2020         // TODO: Send error via HTTP
2021     }
2022
2023     // show error if debug_mode is on
2024     if ($CONFIG['debug_level'] & 4) {
2025         print "<b>$program Error";
2026
2027         if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2028             print " in $arg_arr[file] ($arg_arr[line])";
2029
2030         print ':</b>&nbsp;';
2031         print nl2br($arg_arr['message']);
2032         print '<br />';
2033         flush();
2034     }
2035 }
747289 2036