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 4334 2010-12-10 11:08:22Z 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/', $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) . ')';
1227 function rcube_timer()
1229 return microtime(true);
1237 function rcube_print_time($timer, $label='Timer', $dest='console')
1239 static $print_count = 0;
1242 $now = rcube_timer();
1243 $diff = $now-$timer;
1246 $label = 'Timer '.$print_count;
1248 write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1253 * Return the mailboxlist in HTML
1255 * @param array Named parameters
1256 * @return string HTML code for the gui object
1258 function rcmail_mailbox_list($attrib)
1261 static $a_mailboxes;
1263 $attrib += array('maxlength' => 100, 'realnames' => false);
1265 // add some labels to client
1266 $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1268 $type = $attrib['type'] ? $attrib['type'] : 'ul';
1269 unset($attrib['type']);
1271 if ($type=='ul' && !$attrib['id'])
1272 $attrib['id'] = 'rcmboxlist';
1275 $mbox_name = $RCMAIL->imap->get_mailbox_name();
1277 // build the folders tree
1278 if (empty($a_mailboxes)) {
1280 $a_folders = $RCMAIL->imap->list_mailboxes();
1281 $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1282 $a_mailboxes = array();
1284 foreach ($a_folders as $folder)
1285 rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1288 // allow plugins to alter the folder tree or to localize folder names
1289 $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1291 if ($type=='select') {
1292 $select = new html_select($attrib);
1294 // add no-selection option
1295 if ($attrib['noselection'])
1296 $select->add(rcube_label($attrib['noselection']), '0');
1298 rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1299 $out = $select->show();
1302 $js_mailboxlist = array();
1303 $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1305 $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1306 $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1307 $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1315 * Return the mailboxlist as html_select object
1317 * @param array Named parameters
1318 * @return html_select HTML drop-down object
1320 function rcmail_mailbox_select($p = array())
1324 $p += array('maxlength' => 100, 'realnames' => false);
1325 $a_mailboxes = array();
1327 if ($p['unsubscribed'])
1328 $list = $RCMAIL->imap->list_unsubscribed();
1330 $list = $RCMAIL->imap->list_mailboxes();
1332 foreach ($list as $folder)
1333 if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1334 rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1336 $select = new html_select($p);
1338 if ($p['noselection'])
1339 $select->add($p['noselection'], '');
1341 rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1348 * Create a hierarchical array of the mailbox list
1352 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1356 $pos = strpos($folder, $delm);
1358 if ($pos !== false) {
1359 $subFolders = substr($folder, $pos+1);
1360 $currentFolder = substr($folder, 0, $pos);
1362 // sometimes folder has a delimiter as the last character
1363 if (!strlen($subFolders))
1365 else if (!isset($arrFolders[$currentFolder]))
1368 $virtual = $arrFolders[$currentFolder]['virtual'];
1371 $subFolders = false;
1372 $currentFolder = $folder;
1376 $path .= $currentFolder;
1378 // Check \Noselect option (if options are in cache)
1379 if (!$virtual && ($opts = $RCMAIL->imap->mailbox_options($path))) {
1380 $virtual = in_array('\\Noselect', $opts);
1383 if (!isset($arrFolders[$currentFolder])) {
1384 $arrFolders[$currentFolder] = array(
1386 'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1387 'virtual' => $virtual,
1388 'folders' => array());
1391 $arrFolders[$currentFolder]['virtual'] = $virtual;
1393 if (strlen($subFolders))
1394 rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1399 * Return html for a structured list <ul> for the mailbox tree
1403 function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1405 global $RCMAIL, $CONFIG;
1407 $maxlength = intval($attrib['maxlength']);
1408 $realnames = (bool)$attrib['realnames'];
1409 $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1413 foreach ($arrFolders as $key => $folder) {
1414 $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1417 if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1418 $foldername = rcube_label($folder_class);
1421 $foldername = $folder['name'];
1423 // shorten the folder name to a given length
1424 if ($maxlength && $maxlength > 1) {
1425 $fname = abbreviate_string($foldername, $maxlength);
1426 if ($fname != $foldername)
1427 $title = $foldername;
1428 $foldername = $fname;
1432 // make folder name safe for ids and class names
1433 $folder_id = asciiwords($folder['id'], true, '_');
1434 $classes = array('mailbox');
1436 // set special class for Sent, Drafts, Trash and Junk
1437 if ($folder['id']==$CONFIG['sent_mbox'])
1438 $classes[] = 'sent';
1439 else if ($folder['id']==$CONFIG['drafts_mbox'])
1440 $classes[] = 'drafts';
1441 else if ($folder['id']==$CONFIG['trash_mbox'])
1442 $classes[] = 'trash';
1443 else if ($folder['id']==$CONFIG['junk_mbox'])
1444 $classes[] = 'junk';
1445 else if ($folder['id']=='INBOX')
1446 $classes[] = 'inbox';
1448 $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1450 $classes[] = $zebra_class;
1452 if ($folder['id'] == $mbox_name)
1453 $classes[] = 'selected';
1455 $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1456 $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1458 if ($folder['virtual'])
1459 $classes[] = 'virtual';
1461 $classes[] = 'unread';
1463 $js_name = JQ($folder['id']);
1464 $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1465 $link_attrib = $folder['virtual'] ? array() : array(
1466 'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1467 'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1471 $out .= html::tag('li', array(
1472 'id' => "rcmli".$folder_id,
1473 'class' => join(' ', $classes),
1475 html::a($link_attrib, $html_name) .
1476 (!empty($folder['folders']) ? html::div(array(
1477 'class' => ($collapsed ? 'collapsed' : 'expanded'),
1478 'style' => "position:absolute",
1479 'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1480 ), ' ') : ''));
1482 $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1484 if (!empty($folder['folders'])) {
1485 $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1486 rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1498 * Return html for a flat list <select> for the mailbox tree
1502 function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1506 foreach ($arrFolders as $key=>$folder)
1508 if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1509 $foldername = rcube_label($folder_class);
1512 $foldername = $folder['name'];
1514 // shorten the folder name to a given length
1515 if ($maxlength && $maxlength>1)
1516 $foldername = abbreviate_string($foldername, $maxlength);
1519 $select->add(str_repeat(' ', $nestLevel*4) . $foldername, $folder['id']);
1521 if (!empty($folder['folders']))
1522 $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1532 * Return internal name for the given folder if it matches the configured special folders
1536 function rcmail_folder_classname($folder_id)
1540 if ($folder_id == 'INBOX')
1543 // for these mailboxes we have localized labels and css classes
1544 foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1546 if ($folder_id == $CONFIG[$smbx.'_mbox'])
1553 * Try to localize the given IMAP folder name.
1554 * UTF-7 decode it in case no localized text was found
1556 * @param string Folder name
1557 * @return string Localized folder name in UTF-8 encoding
1559 function rcmail_localize_foldername($name)
1561 if ($folder_class = rcmail_folder_classname($name))
1562 return rcube_label($folder_class);
1564 return rcube_charset_convert($name, 'UTF7-IMAP');
1568 function rcmail_quota_display($attrib)
1573 $attrib['id'] = 'rcmquotadisplay';
1575 if(isset($attrib['display']))
1576 $_SESSION['quota_display'] = $attrib['display'];
1578 $OUTPUT->add_gui_object('quotadisplay', $attrib['id']);
1580 $quota = rcmail_quota_content($attrib);
1582 $OUTPUT->add_script('$(document).ready(function(){
1583 rcmail.set_quota('.json_serialize($quota).')});', 'foot');
1585 return html::span($attrib, '');
1589 function rcmail_quota_content($attrib=NULL)
1593 $quota = $RCMAIL->imap->get_quota();
1594 $quota = $RCMAIL->plugins->exec_hook('quota', $quota);
1596 $quota_result = (array) $quota;
1597 $quota_result['type'] = isset($_SESSION['quota_display']) ? $_SESSION['quota_display'] : '';
1599 if (!$quota['total'] && $RCMAIL->config->get('quota_zero_as_unlimited')) {
1600 $quota_result['title'] = rcube_label('unlimited');
1601 $quota_result['percent'] = 0;
1603 else if ($quota['total']) {
1604 if (!isset($quota['percent']))
1605 $quota_result['percent'] = min(100, round(($quota['used']/max(1,$quota['total']))*100));
1607 $title = sprintf('%s / %s (%.0f%%)',
1608 show_bytes($quota['used'] * 1024), show_bytes($quota['total'] * 1024),
1609 $quota_result['percent']);
1611 $quota_result['title'] = $title;
1613 if ($attrib['width'])
1614 $quota_result['width'] = $attrib['width'];
1615 if ($attrib['height'])
1616 $quota_result['height'] = $attrib['height'];
1619 $quota_result['title'] = rcube_label('unknown');
1620 $quota_result['percent'] = 0;
1623 return $quota_result;
1628 * Outputs error message according to server error/response codes
1630 * @param string Fallback message label
1631 * @param string Fallback message label arguments
1635 function rcmail_display_server_error($fallback=null, $fallback_args=null)
1639 $err_code = $RCMAIL->imap->get_error_code();
1640 $res_code = $RCMAIL->imap->get_response_code();
1642 if ($res_code == rcube_imap::NOPERM) {
1643 $RCMAIL->output->show_message('errornoperm', 'error');
1645 else if ($res_code == rcube_imap::READONLY) {
1646 $RCMAIL->output->show_message('errorreadonly', 'error');
1648 else if ($err_code && ($err_str = $RCMAIL->imap->get_error_str())) {
1649 $RCMAIL->output->show_message('servererrormsg', 'error', array('msg' => $err_str));
1651 else if ($fallback) {
1652 $RCMAIL->output->show_message($fallback, 'error', $fallback_args);
1660 * Output HTML editor scripts
1662 * @param string Editor mode
1665 function rcube_html_editor($mode='')
1667 global $RCMAIL, $CONFIG;
1669 $hook = $RCMAIL->plugins->exec_hook('hmtl_editor', array('mode' => $mode));
1674 $lang = strtolower($_SESSION['language']);
1676 // TinyMCE uses 'tw' for zh_TW (which is wrong, because tw is a code of Twi language)
1677 $lang = ($lang == 'zh_tw') ? 'tw' : substr($lang, 0, 2);
1679 if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1682 $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1683 $RCMAIL->output->include_script('editor.js');
1684 $RCMAIL->output->add_script(sprintf("rcmail_editor_init('\$__skin_path', '%s', %d, '%s');",
1685 JQ($lang), intval($CONFIG['enable_spellcheck']), $mode),
1691 * Replaces TinyMCE's emoticon images with plain-text representation
1693 * @param string HTML content
1694 * @return string HTML content
1696 function rcmail_replace_emoticons($html)
1699 '8-)' => 'smiley-cool',
1700 ':-#' => 'smiley-foot-in-mouth',
1701 ':-*' => 'smiley-kiss',
1702 ':-X' => 'smiley-sealed',
1703 ':-P' => 'smiley-tongue-out',
1704 ':-@' => 'smiley-yell',
1705 ":'(" => 'smiley-cry',
1706 ':-(' => 'smiley-frown',
1707 ':-D' => 'smiley-laughing',
1708 ':-)' => 'smiley-smile',
1709 ':-S' => 'smiley-undecided',
1710 ':-$' => 'smiley-embarassed',
1711 'O:-)' => 'smiley-innocent',
1712 ':-|' => 'smiley-money-mouth',
1713 ':-O' => 'smiley-surprised',
1714 ';-)' => 'smiley-wink',
1717 foreach ($emoticons as $idx => $file) {
1718 // <img title="Cry" src="http://.../program/js/tiny_mce/plugins/emotions/img/smiley-cry.gif" border="0" alt="Cry" />
1719 $search[] = '/<img title="[a-z ]+" src="https?:\/\/[a-z0-9_.\/-]+\/tiny_mce\/plugins\/emotions\/img\/'.$file.'.gif"[^>]+\/>/i';
1723 return preg_replace($search, $replace, $html);
1728 * Check if working in SSL mode
1730 * @param integer HTTPS port number
1731 * @param boolean Enables 'use_https' option checking
1734 function rcube_https_check($port=null, $use_https=true)
1738 if (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) != 'off')
1740 if (!empty($_SERVER['HTTP_X_FORWARDED_PROTO']) && strtolower($_SERVER['HTTP_X_FORWARDED_PROTO']) == 'https')
1742 if ($port && $_SERVER['SERVER_PORT'] == $port)
1744 if ($use_https && isset($RCMAIL) && $RCMAIL->config->get('use_https'))
1752 * For backward compatibility.
1754 * @global rcmail $RCMAIL
1755 * @param string $var_name Variable name.
1758 function rcube_sess_unset($var_name=null)
1762 $RCMAIL->session->remove($var_name);
1768 * Replaces hostname variables
1770 * @param string $name Hostname
1773 function rcube_parse_host($name)
1776 $n = preg_replace('/:\d+$/', '', $_SERVER['SERVER_NAME']);
1777 // %d - domain name without first part, e.g. %d=mail.domain.tld, %m=domain.tld
1778 $d = preg_replace('/^[^\.]+\./', '', $n);
1780 $h = $_SESSION['imap_host'];
1781 // %z - IMAP domain without first part, e.g. %h=imap.domain.tld, %z=domain.tld
1782 $z = preg_replace('/^[^\.]+\./', '', $h);
1784 $name = str_replace(array('%n', '%d', '%h', '%z'), array($n, $d, $h, $z), $name);
1790 * E-mail address validation
1792 * @param string $email Email address
1793 * @param boolean $dns_check True to check dns
1796 function check_email($email, $dns_check=true)
1798 // Check for invalid characters
1799 if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1802 // Check for length limit specified by RFC 5321 (#1486453)
1803 if (strlen($email) > 254)
1806 $email_array = explode('@', $email);
1808 // Check that there's one @ symbol
1809 if (count($email_array) < 2)
1812 $domain_part = array_pop($email_array);
1813 $local_part = implode('@', $email_array);
1815 // from PEAR::Validate
1817 ("\s*(?:[^"\f\n\r\t\v\b\s]+\s*)+")| #1 quoted name
1818 ([-\w!\#\$%\&\'*+~/^`|{}=]+(?:\.[-\w!\#\$%\&\'*+~/^`|{}=]+)*)) #2 OR dot-atom (RFC5322)
1821 if (!preg_match($regexp, $local_part))
1824 // Check domain part
1825 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))
1826 return true; // IP address
1828 // If not an IP address
1829 $domain_array = explode('.', $domain_part);
1830 if (sizeof($domain_array) < 2)
1831 return false; // Not enough parts to be a valid domain
1833 foreach ($domain_array as $part)
1834 if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $part))
1837 if (!$dns_check || !rcmail::get_instance()->config->get('email_dns_check'))
1840 if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<')) {
1842 @exec("nslookup -type=MX " . escapeshellarg($domain_part) . " 2>&1", $lookup);
1843 foreach ($lookup as $line) {
1844 if (strpos($line, 'MX preference'))
1850 // find MX record(s)
1851 if (getmxrr($domain_part, $mx_records))
1854 // find any DNS record
1855 if (checkdnsrr($domain_part, 'ANY'))
1864 * Helper class to turn relative urls into absolute ones
1865 * using a predefined base
1867 class rcube_base_replacer
1871 public function __construct($base)
1873 $this->base_url = $base;
1876 public function callback($matches)
1878 return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1884 * Throw system error and show error page
1886 * @param array Named parameters
1887 * - code: Error code (required)
1888 * - type: Error type [php|db|imap|javascript] (required)
1889 * - message: Error message
1890 * - file: File where error occured
1891 * - line: Line where error occured
1892 * @param boolean True to log the error
1893 * @param boolean Terminate script execution
1895 // may be defined in Installer
1896 if (!function_exists('raise_error')) {
1897 function raise_error($arg=array(), $log=false, $terminate=false)
1899 global $__page_content, $CONFIG, $OUTPUT, $ERROR_CODE, $ERROR_MESSAGE;
1901 // report bug (if not incompatible browser)
1902 if ($log && $arg['type'] && $arg['message'])
1905 // display error page and terminate script
1907 $ERROR_CODE = $arg['code'];
1908 $ERROR_MESSAGE = $arg['message'];
1909 include('program/steps/utils/error.inc');
1917 * Report error according to configured debug_level
1919 * @param array Named parameters
1921 * @see raise_error()
1923 function log_bug($arg_arr)
1926 $program = strtoupper($arg_arr['type']);
1928 // write error to local log file
1929 if ($CONFIG['debug_level'] & 1) {
1930 $post_query = ($_SERVER['REQUEST_METHOD'] == 'POST' ? '?_task='.urlencode($_POST['_task']).'&_action='.urlencode($_POST['_action']) : '');
1931 $log_entry = sprintf("%s Error: %s%s (%s %s)",
1933 $arg_arr['message'],
1934 $arg_arr['file'] ? sprintf(' in %s on line %d', $arg_arr['file'], $arg_arr['line']) : '',
1935 $_SERVER['REQUEST_METHOD'],
1936 $_SERVER['REQUEST_URI'] . $post_query);
1938 if (!write_log('errors', $log_entry)) {
1939 // send error to PHPs error handler if write_log didn't succeed
1940 trigger_error($arg_arr['message']);
1944 // resport the bug to the global bug reporting system
1945 if ($CONFIG['debug_level'] & 2) {
1946 // TODO: Send error via HTTP
1949 // show error if debug_mode is on
1950 if ($CONFIG['debug_level'] & 4) {
1951 print "<b>$program Error";
1953 if (!empty($arg_arr['file']) && !empty($arg_arr['line']))
1954 print " in $arg_arr[file] ($arg_arr[line])";
1956 print ':</b> ';
1957 print nl2br($arg_arr['message']);