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