*/ define('INSTALL_SCRIPT', __FILE__); define('INSTALL_SCRIPT_DIR', dirname(__FILE__) . '/'); define('WCF_DIR', INSTALL_SCRIPT_DIR); define('RELATIVE_WCF_DIR', './'); define('SETUP_FILE', INSTALL_SCRIPT_DIR . 'WCFSetup.tar.gz'); define('NO_IMPORTS', 1); $neededFilesPattern = [ '!^setup/.*!', '!^install/files/acp/images/woltlabSuite.*!', '!^install/files/acp/style/setup/.*!', '!^install/files/lib/data/.*!', '!^install/files/lib/event/.*!', '!^install/files/lib/system/.*!', '!^install/files/lib/util/.*!', '!^install/lang/.*!', '!^install/packages/.*!', ]; function sanitizeStacktrace(\Throwable $e, bool $ignorePaths = false) { $trace = $e->getTrace(); return array_map(function ($item) use ($ignorePaths) { if (!isset($item['file'])) $item['file'] = '[internal function]'; if (!isset($item['line'])) $item['line'] = '?'; if (!isset($item['class'])) $item['class'] = ''; if (!isset($item['type'])) $item['type'] = ''; if (!isset($item['args'])) $item['args'] = []; try { if (!empty($item['args'])) { if ($item['class']) { $function = new \ReflectionMethod($item['class'], $item['function']); } else { $function = new \ReflectionFunction($item['function']); } $parameters = $function->getParameters(); $i = 0; foreach ($parameters as $parameter) { $isSensitive = false; if ( !empty($parameter->getAttributes(\wcf\SensitiveArgument::class)) || !empty($parameter->getAttributes(\SensitiveParameter::class)) ) { $isSensitive = true; } if (\preg_match( '/(?:^(?:password|passphrase|secret)|(?:Password|Passphrase|Secret))/', $parameter->getName() )) { $isSensitive = true; } if ($isSensitive && isset($item['args'][$i])) { $item['args'][$i] = '[redacted]'; } $i++; } // strip database credentials if ( preg_match('~\\\\?wcf\\\\system\\\\database\\\\[a-zA-Z]*Database~', $item['class']) || $item['class'] === 'PDO' ) { if ($item['function'] === '__construct') { $item['args'] = array_map(function () { return '[redacted]'; }, $item['args']); } } } } catch (\Throwable $e) { $item['args'] = array_map(function () { return '[error_during_sanitization]'; }, $item['args']); } if (!$ignorePaths) { $item['args'] = array_map(function ($item) { if (!is_string($item)) return $item; if (preg_match('~^(' . preg_quote($_SERVER['DOCUMENT_ROOT'], '~') . '|' . preg_quote(WCF_DIR, '~') . ')~', $item)) { $item = sanitizePath($item); } return $item; }, $item['args']); $item['file'] = sanitizePath($item['file']); } return $item; }, $trace); } function printException($e) { $exceptionTitle = 'An error has occurred'; $exceptionSubtitle = ''; $exceptionExplanation = ''; $exceptionID = ''; ?>
System Information
PHP Version:
WoltLab Suite Core:
n/a
Peak Memory Usage:
MiB
Request URI:
Referrer:
User Agent:
Error
getDescription()) { ?>getDescription(); ?>
Error Type:
Error Message:
getMessage()); ?>
Error Code:
getCode()); ?>
File:
getFile())); ?> (getLine(); ?>)
:
Template Context:
Stack Trace:
WCF::handleException() Unhandled exception: " . $exception->getMessage() . "\n\n" . $exception->getTraceAsString());
}
});
set_error_handler(static function ($severity, $message, $file, $line) {
// this is necessary for the shut-up operator
if (!(\error_reporting() & $severity)) {
return;
}
throw new ErrorException($message, 0, $severity, $file, $line);
}, E_ALL);
/** @noinspection PhpMultipleClassesDeclarationsInOneFile */
/**
* A SystemException is thrown when an unexpected error occurs.
*
* @package com.woltlab.wcf
* @author Marcel Werk
*/
class SystemException extends \Exception
{
protected $description;
protected $information = '';
protected $functions = '';
/**
* Creates a new SystemException.
*
* @param string $message error message
* @param int $code error code
* @param string $description description of the error
* @param \Exception $previous repacked Exception
*/
public function __construct($message = '', $code = 0, $description = '', \Exception $previous = null)
{
parent::__construct((string) $message, (int) $code, $previous);
$this->description = $description;
}
/**
* Returns the description of this exception.
*
* @return string
*/
public function getDescription()
{
return $this->description;
}
/**
* Prints this exception.
* This method is called by WCF::handleException().
*/
public function show()
{
}
}
/**
* Loads the required classes automatically.
*/
spl_autoload_register(function ($className) {
$namespaces = explode('\\', $className);
if (count($namespaces) > 1) {
// remove 'wcf' component
array_shift($namespaces);
$className = implode('/', $namespaces);
$classPath = TMP_DIR . 'install/files/lib/' . $className . '.class.php';
if (file_exists($classPath)) {
require_once($classPath);
}
}
});
/**
* Helper method to output debug data for all passed variables,
* uses `print_r()` for arrays and objects, `var_dump()` otherwise.
*/
function wcfDebug()
{
echo "";
$args = func_get_args();
$length = count($args);
if ($length === 0) {
echo "ERROR: No arguments provided.
";
} else {
for ($i = 0; $i < $length; $i++) {
$arg = $args[$i];
echo "Argument {$i} (" . gettype($arg) . ")
";
if (is_array($arg) || is_object($arg)) {
print_r($arg);
} else {
var_dump($arg);
}
echo "
";
}
}
$backtrace = debug_backtrace();
// output call location to help finding these debug outputs again
echo "wcfDebug() called in {$backtrace[0]['file']} on line {$backtrace[0]['line']}";
echo "";
exit;
}
/** @noinspection PhpMultipleClassesDeclarationsInOneFile */
/**
* BasicFileUtil contains file-related functions.
*
* @package com.woltlab.wcf
* @author Marcel Werk
*/
class BasicFileUtil
{
/**
* chmod mode
* @var int
*/
protected static $mode = null;
/**
* Tries to make a file or directory writable. It starts of with the least
* permissions and goes up until 0666 for files and 0777 for directories.
*
* @param string $filename
* @throws \Exception
*/
public static function makeWritable($filename)
{
if (!file_exists($filename)) {
return;
}
// determine mode
if (self::$mode === null) {
// do not use PHP_OS here, as this represents the system it was built on != running on
// php_uname() is forbidden on some strange hosts; PHP_EOL is reliable
if (PHP_EOL == "\r\n") {
// Windows
self::$mode = 0777;
} else {
// anything but Windows
clearstatcache();
self::$mode = 0666;
$tmpFilename = '__permissions_' . sha1(time()) . '.txt';
@touch($tmpFilename);
// create a new file and check the file owner, if it is the same
// as this file (uploaded through FTP), we can safely grant write
// permissions exclusively to the owner rather than everyone
if (file_exists($tmpFilename)) {
$scriptOwner = fileowner(__FILE__);
$fileOwner = fileowner($tmpFilename);
if ($scriptOwner === $fileOwner) {
self::$mode = 0644;
}
@unlink($tmpFilename);
}
}
}
if (is_dir($filename)) {
if (self::$mode == 0644) {
@chmod($filename, 0755);
} else {
@chmod($filename, 0777);
}
} else {
@chmod($filename, self::$mode);
}
if (!is_writable($filename)) {
throw new \Exception("Unable to make '" . $filename . "' writable. This is a misconfiguration of your server, please contact your system administrator or hosting provider.");
}
}
/**
* Adds a trailing slash to the given path.
*
* @param string $path
*/
public static function addTrailingSlash($path): string
{
return rtrim($path, '/') . '/';
}
/**
* Creates a path on the local filesystem and returns true on success.
* Parent directories do not need to exists as they will be created if
* necessary.
*
* @param string $path
*/
public static function makePath($path): bool
{
// directory already exists, abort
if (file_exists($path)) {
return false;
}
// check if parent directory exists
$parent = dirname($path);
if ($parent != $path) {
// parent directory does not exist either
// we have to create the parent directory first
$parent = self::addTrailingSlash($parent);
if (!@file_exists($parent)) {
// could not create parent directory either => abort
if (!self::makePath($parent)) {
return false;
}
}
// well, the parent directory exists or has been created
// lets create this path
if (!@mkdir($path)) {
return false;
}
self::makeWritable($path);
return true;
}
return false;
}
}
/** @noinspection PhpMultipleClassesDeclarationsInOneFile */
/**
* Opens tar or tar.gz archives.
*
* Usage:
* ------
* $tar = new Tar('archive.tar');
* $contentList = $tar->getContentList();
* foreach ($contentList as $key => $val) {
* $tar->extract($key, DESTINATION);
* }
*/
class Tar
{
/**
* name of the archive
* @var string
*/
protected $archiveName = '';
/**
* content of the tar file
* @var array
*/
protected $contentList = [];
/**
* indicates if tar file is opened
* @var bool
*/
protected $opened = false;
/**
* indicates if file content has been read
* @var bool
*/
protected $read = false;
/**
* file object
* @var File
*/
protected $file = null;
/**
* indicates if the tar file is (g)zipped
* @var bool
*/
protected $isZipped = false;
/**
* file access mode
* @var string
*/
protected $mode = 'rb';
/**
* chunk size for extracting
* @var int
*/
const CHUNK_SIZE = 8192;
private static array $asciiMap;
/**
* Creates a new Tar object.
* archiveName must be tarball or gzipped tarball
*
* @param string $archiveName
* @throws SystemException
*/
public function __construct($archiveName)
{
if (!is_file($archiveName)) {
throw new SystemException("unable to find tar archive '" . $archiveName . "'");
}
if (!isset(self::$asciiMap)) {
self::$asciiMap = [];
for ($i = 0; $i <= 0xFF; $i++) {
self::$asciiMap[\chr($i)] = $i;
}
}
$this->archiveName = $archiveName;
$this->open();
$this->readContent();
}
/**
* Destructor of this class, closes tar archive.
*/
public function __destruct()
{
$this->close();
}
/**
* Opens the tar archive and stores filehandle.
*/
public function open()
{
if (!$this->opened) {
if ($this->isZipped) $this->file = new GZipFile($this->archiveName, $this->mode);
else {
// test compression
$this->file = new File($this->archiveName, $this->mode);
if ($this->file->read(2) == "\37\213") {
$this->file->close();
$this->isZipped = true;
$this->file = new GZipFile($this->archiveName, $this->mode);
} else {
$this->file->seek(0);
}
}
$this->opened = true;
}
}
/**
* Closes the opened file.
*/
public function close()
{
if ($this->opened) {
$this->file->close();
$this->opened = false;
}
}
/**
* @inheritDoc
*/
public function getContentList()
{
if (!$this->read) {
$this->open();
$this->readContent();
}
return $this->contentList;
}
/**
* @inheritDoc
*/
public function getFileInfo($fileIndex)
{
if (!is_int($fileIndex)) {
$fileIndex = $this->getIndexByFilename($fileIndex);
}
if (!isset($this->contentList[$fileIndex])) {
throw new SystemException("Tar: could find file '" . $fileIndex . "' in archive");
}
return $this->contentList[$fileIndex];
}
/**
* @inheritDoc
*/
public function getIndexByFilename($filename)
{
foreach ($this->contentList as $index => $file) {
if ($file['filename'] == $filename) {
return $index;
}
}
return false;
}
/**
* @inheritDoc
*/
public function extractToString($index)
{
if (!$this->read) {
$this->open();
$this->readContent();
}
$header = $this->getFileInfo($index);
// can not extract a folder
if ($header['type'] != 'file') {
return false;
}
// seek to offset
$this->file->seek($header['offset']);
// read data
$content = $this->file->read($header['size']);
if (strlen($content) != $header['size']) {
throw new SystemException("Could not untar file '" . $header['filename'] . "' to string. Maybe the archive is truncated?");
}
return $content;
}
/**
* @inheritDoc
*/
public function extract($index, $destination)
{
if (!$this->read) {
$this->open();
$this->readContent();
}
$header = $this->getFileInfo($index);
BasicFileUtil::makePath(dirname($destination));
if ($header['type'] === 'folder') {
BasicFileUtil::makePath($destination);
return;
}
if ($header['type'] === 'symlink') {
// skip symlinks
return;
}
// seek to offset
$this->file->seek($header['offset']);
$targetFile = new File($destination);
// read and write data
if ($header['size']) {
$buffer = $this->file->read($header['size']);
$targetFile->write($buffer);
}
$targetFile->close();
BasicFileUtil::makeWritable($destination);
if ($header['mtime']) {
@$targetFile->touch($header['mtime']);
}
// check filesize
if (filesize($destination) != $header['size']) {
throw new SystemException("Could not untar file '" . $header['filename'] . "' to '" . $destination . "'. Maybe disk quota exceeded in folder '" . dirname($destination) . "'.");
}
return true;
}
/**
* Reads table of contents (TOC) from tar archive.
* This does not get the entire to memory but only parts of it.
*/
protected function readContent()
{
$this->contentList = [];
$this->read = true;
$i = 0;
// Read the 512 bytes header
$longFilename = null;
while (strlen($binaryData = $this->file->read(512)) != 0) {
// read header
$header = $this->readHeader($binaryData);
if ($header === false) {
continue;
}
// fixes a bug that files with long names aren't correctly
// extracted
if ($longFilename !== null) {
$header['filename'] = $longFilename;
$longFilename = null;
}
if ($header['typeflag'] == 'L') {
$format = 'Z' . $header['size'] . 'filename';
$fileData = unpack($format, $this->file->read(512));
$longFilename = $fileData['filename'];
$header['size'] = 0;
}
// don't include the @LongLink file in the content list
else {
$this->contentList[$i] = $header;
$this->contentList[$i]['index'] = $i;
$i++;
}
$this->file->seek($this->file->tell() + (512 * ceil($header['size'] / 512)));
}
}
/**
* Unpacks file header for one file entry.
*
* @param string $binaryData
* @return array|bool
*/
protected function readHeader($binaryData)
{
if (strlen($binaryData) != 512) {
return false;
}
$header = [];
$checksum = 0;
// First part of the header
for ($i = 0; $i < 148; $i++) {
$checksum += self::$asciiMap[$binaryData[$i]];
}
// Calculate the checksum
// Ignore the checksum value and replace it by ' ' (space)
for ($i = 148; $i < 156; $i++) {
$checksum += self::$asciiMap[' '];
}
// Last part of the header
for ($i = 156; $i < 512; $i++) {
$checksum += self::$asciiMap[$binaryData[$i]];
}
// extract values
$format = 'Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/Z32uname/Z32gname/Z8devmajor/Z8devminor/Z155prefix';
$data = unpack($format, $binaryData);
// Extract the properties
$header['checksum'] = octdec(trim($data['checksum']));
if ($header['checksum'] == $checksum) {
$header['filename'] = trim($data['filename']);
$header['mode'] = octdec(trim($data['mode']));
$header['uid'] = octdec(trim($data['uid']));
$header['gid'] = octdec(trim($data['gid']));
$header['size'] = octdec(trim($data['size']));
$header['mtime'] = octdec(trim($data['mtime']));
$header['prefix'] = trim($data['prefix']);
if ($header['prefix']) {
$header['filename'] = $header['prefix'] . '/' . $header['filename'];
}
$header['typeflag'] = $data['typeflag'];
if ($header['typeflag'] == '5') {
$header['size'] = 0;
$header['type'] = 'folder';
} else if ($header['typeflag'] == '2') {
$header['type'] = 'symlink';
$header['target'] = $data['link'];
} else {
$header['type'] = 'file';
}
$header['offset'] = $this->file->tell();
return $header;
} else {
return false;
}
}
/**
* Returns true if this tar is (g)zipped.
*
* @return bool
*/
public function isZipped()
{
return $this->isZipped;
}
}
/** @noinspection PhpMultipleClassesDeclarationsInOneFile */
/**
* The File class handles all file operations.
*
* Example:
* using php functions:
* $fp = fopen('filename', 'wb');
* fwrite($fp, '...');
* fclose($fp);
*
* using this class:
* $file = new File('filename');
* $file->write('...');
* $file->close();
*
* @author Marcel Werk
*/
class File
{
protected $resource = null;
protected $filename;
/**
* Opens a new file.
*
* @param string $filename
* @param string $mode
* @throws SystemException
*/
public function __construct($filename, $mode = 'wb')
{
$this->filename = $filename;
$this->resource = fopen($filename, $mode);
if ($this->resource === false) {
throw new SystemException('Can not open file ' . $filename);
}
}
/**
* Calls the specified function on the open file.
* Do not call this function directly. Use $file->write('') instead.
*
* @param string $function
* @param array $arguments
* @return mixed
* @throws SystemException
*/
public function __call($function, $arguments)
{
if (function_exists('f' . $function)) {
array_unshift($arguments, $this->resource);
return call_user_func_array('f' . $function, $arguments);
} else if (function_exists($function)) {
array_unshift($arguments, $this->filename);
return call_user_func_array($function, $arguments);
} else {
throw new SystemException('Can not call file method ' . $function);
}
}
}
/** @noinspection PhpMultipleClassesDeclarationsInOneFile */
/**
* The File class handles all file operations on a zipped file.
*
* @author Marcel Werk
*/
final class GZipFile extends File
{
/** @noinspection PhpMissingParentConstructorInspection */
/**
* Opens a gzip file.
*
* @param string $filename
* @param string $mode
* @throws SystemException
*/
public function __construct($filename, $mode = 'wb')
{
$this->filename = $filename;
$this->resource = gzopen($filename, $mode);
if ($this->resource === false) {
throw new SystemException('Can not open file ' . $filename);
}
}
/**
* Calls the specified function on the open file.
*
* @param string $function
* @param array $arguments
* @return mixed
* @throws SystemException
*/
public function __call($function, $arguments)
{
if (function_exists('gz' . $function)) {
array_unshift($arguments, $this->resource);
return call_user_func_array('gz' . $function, $arguments);
} else if (function_exists($function)) {
array_unshift($arguments, $this->filename);
return call_user_func_array($function, $arguments);
} else {
throw new SystemException('Can not call method ' . $function);
}
}
/**
* @see \gzread()
*/
public function read(int $length): string|false
{
return \gzread($this->resource, $length);
}
/**
* @see \gztell()
*/
public function tell(): int|false
{
return \gztell($this->resource);
}
/**
* @see \gzseek()
*/
public function seek(int $offset, int $whence = \SEEK_SET): int
{
return \gzseek($this->resource, $offset, $whence);
}
/**
* Returns the filesize of the unzipped file.
*
* @return int
*/
public function getFileSize()
{
$byteBlock = 1 << 14;
$eof = $byteBlock;
// the correction is for zip files that are too small
// to get in the first while loop
$correction = 1;
while ($this->seek($eof) == 0) {
$eof += $byteBlock;
$correction = 0;
}
while ($byteBlock > 1) {
$byteBlock >>= 1;
$eof += $byteBlock * ($this->seek($eof) ? -1 : 1);
}
if ($this->seek($eof) == -1) $eof--;
$this->rewind();
return $eof - $correction;
}
}
// Bootstrap Setup.
$prefix = null;
if (isset($_POST['tmpFilePrefix'])) {
$inputPrefix = \preg_replace('/[^a-f0-9_]+/', '', $_POST['tmpFilePrefix']);
if (\is_dir(INSTALL_SCRIPT_DIR . "/WCFSetup-{$inputPrefix}/")) {
// We accept the input prefix if a corresponding directory exists.
$prefix = $inputPrefix;
}
}
// If no trusted prefix was provided, we generate a random prefix and corresponding directory.
if ($prefix === null) {
$prefix = \bin2hex(\random_bytes(8));
$dir = INSTALL_SCRIPT_DIR . "/WCFSetup-{$prefix}/";
\mkdir($dir);
BasicFileUtil::makeWritable($dir);
\file_put_contents($dir . 'lastStep', '0');
}
\define('TMP_FILE_PREFIX', $prefix);
\define('TMP_DIR', INSTALL_SCRIPT_DIR . "/WCFSetup-{$prefix}/");
// check whether setup files are already unzipped
if (!file_exists(TMP_DIR . 'install/files/lib/system/WCFSetup.class.php')) {
// try to unzip all setup files into temp folder
$tar = new Tar(SETUP_FILE);
$contentList = $tar->getContentList();
if (empty($contentList)) {
throw new \Exception("Cannot unpack 'WCFSetup.tar.gz'. File is probably broken.");
}
foreach ($contentList as $file) {
foreach ($neededFilesPattern as $pattern) {
if (preg_match($pattern, $file['filename'])) {
// create directory if not exists
$dir = TMP_DIR . dirname($file['filename']);
if (!@is_dir($dir)) {
@mkdir($dir, 0777, true);
BasicFileUtil::makeWritable($dir);
}
$tar->extract($file['index'], TMP_DIR . $file['filename']);
}
}
}
$tar->close();
@mkdir(TMP_DIR . 'setup/template/compiled/', 0777);
BasicFileUtil::makeWritable(TMP_DIR . 'setup/template/compiled/');
}
if (!class_exists(\wcf\system\WCFSetup::class)) {
throw new \Exception(\sprintf(
"Cannot find class '%s'",
\wcf\system\WCFSetup::class
));
}
// start setup
new \wcf\system\WCFSetup();