alecpl
2010-05-20 24c91ed78e64cb20b2ba08971d2c35979a2de856
commit | author | age
4e17e6 1 <?php
T 2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/main.inc                                              |
6  |                                                                       |
7  | This file is part of the RoundCube Webmail client                     |
cbbef3 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 /**
T 23  * RoundCube Webmail common functions
24  *
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
T 95
6d969b 96 /**
T 97  * Overwrite action variable
98  *
99  * @param string New action value
100  */
10a699 101 function rcmail_overwrite_action($action)
T 102   {
197601 103   $app = rcmail::get_instance();
T 104   $app->action = $action;
105   $app->output->set_env('action', $action);
10a699 106   }
T 107
108
41bece 109 /**
T 110  * Compose an URL for a specific action
111  *
112  * @param string  Request action
113  * @param array   More URL parameters
114  * @param string  Request task (omit if the same)
115  * @return The application URL
116  */
117 function rcmail_url($action, $p=array(), $task=null)
f11541 118 {
197601 119   $app = rcmail::get_instance();
fde466 120   return $app->url((array)$p + array('_action' => $action, 'task' => $task));
f11541 121 }
9fee0e 122
4e17e6 123
6d969b 124 /**
T 125  * Garbage collector function for temp files.
126  * Remove temp files older than two days
127  */
70d4b9 128 function rcmail_temp_gc()
1cded8 129   {
c5ee03 130   $rcmail = rcmail::get_instance();
A 131
132   $tmp = unslashify($rcmail->config->get('temp_dir'));
70d4b9 133   $expire = mktime() - 172800;  // expire in 48 hours
1cded8 134
70d4b9 135   if ($dir = opendir($tmp))
1cded8 136     {
70d4b9 137     while (($fname = readdir($dir)) !== false)
T 138       {
139       if ($fname{0} == '.')
140         continue;
141
142       if (filemtime($tmp.'/'.$fname) < $expire)
143         @unlink($tmp.'/'.$fname);
144       }
145
146     closedir($dir);
147     }
1cded8 148   }
T 149
150
6d969b 151 /**
T 152  * Garbage collector for cache entries.
153  * Remove all expired message cache records
154  */
29c64b 155 function rcmail_cache_gc()
cc9570 156   {
29c64b 157   $rcmail = rcmail::get_instance();
T 158   $db = $rcmail->get_dbh();
cc9570 159   
T 160   // get target timestamp
29c64b 161   $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
cc9570 162   
29c64b 163   $db->query("DELETE FROM ".get_table_name('messages')."
T 164              WHERE  created < " . $db->fromunixtime($ts));
165
166   $db->query("DELETE FROM ".get_table_name('cache')."
167               WHERE  created < " . $db->fromunixtime($ts));
cc9570 168   }
T 169
1cded8 170
2bca6e 171 /**
f52e7a 172  * Catch an error and throw an exception.
A 173  *
174  * @param  int    Level of the error
175  * @param  string Error message
176  */ 
177 function rcube_error_handler($errno, $errstr)
178   {
179   throw new ErrorException($errstr, 0, $errno);
180   }
181
182
183 /**
2bca6e 184  * Convert a string from one charset to another.
T 185  * Uses mbstring and iconv functions if possible
186  *
187  * @param  string Input string
188  * @param  string Suspected charset of the input string
f11541 189  * @param  string Target charset to convert to; defaults to RCMAIL_CHARSET
2bca6e 190  * @return Converted string
T 191  */
3f9edb 192 function rcube_charset_convert($str, $from, $to=NULL)
0af7e8 193   {
f52e7a 194   static $iconv_options = null;
77e232 195   static $mbstring_loaded = null;
A 196   static $mbstring_list = null;
197   static $convert_warning = false;
ce72e0 198   static $conv = null;
A 199   
dbe44c 200   $error = false;
f88d41 201
b77c9d 202   $to = empty($to) ? strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
dbe44c 203   $from = rcube_parse_charset($from);
65d710 204
c9a2fa 205   if ($from == $to || empty($str) || empty($from))
3f9edb 206     return $str;
ca85b1 207
b8e65c 208   // convert charset using iconv module  
ae8a60 209   if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
f52e7a 210     if ($iconv_options === null) {
895d4e 211       // ignore characters not available in output charset
A 212       $iconv_options = '//IGNORE';
f52e7a 213       if (iconv('', $iconv_options, '') === false) {
A 214         // iconv implementation does not support options
215         $iconv_options = '';
216       }
217     }
b69560 218
f52e7a 219     // throw an exception if iconv reports an illegal character in input
A 220     // it means that input string has been truncated
221     set_error_handler('rcube_error_handler', E_NOTICE);
222     try {
223       $_iconv = iconv($from, $to . $iconv_options, $str);
224     } catch (ErrorException $e) {
225       $_iconv = false;
226     }
227     restore_error_handler();
ae8a60 228     if ($_iconv !== false) {
f52e7a 229       return $_iconv;
0393da 230     }
ae8a60 231   }
5f56a5 232
f52e7a 233   if ($mbstring_loaded === null)
d99b93 234     $mbstring_loaded = extension_loaded('mbstring');
A 235     
197601 236   // convert charset using mbstring module
ae8a60 237   if ($mbstring_loaded) {
b19536 238     $aliases['WINDOWS-1257'] = 'ISO-8859-13';
88f66e 239     
f52e7a 240     if ($mbstring_list === null) {
77e232 241       $mbstring_list = mb_list_encodings();
A 242       $mbstring_list = array_map('strtoupper', $mbstring_list);
243     }
dbe44c 244
77e232 245     $mb_from = $aliases[$from] ? $aliases[$from] : $from;
A 246     $mb_to = $aliases[$to] ? $aliases[$to] : $to;
247     
248     // return if encoding found, string matches encoding and convert succeeded
ae8a60 249     if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
T 250       if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
251         return $out;
83dbb7 252     }
ae8a60 253   }
65d710 254
ce72e0 255   // convert charset using bundled classes/functions
a5897a 256   if ($to == 'UTF-8') {
A 257     if ($from == 'UTF7-IMAP') {
258       if ($_str = utf7_to_utf8($str))
ce72e0 259         return $_str;
a5897a 260     }
A 261     else if ($from == 'UTF-7') {
262       if ($_str = rcube_utf7_to_utf8($str))
ce72e0 263         return $_str;
a5897a 264     }
A 265     else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
ce72e0 266       return utf8_encode($str);
a5897a 267     }
ce72e0 268     else if (class_exists('utf8')) {
A 269       if (!$conv)
270         $conv = new utf8($from);
271       else
272         $conv->loadCharset($from);
273
274       if($_str = $conv->strToUtf8($str))
275         return $_str;
a5897a 276     }
ce72e0 277     $error = true;
ae8a60 278   }
a5897a 279   
3f9edb 280   // encode string for output
a5897a 281   if ($from == 'UTF-8') {
A 282     // @TODO: we need a function for UTF-7 (RFC2152) conversion
283     if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
284       if ($_str = utf8_to_utf7($str))
ce72e0 285         return $_str;
a5897a 286     }
A 287     else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
288       return utf8_decode($str);
289     }
ce72e0 290     else if (class_exists('utf8')) {
A 291       if (!$conv)
292         $conv = new utf8($to);
293       else
294         $conv->loadCharset($from);
295
296       if ($_str = $conv->strToUtf8($str))
297         return $_str;
a5897a 298     }
ce72e0 299     $error = true;
ae8a60 300   }
T 301   
1a7f99 302   // report error
ce72e0 303   if ($error && !$convert_warning) {
1a7f99 304     raise_error(array(
T 305       'code' => 500,
306       'type' => 'php',
307       'file' => __FILE__,
ce72e0 308       'line' => __LINE__,
dbe44c 309       'message' => "Could not convert string from $from to $to. Make sure iconv/mbstring is installed or lib/utf8.class is available."
1a7f99 310       ), true, false);
T 311     
312     $convert_warning = true;
ae8a60 313   }
1a7f99 314   
ce72e0 315   // return UTF-8 or original string
3f9edb 316   return $str;
0af7e8 317   }
3f9edb 318
0af7e8 319
2bca6e 320 /**
dbe44c 321  * Parse and validate charset name string (see #1485758).
A 322  * Sometimes charset string is malformed, there are also charset aliases 
323  * but we need strict names for charset conversion (specially utf8 class)
324  *
325  * @param  string  Input charset name
326  * @return The validated charset name
327  */
46a138 328 function rcube_parse_charset($input)
dbe44c 329   {
46a138 330   static $charsets = array();
A 331   $charset = strtoupper($input);
332
333   if (isset($charsets[$input]))
334     return $charsets[$input];
dbe44c 335
7d0b34 336   $charset = preg_replace(array(
A 337     '/^[^0-9A-Z]+/',    // e.g. _ISO-8859-JP$SIO
338     '/\$.*$/',        // e.g. _ISO-8859-JP$SIO
1e3271 339     '/UNICODE-1-1-*/',    // RFC1641/1642
7d0b34 340     ), '', $charset);
dbe44c 341
b1fb69 342   # Aliases: some of them from HTML5 spec.
dbe44c 343   $aliases = array(
ca85b1 344     'USASCII'       => 'WINDOWS-1252',
A 345     'ANSIX31101983' => 'WINDOWS-1252',
346     'ANSIX341968'   => 'WINDOWS-1252',
dbe44c 347     'UNKNOWN8BIT'   => 'ISO-8859-15',
5dc7c2 348     'UNKNOWN'       => 'ISO-8859-15',
A 349     'USERDEFINED'   => 'ISO-8859-15',
dbe44c 350     'KSC56011987'   => 'EUC-KR',
b1fb69 351     'GB2312'         => 'GBK',
A 352     'GB231280'        => 'GBK',
dbe44c 353     'UNICODE'        => 'UTF-8',
b1fb69 354     'UTF7IMAP'        => 'UTF7-IMAP',
A 355     'TIS620'        => 'WINDOWS-874',
356     'ISO88599'        => 'WINDOWS-1254',
357     'ISO885911'        => 'WINDOWS-874',
c8729e 358     'MACROMAN'        => 'MACINTOSH',
1e3271 359     '238'           => 'WINDOWS-1250',
A 360     '178'           => 'WINDOWS-1256',
361     '177'           => 'WINDOWS-1255',
362     '204'           => 'WINDOWS-1251',
363     '161'           => 'WINDOWS-1253',
364     '222'           => 'WINDOWS-874',
365     '134'           => 'GBK',
366     '238'           => 'WINDOWS-1250',
367     '128'           => 'SHIFT-JIS'
dbe44c 368   );
A 369
5dc7c2 370   // allow a-z and 0-9 only and remove X- prefix (e.g. X-ROMAN8 => ROMAN8)
1e3271 371   $str = preg_replace(array('/[^A-Z0-9]/', '/^X+/'), '', $charset);
dbe44c 372
A 373   if (isset($aliases[$str]))
46a138 374     $result = $aliases[$str];
A 375   // UTF
376   else if (preg_match('/U[A-Z][A-Z](7|8|16|32)(BE|LE)*/', $str, $m))
377     $result = 'UTF-' . $m[1] . $m[2];
378   // ISO-8859
379   else if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
ca85b1 380     $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
46a138 381     // some clients sends windows-1252 text as latin1,
A 382     // it is safe to use windows-1252 for all latin1
383     $result = $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
ca85b1 384     }
1e3271 385   // handle broken charset names e.g. WINDOWS-1250HTTP-EQUIVCONTENT-TYPE
46a138 386   else if (preg_match('/(WIN|WINDOWS)([0-9]+)/', $str, $m)) {
A 387     $result = 'WINDOWS-' . $m[2];
388     }
389   else {
390     $result = $charset;
1e3271 391     }
A 392
46a138 393   $charsets[$input] = $result;
A 394
395   return $result;
dbe44c 396   }
a5897a 397
A 398
399 /**
400  * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
401  *
402  * @param  string  Input string
403  * @return The converted string
404  */
405 function rcube_utf7_to_utf8($str)
406 {
407   $Index_64 = array(
408     0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
409     0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
410     0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
411     1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
412     0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
413     1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
414     0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
415     1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
416   );
417
418   $u7len = strlen($str);
419   $str = strval($str);
420   $res = '';
421
422   for ($i=0; $u7len > 0; $i++, $u7len--)
423   {
424     $u7 = $str[$i];
425     if ($u7 == '+')
426     {
427       $i++;
428       $u7len--;
429       $ch = '';
430
431       for (; $u7len > 0; $i++, $u7len--)
432       {
433         $u7 = $str[$i];
434
435         if (!$Index_64[ord($u7)])
436           break;
437
438     $ch .= $u7;
439       }
440
441       if ($ch == '') {
442         if ($u7 == '-')
443           $res .= '+';
444         continue;
445       }
446
447       $res .= rcube_utf16_to_utf8(base64_decode($ch));
448     }
449     else
450     {
451       $res .= $u7;
452     }
453   }
454
455   return $res;
456 }
457
458 /**
459  * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
460  *
461  * @param  string  Input string
462  * @return The converted string
463  */
464 function rcube_utf16_to_utf8($str)
465 {
466   $len = strlen($str);
467   $dec = '';
468
469   for ($i = 0; $i < $len; $i += 2) {
470     $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
471     if ($c >= 0x0001 && $c <= 0x007F) {
472       $dec .= chr($c);
473     } else if ($c > 0x07FF) {
474       $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
475       $dec .= chr(0x80 | (($c >>  6) & 0x3F));
476       $dec .= chr(0x80 | (($c >>  0) & 0x3F));
477     } else {
478       $dec .= chr(0xC0 | (($c >>  6) & 0x1F));
479       $dec .= chr(0x80 | (($c >>  0) & 0x3F));
480     }
481   }
482   return $dec;
483 }
dbe44c 484
A 485
486 /**
2bca6e 487  * Replacing specials characters to a specific encoding type
T 488  *
489  * @param  string  Input string
490  * @param  string  Encoding type: text|html|xml|js|url
491  * @param  string  Replace mode for tags: show|replace|remove
492  * @param  boolean Convert newlines
493  * @return The quoted string
494  */
1cded8 495 function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
T 496   {
257782 497   static $html_encode_arr = false;
A 498   static $js_rep_table = false;
499   static $xml_rep_table = false;
1cded8 500
T 501   if (!$enctype)
c8a21d 502     $enctype = $OUTPUT->type;
1cded8 503
T 504   // encode for HTML output
505   if ($enctype=='html')
506     {
507     if (!$html_encode_arr)
508       {
0af7e8 509       $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);        
1cded8 510       unset($html_encode_arr['?']);
T 511       }
512
513     $ltpos = strpos($str, '<');
514     $encode_arr = $html_encode_arr;
515
516     // don't replace quotes and html tags
517     if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
518       {
519       unset($encode_arr['"']);
520       unset($encode_arr['<']);
521       unset($encode_arr['>']);
10c92b 522       unset($encode_arr['&']);
1cded8 523       }
T 524     else if ($mode=='remove')
525       $str = strip_tags($str);
674a0f 526     
T 527     // avoid douple quotation of &
fdebae 528     $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', strtr($str, $encode_arr));
0af7e8 529       
1cded8 530     return $newlines ? nl2br($out) : $out;
T 531     }
532
2bca6e 533   // if the replace tables for XML and JS are not yet defined
257782 534   if ($js_rep_table===false)
1cded8 535     {
f91a49 536     $js_rep_table = $xml_rep_table = array();
88375f 537     $xml_rep_table['&'] = '&amp;';
1cded8 538
T 539     for ($c=160; $c<256; $c++)  // can be increased to support more charsets
bc6ac4 540       $xml_rep_table[chr($c)] = "&#$c;";
1cded8 541
T 542     $xml_rep_table['"'] = '&quot;';
c21d6d 543     $js_rep_table['"'] = '\\"';
T 544     $js_rep_table["'"] = "\\'";
3d54e6 545     $js_rep_table["\\"] = "\\\\";
bc6ac4 546     // Unicode line and paragraph separators (#1486310)
A 547     $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
548     $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
1cded8 549     }
T 550
74eb6c 551   // encode for javascript use
A 552   if ($enctype=='js')
553     return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
554
555   // encode for plaintext
556   if ($enctype=='text')
557     return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
558
559   if ($enctype=='url')
560     return rawurlencode($str);
561
2bca6e 562   // encode for XML
1cded8 563   if ($enctype=='xml')
T 564     return strtr($str, $xml_rep_table);
565
566   // no encoding given -> return original string
567   return $str;
2bca6e 568   }
f11541 569   
2bca6e 570 /**
6d969b 571  * Quote a given string.
T 572  * Shortcut function for rep_specialchars_output
573  *
574  * @return string HTML-quoted string
575  * @see rep_specialchars_output()
2bca6e 576  */
T 577 function Q($str, $mode='strict', $newlines=TRUE)
578   {
579   return rep_specialchars_output($str, 'html', $mode, $newlines);
580   }
581
582 /**
6d969b 583  * Quote a given string for javascript output.
T 584  * Shortcut function for rep_specialchars_output
585  * 
586  * @return string JS-quoted string
587  * @see rep_specialchars_output()
2bca6e 588  */
18e2a3 589 function JQ($str)
2bca6e 590   {
18e2a3 591   return rep_specialchars_output($str, 'js');
10a699 592   }
ea7c46 593
T 594
595 /**
596  * Read input value and convert it for internal use
597  * Performs stripslashes() and charset conversion if necessary
598  * 
599  * @param  string   Field name to read
600  * @param  int      Source to get value from (GPC)
601  * @param  boolean  Allow HTML tags in field value
602  * @param  string   Charset to convert into
603  * @return string   Field value or NULL if not available
604  */
605 function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
759696 606 {
ea7c46 607   $value = NULL;
T 608   
609   if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
610     $value = $_GET[$fname];
611   else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
612     $value = $_POST[$fname];
613   else if ($source==RCUBE_INPUT_GPC)
614     {
026d68 615     if (isset($_POST[$fname]))
ea7c46 616       $value = $_POST[$fname];
026d68 617     else if (isset($_GET[$fname]))
T 618       $value = $_GET[$fname];
ea7c46 619     else if (isset($_COOKIE[$fname]))
T 620       $value = $_COOKIE[$fname];
621     }
c5ee03 622
72b140 623   return parse_input_value($value, $allow_html, $charset);
A 624 }
625
626 /**
627  * Parse/validate input value. See get_input_value()
628  * Performs stripslashes() and charset conversion if necessary
629  * 
630  * @param  string   Input value
631  * @param  boolean  Allow HTML tags in field value
632  * @param  string   Charset to convert into
633  * @return string   Parsed value
634  */
635 function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
636 {
637   global $OUTPUT;
638
c5ee03 639   if (empty($value))
A 640     return $value;
72b140 641
A 642   if (is_array($value)) {
643     foreach ($value as $idx => $val)
644       $value[$idx] = parse_input_value($val, $allow_html, $charset);
645     return $value;
646   }
c5ee03 647
6e47c0 648   // strip single quotes if magic_quotes_sybase is enabled
T 649   if (ini_get('magic_quotes_sybase'))
650     $value = str_replace("''", "'", $value);
ea7c46 651   // strip slashes if magic_quotes enabled
6e47c0 652   else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
ea7c46 653     $value = stripslashes($value);
T 654
655   // remove HTML tags if not allowed    
656   if (!$allow_html)
657     $value = strip_tags($value);
658   
659   // convert to internal charset
72b140 660   if (is_object($OUTPUT) && $charset)
026d68 661     return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
T 662   else
663     return $value;
759696 664 }
T 665
666 /**
667  * Convert array of request parameters (prefixed with _)
668  * to a regular array with non-prefixed keys.
669  *
670  * @param  int   Source to get value from (GPC)
671  * @return array Hash array with all request parameters
672  */
673 function request2param($mode = RCUBE_INPUT_GPC)
674 {
675   $out = array();
676   $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
677   foreach ($src as $key => $value) {
678     $fname = $key[0] == '_' ? substr($key, 1) : $key;
679     $out[$fname] = get_input_value($key, $mode);
ea7c46 680   }
759696 681   
T 682   return $out;
683 }
ea7c46 684
d5342a 685 /**
T 686  * Remove all non-ascii and non-word chars
8ca0c7 687  * except ., -, _
d5342a 688  */
8ca0c7 689 function asciiwords($str, $css_id = false, $replace_with = '')
d5342a 690 {
6d6e06 691   $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
8ca0c7 692   return preg_replace("/[^$allowed]/i", $replace_with, $str);
d5342a 693 }
6d969b 694
e34ae1 695 /**
T 696  * Remove single and double quotes from given string
6d969b 697  *
T 698  * @param string Input value
699  * @return string Dequoted string
e34ae1 700  */
T 701 function strip_quotes($str)
702 {
703   return preg_replace('/[\'"]/', '', $str);
704 }
10a699 705
6d969b 706
3cf664 707 /**
T 708  * Remove new lines characters from given string
6d969b 709  *
T 710  * @param string Input value
711  * @return string Stripped string
3cf664 712  */
T 713 function strip_newlines($str)
714 {
715   return preg_replace('/[\r\n]/', '', $str);
f11541 716 }
4e17e6 717
T 718
6d969b 719 /**
T 720  * Create a HTML table based on the given data
721  *
722  * @param  array  Named table attributes
723  * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
724  * @param  array  List of cols to show
725  * @param  string Name of the identifier col
726  * @return string HTML table code
727  */
d1d2c4 728 function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
4e17e6 729   {
83a763 730   global $RCMAIL;
4e17e6 731   
83a763 732   $table = new html_table(/*array('cols' => count($a_show_cols))*/);
4e17e6 733     
83a763 734   // add table header
4e17e6 735   foreach ($a_show_cols as $col)
83a763 736     $table->add_header($col, Q(rcube_label($col)));
4e17e6 737   
T 738   $c = 0;
d1d2c4 739   if (!is_array($table_data)) 
83a763 740   {
T 741     $db = $RCMAIL->get_dbh();
742     while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
4e17e6 743     {
83a763 744       $zebra_class = $c % 2 ? 'even' : 'odd';
1fcad1 745       $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class));
d1d2c4 746
S 747       // format each col
748       foreach ($a_show_cols as $col)
83a763 749         $table->add($col, Q($sql_arr[$col]));
T 750       
d1d2c4 751       $c++;
S 752     }
83a763 753   }
d1d2c4 754   else 
83a763 755   {
d1d2c4 756     foreach ($table_data as $row_data)
83a763 757     {
T 758       $zebra_class = $c % 2 ? 'even' : 'odd';
b69560 759       if (!empty($row_data['class']))
A 760         $zebra_class .= ' '.$row_data['class'];
761
1fcad1 762       $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class));
d1d2c4 763
S 764       // format each col
765       foreach ($a_show_cols as $col)
83a763 766         $table->add($col, Q($row_data[$col]));
T 767         
d1d2c4 768       $c++;
4e17e6 769     }
83a763 770   }
4e17e6 771
83a763 772   return $table->show($attrib);
4e17e6 773   }
T 774
775
a0109c 776 /**
S 777  * Create an edit field for inclusion on a form
778  * 
779  * @param string col field name
780  * @param string value field value
781  * @param array attrib HTML element attributes for field
782  * @param string type HTML element type (default 'text')
783  * @return string HTML field definition
784  */
4e17e6 785 function rcmail_get_edit_field($col, $value, $attrib, $type='text')
T 786   {
787   $fname = '_'.$col;
788   $attrib['name'] = $fname;
789   
790   if ($type=='checkbox')
791     {
792     $attrib['value'] = '1';
47124c 793     $input = new html_checkbox($attrib);
4e17e6 794     }
T 795   else if ($type=='textarea')
796     {
797     $attrib['cols'] = $attrib['size'];
47124c 798     $input = new html_textarea($attrib);
4e17e6 799     }
T 800   else
47124c 801     $input = new html_inputfield($attrib);
4e17e6 802
T 803   // use value from post
597170 804   if (!empty($_POST[$fname]))
407dcf 805     $value = get_input_value($fname, RCUBE_INPUT_POST,
A 806         $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
4e17e6 807
T 808   $out = $input->show($value);
809          
810   return $out;
f11541 811   }
T 812
813
6d969b 814 /**
97bd2c 815  * Replace all css definitions with #container [def]
a3e5b4 816  * and remove css-inlined scripting
97bd2c 817  *
T 818  * @param string CSS source code
819  * @param string Container ID to use as prefix
820  * @return string Modified CSS source
821  */
aa055c 822 function rcmail_mod_css_styles($source, $container_id)
97bd2c 823   {
T 824   $last_pos = 0;
aa055c 825   $replacements = new rcube_string_replacer;
a3e5b4 826   
T 827   // ignore the whole block if evil styles are detected
c5ee03 828   $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entity_decode($source));
1c499a 829   if (preg_match('/expression|behavior|url\(|import/', $stripped))
aa055c 830     return '/* evil! */';
97bd2c 831
T 832   // cut out all contents between { and }
833   while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
834   {
aa055c 835     $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
T 836     $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
97bd2c 837     $last_pos = $pos+2;
T 838   }
aa055c 839   
a3e5b4 840   // remove html comments and add #container to each tag selector.
97bd2c 841   // also replace body definition because we also stripped off the <body> tag
T 842   $styles = preg_replace(
843     array(
844       '/(^\s*<!--)|(-->\s*$)/',
845       '/(^\s*|,\s*|\}\s*)([a-z0-9\._#][a-z0-9\.\-_]*)/im',
1608f4 846       "/$container_id\s+body/i",
97bd2c 847     ),
T 848     array(
849       '',
850       "\\1#$container_id \\2",
1608f4 851       "$container_id div.rcmBody",
97bd2c 852     ),
T 853     $source);
1608f4 854   
T 855   // put block contents back in
aa055c 856   $styles = $replacements->resolve($styles);
97bd2c 857
T 858   return $styles;
859   }
fba1f5 860
97bd2c 861
T 862 /**
1c499a 863  * Decode escaped entities used by known XSS exploits.
T 864  * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
865  *
866  * @param string CSS content to decode
867  * @return string Decoded string
868  */
c5ee03 869 function rcmail_xss_entity_decode($content)
1c499a 870 {
T 871   $out = html_entity_decode(html_entity_decode($content));
c5ee03 872   $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
85a913 873   $out = preg_replace('#/\*.*\*/#Um', '', $out);
1c499a 874   return $out;
T 875 }
876
aa055c 877
T 878 /**
c5ee03 879  * preg_replace_callback callback for rcmail_xss_entity_decode_callback
aa055c 880  *
T 881  * @param array matches result from preg_replace_callback
882  * @return string decoded entity
883  */ 
c5ee03 884 function rcmail_xss_entity_decode_callback($matches)
aa055c 885
T 886   return chr(hexdec($matches[1]));
887 }
1c499a 888
T 889 /**
6d969b 890  * Compose a valid attribute string for HTML tags
T 891  *
892  * @param array Named tag attributes
893  * @param array List of allowed attributes
894  * @return string HTML formatted attribute string
895  */
4e17e6 896 function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
T 897   {
898   // allow the following attributes to be added to the <iframe> tag
899   $attrib_str = '';
900   foreach ($allowed_attribs as $a)
901     if (isset($attrib[$a]))
fe79b1 902       $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
4e17e6 903
T 904   return $attrib_str;
905   }
906
907
6d969b 908 /**
T 909  * Convert a HTML attribute string attributes to an associative array (name => value)
910  *
911  * @param string Input string
912  * @return array Key-value pairs of parsed attributes
913  */
fe79b1 914 function parse_attrib_string($str)
T 915   {
916   $attrib = array();
d59aaa 917   preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
fe79b1 918
T 919   // convert attributes to an associative array (name => value)
cc97ea 920   if ($regs) {
T 921     foreach ($regs as $attr) {
922       $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
923     }
924   }
fe79b1 925
T 926   return $attrib;
927   }
928
4e17e6 929
6d969b 930 /**
T 931  * Convert the given date to a human readable form
932  * This uses the date formatting properties from config
933  *
934  * @param mixed Date representation (string or timestamp)
935  * @param string Date format to use
936  * @return string Formatted date string
937  */
4e17e6 938 function format_date($date, $format=NULL)
T 939   {
197601 940   global $CONFIG;
4e17e6 941   
4647e1 942   $ts = NULL;
ea090c 943
4e17e6 944   if (is_numeric($date))
T 945     $ts = $date;
b076a4 946   else if (!empty($date))
ea090c 947     {
33875d 948     // support non-standard "GMTXXXX" literal
A 949     $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
c9ca6a 950     // if date parsing fails, we have a date in non-rfc format.
S 951     // remove token from the end and try again
952     while ((($ts = @strtotime($date))===false) || ($ts < 0))
ea090c 953       {
A 954         $d = explode(' ', $date);
197601 955         array_pop($d);
T 956         if (!$d) break;
957         $date = implode(' ', $d);
ea090c 958       }
A 959     }
960
4647e1 961   if (empty($ts))
b076a4 962     return '';
4647e1 963    
T 964   // get user's timezone
62784a 965   if ($CONFIG['timezone'] === 'auto')
T 966     $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
c8ae24 967   else {
T 968     $tz = $CONFIG['timezone'];
969     if ($CONFIG['dst_active'])
970       $tz++;
971   }
4e17e6 972
T 973   // convert time to user's timezone
4647e1 974   $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
4e17e6 975   
T 976   // get current timestamp in user's timezone
977   $now = time();  // local time
978   $now -= (int)date('Z'); // make GMT time
4647e1 979   $now += ($tz * 3600); // user's time
c45eb5 980   $now_date = getdate($now);
4e17e6 981
749b07 982   $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
T 983   $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
4e17e6 984
539df6 985   // define date format depending on current time
A 986   if (!$format) {
987     if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now)
988       return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
989     else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
990       $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
991     else
992       $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
993     }
4e17e6 994
b6b593 995   // strftime() format
A 996   if (preg_match('/%[a-z]+/i', $format))
997     return strftime($format, $timestamp);
4e17e6 998
T 999   // parse format string manually in order to provide localized weekday and month names
1000   // an alternative would be to convert the date() format string to fit with strftime()
1001   $out = '';
1002   for($i=0; $i<strlen($format); $i++)
1003     {
1004     if ($format{$i}=='\\')  // skip escape chars
1005       continue;
1006     
1007     // write char "as-is"
1008     if ($format{$i}==' ' || $format{$i-1}=='\\')
1009       $out .= $format{$i};
1010     // weekday (short)
1011     else if ($format{$i}=='D')
1012       $out .= rcube_label(strtolower(date('D', $timestamp)));
1013     // weekday long
1014     else if ($format{$i}=='l')
1015       $out .= rcube_label(strtolower(date('l', $timestamp)));
1016     // month name (short)
1017     else if ($format{$i}=='M')
1018       $out .= rcube_label(strtolower(date('M', $timestamp)));
1019     // month name (long)
1020     else if ($format{$i}=='F')
7479cc 1021       $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
5b1de5 1022     else if ($format{$i}=='x')
A 1023       $out .= strftime('%x %X', $timestamp);
4e17e6 1024     else
T 1025       $out .= date($format{$i}, $timestamp);
1026     }
1027   
1028   return $out;
1029   }
1030
1031
6d969b 1032 /**
a9bfe2 1033  * Compose a valid representation of name and e-mail address
6d969b 1034  *
T 1035  * @param string E-mail address
1036  * @param string Person name
1037  * @return string Formatted string
1038  */
f11541 1039 function format_email_recipient($email, $name='')
T 1040   {
1041   if ($name && $name != $email)
0c6f4b 1042     {
T 1043     // Special chars as defined by RFC 822 need to in quoted string (or escaped).
a9bfe2 1044     return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
0c6f4b 1045     }
f11541 1046   else
a9bfe2 1047     return trim($email);
f11541 1048   }
T 1049
1050
1051
c39957 1052 /****** debugging functions ********/
T 1053
1054
1055 /**
1056  * Print or write debug messages
1057  *
1058  * @param mixed Debug message or data
1059  */
ed132e 1060 function console()
c39957 1061   {
cc97ea 1062   $args = func_get_args();
76db10 1063
759696 1064   if (class_exists('rcmail', false)) {
T 1065     $rcmail = rcmail::get_instance();
1066     if (is_object($rcmail->plugins))
1067       $rcmail->plugins->exec_hook('console', $args);
1068   }
cc97ea 1069
ed132e 1070   $msg = array();
cc97ea 1071   foreach ($args as $arg)
76db10 1072     $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
c39957 1073
T 1074   if (!($GLOBALS['CONFIG']['debug_level'] & 4))
ed132e 1075     write_log('console', join(";\n", $msg));
197601 1076   else if ($GLOBALS['OUTPUT']->ajax_call)
ed132e 1077     print "/*\n " . join(";\n", $msg) . " \n*/\n";
c39957 1078   else
T 1079     {
1080     print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
ed132e 1081     print join(";<br/>\n", $msg);
c39957 1082     print "</pre></div>\n";
T 1083     }
1084   }
1085
1086
1087 /**
1088  * Append a line to a logfile in the logs directory.
1089  * Date will be added automatically to the line.
1090  *
653242 1091  * @param $name name of log file
S 1092  * @param line Line to append
c39957 1093  */
T 1094 function write_log($name, $line)
1095   {
75fd64 1096   global $CONFIG, $RCMAIL;
e170b4 1097
T 1098   if (!is_string($line))
1099     $line = var_export($line, true);
0ad27c 1100  
A 1101   if (empty($CONFIG['log_date_format']))
1102     $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
75fd64 1103   
a0c4cb 1104   $date = date($CONFIG['log_date_format']);
T 1105   
75fd64 1106   // trigger logging hook
T 1107   if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
a0c4cb 1108     $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
75fd64 1109     $name = $log['name'];
T 1110     $line = $log['line'];
a0c4cb 1111     $date = $log['date'];
75fd64 1112     if ($log['abort'])
20e251 1113       return true;
75fd64 1114   }
0ad27c 1115  
a0c4cb 1116   $log_entry = sprintf("[%s]: %s\n", $date, $line);
c9ca6a 1117
b77d0d 1118   if ($CONFIG['log_driver'] == 'syslog') {
75fd64 1119     $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
b77d0d 1120     syslog($prio, $log_entry);
186938 1121     return true;
75fd64 1122   }
T 1123   else {
b77d0d 1124     // log_driver == 'file' is assumed here
A 1125     if (empty($CONFIG['log_dir']))
1126       $CONFIG['log_dir'] = INSTALL_PATH.'logs';
c9ca6a 1127
b77d0d 1128     // try to open specific log file for writing
cb8961 1129     $logfile = $CONFIG['log_dir'].'/'.$name;
T 1130     if ($fp = @fopen($logfile, 'a')) {
b77d0d 1131       fwrite($fp, $log_entry);
c9ca6a 1132       fflush($fp);
b77d0d 1133       fclose($fp);
186938 1134       return true;
c39957 1135     }
cb8961 1136     else
T 1137       trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
c39957 1138   }
186938 1139   return false;
b77d0d 1140 }
c39957 1141
cc9570 1142
6d969b 1143 /**
354455 1144  * Write login data (name, ID, IP address) to the 'userlogins' log file.
A 1145  */
1146 function rcmail_log_login()
1147 {
1148   global $RCMAIL;
1149
1150   if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1151     return;
1152
1153   $address = $_SERVER['REMOTE_ADDR'];
1154   // append the NGINX X-Real-IP header, if set
1155   if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1156     $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1157   }
1158   // append the X-Forwarded-For header, if set
1159   if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1160     $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1161   }
1162
1163   if (!empty($remote_ip))
1164     $address .= '(' . implode(',', $remote_ip) . ')';
1165
1166   write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1167     $RCMAIL->user->get_username(), $RCMAIL->user->ID, $address));
1168 }
1169
1170
1171 /**
6d969b 1172  * @access private
T 1173  */
15a9d1 1174 function rcube_timer()
533e86 1175 {
T 1176   return microtime(true);
1177 }
15a9d1 1178   
T 1179
6d969b 1180 /**
T 1181  * @access private
1182  */
8bc018 1183 function rcube_print_time($timer, $label='Timer', $dest='console')
533e86 1184 {
15a9d1 1185   static $print_count = 0;
T 1186   
1187   $print_count++;
1188   $now = rcube_timer();
1189   $diff = $now-$timer;
1190   
1191   if (empty($label))
1192     $label = 'Timer '.$print_count;
1193   
8bc018 1194   write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
533e86 1195 }
15a9d1 1196
93be5b 1197
6d969b 1198 /**
T 1199  * Return the mailboxlist in HTML
1200  *
1201  * @param array Named parameters
1202  * @return string HTML code for the gui object
1203  */
93be5b 1204 function rcmail_mailbox_list($attrib)
62e542 1205 {
25f80d 1206   global $RCMAIL;
93be5b 1207   static $a_mailboxes;
64f20d 1208   
b822b6 1209   $attrib += array('maxlength' => 100, 'realnames' => false);
93be5b 1210
S 1211   // add some labels to client
112c91 1212   $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
93be5b 1213   
S 1214   $type = $attrib['type'] ? $attrib['type'] : 'ul';
6d6e06 1215   unset($attrib['type']);
T 1216
93be5b 1217   if ($type=='ul' && !$attrib['id'])
S 1218     $attrib['id'] = 'rcmboxlist';
1219
1220   // get mailbox list
25f80d 1221   $mbox_name = $RCMAIL->imap->get_mailbox_name();
93be5b 1222   
S 1223   // build the folders tree
62e542 1224   if (empty($a_mailboxes)) {
93be5b 1225     // get mailbox list
25f80d 1226     $a_folders = $RCMAIL->imap->list_mailboxes();
T 1227     $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
93be5b 1228     $a_mailboxes = array();
S 1229
1230     foreach ($a_folders as $folder)
1231       rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
62e542 1232   }
f62d5f 1233   
T 1234   // allow plugins to alter the folder tree or to localize folder names
4fa127 1235   $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
93be5b 1236
6d6e06 1237   if ($type=='select') {
T 1238     $select = new html_select($attrib);
1239     
1240     // add no-selection option
1241     if ($attrib['noselection'])
1242       $select->add(rcube_label($attrib['noselection']), '0');
1243     
f62d5f 1244     rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
62e542 1245     $out = $select->show();
6d6e06 1246   }
T 1247   else {
f89f03 1248     $js_mailboxlist = array();
f62d5f 1249     $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
f89f03 1250     
25f80d 1251     $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
f89f03 1252     $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
25f80d 1253     $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
T 1254   }
93be5b 1255
6d6e06 1256   return $out;
62e542 1257 }
93be5b 1258
S 1259
cb3538 1260 /**
T 1261  * Return the mailboxlist as html_select object
1262  *
1263  * @param array Named parameters
1264  * @return object html_select HTML drop-down object
1265  */
1266 function rcmail_mailbox_select($p = array())
1267 {
1268   global $RCMAIL;
1269   
93a88c 1270   $p += array('maxlength' => 100, 'realnames' => false);
cb3538 1271   $a_mailboxes = array();
T 1272   
1273   foreach ($RCMAIL->imap->list_mailboxes() as $folder)
93a88c 1274     if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
A 1275       rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
cb3538 1276
T 1277   $select = new html_select($p);
1278   
1279   if ($p['noselection'])
1280     $select->add($p['noselection'], '');
1281     
64f20d 1282   rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
cb3538 1283   
T 1284   return $select;
1285 }
93be5b 1286
S 1287
6d969b 1288 /**
T 1289  * Create a hierarchical array of the mailbox list
1290  * @access private
1291  */
93be5b 1292 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
f89f03 1293 {
93be5b 1294   $pos = strpos($folder, $delm);
f89f03 1295   if ($pos !== false) {
93be5b 1296     $subFolders = substr($folder, $pos+1);
S 1297     $currentFolder = substr($folder, 0, $pos);
f89f03 1298     $virtual = !isset($arrFolders[$currentFolder]);
T 1299   }
1300   else {
93be5b 1301     $subFolders = false;
S 1302     $currentFolder = $folder;
f89f03 1303     $virtual = false;
T 1304   }
93be5b 1305
S 1306   $path .= $currentFolder;
1307
f89f03 1308   if (!isset($arrFolders[$currentFolder])) {
6d6e06 1309     $arrFolders[$currentFolder] = array(
T 1310       'id' => $path,
a5897a 1311       'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
f89f03 1312       'virtual' => $virtual,
6d6e06 1313       'folders' => array());
f89f03 1314   }
T 1315   else
1316     $arrFolders[$currentFolder]['virtual'] = $virtual;
93be5b 1317
S 1318   if (!empty($subFolders))
1319     rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
f89f03 1320 }
93be5b 1321   
S 1322
6d969b 1323 /**
T 1324  * Return html for a structured list &lt;ul&gt; for the mailbox tree
1325  * @access private
1326  */
f89f03 1327 function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
T 1328 {
25f80d 1329   global $RCMAIL, $CONFIG;
f89f03 1330   
T 1331   $maxlength = intval($attrib['maxlength']);
1332   $realnames = (bool)$attrib['realnames'];
1333   $msgcounts = $RCMAIL->imap->get_cache('messagecount');
93be5b 1334
S 1335   $idx = 0;
1336   $out = '';
f89f03 1337   foreach ($arrFolders as $key => $folder) {
cb3538 1338     $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
6d6e06 1339     $title = null;
93be5b 1340
f89f03 1341     if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
cb3bad 1342       $foldername = rcube_label($folder_class);
f89f03 1343     }
T 1344     else {
93be5b 1345       $foldername = $folder['name'];
S 1346
1347       // shorten the folder name to a given length
f89f03 1348       if ($maxlength && $maxlength > 1) {
6f2f2d 1349         $fname = abbreviate_string($foldername, $maxlength);
93be5b 1350         if ($fname != $foldername)
6d6e06 1351           $title = $foldername;
93be5b 1352         $foldername = $fname;
S 1353       }
f89f03 1354     }
93be5b 1355
S 1356     // make folder name safe for ids and class names
8ca0c7 1357     $folder_id = asciiwords($folder['id'], true, '_');
6d6e06 1358     $classes = array('mailbox');
93be5b 1359
S 1360     // set special class for Sent, Drafts, Trash and Junk
1361     if ($folder['id']==$CONFIG['sent_mbox'])
6d6e06 1362       $classes[] = 'sent';
93be5b 1363     else if ($folder['id']==$CONFIG['drafts_mbox'])
6d6e06 1364       $classes[] = 'drafts';
93be5b 1365     else if ($folder['id']==$CONFIG['trash_mbox'])
6d6e06 1366       $classes[] = 'trash';
93be5b 1367     else if ($folder['id']==$CONFIG['junk_mbox'])
6d6e06 1368       $classes[] = 'junk';
d6497f 1369     else if ($folder['id']=='INBOX')
A 1370       $classes[] = 'inbox';
6d6e06 1371     else
d6497f 1372       $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
6d6e06 1373       
T 1374     $classes[] = $zebra_class;
1375     
1376     if ($folder['id'] == $mbox_name)
1377       $classes[] = 'selected';
93be5b 1378
f5aa16 1379     $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
f89f03 1380     $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
T 1381     
1382     if ($folder['virtual'])
1383       $classes[] = 'virtual';
1384     else if ($unread)
1385       $classes[] = 'unread';
f5aa16 1386
6d6e06 1387     $js_name = JQ($folder['id']);
f89f03 1388     $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
T 1389     $link_attrib = $folder['virtual'] ? array() : array(
1390       'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1391       'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1392       'title' => $title,
1393     );
1394
6d6e06 1395     $out .= html::tag('li', array(
T 1396         'id' => "rcmli".$folder_id,
1397         'class' => join(' ', $classes),
1398         'noclose' => true),
f89f03 1399       html::a($link_attrib, $html_name) .
e1eb70 1400       (!empty($folder['folders']) ? html::div(array(
T 1401         'class' => ($collapsed ? 'collapsed' : 'expanded'),
1402         'style' => "position:absolute",
1403         'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1404       ), '&nbsp;') : ''));
6d6e06 1405     
f89f03 1406     $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
T 1407     
1408     if (!empty($folder['folders'])) {
1409       $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1410         rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1411     }
93be5b 1412
S 1413     $out .= "</li>\n";
1414     $idx++;
f89f03 1415   }
93be5b 1416
S 1417   return $out;
f89f03 1418 }
93be5b 1419
S 1420
6d969b 1421 /**
T 1422  * Return html for a flat list <select> for the mailbox tree
1423  * @access private
1424  */
64f20d 1425 function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
93be5b 1426   {
S 1427   $idx = 0;
1428   $out = '';
1429   foreach ($arrFolders as $key=>$folder)
1430     {
64f20d 1431     if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
cb3bad 1432       $foldername = rcube_label($folder_class);
93be5b 1433     else
S 1434       {
1435       $foldername = $folder['name'];
1436       
1437       // shorten the folder name to a given length
1438       if ($maxlength && $maxlength>1)
6f2f2d 1439         $foldername = abbreviate_string($foldername, $maxlength);
93be5b 1440       }
S 1441
6d6e06 1442     $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
93be5b 1443
S 1444     if (!empty($folder['folders']))
64f20d 1445       $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
93be5b 1446
S 1447     $idx++;
1448     }
1449
1450   return $out;
1451   }
1452
cb3bad 1453
T 1454 /**
1455  * Return internal name for the given folder if it matches the configured special folders
1456  * @access private
1457  */
1458 function rcmail_folder_classname($folder_id)
1459 {
1460   global $CONFIG;
1461
0a1921 1462   if ($folder_id == 'INBOX')
A 1463     return 'inbox';
1464
cb3bad 1465   // for these mailboxes we have localized labels and css classes
64c9b5 1466   foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
cb3bad 1467   {
f94629 1468     if ($folder_id == $CONFIG[$smbx.'_mbox'])
64c9b5 1469       return $smbx;
cb3bad 1470   }
T 1471 }
1472
1473
fed22f 1474 /**
T 1475  * Try to localize the given IMAP folder name.
1476  * UTF-7 decode it in case no localized text was found
1477  *
1478  * @param string Folder name
1479  * @return string Localized folder name in UTF-8 encoding
1480  */
1481 function rcmail_localize_foldername($name)
1482 {
1483   if ($folder_class = rcmail_folder_classname($name))
1484     return rcube_label($folder_class);
1485   else
a5897a 1486     return rcube_charset_convert($name, 'UTF7-IMAP');
fed22f 1487 }
T 1488
1489
b8ae50 1490 /**
A 1491  * Output HTML editor scripts
1492  *
be5d4a 1493  * @param string Editor mode
b8ae50 1494  */
A 1495 function rcube_html_editor($mode='')
1496 {
9ab7bc 1497   global $RCMAIL, $CONFIG;
b8ae50 1498
dc0040 1499   $hook = $RCMAIL->plugins->exec_hook('hmtl_editor', array('mode' => $mode));
9ab7bc 1500
A 1501   if ($hook['abort'])
1502     return;  
1503
dc0040 1504   $lang = strtolower(substr($_SESSION['language'], 0, 2));
A 1505   if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1506     $lang = 'en';
1507
9ab7bc 1508   $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
A 1509   $RCMAIL->output->include_script('editor.js');
1510   $RCMAIL->output->add_script('rcmail_editor_init("$__skin_path",
dc0040 1511     "'.JQ($lang).'", '.intval($CONFIG['enable_spellcheck']).', "'.$mode.'");');
b8ae50 1512 }
aa055c 1513
T 1514
1515 /**
5818e4 1516  * Check if working in SSL mode
A 1517  *
1518  * @param integer HTTPS port number
1519  * @param boolean Enables 'use_https' option checking
1520  */
1521 function rcube_https_check($port=null, $use_https=true)
1522 {
1523   global $RCMAIL;
1524   
6c5aa6 1525   if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
5818e4 1526     return true;
A 1527   if ($port && $_SERVER['SERVER_PORT'] == $port)
1528     return true;
929a50 1529   if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
5818e4 1530     return true;
A 1531
1532   return false;
1533 }
1534
929a50 1535 // for backward compatibility
A 1536 function rcube_sess_unset($var_name=null)
1537 {
1538   global $RCMAIL;
1539
1540   $RCMAIL->session->remove($var_name);
1541 }
1542
5818e4 1543
A 1544 /**
e4acbb 1545  * E-mail address validation
A 1546  */
1baeb6 1547 function check_email($email, $dns_check=true)
e4acbb 1548 {
A 1549   // Check for invalid characters
1550   if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1551     return false;
1552
aba092 1553   // Check for length limit specified by RFC 5321 (#1486453)
A 1554   if (strlen($email) > 254) 
1555     return false;
1556
1baeb6 1557   $email_array = explode('@', $email);
A 1558
aba092 1559   // Check that there's one @ symbol
1baeb6 1560   if (count($email_array) < 2)
e4acbb 1561     return false;
A 1562
1baeb6 1563   $domain_part = array_pop($email_array);
A 1564   $local_part = implode('@', $email_array);
1565
1566   // from PEAR::Validate
1567   $regexp = '&^(?:
1568     ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                  #1 quoted name
1569     ([-\w!\#\$%\&\'*+~/^`|{}]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}]+)*))     #2 OR dot-atom
1570     $&xi';
1571
1572   if (!preg_match($regexp, $local_part))
1573     return false;
e4acbb 1574
A 1575   // Check domain part
1baeb6 1576   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 1577     return true; // IP address
e4acbb 1578   else {
A 1579     // If not an IP address
1baeb6 1580     $domain_array = explode('.', $domain_part);
e4acbb 1581     if (sizeof($domain_array) < 2)
A 1582       return false; // Not enough parts to be a valid domain
1583
1baeb6 1584     foreach ($domain_array as $part)
A 1585       if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
e4acbb 1586         return false;
A 1587
1baeb6 1588     if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
e4acbb 1589       return true;
A 1590
0f3764 1591     if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
A 1592       $lookup = array();
1593       @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1594       foreach ($lookup as $line) {
1595         if (strpos($line, 'MX preference'))
1596           return true;
1597       }
1598       return false;
1599     }
e4acbb 1600
A 1601     // find MX record(s)
1baeb6 1602     if (getmxrr($domain_part, $mx_records))
e4acbb 1603       return true;
A 1604
1605     // find any DNS record
1baeb6 1606     if (checkdnsrr($domain_part, 'ANY'))
e4acbb 1607       return true;
A 1608   }
1609
1610   return false;
1611 }
1612
1613
1614 /**
aa055c 1615  * Helper class to turn relative urls into absolute ones
T 1616  * using a predefined base
1617  */
1618 class rcube_base_replacer
1619 {
1620   private $base_url;
24c91e 1621
aa055c 1622   public function __construct($base)
T 1623   {
1624     $this->base_url = $base;
1625   }
24c91e 1626
aa055c 1627   public function callback($matches)
T 1628   {
1629     return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1630   }
1631 }
1632
24c91e 1633 /**
A 1634  * Throw system error and show error page
1635  *
1636  * @param array Named parameters
1637  *  - code: Error code (required)
1638  *  - type: Error type [php|db|imap|javascript] (required)
1639  *  - message: Error message
1640  *  - file: File where error occured
1641  *  - line: Line where error occured
1642  * @param boolean True to log the error
1643  * @param boolean Terminate script execution
1644  */
1645 function raise_error($arg=array(), $log=false, $terminate=false)
1646 {
1647     global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
1648
1649     // report bug (if not incompatible browser)
1650     if ($log && $arg['type'] && $arg['message'])
1651         log_bug($arg);
1652
1653     // display error page and terminate script
1654     if ($terminate) {
1655         $ERROR_CODE = $arg['code'];
1656         $ERROR_MESSAGE = $arg['message'];
1657         include('program/steps/utils/error.inc');
1658         exit;
1659     }
1660 }
1661
1662
1663 /**
1664  * Report error according to configured debug_level
1665  *
1666  * @param array Named parameters
1667  * @see raise_error()
1668  */
1669 function log_bug($arg_arr)
1670 {
1671     global $CONFIG;
1672     $program = strtoupper($arg_arr['type']);
1673
1674     // write error to local log file
1675     if ($CONFIG['debug_level'] & 1) {
1676         $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
1677         $log_entry = sprintf("%s Error: %s%s (%s %s)",
1678             $program,
1679             $arg_arr['message'],
1680             $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
1681             $_SERVER['REQUEST_METHOD'],
1682             $_SERVER['REQUEST_URI'] . $post_query);
1683
1684         if (!write_log('errors', $log_entry)) {
1685             // send error to PHPs error handler if write_log didn't succeed
1686             trigger_error($arg_arr['message']);
1687         }
1688     }
1689
1690     // resport the bug to the global bug reporting system
1691     if ($CONFIG['debug_level'] & 2) {
1692         // TODO: Send error via HTTP
1693     }
1694
1695     // show error if debug_mode is on
1696     if ($CONFIG['debug_level'] & 4) {
1697         print "<b>$program Error";
1698
1699         if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
1700             print " in $arg_arr[file] ($arg_arr[line])";
1701
1702         print ':</b>&nbsp;';
1703         print nl2br($arg_arr['message']);
1704         print '<br />';
1705         flush();
1706     }
1707 }
1708
d1d2c4 1709 ?>