alecpl
2011-11-14 854fbf6d2384eeeb8c559d027686c8534f9d070d
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                     |
a77cf2 8  | Copyright (C) 2005-2011, The Roundcube Dev Team                       |
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
926948 29 require_once 'utf7.inc';
4351f7 30 require_once INSTALL_PATH . 'program/include/rcube_shared.inc';
4e17e6 31
ea7c46 32 // define constannts for input reading
T 33 define('RCUBE_INPUT_GET', 0x0101);
34 define('RCUBE_INPUT_POST', 0x0102);
35 define('RCUBE_INPUT_GPC', 0x0103);
36
37
4e17e6 38
6d969b 39 /**
T 40  * Return correct name for a specific database table
41  *
42  * @param string Table name
43  * @return string Translated table name
44  */
4e17e6 45 function get_table_name($table)
T 46   {
47   global $CONFIG;
653242 48
4e17e6 49   // return table name if configured
T 50   $config_key = 'db_table_'.$table;
51
52   if (strlen($CONFIG[$config_key]))
53     return $CONFIG[$config_key];
653242 54
4e17e6 55   return $table;
T 56   }
57
58
6d969b 59 /**
T 60  * Return correct name for a specific database sequence
653242 61  * (used for Postgres only)
6d969b 62  *
T 63  * @param string Secuence name
64  * @return string Translated sequence name
65  */
1cded8 66 function get_sequence_name($sequence)
T 67   {
e1ac21 68   // return sequence name if configured
1cded8 69   $config_key = 'db_sequence_'.$sequence;
54dd42 70   $opt = rcmail::get_instance()->config->get($config_key);
1cded8 71
54dd42 72   if (!empty($opt))
3e8483 73     return $opt;
54dd42 74     
ae8f19 75   return $sequence;
1cded8 76   }
0af7e8 77
T 78
6d969b 79 /**
1854c4 80  * Get localized text in the desired language
T 81  * It's a global wrapper for rcmail::gettext()
6d969b 82  *
1854c4 83  * @param mixed Named parameters array or label name
07b95d 84  * @param string Domain to search in (e.g. plugin name)
1854c4 85  * @return string Localized text
T 86  * @see rcmail::gettext()
6d969b 87  */
cc97ea 88 function rcube_label($p, $domain=null)
1854c4 89 {
cc97ea 90   return rcmail::get_instance()->gettext($p, $domain);
1854c4 91 }
4e17e6 92
T 93
6d969b 94 /**
07b95d 95  * Global wrapper of rcmail::text_exists()
T 96  * to check whether a text label is defined
97  *
98  * @see rcmail::text_exists()
99  */
8703b0 100 function rcube_label_exists($name, $domain=null, &$ref_domain = null)
07b95d 101 {
8703b0 102   return rcmail::get_instance()->text_exists($name, $domain, $ref_domain);
07b95d 103 }
T 104
105
106 /**
6d969b 107  * Overwrite action variable
T 108  *
109  * @param string New action value
110  */
10a699 111 function rcmail_overwrite_action($action)
T 112   {
197601 113   $app = rcmail::get_instance();
T 114   $app->action = $action;
115   $app->output->set_env('action', $action);
10a699 116   }
T 117
118
41bece 119 /**
T 120  * Compose an URL for a specific action
121  *
122  * @param string  Request action
123  * @param array   More URL parameters
124  * @param string  Request task (omit if the same)
125  * @return The application URL
126  */
127 function rcmail_url($action, $p=array(), $task=null)
f11541 128 {
197601 129   $app = rcmail::get_instance();
fde466 130   return $app->url((array)$p + array('_action' => $action, 'task' => $task));
f11541 131 }
9fee0e 132
4e17e6 133
6d969b 134 /**
T 135  * Garbage collector function for temp files.
136  * Remove temp files older than two days
137  */
70d4b9 138 function rcmail_temp_gc()
3ddca3 139 {
c5ee03 140   $rcmail = rcmail::get_instance();
A 141
142   $tmp = unslashify($rcmail->config->get('temp_dir'));
70d4b9 143   $expire = mktime() - 172800;  // expire in 48 hours
1cded8 144
3ddca3 145   if ($dir = opendir($tmp)) {
A 146     while (($fname = readdir($dir)) !== false) {
70d4b9 147       if ($fname{0} == '.')
T 148         continue;
149
150       if (filemtime($tmp.'/'.$fname) < $expire)
151         @unlink($tmp.'/'.$fname);
3ddca3 152     }
70d4b9 153
T 154     closedir($dir);
1cded8 155   }
3ddca3 156 }
1cded8 157
T 158
6d969b 159 /**
T 160  * Garbage collector for cache entries.
161  * Remove all expired message cache records
5c461b 162  * @return void
6d969b 163  */
29c64b 164 function rcmail_cache_gc()
3ddca3 165 {
29c64b 166   $rcmail = rcmail::get_instance();
T 167   $db = $rcmail->get_dbh();
3ddca3 168
cc9570 169   // get target timestamp
29c64b 170   $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
3ddca3 171
80152b 172   $db->query("DELETE FROM ".get_table_name('cache_messages')
A 173         ." WHERE changed < " . $db->fromunixtime($ts));
29c64b 174
80152b 175   $db->query("DELETE FROM ".get_table_name('cache_index')
A 176         ." WHERE changed < " . $db->fromunixtime($ts));
177
178   $db->query("DELETE FROM ".get_table_name('cache_thread')
179         ." WHERE changed < " . $db->fromunixtime($ts));
180
181   $db->query("DELETE FROM ".get_table_name('cache')
182         ." WHERE created < " . $db->fromunixtime($ts));
3ddca3 183 }
cc9570 184
1cded8 185
2bca6e 186 /**
f52e7a 187  * Catch an error and throw an exception.
A 188  *
189  * @param  int    Level of the error
190  * @param  string Error message
191  */ 
192 function rcube_error_handler($errno, $errstr)
3ddca3 193 {
f52e7a 194   throw new ErrorException($errstr, 0, $errno);
3ddca3 195 }
f52e7a 196
A 197
198 /**
2bca6e 199  * Convert a string from one charset to another.
T 200  * Uses mbstring and iconv functions if possible
201  *
202  * @param  string Input string
203  * @param  string Suspected charset of the input string
f11541 204  * @param  string Target charset to convert to; defaults to RCMAIL_CHARSET
5c461b 205  * @return string Converted string
2bca6e 206  */
3f9edb 207 function rcube_charset_convert($str, $from, $to=NULL)
3ddca3 208 {
f52e7a 209   static $iconv_options = null;
77e232 210   static $mbstring_loaded = null;
A 211   static $mbstring_list = null;
ce72e0 212   static $conv = null;
b8d8cb 213
dbe44c 214   $error = false;
f88d41 215
b77c9d 216   $to = empty($to) ? strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
dbe44c 217   $from = rcube_parse_charset($from);
65d710 218
c9a2fa 219   if ($from == $to || empty($str) || empty($from))
3f9edb 220     return $str;
ca85b1 221
acbf0f 222   // convert charset using iconv module
A 223   if (function_exists('iconv') && $from != 'UTF7-IMAP' && $to != 'UTF7-IMAP') {
f52e7a 224     if ($iconv_options === null) {
895d4e 225       // ignore characters not available in output charset
A 226       $iconv_options = '//IGNORE';
f52e7a 227       if (iconv('', $iconv_options, '') === false) {
A 228         // iconv implementation does not support options
229         $iconv_options = '';
230       }
231     }
b69560 232
f52e7a 233     // throw an exception if iconv reports an illegal character in input
A 234     // it means that input string has been truncated
235     set_error_handler('rcube_error_handler', E_NOTICE);
236     try {
237       $_iconv = iconv($from, $to . $iconv_options, $str);
238     } catch (ErrorException $e) {
239       $_iconv = false;
240     }
241     restore_error_handler();
ae8a60 242     if ($_iconv !== false) {
f52e7a 243       return $_iconv;
0393da 244     }
ae8a60 245   }
5f56a5 246
f52e7a 247   if ($mbstring_loaded === null)
d99b93 248     $mbstring_loaded = extension_loaded('mbstring');
3ddca3 249
197601 250   // convert charset using mbstring module
ae8a60 251   if ($mbstring_loaded) {
b19536 252     $aliases['WINDOWS-1257'] = 'ISO-8859-13';
3ddca3 253
f52e7a 254     if ($mbstring_list === null) {
77e232 255       $mbstring_list = mb_list_encodings();
A 256       $mbstring_list = array_map('strtoupper', $mbstring_list);
257     }
dbe44c 258
77e232 259     $mb_from = $aliases[$from] ? $aliases[$from] : $from;
A 260     $mb_to = $aliases[$to] ? $aliases[$to] : $to;
3ddca3 261
77e232 262     // return if encoding found, string matches encoding and convert succeeded
ae8a60 263     if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
T 264       if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
265         return $out;
83dbb7 266     }
ae8a60 267   }
65d710 268
ce72e0 269   // convert charset using bundled classes/functions
a5897a 270   if ($to == 'UTF-8') {
A 271     if ($from == 'UTF7-IMAP') {
272       if ($_str = utf7_to_utf8($str))
ce72e0 273         return $_str;
a5897a 274     }
A 275     else if ($from == 'UTF-7') {
276       if ($_str = rcube_utf7_to_utf8($str))
ce72e0 277         return $_str;
a5897a 278     }
A 279     else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
ce72e0 280       return utf8_encode($str);
a5897a 281     }
ce72e0 282     else if (class_exists('utf8')) {
A 283       if (!$conv)
284         $conv = new utf8($from);
285       else
286         $conv->loadCharset($from);
287
288       if($_str = $conv->strToUtf8($str))
289         return $_str;
a5897a 290     }
ce72e0 291     $error = true;
ae8a60 292   }
4351f7 293
3f9edb 294   // encode string for output
a5897a 295   if ($from == 'UTF-8') {
A 296     // @TODO: we need a function for UTF-7 (RFC2152) conversion
297     if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
298       if ($_str = utf8_to_utf7($str))
ce72e0 299         return $_str;
a5897a 300     }
A 301     else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
302       return utf8_decode($str);
303     }
ce72e0 304     else if (class_exists('utf8')) {
A 305       if (!$conv)
306         $conv = new utf8($to);
307       else
308         $conv->loadCharset($from);
309
310       if ($_str = $conv->strToUtf8($str))
311         return $_str;
a5897a 312     }
ce72e0 313     $error = true;
ae8a60 314   }
4351f7 315
ce72e0 316   // return UTF-8 or original string
3f9edb 317   return $str;
3ddca3 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)
3ddca3 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
b8d8cb 344   if ($charset == 'BINARY')
A 345     return $charsets[$input] = null;
346
b1fb69 347   # Aliases: some of them from HTML5 spec.
dbe44c 348   $aliases = array(
ca85b1 349     'USASCII'       => 'WINDOWS-1252',
A 350     'ANSIX31101983' => 'WINDOWS-1252',
351     'ANSIX341968'   => 'WINDOWS-1252',
dbe44c 352     'UNKNOWN8BIT'   => 'ISO-8859-15',
5dc7c2 353     'UNKNOWN'       => 'ISO-8859-15',
A 354     'USERDEFINED'   => 'ISO-8859-15',
dbe44c 355     'KSC56011987'   => 'EUC-KR',
b1fb69 356     'GB2312'         => 'GBK',
A 357     'GB231280'        => 'GBK',
dbe44c 358     'UNICODE'        => 'UTF-8',
b1fb69 359     'UTF7IMAP'        => 'UTF7-IMAP',
A 360     'TIS620'        => 'WINDOWS-874',
361     'ISO88599'        => 'WINDOWS-1254',
362     'ISO885911'        => 'WINDOWS-874',
c8729e 363     'MACROMAN'        => 'MACINTOSH',
228075 364     '77'            => 'MAC',
A 365     '128'           => 'SHIFT-JIS',
366     '129'           => 'CP949',
367     '130'           => 'CP1361',
1e3271 368     '134'           => 'GBK',
228075 369     '136'           => 'BIG5',
A 370     '161'           => 'WINDOWS-1253',
371     '162'           => 'WINDOWS-1254',
372     '163'           => 'WINDOWS-1258',
373     '177'           => 'WINDOWS-1255',
374     '178'           => 'WINDOWS-1256',
375     '186'           => 'WINDOWS-1257',
376     '204'           => 'WINDOWS-1251',
377     '222'           => 'WINDOWS-874',
1e3271 378     '238'           => 'WINDOWS-1250',
4c6a61 379     'MS950'         => 'CP950',
03d3ba 380     'WINDOWS949'    => 'UHC',
dbe44c 381   );
A 382
4d7acb 383   // allow A-Z and 0-9 only
A 384   $str = preg_replace('/[^A-Z0-9]/', '', $charset);
dbe44c 385
A 386   if (isset($aliases[$str]))
46a138 387     $result = $aliases[$str];
A 388   // UTF
389   else if (preg_match('/U[A-Z][A-Z](7|8|16|32)(BE|LE)*/', $str, $m))
390     $result = 'UTF-' . $m[1] . $m[2];
391   // ISO-8859
392   else if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
ca85b1 393     $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
46a138 394     // some clients sends windows-1252 text as latin1,
A 395     // it is safe to use windows-1252 for all latin1
396     $result = $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
3ddca3 397   }
1e3271 398   // handle broken charset names e.g. WINDOWS-1250HTTP-EQUIVCONTENT-TYPE
46a138 399   else if (preg_match('/(WIN|WINDOWS)([0-9]+)/', $str, $m)) {
A 400     $result = 'WINDOWS-' . $m[2];
3ddca3 401   }
4d7acb 402   // LATIN
1e3ba3 403   else if (preg_match('/LATIN(.*)/', $str, $m)) {
4d7acb 404     $aliases = array('2' => 2, '3' => 3, '4' => 4, '5' => 9, '6' => 10,
A 405         '7' => 13, '8' => 14, '9' => 15, '10' => 16,
1e3ba3 406         'ARABIC' => 6, 'CYRILLIC' => 5, 'GREEK' => 7, 'GREEK1' => 7, 'HEBREW' => 8);
4d7acb 407
A 408     // some clients sends windows-1252 text as latin1,
409     // it is safe to use windows-1252 for all latin1
1e3ba3 410     if ($m[1] == 1) {
4d7acb 411       $result = 'WINDOWS-1252';
3ddca3 412     }
4d7acb 413     // if iconv is not supported we need ISO labels, it's also safe for iconv
1e3ba3 414     else if (!empty($aliases[$m[1]])) {
A 415       $result = 'ISO-8859-'.$aliases[$m[1]];
3ddca3 416     }
4d7acb 417     // iconv requires convertion of e.g. LATIN-1 to LATIN1
A 418     else {
419       $result = $str;
420     }
3ddca3 421   }
46a138 422   else {
A 423     $result = $charset;
3ddca3 424   }
1e3271 425
46a138 426   $charsets[$input] = $result;
A 427
428   return $result;
3ddca3 429 }
a5897a 430
A 431
432 /**
433  * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
434  *
435  * @param  string  Input string
5c461b 436  * @return string  The converted string
a5897a 437  */
A 438 function rcube_utf7_to_utf8($str)
439 {
440   $Index_64 = array(
441     0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
442     0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
443     0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
444     1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
445     0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
446     1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
447     0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
448     1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
449   );
450
451   $u7len = strlen($str);
452   $str = strval($str);
453   $res = '';
454
455   for ($i=0; $u7len > 0; $i++, $u7len--)
456   {
457     $u7 = $str[$i];
458     if ($u7 == '+')
459     {
460       $i++;
461       $u7len--;
462       $ch = '';
463
464       for (; $u7len > 0; $i++, $u7len--)
465       {
466         $u7 = $str[$i];
467
468         if (!$Index_64[ord($u7)])
469           break;
470
471     $ch .= $u7;
472       }
473
474       if ($ch == '') {
475         if ($u7 == '-')
476           $res .= '+';
477         continue;
478       }
479
480       $res .= rcube_utf16_to_utf8(base64_decode($ch));
481     }
482     else
483     {
484       $res .= $u7;
485     }
486   }
487
488   return $res;
489 }
490
491 /**
492  * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
493  *
494  * @param  string  Input string
5c461b 495  * @return string  The converted string
a5897a 496  */
A 497 function rcube_utf16_to_utf8($str)
498 {
499   $len = strlen($str);
500   $dec = '';
501
502   for ($i = 0; $i < $len; $i += 2) {
503     $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
504     if ($c >= 0x0001 && $c <= 0x007F) {
505       $dec .= chr($c);
506     } else if ($c > 0x07FF) {
507       $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
508       $dec .= chr(0x80 | (($c >>  6) & 0x3F));
509       $dec .= chr(0x80 | (($c >>  0) & 0x3F));
510     } else {
511       $dec .= chr(0xC0 | (($c >>  6) & 0x1F));
512       $dec .= chr(0x80 | (($c >>  0) & 0x3F));
513     }
514   }
515   return $dec;
516 }
dbe44c 517
A 518
519 /**
2bca6e 520  * Replacing specials characters to a specific encoding type
T 521  *
522  * @param  string  Input string
523  * @param  string  Encoding type: text|html|xml|js|url
524  * @param  string  Replace mode for tags: show|replace|remove
525  * @param  boolean Convert newlines
5c461b 526  * @return string  The quoted string
2bca6e 527  */
1cded8 528 function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
T 529   {
257782 530   static $html_encode_arr = false;
A 531   static $js_rep_table = false;
532   static $xml_rep_table = false;
1cded8 533
T 534   if (!$enctype)
c8a21d 535     $enctype = $OUTPUT->type;
1cded8 536
T 537   // encode for HTML output
538   if ($enctype=='html')
539     {
540     if (!$html_encode_arr)
541       {
b72e2f 542       $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
1cded8 543       unset($html_encode_arr['?']);
T 544       }
545
546     $ltpos = strpos($str, '<');
547     $encode_arr = $html_encode_arr;
548
549     // don't replace quotes and html tags
550     if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
551       {
552       unset($encode_arr['"']);
553       unset($encode_arr['<']);
554       unset($encode_arr['>']);
10c92b 555       unset($encode_arr['&']);
1cded8 556       }
T 557     else if ($mode=='remove')
558       $str = strip_tags($str);
d2b27d 559
A 560     $out = strtr($str, $encode_arr);
561
674a0f 562     // avoid douple quotation of &
a4c970 563     $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
d2b27d 564
1cded8 565     return $newlines ? nl2br($out) : $out;
T 566     }
567
2bca6e 568   // if the replace tables for XML and JS are not yet defined
257782 569   if ($js_rep_table===false)
1cded8 570     {
f91a49 571     $js_rep_table = $xml_rep_table = array();
88375f 572     $xml_rep_table['&'] = '&amp;';
1cded8 573
T 574     for ($c=160; $c<256; $c++)  // can be increased to support more charsets
bc6ac4 575       $xml_rep_table[chr($c)] = "&#$c;";
1cded8 576
T 577     $xml_rep_table['"'] = '&quot;';
c21d6d 578     $js_rep_table['"'] = '\\"';
T 579     $js_rep_table["'"] = "\\'";
3d54e6 580     $js_rep_table["\\"] = "\\\\";
bc6ac4 581     // Unicode line and paragraph separators (#1486310)
A 582     $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '&#8232;';
583     $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '&#8233;';
1cded8 584     }
T 585
74eb6c 586   // encode for javascript use
A 587   if ($enctype=='js')
588     return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
589
590   // encode for plaintext
591   if ($enctype=='text')
592     return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
593
594   if ($enctype=='url')
595     return rawurlencode($str);
596
2bca6e 597   // encode for XML
1cded8 598   if ($enctype=='xml')
T 599     return strtr($str, $xml_rep_table);
600
601   // no encoding given -> return original string
602   return $str;
2bca6e 603   }
f11541 604   
2bca6e 605 /**
6d969b 606  * Quote a given string.
T 607  * Shortcut function for rep_specialchars_output
608  *
609  * @return string HTML-quoted string
610  * @see rep_specialchars_output()
2bca6e 611  */
T 612 function Q($str, $mode='strict', $newlines=TRUE)
613   {
614   return rep_specialchars_output($str, 'html', $mode, $newlines);
615   }
616
617 /**
6d969b 618  * Quote a given string for javascript output.
T 619  * Shortcut function for rep_specialchars_output
620  * 
621  * @return string JS-quoted string
622  * @see rep_specialchars_output()
2bca6e 623  */
18e2a3 624 function JQ($str)
2bca6e 625   {
18e2a3 626   return rep_specialchars_output($str, 'js');
10a699 627   }
ea7c46 628
T 629
630 /**
631  * Read input value and convert it for internal use
632  * Performs stripslashes() and charset conversion if necessary
633  * 
634  * @param  string   Field name to read
635  * @param  int      Source to get value from (GPC)
636  * @param  boolean  Allow HTML tags in field value
637  * @param  string   Charset to convert into
638  * @return string   Field value or NULL if not available
639  */
640 function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
759696 641 {
ea7c46 642   $value = NULL;
T 643   
644   if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
645     $value = $_GET[$fname];
646   else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
647     $value = $_POST[$fname];
648   else if ($source==RCUBE_INPUT_GPC)
649     {
026d68 650     if (isset($_POST[$fname]))
ea7c46 651       $value = $_POST[$fname];
026d68 652     else if (isset($_GET[$fname]))
T 653       $value = $_GET[$fname];
ea7c46 654     else if (isset($_COOKIE[$fname]))
T 655       $value = $_COOKIE[$fname];
656     }
c5ee03 657
72b140 658   return parse_input_value($value, $allow_html, $charset);
A 659 }
660
661 /**
662  * Parse/validate input value. See get_input_value()
663  * Performs stripslashes() and charset conversion if necessary
664  * 
665  * @param  string   Input value
666  * @param  boolean  Allow HTML tags in field value
667  * @param  string   Charset to convert into
668  * @return string   Parsed value
669  */
670 function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
671 {
672   global $OUTPUT;
673
c5ee03 674   if (empty($value))
A 675     return $value;
72b140 676
A 677   if (is_array($value)) {
678     foreach ($value as $idx => $val)
679       $value[$idx] = parse_input_value($val, $allow_html, $charset);
680     return $value;
681   }
c5ee03 682
6e47c0 683   // strip single quotes if magic_quotes_sybase is enabled
T 684   if (ini_get('magic_quotes_sybase'))
685     $value = str_replace("''", "'", $value);
ea7c46 686   // strip slashes if magic_quotes enabled
6e47c0 687   else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
ea7c46 688     $value = stripslashes($value);
T 689
690   // remove HTML tags if not allowed    
691   if (!$allow_html)
692     $value = strip_tags($value);
693   
694   // convert to internal charset
72b140 695   if (is_object($OUTPUT) && $charset)
026d68 696     return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
T 697   else
698     return $value;
759696 699 }
T 700
701 /**
702  * Convert array of request parameters (prefixed with _)
703  * to a regular array with non-prefixed keys.
704  *
705  * @param  int   Source to get value from (GPC)
706  * @return array Hash array with all request parameters
707  */
ef4998 708 function request2param($mode = RCUBE_INPUT_GPC, $ignore = 'task|action')
759696 709 {
T 710   $out = array();
711   $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
712   foreach ($src as $key => $value) {
713     $fname = $key[0] == '_' ? substr($key, 1) : $key;
ef4998 714     if ($ignore && !preg_match("/($ignore)/", $fname))
T 715       $out[$fname] = get_input_value($key, $mode);
ea7c46 716   }
759696 717   
T 718   return $out;
719 }
ea7c46 720
d5342a 721 /**
T 722  * Remove all non-ascii and non-word chars
8ca0c7 723  * except ., -, _
d5342a 724  */
8ca0c7 725 function asciiwords($str, $css_id = false, $replace_with = '')
d5342a 726 {
6d6e06 727   $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
8ca0c7 728   return preg_replace("/[^$allowed]/i", $replace_with, $str);
d5342a 729 }
6d969b 730
e34ae1 731 /**
ce988a 732  * Convert the given string into a valid HTML identifier
T 733  * Same functionality as done in app.js with this.identifier_expr
734  *
735  */
736 function html_identifier($str)
737 {
738   return asciiwords($str, true, '_');
739 }
740
741 /**
e34ae1 742  * Remove single and double quotes from given string
6d969b 743  *
T 744  * @param string Input value
745  * @return string Dequoted string
e34ae1 746  */
T 747 function strip_quotes($str)
748 {
2aa2b3 749   return str_replace(array("'", '"'), '', $str);
e34ae1 750 }
10a699 751
6d969b 752
3cf664 753 /**
T 754  * Remove new lines characters from given string
6d969b 755  *
T 756  * @param string Input value
757  * @return string Stripped string
3cf664 758  */
T 759 function strip_newlines($str)
760 {
761   return preg_replace('/[\r\n]/', '', $str);
f11541 762 }
4e17e6 763
T 764
6d969b 765 /**
T 766  * Create a HTML table based on the given data
767  *
768  * @param  array  Named table attributes
769  * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
770  * @param  array  List of cols to show
771  * @param  string Name of the identifier col
772  * @return string HTML table code
773  */
d1d2c4 774 function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
57863c 775 {
83a763 776   global $RCMAIL;
57863c 777
83a763 778   $table = new html_table(/*array('cols' => count($a_show_cols))*/);
57863c 779
83a763 780   // add table header
54759c 781   if (!$attrib['noheader'])
A 782     foreach ($a_show_cols as $col)
783       $table->add_header($col, Q(rcube_label($col)));
57863c 784
4e17e6 785   $c = 0;
57863c 786   if (!is_array($table_data))
83a763 787   {
T 788     $db = $RCMAIL->get_dbh();
789     while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
4e17e6 790     {
57863c 791       $table->add_row(array('id' => 'rcmrow' . html_identifier($sql_arr[$id_col])));
d1d2c4 792
S 793       // format each col
794       foreach ($a_show_cols as $col)
83a763 795         $table->add($col, Q($sql_arr[$col]));
57863c 796
d1d2c4 797       $c++;
S 798     }
83a763 799   }
57863c 800   else {
d1d2c4 801     foreach ($table_data as $row_data)
83a763 802     {
57863c 803       $class = !empty($row_data['class']) ? $row_data['class'] : '';
b69560 804
57863c 805       $table->add_row(array('id' => 'rcmrow' . html_identifier($row_data[$id_col]), 'class' => $class));
d1d2c4 806
S 807       // format each col
808       foreach ($a_show_cols as $col)
0501b6 809         $table->add($col, Q(is_array($row_data[$col]) ? $row_data[$col][0] : $row_data[$col]));
57863c 810
d1d2c4 811       $c++;
4e17e6 812     }
83a763 813   }
4e17e6 814
83a763 815   return $table->show($attrib);
57863c 816 }
4e17e6 817
T 818
a0109c 819 /**
S 820  * Create an edit field for inclusion on a form
821  * 
822  * @param string col field name
823  * @param string value field value
824  * @param array attrib HTML element attributes for field
825  * @param string type HTML element type (default 'text')
826  * @return string HTML field definition
827  */
4e17e6 828 function rcmail_get_edit_field($col, $value, $attrib, $type='text')
0501b6 829 {
T 830   static $colcounts = array();
4e17e6 831   
0501b6 832   $fname = '_'.$col;
T 833   $attrib['name'] = $fname . ($attrib['array'] ? '[]' : '');
834   $attrib['class'] = trim($attrib['class'] . ' ff_' . $col);
835   
836   if ($type == 'checkbox') {
4e17e6 837     $attrib['value'] = '1';
47124c 838     $input = new html_checkbox($attrib);
0501b6 839   }
T 840   else if ($type == 'textarea') {
4e17e6 841     $attrib['cols'] = $attrib['size'];
47124c 842     $input = new html_textarea($attrib);
0501b6 843   }
T 844   else if ($type == 'select') {
845     $input = new html_select($attrib);
846     $input->add('---', '');
847     $input->add(array_values($attrib['options']), array_keys($attrib['options']));
848   }
849   else {
850     if ($attrib['type'] != 'text' && $attrib['type'] != 'hidden')
851         $attrib['type'] = 'text';
47124c 852     $input = new html_inputfield($attrib);
0501b6 853   }
4e17e6 854
T 855   // use value from post
0501b6 856   if (isset($_POST[$fname])) {
516467 857     $postvalue = get_input_value($fname, RCUBE_INPUT_POST, true);
0501b6 858     $value = $attrib['array'] ? $postvalue[intval($colcounts[$col]++)] : $postvalue;
T 859   }
4e17e6 860
T 861   $out = $input->show($value);
0501b6 862
4e17e6 863   return $out;
0501b6 864 }
f11541 865
T 866
6d969b 867 /**
97bd2c 868  * Replace all css definitions with #container [def]
a3e5b4 869  * and remove css-inlined scripting
97bd2c 870  *
T 871  * @param string CSS source code
872  * @param string Container ID to use as prefix
873  * @return string Modified CSS source
874  */
aa055c 875 function rcmail_mod_css_styles($source, $container_id)
97bd2c 876   {
T 877   $last_pos = 0;
aa055c 878   $replacements = new rcube_string_replacer;
cb3dfd 879
a3e5b4 880   // ignore the whole block if evil styles are detected
79e634 881   $stripped = preg_replace('/[^a-z\(:;]/', '', rcmail_xss_entity_decode($source));
T 882   if (preg_match('/expression|behavior|url\(|import[^a]/', $stripped))
aa055c 883     return '/* evil! */';
97bd2c 884
8e5ed7 885   // remove css comments (sometimes used for some ugly hacks)
T 886   $source = preg_replace('!/\*(.+)\*/!Ums', '', $source);
887
97bd2c 888   // cut out all contents between { and }
T 889   while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
890   {
aa055c 891     $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
T 892     $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
97bd2c 893     $last_pos = $pos+2;
T 894   }
8e5ed7 895
a3e5b4 896   // remove html comments and add #container to each tag selector.
97bd2c 897   // also replace body definition because we also stripped off the <body> tag
T 898   $styles = preg_replace(
899     array(
900       '/(^\s*<!--)|(-->\s*$)/',
d0b981 901       '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
cb3dfd 902       '/'.preg_quote($container_id, '/').'\s+body/i',
97bd2c 903     ),
T 904     array(
905       '',
906       "\\1#$container_id \\2",
cb3dfd 907       $container_id,
97bd2c 908     ),
T 909     $source);
cb3dfd 910
1608f4 911   // put block contents back in
aa055c 912   $styles = $replacements->resolve($styles);
97bd2c 913
T 914   return $styles;
915   }
fba1f5 916
97bd2c 917
T 918 /**
1c499a 919  * Decode escaped entities used by known XSS exploits.
T 920  * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
921  *
922  * @param string CSS content to decode
923  * @return string Decoded string
924  */
c5ee03 925 function rcmail_xss_entity_decode($content)
1c499a 926 {
T 927   $out = html_entity_decode(html_entity_decode($content));
c5ee03 928   $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
85a913 929   $out = preg_replace('#/\*.*\*/#Um', '', $out);
1c499a 930   return $out;
T 931 }
932
aa055c 933
T 934 /**
c5ee03 935  * preg_replace_callback callback for rcmail_xss_entity_decode_callback
aa055c 936  *
T 937  * @param array matches result from preg_replace_callback
938  * @return string decoded entity
939  */ 
c5ee03 940 function rcmail_xss_entity_decode_callback($matches)
aa055c 941
T 942   return chr(hexdec($matches[1]));
943 }
1c499a 944
T 945 /**
6d969b 946  * Compose a valid attribute string for HTML tags
T 947  *
948  * @param array Named tag attributes
949  * @param array List of allowed attributes
950  * @return string HTML formatted attribute string
951  */
4e17e6 952 function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
T 953   {
954   // allow the following attributes to be added to the <iframe> tag
955   $attrib_str = '';
956   foreach ($allowed_attribs as $a)
957     if (isset($attrib[$a]))
fe79b1 958       $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
4e17e6 959
T 960   return $attrib_str;
961   }
962
963
6d969b 964 /**
T 965  * Convert a HTML attribute string attributes to an associative array (name => value)
966  *
967  * @param string Input string
968  * @return array Key-value pairs of parsed attributes
969  */
fe79b1 970 function parse_attrib_string($str)
T 971   {
972   $attrib = array();
d59aaa 973   preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
fe79b1 974
T 975   // convert attributes to an associative array (name => value)
cc97ea 976   if ($regs) {
T 977     foreach ($regs as $attr) {
978       $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
979     }
980   }
fe79b1 981
T 982   return $attrib;
983   }
984
4e17e6 985
6d969b 986 /**
312784 987  * Improved equivalent to strtotime()
T 988  *
989  * @param string Date string
990  * @return int 
991  */
992 function rcube_strtotime($date)
993 {
994   // check for MS Outlook vCard date format YYYYMMDD
995   if (preg_match('/^([12][90]\d\d)([01]\d)(\d\d)$/', trim($date), $matches)) {
996     return mktime(0,0,0, intval($matches[2]), intval($matches[3]), intval($matches[1]));
997   }
998   else if (is_numeric($date))
999     return $date;
1000
1001   // support non-standard "GMTXXXX" literal
1002   $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
1003
1004   // if date parsing fails, we have a date in non-rfc format.
1005   // remove token from the end and try again
1006   while ((($ts = @strtotime($date)) === false) || ($ts < 0)) {
1007     $d = explode(' ', $date);
1008     array_pop($d);
1009     if (!$d) break;
1010     $date = implode(' ', $d);
1011   }
1012
1013   return $ts;
1014 }
1015
1016
1017 /**
6d969b 1018  * Convert the given date to a human readable form
T 1019  * This uses the date formatting properties from config
1020  *
77153b 1021  * @param mixed  Date representation (string or timestamp)
6d969b 1022  * @param string Date format to use
77153b 1023  * @param bool   Enables date convertion according to user timezone
A 1024  *
6d969b 1025  * @return string Formatted date string
T 1026  */
77153b 1027 function format_date($date, $format=NULL, $convert=true)
de3dde 1028 {
da7178 1029   global $RCMAIL, $CONFIG;
ea090c 1030
312784 1031   if (!empty($date))
T 1032     $ts = rcube_strtotime($date);
ea090c 1033
4647e1 1034   if (empty($ts))
b076a4 1035     return '';
de3dde 1036
77153b 1037   if ($convert) {
A 1038     // get user's timezone offset
1039     $tz = $RCMAIL->config->get_timezone();
4e17e6 1040
77153b 1041     // convert time to user's timezone
A 1042     $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
de3dde 1043
77153b 1044     // get current timestamp in user's timezone
A 1045     $now = time();  // local time
1046     $now -= (int)date('Z'); // make GMT time
1047     $now += ($tz * 3600); // user's time
1048   }
1049   else {
1050     $now       = time();
1051     $timestamp = $ts;
1052   }
4e17e6 1053
539df6 1054   // define date format depending on current time
A 1055   if (!$format) {
77153b 1056     $now_date    = getdate($now);
A 1057     $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1058     $week_limit  = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
1059
de3dde 1060     if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
1cc9e2 1061       $format = $RCMAIL->config->get('date_today', $RCMAIL->config->get('time_format', 'H:i'));
de3dde 1062       $today  = true;
A 1063     }
539df6 1064     else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1cc9e2 1065       $format = $RCMAIL->config->get('date_short', 'D H:i');
539df6 1066     else
1cc9e2 1067       $format = $RCMAIL->config->get('date_long', 'Y-m-d H:i');
de3dde 1068   }
4e17e6 1069
b6b593 1070   // strftime() format
de3dde 1071   if (preg_match('/%[a-z]+/i', $format)) {
A 1072     $format = strftime($format, $timestamp);
1073     return $today ? (rcube_label('today') . ' ' . $format) : $format;
1074   }
4e17e6 1075
T 1076   // parse format string manually in order to provide localized weekday and month names
1077   // an alternative would be to convert the date() format string to fit with strftime()
1078   $out = '';
de3dde 1079   for($i=0; $i<strlen($format); $i++) {
09c59a 1080     if ($format[$i]=='\\')  // skip escape chars
4e17e6 1081       continue;
de3dde 1082
4e17e6 1083     // write char "as-is"
09c59a 1084     if ($format[$i]==' ' || $format{$i-1}=='\\')
T 1085       $out .= $format[$i];
4e17e6 1086     // weekday (short)
09c59a 1087     else if ($format[$i]=='D')
4e17e6 1088       $out .= rcube_label(strtolower(date('D', $timestamp)));
T 1089     // weekday long
09c59a 1090     else if ($format[$i]=='l')
4e17e6 1091       $out .= rcube_label(strtolower(date('l', $timestamp)));
T 1092     // month name (short)
09c59a 1093     else if ($format[$i]=='M')
4e17e6 1094       $out .= rcube_label(strtolower(date('M', $timestamp)));
T 1095     // month name (long)
09c59a 1096     else if ($format[$i]=='F')
7479cc 1097       $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
09c59a 1098     else if ($format[$i]=='x')
5b1de5 1099       $out .= strftime('%x %X', $timestamp);
4e17e6 1100     else
09c59a 1101       $out .= date($format[$i], $timestamp);
4e17e6 1102   }
T 1103
c5dedd 1104   if ($today) {
A 1105     $label = rcube_label('today');
1106     // replcae $ character with "Today" label (#1486120)
1107     if (strpos($out, '$') !== false) {
1108       $out = preg_replace('/\$/', $label, $out, 1);
1109     }
1110     else {
1111       $out = $label . ' ' . $out;
1112     }
1113   }
1114
1115   return $out;
de3dde 1116 }
A 1117
4e17e6 1118
6d969b 1119 /**
a9bfe2 1120  * Compose a valid representation of name and e-mail address
6d969b 1121  *
T 1122  * @param string E-mail address
1123  * @param string Person name
1124  * @return string Formatted string
1125  */
f11541 1126 function format_email_recipient($email, $name='')
23b495 1127 {
A 1128   if ($name && $name != $email) {
0c6f4b 1129     // Special chars as defined by RFC 822 need to in quoted string (or escaped).
a9bfe2 1130     return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
f11541 1131   }
T 1132
23b495 1133   return trim($email);
533e86 1134 }
15a9d1 1135
93be5b 1136
6d969b 1137 /**
T 1138  * Return the mailboxlist in HTML
1139  *
1140  * @param array Named parameters
1141  * @return string HTML code for the gui object
1142  */
93be5b 1143 function rcmail_mailbox_list($attrib)
62e542 1144 {
25f80d 1145   global $RCMAIL;
93be5b 1146   static $a_mailboxes;
0deec4 1147
b822b6 1148   $attrib += array('maxlength' => 100, 'realnames' => false);
93be5b 1149
S 1150   // add some labels to client
112c91 1151   $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
0deec4 1152
93be5b 1153   $type = $attrib['type'] ? $attrib['type'] : 'ul';
6d6e06 1154   unset($attrib['type']);
T 1155
93be5b 1156   if ($type=='ul' && !$attrib['id'])
S 1157     $attrib['id'] = 'rcmboxlist';
1158
94bdcc 1159   if (empty($attrib['folder_name']))
A 1160     $attrib['folder_name'] = '*';
1161
93be5b 1162   // get mailbox list
25f80d 1163   $mbox_name = $RCMAIL->imap->get_mailbox_name();
0deec4 1164
93be5b 1165   // build the folders tree
62e542 1166   if (empty($a_mailboxes)) {
93be5b 1167     // get mailbox list
94bdcc 1168     $a_folders = $RCMAIL->imap->list_mailboxes('', $attrib['folder_name'], $attrib['folder_filter']);
25f80d 1169     $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
93be5b 1170     $a_mailboxes = array();
S 1171
1172     foreach ($a_folders as $folder)
1173       rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
62e542 1174   }
10f08e 1175
f62d5f 1176   // allow plugins to alter the folder tree or to localize folder names
4fa127 1177   $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
93be5b 1178
0deec4 1179   if ($type == 'select') {
6d6e06 1180     $select = new html_select($attrib);
0deec4 1181
6d6e06 1182     // add no-selection option
T 1183     if ($attrib['noselection'])
0deec4 1184       $select->add(rcube_label($attrib['noselection']), '');
A 1185
f62d5f 1186     rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
62e542 1187     $out = $select->show();
6d6e06 1188   }
T 1189   else {
f89f03 1190     $js_mailboxlist = array();
f62d5f 1191     $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
0deec4 1192
25f80d 1193     $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
f89f03 1194     $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
4fb6a2 1195     $RCMAIL->output->set_env('collapsed_folders', (string)$RCMAIL->config->get('collapsed_folders'));
25f80d 1196   }
93be5b 1197
6d6e06 1198   return $out;
62e542 1199 }
93be5b 1200
S 1201
cb3538 1202 /**
T 1203  * Return the mailboxlist as html_select object
1204  *
1205  * @param array Named parameters
5c461b 1206  * @return html_select HTML drop-down object
cb3538 1207  */
T 1208 function rcmail_mailbox_select($p = array())
1209 {
1210   global $RCMAIL;
0deec4 1211
93a88c 1212   $p += array('maxlength' => 100, 'realnames' => false);
cb3538 1213   $a_mailboxes = array();
af3c04 1214
94bdcc 1215   if (empty($p['folder_name']))
A 1216     $p['folder_name'] = '*';
1217
af3c04 1218   if ($p['unsubscribed'])
e750d1 1219     $list = $RCMAIL->imap->list_unsubscribed('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
af3c04 1220   else
e750d1 1221     $list = $RCMAIL->imap->list_mailboxes('', $p['folder_name'], $p['folder_filter'], $p['folder_rights']);
af3c04 1222
d08333 1223   $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
A 1224
1225   foreach ($list as $folder) {
93a88c 1226     if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
d08333 1227       rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
A 1228   }
cb3538 1229
T 1230   $select = new html_select($p);
d08333 1231
cb3538 1232   if ($p['noselection'])
T 1233     $select->add($p['noselection'], '');
d08333 1234
aa07b2 1235   rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames'], 0, $p);
d08333 1236
cb3538 1237   return $select;
T 1238 }
93be5b 1239
S 1240
6d969b 1241 /**
T 1242  * Create a hierarchical array of the mailbox list
1243  * @access private
5c461b 1244  * @return void
6d969b 1245  */
93be5b 1246 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
f89f03 1247 {
a5a4bf 1248   global $RCMAIL;
d08333 1249
A 1250   // Handle namespace prefix
1251   $prefix = '';
1252   if (!$path) {
1253     $n_folder = $folder;
1254     $folder = $RCMAIL->imap->mod_mailbox($folder);
1255
1256     if ($n_folder != $folder) {
1257       $prefix = substr($n_folder, 0, -strlen($folder));
1258     }
1259   }
a5a4bf 1260
93be5b 1261   $pos = strpos($folder, $delm);
10f08e 1262
f89f03 1263   if ($pos !== false) {
93be5b 1264     $subFolders = substr($folder, $pos+1);
S 1265     $currentFolder = substr($folder, 0, $pos);
10f08e 1266
A 1267     // sometimes folder has a delimiter as the last character
448409 1268     if (!strlen($subFolders))
10f08e 1269       $virtual = false;
A 1270     else if (!isset($arrFolders[$currentFolder]))
1271       $virtual = true;
1272     else
1273       $virtual = $arrFolders[$currentFolder]['virtual'];
f89f03 1274   }
T 1275   else {
93be5b 1276     $subFolders = false;
S 1277     $currentFolder = $folder;
f89f03 1278     $virtual = false;
T 1279   }
93be5b 1280
d08333 1281   $path .= $prefix.$currentFolder;
a5a4bf 1282
f89f03 1283   if (!isset($arrFolders[$currentFolder])) {
aa07b2 1284     // Check \Noselect attribute (if attributes are in cache)
A 1285     if (!$virtual && ($attrs = $RCMAIL->imap->mailbox_attributes($path))) {
1286       $virtual = in_array('\\Noselect', $attrs);
d08333 1287     }
A 1288
6d6e06 1289     $arrFolders[$currentFolder] = array(
T 1290       'id' => $path,
a5897a 1291       'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
f89f03 1292       'virtual' => $virtual,
6d6e06 1293       'folders' => array());
f89f03 1294   }
T 1295   else
1296     $arrFolders[$currentFolder]['virtual'] = $virtual;
93be5b 1297
448409 1298   if (strlen($subFolders))
93be5b 1299     rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
f89f03 1300 }
0deec4 1301
93be5b 1302
6d969b 1303 /**
T 1304  * Return html for a structured list &lt;ul&gt; for the mailbox tree
1305  * @access private
5c461b 1306  * @return string
6d969b 1307  */
f89f03 1308 function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
T 1309 {
25f80d 1310   global $RCMAIL, $CONFIG;
0deec4 1311
f89f03 1312   $maxlength = intval($attrib['maxlength']);
T 1313   $realnames = (bool)$attrib['realnames'];
1314   $msgcounts = $RCMAIL->imap->get_cache('messagecount');
93be5b 1315
S 1316   $out = '';
f89f03 1317   foreach ($arrFolders as $key => $folder) {
854fbf 1318     $title        = null;
A 1319     $folder_class = rcmail_folder_classname($folder['id']);
1320     $collapsed    = strpos($CONFIG['collapsed_folders'], '&'.rawurlencode($folder['id']).'&') !== false;
1321     $unread       = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
93be5b 1322
854fbf 1323     if ($folder_class && !$realnames) {
cb3bad 1324       $foldername = rcube_label($folder_class);
f89f03 1325     }
T 1326     else {
93be5b 1327       $foldername = $folder['name'];
S 1328
1329       // shorten the folder name to a given length
f89f03 1330       if ($maxlength && $maxlength > 1) {
6f2f2d 1331         $fname = abbreviate_string($foldername, $maxlength);
93be5b 1332         if ($fname != $foldername)
6d6e06 1333           $title = $foldername;
93be5b 1334         $foldername = $fname;
S 1335       }
f89f03 1336     }
93be5b 1337
S 1338     // make folder name safe for ids and class names
ce988a 1339     $folder_id = html_identifier($folder['id']);
6d6e06 1340     $classes = array('mailbox');
93be5b 1341
S 1342     // set special class for Sent, Drafts, Trash and Junk
854fbf 1343     if ($folder_class)
A 1344       $classes[] = $folder_class;
0deec4 1345
6d6e06 1346     if ($folder['id'] == $mbox_name)
T 1347       $classes[] = 'selected';
0deec4 1348
f89f03 1349     if ($folder['virtual'])
T 1350       $classes[] = 'virtual';
1351     else if ($unread)
1352       $classes[] = 'unread';
f5aa16 1353
6d6e06 1354     $js_name = JQ($folder['id']);
03d772 1355     $html_name = Q($foldername) . ($unread ? html::span('unreadcount', " ($unread)") : '');
f89f03 1356     $link_attrib = $folder['virtual'] ? array() : array(
T 1357       'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1358       'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
ce988a 1359       'rel' => $folder['id'],
f89f03 1360       'title' => $title,
T 1361     );
1362
6d6e06 1363     $out .= html::tag('li', array(
T 1364         'id' => "rcmli".$folder_id,
1365         'class' => join(' ', $classes),
1366         'noclose' => true),
f89f03 1367       html::a($link_attrib, $html_name) .
e1eb70 1368       (!empty($folder['folders']) ? html::div(array(
T 1369         'class' => ($collapsed ? 'collapsed' : 'expanded'),
1370         'style' => "position:absolute",
1371         'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1372       ), '&nbsp;') : ''));
0deec4 1373
f89f03 1374     $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
0deec4 1375
f89f03 1376     if (!empty($folder['folders'])) {
T 1377       $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1378         rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1379     }
93be5b 1380
S 1381     $out .= "</li>\n";
f89f03 1382   }
93be5b 1383
S 1384   return $out;
f89f03 1385 }
93be5b 1386
S 1387
6d969b 1388 /**
T 1389  * Return html for a flat list <select> for the mailbox tree
1390  * @access private
5c461b 1391  * @return string
6d969b 1392  */
aa07b2 1393 function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0, $opts=array())
0deec4 1394 {
aa07b2 1395   global $RCMAIL;
A 1396
93be5b 1397   $out = '';
0deec4 1398
4d784b 1399   foreach ($arrFolders as $key => $folder) {
aa07b2 1400     // skip exceptions (and its subfolders)
A 1401     if (!empty($opts['exceptions']) && in_array($folder['id'], $opts['exceptions'])) {
4d784b 1402       continue;
aa07b2 1403     }
A 1404
1405     // skip folders in which it isn't possible to create subfolders
1406     if (!empty($opts['skip_noinferiors']) && ($attrs = $RCMAIL->imap->mailbox_attributes($folder['id']))
1407         && in_array('\\Noinferiors', $attrs)
1408     ) {
1409       continue;
1410     }
1411
1412     if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1413       $foldername = rcube_label($folder_class);
1414     else {
1415       $foldername = $folder['name'];
1416
1417       // shorten the folder name to a given length
1418       if ($maxlength && $maxlength>1)
1419         $foldername = abbreviate_string($foldername, $maxlength);
1420     }
1421
1422     $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
93be5b 1423
S 1424     if (!empty($folder['folders']))
4d784b 1425       $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength,
aa07b2 1426         $select, $realnames, $nestLevel+1, $opts);
0deec4 1427   }
93be5b 1428
S 1429   return $out;
0deec4 1430 }
93be5b 1431
cb3bad 1432
T 1433 /**
1434  * Return internal name for the given folder if it matches the configured special folders
1435  * @access private
5c461b 1436  * @return string
cb3bad 1437  */
T 1438 function rcmail_folder_classname($folder_id)
1439 {
1440   global $CONFIG;
1441
0a1921 1442   if ($folder_id == 'INBOX')
A 1443     return 'inbox';
1444
cb3bad 1445   // for these mailboxes we have localized labels and css classes
64c9b5 1446   foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
cb3bad 1447   {
f94629 1448     if ($folder_id == $CONFIG[$smbx.'_mbox'])
64c9b5 1449       return $smbx;
cb3bad 1450   }
T 1451 }
1452
1453
fed22f 1454 /**
T 1455  * Try to localize the given IMAP folder name.
1456  * UTF-7 decode it in case no localized text was found
1457  *
1458  * @param string Folder name
1459  * @return string Localized folder name in UTF-8 encoding
1460  */
1461 function rcmail_localize_foldername($name)
1462 {
1463   if ($folder_class = rcmail_folder_classname($name))
1464     return rcube_label($folder_class);
1465   else
a5897a 1466     return rcube_charset_convert($name, 'UTF7-IMAP');
fed22f 1467 }
T 1468
1469
363514 1470 function rcmail_localize_folderpath($path)
A 1471 {
1472     global $RCMAIL;
1473
1474     $protect_folders = $RCMAIL->config->get('protect_default_folders');
1475     $default_folders = (array) $RCMAIL->config->get('default_imap_folders');
1476     $delimiter       = $RCMAIL->imap->get_hierarchy_delimiter();
1477     $path            = explode($delimiter, $path);
1478     $result          = array();
1479
1480     foreach ($path as $idx => $dir) {
1481         $directory = implode($delimiter, array_slice($path, 0, $idx+1));
1482         if ($protect_folders && in_array($directory, $default_folders)) {
1483             unset($result);
1484             $result[] = rcmail_localize_foldername($directory);
1485         }
1486         else {
1487             $result[] = rcube_charset_convert($dir, 'UTF7-IMAP');
1488         }
1489     }
1490
1491     return implode($delimiter, $result);
1492 }
1493
1494
af3c04 1495 function rcmail_quota_display($attrib)
A 1496 {
1497   global $OUTPUT;
1498
1499   if (!$attrib['id'])
1500     $attrib['id'] = 'rcmquotadisplay';
1501
1502   if(isset($attrib['display']))
1503     $_SESSION['quota_display'] = $attrib['display'];
1504
1505   $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1506
1507   $quota = rcmail_quota_content($attrib);
1508
044d66 1509   $OUTPUT->add_script('rcmail.set_quota('.json_serialize($quota).');', 'docready');
af3c04 1510
A 1511   return html::span($attrib, '');
1512 }
1513
1514
1515 function rcmail_quota_content($attrib=NULL)
1516 {
1517   global $RCMAIL;
1518
1519   $quota = $RCMAIL->imap->get_quota();
1520   $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1521
1522   $quota_result = (array) $quota;
1523   $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1524
1525   if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1526     $quota_result['title'] = rcube_label('unlimited');
1527     $quota_result['percent'] = 0;
1528   }
1529   else if ($quota['total']) {
1530     if (!isset($quota['percent']))
1531       $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1532
1533     $title = sprintf('%s / %s (%.0f%%)',
1534         show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1535         $quota_result['percent']);
1536
1537     $quota_result['title'] = $title;
1538
1539     if ($attrib['width'])
1540       $quota_result['width'] = $attrib['width'];
1541     if ($attrib['height'])
1542       $quota_result['height']    = $attrib['height'];
1543   }
1544   else {
1545     $quota_result['title'] = rcube_label('unknown');
1546     $quota_result['percent'] = 0;
1547   }
1548
1549   return $quota_result;
1550 }
1551
1552
b8ae50 1553 /**
90f81a 1554  * Outputs error message according to server error/response codes
A 1555  *
1556  * @param string Fallback message label
1557  * @param string Fallback message label arguments
1558  *
1559  * @return void
1560  */
1561 function rcmail_display_server_error($fallback=null, $fallback_args=null)
1562 {
1563     global $RCMAIL;
1564
1565     $err_code = $RCMAIL->imap->get_error_code();
1566     $res_code = $RCMAIL->imap->get_response_code();
1567
1568     if ($res_code == rcube_imap::NOPERM) {
1569         $RCMAIL->output->show_message('errornoperm', 'error');
1570     }
1571     else if ($res_code == rcube_imap::READONLY) {
1572         $RCMAIL->output->show_message('errorreadonly', 'error');
1573     }
1574     else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
427e3a 1575         // try to detect access rights problem and display appropriate message
A 1576         if (stripos($err_str, 'Permission denied') !== false)
1577             $RCMAIL->output->show_message('errornoperm', 'error');
1578         else
1579             $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
90f81a 1580     }
A 1581     else if ($fallback) {
1582         $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1583     }
1584
1585     return true;
1586 }
1587
1588
1589 /**
b8ae50 1590  * Output HTML editor scripts
A 1591  *
be5d4a 1592  * @param string Editor mode
5c461b 1593  * @return void
b8ae50 1594  */
A 1595 function rcube_html_editor($mode='')
1596 {
9ab7bc 1597   global $RCMAIL, $CONFIG;
b8ae50 1598
f57257 1599   $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
9ab7bc 1600
A 1601   if ($hook['abort'])
66df08 1602     return;
9ab7bc 1603
1fb718 1604   $lang = strtolower($_SESSION['language']);
A 1605
1606   // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1607   $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1608
dc0040 1609   if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
A 1610     $lang = 'en';
1611
9ab7bc 1612   $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
A 1613   $RCMAIL->output->include_script('editor.js');
66df08 1614   $RCMAIL->output->add_script(sprintf("rcmail_editor_init(%s)",
A 1615     json_encode(array(
1616         'mode'       => $mode,
1617         'skin_path'  => '$__skin_path',
1618         'lang'       => $lang,
1619         'spellcheck' => intval($CONFIG['enable_spellcheck']),
1620         'spelldict'  => intval($CONFIG['spellcheck_dictionary']),
1621     ))), 'foot');
b8ae50 1622 }
aa055c 1623
T 1624
1625 /**
747289 1626  * Replaces TinyMCE's emoticon images with plain-text representation
A 1627  *
1628  * @param string HTML content
1629  * @return string HTML content
1630  */
1631 function rcmail_replace_emoticons($html)
1632 {
1633   $emoticons = array(
1634     '8-)' => 'smiley-cool',
1635     ':-#' => 'smiley-foot-in-mouth',
1636     ':-*' => 'smiley-kiss',
1637     ':-X' => 'smiley-sealed',
1638     ':-P' => 'smiley-tongue-out',
1639     ':-@' => 'smiley-yell',
1640     ":'(" => 'smiley-cry',
1641     ':-(' => 'smiley-frown',
1642     ':-D' => 'smiley-laughing',
1643     ':-)' => 'smiley-smile',
a06535 1644     ':-S' => 'smiley-undecided',
A 1645     ':-$' => 'smiley-embarassed',
1646     'O:-)' => 'smiley-innocent',
747289 1647     ':-|' => 'smiley-money-mouth',
a06535 1648     ':-O' => 'smiley-surprised',
747289 1649     ';-)' => 'smiley-wink',
A 1650   );
1651
1652   foreach ($emoticons as $idx => $file) {
1653     // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1654     $search[]  = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1655     $replace[] = $idx;
1656   }
1657
1658   return preg_replace($search, $replace, $html);
1659 }
1660
1661
1662 /**
d91778 1663  * Send the given message using the configured method
T 1664  *
1665  * @param object $message    Reference to Mail_MIME object
1666  * @param string $from       Sender address string
1667  * @param array  $mailto     Array of recipient address strings
1668  * @param array  $smtp_error SMTP error array (reference)
95192c 1669  * @param string $body_file  Location of file with saved message body (reference),
A 1670  *                           used when delay_file_io is enabled
d91778 1671  * @param array  $smtp_opts  SMTP options (e.g. DSN request)
T 1672  *
1673  * @return boolean Send status.
1674  */
95192c 1675 function rcmail_deliver_message(&$message, $from, $mailto, &$smtp_error, &$body_file=null, $smtp_opts=null)
d91778 1676 {
T 1677   global $CONFIG, $RCMAIL;
1678
1679   $headers = $message->headers();
1680
1681   // send thru SMTP server using custom SMTP library
1682   if ($CONFIG['smtp_server']) {
1683     // generate list of recipients
1684     $a_recipients = array($mailto);
1685
1686     if (strlen($headers['Cc']))
1687       $a_recipients[] = $headers['Cc'];
1688     if (strlen($headers['Bcc']))
1689       $a_recipients[] = $headers['Bcc'];
1690
1691     // clean Bcc from header for recipients
1692     $send_headers = $headers;
1693     unset($send_headers['Bcc']);
1694     // here too, it because txtHeaders() below use $message->_headers not only $send_headers
1695     unset($message->_headers['Bcc']);
1696
1697     $smtp_headers = $message->txtHeaders($send_headers, true);
1698
1699     if ($message->getParam('delay_file_io')) {
1700       // use common temp dir
1701       $temp_dir = $RCMAIL->config->get('temp_dir');
1702       $body_file = tempnam($temp_dir, 'rcmMsg');
1703       if (PEAR::isError($mime_result = $message->saveMessageBody($body_file))) {
782d85 1704         raise_error(array('code' => 650, 'type' => 'php',
d91778 1705             'file' => __FILE__, 'line' => __LINE__,
T 1706             'message' => "Could not create message: ".$mime_result->getMessage()),
1707             TRUE, FALSE);
1708         return false;
1709       }
1710       $msg_body = fopen($body_file, 'r');
1711     } else {
1712       $msg_body = $message->get();
1713     }
1714
1715     // send message
1716     if (!is_object($RCMAIL->smtp))
1717       $RCMAIL->smtp_init(true);
1718
1719     $sent = $RCMAIL->smtp->send_mail($from, $a_recipients, $smtp_headers, $msg_body, $smtp_opts);
1720     $smtp_response = $RCMAIL->smtp->get_response();
1721     $smtp_error = $RCMAIL->smtp->get_error();
1722
1723     // log error
1724     if (!$sent)
1725       raise_error(array('code' => 800, 'type' => 'smtp', 'line' => __LINE__, 'file' => __FILE__,
1726                         'message' => "SMTP error: ".join("\n", $smtp_response)), TRUE, FALSE);
1727   }
1728   // send mail using PHP's mail() function
1729   else {
1730     // unset some headers because they will be added by the mail() function
1731     $headers_enc = $message->headers($headers);
1732     $headers_php = $message->_headers;
1733     unset($headers_php['To'], $headers_php['Subject']);
1734
1735     // reset stored headers and overwrite
1736     $message->_headers = array();
1737     $header_str = $message->txtHeaders($headers_php);
1738
1739     // #1485779
1740     if (strtoupper(substr(PHP_OS, 0, 3)) === 'WIN') {
1741       if (preg_match_all('/<([^@]+@[^>]+)>/', $headers_enc['To'], $m)) {
1742         $headers_enc['To'] = implode(', ', $m[1]);
1743       }
1744     }
1745
1746     $msg_body = $message->get();
1747
1748     if (PEAR::isError($msg_body))
782d85 1749       raise_error(array('code' => 650, 'type' => 'php',
d91778 1750             'file' => __FILE__, 'line' => __LINE__,
T 1751             'message' => "Could not create message: ".$msg_body->getMessage()),
1752             TRUE, FALSE);
1753     else {
1754       $delim   = $RCMAIL->config->header_delimiter();
1755       $to      = $headers_enc['To'];
1756       $subject = $headers_enc['Subject'];
1757       $header_str = rtrim($header_str);
1758
1759       if ($delim != "\r\n") {
1760         $header_str = str_replace("\r\n", $delim, $header_str);
1761         $msg_body   = str_replace("\r\n", $delim, $msg_body);
1762         $to         = str_replace("\r\n", $delim, $to);
1763         $subject    = str_replace("\r\n", $delim, $subject);
1764       }
1765
1766       if (ini_get('safe_mode'))
1767         $sent = mail($to, $subject, $msg_body, $header_str);
1768       else
1769         $sent = mail($to, $subject, $msg_body, $header_str, "-f$from");
1770     }
1771   }
1772
1773   if ($sent) {
1774     $RCMAIL->plugins->exec_hook('message_sent', array('headers' => $headers, 'body' => $msg_body));
1775
1776     // remove MDN headers after sending
1777     unset($headers['Return-Receipt-To'], $headers['Disposition-Notification-To']);
1778
1779     // get all recipients
1780     if ($headers['Cc'])
1781       $mailto .= $headers['Cc'];
1782     if ($headers['Bcc'])
1783       $mailto .= $headers['Bcc'];
1784     if (preg_match_all('/<([^@]+@[^>]+)>/', $mailto, $m))
1785       $mailto = implode(', ', array_unique($m[1]));
1786
1787     if ($CONFIG['smtp_log']) {
1788       write_log('sendmail', sprintf("User %s [%s]; Message for %s; %s",
1789         $RCMAIL->user->get_username(),
1790         $_SERVER['REMOTE_ADDR'],
1791         $mailto,
1792         !empty($smtp_response) ? join('; ', $smtp_response) : ''));
1793     }
1794   }
1795
1796   if (is_resource($msg_body)) {
1797     fclose($msg_body);
1798   }
1799
1800   $message->_headers = array();
1801   $message->headers($headers);
1802
1803   return $sent;
1804 }
1805
1806
1807 // Returns unique Message-ID
1808 function rcmail_gen_message_id()
1809 {
1810   global $RCMAIL;
1811
1812   $local_part  = md5(uniqid('rcmail'.mt_rand(),true));
1813   $domain_part = $RCMAIL->user->get_username('domain');
1814
1815   // Try to find FQDN, some spamfilters doesn't like 'localhost' (#1486924)
1816   if (!preg_match('/\.[a-z]+$/i', $domain_part)) {
1817     if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['HTTP_HOST']))
1818       && preg_match('/\.[a-z]+$/i', $host)) {
1819         $domain_part = $host;
1820     }
1821     else if (($host = preg_replace('/:[0-9]+$/', '', $_SERVER['SERVER_NAME']))
1822       && preg_match('/\.[a-z]+$/i', $host)) {
1823         $domain_part = $host;
1824     }
1825   }
1826
1827   return sprintf('<%s@%s>', $local_part, $domain_part);
1828 }
1829
1830
1831 // Returns RFC2822 formatted current date in user's timezone
1832 function rcmail_user_date()
1833 {
da7178 1834   global $RCMAIL, $CONFIG;
d91778 1835
T 1836   // get user's timezone
da7178 1837   $tz = $RCMAIL->config->get_timezone();
d91778 1838
T 1839   $date = time() + $tz * 60 * 60;
1840   $date = gmdate('r', $date);
1841   $tz   = sprintf('%+05d', intval($tz) * 100 + ($tz - intval($tz)) * 60);
1842   $date = preg_replace('/[+-][0-9]{4}$/', $tz, $date);
1843
1844   return $date;
1845 }
1846
1847
1848 /**
5818e4 1849  * Check if working in SSL mode
A 1850  *
1851  * @param integer HTTPS port number
1852  * @param boolean Enables 'use_https' option checking
5c461b 1853  * @return boolean
5818e4 1854  */
A 1855 function rcube_https_check($port=null, $use_https=true)
1856 {
1857   global $RCMAIL;
3a4c9f 1858
6c5aa6 1859   if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
5818e4 1860     return true;
3a4c9f 1861   if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
A 1862     return true;
5818e4 1863   if ($port && $_SERVER['SERVER_PORT'] == $port)
A 1864     return true;
929a50 1865   if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
5818e4 1866     return true;
A 1867
1868   return false;
1869 }
1870
bb8721 1871
5c461b 1872 /**
A 1873  * For backward compatibility.
1874  *
1875  * @global rcmail $RCMAIL
1876  * @param string $var_name Variable name.
1877  * @return void
1878  */
929a50 1879 function rcube_sess_unset($var_name=null)
A 1880 {
1881   global $RCMAIL;
1882
1883   $RCMAIL->session->remove($var_name);
1884 }
1885
5818e4 1886
5c461b 1887 /**
A 1888  * Replaces hostname variables
1889  *
1890  * @param string $name Hostname
a76cbd 1891  * @param string $host Optional IMAP hostname
5c461b 1892  * @return string
A 1893  */
a76cbd 1894 function rcube_parse_host($name, $host='')
bb8721 1895 {
A 1896   // %n - host
1897   $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
b04c51 1898   // %d - domain name without first part, e.g. %n=mail.domain.tld, %d=domain.tld
bb8721 1899   $d = preg_replace('/^[^\.]+\./', '', $n);
A 1900   // %h - IMAP host
a76cbd 1901   $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
11be93 1902   // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
A 1903   $z = preg_replace('/^[^\.]+\./', '', $h);
2a3800 1904   // %s - domain name after the '@' from e-mail address provided at login screen. Returns FALSE if an invalid email is provided
V 1905   if ( strpos($name, '%s') !== false ){
1906     $user_email = rcube_idn_convert(get_input_value('_user', RCUBE_INPUT_POST), true);
1907     if ( preg_match('/(.*)@([a-z0-9\.\-\[\]\:]+)/i', $user_email, $s) < 1 || filter_var($s[1]."@".$s[2], FILTER_VALIDATE_EMAIL) === false )
1908       return false;
1909   }
bb8721 1910
2a3800 1911   $name = str_replace(array('%n', '%d', '%h', '%z', '%s'), array($n, $d, $h, $z, $s[2]), $name);
bb8721 1912   return $name;
A 1913 }
1914
1915
5818e4 1916 /**
e4acbb 1917  * E-mail address validation
5c461b 1918  *
A 1919  * @param string $email Email address
1920  * @param boolean $dns_check True to check dns
1921  * @return boolean
e4acbb 1922  */
1baeb6 1923 function check_email($email, $dns_check=true)
e4acbb 1924 {
A 1925   // Check for invalid characters
1926   if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1927     return false;
1928
aba092 1929   // Check for length limit specified by RFC 5321 (#1486453)
A 1930   if (strlen($email) > 254) 
1931     return false;
1932
1baeb6 1933   $email_array = explode('@', $email);
A 1934
aba092 1935   // Check that there's one @ symbol
1baeb6 1936   if (count($email_array) < 2)
e4acbb 1937     return false;
A 1938
1baeb6 1939   $domain_part = array_pop($email_array);
A 1940   $local_part = implode('@', $email_array);
1941
1942   // from PEAR::Validate
1943   $regexp = '&^(?:
1944     ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")|                  #1 quoted name
d7a5df 1945     ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*))     #2 OR dot-atom (RFC5322)
1baeb6 1946     $&xi';
A 1947
1948   if (!preg_match($regexp, $local_part))
1949     return false;
e4acbb 1950
A 1951   // Check domain part
1baeb6 1952   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 1953     return true; // IP address
e4acbb 1954   else {
A 1955     // If not an IP address
1baeb6 1956     $domain_array = explode('.', $domain_part);
e4acbb 1957     if (sizeof($domain_array) < 2)
A 1958       return false; // Not enough parts to be a valid domain
1959
1baeb6 1960     foreach ($domain_array as $part)
A 1961       if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
e4acbb 1962         return false;
A 1963
1baeb6 1964     if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
e4acbb 1965       return true;
A 1966
0f3764 1967     if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
A 1968       $lookup = array();
1969       @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1970       foreach ($lookup as $line) {
1971         if (strpos($line, 'MX preference'))
1972           return true;
1973       }
1974       return false;
1975     }
e4acbb 1976
A 1977     // find MX record(s)
1baeb6 1978     if (getmxrr($domain_part, $mx_records))
e4acbb 1979       return true;
A 1980
1981     // find any DNS record
1baeb6 1982     if (checkdnsrr($domain_part, 'ANY'))
e4acbb 1983       return true;
A 1984   }
1985
1986   return false;
1987 }
1988
e8d5bd 1989 /*
A 1990  * Idn_to_ascii wrapper.
1991  * Intl/Idn modules version of this function doesn't work with e-mail address
1992  */
1993 function rcube_idn_to_ascii($str)
1994 {
1995   return rcube_idn_convert($str, true);
1996 }
1997
1998 /*
1999  * Idn_to_ascii wrapper.
2000  * Intl/Idn modules version of this function doesn't work with e-mail address
2001  */
2002 function rcube_idn_to_utf8($str)
2003 {
2004   return rcube_idn_convert($str, false);
2005 }
2006
2007 function rcube_idn_convert($input, $is_utf=false)
2008 {
2009   if ($at = strpos($input, '@')) {
2010     $user   = substr($input, 0, $at);
2011     $domain = substr($input, $at+1);
2012   }
2013   else {
2014     $domain = $input;
2015   }
2016
2017   $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
2018
d654e8 2019   if ($domain === false) {
A 2020     return '';
2021   }
2022
e8d5bd 2023   return $at ? $user . '@' . $domain : $domain;
A 2024 }
2025
e4acbb 2026
A 2027 /**
aa055c 2028  * Helper class to turn relative urls into absolute ones
T 2029  * using a predefined base
2030  */
2031 class rcube_base_replacer
2032 {
2033   private $base_url;
24c91e 2034
aa055c 2035   public function __construct($base)
T 2036   {
2037     $this->base_url = $base;
2038   }
24c91e 2039
aa055c 2040   public function callback($matches)
T 2041   {
2042     return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
2043   }
2044 }
2045
874ff4 2046
23b495 2047 /****** debugging and logging functions ********/
A 2048
2049 /**
2050  * Print or write debug messages
2051  *
2052  * @param mixed Debug message or data
2053  * @return void
2054  */
2055 function console()
2056 {
2057     $args = func_get_args();
2058
2059     if (class_exists('rcmail', false)) {
2060         $rcmail = rcmail::get_instance();
2061         if (is_object($rcmail->plugins)) {
2062             $plugin = $rcmail->plugins->exec_hook('console', array('args' => $args));
2063             if ($plugin['abort'])
2064                 return;
2065             $args = $plugin['args'];
2066         }
2067     }
2068
2069     $msg = array();
2070     foreach ($args as $arg)
2071         $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
2072
2073     write_log('console', join(";\n", $msg));
2074 }
2075
2076
2077 /**
2078  * Append a line to a logfile in the logs directory.
2079  * Date will be added automatically to the line.
2080  *
2081  * @param $name name of log file
2082  * @param line Line to append
2083  * @return void
2084  */
2085 function write_log($name, $line)
2086 {
2087   global $CONFIG, $RCMAIL;
2088
2089   if (!is_string($line))
2090     $line = var_export($line, true);
2091  
2092   if (empty($CONFIG['log_date_format']))
2093     $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
2094   
2095   $date = date($CONFIG['log_date_format']);
2096   
2097   // trigger logging hook
2098   if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
2099     $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
2100     $name = $log['name'];
2101     $line = $log['line'];
2102     $date = $log['date'];
2103     if ($log['abort'])
2104       return true;
2105   }
2106  
2107   if ($CONFIG['log_driver'] == 'syslog') {
2108     $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
2109     syslog($prio, $line);
2110     return true;
2111   }
2112   else {
2113     $line = sprintf("[%s]: %s\n", $date, $line);
2114
2115     // log_driver == 'file' is assumed here
2116     if (empty($CONFIG['log_dir']))
2117       $CONFIG['log_dir'] = INSTALL_PATH.'logs';
2118
2119     // try to open specific log file for writing
2120     $logfile = $CONFIG['log_dir'].'/'.$name;
2121     if ($fp = @fopen($logfile, 'a')) {
2122       fwrite($fp, $line);
2123       fflush($fp);
2124       fclose($fp);
2125       return true;
2126     }
2127     else
2128       trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
2129   }
2130
2131   return false;
2132 }
2133
2134
2135 /**
2136  * Write login data (name, ID, IP address) to the 'userlogins' log file.
2137  *
2138  * @return void
2139  */
2140 function rcmail_log_login()
2141 {
2142   global $RCMAIL;
2143
2144   if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
2145     return;
2146
9d5d7a 2147   write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s in session %s',
T 2148     $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip(), session_id()));
23b495 2149 }
A 2150
2151
2152 /**
2153  * Returns remote IP address and forwarded addresses if found
2154  *
2155  * @return string Remote IP address(es)
2156  */
2157 function rcmail_remote_ip()
2158 {
2159     $address = $_SERVER['REMOTE_ADDR'];
2160
2161     // append the NGINX X-Real-IP header, if set
2162     if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
2163         $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
2164     }
2165     // append the X-Forwarded-For header, if set
2166     if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
2167         $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
2168     }
2169
2170     if (!empty($remote_ip))
2171         $address .= '(' . implode(',', $remote_ip) . ')';
2172
2173     return $address;
2174 }
2175
2176
2177 /**
2178  * Check whether the HTTP referer matches the current request
2179  *
2180  * @return boolean True if referer is the same host+path, false if not
2181  */
2182 function rcube_check_referer()
2183 {
2184   $uri = parse_url($_SERVER['REQUEST_URI']);
2185   $referer = parse_url(rc_request_header('Referer'));
2186   return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
2187 }
2188
2189
2190 /**
2191  * @access private
2192  * @return mixed
2193  */
2194 function rcube_timer()
2195 {
2196   return microtime(true);
2197 }
2198
2199
2200 /**
2201  * @access private
2202  * @return void
2203  */
2204 function rcube_print_time($timer, $label='Timer', $dest='console')
2205 {
2206   static $print_count = 0;
4351f7 2207
23b495 2208   $print_count++;
A 2209   $now = rcube_timer();
2210   $diff = $now-$timer;
4351f7 2211
23b495 2212   if (empty($label))
A 2213     $label = 'Timer '.$print_count;
4351f7 2214
23b495 2215   write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
A 2216 }
2217
2218
24c91e 2219 /**
A 2220  * Throw system error and show error page
2221  *
2222  * @param array Named parameters
2223  *  - code: Error code (required)
2224  *  - type: Error type [php|db|imap|javascript] (required)
2225  *  - message: Error message
2226  *  - file: File where error occured
2227  *  - line: Line where error occured
2228  * @param boolean True to log the error
2229  * @param boolean Terminate script execution
2230  */
874ff4 2231 // may be defined in Installer
A 2232 if (!function_exists('raise_error')) {
24c91e 2233 function raise_error($arg=array(), $log=false, $terminate=false)
A 2234 {
2235     global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
2236
2237     // report bug (if not incompatible browser)
2238     if ($log && $arg['type'] && $arg['message'])
23b495 2239         rcube_log_bug($arg);
24c91e 2240
A 2241     // display error page and terminate script
2242     if ($terminate) {
2243         $ERROR_CODE = $arg['code'];
2244         $ERROR_MESSAGE = $arg['message'];
4351f7 2245         include INSTALL_PATH . 'program/steps/utils/error.inc';
24c91e 2246         exit;
A 2247     }
2248 }
874ff4 2249 }
24c91e 2250
A 2251
2252 /**
2253  * Report error according to configured debug_level
2254  *
2255  * @param array Named parameters
5c461b 2256  * @return void
24c91e 2257  * @see raise_error()
A 2258  */
23b495 2259 function rcube_log_bug($arg_arr)
24c91e 2260 {
A 2261     global $CONFIG;
23b495 2262
24c91e 2263     $program = strtoupper($arg_arr['type']);
23b495 2264     $level   = $CONFIG['debug_level'];
A 2265
2266     // disable errors for ajax requests, write to log instead (#1487831)
2267     if (($level & 4) && !empty($_REQUEST['_remote'])) {
2268         $level = ($level ^ 4) | 1;
2269     }
24c91e 2270
A 2271     // write error to local log file
23b495 2272     if ($level & 1) {
24c91e 2273         $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
A 2274         $log_entry = sprintf("%s Error: %s%s (%s %s)",
2275             $program,
2276             $arg_arr['message'],
2277             $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
2278             $_SERVER['REQUEST_METHOD'],
2279             $_SERVER['REQUEST_URI'] . $post_query);
2280
2281         if (!write_log('errors', $log_entry)) {
2282             // send error to PHPs error handler if write_log didn't succeed
2283             trigger_error($arg_arr['message']);
2284         }
2285     }
2286
23b495 2287     // report the bug to the global bug reporting system
A 2288     if ($level & 2) {
24c91e 2289         // TODO: Send error via HTTP
A 2290     }
2291
2292     // show error if debug_mode is on
23b495 2293     if ($level & 4) {
24c91e 2294         print "<b>$program Error";
A 2295
2296         if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2297             print " in $arg_arr[file] ($arg_arr[line])";
2298
2299         print ':</b>&nbsp;';
2300         print nl2br($arg_arr['message']);
2301         print '<br />';
2302         flush();
2303     }
2304 }
747289 2305
4171c5 2306 function rcube_upload_progress()
A 2307 {
2308     global $RCMAIL;
2309
2310     $prefix = ini_get('apc.rfc1867_prefix');
2311     $params = array(
2312         'action' => $RCMAIL->action,
2313         'name' => get_input_value('_progress', RCUBE_INPUT_GET),
2314     );
2315
2316     if (function_exists('apc_fetch')) {
2317         $status = apc_fetch($prefix . $params['name']);
2318
2319         if (!empty($status)) {
d8aff9 2320             $status['percent'] = round($status['current']/$status['total']*100);
4171c5 2321             $params = array_merge($status, $params);
A 2322         }
2323     }
2324
2325     if (isset($params['percent']))
2326         $params['text'] = rcube_label(array('name' => 'uploadprogress', 'vars' => array(
2327             'percent' => $params['percent'] . '%',
2328             'current' => show_bytes($params['current']),
2329             'total'   => show_bytes($params['total'])
2330         )));
b1057a 2331
4171c5 2332     $RCMAIL->output->command('upload_progress_update', $params);
A 2333     $RCMAIL->output->send();
2334 }
2335
fe0cb6 2336 function rcube_upload_init()
4171c5 2337 {
A 2338     global $RCMAIL;
2339
2340     // Enable upload progress bar
2341     if (($seconds = $RCMAIL->config->get('upload_progress')) && ini_get('apc.rfc1867')) {
2342         if ($field_name = ini_get('apc.rfc1867_name')) {
2343             $RCMAIL->output->set_env('upload_progress_name', $field_name);
2344             $RCMAIL->output->set_env('upload_progress_time', (int) $seconds);
2345         }
2346     }
fe0cb6 2347
A 2348     // find max filesize value
2349     $max_filesize = parse_bytes(ini_get('upload_max_filesize'));
2350     $max_postsize = parse_bytes(ini_get('post_max_size'));
2351     if ($max_postsize && $max_postsize < $max_filesize)
2352         $max_filesize = $max_postsize;
2353
2354     $RCMAIL->output->set_env('max_filesize', $max_filesize);
2355     $max_filesize = show_bytes($max_filesize);
2356     $RCMAIL->output->set_env('filesizeerror', rcube_label(array(
2357         'name' => 'filesizeerror', 'vars' => array('size' => $max_filesize))));
2358
2359     return $max_filesize;
4171c5 2360 }
0213f8 2361
A 2362 /**
2363  * Initializes client-side autocompletion
2364  */
2365 function rcube_autocomplete_init()
2366 {
2367     global $RCMAIL;
2368     static $init;
2369
2370     if ($init)
2371         return;
2372
2373     $init = 1;
2374
2375     if (($threads = (int)$RCMAIL->config->get('autocomplete_threads')) > 0) {
2376       $book_types = (array) $RCMAIL->config->get('autocomplete_addressbooks', 'sql');
2377       if (count($book_types) > 1) {
2378         $RCMAIL->output->set_env('autocomplete_threads', $threads);
2379         $RCMAIL->output->set_env('autocomplete_sources', $book_types);
2380       }
2381     }
2382
2383     $RCMAIL->output->set_env('autocomplete_max', (int)$RCMAIL->config->get('autocomplete_max', 15));
2384     $RCMAIL->output->set_env('autocomplete_min_length', $RCMAIL->config->get('autocomplete_min_length'));
5f7129 2385     $RCMAIL->output->add_label('autocompletechars', 'autocompletemore');
0213f8 2386 }