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