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 4509 2011-02-09 10:51:50Z thomasb $
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 return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
1025 else if ($CONFIG['prettydate'] && $timestamp > $week_limit && $timestamp < $now)
1026 $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
1028 $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
1031 // strftime() format
1032 if (preg_match('/%[a-z]+/i', $format))
1033 return strftime($format, $timestamp);
1035 // parse format string manually in order to provide localized weekday and month names
1036 // an alternative would be to convert the date() format string to fit with strftime()
1038 for($i=0; $i<strlen($format); $i++)
1040 if ($format{$i}=='\\') // skip escape chars
1043 // write char "as-is"
1044 if ($format{$i}==' ' || $format{$i-1}=='\\')
1045 $out .= $format{$i};
1047 else if ($format{$i}=='D')
1048 $out .= rcube_label(strtolower(date('D', $timestamp)));
1050 else if ($format{$i}=='l')
1051 $out .= rcube_label(strtolower(date('l', $timestamp)));
1052 // month name (short)
1053 else if ($format{$i}=='M')
1054 $out .= rcube_label(strtolower(date('M', $timestamp)));
1055 // month name (long)
1056 else if ($format{$i}=='F')
1057 $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
1058 else if ($format{$i}=='x')
1059 $out .= strftime('%x %X', $timestamp);
1061 $out .= date($format{$i}, $timestamp);
1069 * Compose a valid representation of name and e-mail address
1071 * @param string E-mail address
1072 * @param string Person name
1073 * @return string Formatted string
1075 function format_email_recipient($email, $name='')
1077 if ($name && $name != $email)
1079 // Special chars as defined by RFC 822 need to in quoted string (or escaped).
1080 return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
1083 return trim($email);
1088 /****** debugging functions ********/
1092 * Print or write debug messages
1094 * @param mixed Debug message or data
1099 $args = func_get_args();
1101 if (class_exists('rcmail', false)) {
1102 $rcmail = rcmail::get_instance();
1103 if (is_object($rcmail->plugins))
1104 $rcmail->plugins->exec_hook('console', $args);
1108 foreach ($args as $arg)
1109 $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
1111 if (!($GLOBALS['CONFIG']['debug_level'] & 4))
1112 write_log('console', join(";\n", $msg));
1113 else if ($GLOBALS['OUTPUT']->ajax_call)
1114 print "/*\n " . join(";\n", $msg) . " \n*/\n";
1117 print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
1118 print join(";<br/>\n", $msg);
1119 print "</pre></div>\n";
1125 * Append a line to a logfile in the logs directory.
1126 * Date will be added automatically to the line.
1128 * @param $name name of log file
1129 * @param line Line to append
1132 function write_log($name, $line)
1134 global $CONFIG, $RCMAIL;
1136 if (!is_string($line))
1137 $line = var_export($line, true);
1139 if (empty($CONFIG['log_date_format']))
1140 $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1142 $date = date($CONFIG['log_date_format']);
1144 // trigger logging hook
1145 if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1146 $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1147 $name = $log['name'];
1148 $line = $log['line'];
1149 $date = $log['date'];
1154 if ($CONFIG['log_driver'] == 'syslog') {
1155 $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1156 syslog($prio, $line);
1160 $line = sprintf("[%s]: %s\n", $date, $line);
1162 // log_driver == 'file' is assumed here
1163 if (empty($CONFIG['log_dir']))
1164 $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1166 // try to open specific log file for writing
1167 $logfile = $CONFIG['log_dir'].'/'.$name;
1168 if ($fp = @fopen($logfile, 'a')) {
1175 trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1182 * Write login data (name, ID, IP address) to the 'userlogins' log file.
1186 function rcmail_log_login()
1190 if (!$RCMAIL->config->get('log_logins') || !$RCMAIL->user)
1193 write_log('userlogins', sprintf('Successful login for %s (ID: %d) from %s',
1194 $RCMAIL->user->get_username(), $RCMAIL->user->ID, rcmail_remote_ip()));
1199 * Returns remote IP address and forwarded addresses if found
1201 * @return string Remote IP address(es)
1203 function rcmail_remote_ip()
1205 $address = $_SERVER['REMOTE_ADDR'];
1207 // append the NGINX X-Real-IP header, if set
1208 if (!empty($_SERVER['HTTP_X_REAL_IP'])) {
1209 $remote_ip[] = 'X-Real-IP: ' . $_SERVER['HTTP_X_REAL_IP'];
1211 // append the X-Forwarded-For header, if set
1212 if (!empty($_SERVER['HTTP_X_FORWARDED_FOR'])) {
1213 $remote_ip[] = 'X-Forwarded-For: ' . $_SERVER['HTTP_X_FORWARDED_FOR'];
1216 if (!empty($remote_ip))
1217 $address .= '(' . implode(',', $remote_ip) . ')';
1224 * Check whether the HTTP referer matches the current request
1226 * @return boolean True if referer is the same host+path, false if not
1228 function rcube_check_referer()
1230 $uri = parse_url($_SERVER['REQUEST_URI']);
1231 $referer = parse_url(rc_request_header('Referer'));
1232 return $referer['host'] == rc_request_header('Host') && $referer['path'] == $uri['path'];
1240 function rcube_timer()
1242 return microtime(true);
1250 function rcube_print_time($timer, $label='Timer', $dest='console')
1252 static $print_count = 0;
1255 $now = rcube_timer();
1256 $diff = $now-$timer;
1259 $label = 'Timer '.$print_count;
1261 write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1266 * Return the mailboxlist in HTML
1268 * @param array Named parameters
1269 * @return string HTML code for the gui object
1271 function rcmail_mailbox_list($attrib)
1274 static $a_mailboxes;
1276 $attrib += array('maxlength' => 100, 'realnames' => false);
1278 // add some labels to client
1279 $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1281 $type = $attrib['type'] ? $attrib['type'] : 'ul';
1282 unset($attrib['type']);
1284 if ($type=='ul' && !$attrib['id'])
1285 $attrib['id'] = 'rcmboxlist';
1288 $mbox_name = $RCMAIL->imap->get_mailbox_name();
1290 // build the folders tree
1291 if (empty($a_mailboxes)) {
1293 $a_folders = $RCMAIL->imap->list_mailboxes();
1294 $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1295 $a_mailboxes = array();
1297 foreach ($a_folders as $folder)
1298 rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1301 // allow plugins to alter the folder tree or to localize folder names
1302 $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1304 if ($type=='select') {
1305 $select = new html_select($attrib);
1307 // add no-selection option
1308 if ($attrib['noselection'])
1309 $select->add(rcube_label($attrib['noselection']), '0');
1311 rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1312 $out = $select->show();
1315 $js_mailboxlist = array();
1316 $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1318 $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1319 $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1320 $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1328 * Return the mailboxlist as html_select object
1330 * @param array Named parameters
1331 * @return html_select HTML drop-down object
1333 function rcmail_mailbox_select($p = array())
1337 $p += array('maxlength' => 100, 'realnames' => false);
1338 $a_mailboxes = array();
1340 if ($p['unsubscribed'])
1341 $list = $RCMAIL->imap->list_unsubscribed();
1343 $list = $RCMAIL->imap->list_mailboxes();
1345 foreach ($list as $folder)
1346 if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1347 rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1349 $select = new html_select($p);
1351 if ($p['noselection'])
1352 $select->add($p['noselection'], '');
1354 rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1361 * Create a hierarchical array of the mailbox list
1365 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1369 $pos = strpos($folder, $delm);
1371 if ($pos !== false) {
1372 $subFolders = substr($folder, $pos+1);
1373 $currentFolder = substr($folder, 0, $pos);
1375 // sometimes folder has a delimiter as the last character
1376 if (!strlen($subFolders))
1378 else if (!isset($arrFolders[$currentFolder]))
1381 $virtual = $arrFolders[$currentFolder]['virtual'];
1384 $subFolders = false;
1385 $currentFolder = $folder;
1389 $path .= $currentFolder;
1391 // Check \Noselect option (if options are in cache)
1392 if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
1393 $virtual = in_array('\\Noselect', $opts);
1396 if (!isset($arrFolders[$currentFolder])) {
1397 $arrFolders[$currentFolder] = array(
1399 'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1400 'virtual' => $virtual,
1401 'folders' => array());
1404 $arrFolders[$currentFolder]['virtual'] = $virtual;
1406 if (strlen($subFolders))
1407 rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1412 * Return html for a structured list <ul> for the mailbox tree
1416 function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1418 global $RCMAIL, $CONFIG;
1420 $maxlength = intval($attrib['maxlength']);
1421 $realnames = (bool)$attrib['realnames'];
1422 $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1426 foreach ($arrFolders as $key => $folder) {
1427 $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1430 if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1431 $foldername = rcube_label($folder_class);
1434 $foldername = $folder['name'];
1436 // shorten the folder name to a given length
1437 if ($maxlength && $maxlength > 1) {
1438 $fname = abbreviate_string($foldername, $maxlength);
1439 if ($fname != $foldername)
1440 $title = $foldername;
1441 $foldername = $fname;
1445 // make folder name safe for ids and class names
1446 $folder_id = asciiwords($folder['id'], true, '_');
1447 $classes = array('mailbox');
1449 // set special class for Sent, Drafts, Trash and Junk
1450 if ($folder['id']==$CONFIG['sent_mbox'])
1451 $classes[] = 'sent';
1452 else if ($folder['id']==$CONFIG['drafts_mbox'])
1453 $classes[] = 'drafts';
1454 else if ($folder['id']==$CONFIG['trash_mbox'])
1455 $classes[] = 'trash';
1456 else if ($folder['id']==$CONFIG['junk_mbox'])
1457 $classes[] = 'junk';
1458 else if ($folder['id']=='INBOX')
1459 $classes[] = 'inbox';
1461 $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1463 $classes[] = $zebra_class;
1465 if ($folder['id'] == $mbox_name)
1466 $classes[] = 'selected';
1468 $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1469 $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1471 if ($folder['virtual'])
1472 $classes[] = 'virtual';
1474 $classes[] = 'unread';
1476 $js_name = JQ($folder['id']);
1477 $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1478 $link_attrib = $folder['virtual'] ? array() : array(
1479 'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1480 'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1484 $out .= html::tag('li', array(
1485 'id' => "rcmli".$folder_id,
1486 'class' => join(' ', $classes),
1488 html::a($link_attrib, $html_name) .
1489 (!empty($folder['folders']) ? html::div(array(
1490 'class' => ($collapsed ? 'collapsed' : 'expanded'),
1491 'style' => "position:absolute",
1492 'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1493 ), ' ') : ''));
1495 $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1497 if (!empty($folder['folders'])) {
1498 $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1499 rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1511 * Return html for a flat list <select> for the mailbox tree
1515 function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1519 foreach ($arrFolders as $key=>$folder)
1521 if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1522 $foldername = rcube_label($folder_class);
1525 $foldername = $folder['name'];
1527 // shorten the folder name to a given length
1528 if ($maxlength && $maxlength>1)
1529 $foldername = abbreviate_string($foldername, $maxlength);
1532 $select->add(str_repeat(' ', $nestLevel*4) . $foldername, $folder['id']);
1534 if (!empty($folder['folders']))
1535 $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1545 * Return internal name for the given folder if it matches the configured special folders
1549 function rcmail_folder_classname($folder_id)
1553 if ($folder_id == 'INBOX')
1556 // for these mailboxes we have localized labels and css classes
1557 foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1559 if ($folder_id == $CONFIG[$smbx.'_mbox'])
1566 * Try to localize the given IMAP folder name.
1567 * UTF-7 decode it in case no localized text was found
1569 * @param string Folder name
1570 * @return string Localized folder name in UTF-8 encoding
1572 function rcmail_localize_foldername($name)
1574 if ($folder_class = rcmail_folder_classname($name))
1575 return rcube_label($folder_class);
1577 return rcube_charset_convert($name, 'UTF7-IMAP');
1581 function rcmail_quota_display($attrib)
1586 $attrib['id'] = 'rcmquotadisplay';
1588 if(isset($attrib['display']))
1589 $_SESSION['quota_display'] = $attrib['display'];
1591 $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1593 $quota = rcmail_quota_content($attrib);
1595 $OUTPUT->add_script('$(document).ready(function(){
1596 rcmail.set_quota('.json_serialize($quota).')});', 'foot');
1598 return html::span($attrib, '');
1602 function rcmail_quota_content($attrib=NULL)
1606 $quota = $RCMAIL->imap->get_quota();
1607 $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1609 $quota_result = (array) $quota;
1610 $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1612 if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1613 $quota_result['title'] = rcube_label('unlimited');
1614 $quota_result['percent'] = 0;
1616 else if ($quota['total']) {
1617 if (!isset($quota['percent']))
1618 $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1620 $title = sprintf('%s / %s (%.0f%%)',
1621 show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1622 $quota_result['percent']);
1624 $quota_result['title'] = $title;
1626 if ($attrib['width'])
1627 $quota_result['width'] = $attrib['width'];
1628 if ($attrib['height'])
1629 $quota_result['height'] = $attrib['height'];
1632 $quota_result['title'] = rcube_label('unknown');
1633 $quota_result['percent'] = 0;
1636 return $quota_result;
1641 * Outputs error message according to server error/response codes
1643 * @param string Fallback message label
1644 * @param string Fallback message label arguments
1648 function rcmail_display_server_error($fallback=null, $fallback_args=null)
1652 $err_code = $RCMAIL->imap->get_error_code();
1653 $res_code = $RCMAIL->imap->get_response_code();
1655 if ($res_code == rcube_imap::NOPERM) {
1656 $RCMAIL->output->show_message('errornoperm', 'error');
1658 else if ($res_code == rcube_imap::READONLY) {
1659 $RCMAIL->output->show_message('errorreadonly', 'error');
1661 else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1662 $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1664 else if ($fallback) {
1665 $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1673 * Output HTML editor scripts
1675 * @param string Editor mode
1678 function rcube_html_editor($mode='')
1680 global $RCMAIL, $CONFIG;
1682 $hook = $RCMAIL->plugins->exec_hook('hmtl_editor', array('mode' => $mode));
1687 $lang = strtolower($_SESSION['language']);
1689 // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1690 $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1692 if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1695 $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1696 $RCMAIL->output->include_script('editor.js');
1697 $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
1698 JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
1704 * Replaces TinyMCE's emoticon images with plain-text representation
1706 * @param string HTML content
1707 * @return string HTML content
1709 function rcmail_replace_emoticons($html)
1712 '8-)' => 'smiley-cool',
1713 ':-#' => 'smiley-foot-in-mouth',
1714 ':-*' => 'smiley-kiss',
1715 ':-X' => 'smiley-sealed',
1716 ':-P' => 'smiley-tongue-out',
1717 ':-@' => 'smiley-yell',
1718 ":'(" => 'smiley-cry',
1719 ':-(' => 'smiley-frown',
1720 ':-D' => 'smiley-laughing',
1721 ':-)' => 'smiley-smile',
1722 ':-S' => 'smiley-undecided',
1723 ':-$' => 'smiley-embarassed',
1724 'O:-)' => 'smiley-innocent',
1725 ':-|' => 'smiley-money-mouth',
1726 ':-O' => 'smiley-surprised',
1727 ';-)' => 'smiley-wink',
1730 foreach ($emoticons as $idx => $file) {
1731 // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1732 $search[] = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1736 return preg_replace($search, $replace, $html);
1741 * Check if working in SSL mode
1743 * @param integer HTTPS port number
1744 * @param boolean Enables 'use_https' option checking
1747 function rcube_https_check($port=null, $use_https=true)
1751 if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1753 if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1755 if ($port && $_SERVER['SERVER_PORT'] == $port)
1757 if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1765 * For backward compatibility.
1767 * @global rcmail $RCMAIL
1768 * @param string $var_name Variable name.
1771 function rcube_sess_unset($var_name=null)
1775 $RCMAIL->session->remove($var_name);
1781 * Replaces hostname variables
1783 * @param string $name Hostname
1784 * @param string $host Optional IMAP hostname
1787 function rcube_parse_host($name, $host='')
1790 $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1791 // %d - domain name without first part, e.g. %d=mail.domain.tld, %m=domain.tld
1792 $d = preg_replace('/^[^\.]+\./', '', $n);
1794 $h = $_SESSION['imap_host'] ? $_SESSION['imap_host'] : $host;
1795 // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1796 $z = preg_replace('/^[^\.]+\./', '', $h);
1798 $name = str_replace(array('%n', '%d', '%h', '%z'), array($n, $d, $h, $z), $name);
1804 * E-mail address validation
1806 * @param string $email Email address
1807 * @param boolean $dns_check True to check dns
1810 function check_email($email, $dns_check=true)
1812 // Check for invalid characters
1813 if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1816 // Check for length limit specified by RFC 5321 (#1486453)
1817 if (strlen($email) > 254)
1820 $email_array = explode('@', $email);
1822 // Check that there's one @ symbol
1823 if (count($email_array) < 2)
1826 $domain_part = array_pop($email_array);
1827 $local_part = implode('@', $email_array);
1829 // from PEAR::Validate
1831 ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| #1 quoted name
1832 ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*)) #2 OR dot-atom (RFC5322)
1835 if (!preg_match($regexp, $local_part))
1838 // Check domain part
1839 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))
1840 return true; // IP address
1842 // If not an IP address
1843 $domain_array = explode('.', $domain_part);
1844 if (sizeof($domain_array) < 2)
1845 return false; // Not enough parts to be a valid domain
1847 foreach ($domain_array as $part)
1848 if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1851 if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1854 if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1856 @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1857 foreach ($lookup as $line) {
1858 if (strpos($line, 'MX preference'))
1864 // find MX record(s)
1865 if (getmxrr($domain_part, $mx_records))
1868 // find any DNS record
1869 if (checkdnsrr($domain_part, 'ANY'))
1877 * Idn_to_ascii wrapper.
1878 * Intl/Idn modules version of this function doesn't work with e-mail address
1880 function rcube_idn_to_ascii($str)
1882 return rcube_idn_convert($str, true);
1886 * Idn_to_ascii wrapper.
1887 * Intl/Idn modules version of this function doesn't work with e-mail address
1889 function rcube_idn_to_utf8($str)
1891 return rcube_idn_convert($str, false);
1894 function rcube_idn_convert($input, $is_utf=false)
1896 if ($at = strpos($input, '@')) {
1897 $user = substr($input, 0, $at);
1898 $domain = substr($input, $at+1);
1904 $domain = $is_utf ? idn_to_ascii($domain) : idn_to_utf8($domain);
1906 return $at ? $user . '@' . $domain : $domain;
1911 * Helper class to turn relative urls into absolute ones
1912 * using a predefined base
1914 class rcube_base_replacer
1918 public function __construct($base)
1920 $this->base_url = $base;
1923 public function callback($matches)
1925 return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1931 * Throw system error and show error page
1933 * @param array Named parameters
1934 * - code: Error code (required)
1935 * - type: Error type [php|db|imap|javascript] (required)
1936 * - message: Error message
1937 * - file: File where error occured
1938 * - line: Line where error occured
1939 * @param boolean True to log the error
1940 * @param boolean Terminate script execution
1942 // may be defined in Installer
1943 if (!function_exists('raise_error')) {
1944 function raise_error($arg=array(), $log=false, $terminate=false)
1946 global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
1948 // report bug (if not incompatible browser)
1949 if ($log && $arg['type'] && $arg['message'])
1952 // display error page and terminate script
1954 $ERROR_CODE = $arg['code'];
1955 $ERROR_MESSAGE = $arg['message'];
1956 include('program/steps/utils/error.inc');
1964 * Report error according to configured debug_level
1966 * @param array Named parameters
1968 * @see raise_error()
1970 function log_bug($arg_arr)
1973 $program = strtoupper($arg_arr['type']);
1975 // write error to local log file
1976 if ($CONFIG['debug_level'] & 1) {
1977 $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
1978 $log_entry = sprintf("%s Error: %s%s (%s %s)",
1980 $arg_arr['message'],
1981 $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
1982 $_SERVER['REQUEST_METHOD'],
1983 $_SERVER['REQUEST_URI'] . $post_query);
1985 if (!write_log('errors', $log_entry)) {
1986 // send error to PHPs error handler if write_log didn't succeed
1987 trigger_error($arg_arr['message']);
1991 // resport the bug to the global bug reporting system
1992 if ($CONFIG['debug_level'] & 2) {
1993 // TODO: Send error via HTTP
1996 // show error if debug_mode is on
1997 if ($CONFIG['debug_level'] & 4) {
1998 print "<b>$program Error";
2000 if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
2001 print " in $arg_arr[file] ($arg_arr[line])";
2003 print ':</b> ';
2004 print nl2br($arg_arr['message']);