4 +-----------------------------------------------------------------------+
5 | program/include/main.inc |
7 | This file is part of the Roundcube Webmail client |
8 | Copyright (C) 2005-2009, Roundcube Dev, - Switzerland |
9 | Licensed under the GNU GPL |
12 | Provide basic functions for the webmail package |
14 +-----------------------------------------------------------------------+
15 | Author: Thomas Bruederli <roundcube@gmail.com> |
16 +-----------------------------------------------------------------------+
18 $Id: main.inc 4830 2011-06-02 12:36:32Z alec $
23 * Roundcube Webmail common functions
26 * @author Thomas Bruederli <roundcube@gmail.com>
29 require_once('lib/utf7.inc');
30 require_once('include/rcube_shared.inc');
32 // define constannts for input reading
33 define('RCUBE_INPUT_GET', 0x0101);
34 define('RCUBE_INPUT_POST', 0x0102);
35 define('RCUBE_INPUT_GPC', 0x0103);
40 * Return correct name for a specific database table
42 * @param string Table name
43 * @return string Translated table name
45 function get_table_name($table)
49 // return table name if configured
50 $config_key = 'db_table_'.$table;
52 if (strlen($CONFIG[$config_key]))
53 return $CONFIG[$config_key];
60 * Return correct name for a specific database sequence
61 * (used for Postgres only)
63 * @param string Secuence name
64 * @return string Translated sequence name
66 function get_sequence_name($sequence)
68 // return sequence name if configured
69 $config_key = 'db_sequence_'.$sequence;
70 $opt = rcmail::get_instance()->config->get($config_key);
80 * Get localized text in the desired language
81 * It's a global wrapper for rcmail::gettext()
83 * @param mixed Named parameters array or label name
84 * @return string Localized text
85 * @see rcmail::gettext()
87 function rcube_label($p, $domain=null)
89 return rcmail::get_instance()->gettext($p, $domain);
94 * Overwrite action variable
96 * @param string New action value
98 function rcmail_overwrite_action($action)
100 $app = rcmail::get_instance();
101 $app->action = $action;
102 $app->output->set_env('action', $action);
107 * Compose an URL for a specific action
109 * @param string Request action
110 * @param array More URL parameters
111 * @param string Request task (omit if the same)
112 * @return The application URL
114 function rcmail_url($action, $p=array(), $task=null)
116 $app = rcmail::get_instance();
117 return $app->url((array)$p + array('_action' => $action, 'task' => $task));
122 * Garbage collector function for temp files.
123 * Remove temp files older than two days
125 function rcmail_temp_gc()
127 $rcmail = rcmail::get_instance();
129 $tmp = unslashify($rcmail->config->get('temp_dir'));
130 $expire = mktime() - 172800; // expire in 48 hours
132 if ($dir = opendir($tmp))
134 while (($fname = readdir($dir)) !== false)
136 if ($fname{0} == '.')
139 if (filemtime($tmp.'/'.$fname) < $expire)
140 @unlink($tmp.'/'.$fname);
149 * Garbage collector for cache entries.
150 * Remove all expired message cache records
153 function rcmail_cache_gc()
155 $rcmail = rcmail::get_instance();
156 $db = $rcmail->get_dbh();
158 // get target timestamp
159 $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
161 $db->query("DELETE FROM ".get_table_name('messages')."
162 WHERE created < " . $db->fromunixtime($ts));
164 $db->query("DELETE FROM ".get_table_name('cache')."
165 WHERE created < " . $db->fromunixtime($ts));
170 * Catch an error and throw an exception.
172 * @param int Level of the error
173 * @param string Error message
175 function rcube_error_handler($errno, $errstr)
177 throw new ErrorException($errstr, 0, $errno);
182 * Convert a string from one charset to another.
183 * Uses mbstring and iconv functions if possible
185 * @param string Input string
186 * @param string Suspected charset of the input string
187 * @param string Target charset to convert to; defaults to RCMAIL_CHARSET
188 * @return string Converted string
190 function rcube_charset_convert($str, $from, $to=NULL)
192 static $iconv_options = null;
193 static $mbstring_loaded = null;
194 static $mbstring_list = null;
195 static $convert_warning = false;
200 $to = empty($to) ? strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
201 $from = rcube_parse_charset($from);
203 if ($from == $to || empty($str) || empty($from))
206 // convert charset using iconv module
207 if (function_exists('iconv') && $from != 'UTF7-IMAP' && $to != 'UTF7-IMAP') {
208 if ($iconv_options === null) {
209 // ignore characters not available in output charset
210 $iconv_options = '//IGNORE';
211 if (iconv('', $iconv_options, '') === false) {
212 // iconv implementation does not support options
217 // throw an exception if iconv reports an illegal character in input
218 // it means that input string has been truncated
219 set_error_handler('rcube_error_handler', E_NOTICE);
221 $_iconv = iconv($from, $to . $iconv_options, $str);
222 } catch (ErrorException $e) {
225 restore_error_handler();
226 if ($_iconv !== false) {
231 if ($mbstring_loaded === null)
232 $mbstring_loaded = extension_loaded('mbstring');
234 // convert charset using mbstring module
235 if ($mbstring_loaded) {
236 $aliases['WINDOWS-1257'] = 'ISO-8859-13';
238 if ($mbstring_list === null) {
239 $mbstring_list = mb_list_encodings();
240 $mbstring_list = array_map('strtoupper', $mbstring_list);
243 $mb_from = $aliases[$from] ? $aliases[$from] : $from;
244 $mb_to = $aliases[$to] ? $aliases[$to] : $to;
246 // return if encoding found, string matches encoding and convert succeeded
247 if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
248 if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
253 // convert charset using bundled classes/functions
254 if ($to == 'UTF-8') {
255 if ($from == 'UTF7-IMAP') {
256 if ($_str = utf7_to_utf8($str))
259 else if ($from == 'UTF-7') {
260 if ($_str = rcube_utf7_to_utf8($str))
263 else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
264 return utf8_encode($str);
266 else if (class_exists('utf8')) {
268 $conv = new utf8($from);
270 $conv->loadCharset($from);
272 if($_str = $conv->strToUtf8($str))
278 // encode string for output
279 if ($from == 'UTF-8') {
280 // @TODO: we need a function for UTF-7 (RFC2152) conversion
281 if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
282 if ($_str = utf8_to_utf7($str))
285 else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
286 return utf8_decode($str);
288 else if (class_exists('utf8')) {
290 $conv = new utf8($to);
292 $conv->loadCharset($from);
294 if ($_str = $conv->strToUtf8($str))
301 if ($error && !$convert_warning) {
307 'message' => "Could not convert string from $from to $to. Make sure iconv/mbstring is installed or lib/utf8.class is available."
310 $convert_warning = true;
313 // return UTF-8 or original string
319 * Parse and validate charset name string (see #1485758).
320 * Sometimes charset string is malformed, there are also charset aliases
321 * but we need strict names for charset conversion (specially utf8 class)
323 * @param string Input charset name
324 * @return string The validated charset name
326 function rcube_parse_charset($input)
328 static $charsets = array();
329 $charset = strtoupper($input);
331 if (isset($charsets[$input]))
332 return $charsets[$input];
334 $charset = preg_replace(array(
335 '/^[^0-9A-Z]+/', // e.g. _ISO-8859-JP$SIO
336 '/\$.*$/', // e.g. _ISO-8859-JP$SIO
337 '/UNICODE-1-1-*/', // RFC1641/1642
338 '/^X-/', // X- prefix (e.g. X-ROMAN8 => ROMAN8)
341 if ($charset == 'BINARY')
342 return $charsets[$input] = null;
344 # Aliases: some of them from HTML5 spec.
346 'USASCII' => 'WINDOWS-1252',
347 'ANSIX31101983' => 'WINDOWS-1252',
348 'ANSIX341968' => 'WINDOWS-1252',
349 'UNKNOWN8BIT' => 'ISO-8859-15',
350 'UNKNOWN' => 'ISO-8859-15',
351 'USERDEFINED' => 'ISO-8859-15',
352 'KSC56011987' => 'EUC-KR',
355 'UNICODE' => 'UTF-8',
356 'UTF7IMAP' => 'UTF7-IMAP',
357 'TIS620' => 'WINDOWS-874',
358 'ISO88599' => 'WINDOWS-1254',
359 'ISO885911' => 'WINDOWS-874',
360 'MACROMAN' => 'MACINTOSH',
362 '128' => 'SHIFT-JIS',
367 '161' => 'WINDOWS-1253',
368 '162' => 'WINDOWS-1254',
369 '163' => 'WINDOWS-1258',
370 '177' => 'WINDOWS-1255',
371 '178' => 'WINDOWS-1256',
372 '186' => 'WINDOWS-1257',
373 '204' => 'WINDOWS-1251',
374 '222' => 'WINDOWS-874',
375 '238' => 'WINDOWS-1250',
377 'WINDOWS949' => 'UHC',
380 // allow A-Z and 0-9 only
381 $str = preg_replace('/[^A-Z0-9]/', '', $charset);
383 if (isset($aliases[$str]))
384 $result = $aliases[$str];
386 else if (preg_match('/U[A-Z][A-Z](7|8|16|32)(BE|LE)*/', $str, $m))
387 $result = 'UTF-' . $m[1] . $m[2];
389 else if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
390 $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
391 // some clients sends windows-1252 text as latin1,
392 // it is safe to use windows-1252 for all latin1
393 $result = $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
395 // handle broken charset names e.g. WINDOWS-1250HTTP-EQUIVCONTENT-TYPE
396 else if (preg_match('/(WIN|WINDOWS)([0-9]+)/', $str, $m)) {
397 $result = 'WINDOWS-' . $m[2];
400 else if (preg_match('/LATIN(.*)/', $str, $m)) {
401 $aliases = array('2' => 2, '3' => 3, '4' => 4, '5' => 9, '6' => 10,
402 '7' => 13, '8' => 14, '9' => 15, '10' => 16,
403 'ARABIC' => 6, 'CYRILLIC' => 5, 'GREEK' => 7, 'GREEK1' => 7, 'HEBREW' => 8);
405 // some clients sends windows-1252 text as latin1,
406 // it is safe to use windows-1252 for all latin1
408 $result = 'WINDOWS-1252';
410 // if iconv is not supported we need ISO labels, it's also safe for iconv
411 else if (!empty($aliases[$m[1]])) {
412 $result = 'ISO-8859-'.$aliases[$m[1]];
414 // iconv requires convertion of e.g. LATIN-1 to LATIN1
423 $charsets[$input] = $result;
430 * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
432 * @param string Input string
433 * @return string The converted string
435 function rcube_utf7_to_utf8($str)
438 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
439 0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
440 0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
441 1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
442 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
443 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
444 0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
445 1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
448 $u7len = strlen($str);
452 for ($i=0; $u7len > 0; $i++, $u7len--)
461 for (; $u7len > 0; $i++, $u7len--)
465 if (!$Index_64[ord($u7)])
477 $res .= rcube_utf16_to_utf8(base64_decode($ch));
489 * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
491 * @param string Input string
492 * @return string The converted string
494 function rcube_utf16_to_utf8($str)
499 for ($i = 0; $i < $len; $i += 2) {
500 $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
501 if ($c >= 0x0001 && $c <= 0x007F) {
503 } else if ($c > 0x07FF) {
504 $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
505 $dec .= chr(0x80 | (($c >> 6) & 0x3F));
506 $dec .= chr(0x80 | (($c >> 0) & 0x3F));
508 $dec .= chr(0xC0 | (($c >> 6) & 0x1F));
509 $dec .= chr(0x80 | (($c >> 0) & 0x3F));
517 * Replacing specials characters to a specific encoding type
519 * @param string Input string
520 * @param string Encoding type: text|html|xml|js|url
521 * @param string Replace mode for tags: show|replace|remove
522 * @param boolean Convert newlines
523 * @return string The quoted string
525 function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
527 static $html_encode_arr = false;
528 static $js_rep_table = false;
529 static $xml_rep_table = false;
532 $enctype = $OUTPUT->type;
534 // encode for HTML output
535 if ($enctype=='html')
537 if (!$html_encode_arr)
539 $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);
540 unset($html_encode_arr['?']);
543 $ltpos = strpos($str, '<');
544 $encode_arr = $html_encode_arr;
546 // don't replace quotes and html tags
547 if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
549 unset($encode_arr['"']);
550 unset($encode_arr['<']);
551 unset($encode_arr['>']);
552 unset($encode_arr['&']);
554 else if ($mode=='remove')
555 $str = strip_tags($str);
557 $out = strtr($str, $encode_arr);
559 // avoid douple quotation of &
560 $out = preg_replace('/&([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', $out);
562 return $newlines ? nl2br($out) : $out;
565 // if the replace tables for XML and JS are not yet defined
566 if ($js_rep_table===false)
568 $js_rep_table = $xml_rep_table = array();
569 $xml_rep_table['&'] = '&';
571 for ($c=160; $c<256; $c++) // can be increased to support more charsets
572 $xml_rep_table[chr($c)] = "&#$c;";
574 $xml_rep_table['"'] = '"';
575 $js_rep_table['"'] = '\\"';
576 $js_rep_table["'"] = "\\'";
577 $js_rep_table["\\"] = "\\\\";
578 // Unicode line and paragraph separators (#1486310)
579 $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A8))] = '
';
580 $js_rep_table[chr(hexdec(E2)).chr(hexdec(80)).chr(hexdec(A9))] = '
';
583 // encode for javascript use
585 return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
587 // encode for plaintext
588 if ($enctype=='text')
589 return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
592 return rawurlencode($str);
596 return strtr($str, $xml_rep_table);
598 // no encoding given -> return original string
603 * Quote a given string.
604 * Shortcut function for rep_specialchars_output
606 * @return string HTML-quoted string
607 * @see rep_specialchars_output()
609 function Q($str, $mode='strict', $newlines=TRUE)
611 return rep_specialchars_output($str, 'html', $mode, $newlines);
615 * Quote a given string for javascript output.
616 * Shortcut function for rep_specialchars_output
618 * @return string JS-quoted string
619 * @see rep_specialchars_output()
623 return rep_specialchars_output($str, 'js');
628 * Read input value and convert it for internal use
629 * Performs stripslashes() and charset conversion if necessary
631 * @param string Field name to read
632 * @param int Source to get value from (GPC)
633 * @param boolean Allow HTML tags in field value
634 * @param string Charset to convert into
635 * @return string Field value or NULL if not available
637 function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
641 if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
642 $value = $_GET[$fname];
643 else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
644 $value = $_POST[$fname];
645 else if ($source==RCUBE_INPUT_GPC)
647 if (isset($_POST[$fname]))
648 $value = $_POST[$fname];
649 else if (isset($_GET[$fname]))
650 $value = $_GET[$fname];
651 else if (isset($_COOKIE[$fname]))
652 $value = $_COOKIE[$fname];
655 return parse_input_value($value, $allow_html, $charset);
659 * Parse/validate input value. See get_input_value()
660 * Performs stripslashes() and charset conversion if necessary
662 * @param string Input value
663 * @param boolean Allow HTML tags in field value
664 * @param string Charset to convert into
665 * @return string Parsed value
667 function parse_input_value($value, $allow_html=FALSE, $charset=NULL)
674 if (is_array($value)) {
675 foreach ($value as $idx => $val)
676 $value[$idx] = parse_input_value($val, $allow_html, $charset);
680 // strip single quotes if magic_quotes_sybase is enabled
681 if (ini_get('magic_quotes_sybase'))
682 $value = str_replace("''", "'", $value);
683 // strip slashes if magic_quotes enabled
684 else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
685 $value = stripslashes($value);
687 // remove HTML tags if not allowed
689 $value = strip_tags($value);
691 // convert to internal charset
692 if (is_object($OUTPUT) && $charset)
693 return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
699 * Convert array of request parameters (prefixed with _)
700 * to a regular array with non-prefixed keys.
702 * @param int Source to get value from (GPC)
703 * @return array Hash array with all request parameters
705 function request2param($mode = RCUBE_INPUT_GPC)
708 $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
709 foreach ($src as $key => $value) {
710 $fname = $key[0] == '_' ? substr($key, 1) : $key;
711 $out[$fname] = get_input_value($key, $mode);
718 * Remove all non-ascii and non-word chars
721 function asciiwords($str, $css_id = false, $replace_with = '')
723 $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
724 return preg_replace("/[^$allowed]/i", $replace_with, $str);
728 * Remove single and double quotes from given string
730 * @param string Input value
731 * @return string Dequoted string
733 function strip_quotes($str)
735 return str_replace(array("'", '"'), '', $str);
740 * Remove new lines characters from given string
742 * @param string Input value
743 * @return string Stripped string
745 function strip_newlines($str)
747 return preg_replace('/[\r\n]/', '', $str);
752 * Create a HTML table based on the given data
754 * @param array Named table attributes
755 * @param mixed Table row data. Either a two-dimensional array or a valid SQL result set
756 * @param array List of cols to show
757 * @param string Name of the identifier col
758 * @return string HTML table code
760 function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
764 $table = new html_table(/*array('cols' => count($a_show_cols))*/);
767 if (!$attrib['noheader'])
768 foreach ($a_show_cols as $col)
769 $table->add_header($col, Q(rcube_label($col)));
772 if (!is_array($table_data))
774 $db = $RCMAIL->get_dbh();
775 while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
777 $zebra_class = $c % 2 ? 'even' : 'odd';
778 $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class));
781 foreach ($a_show_cols as $col)
782 $table->add($col, Q($sql_arr[$col]));
789 foreach ($table_data as $row_data)
791 $zebra_class = $c % 2 ? 'even' : 'odd';
792 if (!empty($row_data['class']))
793 $zebra_class .= ' '.$row_data['class'];
795 $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class));
798 foreach ($a_show_cols as $col)
799 $table->add($col, Q($row_data[$col]));
805 return $table->show($attrib);
810 * Create an edit field for inclusion on a form
812 * @param string col field name
813 * @param string value field value
814 * @param array attrib HTML element attributes for field
815 * @param string type HTML element type (default 'text')
816 * @return string HTML field definition
818 function rcmail_get_edit_field($col, $value, $attrib, $type='text')
821 $attrib['name'] = $fname;
823 if ($type=='checkbox')
825 $attrib['value'] = '1';
826 $input = new html_checkbox($attrib);
828 else if ($type=='textarea')
830 $attrib['cols'] = $attrib['size'];
831 $input = new html_textarea($attrib);
834 $input = new html_inputfield($attrib);
836 // use value from post
837 if (!empty($_POST[$fname]))
838 $value = get_input_value($fname, RCUBE_INPUT_POST,
839 $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
841 $out = $input->show($value);
848 * Replace all css definitions with #container [def]
849 * and remove css-inlined scripting
851 * @param string CSS source code
852 * @param string Container ID to use as prefix
853 * @return string Modified CSS source
855 function rcmail_mod_css_styles($source, $container_id)
858 $replacements = new rcube_string_replacer;
860 // ignore the whole block if evil styles are detected
861 $stripped = preg_replace('/[^a-z\(:;]/', '', rcmail_xss_entity_decode($source));
862 if (preg_match('/expression|behavior|url\(|import[^a]/', $stripped))
863 return '/* evil! */';
865 // remove css comments (sometimes used for some ugly hacks)
866 $source = preg_replace('!/\*(.+)\*/!Ums', '', $source);
868 // cut out all contents between { and }
869 while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
871 $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
872 $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
876 // remove html comments and add #container to each tag selector.
877 // also replace body definition because we also stripped off the <body> tag
878 $styles = preg_replace(
880 '/(^\s*<!--)|(-->\s*$)/',
881 '/(^\s*|,\s*|\}\s*)([a-z0-9\._#\*][a-z0-9\.\-_]*)/im',
882 '/'.preg_quote($container_id, '/').'\s+body/i',
886 "\\1#$container_id \\2",
891 // put block contents back in
892 $styles = $replacements->resolve($styles);
899 * Decode escaped entities used by known XSS exploits.
900 * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
902 * @param string CSS content to decode
903 * @return string Decoded string
905 function rcmail_xss_entity_decode($content)
907 $out = html_entity_decode(html_entity_decode($content));
908 $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
909 $out = preg_replace('#/\*.*\*/#Um', '', $out);
915 * preg_replace_callback callback for rcmail_xss_entity_decode_callback
917 * @param array matches result from preg_replace_callback
918 * @return string decoded entity
920 function rcmail_xss_entity_decode_callback($matches)
922 return chr(hexdec($matches[1]));
926 * Compose a valid attribute string for HTML tags
928 * @param array Named tag attributes
929 * @param array List of allowed attributes
930 * @return string HTML formatted attribute string
932 function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
934 // allow the following attributes to be added to the <iframe> tag
936 foreach ($allowed_attribs as $a)
937 if (isset($attrib[$a]))
938 $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '"', $attrib[$a]));
945 * Convert a HTML attribute string attributes to an associative array (name => value)
947 * @param string Input string
948 * @return array Key-value pairs of parsed attributes
950 function parse_attrib_string($str)
953 preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
955 // convert attributes to an associative array (name => value)
957 foreach ($regs as $attr) {
958 $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
967 * Convert the given date to a human readable form
968 * This uses the date formatting properties from config
970 * @param mixed Date representation (string or timestamp)
971 * @param string Date format to use
972 * @return string Formatted date string
974 function format_date($date, $format=NULL)
980 if (is_numeric($date))
982 else if (!empty($date))
984 // support non-standard "GMTXXXX" literal
985 $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
986 // if date parsing fails, we have a date in non-rfc format.
987 // remove token from the end and try again
988 while ((($ts = @strtotime($date))===false) || ($ts < 0))
990 $d = explode(' ', $date);
993 $date = implode(' ', $d);
1000 // get user's timezone
1001 if ($CONFIG['timezone'] === 'auto')
1002 $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
1004 $tz = $CONFIG['timezone'];
1005 if ($CONFIG['dst_active'])
1009 // convert time to user's timezone
1010 $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
1012 // get current timestamp in user's timezone
1013 $now = time(); // local time
1014 $now -= (int)date('Z'); // make GMT time
1015 $now += ($tz * 3600); // user's time
1016 $now_date = getdate($now);
1018 $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
1019 $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
1021 // define date format depending on current time
1023 if ($CONFIG['prettydate'] && $timestamp > $today_limit && $timestamp < $now) {
1024 $format = $CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i';
1027 else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1028 $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
1030 $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
1033 // strftime() format
1034 if (preg_match('/%[a-z]+/i', $format)) {
1035 $format = strftime($format, $timestamp);
1036 return $today ? (rcube_label('today') . ' ' . $format) : $format;
1039 // parse format string manually in order to provide localized weekday and month names
1040 // an alternative would be to convert the date() format string to fit with strftime()
1042 for($i=0; $i<strlen($format); $i++) {
1043 if ($format{$i}=='\\') // skip escape chars
1046 // write char "as-is"
1047 if ($format{$i}==' ' || $format{$i-1}=='\\')
1048 $out .= $format{$i};
1050 else if ($format{$i}=='D')
1051 $out .= rcube_label(strtolower(date('D', $timestamp)));
1053 else if ($format{$i}=='l')
1054 $out .= rcube_label(strtolower(date('l', $timestamp)));
1055 // month name (short)
1056 else if ($format{$i}=='M')
1057 $out .= rcube_label(strtolower(date('M', $timestamp)));
1058 // month name (long)
1059 else if ($format{$i}=='F')
1060 $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1061 else if ($format{$i}=='x')
1062 $out .= strftime('%x %X', $timestamp);
1064 $out .= date($format{$i}, $timestamp);
1068 $label = rcube_label('today');
1069 // replcae $ character with "Today" label (#1486120)
1070 if (strpos($out, '$') !== false) {
1071 $out = preg_replace('/\$/', $label, $out, 1);
1074 $out = $label . ' ' . $out;
1083 * Compose a valid representation of name and e-mail address
1085 * @param string E-mail address
1086 * @param string Person name
1087 * @return string Formatted string
1089 function format_email_recipient($email, $name='')
1091 if ($name && $name != $email)
1093 // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1094 return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1097 return trim($email);
1102 /****** debugging functions ********/
1106 * Print or write debug messages
1108 * @param mixed Debug message or data
1113 $args = func_get_args();
1115 if (class_exists('rcmail', false)) {
1116 $rcmail = rcmail::get_instance();
1117 if (is_object($rcmail->plugins))
1118 $rcmail->plugins->exec_hook('console', $args);
1122 foreach ($args as $arg)
1123 $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1125 if (!($GLOBALS['CONFIG']['debug_level'] & 4))
1126 write_log('console', join(";\n", $msg));
1127 else if ($GLOBALS['OUTPUT']->ajax_call)
1128 print "/*\n " . join(";\n", $msg) . " \n*/\n";
1131 print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
1132 print join(";<br/>\n", $msg);
1133 print "</pre></div>\n";
1139 * Append a line to a logfile in the logs directory.
1140 * Date will be added automatically to the line.
1142 * @param $name name of log file
1143 * @param line Line to append
1146 function write_log($name, $line)
1148 global $CONFIG, $RCMAIL;
1150 if (!is_string($line))
1151 $line = var_export($line, true);
1153 if (empty($CONFIG['log_date_format']))
1154 $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1156 $date = date($CONFIG['log_date_format']);
1158 // trigger logging hook
1159 if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1160 $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1161 $name = $log['name'];
1162 $line = $log['line'];
1163 $date = $log['date'];
1168 if ($CONFIG['log_driver'] == 'syslog') {
1169 $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1170 syslog($prio, $line);
1174 $line = sprintf("[%s]: %s\n", $date, $line);
1176 // log_driver == 'file' is assumed here
1177 if (empty($CONFIG['log_dir']))
1178 $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1180 // try to open specific log file for writing
1181 $logfile = $CONFIG['log_dir'].'/'.$name;
1182 if ($fp = @fopen($logfile, 'a')) {
1189 trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1196 * Write login data (name, ID, IP address) to the 'userlogins' log file.
1200 function rcmail_log_login()
1204 if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1207 write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1208 $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip()));
1213 * Returns remote IP address and forwarded addresses if found
1215 * @return string Remote IP address(es)
1217 function rcmail_remote_ip()
1219 $address = $_SERVER['REMOTE_ADDR'];
1221 // append the NGINX X-Real-IP header, if set
1222 if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1223 $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1225 // append the X-Forwarded-For header, if set
1226 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1227 $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1230 if (!empty($remote_ip))
1231 $address .= '(' . implode(',', $remote_ip) . ')';
1238 * Check whether the HTTP referer matches the current request
1240 * @return boolean True if referer is the same host+path, false if not
1242 function rcube_check_referer()
1244 $uri = parse_url($_SERVER['REQUEST_URI']);
1245 $referer = parse_url(rc_request_header('Referer'));
1246 return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
1254 function rcube_timer()
1256 return microtime(true);
1264 function rcube_print_time($timer, $label='Timer', $dest='console')
1266 static $print_count = 0;
1269 $now = rcube_timer();
1270 $diff = $now-$timer;
1273 $label = 'Timer '.$print_count;
1275 write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1280 * Return the mailboxlist in HTML
1282 * @param array Named parameters
1283 * @return string HTML code for the gui object
1285 function rcmail_mailbox_list($attrib)
1288 static $a_mailboxes;
1290 $attrib += array('maxlength' => 100, 'realnames' => false);
1292 // add some labels to client
1293 $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1295 $type = $attrib['type'] ? $attrib['type'] : 'ul';
1296 unset($attrib['type']);
1298 if ($type=='ul' && !$attrib['id'])
1299 $attrib['id'] = 'rcmboxlist';
1302 $mbox_name = $RCMAIL->imap->get_mailbox_name();
1304 // build the folders tree
1305 if (empty($a_mailboxes)) {
1307 $a_folders = $RCMAIL->imap->list_mailboxes();
1308 $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1309 $a_mailboxes = array();
1311 foreach ($a_folders as $folder)
1312 rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1315 // allow plugins to alter the folder tree or to localize folder names
1316 $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1318 if ($type == 'select') {
1319 $select = new html_select($attrib);
1321 // add no-selection option
1322 if ($attrib['noselection'])
1323 $select->add(rcube_label($attrib['noselection']), '');
1325 rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1326 $out = $select->show();
1329 $js_mailboxlist = array();
1330 $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1332 $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1333 $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1334 $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1342 * Return the mailboxlist as html_select object
1344 * @param array Named parameters
1345 * @return html_select HTML drop-down object
1347 function rcmail_mailbox_select($p = array())
1351 $p += array('maxlength' => 100, 'realnames' => false);
1352 $a_mailboxes = array();
1354 if ($p['unsubscribed'])
1355 $list = $RCMAIL->imap->list_unsubscribed();
1357 $list = $RCMAIL->imap->list_mailboxes();
1359 foreach ($list as $folder)
1360 if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1361 rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1363 $select = new html_select($p);
1365 if ($p['noselection'])
1366 $select->add($p['noselection'], '');
1368 rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1375 * Create a hierarchical array of the mailbox list
1379 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1383 $pos = strpos($folder, $delm);
1385 if ($pos !== false) {
1386 $subFolders = substr($folder, $pos+1);
1387 $currentFolder = substr($folder, 0, $pos);
1389 // sometimes folder has a delimiter as the last character
1390 if (!strlen($subFolders))
1392 else if (!isset($arrFolders[$currentFolder]))
1395 $virtual = $arrFolders[$currentFolder]['virtual'];
1398 $subFolders = false;
1399 $currentFolder = $folder;
1403 $path .= $currentFolder;
1405 // Check \Noselect option (if options are in cache)
1406 if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
1407 $virtual = in_array('\\Noselect', $opts);
1410 if (!isset($arrFolders[$currentFolder])) {
1411 $arrFolders[$currentFolder] = array(
1413 'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1414 'virtual' => $virtual,
1415 'folders' => array());
1418 $arrFolders[$currentFolder]['virtual'] = $virtual;
1420 if (strlen($subFolders))
1421 rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1426 * Return html for a structured list <ul> for the mailbox tree
1430 function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1432 global $RCMAIL, $CONFIG;
1434 $maxlength = intval($attrib['maxlength']);
1435 $realnames = (bool)$attrib['realnames'];
1436 $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1440 foreach ($arrFolders as $key => $folder) {
1441 $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1444 if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1445 $foldername = rcube_label($folder_class);
1448 $foldername = $folder['name'];
1450 // shorten the folder name to a given length
1451 if ($maxlength && $maxlength > 1) {
1452 $fname = abbreviate_string($foldername, $maxlength);
1453 if ($fname != $foldername)
1454 $title = $foldername;
1455 $foldername = $fname;
1459 // make folder name safe for ids and class names
1460 $folder_id = asciiwords($folder['id'], true, '_');
1461 $classes = array('mailbox');
1463 // set special class for Sent, Drafts, Trash and Junk
1464 if ($folder['id']==$CONFIG['sent_mbox'])
1465 $classes[] = 'sent';
1466 else if ($folder['id']==$CONFIG['drafts_mbox'])
1467 $classes[] = 'drafts';
1468 else if ($folder['id']==$CONFIG['trash_mbox'])
1469 $classes[] = 'trash';
1470 else if ($folder['id']==$CONFIG['junk_mbox'])
1471 $classes[] = 'junk';
1472 else if ($folder['id']=='INBOX')
1473 $classes[] = 'inbox';
1475 $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1477 $classes[] = $zebra_class;
1479 if ($folder['id'] == $mbox_name)
1480 $classes[] = 'selected';
1482 $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1483 $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1485 if ($folder['virtual'])
1486 $classes[] = 'virtual';
1488 $classes[] = 'unread';
1490 $js_name = JQ($folder['id']);
1491 $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1492 $link_attrib = $folder['virtual'] ? array() : array(
1493 'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1494 'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1498 $out .= html::tag('li', array(
1499 'id' => "rcmli".$folder_id,
1500 'class' => join(' ', $classes),
1502 html::a($link_attrib, $html_name) .
1503 (!empty($folder['folders']) ? html::div(array(
1504 'class' => ($collapsed ? 'collapsed' : 'expanded'),
1505 'style' => "position:absolute",
1506 'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1507 ), ' ') : ''));
1509 $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1511 if (!empty($folder['folders'])) {
1512 $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1513 rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1525 * Return html for a flat list <select> for the mailbox tree
1529 function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1533 foreach ($arrFolders as $key=>$folder) {
1534 if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1535 $foldername = rcube_label($folder_class);
1537 $foldername = $folder['name'];
1539 // shorten the folder name to a given length
1540 if ($maxlength && $maxlength>1)
1541 $foldername = abbreviate_string($foldername, $maxlength);
1544 $select->add(str_repeat(' ', $nestLevel*4) . $foldername, $folder['id']);
1546 if (!empty($folder['folders']))
1547 $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1555 * Return internal name for the given folder if it matches the configured special folders
1559 function rcmail_folder_classname($folder_id)
1563 if ($folder_id == 'INBOX')
1566 // for these mailboxes we have localized labels and css classes
1567 foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1569 if ($folder_id == $CONFIG[$smbx.'_mbox'])
1576 * Try to localize the given IMAP folder name.
1577 * UTF-7 decode it in case no localized text was found
1579 * @param string Folder name
1580 * @return string Localized folder name in UTF-8 encoding
1582 function rcmail_localize_foldername($name)
1584 if ($folder_class = rcmail_folder_classname($name))
1585 return rcube_label($folder_class);
1587 return rcube_charset_convert($name, 'UTF7-IMAP');
1591 function rcmail_quota_display($attrib)
1596 $attrib['id'] = 'rcmquotadisplay';
1598 if(isset($attrib['display']))
1599 $_SESSION['quota_display'] = $attrib['display'];
1601 $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1603 $quota = rcmail_quota_content($attrib);
1605 $OUTPUT->add_script('$(document).ready(function(){
1606 rcmail.set_quota('.json_serialize($quota).')});', 'foot');
1608 return html::span($attrib, '');
1612 function rcmail_quota_content($attrib=NULL)
1616 $quota = $RCMAIL->imap->get_quota();
1617 $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1619 $quota_result = (array) $quota;
1620 $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1622 if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1623 $quota_result['title'] = rcube_label('unlimited');
1624 $quota_result['percent'] = 0;
1626 else if ($quota['total']) {
1627 if (!isset($quota['percent']))
1628 $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1630 $title = sprintf('%s / %s (%.0f%%)',
1631 show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1632 $quota_result['percent']);
1634 $quota_result['title'] = $title;
1636 if ($attrib['width'])
1637 $quota_result['width'] = $attrib['width'];
1638 if ($attrib['height'])
1639 $quota_result['height'] = $attrib['height'];
1642 $quota_result['title'] = rcube_label('unknown');
1643 $quota_result['percent'] = 0;
1646 return $quota_result;
1651 * Outputs error message according to server error/response codes
1653 * @param string Fallback message label
1654 * @param string Fallback message label arguments
1658 function rcmail_display_server_error($fallback=null, $fallback_args=null)
1662 $err_code = $RCMAIL->imap->get_error_code();
1663 $res_code = $RCMAIL->imap->get_response_code();
1665 if ($res_code == rcube_imap::NOPERM) {
1666 $RCMAIL->output->show_message('errornoperm', 'error');
1668 else if ($res_code == rcube_imap::READONLY) {
1669 $RCMAIL->output->show_message('errorreadonly', 'error');
1671 else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1672 $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1674 else if ($fallback) {
1675 $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1683 * Output HTML editor scripts
1685 * @param string Editor mode
1688 function rcube_html_editor($mode='')
1690 global $RCMAIL, $CONFIG;
1692 $hook = $RCMAIL->plugins->exec_hook('html_editor', array('mode' => $mode));
1697 $lang = strtolower($_SESSION['language']);
1699 // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1700 $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1702 if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1705 $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1706 $RCMAIL->output->include_script('editor.js');
1707 $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
1708 JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
1714 * Replaces TinyMCE's emoticon images with plain-text representation
1716 * @param string HTML content
1717 * @return string HTML content
1719 function rcmail_replace_emoticons($html)
1722 '8-)' => 'smiley-cool',
1723 ':-#' => 'smiley-foot-in-mouth',
1724 ':-*' => 'smiley-kiss',
1725 ':-X' => 'smiley-sealed',
1726 ':-P' => 'smiley-tongue-out',
1727 ':-@' => 'smiley-yell',
1728 ":'(" => 'smiley-cry',
1729 ':-(' => 'smiley-frown',
1730 ':-D' => 'smiley-laughing',
1731 ':-)' => 'smiley-smile',
1732 ':-S' => 'smiley-undecided',
1733 ':-$' => 'smiley-embarassed',
1734 'O:-)' => 'smiley-innocent',
1735 ':-|' => 'smiley-money-mouth',
1736 ':-O' => 'smiley-surprised',
1737 ';-)' => 'smiley-wink',
1740 foreach ($emoticons as $idx => $file) {
1741 // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1742 $search[] = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1746 return preg_replace($search, $replace, $html);
1751 * Check if working in SSL mode
1753 * @param integer HTTPS port number
1754 * @param boolean Enables 'use_https' option checking
1757 function rcube_https_check($port=null, $use_https=true)
1761 if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1763 if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1765 if ($port && $_SERVER['SERVER_PORT'] == $port)
1767 if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1775 * For backward compatibility.
1777 * @global rcmail $RCMAIL
1778 * @param string $var_name Variable name.
1781 function rcube_sess_unset($var_name=null)
1785 $RCMAIL->session->remove($var_name);
1791 * Replaces hostname variables
1793 * @param string $name Hostname
1794 * @param string $host Optional IMAP hostname
1797 function rcube_parse_host($name, $host='')
1800 $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1801 // %d - domain name without first part, e.g. %d=mail.domain.tld, %m=domain.tld
1802 $d = preg_replace('/^[^\.]+\./', '', $n);
1804 $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
1805 // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1806 $z = preg_replace('/^[^\.]+\./', '', $h);
1808 $name = str_replace(array('%n', '%d', '%h', '%z'), array($n, $d, $h, $z), $name);
1814 * E-mail address validation
1816 * @param string $email Email address
1817 * @param boolean $dns_check True to check dns
1820 function check_email($email, $dns_check=true)
1822 // Check for invalid characters
1823 if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1826 // Check for length limit specified by RFC 5321 (#1486453)
1827 if (strlen($email) > 254)
1830 $email_array = explode('@', $email);
1832 // Check that there's one @ symbol
1833 if (count($email_array) < 2)
1836 $domain_part = array_pop($email_array);
1837 $local_part = implode('@', $email_array);
1839 // from PEAR::Validate
1841 ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| #1 quoted name
1842 ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*)) #2 OR dot-atom (RFC5322)
1845 if (!preg_match($regexp, $local_part))
1848 // Check domain part
1849 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))
1850 return true; // IP address
1852 // If not an IP address
1853 $domain_array = explode('.', $domain_part);
1854 if (sizeof($domain_array) < 2)
1855 return false; // Not enough parts to be a valid domain
1857 foreach ($domain_array as $part)
1858 if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1861 if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1864 if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1866 @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1867 foreach ($lookup as $line) {
1868 if (strpos($line, 'MX preference'))
1874 // find MX record(s)
1875 if (getmxrr($domain_part, $mx_records))
1878 // find any DNS record
1879 if (checkdnsrr($domain_part, 'ANY'))
1887 * Idn_to_ascii wrapper.
1888 * Intl/Idn modules version of this function doesn't work with e-mail address
1890 function rcube_idn_to_ascii($str)
1892 return rcube_idn_convert($str, true);
1896 * Idn_to_ascii wrapper.
1897 * Intl/Idn modules version of this function doesn't work with e-mail address
1899 function rcube_idn_to_utf8($str)
1901 return rcube_idn_convert($str, false);
1904 function rcube_idn_convert($input, $is_utf=false)
1906 if ($at = strpos($input, '@')) {
1907 $user = substr($input, 0, $at);
1908 $domain = substr($input, $at+1);
1914 $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
1916 if ($domain === false) {
1920 return $at ? $user . '@' . $domain : $domain;
1925 * Helper class to turn relative urls into absolute ones
1926 * using a predefined base
1928 class rcube_base_replacer
1932 public function __construct($base)
1934 $this->base_url = $base;
1937 public function callback($matches)
1939 return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1945 * Throw system error and show error page
1947 * @param array Named parameters
1948 * - code: Error code (required)
1949 * - type: Error type [php|db|imap|javascript] (required)
1950 * - message: Error message
1951 * - file: File where error occured
1952 * - line: Line where error occured
1953 * @param boolean True to log the error
1954 * @param boolean Terminate script execution
1956 // may be defined in Installer
1957 if (!function_exists('raise_error')) {
1958 function raise_error($arg=array(), $log=false, $terminate=false)
1960 global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
1962 // report bug (if not incompatible browser)
1963 if ($log && $arg['type'] && $arg['message'])
1966 // display error page and terminate script
1968 $ERROR_CODE = $arg['code'];
1969 $ERROR_MESSAGE = $arg['message'];
1970 include('program/steps/utils/error.inc');
1978 * Report error according to configured debug_level
1980 * @param array Named parameters
1982 * @see raise_error()
1984 function log_bug($arg_arr)
1987 $program = strtoupper($arg_arr['type']);
1989 // write error to local log file
1990 if ($CONFIG['debug_level'] & 1) {
1991 $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
1992 $log_entry = sprintf("%s Error: %s%s (%s %s)",
1994 $arg_arr['message'],
1995 $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
1996 $_SERVER['REQUEST_METHOD'],
1997 $_SERVER['REQUEST_URI'] . $post_query);
1999 if (!write_log('errors', $log_entry)) {
2000 // send error to PHPs error handler if write_log didn't succeed
2001 trigger_error($arg_arr['message']);
2005 // resport the bug to the global bug reporting system
2006 if ($CONFIG['debug_level'] & 2) {
2007 // TODO: Send error via HTTP
2010 // show error if debug_mode is on
2011 if ($CONFIG['debug_level'] & 4) {
2012 print "<b>$program Error";
2014 if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2015 print " in $arg_arr[file] ($arg_arr[line])";
2017 print ':</b> ';
2018 print nl2br($arg_arr['message']);