vbenincasa
2010-06-09 d0b981757ab416dfd182e6b91e7f9a66132116f9
commit | author | age
d0b981 1 <?php
4e17e6 2
T 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
54759c 735   if (!$attrib['noheader'])
A 736     foreach ($a_show_cols as $col)
737       $table->add_header($col, Q(rcube_label($col)));
4e17e6 738   
T 739   $c = 0;
d1d2c4 740   if (!is_array($table_data)) 
83a763 741   {
T 742     $db = $RCMAIL->get_dbh();
743     while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
4e17e6 744     {
83a763 745       $zebra_class = $c % 2 ? 'even' : 'odd';
1fcad1 746       $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class));
d1d2c4 747
S 748       // format each col
749       foreach ($a_show_cols as $col)
83a763 750         $table->add($col, Q($sql_arr[$col]));
T 751       
d1d2c4 752       $c++;
S 753     }
83a763 754   }
d1d2c4 755   else 
83a763 756   {
d1d2c4 757     foreach ($table_data as $row_data)
83a763 758     {
T 759       $zebra_class = $c % 2 ? 'even' : 'odd';
b69560 760       if (!empty($row_data['class']))
A 761         $zebra_class .= ' '.$row_data['class'];
762
1fcad1 763       $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class));
d1d2c4 764
S 765       // format each col
766       foreach ($a_show_cols as $col)
83a763 767         $table->add($col, Q($row_data[$col]));
T 768         
d1d2c4 769       $c++;
4e17e6 770     }
83a763 771   }
4e17e6 772
83a763 773   return $table->show($attrib);
4e17e6 774   }
T 775
776
a0109c 777 /**
S 778  * Create an edit field for inclusion on a form
779  * 
780  * @param string col field name
781  * @param string value field value
782  * @param array attrib HTML element attributes for field
783  * @param string type HTML element type (default 'text')
784  * @return string HTML field definition
785  */
4e17e6 786 function rcmail_get_edit_field($col, $value, $attrib, $type='text')
T 787   {
788   $fname = '_'.$col;
789   $attrib['name'] = $fname;
790   
791   if ($type=='checkbox')
792     {
793     $attrib['value'] = '1';
47124c 794     $input = new html_checkbox($attrib);
4e17e6 795     }
T 796   else if ($type=='textarea')
797     {
798     $attrib['cols'] = $attrib['size'];
47124c 799     $input = new html_textarea($attrib);
4e17e6 800     }
T 801   else
47124c 802     $input = new html_inputfield($attrib);
4e17e6 803
T 804   // use value from post
597170 805   if (!empty($_POST[$fname]))
407dcf 806     $value = get_input_value($fname, RCUBE_INPUT_POST,
A 807         $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
4e17e6 808
T 809   $out = $input->show($value);
810          
811   return $out;
f11541 812   }
T 813
814
6d969b 815 /**
97bd2c 816  * Replace all css definitions with #container [def]
a3e5b4 817  * and remove css-inlined scripting
97bd2c 818  *
T 819  * @param string CSS source code
820  * @param string Container ID to use as prefix
821  * @return string Modified CSS source
822  */
aa055c 823 function rcmail_mod_css_styles($source, $container_id)
97bd2c 824   {
T 825   $last_pos = 0;
aa055c 826   $replacements = new rcube_string_replacer;
a3e5b4 827   
T 828   // ignore the whole block if evil styles are detected
c5ee03 829   $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entity_decode($source));
1c499a 830   if (preg_match('/expression|behavior|url\(|import/', $stripped))
aa055c 831     return '/* evil! */';
97bd2c 832
T 833   // cut out all contents between { and }
834   while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
835   {
aa055c 836     $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
T 837     $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
97bd2c 838     $last_pos = $pos+2;
T 839   }
aa055c 840   
a3e5b4 841   // remove html comments and add #container to each tag selector.
97bd2c 842   // also replace body definition because we also stripped off the <body> tag
T 843   $styles = preg_replace(
844     array(
845       '/(^\s*<!--)|(-->\s*$)/',
d0b981 846       '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
1608f4 847       "/$container_id\s+body/i",
97bd2c 848     ),
T 849     array(
850       '',
851       "\\1#$container_id \\2",
1608f4 852       "$container_id div.rcmBody",
97bd2c 853     ),
T 854     $source);
1608f4 855   
T 856   // put block contents back in
aa055c 857   $styles = $replacements->resolve($styles);
97bd2c 858
T 859   return $styles;
860   }
fba1f5 861
97bd2c 862
T 863 /**
1c499a 864  * Decode escaped entities used by known XSS exploits.
T 865  * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
866  *
867  * @param string CSS content to decode
868  * @return string Decoded string
869  */
c5ee03 870 function rcmail_xss_entity_decode($content)
1c499a 871 {
T 872   $out = html_entity_decode(html_entity_decode($content));
c5ee03 873   $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
85a913 874   $out = preg_replace('#/\*.*\*/#Um', '', $out);
1c499a 875   return $out;
T 876 }
877
aa055c 878
T 879 /**
c5ee03 880  * preg_replace_callback callback for rcmail_xss_entity_decode_callback
aa055c 881  *
T 882  * @param array matches result from preg_replace_callback
883  * @return string decoded entity
884  */ 
c5ee03 885 function rcmail_xss_entity_decode_callback($matches)
aa055c 886
T 887   return chr(hexdec($matches[1]));
888 }
1c499a 889
T 890 /**
6d969b 891  * Compose a valid attribute string for HTML tags
T 892  *
893  * @param array Named tag attributes
894  * @param array List of allowed attributes
895  * @return string HTML formatted attribute string
896  */
4e17e6 897 function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
T 898   {
899   // allow the following attributes to be added to the <iframe> tag
900   $attrib_str = '';
901   foreach ($allowed_attribs as $a)
902     if (isset($attrib[$a]))
fe79b1 903       $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
4e17e6 904
T 905   return $attrib_str;
906   }
907
908
6d969b 909 /**
T 910  * Convert a HTML attribute string attributes to an associative array (name => value)
911  *
912  * @param string Input string
913  * @return array Key-value pairs of parsed attributes
914  */
fe79b1 915 function parse_attrib_string($str)
T 916   {
917   $attrib = array();
d59aaa 918   preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
fe79b1 919
T 920   // convert attributes to an associative array (name => value)
cc97ea 921   if ($regs) {
T 922     foreach ($regs as $attr) {
923       $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
924     }
925   }
fe79b1 926
T 927   return $attrib;
928   }
929
4e17e6 930
6d969b 931 /**
T 932  * Convert the given date to a human readable form
933  * This uses the date formatting properties from config
934  *
935  * @param mixed Date representation (string or timestamp)
936  * @param string Date format to use
937  * @return string Formatted date string
938  */
4e17e6 939 function format_date($date, $format=NULL)
T 940   {
197601 941   global $CONFIG;
4e17e6 942   
4647e1 943   $ts = NULL;
ea090c 944
4e17e6 945   if (is_numeric($date))
T 946     $ts = $date;
b076a4 947   else if (!empty($date))
ea090c 948     {
33875d 949     // support non-standard "GMTXXXX" literal
A 950     $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
c9ca6a 951     // if date parsing fails, we have a date in non-rfc format.
S 952     // remove token from the end and try again
953     while ((($ts = @strtotime($date))===false) || ($ts < 0))
ea090c 954       {
A 955         $d = explode(' ', $date);
197601 956         array_pop($d);
T 957         if (!$d) break;
958         $date = implode(' ', $d);
ea090c 959       }
A 960     }
961
4647e1 962   if (empty($ts))
b076a4 963     return '';
4647e1 964    
T 965   // get user's timezone
62784a 966   if ($CONFIG['timezone'] === 'auto')
T 967     $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
c8ae24 968   else {
T 969     $tz = $CONFIG['timezone'];
970     if ($CONFIG['dst_active'])
971       $tz++;
972   }
4e17e6 973
T 974   // convert time to user's timezone
4647e1 975   $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
4e17e6 976   
T 977   // get current timestamp in user's timezone
978   $now = time();  // local time
979   $now -= (int)date('Z'); // make GMT time
4647e1 980   $now += ($tz * 3600); // user's time
c45eb5 981   $now_date = getdate($now);
4e17e6 982
749b07 983   $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
T 984   $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
4e17e6 985
539df6 986   // define date format depending on current time
A 987   if (!$format) {
988     if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now)
989       return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
990     else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
991       $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
992     else
993       $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
994     }
4e17e6 995
b6b593 996   // strftime() format
A 997   if (preg_match('/%[a-z]+/i', $format))
998     return strftime($format, $timestamp);
4e17e6 999
T 1000   // parse format string manually in order to provide localized weekday and month names
1001   // an alternative would be to convert the date() format string to fit with strftime()
1002   $out = '';
1003   for($i=0; $i<strlen($format); $i++)
1004     {
1005     if ($format{$i}=='\\')  // skip escape chars
1006       continue;
1007     
1008     // write char "as-is"
1009     if ($format{$i}==' ' || $format{$i-1}=='\\')
1010       $out .= $format{$i};
1011     // weekday (short)
1012     else if ($format{$i}=='D')
1013       $out .= rcube_label(strtolower(date('D', $timestamp)));
1014     // weekday long
1015     else if ($format{$i}=='l')
1016       $out .= rcube_label(strtolower(date('l', $timestamp)));
1017     // month name (short)
1018     else if ($format{$i}=='M')
1019       $out .= rcube_label(strtolower(date('M', $timestamp)));
1020     // month name (long)
1021     else if ($format{$i}=='F')
7479cc 1022       $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
5b1de5 1023     else if ($format{$i}=='x')
A 1024       $out .= strftime('%x %X', $timestamp);
4e17e6 1025     else
T 1026       $out .= date($format{$i}, $timestamp);
1027     }
1028   
1029   return $out;
1030   }
1031
1032
6d969b 1033 /**
a9bfe2 1034  * Compose a valid representation of name and e-mail address
6d969b 1035  *
T 1036  * @param string E-mail address
1037  * @param string Person name
1038  * @return string Formatted string
1039  */
f11541 1040 function format_email_recipient($email, $name='')
T 1041   {
1042   if ($name && $name != $email)
0c6f4b 1043     {
T 1044     // Special chars as defined by RFC 822 need to in quoted string (or escaped).
a9bfe2 1045     return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
0c6f4b 1046     }
f11541 1047   else
a9bfe2 1048     return trim($email);
f11541 1049   }
T 1050
1051
1052
c39957 1053 /****** debugging functions ********/
T 1054
1055
1056 /**
1057  * Print or write debug messages
1058  *
1059  * @param mixed Debug message or data
1060  */
ed132e 1061 function console()
c39957 1062   {
cc97ea 1063   $args = func_get_args();
76db10 1064
759696 1065   if (class_exists('rcmail', false)) {
T 1066     $rcmail = rcmail::get_instance();
1067     if (is_object($rcmail->plugins))
1068       $rcmail->plugins->exec_hook('console', $args);
1069   }
cc97ea 1070
ed132e 1071   $msg = array();
cc97ea 1072   foreach ($args as $arg)
76db10 1073     $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
c39957 1074
T 1075   if (!($GLOBALS['CONFIG']['debug_level'] & 4))
ed132e 1076     write_log('console', join(";\n", $msg));
197601 1077   else if ($GLOBALS['OUTPUT']->ajax_call)
ed132e 1078     print "/*\n " . join(";\n", $msg) . " \n*/\n";
c39957 1079   else
T 1080     {
1081     print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
ed132e 1082     print join(";<br/>\n", $msg);
c39957 1083     print "</pre></div>\n";
T 1084     }
1085   }
1086
1087
1088 /**
1089  * Append a line to a logfile in the logs directory.
1090  * Date will be added automatically to the line.
1091  *
653242 1092  * @param $name name of log file
S 1093  * @param line Line to append
c39957 1094  */
T 1095 function write_log($name, $line)
1096   {
75fd64 1097   global $CONFIG, $RCMAIL;
e170b4 1098
T 1099   if (!is_string($line))
1100     $line = var_export($line, true);
0ad27c 1101  
A 1102   if (empty($CONFIG['log_date_format']))
1103     $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
75fd64 1104   
a0c4cb 1105   $date = date($CONFIG['log_date_format']);
T 1106   
75fd64 1107   // trigger logging hook
T 1108   if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
a0c4cb 1109     $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
75fd64 1110     $name = $log['name'];
T 1111     $line = $log['line'];
a0c4cb 1112     $date = $log['date'];
75fd64 1113     if ($log['abort'])
20e251 1114       return true;
75fd64 1115   }
0ad27c 1116  
a0c4cb 1117   $log_entry = sprintf("[%s]: %s\n", $date, $line);
c9ca6a 1118
b77d0d 1119   if ($CONFIG['log_driver'] == 'syslog') {
75fd64 1120     $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
b77d0d 1121     syslog($prio, $log_entry);
186938 1122     return true;
75fd64 1123   }
T 1124   else {
b77d0d 1125     // log_driver == 'file' is assumed here
A 1126     if (empty($CONFIG['log_dir']))
1127       $CONFIG['log_dir'] = INSTALL_PATH.'logs';
c9ca6a 1128
b77d0d 1129     // try to open specific log file for writing
cb8961 1130     $logfile = $CONFIG['log_dir'].'/'.$name;
T 1131     if ($fp = @fopen($logfile, 'a')) {
b77d0d 1132       fwrite($fp, $log_entry);
c9ca6a 1133       fflush($fp);
b77d0d 1134       fclose($fp);
186938 1135       return true;
c39957 1136     }
cb8961 1137     else
T 1138       trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
c39957 1139   }
186938 1140   return false;
b77d0d 1141 }
c39957 1142
cc9570 1143
6d969b 1144 /**
354455 1145  * Write login data (name, ID, IP address) to the 'userlogins' log file.
A 1146  */
1147 function rcmail_log_login()
1148 {
1149   global $RCMAIL;
1150
1151   if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1152     return;
1153
1154   $address = $_SERVER['REMOTE_ADDR'];
1155   // append the NGINX X-Real-IP header, if set
1156   if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1157     $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1158   }
1159   // append the X-Forwarded-For header, if set
1160   if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1161     $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1162   }
1163
1164   if (!empty($remote_ip))
1165     $address .= '(' . implode(',', $remote_ip) . ')';
1166
1167   write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1168     $RCMAIL->user->get_username(), $RCMAIL->user->ID, $address));
1169 }
1170
1171
1172 /**
6d969b 1173  * @access private
T 1174  */
15a9d1 1175 function rcube_timer()
533e86 1176 {
T 1177   return microtime(true);
1178 }
15a9d1 1179   
T 1180
6d969b 1181 /**
T 1182  * @access private
1183  */
8bc018 1184 function rcube_print_time($timer, $label='Timer', $dest='console')
533e86 1185 {
15a9d1 1186   static $print_count = 0;
T 1187   
1188   $print_count++;
1189   $now = rcube_timer();
1190   $diff = $now-$timer;
1191   
1192   if (empty($label))
1193     $label = 'Timer '.$print_count;
1194   
8bc018 1195   write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
533e86 1196 }
15a9d1 1197
93be5b 1198
6d969b 1199 /**
T 1200  * Return the mailboxlist in HTML
1201  *
1202  * @param array Named parameters
1203  * @return string HTML code for the gui object
1204  */
93be5b 1205 function rcmail_mailbox_list($attrib)
62e542 1206 {
25f80d 1207   global $RCMAIL;
93be5b 1208   static $a_mailboxes;
64f20d 1209   
b822b6 1210   $attrib += array('maxlength' => 100, 'realnames' => false);
93be5b 1211
S 1212   // add some labels to client
112c91 1213   $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
93be5b 1214   
S 1215   $type = $attrib['type'] ? $attrib['type'] : 'ul';
6d6e06 1216   unset($attrib['type']);
T 1217
93be5b 1218   if ($type=='ul' && !$attrib['id'])
S 1219     $attrib['id'] = 'rcmboxlist';
1220
1221   // get mailbox list
25f80d 1222   $mbox_name = $RCMAIL->imap->get_mailbox_name();
93be5b 1223   
S 1224   // build the folders tree
62e542 1225   if (empty($a_mailboxes)) {
93be5b 1226     // get mailbox list
25f80d 1227     $a_folders = $RCMAIL->imap->list_mailboxes();
T 1228     $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
93be5b 1229     $a_mailboxes = array();
S 1230
1231     foreach ($a_folders as $folder)
1232       rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
62e542 1233   }
f62d5f 1234   
T 1235   // allow plugins to alter the folder tree or to localize folder names
4fa127 1236   $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
93be5b 1237
6d6e06 1238   if ($type=='select') {
T 1239     $select = new html_select($attrib);
1240     
1241     // add no-selection option
1242     if ($attrib['noselection'])
1243       $select->add(rcube_label($attrib['noselection']), '0');
1244     
f62d5f 1245     rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
62e542 1246     $out = $select->show();
6d6e06 1247   }
T 1248   else {
f89f03 1249     $js_mailboxlist = array();
f62d5f 1250     $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
f89f03 1251     
25f80d 1252     $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
f89f03 1253     $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
25f80d 1254     $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
T 1255   }
93be5b 1256
6d6e06 1257   return $out;
62e542 1258 }
93be5b 1259
S 1260
cb3538 1261 /**
T 1262  * Return the mailboxlist as html_select object
1263  *
1264  * @param array Named parameters
1265  * @return object html_select HTML drop-down object
1266  */
1267 function rcmail_mailbox_select($p = array())
1268 {
1269   global $RCMAIL;
1270   
93a88c 1271   $p += array('maxlength' => 100, 'realnames' => false);
cb3538 1272   $a_mailboxes = array();
T 1273   
1274   foreach ($RCMAIL->imap->list_mailboxes() as $folder)
93a88c 1275     if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
A 1276       rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
cb3538 1277
T 1278   $select = new html_select($p);
1279   
1280   if ($p['noselection'])
1281     $select->add($p['noselection'], '');
1282     
64f20d 1283   rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
cb3538 1284   
T 1285   return $select;
1286 }
93be5b 1287
S 1288
6d969b 1289 /**
T 1290  * Create a hierarchical array of the mailbox list
1291  * @access private
1292  */
93be5b 1293 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
f89f03 1294 {
93be5b 1295   $pos = strpos($folder, $delm);
f89f03 1296   if ($pos !== false) {
93be5b 1297     $subFolders = substr($folder, $pos+1);
S 1298     $currentFolder = substr($folder, 0, $pos);
f89f03 1299     $virtual = !isset($arrFolders[$currentFolder]);
T 1300   }
1301   else {
93be5b 1302     $subFolders = false;
S 1303     $currentFolder = $folder;
f89f03 1304     $virtual = false;
T 1305   }
93be5b 1306
S 1307   $path .= $currentFolder;
1308
f89f03 1309   if (!isset($arrFolders[$currentFolder])) {
6d6e06 1310     $arrFolders[$currentFolder] = array(
T 1311       'id' => $path,
a5897a 1312       'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
f89f03 1313       'virtual' => $virtual,
6d6e06 1314       'folders' => array());
f89f03 1315   }
T 1316   else
1317     $arrFolders[$currentFolder]['virtual'] = $virtual;
93be5b 1318
S 1319   if (!empty($subFolders))
1320     rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
f89f03 1321 }
93be5b 1322   
S 1323
6d969b 1324 /**
T 1325  * Return html for a structured list &lt;ul&gt; for the mailbox tree
1326  * @access private
1327  */
f89f03 1328 function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
T 1329 {
25f80d 1330   global $RCMAIL, $CONFIG;
f89f03 1331   
T 1332   $maxlength = intval($attrib['maxlength']);
1333   $realnames = (bool)$attrib['realnames'];
1334   $msgcounts = $RCMAIL->imap->get_cache('messagecount');
93be5b 1335
S 1336   $idx = 0;
1337   $out = '';
f89f03 1338   foreach ($arrFolders as $key => $folder) {
cb3538 1339     $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
6d6e06 1340     $title = null;
93be5b 1341
f89f03 1342     if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
cb3bad 1343       $foldername = rcube_label($folder_class);
f89f03 1344     }
T 1345     else {
93be5b 1346       $foldername = $folder['name'];
S 1347
1348       // shorten the folder name to a given length
f89f03 1349       if ($maxlength && $maxlength > 1) {
6f2f2d 1350         $fname = abbreviate_string($foldername, $maxlength);
93be5b 1351         if ($fname != $foldername)
6d6e06 1352           $title = $foldername;
93be5b 1353         $foldername = $fname;
S 1354       }
f89f03 1355     }
93be5b 1356
S 1357     // make folder name safe for ids and class names
8ca0c7 1358     $folder_id = asciiwords($folder['id'], true, '_');
6d6e06 1359     $classes = array('mailbox');
93be5b 1360
S 1361     // set special class for Sent, Drafts, Trash and Junk
1362     if ($folder['id']==$CONFIG['sent_mbox'])
6d6e06 1363       $classes[] = 'sent';
93be5b 1364     else if ($folder['id']==$CONFIG['drafts_mbox'])
6d6e06 1365       $classes[] = 'drafts';
93be5b 1366     else if ($folder['id']==$CONFIG['trash_mbox'])
6d6e06 1367       $classes[] = 'trash';
93be5b 1368     else if ($folder['id']==$CONFIG['junk_mbox'])
6d6e06 1369       $classes[] = 'junk';
d6497f 1370     else if ($folder['id']=='INBOX')
A 1371       $classes[] = 'inbox';
6d6e06 1372     else
d6497f 1373       $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
6d6e06 1374       
T 1375     $classes[] = $zebra_class;
1376     
1377     if ($folder['id'] == $mbox_name)
1378       $classes[] = 'selected';
93be5b 1379
f5aa16 1380     $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
f89f03 1381     $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
T 1382     
1383     if ($folder['virtual'])
1384       $classes[] = 'virtual';
1385     else if ($unread)
1386       $classes[] = 'unread';
f5aa16 1387
6d6e06 1388     $js_name = JQ($folder['id']);
f89f03 1389     $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
T 1390     $link_attrib = $folder['virtual'] ? array() : array(
1391       'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1392       'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1393       'title' => $title,
1394     );
1395
6d6e06 1396     $out .= html::tag('li', array(
T 1397         'id' => "rcmli".$folder_id,
1398         'class' => join(' ', $classes),
1399         'noclose' => true),
f89f03 1400       html::a($link_attrib, $html_name) .
e1eb70 1401       (!empty($folder['folders']) ? html::div(array(
T 1402         'class' => ($collapsed ? 'collapsed' : 'expanded'),
1403         'style' => "position:absolute",
1404         'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1405       ), '&nbsp;') : ''));
6d6e06 1406     
f89f03 1407     $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
T 1408     
1409     if (!empty($folder['folders'])) {
1410       $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1411         rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1412     }
93be5b 1413
S 1414     $out .= "</li>\n";
1415     $idx++;
f89f03 1416   }
93be5b 1417
S 1418   return $out;
f89f03 1419 }
93be5b 1420
S 1421
6d969b 1422 /**
T 1423  * Return html for a flat list <select> for the mailbox tree
1424  * @access private
1425  */
64f20d 1426 function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
93be5b 1427   {
S 1428   $idx = 0;
1429   $out = '';
1430   foreach ($arrFolders as $key=>$folder)
1431     {
64f20d 1432     if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
cb3bad 1433       $foldername = rcube_label($folder_class);
93be5b 1434     else
S 1435       {
1436       $foldername = $folder['name'];
1437       
1438       // shorten the folder name to a given length
1439       if ($maxlength && $maxlength>1)
6f2f2d 1440         $foldername = abbreviate_string($foldername, $maxlength);
93be5b 1441       }
S 1442
6d6e06 1443     $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
93be5b 1444
S 1445     if (!empty($folder['folders']))
64f20d 1446       $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
93be5b 1447
S 1448     $idx++;
1449     }
1450
1451   return $out;
1452   }
1453
cb3bad 1454
T 1455 /**
1456  * Return internal name for the given folder if it matches the configured special folders
1457  * @access private
1458  */
1459 function rcmail_folder_classname($folder_id)
1460 {
1461   global $CONFIG;
1462
0a1921 1463   if ($folder_id == 'INBOX')
A 1464     return 'inbox';
1465
cb3bad 1466   // for these mailboxes we have localized labels and css classes
64c9b5 1467   foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
cb3bad 1468   {
f94629 1469     if ($folder_id == $CONFIG[$smbx.'_mbox'])
64c9b5 1470       return $smbx;
cb3bad 1471   }
T 1472 }
1473
1474
fed22f 1475 /**
T 1476  * Try to localize the given IMAP folder name.
1477  * UTF-7 decode it in case no localized text was found
1478  *
1479  * @param string Folder name
1480  * @return string Localized folder name in UTF-8 encoding
1481  */
1482 function rcmail_localize_foldername($name)
1483 {
1484   if ($folder_class = rcmail_folder_classname($name))
1485     return rcube_label($folder_class);
1486   else
a5897a 1487     return rcube_charset_convert($name, 'UTF7-IMAP');
fed22f 1488 }
T 1489
1490
b8ae50 1491 /**
A 1492  * Output HTML editor scripts
1493  *
be5d4a 1494  * @param string Editor mode
b8ae50 1495  */
A 1496 function rcube_html_editor($mode='')
1497 {
9ab7bc 1498   global $RCMAIL, $CONFIG;
b8ae50 1499
dc0040 1500   $hook = $RCMAIL->plugins->exec_hook('hmtl_editor', array('mode' => $mode));
9ab7bc 1501
A 1502   if ($hook['abort'])
1503     return;  
1504
dc0040 1505   $lang = strtolower(substr($_SESSION['language'], 0, 2));
A 1506   if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1507     $lang = 'en';
1508
9ab7bc 1509   $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
A 1510   $RCMAIL->output->include_script('editor.js');
1511   $RCMAIL->output->add_script('rcmail_editor_init("$__skin_path",
dc0040 1512     "'.JQ($lang).'", '.intval($CONFIG['enable_spellcheck']).', "'.$mode.'");');
b8ae50 1513 }
aa055c 1514
T 1515
1516 /**
5818e4 1517  * Check if working in SSL mode
A 1518  *
1519  * @param integer HTTPS port number
1520  * @param boolean Enables 'use_https' option checking
1521  */
1522 function rcube_https_check($port=null, $use_https=true)
1523 {
1524   global $RCMAIL;
1525   
6c5aa6 1526   if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
5818e4 1527     return true;
A 1528   if ($port && $_SERVER['SERVER_PORT'] == $port)
1529     return true;
929a50 1530   if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
5818e4 1531     return true;
A 1532
1533   return false;
1534 }
1535
bb8721 1536
929a50 1537 // for backward compatibility
A 1538 function rcube_sess_unset($var_name=null)
1539 {
1540   global $RCMAIL;
1541
1542   $RCMAIL->session->remove($var_name);
1543 }
1544
5818e4 1545
bb8721 1546 // Replaces hostname variables
A 1547 function rcube_parse_host($name)
1548 {
1549   // %n - host
1550   $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1551   // %d - domain name without first part, e.g. %d=mail.domain.tld, %m=domain.tld
1552   $d = preg_replace('/^[^\.]+\./', '', $n);
1553   // %h - IMAP host
1554   $h = $_SESSION['imap_host'];
1555
1556   $name = str_replace(array('%n', '%d', '%h'), array($n, $d, $h), $name);
1557   return $name;
1558 }
1559
1560
5818e4 1561 /**
e4acbb 1562  * E-mail address validation
A 1563  */
1baeb6 1564 function check_email($email, $dns_check=true)
e4acbb 1565 {
A 1566   // Check for invalid characters
1567   if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1568     return false;
1569
aba092 1570   // Check for length limit specified by RFC 5321 (#1486453)
A 1571   if (strlen($email) > 254) 
1572     return false;
1573
1baeb6 1574   $email_array = explode('@', $email);
A 1575
aba092 1576   // Check that there's one @ symbol
1baeb6 1577   if (count($email_array) < 2)
e4acbb 1578     return false;
A 1579
1baeb6 1580   $domain_part = array_pop($email_array);
A 1581   $local_part = implode('@', $email_array);
1582
1583   // from PEAR::Validate
1584   $regexp = '&^(?:
1585     ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                  #1 quoted name
1586     ([-\w!\#\$%\&\'*+~/^`|{}]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}]+)*))     #2 OR dot-atom
1587     $&xi';
1588
1589   if (!preg_match($regexp, $local_part))
1590     return false;
e4acbb 1591
A 1592   // Check domain part
1baeb6 1593   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 1594     return true; // IP address
e4acbb 1595   else {
A 1596     // If not an IP address
1baeb6 1597     $domain_array = explode('.', $domain_part);
e4acbb 1598     if (sizeof($domain_array) < 2)
A 1599       return false; // Not enough parts to be a valid domain
1600
1baeb6 1601     foreach ($domain_array as $part)
A 1602       if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
e4acbb 1603         return false;
A 1604
1baeb6 1605     if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
e4acbb 1606       return true;
A 1607
0f3764 1608     if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
A 1609       $lookup = array();
1610       @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1611       foreach ($lookup as $line) {
1612         if (strpos($line, 'MX preference'))
1613           return true;
1614       }
1615       return false;
1616     }
e4acbb 1617
A 1618     // find MX record(s)
1baeb6 1619     if (getmxrr($domain_part, $mx_records))
e4acbb 1620       return true;
A 1621
1622     // find any DNS record
1baeb6 1623     if (checkdnsrr($domain_part, 'ANY'))
e4acbb 1624       return true;
A 1625   }
1626
1627   return false;
1628 }
1629
1630
1631 /**
aa055c 1632  * Helper class to turn relative urls into absolute ones
T 1633  * using a predefined base
1634  */
1635 class rcube_base_replacer
1636 {
1637   private $base_url;
24c91e 1638
aa055c 1639   public function __construct($base)
T 1640   {
1641     $this->base_url = $base;
1642   }
24c91e 1643
aa055c 1644   public function callback($matches)
T 1645   {
1646     return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1647   }
1648 }
1649
874ff4 1650
24c91e 1651 /**
A 1652  * Throw system error and show error page
1653  *
1654  * @param array Named parameters
1655  *  - code: Error code (required)
1656  *  - type: Error type [php|db|imap|javascript] (required)
1657  *  - message: Error message
1658  *  - file: File where error occured
1659  *  - line: Line where error occured
1660  * @param boolean True to log the error
1661  * @param boolean Terminate script execution
1662  */
874ff4 1663 // may be defined in Installer
A 1664 if (!function_exists('raise_error')) {
24c91e 1665 function raise_error($arg=array(), $log=false, $terminate=false)
A 1666 {
1667     global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
1668
1669     // report bug (if not incompatible browser)
1670     if ($log && $arg['type'] && $arg['message'])
1671         log_bug($arg);
1672
1673     // display error page and terminate script
1674     if ($terminate) {
1675         $ERROR_CODE = $arg['code'];
1676         $ERROR_MESSAGE = $arg['message'];
1677         include('program/steps/utils/error.inc');
1678         exit;
1679     }
1680 }
874ff4 1681 }
24c91e 1682
A 1683
1684 /**
1685  * Report error according to configured debug_level
1686  *
1687  * @param array Named parameters
1688  * @see raise_error()
1689  */
1690 function log_bug($arg_arr)
1691 {
1692     global $CONFIG;
1693     $program = strtoupper($arg_arr['type']);
1694
1695     // write error to local log file
1696     if ($CONFIG['debug_level'] & 1) {
1697         $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
1698         $log_entry = sprintf("%s Error: %s%s (%s %s)",
1699             $program,
1700             $arg_arr['message'],
1701             $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
1702             $_SERVER['REQUEST_METHOD'],
1703             $_SERVER['REQUEST_URI'] . $post_query);
1704
1705         if (!write_log('errors', $log_entry)) {
1706             // send error to PHPs error handler if write_log didn't succeed
1707             trigger_error($arg_arr['message']);
1708         }
1709     }
1710
1711     // resport the bug to the global bug reporting system
1712     if ($CONFIG['debug_level'] & 2) {
1713         // TODO: Send error via HTTP
1714     }
1715
1716     // show error if debug_mode is on
1717     if ($CONFIG['debug_level'] & 4) {
1718         print "<b>$program Error";
1719
1720         if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
1721             print " in $arg_arr[file] ($arg_arr[line])";
1722
1723         print ':</b>&nbsp;';
1724         print nl2br($arg_arr['message']);
1725         print '<br />';
1726         flush();
1727     }
1728 }
1729
d1d2c4 1730 ?>