]> git.donarmstrong.com Git - roundcube.git/blob - program/include/rcube_shared.inc
Imported Upstream version 0.5.1
[roundcube.git] / program / include / rcube_shared.inc
1 <?php
2
3 /*
4  +-----------------------------------------------------------------------+
5  | rcube_shared.inc                                                      |
6  |                                                                       |
7  | This file is part of the Roundcube PHP suite                          |
8  | Copyright (C) 2005-2007, Roundcube Dev. - Switzerland                 |
9  | Licensed under the GNU GPL                                            |
10  |                                                                       |
11  | CONTENTS:                                                             |
12  |   Shared functions and classes used in PHP projects                   |
13  |                                                                       |
14  +-----------------------------------------------------------------------+
15  | Author: Thomas Bruederli <roundcube@gmail.com>                        |
16  +-----------------------------------------------------------------------+
17
18  $Id: rcube_shared.inc 4509 2011-02-09 10:51:50Z thomasb $
19
20 */
21
22
23 /**
24  * Roundcube shared functions
25  * 
26  * @package Core
27  */
28
29
30 /**
31  * Send HTTP headers to prevent caching this page
32  */
33 function send_nocacheing_headers()
34 {
35   global $OUTPUT;
36
37   if (headers_sent())
38     return;
39
40   header("Expires: ".gmdate("D, d M Y H:i:s")." GMT");
41   header("Last-Modified: ".gmdate("D, d M Y H:i:s")." GMT");
42   // Request browser to disable DNS prefetching (CVE-2010-0464)
43   header("X-DNS-Prefetch-Control: off");
44
45   // We need to set the following headers to make downloads work using IE in HTTPS mode.
46   if ($OUTPUT->browser->ie && rcube_https_check()) {
47     header('Pragma: private');
48     header("Cache-Control: private, must-revalidate");
49   } else {
50     header("Cache-Control: private, no-cache, must-revalidate, post-check=0, pre-check=0");
51     header("Pragma: no-cache");
52   }
53 }
54
55
56 /**
57  * Send header with expire date 30 days in future
58  *
59  * @param int Expiration time in seconds
60  */
61 function send_future_expire_header($offset=2600000)
62 {
63   if (headers_sent())
64     return;
65
66   header("Expires: ".gmdate("D, d M Y H:i:s", mktime()+$offset)." GMT");
67   header("Cache-Control: max-age=$offset");
68   header("Pragma: ");
69 }
70
71
72 /**
73  * Check request for If-Modified-Since and send an according response.
74  * This will terminate the current script if headers match the given values
75  *
76  * @param int Modified date as unix timestamp
77  * @param string Etag value for caching
78  */
79 function send_modified_header($mdate, $etag=null, $skip_check=false)
80 {
81   if (headers_sent())
82     return;
83     
84   $iscached = false;
85   $etag = $etag ? "\"$etag\"" : null;
86
87   if (!$skip_check)
88   {
89     if ($_SERVER['HTTP_IF_MODIFIED_SINCE'] && strtotime($_SERVER['HTTP_IF_MODIFIED_SINCE']) >= $mdate)
90       $iscached = true;
91   
92     if ($etag)
93       $iscached = ($_SERVER['HTTP_IF_NONE_MATCH'] == $etag);
94   }
95   
96   if ($iscached)
97     header("HTTP/1.x 304 Not Modified");
98   else
99     header("Last-Modified: ".gmdate("D, d M Y H:i:s", $mdate)." GMT");
100   
101   header("Cache-Control: private, must-revalidate, max-age=0");
102   header("Expires: ");
103   header("Pragma: ");
104   
105   if ($etag)
106     header("Etag: $etag");
107   
108   if ($iscached)
109     {
110     ob_end_clean();
111     exit;
112     }
113 }
114
115
116 /**
117  * Similar function as in_array() but case-insensitive
118  *
119  * @param mixed Needle value
120  * @param array Array to search in
121  * @return boolean True if found, False if not
122  */
123 function in_array_nocase($needle, $haystack)
124 {
125   $needle = mb_strtolower($needle);
126   foreach ($haystack as $value)
127     if ($needle===mb_strtolower($value))
128       return true;
129   
130   return false;
131 }
132
133
134 /**
135  * Find out if the string content means TRUE or FALSE
136  *
137  * @param string Input value
138  * @return boolean Imagine what!
139  */
140 function get_boolean($str)
141 {
142   $str = strtolower($str);
143   if (in_array($str, array('false', '0', 'no', 'nein', ''), TRUE))
144     return FALSE;
145   else
146     return TRUE;
147 }
148
149
150 /**
151  * Parse a human readable string for a number of bytes
152  *
153  * @param string Input string
154  * @return float Number of bytes
155  */
156 function parse_bytes($str)
157 {
158   if (is_numeric($str))
159     return floatval($str);
160
161   if (preg_match('/([0-9\.]+)\s*([a-z]*)/i', $str, $regs))
162   {
163     $bytes = floatval($regs[1]);
164     switch (strtolower($regs[2]))
165     {
166       case 'g':
167       case 'gb':
168         $bytes *= 1073741824;
169         break;
170       case 'm':
171       case 'mb':
172         $bytes *= 1048576;
173         break;
174       case 'k':
175       case 'kb':
176         $bytes *= 1024;
177         break;
178     }
179   }
180
181   return floatval($bytes);
182 }
183     
184 /**
185  * Create a human readable string for a number of bytes
186  *
187  * @param int Number of bytes
188  * @return string Byte string
189  */
190 function show_bytes($bytes)
191 {
192   if ($bytes > 1073741824)
193   {
194     $gb = $bytes/1073741824;
195     $str = sprintf($gb>=10 ? "%d " : "%.1f ", $gb) . rcube_label('GB');
196   }
197   else if ($bytes > 1048576)
198   {
199     $mb = $bytes/1048576;
200     $str = sprintf($mb>=10 ? "%d " : "%.1f ", $mb) . rcube_label('MB');
201   }
202   else if ($bytes > 1024)
203     $str = sprintf("%d ",  round($bytes/1024)) . rcube_label('KB');
204   else
205     $str = sprintf('%d ', $bytes) . rcube_label('B');
206
207   return $str;
208 }
209
210
211 /**
212  * Convert paths like ../xxx to an absolute path using a base url
213  *
214  * @param string Relative path
215  * @param string Base URL
216  * @return string Absolute URL
217  */
218 function make_absolute_url($path, $base_url)
219 {
220   $host_url = $base_url;
221   $abs_path = $path;
222   
223   // check if path is an absolute URL
224   if (preg_match('/^[fhtps]+:\/\//', $path))
225     return $path;
226
227   // cut base_url to the last directory
228   if (strrpos($base_url, '/')>7)
229   {
230     $host_url = substr($base_url, 0, strpos($base_url, '/', 7));
231     $base_url = substr($base_url, 0, strrpos($base_url, '/'));
232   }
233
234   // $path is absolute
235   if ($path{0}=='/')
236     $abs_path = $host_url.$path;
237   else
238   {
239     // strip './' because its the same as ''
240     $path = preg_replace('/^\.\//', '', $path);
241
242     if (preg_match_all('/\.\.\//', $path, $matches, PREG_SET_ORDER))
243       foreach ($matches as $a_match)
244       {
245         if (strrpos($base_url, '/'))
246           $base_url = substr($base_url, 0, strrpos($base_url, '/'));
247         
248         $path = substr($path, 3);
249       }
250
251     $abs_path = $base_url.'/'.$path;
252   }
253     
254   return $abs_path;
255 }
256
257 /**
258  * Wrapper function for wordwrap
259  */
260 function rc_wordwrap($string, $width=75, $break="\n", $cut=false)
261 {
262   $para = explode($break, $string);
263   $string = '';
264   while (count($para)) {
265     $line = array_shift($para);
266     if ($line[0] == '>') {
267       $string .= $line.$break;
268       continue;
269     }
270     $list = explode(' ', $line);
271     $len = 0;
272     while (count($list)) {
273       $line = array_shift($list);
274       $l = mb_strlen($line);
275       $newlen = $len + $l + ($len ? 1 : 0);
276
277       if ($newlen <= $width) {
278         $string .= ($len ? ' ' : '').$line;
279         $len += (1 + $l);
280       } else {
281         if ($l > $width) {
282           if ($cut) {
283             $start = 0;
284             while ($l) {
285               $str = mb_substr($line, $start, $width);
286               $strlen = mb_strlen($str);
287               $string .= ($len ? $break : '').$str;
288               $start += $strlen;
289               $l -= $strlen;
290               $len = $strlen;
291             }
292           } else {
293                 $string .= ($len ? $break : '').$line;
294             if (count($list)) $string .= $break;
295             $len = 0;
296           }
297         } else {
298           $string .= $break.$line;
299           $len = $l;
300         }
301       }
302     }
303     if (count($para)) $string .= $break;
304   }
305   return $string;
306 }
307
308 /**
309  * Read a specific HTTP request header
310  *
311  * @access static
312  * @param  string $name Header name
313  * @return mixed  Header value or null if not available
314  */
315 function rc_request_header($name)
316 {
317   if (function_exists('getallheaders'))
318   {
319     $hdrs = array_change_key_case(getallheaders(), CASE_UPPER);
320     $key  = strtoupper($name);
321   }
322   else
323   {
324     $key  = 'HTTP_' . strtoupper(strtr($name, '-', '_'));
325     $hdrs = array_change_key_case($_SERVER, CASE_UPPER);
326   }
327
328   return $hdrs[$key];
329   }
330
331
332 /**
333  * Make sure the string ends with a slash
334  */
335 function slashify($str)
336 {
337   return unslashify($str).'/';
338 }
339
340
341 /**
342  * Remove slash at the end of the string
343  */
344 function unslashify($str)
345 {
346   return preg_replace('/\/$/', '', $str);
347 }
348   
349
350 /**
351  * Delete all files within a folder
352  *
353  * @param string Path to directory
354  * @return boolean True on success, False if directory was not found
355  */
356 function clear_directory($dir_path)
357 {
358   $dir = @opendir($dir_path);
359   if(!$dir) return FALSE;
360
361   while ($file = readdir($dir))
362     if (strlen($file)>2)
363       unlink("$dir_path/$file");
364
365   closedir($dir);
366   return TRUE;
367 }
368
369
370 /**
371  * Create a unix timestamp with a specified offset from now
372  *
373  * @param string String representation of the offset (e.g. 20min, 5h, 2days)
374  * @param int Factor to multiply with the offset
375  * @return int Unix timestamp
376  */
377 function get_offset_time($offset_str, $factor=1)
378   {
379   if (preg_match('/^([0-9]+)\s*([smhdw])/i', $offset_str, $regs))
380   {
381     $amount = (int)$regs[1];
382     $unit = strtolower($regs[2]);
383   }
384   else
385   {
386     $amount = (int)$offset_str;
387     $unit = 's';
388   }
389     
390   $ts = mktime();
391   switch ($unit)
392   {
393     case 'w':
394       $amount *= 7;
395     case 'd':
396       $amount *= 24;
397     case 'h':
398       $amount *= 60;
399     case 'm':
400       $amount *= 60;
401     case 's':
402       $ts += $amount * $factor;
403   }
404
405   return $ts;
406 }
407
408
409 /**
410  * Truncate string if it is longer than the allowed length
411  * Replace the middle or the ending part of a string with a placeholder
412  *
413  * @param string Input string
414  * @param int    Max. length
415  * @param string Replace removed chars with this
416  * @param bool   Set to True if string should be truncated from the end
417  * @return string Abbreviated string
418  */
419 function abbreviate_string($str, $maxlength, $place_holder='...', $ending=false)
420 {
421   $length = mb_strlen($str);
422   
423   if ($length > $maxlength)
424   {
425     if ($ending)
426       return mb_substr($str, 0, $maxlength) . $place_holder;
427
428     $place_holder_length = mb_strlen($place_holder);
429     $first_part_length = floor(($maxlength - $place_holder_length)/2);
430     $second_starting_location = $length - $maxlength + $first_part_length + $place_holder_length;
431     $str = mb_substr($str, 0, $first_part_length) . $place_holder . mb_substr($str, $second_starting_location);
432   }
433
434   return $str;
435 }
436
437 /**
438  * A method to guess the mime_type of an attachment.
439  *
440  * @param string $path      Path to the file.
441  * @param string $name      File name (with suffix)
442  * @param string $failover  Mime type supplied for failover.
443  * @param string $is_stream Set to True if $path contains file body
444  *
445  * @return string
446  * @author Till Klampaeckel <till@php.net>
447  * @see    http://de2.php.net/manual/en/ref.fileinfo.php
448  * @see    http://de2.php.net/mime_content_type
449  */
450 function rc_mime_content_type($path, $name, $failover = 'application/octet-stream', $is_stream=false)
451 {
452     $mime_type = null;
453     $mime_magic = rcmail::get_instance()->config->get('mime_magic');
454     $mime_ext = @include(RCMAIL_CONFIG_DIR . '/mimetypes.php');
455     $suffix = $name ? substr($name, strrpos($name, '.')+1) : '*';
456
457     // use file name suffix with hard-coded mime-type map
458     if (is_array($mime_ext)) {
459         $mime_type = $mime_ext[$suffix];
460     }
461     // try fileinfo extension if available
462     if (!$mime_type && function_exists('finfo_open')) {
463         if ($finfo = finfo_open(FILEINFO_MIME, $mime_magic)) {
464             if ($is_stream)
465                 $mime_type = finfo_buffer($finfo, $path);
466             else
467                 $mime_type = finfo_file($finfo, $path);
468             finfo_close($finfo);
469         }
470     }
471     // try PHP's mime_content_type
472     if (!$mime_type && !$is_stream && function_exists('mime_content_type')) {
473       $mime_type = @mime_content_type($path);
474     }
475     // fall back to user-submitted string
476     if (!$mime_type) {
477         $mime_type = $failover;
478     }
479     else {
480         // Sometimes (PHP-5.3?) content-type contains charset definition,
481         // Remove it (#1487122) also "charset=binary" is useless
482         $mime_type = array_shift(preg_split('/[; ]/', $mime_type));
483     }
484
485     return $mime_type;
486 }
487
488 /**
489  * A method to guess encoding of a string.
490  *
491  * @param string $string        String.
492  * @param string $failover      Default result for failover.
493  *
494  * @return string
495  */
496 function rc_detect_encoding($string, $failover='')
497 {
498     if (!function_exists('mb_detect_encoding')) {
499         return $failover;
500     }
501
502     // FIXME: the order is important, because sometimes 
503     // iso string is detected as euc-jp and etc.
504     $enc = array(
505       'UTF-8', 'SJIS', 'BIG5', 'GB2312',
506       'ISO-8859-1', 'ISO-8859-2', 'ISO-8859-3', 'ISO-8859-4',
507       'ISO-8859-5', 'ISO-8859-6', 'ISO-8859-7', 'ISO-8859-8', 'ISO-8859-9',
508       'ISO-8859-10', 'ISO-8859-13', 'ISO-8859-14', 'ISO-8859-15', 'ISO-8859-16',
509       'WINDOWS-1252', 'WINDOWS-1251', 'EUC-JP', 'EUC-TW', 'KOI8-R', 
510       'ISO-2022-KR', 'ISO-2022-JP'
511     );
512
513     $result = mb_detect_encoding($string, join(',', $enc));
514
515     return $result ? $result : $failover;
516 }
517
518 /**
519  * Removes non-unicode characters from input
520  *
521  * @param mixed $input String or array.
522  * @return string
523  */
524 function rc_utf8_clean($input)
525 {
526   // handle input of type array
527   if (is_array($input)) {
528     foreach ($input as $idx => $val)
529       $input[$idx] = rc_utf8_clean($val);
530     return $input;
531   }
532   
533   if (!is_string($input) || $input == '')
534     return $input;
535
536   // iconv/mbstring are much faster (especially with long strings)
537   if (function_exists('mb_convert_encoding') && ($res = mb_convert_encoding($input, 'UTF-8', 'UTF-8')) !== false)
538     return $res;
539
540   if (function_exists('iconv') && ($res = @iconv('UTF-8', 'UTF-8//IGNORE', $input)) !== false)
541     return $res;
542
543   $regexp = '/^('.
544 //    '[\x00-\x7F]'.                                  // UTF8-1
545     '|[\xC2-\xDF][\x80-\xBF]'.                      // UTF8-2
546     '|\xE0[\xA0-\xBF][\x80-\xBF]'.                  // UTF8-3
547     '|[\xE1-\xEC][\x80-\xBF][\x80-\xBF]'.           // UTF8-3
548     '|\xED[\x80-\x9F][\x80-\xBF]'.                  // UTF8-3
549     '|[\xEE-\xEF][\x80-\xBF][\x80-\xBF]'.           // UTF8-3
550     '|\xF0[\x90-\xBF][\x80-\xBF][\x80-\xBF]'.       // UTF8-4
551     '|[\xF1-\xF3][\x80-\xBF][\x80-\xBF][\x80-\xBF]'.// UTF8-4
552     '|\xF4[\x80-\x8F][\x80-\xBF][\x80-\xBF]'.       // UTF8-4
553     ')$/';
554   
555   $seq = '';
556   $out = '';
557
558   for ($i = 0, $len = strlen($input); $i < $len; $i++) {
559     $chr = $input[$i];
560     $ord = ord($chr);
561     // 1-byte character
562     if ($ord <= 0x7F) {
563       if ($seq)
564         $out .= preg_match($regexp, $seq) ? $seq : '';
565       $seq = '';
566       $out .= $chr;
567     // first (or second) byte of multibyte sequence
568     } else if ($ord >= 0xC0) {
569       if (strlen($seq)>1) {
570         $out .= preg_match($regexp, $seq) ? $seq : '';
571         $seq = '';
572       } else if ($seq && ord($seq) < 0xC0) {
573         $seq = '';
574       }
575       $seq .= $chr;
576     // next byte of multibyte sequence
577     } else if ($seq) {
578       $seq .= $chr;
579     }
580   }
581
582   if ($seq)
583     $out .= preg_match($regexp, $seq) ? $seq : '';
584
585   return $out;
586 }
587
588
589 /**
590  * Convert a variable into a javascript object notation
591  *
592  * @param mixed Input value
593  * @return string Serialized JSON string
594  */
595 function json_serialize($input)
596 {
597   $input = rc_utf8_clean($input);
598
599   // sometimes even using rc_utf8_clean() the input contains invalid UTF-8 sequences
600   // that's why we have @ here
601   return @json_encode($input);
602 }
603
604
605 /**
606  * Explode quoted string
607  * 
608  * @param string Delimiter expression string for preg_match()
609  * @param string Input string
610  */
611 function rcube_explode_quoted_string($delimiter, $string)
612 {
613   $result = array();
614   $strlen = strlen($string);
615
616   for ($q=$p=$i=0; $i < $strlen; $i++) {
617     if ($string[$i] == "\"" && $string[$i-1] != "\\") {
618       $q = $q ? false : true;
619     } 
620     else if (!$q && preg_match("/$delimiter/", $string[$i])) {
621       $result[] = substr($string, $p, $i - $p);
622       $p = $i + 1;
623     }
624   }
625   
626   $result[] = substr($string, $p);
627   return $result;
628 }
629
630
631 /**
632  * Get all keys from array (recursive)
633  * 
634  * @param array Input array
635  * @return array
636  */
637 function array_keys_recursive($array)
638 {
639   $keys = array();
640   
641   if (!empty($array))
642     foreach ($array as $key => $child) {
643       $keys[] = $key;
644       foreach (array_keys_recursive($child) as $val)
645         $keys[] = $val;
646     }
647   return $keys;
648 }
649
650
651 /**
652  * mbstring replacement functions
653  */
654
655 if (!extension_loaded('mbstring'))
656 {
657     function mb_strlen($str)
658     {
659         return strlen($str);
660     }
661
662     function mb_strtolower($str)
663     {
664         return strtolower($str);
665     }
666
667     function mb_strtoupper($str)
668     {
669         return strtoupper($str);
670     }
671
672     function mb_substr($str, $start, $len=null)
673     {
674         return substr($str, $start, $len);
675     }
676
677     function mb_strpos($haystack, $needle, $offset=0)
678     {
679         return strpos($haystack, $needle, $offset);
680     }
681
682     function mb_strrpos($haystack, $needle, $offset=0)
683     {
684         return strrpos($haystack, $needle, $offset);
685     }
686 }
687
688 /**
689  * intl replacement functions
690  */
691
692 if (!function_exists('idn_to_utf8'))
693 {
694     function idn_to_utf8($domain, $flags=null)
695     {
696         static $idn, $loaded;
697
698         if (!$loaded) {
699             $idn = new Net_IDNA2();
700             $loaded = true;
701         }
702
703         if ($idn && $domain && preg_match('/(^|\.)xn--/i', $domain)) {
704             try {
705                 $domain = $idn->decode($domain);
706             }
707             catch (Exception $e) {
708             }
709         }
710         return $domain;
711     }
712 }
713
714 if (!function_exists('idn_to_ascii'))
715 {
716     function idn_to_ascii($domain, $flags=null)
717     {
718         static $idn, $loaded;
719
720         if (!$loaded) {
721             $idn = new Net_IDNA2();
722             $loaded = true;
723         }
724
725         if ($idn && $domain && preg_match('/[^\x20-\x7E]/', $domain)) {
726             try {
727                 $domain = $idn->encode($domain);
728             }
729             catch (Exception $e) {
730             }
731         }
732         return $domain;
733     }
734 }
735