source: subversion/trunk/roundcubemail/program/include/rcube_shared.inc @ 3756

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