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