]> git.donarmstrong.com Git - roundcube.git/blob - program/include/main.inc
Imported Upstream version 0.3.1
[roundcube.git] / program / include / main.inc
1 <?php
2
3 /*
4  +-----------------------------------------------------------------------+
5  | program/include/main.inc                                              |
6  |                                                                       |
7  | This file is part of the RoundCube Webmail client                     |
8  | Copyright (C) 2005-2009, RoundCube Dev, - Switzerland                 |
9  | Licensed under the GNU GPL                                            |
10  |                                                                       |
11  | PURPOSE:                                                              |
12  |   Provide basic functions for the webmail package                     |
13  |                                                                       |
14  +-----------------------------------------------------------------------+
15  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16  +-----------------------------------------------------------------------+
17
18  $Id: main.inc 3063 2009-10-27 09:43:39Z alec $
19
20 */
21
22 /**
23  * RoundCube Webmail common functions
24  *
25  * @package Core
26  * @author Thomas Bruederli <roundcube@gmail.com>
27  */
28
29 require_once('lib/utf7.inc');
30 require_once('include/rcube_shared.inc');
31
32 // define constannts for input reading
33 define('RCUBE_INPUT_GET', 0x0101);
34 define('RCUBE_INPUT_POST', 0x0102);
35 define('RCUBE_INPUT_GPC', 0x0103);
36
37
38
39 /**
40  * Return correct name for a specific database table
41  *
42  * @param string Table name
43  * @return string Translated table name
44  */
45 function get_table_name($table)
46   {
47   global $CONFIG;
48
49   // return table name if configured
50   $config_key = 'db_table_'.$table;
51
52   if (strlen($CONFIG[$config_key]))
53     return $CONFIG[$config_key];
54
55   return $table;
56   }
57
58
59 /**
60  * Return correct name for a specific database sequence
61  * (used for Postgres only)
62  *
63  * @param string Secuence name
64  * @return string Translated sequence name
65  */
66 function get_sequence_name($sequence)
67   {
68   // return table name if configured
69   $config_key = 'db_sequence_'.$sequence;
70   $opt = rcmail::get_instance()->config->get($config_key);
71
72   if (!empty($opt))
73     return $opt;
74     
75   return $sequence;
76   }
77
78
79 /**
80  * Get localized text in the desired language
81  * It's a global wrapper for rcmail::gettext()
82  *
83  * @param mixed Named parameters array or label name
84  * @return string Localized text
85  * @see rcmail::gettext()
86  */
87 function rcube_label($p, $domain=null)
88 {
89   return rcmail::get_instance()->gettext($p, $domain);
90 }
91
92
93 /**
94  * Overwrite action variable
95  *
96  * @param string New action value
97  */
98 function rcmail_overwrite_action($action)
99   {
100   $app = rcmail::get_instance();
101   $app->action = $action;
102   $app->output->set_env('action', $action);
103   }
104
105
106 /**
107  * Compose an URL for a specific action
108  *
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
113  */
114 function rcmail_url($action, $p=array(), $task=null)
115 {
116   $app = rcmail::get_instance();
117   return $app->url((array)$p + array('_action' => $action, 'task' => $task));
118 }
119
120
121 /**
122  * Garbage collector function for temp files.
123  * Remove temp files older than two days
124  */
125 function rcmail_temp_gc()
126   {
127   $rcmail = rcmail::get_instance();
128
129   $tmp = unslashify($rcmail->config->get('temp_dir'));
130   $expire = mktime() - 172800;  // expire in 48 hours
131
132   if ($dir = opendir($tmp))
133     {
134     while (($fname = readdir($dir)) !== false)
135       {
136       if ($fname{0} == '.')
137         continue;
138
139       if (filemtime($tmp.'/'.$fname) < $expire)
140         @unlink($tmp.'/'.$fname);
141       }
142
143     closedir($dir);
144     }
145   }
146
147
148 /**
149  * Garbage collector for cache entries.
150  * Remove all expired message cache records
151  */
152 function rcmail_cache_gc()
153   {
154   $rcmail = rcmail::get_instance();
155   $db = $rcmail->get_dbh();
156   
157   // get target timestamp
158   $ts = get_offset_time($rcmail->config->get('message_cache_lifetime', '30d'), -1);
159   
160   $db->query("DELETE FROM ".get_table_name('messages')."
161              WHERE  created < " . $db->fromunixtime($ts));
162
163   $db->query("DELETE FROM ".get_table_name('cache')."
164               WHERE  created < " . $db->fromunixtime($ts));
165   }
166
167
168 /**
169  * Convert a string from one charset to another.
170  * Uses mbstring and iconv functions if possible
171  *
172  * @param  string Input string
173  * @param  string Suspected charset of the input string
174  * @param  string Target charset to convert to; defaults to RCMAIL_CHARSET
175  * @return Converted string
176  */
177 function rcube_charset_convert($str, $from, $to=NULL)
178   {
179   static $mbstring_loaded = null;
180   static $mbstring_list = null;
181   static $convert_warning = false;
182   static $conv = null;
183   
184   $error = false;
185
186   $to = empty($to) ? $to = strtoupper(RCMAIL_CHARSET) : rcube_parse_charset($to);
187   $from = rcube_parse_charset($from);
188
189   if ($from == $to || empty($str) || empty($from))
190     return $str;
191
192   // convert charset using iconv module  
193   if (function_exists('iconv') && $from != 'UTF-7' && $to != 'UTF-7') {
194     $_iconv = iconv($from, $to . '//IGNORE', $str);
195     if ($_iconv !== false) {
196         return $_iconv;
197     }
198   }
199
200   if (is_null($mbstring_loaded))
201     $mbstring_loaded = extension_loaded('mbstring');
202     
203   // convert charset using mbstring module
204   if ($mbstring_loaded) {
205     $aliases['WINDOWS-1257'] = 'ISO-8859-13';
206     
207     if (is_null($mbstring_list)) {
208       $mbstring_list = mb_list_encodings();
209       $mbstring_list = array_map('strtoupper', $mbstring_list);
210     }
211
212     $mb_from = $aliases[$from] ? $aliases[$from] : $from;
213     $mb_to = $aliases[$to] ? $aliases[$to] : $to;
214     
215     // return if encoding found, string matches encoding and convert succeeded
216     if (in_array($mb_from, $mbstring_list) && in_array($mb_to, $mbstring_list)) {
217       if (mb_check_encoding($str, $mb_from) && ($out = mb_convert_encoding($str, $mb_to, $mb_from)))
218         return $out;
219     }
220   }
221
222   // convert charset using bundled classes/functions
223   if ($to == 'UTF-8') {
224     if ($from == 'UTF7-IMAP') {
225       if ($_str = utf7_to_utf8($str))
226         return $_str;
227     }
228     else if ($from == 'UTF-7') {
229       if ($_str = rcube_utf7_to_utf8($str))
230         return $_str;
231     }
232     else if (($from == 'ISO-8859-1') && function_exists('utf8_encode')) {
233       return utf8_encode($str);
234     }
235     else if (class_exists('utf8')) {
236       if (!$conv)
237         $conv = new utf8($from);
238       else
239         $conv->loadCharset($from);
240
241       if($_str = $conv->strToUtf8($str))
242         return $_str;
243     }
244     $error = true;
245   }
246   
247   // encode string for output
248   if ($from == 'UTF-8') {
249     // @TODO: we need a function for UTF-7 (RFC2152) conversion
250     if ($to == 'UTF7-IMAP' || $to == 'UTF-7') {
251       if ($_str = utf8_to_utf7($str))
252         return $_str;
253     }
254     else if ($to == 'ISO-8859-1' && function_exists('utf8_decode')) {
255       return utf8_decode($str);
256     }
257     else if (class_exists('utf8')) {
258       if (!$conv)
259         $conv = new utf8($to);
260       else
261         $conv->loadCharset($from);
262
263       if ($_str = $conv->strToUtf8($str))
264         return $_str;
265     }
266     $error = true;
267   }
268   
269   // report error
270   if ($error && !$convert_warning) {
271     raise_error(array(
272       'code' => 500,
273       'type' => 'php',
274       'file' => __FILE__,
275       'line' => __LINE__,
276       'message' => "Could not convert string from $from to $to. Make sure iconv/mbstring is installed or lib/utf8.class is available."
277       ), true, false);
278     
279     $convert_warning = true;
280   }
281   
282   // return UTF-8 or original string
283   return $str;
284   }
285
286
287 /**
288  * Parse and validate charset name string (see #1485758).
289  * Sometimes charset string is malformed, there are also charset aliases 
290  * but we need strict names for charset conversion (specially utf8 class)
291  *
292  * @param  string  Input charset name
293  * @return The validated charset name
294  */
295 function rcube_parse_charset($charset)
296   {
297   $charset = strtoupper($charset);
298
299   # RFC1642
300   $charset = str_replace('UNICODE-1-1-', '', $charset);
301
302   # Aliases: some of them from HTML5 spec.
303   $aliases = array(
304     'USASCII'       => 'WINDOWS-1252',
305     'ANSIX31101983' => 'WINDOWS-1252',
306     'ANSIX341968'   => 'WINDOWS-1252',
307     'UNKNOWN8BIT'   => 'ISO-8859-15',
308     'UNKNOWN'       => 'ISO-8859-15',
309     'USERDEFINED'   => 'ISO-8859-15',
310     'KSC56011987'   => 'EUC-KR',
311     'GB2312'        => 'GBK',
312     'GB231280'      => 'GBK',
313     'UNICODE'       => 'UTF-8',
314     'UTF7IMAP'      => 'UTF7-IMAP',
315     'TIS620'        => 'WINDOWS-874',
316     'ISO88599'      => 'WINDOWS-1254',
317     'ISO885911'     => 'WINDOWS-874',
318   );
319
320   // allow a-z and 0-9 only and remove X- prefix (e.g. X-ROMAN8 => ROMAN8)
321   $str = preg_replace(array('/[^a-z0-9]/i', '/^x+/i'), '', $charset);
322
323   if (isset($aliases[$str]))
324     return $aliases[$str];
325
326   if (preg_match('/UTF(7|8|16|32)(BE|LE)*/', $str, $m))
327     return 'UTF-' . $m[1] . $m[2];
328
329   if (preg_match('/ISO8859([0-9]{0,2})/', $str, $m)) {
330     $iso = 'ISO-8859-' . ($m[1] ? $m[1] : 1);
331     # some clients sends windows-1252 text as latin1,
332     # it is safe to use windows-1252 for all latin1
333     return $iso == 'ISO-8859-1' ? 'WINDOWS-1252' : $iso;
334     }
335
336   return $charset;
337   }
338
339
340 /**
341  * Converts string from standard UTF-7 (RFC 2152) to UTF-8.
342  *
343  * @param  string  Input string
344  * @return The converted string
345  */
346 function rcube_utf7_to_utf8($str)
347 {
348   $Index_64 = array(
349     0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
350     0,0,0,0, 0,0,0,0, 0,0,0,0, 0,0,0,0,
351     0,0,0,0, 0,0,0,0, 0,0,0,1, 0,0,0,0,
352     1,1,1,1, 1,1,1,1, 1,1,0,0, 0,0,0,0,
353     0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
354     1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
355     0,1,1,1, 1,1,1,1, 1,1,1,1, 1,1,1,1,
356     1,1,1,1, 1,1,1,1, 1,1,1,0, 0,0,0,0,
357   );
358
359   $u7len = strlen($str);
360   $str = strval($str);
361   $res = '';
362
363   for ($i=0; $u7len > 0; $i++, $u7len--)
364   {
365     $u7 = $str[$i];
366     if ($u7 == '+')
367     {
368       $i++;
369       $u7len--;
370       $ch = '';
371
372       for (; $u7len > 0; $i++, $u7len--)
373       {
374         $u7 = $str[$i];
375
376         if (!$Index_64[ord($u7)])
377           break;
378
379         $ch .= $u7;
380       }
381
382       if ($ch == '') {
383         if ($u7 == '-')
384           $res .= '+';
385         continue;
386       }
387
388       $res .= rcube_utf16_to_utf8(base64_decode($ch));
389     }
390     else
391     {
392       $res .= $u7;
393     }
394   }
395
396   return $res;
397 }
398
399 /**
400  * Converts string from UTF-16 to UTF-8 (helper for utf-7 to utf-8 conversion)
401  *
402  * @param  string  Input string
403  * @return The converted string
404  */
405 function rcube_utf16_to_utf8($str)
406 {
407   $len = strlen($str);
408   $dec = '';
409
410   for ($i = 0; $i < $len; $i += 2) {
411     $c = ord($str[$i]) << 8 | ord($str[$i + 1]);
412     if ($c >= 0x0001 && $c <= 0x007F) {
413       $dec .= chr($c);
414     } else if ($c > 0x07FF) {
415       $dec .= chr(0xE0 | (($c >> 12) & 0x0F));
416       $dec .= chr(0x80 | (($c >>  6) & 0x3F));
417       $dec .= chr(0x80 | (($c >>  0) & 0x3F));
418     } else {
419       $dec .= chr(0xC0 | (($c >>  6) & 0x1F));
420       $dec .= chr(0x80 | (($c >>  0) & 0x3F));
421     }
422   }
423   return $dec;
424 }
425
426
427 /**
428  * Replacing specials characters to a specific encoding type
429  *
430  * @param  string  Input string
431  * @param  string  Encoding type: text|html|xml|js|url
432  * @param  string  Replace mode for tags: show|replace|remove
433  * @param  boolean Convert newlines
434  * @return The quoted string
435  */
436 function rep_specialchars_output($str, $enctype='', $mode='', $newlines=TRUE)
437   {
438   static $html_encode_arr = false;
439   static $js_rep_table = false;
440   static $xml_rep_table = false;
441
442   if (!$enctype)
443     $enctype = $OUTPUT->type;
444
445   // encode for HTML output
446   if ($enctype=='html')
447     {
448     if (!$html_encode_arr)
449       {
450       $html_encode_arr = get_html_translation_table(HTML_SPECIALCHARS);        
451       unset($html_encode_arr['?']);
452       }
453
454     $ltpos = strpos($str, '<');
455     $encode_arr = $html_encode_arr;
456
457     // don't replace quotes and html tags
458     if (($mode=='show' || $mode=='') && $ltpos!==false && strpos($str, '>', $ltpos)!==false)
459       {
460       unset($encode_arr['"']);
461       unset($encode_arr['<']);
462       unset($encode_arr['>']);
463       unset($encode_arr['&']);
464       }
465     else if ($mode=='remove')
466       $str = strip_tags($str);
467     
468     // avoid douple quotation of &
469     $out = preg_replace('/&amp;([A-Za-z]{2,6}|#[0-9]{2,4});/', '&\\1;', strtr($str, $encode_arr));
470       
471     return $newlines ? nl2br($out) : $out;
472     }
473
474   // if the replace tables for XML and JS are not yet defined
475   if ($js_rep_table===false)
476     {
477     $js_rep_table = $xml_rep_table = array();
478     $xml_rep_table['&'] = '&amp;';
479
480     for ($c=160; $c<256; $c++)  // can be increased to support more charsets
481       $xml_rep_table[Chr($c)] = "&#$c;";
482
483     $xml_rep_table['"'] = '&quot;';
484     $js_rep_table['"'] = '\\"';
485     $js_rep_table["'"] = "\\'";
486     $js_rep_table["\\"] = "\\\\";
487     }
488
489   // encode for javascript use
490   if ($enctype=='js')
491     return preg_replace(array("/\r?\n/", "/\r/", '/<\\//'), array('\n', '\n', '<\\/'), strtr($str, $js_rep_table));
492
493   // encode for plaintext
494   if ($enctype=='text')
495     return str_replace("\r\n", "\n", $mode=='remove' ? strip_tags($str) : $str);
496
497   if ($enctype=='url')
498     return rawurlencode($str);
499
500   // encode for XML
501   if ($enctype=='xml')
502     return strtr($str, $xml_rep_table);
503
504   // no encoding given -> return original string
505   return $str;
506   }
507   
508 /**
509  * Quote a given string.
510  * Shortcut function for rep_specialchars_output
511  *
512  * @return string HTML-quoted string
513  * @see rep_specialchars_output()
514  */
515 function Q($str, $mode='strict', $newlines=TRUE)
516   {
517   return rep_specialchars_output($str, 'html', $mode, $newlines);
518   }
519
520 /**
521  * Quote a given string for javascript output.
522  * Shortcut function for rep_specialchars_output
523  * 
524  * @return string JS-quoted string
525  * @see rep_specialchars_output()
526  */
527 function JQ($str)
528   {
529   return rep_specialchars_output($str, 'js');
530   }
531
532
533 /**
534  * Read input value and convert it for internal use
535  * Performs stripslashes() and charset conversion if necessary
536  * 
537  * @param  string   Field name to read
538  * @param  int      Source to get value from (GPC)
539  * @param  boolean  Allow HTML tags in field value
540  * @param  string   Charset to convert into
541  * @return string   Field value or NULL if not available
542  */
543 function get_input_value($fname, $source, $allow_html=FALSE, $charset=NULL)
544 {
545   global $OUTPUT;
546   $value = NULL;
547   
548   if ($source==RCUBE_INPUT_GET && isset($_GET[$fname]))
549     $value = $_GET[$fname];
550   else if ($source==RCUBE_INPUT_POST && isset($_POST[$fname]))
551     $value = $_POST[$fname];
552   else if ($source==RCUBE_INPUT_GPC)
553     {
554     if (isset($_POST[$fname]))
555       $value = $_POST[$fname];
556     else if (isset($_GET[$fname]))
557       $value = $_GET[$fname];
558     else if (isset($_COOKIE[$fname]))
559       $value = $_COOKIE[$fname];
560     }
561
562   if (empty($value))
563     return $value;
564
565   // strip single quotes if magic_quotes_sybase is enabled
566   if (ini_get('magic_quotes_sybase'))
567     $value = str_replace("''", "'", $value);
568   // strip slashes if magic_quotes enabled
569   else if (get_magic_quotes_gpc() || get_magic_quotes_runtime())
570     $value = stripslashes($value);
571
572   // remove HTML tags if not allowed    
573   if (!$allow_html)
574     $value = strip_tags($value);
575   
576   // convert to internal charset
577   if (is_object($OUTPUT))
578     return rcube_charset_convert($value, $OUTPUT->get_charset(), $charset);
579   else
580     return $value;
581 }
582
583 /**
584  * Convert array of request parameters (prefixed with _)
585  * to a regular array with non-prefixed keys.
586  *
587  * @param  int   Source to get value from (GPC)
588  * @return array Hash array with all request parameters
589  */
590 function request2param($mode = RCUBE_INPUT_GPC)
591 {
592   $out = array();
593   $src = $mode == RCUBE_INPUT_GET ? $_GET : ($mode == RCUBE_INPUT_POST ? $_POST : $_REQUEST);
594   foreach ($src as $key => $value) {
595     $fname = $key[0] == '_' ? substr($key, 1) : $key;
596     $out[$fname] = get_input_value($key, $mode);
597   }
598   
599   return $out;
600 }
601
602 /**
603  * Remove all non-ascii and non-word chars
604  * except ., -, _
605  */
606 function asciiwords($str, $css_id = false, $replace_with = '')
607 {
608   $allowed = 'a-z0-9\_\-' . (!$css_id ? '\.' : '');
609   return preg_replace("/[^$allowed]/i", $replace_with, $str);
610 }
611
612 /**
613  * Remove single and double quotes from given string
614  *
615  * @param string Input value
616  * @return string Dequoted string
617  */
618 function strip_quotes($str)
619 {
620   return preg_replace('/[\'"]/', '', $str);
621 }
622
623
624 /**
625  * Remove new lines characters from given string
626  *
627  * @param string Input value
628  * @return string Stripped string
629  */
630 function strip_newlines($str)
631 {
632   return preg_replace('/[\r\n]/', '', $str);
633 }
634
635
636 /**
637  * Create a HTML table based on the given data
638  *
639  * @param  array  Named table attributes
640  * @param  mixed  Table row data. Either a two-dimensional array or a valid SQL result set
641  * @param  array  List of cols to show
642  * @param  string Name of the identifier col
643  * @return string HTML table code
644  */
645 function rcube_table_output($attrib, $table_data, $a_show_cols, $id_col)
646   {
647   global $RCMAIL;
648   
649   $table = new html_table(/*array('cols' => count($a_show_cols))*/);
650     
651   // add table header
652   foreach ($a_show_cols as $col)
653     $table->add_header($col, Q(rcube_label($col)));
654   
655   $c = 0;
656   if (!is_array($table_data)) 
657   {
658     $db = $RCMAIL->get_dbh();
659     while ($table_data && ($sql_arr = $db->fetch_assoc($table_data)))
660     {
661       $zebra_class = $c % 2 ? 'even' : 'odd';
662       $table->add_row(array('id' => 'rcmrow' . $sql_arr[$id_col], 'class' => $zebra_class));
663
664       // format each col
665       foreach ($a_show_cols as $col)
666         $table->add($col, Q($sql_arr[$col]));
667       
668       $c++;
669     }
670   }
671   else 
672   {
673     foreach ($table_data as $row_data)
674     {
675       $zebra_class = $c % 2 ? 'even' : 'odd';
676       $table->add_row(array('id' => 'rcmrow' . $row_data[$id_col], 'class' => $zebra_class));
677
678       // format each col
679       foreach ($a_show_cols as $col)
680         $table->add($col, Q($row_data[$col]));
681         
682       $c++;
683     }
684   }
685
686   return $table->show($attrib);
687   }
688
689
690 /**
691  * Create an edit field for inclusion on a form
692  * 
693  * @param string col field name
694  * @param string value field value
695  * @param array attrib HTML element attributes for field
696  * @param string type HTML element type (default 'text')
697  * @return string HTML field definition
698  */
699 function rcmail_get_edit_field($col, $value, $attrib, $type='text')
700   {
701   $fname = '_'.$col;
702   $attrib['name'] = $fname;
703   
704   if ($type=='checkbox')
705     {
706     $attrib['value'] = '1';
707     $input = new html_checkbox($attrib);
708     }
709   else if ($type=='textarea')
710     {
711     $attrib['cols'] = $attrib['size'];
712     $input = new html_textarea($attrib);
713     }
714   else
715     $input = new html_inputfield($attrib);
716
717   // use value from post
718   if (!empty($_POST[$fname]))
719     $value = get_input_value($fname, RCUBE_INPUT_POST,
720             $type == 'textarea' && strpos($attrib['class'], 'mce_editor')!==false ? true : false);
721
722   $out = $input->show($value);
723          
724   return $out;
725   }
726
727
728 /**
729  * Replace all css definitions with #container [def]
730  * and remove css-inlined scripting
731  *
732  * @param string CSS source code
733  * @param string Container ID to use as prefix
734  * @return string Modified CSS source
735  */
736 function rcmail_mod_css_styles($source, $container_id)
737   {
738   $last_pos = 0;
739   $replacements = new rcube_string_replacer;
740   
741   // ignore the whole block if evil styles are detected
742   $stripped = preg_replace('/[^a-z\(:]/', '', rcmail_xss_entity_decode($source));
743   if (preg_match('/expression|behavior|url\(|import/', $stripped))
744     return '/* evil! */';
745
746   // cut out all contents between { and }
747   while (($pos = strpos($source, '{', $last_pos)) && ($pos2 = strpos($source, '}', $pos)))
748   {
749     $key = $replacements->add(substr($source, $pos+1, $pos2-($pos+1)));
750     $source = substr($source, 0, $pos+1) . $replacements->get_replacement($key) . substr($source, $pos2, strlen($source)-$pos2);
751     $last_pos = $pos+2;
752   }
753   
754   // remove html comments and add #container to each tag selector.
755   // also replace body definition because we also stripped off the <body> tag
756   $styles = preg_replace(
757     array(
758       '/(^\s*<!--)|(-->\s*$)/',
759       '/(^\s*|,\s*|\}\s*)([a-z0-9\._#][a-z0-9\.\-_]*)/im',
760       "/$container_id\s+body/i",
761     ),
762     array(
763       '',
764       "\\1#$container_id \\2",
765       "$container_id div.rcmBody",
766     ),
767     $source);
768   
769   // put block contents back in
770   $styles = $replacements->resolve($styles);
771
772   return $styles;
773   }
774
775
776 /**
777  * Decode escaped entities used by known XSS exploits.
778  * See http://downloads.securityfocus.com/vulnerabilities/exploits/26800.eml for examples
779  *
780  * @param string CSS content to decode
781  * @return string Decoded string
782  */
783 function rcmail_xss_entity_decode($content)
784 {
785   $out = html_entity_decode(html_entity_decode($content));
786   $out = preg_replace_callback('/\\\([0-9a-f]{4})/i', 'rcmail_xss_entity_decode_callback', $out);
787   $out = preg_replace('#/\*.*\*/#Um', '', $out);
788   return $out;
789 }
790
791
792 /**
793  * preg_replace_callback callback for rcmail_xss_entity_decode_callback
794  *
795  * @param array matches result from preg_replace_callback
796  * @return string decoded entity
797  */ 
798 function rcmail_xss_entity_decode_callback($matches)
799
800   return chr(hexdec($matches[1]));
801 }
802
803 /**
804  * Compose a valid attribute string for HTML tags
805  *
806  * @param array Named tag attributes
807  * @param array List of allowed attributes
808  * @return string HTML formatted attribute string
809  */
810 function create_attrib_string($attrib, $allowed_attribs=array('id', 'class', 'style'))
811   {
812   // allow the following attributes to be added to the <iframe> tag
813   $attrib_str = '';
814   foreach ($allowed_attribs as $a)
815     if (isset($attrib[$a]))
816       $attrib_str .= sprintf(' %s="%s"', $a, str_replace('"', '&quot;', $attrib[$a]));
817
818   return $attrib_str;
819   }
820
821
822 /**
823  * Convert a HTML attribute string attributes to an associative array (name => value)
824  *
825  * @param string Input string
826  * @return array Key-value pairs of parsed attributes
827  */
828 function parse_attrib_string($str)
829   {
830   $attrib = array();
831   preg_match_all('/\s*([-_a-z]+)=(["\'])??(?(2)([^\2]*)\2|(\S+?))/Ui', stripslashes($str), $regs, PREG_SET_ORDER);
832
833   // convert attributes to an associative array (name => value)
834   if ($regs) {
835     foreach ($regs as $attr) {
836       $attrib[strtolower($attr[1])] = html_entity_decode($attr[3] . $attr[4]);
837     }
838   }
839
840   return $attrib;
841   }
842
843
844 /**
845  * Convert the given date to a human readable form
846  * This uses the date formatting properties from config
847  *
848  * @param mixed Date representation (string or timestamp)
849  * @param string Date format to use
850  * @return string Formatted date string
851  */
852 function format_date($date, $format=NULL)
853   {
854   global $CONFIG;
855   
856   $ts = NULL;
857
858   if (is_numeric($date))
859     $ts = $date;
860   else if (!empty($date))
861     {
862     // support non-standard "GMTXXXX" literal
863     $date = preg_replace('/GMT\s*([+-][0-9]+)/', '\\1', $date);
864     // if date parsing fails, we have a date in non-rfc format.
865     // remove token from the end and try again
866     while ((($ts = @strtotime($date))===false) || ($ts < 0))
867       {
868         $d = explode(' ', $date);
869         array_pop($d);
870         if (!$d) break;
871         $date = implode(' ', $d);
872       }
873     }
874
875   if (empty($ts))
876     return '';
877    
878   // get user's timezone
879   if ($CONFIG['timezone'] === 'auto')
880     $tz = isset($_SESSION['timezone']) ? $_SESSION['timezone'] : date('Z')/3600;
881   else {
882     $tz = $CONFIG['timezone'];
883     if ($CONFIG['dst_active'])
884       $tz++;
885   }
886
887   // convert time to user's timezone
888   $timestamp = $ts - date('Z', $ts) + ($tz * 3600);
889   
890   // get current timestamp in user's timezone
891   $now = time();  // local time
892   $now -= (int)date('Z'); // make GMT time
893   $now += ($tz * 3600); // user's time
894   $now_date = getdate($now);
895
896   $today_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday'], $now_date['year']);
897   $week_limit = mktime(0, 0, 0, $now_date['mon'], $now_date['mday']-6, $now_date['year']);
898
899   // define date format depending on current time  
900   if ($CONFIG['prettydate'] && !$format && $timestamp > $today_limit && $timestamp < $now)
901     return sprintf('%s %s', rcube_label('today'), date($CONFIG['date_today'] ? $CONFIG['date_today'] : 'H:i', $timestamp));
902   else if ($CONFIG['prettydate'] && !$format && $timestamp > $week_limit && $timestamp < $now)
903     $format = $CONFIG['date_short'] ? $CONFIG['date_short'] : 'D H:i';
904   else if (!$format)
905     $format = $CONFIG['date_long'] ? $CONFIG['date_long'] : 'd.m.Y H:i';
906
907   // strftime() format
908   if (preg_match('/%[a-z]+/i', $format))
909     return strftime($format, $timestamp);
910
911   // parse format string manually in order to provide localized weekday and month names
912   // an alternative would be to convert the date() format string to fit with strftime()
913   $out = '';
914   for($i=0; $i<strlen($format); $i++)
915     {
916     if ($format{$i}=='\\')  // skip escape chars
917       continue;
918     
919     // write char "as-is"
920     if ($format{$i}==' ' || $format{$i-1}=='\\')
921       $out .= $format{$i};
922     // weekday (short)
923     else if ($format{$i}=='D')
924       $out .= rcube_label(strtolower(date('D', $timestamp)));
925     // weekday long
926     else if ($format{$i}=='l')
927       $out .= rcube_label(strtolower(date('l', $timestamp)));
928     // month name (short)
929     else if ($format{$i}=='M')
930       $out .= rcube_label(strtolower(date('M', $timestamp)));
931     // month name (long)
932     else if ($format{$i}=='F')
933       $out .= rcube_label('long'.strtolower(date('M', $timestamp)));
934     else if ($format{$i}=='x')
935       $out .= strftime('%x %X', $timestamp);
936     else
937       $out .= date($format{$i}, $timestamp);
938     }
939   
940   return $out;
941   }
942
943
944 /**
945  * Compose a valid representation of name and e-mail address
946  *
947  * @param string E-mail address
948  * @param string Person name
949  * @return string Formatted string
950  */
951 function format_email_recipient($email, $name='')
952   {
953   if ($name && $name != $email)
954     {
955     // Special chars as defined by RFC 822 need to in quoted string (or escaped).
956     return sprintf('%s <%s>', preg_match('/[\(\)\<\>\\\.\[\]@,;:"]/', $name) ? '"'.addcslashes($name, '"').'"' : $name, trim($email));
957     }
958   else
959     return trim($email);
960   }
961
962
963
964 /****** debugging functions ********/
965
966
967 /**
968  * Print or write debug messages
969  *
970  * @param mixed Debug message or data
971  */
972 function console()
973   {
974   $args = func_get_args();
975
976   if (class_exists('rcmail', false)) {
977     $rcmail = rcmail::get_instance();
978     if (is_object($rcmail->plugins))
979       $rcmail->plugins->exec_hook('console', $args);
980   }
981
982   $msg = array();
983   foreach ($args as $arg)
984     $msg[] = !is_string($arg) ? var_export($arg, true) : $arg;
985
986   if (!($GLOBALS['CONFIG']['debug_level'] & 4))
987     write_log('console', join(";\n", $msg));
988   else if ($GLOBALS['OUTPUT']->ajax_call)
989     print "/*\n " . join(";\n", $msg) . " \n*/\n";
990   else
991     {
992     print '<div style="background:#eee; border:1px solid #ccc; margin-bottom:3px; padding:6px"><pre>';
993     print join(";<br/>\n", $msg);
994     print "</pre></div>\n";
995     }
996   }
997
998
999 /**
1000  * Append a line to a logfile in the logs directory.
1001  * Date will be added automatically to the line.
1002  *
1003  * @param $name name of log file
1004  * @param line Line to append
1005  */
1006 function write_log($name, $line)
1007   {
1008   global $CONFIG, $RCMAIL;
1009
1010   if (!is_string($line))
1011     $line = var_export($line, true);
1012  
1013   if (empty($CONFIG['log_date_format']))
1014     $CONFIG['log_date_format'] = 'd-M-Y H:i:s O';
1015   
1016   $date = date($CONFIG['log_date_format']);
1017   
1018   // trigger logging hook
1019   if (is_object($RCMAIL) && is_object($RCMAIL->plugins)) {
1020     $log = $RCMAIL->plugins->exec_hook('write_log', array('name' => $name, 'date' => $date, 'line' => $line));
1021     $name = $log['name'];
1022     $line = $log['line'];
1023     $date = $log['date'];
1024     if ($log['abort'])
1025       return true;
1026   }
1027  
1028   $log_entry = sprintf("[%s]: %s\n", $date, $line);
1029
1030   if ($CONFIG['log_driver'] == 'syslog') {
1031     $prio = $name == 'errors' ? LOG_ERR : LOG_INFO;
1032     syslog($prio, $log_entry);
1033     return true;
1034   }
1035   else {
1036     // log_driver == 'file' is assumed here
1037     if (empty($CONFIG['log_dir']))
1038       $CONFIG['log_dir'] = INSTALL_PATH.'logs';
1039
1040     // try to open specific log file for writing
1041     $logfile = $CONFIG['log_dir'].'/'.$name;
1042     if ($fp = @fopen($logfile, 'a')) {
1043       fwrite($fp, $log_entry);
1044       fflush($fp);
1045       fclose($fp);
1046       return true;
1047     }
1048     else
1049       trigger_error("Error writing to log file $logfile; Please check permissions", E_USER_WARNING);
1050   }
1051   return false;
1052 }
1053
1054
1055 /**
1056  * @access private
1057  */
1058 function rcube_timer()
1059 {
1060   return microtime(true);
1061 }
1062   
1063
1064 /**
1065  * @access private
1066  */
1067 function rcube_print_time($timer, $label='Timer', $dest='console')
1068 {
1069   static $print_count = 0;
1070   
1071   $print_count++;
1072   $now = rcube_timer();
1073   $diff = $now-$timer;
1074   
1075   if (empty($label))
1076     $label = 'Timer '.$print_count;
1077   
1078   write_log($dest, sprintf("%s: %0.4f sec", $label, $diff));
1079 }
1080
1081
1082 /**
1083  * Return the mailboxlist in HTML
1084  *
1085  * @param array Named parameters
1086  * @return string HTML code for the gui object
1087  */
1088 function rcmail_mailbox_list($attrib)
1089 {
1090   global $RCMAIL;
1091   static $a_mailboxes;
1092   
1093   $attrib += array('maxlength' => 100, 'relanames' => false);
1094
1095   // add some labels to client
1096   $RCMAIL->output->add_label('purgefolderconfirm', 'deletemessagesconfirm');
1097   
1098   $type = $attrib['type'] ? $attrib['type'] : 'ul';
1099   unset($attrib['type']);
1100
1101   if ($type=='ul' && !$attrib['id'])
1102     $attrib['id'] = 'rcmboxlist';
1103
1104   // get mailbox list
1105   $mbox_name = $RCMAIL->imap->get_mailbox_name();
1106   
1107   // build the folders tree
1108   if (empty($a_mailboxes)) {
1109     // get mailbox list
1110     $a_folders = $RCMAIL->imap->list_mailboxes();
1111     $delimiter = $RCMAIL->imap->get_hierarchy_delimiter();
1112     $a_mailboxes = array();
1113
1114     foreach ($a_folders as $folder)
1115       rcmail_build_folder_tree($a_mailboxes, $folder, $delimiter);
1116   }
1117   
1118   // allow plugins to alter the folder tree or to localize folder names
1119   $hook = $RCMAIL->plugins->exec_hook('render_mailboxlist', array('list' => $a_mailboxes, 'delimiter' => $delimiter));
1120
1121   if ($type=='select') {
1122     $select = new html_select($attrib);
1123     
1124     // add no-selection option
1125     if ($attrib['noselection'])
1126       $select->add(rcube_label($attrib['noselection']), '0');
1127     
1128     rcmail_render_folder_tree_select($hook['list'], $mbox_name, $attrib['maxlength'], $select, $attrib['realnames']);
1129     $out = $select->show();
1130   }
1131   else {
1132     $js_mailboxlist = array();
1133     $out = html::tag('ul', $attrib, rcmail_render_folder_tree_html($hook['list'], $mbox_name, $js_mailboxlist, $attrib), html::$common_attrib);
1134     
1135     $RCMAIL->output->add_gui_object('mailboxlist', $attrib['id']);
1136     $RCMAIL->output->set_env('mailboxes', $js_mailboxlist);
1137     $RCMAIL->output->set_env('collapsed_folders', $RCMAIL->config->get('collapsed_folders'));
1138   }
1139
1140   return $out;
1141 }
1142
1143
1144 /**
1145  * Return the mailboxlist as html_select object
1146  *
1147  * @param array Named parameters
1148  * @return object html_select HTML drop-down object
1149  */
1150 function rcmail_mailbox_select($p = array())
1151 {
1152   global $RCMAIL;
1153   
1154   $p += array('maxlength' => 100, 'realnames' => false);
1155   $a_mailboxes = array();
1156   
1157   foreach ($RCMAIL->imap->list_mailboxes() as $folder)
1158     if (empty($p['exceptions']) || !in_array($folder, $p['exceptions']))
1159       rcmail_build_folder_tree($a_mailboxes, $folder, $RCMAIL->imap->get_hierarchy_delimiter());
1160
1161   $select = new html_select($p);
1162   
1163   if ($p['noselection'])
1164     $select->add($p['noselection'], '');
1165     
1166   rcmail_render_folder_tree_select($a_mailboxes, $mbox, $p['maxlength'], $select, $p['realnames']);
1167   
1168   return $select;
1169 }
1170
1171
1172 /**
1173  * Create a hierarchical array of the mailbox list
1174  * @access private
1175  */
1176 function rcmail_build_folder_tree(&$arrFolders, $folder, $delm='/', $path='')
1177 {
1178   $pos = strpos($folder, $delm);
1179   if ($pos !== false) {
1180     $subFolders = substr($folder, $pos+1);
1181     $currentFolder = substr($folder, 0, $pos);
1182     $virtual = !isset($arrFolders[$currentFolder]);
1183   }
1184   else {
1185     $subFolders = false;
1186     $currentFolder = $folder;
1187     $virtual = false;
1188   }
1189
1190   $path .= $currentFolder;
1191
1192   if (!isset($arrFolders[$currentFolder])) {
1193     $arrFolders[$currentFolder] = array(
1194       'id' => $path,
1195       'name' => rcube_charset_convert($currentFolder, 'UTF7-IMAP'),
1196       'virtual' => $virtual,
1197       'folders' => array());
1198   }
1199   else
1200     $arrFolders[$currentFolder]['virtual'] = $virtual;
1201
1202   if (!empty($subFolders))
1203     rcmail_build_folder_tree($arrFolders[$currentFolder]['folders'], $subFolders, $delm, $path.$delm);
1204 }
1205   
1206
1207 /**
1208  * Return html for a structured list &lt;ul&gt; for the mailbox tree
1209  * @access private
1210  */
1211 function rcmail_render_folder_tree_html(&$arrFolders, &$mbox_name, &$jslist, $attrib, $nestLevel=0)
1212 {
1213   global $RCMAIL, $CONFIG;
1214   
1215   $maxlength = intval($attrib['maxlength']);
1216   $realnames = (bool)$attrib['realnames'];
1217   $msgcounts = $RCMAIL->imap->get_cache('messagecount');
1218
1219   $idx = 0;
1220   $out = '';
1221   foreach ($arrFolders as $key => $folder) {
1222     $zebra_class = (($nestLevel+1)*$idx) % 2 == 0 ? 'even' : 'odd';
1223     $title = null;
1224
1225     if (($folder_class = rcmail_folder_classname($folder['id'])) && !$realnames) {
1226       $foldername = rcube_label($folder_class);
1227     }
1228     else {
1229       $foldername = $folder['name'];
1230
1231       // shorten the folder name to a given length
1232       if ($maxlength && $maxlength > 1) {
1233         $fname = abbreviate_string($foldername, $maxlength);
1234         if ($fname != $foldername)
1235           $title = $foldername;
1236         $foldername = $fname;
1237       }
1238     }
1239
1240     // make folder name safe for ids and class names
1241     $folder_id = asciiwords($folder['id'], true, '_');
1242     $classes = array('mailbox');
1243
1244     // set special class for Sent, Drafts, Trash and Junk
1245     if ($folder['id']==$CONFIG['sent_mbox'])
1246       $classes[] = 'sent';
1247     else if ($folder['id']==$CONFIG['drafts_mbox'])
1248       $classes[] = 'drafts';
1249     else if ($folder['id']==$CONFIG['trash_mbox'])
1250       $classes[] = 'trash';
1251     else if ($folder['id']==$CONFIG['junk_mbox'])
1252       $classes[] = 'junk';
1253     else if ($folder['id']=='INBOX')
1254       $classes[] = 'inbox';
1255     else
1256       $classes[] = '_'.asciiwords($folder_class ? $folder_class : strtolower($folder['id']), true);
1257       
1258     $classes[] = $zebra_class;
1259     
1260     if ($folder['id'] == $mbox_name)
1261       $classes[] = 'selected';
1262
1263     $collapsed = preg_match('/&'.rawurlencode($folder['id']).'&/', $RCMAIL->config->get('collapsed_folders'));
1264     $unread = $msgcounts ? intval($msgcounts[$folder['id']]['UNSEEN']) : 0;
1265     
1266     if ($folder['virtual'])
1267       $classes[] = 'virtual';
1268     else if ($unread)
1269       $classes[] = 'unread';
1270
1271     $js_name = JQ($folder['id']);
1272     $html_name = Q($foldername . ($unread ? " ($unread)" : ''));
1273     $link_attrib = $folder['virtual'] ? array() : array(
1274       'href' => rcmail_url('', array('_mbox' => $folder['id'])),
1275       'onclick' => sprintf("return %s.command('list','%s',this)", JS_OBJECT_NAME, $js_name),
1276       'title' => $title,
1277     );
1278
1279     $out .= html::tag('li', array(
1280         'id' => "rcmli".$folder_id,
1281         'class' => join(' ', $classes),
1282         'noclose' => true),
1283       html::a($link_attrib, $html_name) .
1284       (!empty($folder['folders']) ? html::div(array(
1285         'class' => ($collapsed ? 'collapsed' : 'expanded'),
1286         'style' => "position:absolute",
1287         'onclick' => sprintf("%s.command('collapse-folder', '%s')", JS_OBJECT_NAME, $js_name)
1288       ), '&nbsp;') : ''));
1289     
1290     $jslist[$folder_id] = array('id' => $folder['id'], 'name' => $foldername, 'virtual' => $folder['virtual']);
1291     
1292     if (!empty($folder['folders'])) {
1293       $out .= html::tag('ul', array('style' => ($collapsed ? "display:none;" : null)),
1294         rcmail_render_folder_tree_html($folder['folders'], $mbox_name, $jslist, $attrib, $nestLevel+1));
1295     }
1296
1297     $out .= "</li>\n";
1298     $idx++;
1299   }
1300
1301   return $out;
1302 }
1303
1304
1305 /**
1306  * Return html for a flat list <select> for the mailbox tree
1307  * @access private
1308  */
1309 function rcmail_render_folder_tree_select(&$arrFolders, &$mbox_name, $maxlength, &$select, $realnames=false, $nestLevel=0)
1310   {
1311   $idx = 0;
1312   $out = '';
1313   foreach ($arrFolders as $key=>$folder)
1314     {
1315     if (!$realnames && ($folder_class = rcmail_folder_classname($folder['id'])))
1316       $foldername = rcube_label($folder_class);
1317     else
1318       {
1319       $foldername = $folder['name'];
1320       
1321       // shorten the folder name to a given length
1322       if ($maxlength && $maxlength>1)
1323         $foldername = abbreviate_string($foldername, $maxlength);
1324       }
1325
1326     $select->add(str_repeat('&nbsp;', $nestLevel*4) . $foldername, $folder['id']);
1327
1328     if (!empty($folder['folders']))
1329       $out .= rcmail_render_folder_tree_select($folder['folders'], $mbox_name, $maxlength, $select, $realnames, $nestLevel+1);
1330
1331     $idx++;
1332     }
1333
1334   return $out;
1335   }
1336
1337
1338 /**
1339  * Return internal name for the given folder if it matches the configured special folders
1340  * @access private
1341  */
1342 function rcmail_folder_classname($folder_id)
1343 {
1344   global $CONFIG;
1345
1346   // for these mailboxes we have localized labels and css classes
1347   foreach (array('sent', 'drafts', 'trash', 'junk') as $smbx)
1348   {
1349     if ($folder_id == $CONFIG[$smbx.'_mbox'])
1350       return $smbx;
1351   }
1352
1353   if ($folder_id == 'INBOX')
1354     return 'inbox';
1355 }
1356
1357
1358 /**
1359  * Try to localize the given IMAP folder name.
1360  * UTF-7 decode it in case no localized text was found
1361  *
1362  * @param string Folder name
1363  * @return string Localized folder name in UTF-8 encoding
1364  */
1365 function rcmail_localize_foldername($name)
1366 {
1367   if ($folder_class = rcmail_folder_classname($name))
1368     return rcube_label($folder_class);
1369   else
1370     return rcube_charset_convert($name, 'UTF7-IMAP');
1371 }
1372
1373
1374 /**
1375  * Output HTML editor scripts
1376  *
1377  * @param string Editor mode
1378  */
1379 function rcube_html_editor($mode='')
1380 {
1381   global $RCMAIL, $CONFIG;
1382
1383   $hook = $RCMAIL->plugins->exec_hook('hmtl_editor', array('mode' => $mode));
1384
1385   if ($hook['abort'])
1386     return;  
1387
1388   $lang = strtolower(substr($_SESSION['language'], 0, 2));
1389   if (!file_exists(INSTALL_PATH . 'program/js/tiny_mce/langs/'.$lang.'.js'))
1390     $lang = 'en';
1391
1392   $RCMAIL->output->include_script('tiny_mce/tiny_mce.js');
1393   $RCMAIL->output->include_script('editor.js');
1394   $RCMAIL->output->add_script('rcmail_editor_init("$__skin_path",
1395     "'.JQ($lang).'", '.intval($CONFIG['enable_spellcheck']).', "'.$mode.'");');
1396 }
1397
1398
1399 /**
1400  * Check if working in SSL mode
1401  *
1402  * @param integer HTTPS port number
1403  * @param boolean Enables 'use_https' option checking
1404  */
1405 function rcube_https_check($port=null, $use_https=true)
1406 {
1407   global $RCMAIL;
1408   
1409   if (isset($_SERVER['HTTPS']) && $_SERVER['HTTPS'] != 'off')
1410     return true;
1411   if ($port && $_SERVER['SERVER_PORT'] == $port)
1412     return true;
1413   if ($use_https && $RCMAIL->config->get('use_https'))
1414     return true;
1415
1416   return false;
1417 }
1418
1419
1420 /**
1421  * E-mail address validation
1422  */
1423 function check_email($email)
1424 {
1425   // Check for invalid characters
1426   if (preg_match('/[\x00-\x1F\x7F-\xFF]/', $email))
1427     return false;
1428
1429   // Check that there's one @ symbol, and that the lengths are right
1430   if (!preg_match('/^([^@]{1,64})@([^@]{1,255})$/', $email, $email_array))
1431     return false;
1432
1433   // Check local part
1434   $local_array = explode('.', $email_array[1]);
1435   foreach ($local_array as $local_part)
1436     if (!preg_match('/^(([A-Za-z0-9!#$%&\'*+\/=?^_`{|}~-]+)|("[^"]+"))$/', $local_part))
1437       return false;
1438
1439   // Check domain part
1440   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}$/', $email_array[2]) 
1441       || 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}\]$/', $email_array[2]))
1442     return true; // If an IP address
1443   else {
1444     // If not an IP address
1445     $domain_array = explode('.', $email_array[2]);
1446     if (sizeof($domain_array) < 2)
1447       return false; // Not enough parts to be a valid domain
1448
1449     foreach ($domain_array as $domain_part)
1450       if (!preg_match('/^(([A-Za-z0-9][A-Za-z0-9-]{0,61}[A-Za-z0-9])|([A-Za-z0-9]))$/', $domain_part))
1451         return false;
1452
1453     if (!rcmail::get_instance()->config->get('email_dns_check'))
1454       return true;
1455
1456     if (strtoupper(substr(PHP_OS, 0, 3)) == 'WIN' && version_compare(PHP_VERSION, '5.3.0', '<'))
1457       return true;
1458
1459     // find MX record(s)
1460     if (getmxrr($email_array[2], $mx_records))
1461       return true;
1462
1463     // find any DNS record
1464     if (checkdnsrr($email_array[2], 'ANY'))
1465       return true;
1466   }
1467
1468   return false;
1469 }
1470
1471
1472 /**
1473  * Helper class to turn relative urls into absolute ones
1474  * using a predefined base
1475  */
1476 class rcube_base_replacer
1477 {
1478   private $base_url;
1479   
1480   public function __construct($base)
1481   {
1482     $this->base_url = $base;
1483   }
1484   
1485   public function callback($matches)
1486   {
1487     return $matches[1] . '="' . make_absolute_url($matches[3], $this->base_url) . '"';
1488   }
1489 }
1490
1491 ?>