Restored the user online location for profiles
[GitHub/WoltLab/WCF.git] / wcfsetup / install.php
CommitLineData
158bd3ca
TD
1<?php
2/**
3 * This script tries to find the temp folder and unzip all setup files into.
e3369fd2 4 *
158bd3ca 5 * @author Marcel Werk
7b7b9764 6 * @copyright 2001-2019 WoltLab GmbH
158bd3ca
TD
7 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
8 */
392cd6cb 9// @codingStandardsIgnoreFile
158bd3ca 10// define constants
728b9dd6 11define('INSTALL_SCRIPT', __FILE__);
158bd3ca
TD
12define('INSTALL_SCRIPT_DIR', dirname(__FILE__).'/');
13define('SETUP_FILE', INSTALL_SCRIPT_DIR . 'WCFSetup.tar.gz');
14define('NO_IMPORTS', 1);
15
16// set exception handler
17set_exception_handler('handleException');
18// set php error handler
19set_error_handler('handleError', E_ALL);
20
21// define list of needed file
058cbd6a 22$neededFilesPattern = [
158bd3ca 23 '!^setup/.*!',
07937b16 24 '!^install/files/acp/images/woltlabSuite.*!',
e94c3830 25 '!^install/files/acp/style/setup/.*!',
158bd3ca 26 '!^install/files/lib/data/.*!',
7da7f7cc 27 '!^install/files/icon/.*!',
e2a34399 28 '!^install/files/font/.*!',
158bd3ca
TD
29 '!^install/files/lib/system/.*!',
30 '!^install/files/lib/util/.*!',
158bd3ca 31 '!^install/lang/.*!',
058cbd6a 32 '!^install/packages/.*!'];
158bd3ca
TD
33
34// define needed functions and classes
3e823cfa 35/** @noinspection PhpMultipleClassesDeclarationsInOneFile */
158bd3ca
TD
36/**
37 * WCF::handleException() calls the show method on exceptions that implement this interface.
38 *
f4f05aa5 39 * @package com.woltlab.wcf
158bd3ca
TD
40 * @author Marcel Werk
41 */
42interface IPrintableException {
43 public function show();
44}
45
46// define needed classes
47// needed are:
48// SystemException, PrintableException, BasicFileUtil, Tar, File, ZipFile
3e823cfa 49/** @noinspection PhpMultipleClassesDeclarationsInOneFile */
158bd3ca
TD
50/**
51 * A SystemException is thrown when an unexpected error occurs.
52 *
f4f05aa5 53 * @package com.woltlab.wcf
158bd3ca
TD
54 * @author Marcel Werk
55 */
56class SystemException extends \Exception implements IPrintableException {
57 protected $description;
58 protected $information = '';
59 protected $functions = '';
60
61 /**
62 * Creates a new SystemException.
63 *
ac52543a
MS
64 * @param string $message error message
65 * @param integer $code error code
66 * @param string $description description of the error
3353ca3e 67 * @param \Exception $previous repacked Exception
158bd3ca 68 */
3353ca3e
AE
69 public function __construct($message = '', $code = 0, $description = '', \Exception $previous = null) {
70 parent::__construct((string) $message, (int) $code, $previous);
158bd3ca
TD
71 $this->description = $description;
72 }
73
74 /**
75 * Returns the description of this exception.
76 *
39bea7dd 77 * @return string
158bd3ca
TD
78 */
79 public function getDescription() {
80 return $this->description;
81 }
82
83 /**
84 * Prints this exception.
85 * This method is called by WCF::handleException().
86 */
87 public function show() {
314f7e0e
MW
88 /*
89 * A notice on the HTML used below:
90 *
91 * It might appear a bit weird to use <p> all over the place where semantically
92 * other elements would fit in way better. The reason behind this is that we avoid
93 * inheriting unwanted styles (e.g. exception displayed in an overlay) and that
94 * the output needs to be properly readable when copied & pasted somewhere.
95 *
96 * Besides the visual appearance, the output was built to provide a maximum of
97 * compatibility and readability when pasted somewhere else, e.g. a WYSIWYG editor
98 * without the potential of messing up the formatting and thus harming the readability.
99 */
100?><!DOCTYPE html>
158bd3ca
TD
101<html>
102<head>
314f7e0e
MW
103 <title>Fatal Error: <?php echo htmlentities($this->getMessage()); ?></title>
104 <meta charset="utf-8">
105 <meta name="viewport" content="width=device-width, initial-scale=1">
106 <style>
107 .exceptionBody {
108 margin: 0;
109 padding: 0;
110 }
111
112 .exceptionContainer {
113 box-sizing: border-box;
114 font-family: 'Segoe UI', 'Lucida Grande', 'Helvetica Neue', Helvetica, Arial, sans-serif;
115 font-size: 14px;
116 padding-bottom: 20px;
117 }
118
119 .exceptionContainer * {
120 box-sizing: inherit;
121 color: #000;
122 line-height: 1.5em;
123 margin: 0;
124 padding: 0;
125 }
126
127 .exceptionHeader {
3bfbe63f 128 background-color: rgb(58, 109, 156);
314f7e0e
MW
129 padding: 30px 0;
130 }
131
132 .exceptionTitle {
133 color: #fff;
134 font-size: 28px;
135 font-weight: 300;
136 }
137
138 .exceptionErrorCode {
139 color: #fff;
140 margin-top: .5em;
141 }
142
143 .exceptionErrorCode .exceptionInlineCode {
3bfbe63f 144 background-color: rgb(43, 79, 113);
314f7e0e
MW
145 border-radius: 3px;
146 color: #fff;
147 font-family: monospace;
148 padding: 3px 10px;
149 white-space: nowrap;
150 }
151
152 .exceptionSubtitle {
153 border-bottom: 1px solid rgb(238, 238, 238);
154 color: rgb(44, 62, 80);
155 font-size: 24px;
156 font-weight: 300;
157 margin-bottom: 15px;
158 padding-bottom: 10px;
159 }
160
161 .exceptionContainer > .exceptionBoundary {
162 margin-top: 30px;
163 }
164
165 .exceptionText .exceptionInlineCodeWrapper {
166 border: 1px solid rgb(169, 169, 169);
167 border-radius: 3px;
168 padding: 2px 5px;
169 }
2d63c13c 170
314f7e0e
MW
171 .exceptionText .exceptionInlineCode {
172 font-family: monospace;
173 white-space: nowrap;
174 }
175
176 .exceptionFieldTitle {
177 color: rgb(59, 109, 169);
178 }
179
180 .exceptionFieldTitle .exceptionColon {
181 /* hide colon in browser, but will be visible after copy & paste */
182 opacity: 0;
183 }
184
185 .exceptionFieldValue {
186 font-size: 18px;
187 min-height: 1.5em;
188 }
189
190 .exceptionSystemInformation,
191 .exceptionErrorDetails,
192 .exceptionStacktrace {
193 list-style-type: none;
194 }
195
196 .exceptionSystemInformation > li:not(:first-child),
197 .exceptionErrorDetails > li:not(:first-child) {
198 margin-top: 10px;
199 }
200
201 .exceptionStacktrace {
202 display: block;
203 margin-top: 5px;
204 overflow: auto;
205 padding-bottom: 20px;
206 }
207
208 .exceptionStacktraceFile,
209 .exceptionStacktraceFile span,
210 .exceptionStacktraceCall,
211 .exceptionStacktraceCall span {
212 font-family: monospace !important;
213 white-space: nowrap !important;
214 }
215
216 .exceptionStacktraceCall + .exceptionStacktraceFile {
217 margin-top: 5px;
218 }
219
220 .exceptionStacktraceCall {
221 padding-left: 40px;
222 }
223
224 .exceptionStacktraceCall,
225 .exceptionStacktraceCall span {
226 color: rgb(102, 102, 102) !important;
227 font-size: 13px !important;
228 }
229
230 /* mobile */
231 @media (max-width: 767px) {
232 .exceptionBoundary {
233 min-width: 320px;
234 padding: 0 10px;
235 }
158bd3ca 236
314f7e0e
MW
237 .exceptionText .exceptionInlineCodeWrapper {
238 display: inline-block;
239 overflow: auto;
240 }
158bd3ca 241
314f7e0e
MW
242 .exceptionErrorCode .exceptionInlineCode {
243 font-size: 13px;
244 padding: 2px 5px;
245 }
246 }
247
248 /* desktop */
249 @media (min-width: 768px) {
250 .exceptionBoundary {
251 margin: 0 auto;
252 max-width: 1400px;
253 min-width: 1200px;
254 padding: 0 10px;
255 }
256
257 .exceptionSystemInformation {
258 display: flex;
259 flex-wrap: wrap;
260 }
261
262 .exceptionSystemInformation1,
263 .exceptionSystemInformation3,
264 .exceptionSystemInformation5 {
265 flex: 0 0 200px;
266 margin: 0 0 10px 0 !important;
267 }
268
269 .exceptionSystemInformation2,
270 .exceptionSystemInformation4,
271 .exceptionSystemInformation6 {
272 flex: 0 0 calc(100% - 210px);
273 margin: 0 0 10px 10px !important;
274 max-width: calc(100% - 210px);
275 }
276
277 .exceptionSystemInformation1 { order: 1; }
278 .exceptionSystemInformation2 { order: 2; }
279 .exceptionSystemInformation3 { order: 3; }
280 .exceptionSystemInformation4 { order: 4; }
281 .exceptionSystemInformation5 { order: 5; }
282 .exceptionSystemInformation6 { order: 6; }
283
284 .exceptionSystemInformation .exceptionFieldValue {
285 overflow: hidden;
286 text-overflow: ellipsis;
287 white-space: nowrap;
288 }
289 }
290 </style>
291</head>
292<body class="exceptionBody">
293 <div class="exceptionContainer">
294 <div class="exceptionHeader">
295 <div class="exceptionBoundary">
e8d7ee4d 296 <p class="exceptionTitle">An error has occurred</p>
314f7e0e 297 </div>
158bd3ca
TD
298 </div>
299
314f7e0e
MW
300 <div class="exceptionBoundary">
301 <p class="exceptionSubtitle">System Information</p>
302 <ul class="exceptionSystemInformation">
303 <li class="exceptionSystemInformation1">
304 <p class="exceptionFieldTitle">PHP Version<span class="exceptionColon">:</span></p>
305 <p class="exceptionFieldValue"><?php echo htmlentities(phpversion()); ?></p>
306 </li>
307 <li class="exceptionSystemInformation3">
308 <p class="exceptionFieldTitle">WoltLab Suite Core<span class="exceptionColon">:</span></p>
dd2d8c0c 309 <p class="exceptionFieldValue">5.2</p>
314f7e0e
MW
310 </li>
311 <li class="exceptionSystemInformation5">
312 <p class="exceptionFieldTitle">Peak Memory Usage<span class="exceptionColon">:</span></p>
313 <p class="exceptionFieldValue"><?php echo round(memory_get_peak_usage() / 1024 / 1024, 3); ?>/<?php echo ini_get('memory_limit'); ?></p>
314 </li>
315 <li class="exceptionSystemInformation2">
316 <p class="exceptionFieldTitle">Request URI<span class="exceptionColon">:</span></p>
317 <p class="exceptionFieldValue"><?php if (isset($_SERVER['REQUEST_URI'])) echo htmlentities($_SERVER['REQUEST_URI']); ?></p>
318 </li>
319 <li class="exceptionSystemInformation4">
320 <p class="exceptionFieldTitle">Referrer<span class="exceptionColon">:</span></p>
321 <p class="exceptionFieldValue"><?php if (isset($_SERVER['HTTP_REFERER'])) echo htmlentities($_SERVER['HTTP_REFERER']); ?></p>
322 </li>
323 <li class="exceptionSystemInformation6">
324 <p class="exceptionFieldTitle">User Agent<span class="exceptionColon">:</span></p>
325 <p class="exceptionFieldValue"><?php if (isset($_SERVER['HTTP_USER_AGENT'])) echo htmlentities($_SERVER['HTTP_USER_AGENT']); ?></p>
326 </li>
327 </ul>
328 </div>
329
330 <?php
3353ca3e 331 $e = $this;
314f7e0e
MW
332 $first = true;
333 do {
3353ca3e
AE
334 $trace = $e->getTrace();
335 if (isset($trace[0]['function']) && $trace[0]['function'] === 'handleException') {
336 // ignore repacked exception
337 continue;
338 }
339
314f7e0e
MW
340 ?>
341 <div class="exceptionBoundary">
3353ca3e 342 <p class="exceptionSubtitle"><?php if (!$e->getPrevious() && !$first) { echo "Original "; } else if ($e->getPrevious() && $first) { echo "Final "; } ?>Error</p>
2c4b885b 343 <?php if (($e instanceof SystemException || $e instanceof \wcf\system\exception\SystemException) && $e->getDescription()) { ?>
3353ca3e 344 <p class="exceptionText"><?php echo $e->getDescription(); ?></p>
314f7e0e
MW
345 <?php } ?>
346 <ul class="exceptionErrorDetails">
347 <li>
348 <p class="exceptionFieldTitle">Error Type<span class="exceptionColon">:</span></p>
3353ca3e 349 <p class="exceptionFieldValue"><?php echo htmlentities(get_class($e)); ?></p>
314f7e0e
MW
350 </li>
351 <li>
352 <p class="exceptionFieldTitle">Error Message<span class="exceptionColon">:</span></p>
3353ca3e 353 <p class="exceptionFieldValue"><?php echo htmlentities($e->getMessage()); ?></p>
314f7e0e 354 </li>
3353ca3e 355 <?php if ($e->getCode()) { ?>
314f7e0e
MW
356 <li>
357 <p class="exceptionFieldTitle">Error Code<span class="exceptionColon">:</span></p>
3353ca3e 358 <p class="exceptionFieldValue"><?php echo intval($e->getCode()); ?></p>
314f7e0e
MW
359 </li>
360 <?php } ?>
361 <li>
362 <p class="exceptionFieldTitle">File<span class="exceptionColon">:</span></p>
3353ca3e 363 <p class="exceptionFieldValue" style="word-break: break-all"><?php echo htmlentities($e->getFile()); ?> (<?php echo $e->getLine(); ?>)</p>
314f7e0e
MW
364 </li>
365
366 <li>
367 <p class="exceptionFieldTitle">Stack Trace<span class="exceptionColon">:</span></p>
368 <ul class="exceptionStacktrace">
369 <?php
3353ca3e 370 $trace = $e->getTrace();
314f7e0e
MW
371 for ($i = 0, $max = count($trace); $i < $max; $i++) {
372 ?>
373 <li class="exceptionStacktraceFile"><?php echo '#'.$i.' '.htmlentities($trace[$i]['file']).' ('.$trace[$i]['line'].')'.':'; ?></li>
374 <li class="exceptionStacktraceCall">
375 <?php
376 echo $trace[$i]['class'].$trace[$i]['type'].$trace[$i]['function'].'(';
377 echo implode(', ', array_map(function ($item) {
378 switch (gettype($item)) {
379 case 'integer':
380 case 'double':
381 return $item;
382 case 'NULL':
383 return 'null';
384 case 'string':
385 return "'".addcslashes(htmlentities($item), "\\'")."'";
386 case 'boolean':
387 return $item ? 'true' : 'false';
388 case 'array':
389 $keys = array_keys($item);
390 if (count($keys) > 5) return "[ ".count($keys)." items ]";
391 return '[ '.implode(', ', array_map(function ($item) {
392 return $item.' => ';
393 }, $keys)).']';
394 case 'object':
395 return get_class($item);
396 }
397
398 throw new \LogicException('Unreachable');
399 }, $trace[$i]['args']));
400 echo ')</li>';
401 }
402 ?>
403 </ul>
404 </li>
405 </ul>
406 </div>
407 <?php
408 $first = false;
3353ca3e
AE
409 } while ($e = $e->getPrevious());
410 ?>
158bd3ca
TD
411 </div>
412</body>
413</html>
414
415<?php
416 }
417}
418
158bd3ca
TD
419/**
420 * Loads the required classes automatically.
421 */
db8aa273 422spl_autoload_register(function($className) {
158bd3ca
TD
423 $namespaces = explode('\\', $className);
424 if (count($namespaces) > 1) {
425 // remove 'wcf' component
426 array_shift($namespaces);
427
428 $className = implode('/', $namespaces);
01bd2eff 429 $classPath = TMP_DIR . 'install/files/lib/' . $className . '.class.php';
158bd3ca
TD
430 if (file_exists($classPath)) {
431 require_once($classPath);
432 }
433 }
db8aa273 434});
158bd3ca
TD
435
436/**
437 * Escapes strings for execution in sql queries.
ac52543a
MS
438 *
439 * @param string $string
440 * @return string
158bd3ca
TD
441 */
442function escapeString($string) {
443 return \wcf\system\WCF::getDB()->escapeString($string);
444}
445
1a2817c4
AE
446/**
447 * Helper method to output debug data for all passed variables,
448 * uses `print_r()` for arrays and objects, `var_dump()` otherwise.
449 */
450function wcfDebug() {
451 echo "<pre>";
452
453 $args = func_get_args();
454 $length = count($args);
455 if ($length === 0) {
456 echo "ERROR: No arguments provided.<hr>";
457 }
458 else {
459 for ($i = 0; $i < $length; $i++) {
460 $arg = $args[$i];
461
462 echo "<h2>Argument {$i} (" . gettype($arg) . ")</h2>";
463
464 if (is_array($arg) || is_object($arg)) {
465 print_r($arg);
466 }
467 else {
468 var_dump($arg);
469 }
470
471 echo "<hr>";
472 }
473 }
474
475 $backtrace = debug_backtrace();
476
477 // output call location to help finding these debug outputs again
478 echo "wcfDebug() called in {$backtrace[0]['file']} on line {$backtrace[0]['line']}";
479
480 echo "</pre>";
481
482 exit;
483}
484
158bd3ca
TD
485/**
486 * Calls the show method on the given exception.
487 *
9a132951 488 * @param mixed $e
158bd3ca 489 */
9a132951
M
490function handleException($e) {
491 try {
492 if (!($e instanceof \Exception)) throw $e;
493
494 if ($e instanceof IPrintableException || $e instanceof \wcf\system\exception\IPrintableException) {
495 $e->show();
496 exit;
497 }
3353ca3e
AE
498
499 // repacking
500 (new SystemException($e->getMessage(), $e->getCode(), '', $e))->show();
501 exit;
9a132951
M
502 }
503 catch (\Throwable $exception) {
504 die("<pre>WCF::handleException() Unhandled exception: ".$exception->getMessage()."\n\n".$exception->getTraceAsString());
505 }
158bd3ca
TD
506}
507
508/**
509 * Catches php errors and throws instead a system exception.
510 *
511 * @param integer $errorNo
512 * @param string $message
513 * @param string $filename
514 * @param integer $lineNo
2b770bdd 515 * @throws SystemException
158bd3ca
TD
516 */
517function handleError($errorNo, $message, $filename, $lineNo) {
7f54edaa
TD
518 if (!(error_reporting() & $errorNo)) return;
519 $type = 'error';
520 switch ($errorNo) {
521 case 2: $type = 'warning';
522 break;
523 case 8: $type = 'notice';
524 break;
158bd3ca 525 }
7f54edaa
TD
526
527 throw new SystemException('PHP '.$type.' in file '.$filename.' ('.$lineNo.'): '.$message, 0);
158bd3ca
TD
528}
529
745a1974
AE
530if (!function_exists('is_countable')) {
531 function is_countable($var) { return is_array($var) || $var instanceof Countable || $var instanceof ResourceBundle || $var instanceof SimpleXmlElement; }
532}
533
3e823cfa 534/** @noinspection PhpMultipleClassesDeclarationsInOneFile */
158bd3ca
TD
535/**
536 * BasicFileUtil contains file-related functions.
537 *
f4f05aa5 538 * @package com.woltlab.wcf
158bd3ca
TD
539 * @author Marcel Werk
540 */
541class BasicFileUtil {
d8fa09e0
AE
542 /**
543 * chmod mode
544 * @var integer
545 */
546 protected static $mode = null;
547
158bd3ca
TD
548 /**
549 * Tries to find the temp folder.
550 *
551 * @return string
2b770bdd 552 * @throws SystemException
158bd3ca
TD
553 */
554 public static function getTempFolder() {
158bd3ca
TD
555 // use tmp folder in document root by default
556 if (!empty($_SERVER['DOCUMENT_ROOT'])) {
069cd37e
MW
557 if (strpos($_SERVER['DOCUMENT_ROOT'], 'strato') !== false) {
558 // strato bugfix
559 // create tmp folder in document root automatically
560 if (!@file_exists($_SERVER['DOCUMENT_ROOT'].'/tmp')) {
561 @mkdir($_SERVER['DOCUMENT_ROOT'].'/tmp/', 0777);
562 try {
563 self::makeWritable($_SERVER['DOCUMENT_ROOT'].'/tmp/');
564 }
565 catch (SystemException $e) {}
566 }
158bd3ca 567 }
069cd37e
MW
568 if (@file_exists($_SERVER['DOCUMENT_ROOT'].'/tmp') && @is_writable($_SERVER['DOCUMENT_ROOT'].'/tmp')) {
569 return $_SERVER['DOCUMENT_ROOT'].'/tmp/';
158bd3ca
TD
570 }
571 }
e3369fd2 572
069cd37e
MW
573 if (isset($_ENV['TMP']) && @is_writable($_ENV['TMP'])) {
574 return $_ENV['TMP'] . '/';
158bd3ca 575 }
069cd37e
MW
576 if (isset($_ENV['TEMP']) && @is_writable($_ENV['TEMP'])) {
577 return $_ENV['TEMP'] . '/';
578 }
579 if (isset($_ENV['TMPDIR']) && @is_writable($_ENV['TMPDIR'])) {
580 return $_ENV['TMPDIR'] . '/';
581 }
e3369fd2 582
069cd37e
MW
583 if (($path = ini_get('upload_tmp_dir')) && @is_writable($path)) {
584 return $path . '/';
585 }
586 if (@file_exists('/tmp/') && @is_writable('/tmp/')) {
587 return '/tmp/';
158bd3ca 588 }
069cd37e
MW
589 if (function_exists('session_save_path') && ($path = session_save_path()) && @is_writable($path)) {
590 return $path . '/';
591 }
2d63c13c 592
5805a819 593 $path = INSTALL_SCRIPT_DIR.'tmp/';
069cd37e
MW
594 if (@file_exists($path) && @is_writable($path)) {
595 return $path;
596 }
597 else {
598 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.');
599 }
600 }
601
602 /**
603 * Returns the temp folder for the installation.
604 *
605 * @return string
606 */
607 public static function getInstallTempFolder() {
608 $dir = self::getTempFolder() . TMP_FILE_PREFIX . '/';
609 @mkdir($dir);
610 self::makeWritable($dir);
158bd3ca 611
158bd3ca
TD
612 return $dir;
613 }
1232bce2
AE
614
615 /**
616 * Tries to make a file or directory writable. It starts of with the least
d8fa09e0 617 * permissions and goes up until 0666 for files and 0777 for directories.
1232bce2
AE
618 *
619 * @param string $filename
2b770bdd 620 * @throws SystemException
1232bce2
AE
621 */
622 public static function makeWritable($filename) {
043b049d 623 if (!file_exists($filename)) {
1232bce2 624 return;
158bd3ca
TD
625 }
626
d8fa09e0
AE
627 // determine mode
628 if (self::$mode === null) {
629 // do not use PHP_OS here, as this represents the system it was built on != running on
0436b618
AE
630 // php_uname() is forbidden on some strange hosts; PHP_EOL is reliable
631 if (PHP_EOL == "\r\n") {
632 // Windows
d8fa09e0
AE
633 self::$mode = 0777;
634 }
635 else {
0436b618 636 // anything but Windows
adbd8054
AE
637 clearstatcache();
638
d8fa09e0
AE
639 self::$mode = 0666;
640
0c1810be
AE
641 $tmpFilename = '__permissions_'.sha1(time()).'.txt';
642 @touch($tmpFilename);
d8fa09e0
AE
643
644 // create a new file and check the file owner, if it is the same
645 // as this file (uploaded through FTP), we can safely grant write
646 // permissions exclusively to the owner rather than everyone
0c1810be 647 if (file_exists($tmpFilename)) {
d8fa09e0 648 $scriptOwner = fileowner(__FILE__);
0c1810be 649 $fileOwner = fileowner($tmpFilename);
d8fa09e0
AE
650
651 if ($scriptOwner === $fileOwner) {
652 self::$mode = 0644;
653 }
654
0c1810be 655 @unlink($tmpFilename);
d8fa09e0
AE
656 }
657 }
658 }
158bd3ca 659
1232bce2 660 if (is_dir($filename)) {
d8fa09e0 661 if (self::$mode == 0644) {
7fe5312d 662 @chmod($filename, 0755);
1232bce2 663 }
d8fa09e0 664 else {
7fe5312d 665 @chmod($filename, 0777);
1232bce2
AE
666 }
667 }
d8fa09e0 668 else {
7fe5312d 669 @chmod($filename, self::$mode);
d8fa09e0
AE
670 }
671
672 if (!is_writable($filename)) {
673 throw new SystemException("Unable to make '".$filename."' writable. This is a misconfiguration of your server, please contact your system administrator or hosting provider.");
674 }
158bd3ca 675 }
e5521c73
TD
676
677 /**
678 * Removes a leading slash from the given path.
679 *
680 * @param string $path
681 * @return string
682 */
683 public static function removeLeadingSlash($path) {
684 return ltrim($path, '/');
685 }
686
687 /**
688 * Removes a trailing slash from the given path.
689 *
690 * @param string $path
691 * @return string
692 */
693 public static function removeTrailingSlash($path) {
694 return rtrim($path, '/');
695 }
696
697 /**
698 * Adds a trailing slash to the given path.
699 *
700 * @param string $path
701 * @return string
702 */
703 public static function addTrailingSlash($path) {
704 return rtrim($path, '/').'/';
705 }
706
707 /**
708 * Adds a leading slash to the given path.
709 *
710 * @param string $path
711 * @return string
712 */
713 public static function addLeadingSlash($path) {
714 return '/'.ltrim($path, '/');
715 }
716
717 /**
718 * Creates a path on the local filesystem and returns true on success.
719 * Parent directories do not need to exists as they will be created if
720 * necessary.
721 *
722 * @param string $path
723 * @return boolean
724 */
725 public static function makePath($path) {
726 // directory already exists, abort
727 if (file_exists($path)) {
728 return false;
729 }
730
731 // check if parent directory exists
732 $parent = dirname($path);
733 if ($parent != $path) {
734 // parent directory does not exist either
735 // we have to create the parent directory first
736 $parent = self::addTrailingSlash($parent);
737 if (!@file_exists($parent)) {
738 // could not create parent directory either => abort
739 if (!self::makePath($parent)) {
740 return false;
741 }
742 }
743
744 // well, the parent directory exists or has been created
745 // lets create this path
746 if (!@mkdir($path)) {
747 return false;
748 }
749
750 self::makeWritable($path);
751
752 return true;
753 }
754
755 return false;
756 }
158bd3ca
TD
757}
758
3e823cfa 759/** @noinspection PhpMultipleClassesDeclarationsInOneFile */
158bd3ca
TD
760/**
761 * Opens tar or tar.gz archives.
762 *
763 * Usage:
764 * ------
765 * $tar = new Tar('archive.tar');
766 * $contentList = $tar->getContentList();
767 * foreach ($contentList as $key => $val) {
768 * $tar->extract($key, DESTINATION);
769 * }
770 */
771class Tar {
e5521c73
TD
772 /**
773 * name of the archive
774 * @var string
775 */
158bd3ca 776 protected $archiveName = '';
e5521c73
TD
777
778 /**
779 * content of the tar file
780 * @var array
781 */
058cbd6a 782 protected $contentList = [];
e5521c73
TD
783
784 /**
785 * indicates if tar file is opened
786 * @var boolean
787 */
158bd3ca 788 protected $opened = false;
e5521c73
TD
789
790 /**
791 * indicates if file content has been read
792 * @var boolean
793 */
158bd3ca 794 protected $read = false;
e5521c73
TD
795
796 /**
797 * file object
798 * @var File
799 */
158bd3ca 800 protected $file = null;
e5521c73
TD
801
802 /**
803 * indicates if the tar file is (g)zipped
804 * @var boolean
805 */
158bd3ca 806 protected $isZipped = false;
e5521c73
TD
807
808 /**
809 * file access mode
810 * @var string
811 */
158bd3ca
TD
812 protected $mode = 'rb';
813
e5521c73
TD
814 /**
815 * chunk size for extracting
816 * @var integer
817 */
818 const CHUNK_SIZE = 8192;
819
158bd3ca
TD
820 /**
821 * Creates a new Tar object.
822 * archiveName must be tarball or gzipped tarball
e5521c73 823 *
39bea7dd 824 * @param string $archiveName
2b770bdd 825 * @throws SystemException
158bd3ca
TD
826 */
827 public function __construct($archiveName) {
158bd3ca 828 if (!is_file($archiveName)) {
4fe0b42b 829 throw new SystemException("unable to find tar archive '".$archiveName."'");
158bd3ca
TD
830 }
831
832 $this->archiveName = $archiveName;
833 $this->open();
834 $this->readContent();
835 }
836
837 /**
838 * Destructor of this class, closes tar archive.
839 */
840 public function __destruct() {
841 $this->close();
842 }
843
844 /**
845 * Opens the tar archive and stores filehandle.
846 */
847 public function open() {
848 if (!$this->opened) {
e5521c73 849 if ($this->isZipped) $this->file = new GZipFile($this->archiveName, $this->mode);
158bd3ca
TD
850 else {
851 // test compression
852 $this->file = new File($this->archiveName, $this->mode);
853 if ($this->file->read(2) == "\37\213") {
854 $this->file->close();
855 $this->isZipped = true;
e5521c73 856 $this->file = new GZipFile($this->archiveName, $this->mode);
158bd3ca
TD
857 }
858 else {
859 $this->file->seek(0);
860 }
861 }
862 $this->opened = true;
863 }
864 }
e5521c73 865
158bd3ca
TD
866 /**
867 * Closes the opened file.
868 */
869 public function close() {
870 if ($this->opened) {
871 $this->file->close();
872 $this->opened = false;
873 }
874 }
875
876 /**
e5521c73 877 * @inheritDoc
158bd3ca
TD
878 */
879 public function getContentList() {
880 if (!$this->read) {
881 $this->open();
882 $this->readContent();
883 }
884 return $this->contentList;
885 }
886
887 /**
e5521c73 888 * @inheritDoc
158bd3ca
TD
889 */
890 public function getFileInfo($fileIndex) {
891 if (!is_int($fileIndex)) {
892 $fileIndex = $this->getIndexByFilename($fileIndex);
893 }
894
895 if (!isset($this->contentList[$fileIndex])) {
6286572b 896 throw new SystemException("Tar: could find file '".$fileIndex."' in archive");
158bd3ca
TD
897 }
898 return $this->contentList[$fileIndex];
899 }
900
901 /**
e5521c73 902 * @inheritDoc
158bd3ca
TD
903 */
904 public function getIndexByFilename($filename) {
905 foreach ($this->contentList as $index => $file) {
906 if ($file['filename'] == $filename) {
907 return $index;
908 }
909 }
910 return false;
911 }
912
913 /**
e5521c73 914 * @inheritDoc
158bd3ca
TD
915 */
916 public function extractToString($index) {
917 if (!$this->read) {
918 $this->open();
919 $this->readContent();
920 }
921 $header = $this->getFileInfo($index);
922
923 // can not extract a folder
924 if ($header['type'] != 'file') {
925 return false;
926 }
927
928 // seek to offset
929 $this->file->seek($header['offset']);
930
931 // read data
12ed33df
TD
932 $content = $this->file->read($header['size']);
933
934 if (strlen($content) != $header['size']) {
935 throw new SystemException("Could not untar file '".$header['filename']."' to string. Maybe the archive is truncated?");
158bd3ca
TD
936 }
937
938 return $content;
939 }
940
941 /**
e5521c73 942 * @inheritDoc
158bd3ca
TD
943 */
944 public function extract($index, $destination) {
945 if (!$this->read) {
946 $this->open();
947 $this->readContent();
948 }
949 $header = $this->getFileInfo($index);
950
e5521c73
TD
951 BasicFileUtil::makePath(dirname($destination));
952 if ($header['type'] === 'folder') {
953 BasicFileUtil::makePath($destination);
954 return;
955 }
956 if ($header['type'] === 'symlink') {
957 // skip symlinks
958 return;
158bd3ca
TD
959 }
960
961 // seek to offset
962 $this->file->seek($header['offset']);
963
964 $targetFile = new File($destination);
965
e5521c73
TD
966 // read and write data
967 if ($header['size']) {
968 $buffer = $this->file->read($header['size']);
969 $targetFile->write($buffer);
158bd3ca 970 }
158bd3ca 971 $targetFile->close();
e5521c73 972
1232bce2 973 BasicFileUtil::makeWritable($destination);
158bd3ca
TD
974
975 if ($header['mtime']) {
976 @$targetFile->touch($header['mtime']);
977 }
978
979 // check filesize
980 if (filesize($destination) != $header['size']) {
4fe0b42b 981 throw new SystemException("Could not untar file '".$header['filename']."' to '".$destination."'. Maybe disk quota exceeded in folder '".dirname($destination)."'.");
158bd3ca
TD
982 }
983
984 return true;
985 }
986
987 /**
988 * Reads table of contents (TOC) from tar archive.
989 * This does not get the entire to memory but only parts of it.
990 */
991 protected function readContent() {
058cbd6a 992 $this->contentList = [];
158bd3ca
TD
993 $this->read = true;
994 $i = 0;
995
996 // Read the 512 bytes header
db8aa273 997 $longFilename = null;
158bd3ca
TD
998 while (strlen($binaryData = $this->file->read(512)) != 0) {
999 // read header
1000 $header = $this->readHeader($binaryData);
1001 if ($header === false) {
1002 continue;
1003 }
db8aa273
AE
1004
1005 // fixes a bug that files with long names aren't correctly
1006 // extracted
1007 if ($longFilename !== null) {
1008 $header['filename'] = $longFilename;
1009 $longFilename = null;
1010 }
1011 if ($header['typeflag'] == 'L') {
1012 $format = 'Z'.$header['size'].'filename';
1013
1014 $fileData = unpack($format, $this->file->read(512));
1015 $longFilename = $fileData['filename'];
1016 $header['size'] = 0;
1017 }
1018 // don't include the @LongLink file in the content list
1019 else {
1020 $this->contentList[$i] = $header;
1021 $this->contentList[$i]['index'] = $i;
1022 $i++;
1023 }
158bd3ca 1024
63b9817b 1025 $this->file->seek($this->file->tell() + (512 * ceil($header['size'] / 512)));
158bd3ca
TD
1026 }
1027 }
1028
1029 /**
1030 * Unpacks file header for one file entry.
db8aa273 1031 *
39bea7dd 1032 * @param string $binaryData
4be0ecec 1033 * @return array|boolean
158bd3ca
TD
1034 */
1035 protected function readHeader($binaryData) {
1036 if (strlen($binaryData) != 512) {
1037 return false;
1038 }
2d63c13c 1039
058cbd6a 1040 $header = [];
158bd3ca
TD
1041 $checksum = 0;
1042 // First part of the header
1043 for ($i = 0; $i < 148; $i++) {
1044 $checksum += ord(substr($binaryData, $i, 1));
1045 }
1046 // Calculate the checksum
1047 // Ignore the checksum value and replace it by ' ' (space)
1048 for ($i = 148; $i < 156; $i++) {
1049 $checksum += ord(' ');
1050 }
1051 // Last part of the header
1052 for ($i = 156; $i < 512; $i++) {
1053 $checksum += ord(substr($binaryData, $i, 1));
1054 }
2d63c13c 1055
db8aa273
AE
1056 // extract values
1057 $format = 'Z100filename/Z8mode/Z8uid/Z8gid/Z12size/Z12mtime/Z8checksum/Z1typeflag/Z100link/Z6magic/Z2version/Z32uname/Z32gname/Z8devmajor/Z8devminor/Z155prefix';
32b198a0
AE
1058
1059 $data = unpack($format, $binaryData);
158bd3ca
TD
1060
1061 // Extract the properties
379875ee 1062 $header['checksum'] = octdec(trim($data['checksum']));
158bd3ca
TD
1063 if ($header['checksum'] == $checksum) {
1064 $header['filename'] = trim($data['filename']);
379875ee
MS
1065 $header['mode'] = octdec(trim($data['mode']));
1066 $header['uid'] = octdec(trim($data['uid']));
1067 $header['gid'] = octdec(trim($data['gid']));
1068 $header['size'] = octdec(trim($data['size']));
1069 $header['mtime'] = octdec(trim($data['mtime']));
158bd3ca
TD
1070 $header['prefix'] = trim($data['prefix']);
1071 if ($header['prefix']) {
1072 $header['filename'] = $header['prefix'].'/'.$header['filename'];
1073 }
e5521c73
TD
1074 $header['typeflag'] = $data['typeflag'];
1075 if ($header['typeflag'] == '5') {
158bd3ca
TD
1076 $header['size'] = 0;
1077 $header['type'] = 'folder';
1078 }
e5521c73
TD
1079 else if ($header['typeflag'] == '2') {
1080 $header['type'] = 'symlink';
1081 $header['target'] = $data['link'];
1082 }
158bd3ca
TD
1083 else {
1084 $header['type'] = 'file';
1085 }
1086 $header['offset'] = $this->file->tell();
1087
1088 return $header;
1089 }
1090 else {
1091 return false;
1092 }
1093 }
e5521c73
TD
1094
1095 /**
1096 * Returns true if this tar is (g)zipped.
1097 *
1098 * @return boolean
1099 */
1100 public function isZipped() {
1101 return $this->isZipped;
1102 }
158bd3ca
TD
1103}
1104
3e823cfa 1105/** @noinspection PhpMultipleClassesDeclarationsInOneFile */
158bd3ca
TD
1106/**
1107 * The File class handles all file operations.
1108 *
1109 * Example:
1110 * using php functions:
1111 * $fp = fopen('filename', 'wb');
1112 * fwrite($fp, '...');
1113 * fclose($fp);
1114 *
1115 * using this class:
1116 * $file = new File('filename');
1117 * $file->write('...');
1118 * $file->close();
1119 *
1120 * @author Marcel Werk
1121 */
1122class File {
1123 protected $resource = null;
1124 protected $filename;
1125
1126 /**
1127 * Opens a new file.
1128 *
39bea7dd
MS
1129 * @param string $filename
1130 * @param string $mode
2b770bdd 1131 * @throws SystemException
158bd3ca
TD
1132 */
1133 public function __construct($filename, $mode = 'wb') {
1134 $this->filename = $filename;
1135 $this->resource = fopen($filename, $mode);
1136 if ($this->resource === false) {
4fe0b42b 1137 throw new SystemException('Can not open file ' . $filename);
158bd3ca
TD
1138 }
1139 }
1140
1141 /**
1142 * Calls the specified function on the open file.
1143 * Do not call this function directly. Use $file->write('') instead.
1144 *
39bea7dd
MS
1145 * @param string $function
1146 * @param array $arguments
71952a87 1147 * @return mixed
2b770bdd 1148 * @throws SystemException
158bd3ca
TD
1149 */
1150 public function __call($function, $arguments) {
1151 if (function_exists('f' . $function)) {
1152 array_unshift($arguments, $this->resource);
39bea7dd 1153 return call_user_func_array('f' . $function, $arguments);
158bd3ca
TD
1154 }
1155 else if (function_exists($function)) {
1156 array_unshift($arguments, $this->filename);
39bea7dd 1157 return call_user_func_array($function, $arguments);
158bd3ca
TD
1158 }
1159 else {
4fe0b42b 1160 throw new SystemException('Can not call file method ' . $function);
158bd3ca
TD
1161 }
1162 }
1163}
1164
3e823cfa 1165/** @noinspection PhpMultipleClassesDeclarationsInOneFile */
158bd3ca
TD
1166/**
1167 * The File class handles all file operations on a zipped file.
1168 *
1169 * @author Marcel Werk
1170 */
e5521c73 1171class GZipFile extends File {
eedfeca6
AE
1172 /**
1173 * checks if gz*64 functions are available instead of gz*
1174 * https://bugs.php.net/bug.php?id=53829
1175 * @var boolean
1176 */
1177 protected static $gzopen64 = null;
1178
e4bda351 1179 /** @noinspection PhpMissingParentConstructorInspection */
158bd3ca 1180 /**
e5521c73
TD
1181 * Opens a gzip file.
1182 *
39bea7dd
MS
1183 * @param string $filename
1184 * @param string $mode
2b770bdd 1185 * @throws SystemException
158bd3ca
TD
1186 */
1187 public function __construct($filename, $mode = 'wb') {
eedfeca6 1188 if (self::$gzopen64 === null) {
63b9817b 1189 self::$gzopen64 = function_exists('gzopen64');
eedfeca6
AE
1190 }
1191
158bd3ca 1192 $this->filename = $filename;
083a041c 1193 /** @noinspection PhpUndefinedFunctionInspection */
e5521c73 1194 $this->resource = (self::$gzopen64 ? gzopen64($filename, $mode) : gzopen($filename, $mode));
158bd3ca 1195 if ($this->resource === false) {
4fe0b42b 1196 throw new SystemException('Can not open file ' . $filename);
158bd3ca
TD
1197 }
1198 }
1199
1200 /**
1201 * Calls the specified function on the open file.
e5521c73 1202 *
39bea7dd
MS
1203 * @param string $function
1204 * @param array $arguments
71952a87 1205 * @return mixed
2b770bdd 1206 * @throws SystemException
158bd3ca
TD
1207 */
1208 public function __call($function, $arguments) {
eedfeca6
AE
1209 if (self::$gzopen64 && function_exists('gz' . $function . '64')) {
1210 array_unshift($arguments, $this->resource);
1211 return call_user_func_array('gz' . $function . '64', $arguments);
1212 }
1213 else if (function_exists('gz' . $function)) {
158bd3ca 1214 array_unshift($arguments, $this->resource);
39bea7dd 1215 return call_user_func_array('gz' . $function, $arguments);
158bd3ca
TD
1216 }
1217 else if (function_exists($function)) {
1218 array_unshift($arguments, $this->filename);
39bea7dd 1219 return call_user_func_array($function, $arguments);
158bd3ca
TD
1220 }
1221 else {
4fe0b42b 1222 throw new SystemException('Can not call method ' . $function);
158bd3ca
TD
1223 }
1224 }
1225
1226 /**
e5521c73
TD
1227 * Returns the filesize of the unzipped file.
1228 *
1229 * @return integer
158bd3ca
TD
1230 */
1231 public function getFileSize() {
1232 $byteBlock = 1<<14;
1233 $eof = $byteBlock;
1234
1235 // the correction is for zip files that are too small
1236 // to get in the first while loop
1237 $correction = 1;
1238 while ($this->seek($eof) == 0) {
1239 $eof += $byteBlock;
1240 $correction = 0;
1241 }
1242
1243 while ($byteBlock > 1) {
1244 $byteBlock >>= 1;
1245 $eof += $byteBlock * ($this->seek($eof) ? -1 : 1);
1246 }
1247
e5521c73 1248 if ($this->seek($eof) == -1) $eof--;
158bd3ca
TD
1249
1250 $this->rewind();
1251 return $eof - $correction;
1252 }
1253}
1254
1255// let's go
1256// get temp file prefix
1257if (isset($_REQUEST['tmpFilePrefix'])) {
1258 $prefix = preg_replace('/[^a-f0-9_]+/', '', $_REQUEST['tmpFilePrefix']);
1259}
1260else {
1261 $prefix = substr(sha1(uniqid(microtime())), 0, 8);
1262}
1263define('TMP_FILE_PREFIX', $prefix);
1264
1265// try to find the temp folder
99be741e 1266define('TMP_DIR', BasicFileUtil::getInstallTempFolder());
158bd3ca 1267
7da7f7cc
AE
1268/**
1269 * Reads a file resource from temp folder.
1270 *
1271 * @param string $key
1272 * @param string $directory
1273 */
1274function readFileResource($key, $directory) {
ec6e78b9 1275 if (preg_match('~[\w\-]+\.(css|jpg|png|svg|eot|woff|ttf)~', $_GET[$key], $match)) {
7da7f7cc
AE
1276 switch ($match[1]) {
1277 case 'css':
1278 header('Content-Type: text/css');
1279 break;
1280
1281 case 'jpg':
1282 header('Content-Type: image/jpg');
1283 break;
1284
1285 case 'png':
1286 header('Content-Type: image/png');
1287 break;
1288
1289 case 'svg':
1290 header('Content-Type: image/svg+xml');
1291 break;
ec6e78b9
MW
1292
1293 case 'eot':
1294 header('Content-Type: application/vnd.ms-fontobject');
1295 break;
1296
1297 case 'woff':
1298 header('Content-Type: application/font-woff');
1299 break;
1300
1301 case 'ttf':
1302 header('Content-Type: application/octet-stream');
1303 break;
7da7f7cc
AE
1304 }
1305
2d9861cd
AE
1306 header('Expires: '.gmdate('D, d M Y H:i:s', time() + 3600).' GMT');
1307 header('Last-Modified: Mon, 26 Jul 1997 05:00:00 GMT');
1308 header('Cache-Control: public, max-age=3600');
1309
7da7f7cc 1310 readfile($directory . $_GET[$key]);
158bd3ca
TD
1311 }
1312 exit;
1313}
1314
7da7f7cc
AE
1315// show image from temp folder
1316if (isset($_GET['showImage'])) {
1317 readFileResource('showImage', TMP_DIR . 'install/files/acp/images/');
1318}
1319// show icon from temp folder
1320if (isset($_GET['showIcon'])) {
1321 readFileResource('showIcon', TMP_DIR . 'install/files/icon/');
1322}
1323// show css from temp folder
1324if (isset($_GET['showCSS'])) {
e94c3830 1325 readFileResource('showCSS', TMP_DIR . 'install/files/acp/style/setup/');
7da7f7cc 1326}
ec6e78b9
MW
1327// show fonts from temp folder
1328if (isset($_GET['showFont'])) {
1329 readFileResource('showFont', TMP_DIR . 'install/files/font/');
1330}
7da7f7cc 1331
53e00c6b 1332// check whether setup files are already unzipped
158bd3ca
TD
1333if (!file_exists(TMP_DIR . 'install/files/lib/system/WCFSetup.class.php')) {
1334 // try to unzip all setup files into temp folder
1335 $tar = new Tar(SETUP_FILE);
1336 $contentList = $tar->getContentList();
15fa2802 1337 if (empty($contentList)) {
3a4862d3 1338 throw new SystemException("Cannot unpack 'WCFSetup.tar.gz'. File is probably broken.");
158bd3ca
TD
1339 }
1340
1341 foreach ($contentList as $file) {
1342 foreach ($neededFilesPattern as $pattern) {
1343 if (preg_match($pattern, $file['filename'])) {
1344 // create directory if not exists
1345 $dir = TMP_DIR . dirname($file['filename']);
1346 if (!@is_dir($dir)) {
1347 @mkdir($dir, 0777, true);
1232bce2 1348 BasicFileUtil::makeWritable($dir);
158bd3ca
TD
1349 }
1350
1351 $tar->extract($file['index'], TMP_DIR . $file['filename']);
1352 }
1353 }
1354 }
1355 $tar->close();
1356
1357 // create cache folders
1358 @mkdir(TMP_DIR . 'setup/lang/cache/', 0777);
1232bce2 1359 BasicFileUtil::makeWritable(TMP_DIR . 'setup/lang/cache/');
158bd3ca
TD
1360
1361 @mkdir(TMP_DIR . 'setup/template/compiled/', 0777);
1232bce2 1362 BasicFileUtil::makeWritable(TMP_DIR . 'setup/template/compiled/');
158bd3ca
TD
1363}
1364
1365if (!class_exists('wcf\system\WCFSetup')) {
3a4862d3 1366 throw new SystemException("Cannot find class 'WCFSetup'");
158bd3ca
TD
1367}
1368
1369// start setup
dcb3a44c 1370new \wcf\system\WCFSetup();