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