1<?php 2/** 3 * File IO functions 4 * 5 * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) 6 * @author Andreas Gohr <andi@splitbrain.org> 7 */ 8use dokuwiki\Utf8\PhpString; 9use dokuwiki\HTTP\DokuHTTPClient; 10use dokuwiki\Extension\Event; 11 12/** 13 * Removes empty directories 14 * 15 * Sends IO_NAMESPACE_DELETED events for 'pages' and 'media' namespaces. 16 * Event data: 17 * $data[0] ns: The colon separated namespace path minus the trailing page name. 18 * $data[1] ns_type: 'pages' or 'media' namespace tree. 19 * 20 * @param string $id - a pageid, the namespace of that id will be tried to deleted 21 * @param string $basedir - the config name of the type to delete (datadir or mediadir usally) 22 * @return bool - true if at least one namespace was deleted 23 * 24 * @author Andreas Gohr <andi@splitbrain.org> 25 * @author Ben Coburn <btcoburn@silicodon.net> 26 */ 27function io_sweepNS($id, $basedir = 'datadir') 28{ 29 global $conf; 30 $types = ['datadir' => 'pages', 'mediadir' => 'media']; 31 $ns_type = ($types[$basedir] ?? false); 32 33 $delone = false; 34 35 //scan all namespaces 36 while (($id = getNS($id)) !== false) { 37 $dir = $conf[$basedir] . '/' . utf8_encodeFN(str_replace(':', '/', $id)); 38 39 //try to delete dir else return 40 if (@rmdir($dir)) { 41 if ($ns_type !== false) { 42 $data = [$id, $ns_type]; 43 $delone = true; // we deleted at least one dir 44 Event::createAndTrigger('IO_NAMESPACE_DELETED', $data); 45 } 46 } else { 47 return $delone; } 48 } 49 return $delone; 50} 51 52/** 53 * Used to read in a DokuWiki page from file, and send IO_WIKIPAGE_READ events. 54 * 55 * Generates the action event which delegates to io_readFile(). 56 * Action plugins are allowed to modify the page content in transit. 57 * The file path should not be changed. 58 * 59 * Event data: 60 * $data[0] The raw arguments for io_readFile as an array. 61 * $data[1] ns: The colon separated namespace path minus the trailing page name. (false if root ns) 62 * $data[2] page_name: The wiki page name. 63 * $data[3] rev: The page revision, false for current wiki pages. 64 * 65 * @author Ben Coburn <btcoburn@silicodon.net> 66 * 67 * @param string $file filename 68 * @param string $id page id 69 * @param bool|int $rev revision timestamp 70 * @return string 71 */ 72function io_readWikiPage($file, $id, $rev = false) 73{ 74 if (empty($rev)) { 75 $rev = false; } 76 $data = [[$file, true], getNS($id), noNS($id), $rev]; 77 return Event::createAndTrigger('IO_WIKIPAGE_READ', $data, '_io_readWikiPage_action', false); 78} 79 80/** 81 * Callback adapter for io_readFile(). 82 * 83 * @author Ben Coburn <btcoburn@silicodon.net> 84 * 85 * @param array $data event data 86 * @return string 87 */ 88function _io_readWikiPage_action($data) 89{ 90 if (is_array($data) && is_array($data[0]) && count($data[0]) === 2) { 91 return io_readFile(...$data[0]); 92 } else { 93 return ''; //callback error 94 } 95} 96 97/** 98 * Returns content of $file as cleaned string. 99 * 100 * Uses gzip if extension is .gz 101 * 102 * If you want to use the returned value in unserialize 103 * be sure to set $clean to false! 104 * 105 * @author Andreas Gohr <andi@splitbrain.org> 106 * 107 * @param string $file filename 108 * @param bool $clean 109 * @return string|bool the file contents or false on error 110 */ 111function io_readFile($file, $clean = true) 112{ 113 $ret = ''; 114 if (file_exists($file)) { 115 if (substr($file, -3) == '.gz') { 116 if (!DOKU_HAS_GZIP) return false; 117 $ret = gzfile($file); 118 if (is_array($ret)) $ret = implode('', $ret); 119 } elseif (substr($file, -4) == '.bz2') { 120 if (!DOKU_HAS_BZIP) return false; 121 $ret = bzfile($file); 122 } else { 123 $ret = file_get_contents($file); 124 } 125 } 126 if ($ret === null) return false; 127 if ($ret !== false && $clean) { 128 return cleanText($ret); 129 } else { 130 return $ret; 131 } 132} 133/** 134 * Returns the content of a .bz2 compressed file as string 135 * 136 * @author marcel senf <marcel@rucksackreinigung.de> 137 * @author Andreas Gohr <andi@splitbrain.org> 138 * 139 * @param string $file filename 140 * @param bool $array return array of lines 141 * @return string|array|bool content or false on error 142 */ 143function bzfile($file, $array = false) 144{ 145 $bz = bzopen($file, "r"); 146 if ($bz === false) return false; 147 148 if ($array) $lines = []; 149 $str = ''; 150 while (!feof($bz)) { 151 //8192 seems to be the maximum buffersize? 152 $buffer = bzread($bz, 8192); 153 if (($buffer === false) || (bzerrno($bz) !== 0)) { 154 return false; 155 } 156 $str .= $buffer; 157 if ($array) { 158 $pos = strpos($str, "\n"); 159 while ($pos !== false) { 160 $lines[] = substr($str, 0, $pos + 1); 161 $str = substr($str, $pos + 1); 162 $pos = strpos($str, "\n"); 163 } 164 } 165 } 166 bzclose($bz); 167 if ($array) { 168 if ($str !== '') $lines[] = $str; 169 return $lines; 170 } 171 return $str; 172} 173 174/** 175 * Used to write out a DokuWiki page to file, and send IO_WIKIPAGE_WRITE events. 176 * 177 * This generates an action event and delegates to io_saveFile(). 178 * Action plugins are allowed to modify the page content in transit. 179 * The file path should not be changed. 180 * (The append parameter is set to false.) 181 * 182 * Event data: 183 * $data[0] The raw arguments for io_saveFile as an array. 184 * $data[1] ns: The colon separated namespace path minus the trailing page name. (false if root ns) 185 * $data[2] page_name: The wiki page name. 186 * $data[3] rev: The page revision, false for current wiki pages. 187 * 188 * @author Ben Coburn <btcoburn@silicodon.net> 189 * 190 * @param string $file filename 191 * @param string $content 192 * @param string $id page id 193 * @param int|bool $rev timestamp of revision 194 * @return bool 195 */ 196function io_writeWikiPage($file, $content, $id, $rev = false) 197{ 198 if (empty($rev)) { 199 $rev = false; } 200 if ($rev === false) { 201 io_createNamespace($id); } // create namespaces as needed 202 $data = [[$file, $content, false], getNS($id), noNS($id), $rev]; 203 return Event::createAndTrigger('IO_WIKIPAGE_WRITE', $data, '_io_writeWikiPage_action', false); 204} 205 206/** 207 * Callback adapter for io_saveFile(). 208 * @author Ben Coburn <btcoburn@silicodon.net> 209 * 210 * @param array $data event data 211 * @return bool 212 */ 213function _io_writeWikiPage_action($data) 214{ 215 if (is_array($data) && is_array($data[0]) && count($data[0]) === 3) { 216 $ok = io_saveFile(...$data[0]); 217 // for attic files make sure the file has the mtime of the revision 218 if ($ok && is_int($data[3]) && $data[3] > 0) { 219 @touch($data[0][0], $data[3]); 220 } 221 return $ok; 222 } else { 223 return false; //callback error 224 } 225} 226 227/** 228 * Internal function to save contents to a file. 229 * 230 * @author Andreas Gohr <andi@splitbrain.org> 231 * 232 * @param string $file filename path to file 233 * @param string $content 234 * @param bool $append 235 * @return bool true on success, otherwise false 236 */ 237function _io_saveFile($file, $content, $append) 238{ 239 global $conf; 240 $mode = ($append) ? 'ab' : 'wb'; 241 $fileexists = file_exists($file); 242 243 if (substr($file, -3) == '.gz') { 244 if (!DOKU_HAS_GZIP) return false; 245 $fh = @gzopen($file, $mode . '9'); 246 if (!$fh) return false; 247 gzwrite($fh, $content); 248 gzclose($fh); 249 } elseif (substr($file, -4) == '.bz2') { 250 if (!DOKU_HAS_BZIP) return false; 251 if ($append) { 252 $bzcontent = bzfile($file); 253 if ($bzcontent === false) return false; 254 $content = $bzcontent . $content; 255 } 256 $fh = @bzopen($file, 'w'); 257 if (!$fh) return false; 258 bzwrite($fh, $content); 259 bzclose($fh); 260 } else { 261 $fh = @fopen($file, $mode); 262 if (!$fh) return false; 263 fwrite($fh, $content); 264 fclose($fh); 265 } 266 267 if (!$fileexists && $conf['fperm']) chmod($file, $conf['fperm']); 268 return true; 269} 270 271/** 272 * Saves $content to $file. 273 * 274 * If the third parameter is set to true the given content 275 * will be appended. 276 * 277 * Uses gzip if extension is .gz 278 * and bz2 if extension is .bz2 279 * 280 * @author Andreas Gohr <andi@splitbrain.org> 281 * 282 * @param string $file filename path to file 283 * @param string $content 284 * @param bool $append 285 * @return bool true on success, otherwise false 286 */ 287function io_saveFile($file, $content, $append = false) 288{ 289 io_makeFileDir($file); 290 io_lock($file); 291 if (!_io_saveFile($file, $content, $append)) { 292 msg("Writing $file failed", -1); 293 io_unlock($file); 294 return false; 295 } 296 io_unlock($file); 297 return true; 298} 299 300/** 301 * Replace one or more occurrences of a line in a file. 302 * 303 * The default, when $maxlines is 0 is to delete all matching lines then append a single line. 304 * A regex that matches any part of the line will remove the entire line in this mode. 305 * Captures in $newline are not available. 306 * 307 * Otherwise each line is matched and replaced individually, up to the first $maxlines lines 308 * or all lines if $maxlines is -1. If $regex is true then captures can be used in $newline. 309 * 310 * Be sure to include the trailing newline in $oldline when replacing entire lines. 311 * 312 * Uses gzip if extension is .gz 313 * and bz2 if extension is .bz2 314 * 315 * @author Steven Danz <steven-danz@kc.rr.com> 316 * @author Christopher Smith <chris@jalakai.co.uk> 317 * @author Patrick Brown <ptbrown@whoopdedo.org> 318 * 319 * @param string $file filename 320 * @param string $oldline exact linematch to remove 321 * @param string $newline new line to insert 322 * @param bool $regex use regexp? 323 * @param int $maxlines number of occurrences of the line to replace 324 * @return bool true on success 325 */ 326function io_replaceInFile($file, $oldline, $newline, $regex = false, $maxlines = 0) 327{ 328 if ((string)$oldline === '') { 329 trigger_error('$oldline parameter cannot be empty in io_replaceInFile()', E_USER_WARNING); 330 return false; 331 } 332 333 if (!file_exists($file)) return true; 334 335 io_lock($file); 336 337 // load into array 338 if (substr($file, -3) == '.gz') { 339 if (!DOKU_HAS_GZIP) return false; 340 $lines = gzfile($file); 341 } elseif (substr($file, -4) == '.bz2') { 342 if (!DOKU_HAS_BZIP) return false; 343 $lines = bzfile($file, true); 344 } else { 345 $lines = file($file); 346 } 347 348 // make non-regexes into regexes 349 $pattern = $regex ? $oldline : '/^' . preg_quote($oldline, '/') . '$/'; 350 $replace = $regex ? $newline : addcslashes($newline, '\$'); 351 352 // remove matching lines 353 if ($maxlines > 0) { 354 $count = 0; 355 $matched = 0; 356 foreach ($lines as $i => $line) { 357 if ($count >= $maxlines) break; 358 // $matched will be set to 0|1 depending on whether pattern is matched and line replaced 359 $lines[$i] = preg_replace($pattern, $replace, $line, -1, $matched); 360 if ($matched) $count++; 361 } 362 } elseif ($maxlines == 0) { 363 $lines = preg_grep($pattern, $lines, PREG_GREP_INVERT); 364 if ((string)$newline !== '') { 365 $lines[] = $newline; 366 } 367 } else { 368 $lines = preg_replace($pattern, $replace, $lines); 369 } 370 371 if (count($lines)) { 372 if (!_io_saveFile($file, implode('', $lines), false)) { 373 msg("Removing content from $file failed", -1); 374 io_unlock($file); 375 return false; 376 } 377 } else { 378 @unlink($file); 379 } 380 381 io_unlock($file); 382 return true; 383} 384 385/** 386 * Delete lines that match $badline from $file. 387 * 388 * Be sure to include the trailing newline in $badline 389 * 390 * @author Patrick Brown <ptbrown@whoopdedo.org> 391 * 392 * @param string $file filename 393 * @param string $badline exact linematch to remove 394 * @param bool $regex use regexp? 395 * @return bool true on success 396 */ 397function io_deleteFromFile($file, $badline, $regex = false) 398{ 399 return io_replaceInFile($file, $badline, null, $regex, 0); 400} 401 402/** 403 * Tries to lock a file 404 * 405 * Locking is only done for io_savefile and uses directories 406 * inside $conf['lockdir'] 407 * 408 * It waits maximal 3 seconds for the lock, after this time 409 * the lock is assumed to be stale and the function goes on 410 * 411 * @author Andreas Gohr <andi@splitbrain.org> 412 * 413 * @param string $file filename 414 */ 415function io_lock($file) 416{ 417 global $conf; 418 419 $lockDir = $conf['lockdir'] . '/' . md5($file); 420 @ignore_user_abort(1); 421 422 $timeStart = time(); 423 do { 424 //waited longer than 3 seconds? -> stale lock 425 if ((time() - $timeStart) > 3) break; 426 $locked = @mkdir($lockDir); 427 if ($locked) { 428 if ($conf['dperm']) chmod($lockDir, $conf['dperm']); 429 break; 430 } 431 usleep(50); 432 } while ($locked === false); 433} 434 435/** 436 * Unlocks a file 437 * 438 * @author Andreas Gohr <andi@splitbrain.org> 439 * 440 * @param string $file filename 441 */ 442function io_unlock($file) 443{ 444 global $conf; 445 446 $lockDir = $conf['lockdir'] . '/' . md5($file); 447 @rmdir($lockDir); 448 @ignore_user_abort(0); 449} 450 451/** 452 * Create missing namespace directories and send the IO_NAMESPACE_CREATED events 453 * in the order of directory creation. (Parent directories first.) 454 * 455 * Event data: 456 * $data[0] ns: The colon separated namespace path minus the trailing page name. 457 * $data[1] ns_type: 'pages' or 'media' namespace tree. 458 * 459 * @author Ben Coburn <btcoburn@silicodon.net> 460 * 461 * @param string $id page id 462 * @param string $ns_type 'pages' or 'media' 463 */ 464function io_createNamespace($id, $ns_type = 'pages') 465{ 466 // verify ns_type 467 $types = ['pages' => 'wikiFN', 'media' => 'mediaFN']; 468 if (!isset($types[$ns_type])) { 469 trigger_error('Bad $ns_type parameter for io_createNamespace().'); 470 return; 471 } 472 // make event list 473 $missing = []; 474 $ns_stack = explode(':', $id); 475 $ns = $id; 476 $tmp = dirname($file = call_user_func($types[$ns_type], $ns)); 477 while (!@is_dir($tmp) && !(file_exists($tmp) && !is_dir($tmp))) { 478 array_pop($ns_stack); 479 $ns = implode(':', $ns_stack); 480 if (strlen($ns) == 0) { 481 break; } 482 $missing[] = $ns; 483 $tmp = dirname(call_user_func($types[$ns_type], $ns)); 484 } 485 // make directories 486 io_makeFileDir($file); 487 // send the events 488 $missing = array_reverse($missing); // inside out 489 foreach ($missing as $ns) { 490 $data = [$ns, $ns_type]; 491 Event::createAndTrigger('IO_NAMESPACE_CREATED', $data); 492 } 493} 494 495/** 496 * Create the directory needed for the given file 497 * 498 * @author Andreas Gohr <andi@splitbrain.org> 499 * 500 * @param string $file file name 501 */ 502function io_makeFileDir($file) 503{ 504 $dir = dirname($file); 505 if (!@is_dir($dir)) { 506 io_mkdir_p($dir) || msg("Creating directory $dir failed", -1); 507 } 508} 509 510/** 511 * Creates a directory hierachy. 512 * 513 * @link http://php.net/manual/en/function.mkdir.php 514 * @author <saint@corenova.com> 515 * @author Andreas Gohr <andi@splitbrain.org> 516 * 517 * @param string $target filename 518 * @return bool|int|string 519 */ 520function io_mkdir_p($target) 521{ 522 global $conf; 523 if (@is_dir($target) || empty($target)) return 1; // best case check first 524 if (file_exists($target) && !is_dir($target)) return 0; 525 //recursion 526 if (io_mkdir_p(substr($target, 0, strrpos($target, '/')))) { 527 $ret = @mkdir($target); // crawl back up & create dir tree 528 if ($ret && !empty($conf['dperm'])) chmod($target, $conf['dperm']); 529 return $ret; 530 } 531 return 0; 532} 533 534/** 535 * Recursively delete a directory 536 * 537 * @author Andreas Gohr <andi@splitbrain.org> 538 * @param string $path 539 * @param bool $removefiles defaults to false which will delete empty directories only 540 * @return bool 541 */ 542function io_rmdir($path, $removefiles = false) 543{ 544 if (!is_string($path) || $path == "") return false; 545 if (!file_exists($path)) return true; // it's already gone or was never there, count as success 546 547 if (is_dir($path) && !is_link($path)) { 548 $dirs = []; 549 $files = []; 550 if (!$dh = @opendir($path)) return false; 551 while (false !== ($f = readdir($dh))) { 552 if ($f == '..' || $f == '.') continue; 553 554 // collect dirs and files first 555 if (is_dir("$path/$f") && !is_link("$path/$f")) { 556 $dirs[] = "$path/$f"; 557 } elseif ($removefiles) { 558 $files[] = "$path/$f"; 559 } else { 560 return false; // abort when non empty 561 } 562 } 563 closedir($dh); 564 // now traverse into directories first 565 foreach ($dirs as $dir) { 566 if (!io_rmdir($dir, $removefiles)) return false; // abort on any error 567 } 568 // now delete files 569 foreach ($files as $file) { 570 if (!@unlink($file)) return false; //abort on any error 571 } 572 // remove self 573 return @rmdir($path); 574 } elseif ($removefiles) { 575 return @unlink($path); 576 } 577 return false; 578} 579 580/** 581 * Creates a unique temporary directory and returns 582 * its path. 583 * 584 * @author Michael Klier <chi@chimeric.de> 585 * 586 * @return false|string path to new directory or false 587 */ 588function io_mktmpdir() 589{ 590 global $conf; 591 592 $base = $conf['tmpdir']; 593 $dir = md5(uniqid(random_int(0, mt_getrandmax()), true)); 594 $tmpdir = $base . '/' . $dir; 595 596 if (io_mkdir_p($tmpdir)) { 597 return($tmpdir); 598 } else { 599 return false; 600 } 601} 602 603/** 604 * downloads a file from the net and saves it 605 * 606 * if $useAttachment is false, 607 * - $file is the full filename to save the file, incl. path 608 * - if successful will return true, false otherwise 609 * 610 * if $useAttachment is true, 611 * - $file is the directory where the file should be saved 612 * - if successful will return the name used for the saved file, false otherwise 613 * 614 * @author Andreas Gohr <andi@splitbrain.org> 615 * @author Chris Smith <chris@jalakai.co.uk> 616 * 617 * @param string $url url to download 618 * @param string $file path to file or directory where to save 619 * @param bool $useAttachment true: try to use name of download, uses otherwise $defaultName 620 * false: uses $file as path to file 621 * @param string $defaultName fallback for if using $useAttachment 622 * @param int $maxSize maximum file size 623 * @return bool|string if failed false, otherwise true or the name of the file in the given dir 624 */ 625function io_download($url, $file, $useAttachment = false, $defaultName = '', $maxSize = 2_097_152) 626{ 627 global $conf; 628 $http = new DokuHTTPClient(); 629 $http->max_bodysize = $maxSize; 630 $http->timeout = 25; //max. 25 sec 631 $http->keep_alive = false; // we do single ops here, no need for keep-alive 632 633 $data = $http->get($url); 634 if (!$data) return false; 635 636 $name = ''; 637 if ($useAttachment) { 638 if (isset($http->resp_headers['content-disposition'])) { 639 $content_disposition = $http->resp_headers['content-disposition']; 640 $match = []; 641 if ( 642 is_string($content_disposition) && 643 preg_match('/attachment;\s*filename\s*=\s*"([^"]*)"/i', $content_disposition, $match) 644 ) { 645 $name = PhpString::basename($match[1]); 646 } 647 } 648 649 if (!$name) { 650 if (!$defaultName) return false; 651 $name = $defaultName; 652 } 653 654 $file .= $name; 655 } 656 657 $fileexists = file_exists($file); 658 $fp = @fopen($file, "w"); 659 if (!$fp) return false; 660 fwrite($fp, $data); 661 fclose($fp); 662 if (!$fileexists && $conf['fperm']) chmod($file, $conf['fperm']); 663 if ($useAttachment) return $name; 664 return true; 665} 666 667/** 668 * Windows compatible rename 669 * 670 * rename() can not overwrite existing files on Windows 671 * this function will use copy/unlink instead 672 * 673 * @param string $from 674 * @param string $to 675 * @return bool succes or fail 676 */ 677function io_rename($from, $to) 678{ 679 global $conf; 680 if (!@rename($from, $to)) { 681 if (@copy($from, $to)) { 682 if ($conf['fperm']) chmod($to, $conf['fperm']); 683 @unlink($from); 684 return true; 685 } 686 return false; 687 } 688 return true; 689} 690 691/** 692 * Runs an external command with input and output pipes. 693 * Returns the exit code from the process. 694 * 695 * @author Tom N Harris <tnharris@whoopdedo.org> 696 * 697 * @param string $cmd 698 * @param string $input input pipe 699 * @param string $output output pipe 700 * @return int exit code from process 701 */ 702function io_exec($cmd, $input, &$output) 703{ 704 $descspec = [ 705 0 => ["pipe", "r"], 706 1 => ["pipe", "w"], 707 2 => ["pipe", "w"] 708 ]; 709 $ph = proc_open($cmd, $descspec, $pipes); 710 if (!$ph) return -1; 711 fclose($pipes[2]); // ignore stderr 712 fwrite($pipes[0], $input); 713 fclose($pipes[0]); 714 $output = stream_get_contents($pipes[1]); 715 fclose($pipes[1]); 716 return proc_close($ph); 717} 718 719/** 720 * Search a file for matching lines 721 * 722 * This is probably not faster than file()+preg_grep() but less 723 * memory intensive because not the whole file needs to be loaded 724 * at once. 725 * 726 * @author Andreas Gohr <andi@splitbrain.org> 727 * @param string $file The file to search 728 * @param string $pattern PCRE pattern 729 * @param int $max How many lines to return (0 for all) 730 * @param bool $backref When true returns array with backreferences instead of lines 731 * @return array matching lines or backref, false on error 732 */ 733function io_grep($file, $pattern, $max = 0, $backref = false) 734{ 735 $fh = @fopen($file, 'r'); 736 if (!$fh) return false; 737 $matches = []; 738 739 $cnt = 0; 740 $line = ''; 741 while (!feof($fh)) { 742 $line .= fgets($fh, 4096); // read full line 743 if (substr($line, -1) != "\n") continue; 744 745 // check if line matches 746 if (preg_match($pattern, $line, $match)) { 747 if ($backref) { 748 $matches[] = $match; 749 } else { 750 $matches[] = $line; 751 } 752 $cnt++; 753 } 754 if ($max && $max == $cnt) break; 755 $line = ''; 756 } 757 fclose($fh); 758 return $matches; 759} 760 761 762/** 763 * Get size of contents of a file, for a compressed file the uncompressed size 764 * Warning: reading uncompressed size of content of bz-files requires uncompressing 765 * 766 * @author Gerrit Uitslag <klapinklapin@gmail.com> 767 * 768 * @param string $file filename path to file 769 * @return int size of file 770 */ 771function io_getSizeFile($file) 772{ 773 if (!file_exists($file)) return 0; 774 775 if (substr($file, -3) == '.gz') { 776 $fp = @fopen($file, "rb"); 777 if ($fp === false) return 0; 778 fseek($fp, -4, SEEK_END); 779 $buffer = fread($fp, 4); 780 fclose($fp); 781 $array = unpack("V", $buffer); 782 $uncompressedsize = end($array); 783 } elseif (substr($file, -4) == '.bz2') { 784 if (!DOKU_HAS_BZIP) return 0; 785 $bz = bzopen($file, "r"); 786 if ($bz === false) return 0; 787 $uncompressedsize = 0; 788 while (!feof($bz)) { 789 //8192 seems to be the maximum buffersize? 790 $buffer = bzread($bz, 8192); 791 if (($buffer === false) || (bzerrno($bz) !== 0)) { 792 return 0; 793 } 794 $uncompressedsize += strlen($buffer); 795 } 796 } else { 797 $uncompressedsize = filesize($file); 798 } 799 800 return $uncompressedsize; 801} 802