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