3 * This script tries to find the temp folder and unzip all setup files into.
6 * @copyright 2001-2013 WoltLab GmbH
7 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
10 define('INSTALL_SCRIPT', __FILE__
);
11 define('INSTALL_SCRIPT_DIR', dirname(__FILE__
).'/');
12 define('SETUP_FILE', INSTALL_SCRIPT_DIR
. 'WCFSetup.tar.gz');
13 define('NO_IMPORTS', 1);
15 // set exception handler
16 set_exception_handler('handleException');
17 // set php error handler
18 set_error_handler('handleError', E_ALL
);
20 // define list of needed file
21 $neededFilesPattern = array(
23 '!^install/files/acp/images/wcfLogo.*!',
24 '!^install/files/acp/style/setup/.*!',
25 '!^install/files/lib/data/.*!',
26 '!^install/files/icon/.*!',
27 '!^install/files/font/.*!',
28 '!^install/files/lib/system/.*!',
29 '!^install/files/lib/util/.*!',
31 '!^install/packages/.*!');
33 // define needed functions and classes
35 * WCF::handleException() calls the show method on exceptions that implement this interface.
37 * @package com.woltlab.wcf
40 interface IPrintableException
{
41 public function show();
44 // define needed classes
46 // SystemException, PrintableException, BasicFileUtil, Tar, File, ZipFile
48 * A SystemException is thrown when an unexpected error occurs.
50 * @package com.woltlab.wcf
53 class SystemException
extends \Exception
implements IPrintableException
{
54 protected $description;
55 protected $information = '';
56 protected $functions = '';
59 * Creates a new SystemException.
61 * @param message string error message
62 * @param code integer error code
63 * @param description string description of the error
65 public function __construct($message = '', $code = 0, $description = '') {
66 parent
::__construct($message, $code);
67 $this->description
= $description;
71 * Returns the description of this exception.
75 public function getDescription() {
76 return $this->description
;
80 * Prints this exception.
81 * This method is called by WCF::handleException().
83 public function show() {
87 <title
>Fatal error
: <?php
echo htmlspecialchars($this->getMessage()); ?
></title
>
89 <style type
="text/css">
91 font
-family
: Verdana
, Helvetica
, sans
-serif
;
95 border
: 1px outset lightgrey
;
97 background
-color
: lightgrey
;
101 border
: 1px inset lightgrey
;
106 background
-color
: #154268;
125 <h1
>Fatal error
: <?php
echo htmlspecialchars($this->getMessage()); ?
></h1
>
128 <p
><?php
echo $this->getDescription(); ?
></p
>
129 <?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 } ?>
131 <h2
>Information
:</h2
>
133 <b
>error message
:</b
> <?php
echo htmlspecialchars($this->getMessage()); ?
><br
/>
134 <b
>error code
:</b
> <?php
echo intval($this->getCode()); ?
><br
/>
135 <?php
echo $this->information
; ?
>
136 <b
>file
:</b
> <?php
echo htmlspecialchars($this->getFile()); ?
> (<?php
echo $this->getLine(); ?
>)<br
/>
137 <b
>php version
:</b
> <?php
echo htmlspecialchars(phpversion()); ?
><br
/>
138 <b
>wcf version
:</b
> <?php
if (defined('WCF_VERSION')) echo WCF_VERSION
; ?
><br
/>
139 <b
>date
:</b
> <?php
echo gmdate('r'); ?
><br
/>
140 <b
>request
:</b
> <?php
if (isset($_SERVER['REQUEST_URI'])) echo htmlspecialchars($_SERVER['REQUEST_URI']); ?
><br
/>
141 <b
>referer
:</b
> <?php
if (isset($_SERVER['HTTP_REFERER'])) echo htmlspecialchars($_SERVER['HTTP_REFERER']); ?
><br
/>
145 <pre
><?php
echo htmlspecialchars($this->getTraceAsString()); ?
></pre
>
148 <?php
echo $this->functions
; ?
>
159 * Loads the required classes automatically.
161 function __autoload($className) {
162 $namespaces = explode('\\', $className);
163 if (count($namespaces) > 1) {
164 // remove 'wcf' component
165 array_shift($namespaces);
167 $className = implode('/', $namespaces);
168 $classPath = TMP_DIR
. 'install/files/lib/' . $className . '.class.php';
169 if (file_exists($classPath)) {
170 require_once($classPath);
176 * Escapes strings for execution in sql queries.
178 function escapeString($string) {
179 return \wcf\system\WCF
::getDB()->escapeString($string);
183 * Calls the show method on the given exception.
185 * @param Exception $e
187 function handleException(\Exception
$e) {
188 if ($e instanceof IPrintableException ||
$e instanceof \wcf\system\exception\IPrintableException
) {
197 * Catches php errors and throws instead a system exception.
199 * @param integer $errorNo
200 * @param string $message
201 * @param string $filename
202 * @param integer $lineNo
204 function handleError($errorNo, $message, $filename, $lineNo) {
205 if (error_reporting() != 0) {
208 case 2: $type = 'warning';
210 case 8: $type = 'notice';
214 throw new SystemException('PHP '.$type.' in file '.$filename.' ('.$lineNo.'): '.$message, 0);
219 * BasicFileUtil contains file-related functions.
221 * @package com.woltlab.wcf
222 * @author Marcel Werk
224 class BasicFileUtil
{
229 protected static $mode = null;
232 * Tries to find the temp folder.
236 public static function getTempFolder() {
237 // use tmp folder in document root by default
238 if (!empty($_SERVER['DOCUMENT_ROOT'])) {
239 if (strpos($_SERVER['DOCUMENT_ROOT'], 'strato') !== false) {
241 // create tmp folder in document root automatically
242 if (!@file_exists
($_SERVER['DOCUMENT_ROOT'].'/tmp')) {
243 @mkdir
($_SERVER['DOCUMENT_ROOT'].'/tmp/', 0777);
245 self
::makeWritable($_SERVER['DOCUMENT_ROOT'].'/tmp/');
247 catch (SystemException
$e) {}
250 if (@file_exists
($_SERVER['DOCUMENT_ROOT'].'/tmp') && @is_writable
($_SERVER['DOCUMENT_ROOT'].'/tmp')) {
251 return $_SERVER['DOCUMENT_ROOT'].'/tmp/';
255 if (isset($_ENV['TMP']) && @is_writable
($_ENV['TMP'])) {
256 return $_ENV['TMP'] . '/';
258 if (isset($_ENV['TEMP']) && @is_writable
($_ENV['TEMP'])) {
259 return $_ENV['TEMP'] . '/';
261 if (isset($_ENV['TMPDIR']) && @is_writable
($_ENV['TMPDIR'])) {
262 return $_ENV['TMPDIR'] . '/';
265 if (($path = ini_get('upload_tmp_dir')) && @is_writable
($path)) {
268 if (@file_exists
('/tmp/') && @is_writable
('/tmp/')) {
271 if (function_exists('session_save_path') && ($path = session_save_path()) && @is_writable
($path)) {
275 $path = INSTALL_SCRIPT_DIR
.'tmp/';
276 if (@file_exists
($path) && @is_writable
($path)) {
280 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 '.$path.' using your favorite ftp program, make it writable and then retry this installation.');
285 * Returns the temp folder for the installation.
289 public static function getInstallTempFolder() {
290 $dir = self
::getTempFolder() . TMP_FILE_PREFIX
. '/';
292 self
::makeWritable($dir);
298 * Tries to make a file or directory writable. It starts of with the least
299 * permissions and goes up until 0666 for files and 0777 for directories.
301 * @param string $filename
303 public static function makeWritable($filename) {
304 if (!file_exists($filename)) {
309 if (self
::$mode === null) {
310 // do not use PHP_OS here, as this represents the system it was built on != running on
311 if (strpos(php_uname(), 'Windows') !== false) {
319 $tmpFilename = '__permissions_'.sha1(time()).'.txt';
320 @touch
($tmpFilename);
322 // create a new file and check the file owner, if it is the same
323 // as this file (uploaded through FTP), we can safely grant write
324 // permissions exclusively to the owner rather than everyone
325 if (file_exists($tmpFilename)) {
326 $scriptOwner = fileowner(__FILE__
);
327 $fileOwner = fileowner($tmpFilename);
329 if ($scriptOwner === $fileOwner) {
333 @unlink
($tmpFilename);
339 if (is_dir($filename)) {
340 if (self
::$mode == 0644) {
341 @chmod
($filename, 0755);
344 @chmod
($filename, 0777);
348 @chmod
($filename, self
::$mode);
351 if (!is_writable($filename)) {
352 throw new SystemException("Unable to make '".$filename."' writable. This is a misconfiguration of your server, please contact your system administrator or hosting provider.");
358 * Opens tar or tar.gz archives.
362 * $tar = new Tar('archive.tar');
363 * $contentList = $tar->getContentList();
364 * foreach ($contentList as $key => $val) {
365 * $tar->extract($key, DESTINATION);
369 protected $archiveName = '';
370 protected $contentList = array();
371 protected $opened = false;
372 protected $read = false;
373 protected $file = null;
374 protected $isZipped = false;
375 protected $mode = 'rb';
378 * Creates a new Tar object.
379 * archiveName must be tarball or gzipped tarball
381 * @param string $archiveName
383 public function __construct($archiveName) {
384 if (!is_file($archiveName)) {
385 throw new SystemException("unable to find tar archive '".$archiveName."'");
388 $this->archiveName
= $archiveName;
390 $this->readContent();
394 * Destructor of this class, closes tar archive.
396 public function __destruct() {
401 * Opens the tar archive and stores filehandle.
403 public function open() {
404 if (!$this->opened
) {
405 if ($this->isZipped
) $this->file
= new ZipFile($this->archiveName
, $this->mode
);
408 $this->file
= new File($this->archiveName
, $this->mode
);
409 if ($this->file
->read(2) == "\37\213") {
410 $this->file
->close();
411 $this->isZipped
= true;
412 $this->file
= new ZipFile($this->archiveName
, $this->mode
);
415 $this->file
->seek(0);
418 $this->opened
= true;
423 * Closes the opened file.
425 public function close() {
427 $this->file
->close();
428 $this->opened
= false;
433 * Returns the table of contents (TOC) list for this tar archive.
435 * @return array list of content
437 public function getContentList() {
440 $this->readContent();
442 return $this->contentList
;
446 * Returns an associative array with information
447 * about a specific file in the archive.
449 * @param mixed $fileindex index or name of the requested file
450 * @return array $fileInfo
452 public function getFileInfo($fileIndex) {
453 if (!is_int($fileIndex)) {
454 $fileIndex = $this->getIndexByFilename($fileIndex);
457 if (!isset($this->contentList
[$fileIndex])) {
458 throw new SystemException("Tar: could find file '".$fileIndex."' in archive");
460 return $this->contentList
[$fileIndex];
464 * Searchs a file in the tar archive
465 * and returns the numeric fileindex.
466 * Returns false if not found.
468 * @param string $filename
469 * @return integer index of the requested file
471 public function getIndexByFilename($filename) {
472 foreach ($this->contentList
as $index => $file) {
473 if ($file['filename'] == $filename) {
481 * Extracts a specific file and returns the content as string.
482 * Returns false if extraction failed.
484 * @param mixed $index index or name of the requested file
485 * @return string content of the requested file
487 public function extractToString($index) {
490 $this->readContent();
492 $header = $this->getFileInfo($index);
494 // can not extract a folder
495 if ($header['type'] != 'file') {
500 $this->file
->seek($header['offset']);
504 $n = floor($header['size'] / 512);
505 for($i = 0; $i < $n; $i++
) {
506 $content .= $this->file
->read(512);
508 if(($header['size'] %
512) != 0) {
509 $buffer = $this->file
->read(512);
510 $content .= substr($buffer, 0, ($header['size'] %
512));
517 * Extracts a specific file and writes it's content
518 * to the file specified with $destination.
520 * @param mixed $index index or name of the requested file
521 * @param string $destination
522 * @return boolean $success
524 public function extract($index, $destination) {
527 $this->readContent();
529 $header = $this->getFileInfo($index);
531 // can not extract a folder
532 if ($header['type'] != 'file') {
537 $this->file
->seek($header['offset']);
539 $targetFile = new File($destination);
542 $n = floor($header['size'] / 512);
543 for ($i = 0; $i < $n; $i++
) {
544 $content = $this->file
->read(512);
545 $targetFile->write($content, 512);
547 if (($header['size'] %
512) != 0) {
548 $content = $this->file
->read(512);
549 $targetFile->write($content, ($header['size'] %
512));
552 $targetFile->close();
553 BasicFileUtil
::makeWritable($destination);
555 if ($header['mtime']) {
556 @$targetFile->touch($header['mtime']);
560 if (filesize($destination) != $header['size']) {
561 throw new SystemException("Could not untar file '".$header['filename']."' to '".$destination."'. Maybe disk quota exceeded in folder '".dirname($destination)."'.");
568 * Reads table of contents (TOC) from tar archive.
569 * This does not get the entire to memory but only parts of it.
571 protected function readContent() {
572 $this->contentList
= array();
576 // Read the 512 bytes header
577 while (strlen($binaryData = $this->file
->read(512)) != 0) {
579 $header = $this->readHeader($binaryData);
580 if ($header === false) {
583 $this->contentList
[$i] = $header;
584 $this->contentList
[$i]['index'] = $i;
587 $this->file
->seek($this->file
->tell() +
(512 * ceil(($header['size'] / 512))));
592 * Unpacks file header for one file entry.
594 * @param string $binaryData
595 * @return array $fileheader
597 protected function readHeader($binaryData) {
598 if (strlen($binaryData) != 512) {
604 // First part of the header
605 for ($i = 0; $i < 148; $i++
) {
606 $checksum +
= ord(substr($binaryData, $i, 1));
608 // Calculate the checksum
609 // Ignore the checksum value and replace it by ' ' (space)
610 for ($i = 148; $i < 156; $i++
) {
611 $checksum +
= ord(' ');
613 // Last part of the header
614 for ($i = 156; $i < 512; $i++
) {
615 $checksum +
= ord(substr($binaryData, $i, 1));
618 // Extract the values
619 //$data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", $binaryData);
620 if (version_compare(PHP_VERSION
, '5.5.0-dev', '>=')) {
621 $format = 'Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/Z32uname/Z32gname/Z8devmajor/Z8devminor/Z155prefix';
624 $format = 'a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix';
627 $data = unpack($format, $binaryData);
629 // Extract the properties
630 $header['checksum'] = octDec(trim($data['checksum']));
631 if ($header['checksum'] == $checksum) {
632 $header['filename'] = trim($data['filename']);
633 $header['mode'] = octDec(trim($data['mode']));
634 $header['uid'] = octDec(trim($data['uid']));
635 $header['gid'] = octDec(trim($data['gid']));
636 $header['size'] = octDec(trim($data['size']));
637 $header['mtime'] = octDec(trim($data['mtime']));
638 $header['prefix'] = trim($data['prefix']);
639 if ($header['prefix']) {
640 $header['filename'] = $header['prefix'].'/'.$header['filename'];
642 if (($header['typeflag'] = $data['typeflag']) == '5') {
644 $header['type'] = 'folder';
647 $header['type'] = 'file';
649 $header['offset'] = $this->file
->tell();
660 * The File class handles all file operations.
663 * using php functions:
664 * $fp = fopen('filename', 'wb');
665 * fwrite($fp, '...');
669 * $file = new File('filename');
670 * $file->write('...');
673 * @author Marcel Werk
676 protected $resource = null;
682 * @param string $filename
683 * @param string $mode
685 public function __construct($filename, $mode = 'wb') {
686 $this->filename
= $filename;
687 $this->resource = fopen($filename, $mode);
688 if ($this->resource === false) {
689 throw new SystemException('Can not open file ' . $filename);
694 * Calls the specified function on the open file.
695 * Do not call this function directly. Use $file->write('') instead.
697 * @param string $function
698 * @param array $arguments
700 public function __call($function, $arguments) {
701 if (function_exists('f' . $function)) {
702 array_unshift($arguments, $this->resource);
703 return call_user_func_array('f' . $function, $arguments);
705 else if (function_exists($function)) {
706 array_unshift($arguments, $this->filename
);
707 return call_user_func_array($function, $arguments);
710 throw new SystemException('Can not call file method ' . $function);
716 * The File class handles all file operations on a zipped file.
718 * @author Marcel Werk
720 class ZipFile
extends File
{
722 * Opens a new zipped file.
724 * @param string $filename
725 * @param string $mode
727 public function __construct($filename, $mode = 'wb') {
728 $this->filename
= $filename;
729 if (!function_exists('gzopen')) {
730 throw new SystemException('Can not find functions of the zlib extension');
732 $this->resource = @gzopen
($filename, $mode);
733 if ($this->resource === false) {
734 throw new SystemException('Can not open file ' . $filename);
739 * Calls the specified function on the open file.
741 * @param string $function
742 * @param array $arguments
744 public function __call($function, $arguments) {
745 if (function_exists('gz' . $function)) {
746 array_unshift($arguments, $this->resource);
747 return call_user_func_array('gz' . $function, $arguments);
749 else if (function_exists($function)) {
750 array_unshift($arguments, $this->filename
);
751 return call_user_func_array($function, $arguments);
754 throw new SystemException('Can not call method ' . $function);
759 * Returns the filesize of the unzipped file
761 public function getFileSize() {
765 // the correction is for zip files that are too small
766 // to get in the first while loop
768 while ($this->seek($eof) == 0) {
773 while ($byteBlock > 1) {
775 $eof +
= $byteBlock * ($this->seek($eof) ?
-1 : 1);
778 if ($this->seek($eof) == -1) $eof -= 1;
781 return $eof - $correction;
786 // get temp file prefix
787 if (isset($_REQUEST['tmpFilePrefix'])) {
788 $prefix = preg_replace('/[^a-f0-9_]+/', '', $_REQUEST['tmpFilePrefix']);
791 $prefix = substr(sha1(uniqid(microtime())), 0, 8);
793 define('TMP_FILE_PREFIX', $prefix);
795 // try to find the temp folder
796 define('TMP_DIR', BasicFileUtil
::getInstallTempFolder());
799 * Reads a file resource from temp folder.
802 * @param string $directory
804 function readFileResource($key, $directory) {
805 if (preg_match('~[\w\-]+\.(css|jpg|png|svg|eot|woff|ttf)~', $_GET[$key], $match)) {
808 header('Content-Type: text/css');
812 header('Content-Type: image/jpg');
816 header('Content-Type: image/png');
820 header('Content-Type: image/svg+xml');
824 header('Content-Type: application/vnd.ms-fontobject');
828 header('Content-Type: application/font-woff');
832 header('Content-Type: application/octet-stream');
836 header('Expires: '.gmdate('D, d M Y H:i:s', time() +
3600).' GMT');
837 header('Last-Modified: Mon, 26 Jul 1997 05:00:00 GMT');
838 header('Cache-Control: public, max-age=3600');
840 readfile($directory . $_GET[$key]);
845 // show image from temp folder
846 if (isset($_GET['showImage'])) {
847 readFileResource('showImage', TMP_DIR
. 'install/files/acp/images/');
849 // show icon from temp folder
850 if (isset($_GET['showIcon'])) {
851 readFileResource('showIcon', TMP_DIR
. 'install/files/icon/');
853 // show css from temp folder
854 if (isset($_GET['showCSS'])) {
855 readFileResource('showCSS', TMP_DIR
. 'install/files/acp/style/setup/');
857 // show fonts from temp folder
858 if (isset($_GET['showFont'])) {
859 readFileResource('showFont', TMP_DIR
. 'install/files/font/');
862 // check whether setup files are already unzipped
863 if (!file_exists(TMP_DIR
. 'install/files/lib/system/WCFSetup.class.php')) {
864 // try to unzip all setup files into temp folder
865 $tar = new Tar(SETUP_FILE
);
866 $contentList = $tar->getContentList();
867 if (empty($contentList)) {
868 throw new SystemException("Can not unpack 'WCFSetup.tar.gz'. File is probably broken.");
871 foreach ($contentList as $file) {
872 foreach ($neededFilesPattern as $pattern) {
873 if (preg_match($pattern, $file['filename'])) {
874 // create directory if not exists
875 $dir = TMP_DIR
. dirname($file['filename']);
876 if (!@is_dir
($dir)) {
877 @mkdir
($dir, 0777, true);
878 BasicFileUtil
::makeWritable($dir);
881 $tar->extract($file['index'], TMP_DIR
. $file['filename']);
887 // create cache folders
888 @mkdir
(TMP_DIR
. 'setup/lang/cache/', 0777);
889 BasicFileUtil
::makeWritable(TMP_DIR
. 'setup/lang/cache/');
891 @mkdir
(TMP_DIR
. 'setup/template/compiled/', 0777);
892 BasicFileUtil
::makeWritable(TMP_DIR
. 'setup/template/compiled/');
895 if (!class_exists('wcf\system\WCFSetup')) {
896 throw new SystemException("Can not find class 'WCFSetup'");
900 new \wcf\system\
WCFSetup();