3 * This script tries to find the temp folder and unzip all setup files into.
6 * @copyright 2001-2011 WoltLab GmbH
7 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
10 define('INSTALL_SCRIPT_DIR', dirname(__FILE__
).'/');
11 define('SETUP_FILE', INSTALL_SCRIPT_DIR
. 'WCFSetup.tar.gz');
12 define('NO_IMPORTS', 1);
14 // set exception handler
15 set_exception_handler('handleException');
16 // set php error handler
17 set_error_handler('handleError', E_ALL
);
19 // define list of needed file
20 $neededFilesPattern = array(
22 '!^install/files/acp/images/wcfLogo.*!',
23 '!^install/files/acp/style/setup/.*!',
24 '!^install/files/lib/data/.*!',
25 '!^install/files/icon/.*!',
26 '!^install/files/lib/system/.*!',
27 '!^install/files/lib/util/.*!',
29 '!^install/packages/.*!');
31 // define needed functions and classes
33 * WCF::handleException() calls the show method on exceptions that implement this interface.
35 * @package com.woltlab.wcf.system.exception
38 interface IPrintableException
{
39 public function show();
42 // define needed classes
44 // SystemException, PrintableException, BasicFileUtil, Tar, File, ZipFile
46 * A SystemException is thrown when an unexpected error occurs.
48 * @package com.woltlab.wcf.system.exception
51 class SystemException
extends \Exception
implements IPrintableException
{
52 protected $description;
53 protected $information = '';
54 protected $functions = '';
57 * Creates a new SystemException.
59 * @param message string error message
60 * @param code integer error code
61 * @param description string description of the error
63 public function __construct($message = '', $code = 0, $description = '') {
64 parent
::__construct($message, $code);
65 $this->description
= $description;
69 * Returns the description of this exception.
73 public function getDescription() {
74 return $this->description
;
78 * Prints this exception.
79 * This method is called by WCF::handleException().
81 public function show() {
85 <title
>Fatal error
: <?php
echo htmlspecialchars($this->getMessage()); ?
></title
>
87 <style type
="text/css">
89 font
-family
: Verdana
, Helvetica
, sans
-serif
;
93 border
: 1px outset lightgrey
;
95 background
-color
: lightgrey
;
99 border
: 1px inset lightgrey
;
104 background
-color
: #154268;
123 <h1
>Fatal error
: <?php
echo htmlspecialchars($this->getMessage()); ?
></h1
>
126 <p
><?php
echo $this->getDescription(); ?
></p
>
127 <?php
if ($this->getCode()) { ?
><p
>You get more information about the problem in our knowledge base
: <a href
="http://www.woltlab.com/help/?code=<?php echo intval($this->getCode()); ?>">http
://www.woltlab.com/help/?code=<?php echo intval($this->getCode()); ?></a></p><?php } ?>
129 <h2
>Information
:</h2
>
131 <b
>error message
:</b
> <?php
echo htmlspecialchars($this->getMessage()); ?
><br
/>
132 <b
>error code
:</b
> <?php
echo intval($this->getCode()); ?
><br
/>
133 <?php
echo $this->information
; ?
>
134 <b
>file
:</b
> <?php
echo htmlspecialchars($this->getFile()); ?
> (<?php
echo $this->getLine(); ?
>)<br
/>
135 <b
>php version
:</b
> <?php
echo htmlspecialchars(phpversion()); ?
><br
/>
136 <b
>wcf version
:</b
> <?php
if (defined('WCF_VERSION')) echo WCF_VERSION
; ?
><br
/>
137 <b
>date
:</b
> <?php
echo gmdate('r'); ?
><br
/>
138 <b
>request
:</b
> <?php
if (isset($_SERVER['REQUEST_URI'])) echo htmlspecialchars($_SERVER['REQUEST_URI']); ?
><br
/>
139 <b
>referer
:</b
> <?php
if (isset($_SERVER['HTTP_REFERER'])) echo htmlspecialchars($_SERVER['HTTP_REFERER']); ?
><br
/>
143 <pre
><?php
echo htmlspecialchars($this->getTraceAsString()); ?
></pre
>
146 <?php
echo $this->functions
; ?
>
157 * Loads the required classes automatically.
159 function __autoload($className) {
160 $namespaces = explode('\\', $className);
161 if (count($namespaces) > 1) {
162 // remove 'wcf' component
163 array_shift($namespaces);
165 $className = implode('/', $namespaces);
166 $classPath = TMP_DIR
. 'install/files/lib/' . $className . '.class.php';
167 if (file_exists($classPath)) {
168 require_once($classPath);
174 * Escapes strings for execution in sql queries.
176 function escapeString($string) {
177 return \wcf\system\WCF
::getDB()->escapeString($string);
181 * Calls the show method on the given exception.
183 * @param Exception $e
185 function handleException(\Exception
$e) {
186 if ($e instanceof IPrintableException ||
$e instanceof \wcf\system\exception\IPrintableException
) {
195 * Catches php errors and throws instead a system exception.
197 * @param integer $errorNo
198 * @param string $message
199 * @param string $filename
200 * @param integer $lineNo
202 function handleError($errorNo, $message, $filename, $lineNo) {
203 if (error_reporting() != 0) {
206 case 2: $type = 'warning';
208 case 8: $type = 'notice';
212 throw new SystemException('PHP '.$type.' in file '.$filename.' ('.$lineNo.'): '.$message, 0);
217 * BasicFileUtil contains file-related functions.
219 * @package com.woltlab.wcf.util
220 * @author Marcel Werk
222 class BasicFileUtil
{
224 * Tries to find the temp folder.
228 public static function getTempFolder() {
229 $tmpDirName = TMP_FILE_PREFIX
.'/';
231 // use tmp folder in document root by default
232 if (!empty($_SERVER['DOCUMENT_ROOT'])) {
233 if (!@file_exists
($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName)) {
234 @mkdir
($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName, 0777, true);
235 @chmod
($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName, 0777);
238 if (@file_exists
($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName) && @is_writable
($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName)) {
239 return $_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName;
243 foreach (array('TMP', 'TEMP', 'TMPDIR') as $tmpDir) {
244 if (isset($_ENV[$tmpDir]) && @is_writable
($_ENV[$tmpDir])) {
245 $dir = $_ENV[$tmpDir] . '/' . $tmpDirName;
249 if (@file_exists
($dir) && @is_writable
($dir)) {
255 $dir = INSTALL_SCRIPT_DIR
. 'tmp/' . $tmpDirName;
259 if (!@file_exists
($dir) ||
!@is_writable
($dir)) {
260 $tmpDir = explode('/', $dir);
262 $dir = implode('/', $tmpDir);
264 throw new SystemException('There is no access to the system temporary folder due to an unknown reason and no user specific temporary folder exists in '.INSTALL_SCRIPT_DIR
.'! This is a misconfiguration of your webserver software! Please create a folder called '.$dir.' using your favorite ftp program, make it writable and then retry this installation.');
272 * Opens tar or tar.gz archives.
276 * $tar = new Tar('archive.tar');
277 * $contentList = $tar->getContentList();
278 * foreach ($contentList as $key => $val) {
279 * $tar->extract($key, DESTINATION);
283 protected $archiveName = '';
284 protected $contentList = array();
285 protected $opened = false;
286 protected $read = false;
287 protected $file = null;
288 protected $isZipped = false;
289 protected $mode = 'rb';
292 * Creates a new Tar object.
293 * archiveName must be tarball or gzipped tarball
295 * @param string $archiveName
297 public function __construct($archiveName) {
298 if (!is_file($archiveName)) {
299 throw new SystemException("unable to find tar archive '".$archiveName."'");
302 $this->archiveName
= $archiveName;
304 $this->readContent();
308 * Destructor of this class, closes tar archive.
310 public function __destruct() {
315 * Opens the tar archive and stores filehandle.
317 public function open() {
318 if (!$this->opened
) {
319 if ($this->isZipped
) $this->file
= new ZipFile($this->archiveName
, $this->mode
);
322 $this->file
= new File($this->archiveName
, $this->mode
);
323 if ($this->file
->read(2) == "\37\213") {
324 $this->file
->close();
325 $this->isZipped
= true;
326 $this->file
= new ZipFile($this->archiveName
, $this->mode
);
329 $this->file
->seek(0);
332 $this->opened
= true;
337 * Closes the opened file.
339 public function close() {
341 $this->file
->close();
342 $this->opened
= false;
347 * Returns the table of contents (TOC) list for this tar archive.
349 * @return array list of content
351 public function getContentList() {
354 $this->readContent();
356 return $this->contentList
;
360 * Returns an associative array with information
361 * about a specific file in the archive.
363 * @param mixed $fileindex index or name of the requested file
364 * @return array $fileInfo
366 public function getFileInfo($fileIndex) {
367 if (!is_int($fileIndex)) {
368 $fileIndex = $this->getIndexByFilename($fileIndex);
371 if (!isset($this->contentList
[$fileIndex])) {
372 throw new SystemException("Tar: could find file '".$fileIndex."' in archive");
374 return $this->contentList
[$fileIndex];
378 * Searchs a file in the tar archive
379 * and returns the numeric fileindex.
380 * Returns false if not found.
382 * @param string $filename
383 * @return integer index of the requested file
385 public function getIndexByFilename($filename) {
386 foreach ($this->contentList
as $index => $file) {
387 if ($file['filename'] == $filename) {
395 * Extracts a specific file and returns the content as string.
396 * Returns false if extraction failed.
398 * @param mixed $index index or name of the requested file
399 * @return string content of the requested file
401 public function extractToString($index) {
404 $this->readContent();
406 $header = $this->getFileInfo($index);
408 // can not extract a folder
409 if ($header['type'] != 'file') {
414 $this->file
->seek($header['offset']);
418 $n = floor($header['size'] / 512);
419 for($i = 0; $i < $n; $i++
) {
420 $content .= $this->file
->read(512);
422 if(($header['size'] %
512) != 0) {
423 $buffer = $this->file
->read(512);
424 $content .= substr($buffer, 0, ($header['size'] %
512));
431 * Extracts a specific file and writes it's content
432 * to the file specified with $destination.
434 * @param mixed $index index or name of the requested file
435 * @param string $destination
436 * @return boolean $success
438 public function extract($index, $destination) {
441 $this->readContent();
443 $header = $this->getFileInfo($index);
445 // can not extract a folder
446 if ($header['type'] != 'file') {
451 $this->file
->seek($header['offset']);
453 $targetFile = new File($destination);
456 $n = floor($header['size'] / 512);
457 for ($i = 0; $i < $n; $i++
) {
458 $content = $this->file
->read(512);
459 $targetFile->write($content, 512);
461 if (($header['size'] %
512) != 0) {
462 $content = $this->file
->read(512);
463 $targetFile->write($content, ($header['size'] %
512));
466 $targetFile->close();
467 if (function_exists('apache_get_version') ||
!@$targetFile->is_writable()) {
468 @$targetFile->chmod(0777);
471 @$targetFile->chmod(0755);
474 if ($header['mtime']) {
475 @$targetFile->touch($header['mtime']);
479 if (filesize($destination) != $header['size']) {
480 throw new SystemException("Could not untar file '".$header['filename']."' to '".$destination."'. Maybe disk quota exceeded in folder '".dirname($destination)."'.");
487 * Reads table of contents (TOC) from tar archive.
488 * This does not get the entire to memory but only parts of it.
490 protected function readContent() {
491 $this->contentList
= array();
495 // Read the 512 bytes header
496 while (strlen($binaryData = $this->file
->read(512)) != 0) {
498 $header = $this->readHeader($binaryData);
499 if ($header === false) {
502 $this->contentList
[$i] = $header;
503 $this->contentList
[$i]['index'] = $i;
506 $this->file
->seek($this->file
->tell() +
(512 * ceil(($header['size'] / 512))));
511 * Unpacks file header for one file entry.
513 * @param string $binaryData
514 * @return array $fileheader
516 protected function readHeader($binaryData) {
517 if (strlen($binaryData) != 512) {
523 // First part of the header
524 for ($i = 0; $i < 148; $i++
) {
525 $checksum +
= ord(substr($binaryData, $i, 1));
527 // Calculate the checksum
528 // Ignore the checksum value and replace it by ' ' (space)
529 for ($i = 148; $i < 156; $i++
) {
530 $checksum +
= ord(' ');
532 // Last part of the header
533 for ($i = 156; $i < 512; $i++
) {
534 $checksum +
= ord(substr($binaryData, $i, 1));
537 // Extract the values
538 //$data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", $binaryData);
539 $data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix", $binaryData);
541 // Extract the properties
542 $header['checksum'] = octDec(trim($data['checksum']));
543 if ($header['checksum'] == $checksum) {
544 $header['filename'] = trim($data['filename']);
545 $header['mode'] = octDec(trim($data['mode']));
546 $header['uid'] = octDec(trim($data['uid']));
547 $header['gid'] = octDec(trim($data['gid']));
548 $header['size'] = octDec(trim($data['size']));
549 $header['mtime'] = octDec(trim($data['mtime']));
550 $header['prefix'] = trim($data['prefix']);
551 if ($header['prefix']) {
552 $header['filename'] = $header['prefix'].'/'.$header['filename'];
554 if (($header['typeflag'] = $data['typeflag']) == '5') {
556 $header['type'] = 'folder';
559 $header['type'] = 'file';
561 $header['offset'] = $this->file
->tell();
572 * The File class handles all file operations.
575 * using php functions:
576 * $fp = fopen('filename', 'wb');
577 * fwrite($fp, '...');
581 * $file = new File('filename');
582 * $file->write('...');
585 * @author Marcel Werk
588 protected $resource = null;
594 * @param string $filename
595 * @param string $mode
597 public function __construct($filename, $mode = 'wb') {
598 $this->filename
= $filename;
599 $this->resource = fopen($filename, $mode);
600 if ($this->resource === false) {
601 throw new SystemException('Can not open file ' . $filename);
606 * Calls the specified function on the open file.
607 * Do not call this function directly. Use $file->write('') instead.
609 * @param string $function
610 * @param array $arguments
612 public function __call($function, $arguments) {
613 if (function_exists('f' . $function)) {
614 array_unshift($arguments, $this->resource);
615 return call_user_func_array('f' . $function, $arguments);
617 else if (function_exists($function)) {
618 array_unshift($arguments, $this->filename
);
619 return call_user_func_array($function, $arguments);
622 throw new SystemException('Can not call file method ' . $function);
628 * The File class handles all file operations on a zipped file.
630 * @author Marcel Werk
632 class ZipFile
extends File
{
634 * Opens a new zipped file.
636 * @param string $filename
637 * @param string $mode
639 public function __construct($filename, $mode = 'wb') {
640 $this->filename
= $filename;
641 if (!function_exists('gzopen')) {
642 throw new SystemException('Can not find functions of the zlib extension');
644 $this->resource = @gzopen
($filename, $mode);
645 if ($this->resource === false) {
646 throw new SystemException('Can not open file ' . $filename);
651 * Calls the specified function on the open file.
653 * @param string $function
654 * @param array $arguments
656 public function __call($function, $arguments) {
657 if (function_exists('gz' . $function)) {
658 array_unshift($arguments, $this->resource);
659 return call_user_func_array('gz' . $function, $arguments);
661 else if (function_exists($function)) {
662 array_unshift($arguments, $this->filename
);
663 return call_user_func_array($function, $arguments);
666 throw new SystemException('Can not call method ' . $function);
671 * Returns the filesize of the unzipped file
673 public function getFileSize() {
677 // the correction is for zip files that are too small
678 // to get in the first while loop
680 while ($this->seek($eof) == 0) {
685 while ($byteBlock > 1) {
687 $eof +
= $byteBlock * ($this->seek($eof) ?
-1 : 1);
690 if ($this->seek($eof) == -1) $eof -= 1;
693 return $eof - $correction;
698 // get temp file prefix
699 if (isset($_REQUEST['tmpFilePrefix'])) {
700 $prefix = preg_replace('/[^a-f0-9_]+/', '', $_REQUEST['tmpFilePrefix']);
703 $prefix = substr(sha1(uniqid(microtime())), 0, 8);
705 define('TMP_FILE_PREFIX', $prefix);
707 // try to find the temp folder
708 define('TMP_DIR', BasicFileUtil
::getTempFolder());
711 * Reads a file resource from temp folder.
714 * @param string $directory
716 function readFileResource($key, $directory) {
717 if (preg_match('~[\w\-]+\.(css|jpg|png|svg)~', $_GET[$key], $match)) {
720 header('Content-Type: text/css');
724 header('Content-Type: image/jpg');
728 header('Content-Type: image/png');
732 header('Content-Type: image/svg+xml');
736 header('Expires: '.gmdate('D, d M Y H:i:s', time() +
3600).' GMT');
737 header('Last-Modified: Mon, 26 Jul 1997 05:00:00 GMT');
738 header('Cache-Control: public, max-age=3600');
740 readfile($directory . $_GET[$key]);
745 // show image from temp folder
746 if (isset($_GET['showImage'])) {
747 readFileResource('showImage', TMP_DIR
. 'install/files/acp/images/');
749 // show icon from temp folder
750 if (isset($_GET['showIcon'])) {
751 readFileResource('showIcon', TMP_DIR
. 'install/files/icon/');
753 // show css from temp folder
754 if (isset($_GET['showCSS'])) {
755 readFileResource('showCSS', TMP_DIR
. 'install/files/acp/style/setup/');
758 // check whether setup files are already unzipped
759 if (!file_exists(TMP_DIR
. 'install/files/lib/system/WCFSetup.class.php')) {
760 // try to unzip all setup files into temp folder
761 $tar = new Tar(SETUP_FILE
);
762 $contentList = $tar->getContentList();
763 if (empty($contentList)) {
764 throw new SystemException("Can not unpack 'WCFSetup.tar.gz'. File is probably broken.");
767 foreach ($contentList as $file) {
768 foreach ($neededFilesPattern as $pattern) {
769 if (preg_match($pattern, $file['filename'])) {
770 // create directory if not exists
771 $dir = TMP_DIR
. dirname($file['filename']);
772 if (!@is_dir
($dir)) {
773 @mkdir
($dir, 0777, true);
777 $tar->extract($file['index'], TMP_DIR
. $file['filename']);
783 // create cache folders
784 @mkdir
(TMP_DIR
. 'setup/lang/cache/', 0777);
785 @chmod
(TMP_DIR
. 'setup/lang/cache/', 0777);
787 @mkdir
(TMP_DIR
. 'setup/template/compiled/', 0777);
788 @chmod
(TMP_DIR
. 'setup/template/compiled/', 0777);
791 if (!class_exists('wcf\system\WCFSetup')) {
792 throw new SystemException("Can not find class 'WCFSetup'");
796 new \wcf\system\
WCFSetup();