Merge pull request #152 from Gravatronics/sqlFile
[GitHub/WoltLab/WCF.git] / wcfsetup / install.php
1 <?php
2 /**
3 * This script tries to find the temp folder and unzip all setup files into.
4 *
5 * @author Marcel Werk
6 * @copyright 2001-2011 WoltLab GmbH
7 * @license GNU Lesser General Public License <http://opensource.org/licenses/lgpl-license.php>
8 */
9 // define constants
10 define('INSTALL_SCRIPT_DIR', dirname(__FILE__).'/');
11 define('SETUP_FILE', INSTALL_SCRIPT_DIR . 'WCFSetup.tar.gz');
12 define('NO_IMPORTS', 1);
13
14 // set exception handler
15 set_exception_handler('handleException');
16 // set php error handler
17 set_error_handler('handleError', E_ALL);
18
19 // define list of needed file
20 $neededFilesPattern = array(
21 '!^setup/.*!',
22 '!^install/files/lib/data/.*!',
23 '!^install/files/lib/system/.*!',
24 '!^install/files/lib/util/.*!',
25 '!^install/files/acp/images/setup.*!',
26 '!^install/lang/.*!',
27 '!^install/packages/.*!');
28
29 // define needed functions and classes
30 /**
31 * WCF::handleException() calls the show method on exceptions that implement this interface.
32 *
33 * @package com.woltlab.wcf.system.exception
34 * @author Marcel Werk
35 */
36 interface IPrintableException {
37 public function show();
38 }
39
40 // define needed classes
41 // needed are:
42 // SystemException, PrintableException, BasicFileUtil, Tar, File, ZipFile
43 /**
44 * A SystemException is thrown when an unexpected error occurs.
45 *
46 * @package com.woltlab.wcf.system.exception
47 * @author Marcel Werk
48 */
49 class SystemException extends \Exception implements IPrintableException {
50 protected $description;
51 protected $information = '';
52 protected $functions = '';
53
54 /**
55 * Creates a new SystemException.
56 *
57 * @param message string error message
58 * @param code integer error code
59 * @param description string description of the error
60 */
61 public function __construct($message = '', $code = 0, $description = '') {
62 parent::__construct($message, $code);
63 $this->description = $description;
64 }
65
66 /**
67 * Returns the description of this exception.
68 *
69 * @return string
70 */
71 public function getDescription() {
72 return $this->description;
73 }
74
75 /**
76 * Prints this exception.
77 * This method is called by WCF::handleException().
78 */
79 public function show() {
80 ?>
81 <html>
82 <head>
83 <title>Fatal error: <?php echo htmlspecialchars($this->getMessage()); ?></title>
84
85 <style type="text/css">
86 body {
87 font-family: Verdana, Helvetica, sans-serif;
88 font-size: 0.8em;
89 }
90 div {
91 border: 1px outset lightgrey;
92 padding: 3px;
93 background-color: lightgrey;
94 }
95
96 div div {
97 border: 1px inset lightgrey;
98 padding: 4px;
99 }
100
101 h1 {
102 background-color: #154268;
103 padding: 4px;
104 color: #fff;
105 margin: 0 0 3px 0;
106 font-size: 1.15em;
107 }
108 h2 {
109 font-size: 1.1em;
110 margin-bottom: 0;
111 }
112
113 pre, p {
114 margin: 0;
115 }
116 </style>
117 </head>
118
119 <body>
120 <div>
121 <h1>Fatal error: <?php echo htmlspecialchars($this->getMessage()); ?></h1>
122
123 <div>
124 <p><?php echo $this->getDescription(); ?></p>
125 <?php if ($this->getCode()) { ?><p>You get more information about the problem in our knowledge base: <a href="http://www.woltlab.com/help/?code=<?php echo intval($this->getCode()); ?>">http://www.woltlab.com/help/?code=<?php echo intval($this->getCode()); ?></a></p><?php } ?>
126
127 <h2>Information:</h2>
128 <p>
129 <b>error message:</b> <?php echo htmlspecialchars($this->getMessage()); ?><br />
130 <b>error code:</b> <?php echo intval($this->getCode()); ?><br />
131 <?php echo $this->information; ?>
132 <b>file:</b> <?php echo htmlspecialchars($this->getFile()); ?> (<?php echo $this->getLine(); ?>)<br />
133 <b>php version:</b> <?php echo htmlspecialchars(phpversion()); ?><br />
134 <b>wcf version:</b> <?php if (defined('WCF_VERSION')) echo WCF_VERSION; ?><br />
135 <b>date:</b> <?php echo gmdate('r'); ?><br />
136 <b>request:</b> <?php if (isset($_SERVER['REQUEST_URI'])) echo htmlspecialchars($_SERVER['REQUEST_URI']); ?><br />
137 <b>referer:</b> <?php if (isset($_SERVER['HTTP_REFERER'])) echo htmlspecialchars($_SERVER['HTTP_REFERER']); ?><br />
138 </p>
139
140 <h2>Stacktrace:</h2>
141 <pre><?php echo htmlspecialchars($this->getTraceAsString()); ?></pre>
142 </div>
143
144 <?php echo $this->functions; ?>
145 </div>
146 </body>
147 </html>
148
149 <?php
150 }
151 }
152
153
154 /**
155 * Loads the required classes automatically.
156 */
157 function __autoload($className) {
158 $namespaces = explode('\\', $className);
159 if (count($namespaces) > 1) {
160 // remove 'wcf' component
161 array_shift($namespaces);
162
163 $className = implode('/', $namespaces);
164 $classPath = TMP_DIR . 'install/files/lib/' . $className . '.class.php';
165 if (file_exists($classPath)) {
166 require_once($classPath);
167 }
168 }
169 }
170
171 /**
172 * Escapes strings for execution in sql queries.
173 */
174 function escapeString($string) {
175 return \wcf\system\WCF::getDB()->escapeString($string);
176 }
177
178 /**
179 * Calls the show method on the given exception.
180 *
181 * @param Exception $e
182 */
183 function handleException(\Exception $e) {
184 if ($e instanceof IPrintableException || $e instanceof \wcf\system\exception\IPrintableException) {
185 $e->show();
186 exit;
187 }
188
189 print $e;
190 }
191
192 /**
193 * Catches php errors and throws instead a system exception.
194 *
195 * @param integer $errorNo
196 * @param string $message
197 * @param string $filename
198 * @param integer $lineNo
199 */
200 function handleError($errorNo, $message, $filename, $lineNo) {
201 if (error_reporting() != 0) {
202 $type = 'error';
203 switch ($errorNo) {
204 case 2: $type = 'warning';
205 break;
206 case 8: $type = 'notice';
207 break;
208 }
209
210 throw new SystemException('PHP '.$type.' in file '.$filename.' ('.$lineNo.'): '.$message, 0);
211 }
212 }
213
214 /**
215 * BasicFileUtil contains file-related functions.
216 *
217 * @package com.woltlab.wcf.util
218 * @author Marcel Werk
219 */
220 class BasicFileUtil {
221 /**
222 * Tries to find the temp folder.
223 *
224 * @return string
225 */
226 public static function getTempFolder() {
227 $tmpDirName = TMP_FILE_PREFIX.'/';
228
229 // use tmp folder in document root by default
230 if (!empty($_SERVER['DOCUMENT_ROOT'])) {
231 if (!@file_exists($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName)) {
232 @mkdir($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName, 0777, true);
233 @chmod($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName, 0777);
234 }
235
236 if (@file_exists($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName) && @is_writable($_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName)) {
237 return $_SERVER['DOCUMENT_ROOT'].'/tmp/'.$tmpDirName;
238 }
239 }
240
241 foreach (array('TMP', 'TEMP', 'TMPDIR') as $tmpDir) {
242 if (isset($_ENV[$tmpDir]) && @is_writable($_ENV[$tmpDir])) {
243 $dir = $_ENV[$tmpDir] . '/' . $tmpDirName;
244 @mkdir($dir, 0777);
245 @chmod($dir, 0777);
246
247 if (@file_exists($dir) && @is_writable($dir)) {
248 return $dir;
249 }
250 }
251 }
252
253 $dir = INSTALL_SCRIPT_DIR . 'tmp/' . $tmpDirName;
254 @mkdir($dir, 0777);
255 @chmod($dir, 0777);
256
257 if (!@file_exists($dir) || !@is_writable($dir)) {
258 $tmpDir = explode('/', $dir);
259 array_pop($tmpDir);
260 $dir = implode('/', $tmpDir);
261
262 throw new SystemException('There is no access to the system temporary folder due to an unknown reason and no user specific temporary folder exists in '.INSTALL_SCRIPT_DIR.'! This is a misconfiguration of your webserver software! Please create a folder called '.$dir.' using your favorite ftp program, make it writable and then retry this installation.');
263 }
264
265 return $dir;
266 }
267 }
268
269 /**
270 * Opens tar or tar.gz archives.
271 *
272 * Usage:
273 * ------
274 * $tar = new Tar('archive.tar');
275 * $contentList = $tar->getContentList();
276 * foreach ($contentList as $key => $val) {
277 * $tar->extract($key, DESTINATION);
278 * }
279 */
280 class Tar {
281 protected $archiveName = '';
282 protected $contentList = array();
283 protected $opened = false;
284 protected $read = false;
285 protected $file = null;
286 protected $isZipped = false;
287 protected $mode = 'rb';
288
289 /**
290 * Creates a new Tar object.
291 * archiveName must be tarball or gzipped tarball
292 *
293 * @param string $archiveName
294 */
295 public function __construct($archiveName) {
296 $match = array();
297 if (!is_file($archiveName)) {
298 throw new SystemException("unable to find tar archive '".$archiveName."'");
299 }
300
301 $this->archiveName = $archiveName;
302 $this->open();
303 $this->readContent();
304 }
305
306 /**
307 * Destructor of this class, closes tar archive.
308 */
309 public function __destruct() {
310 $this->close();
311 }
312
313 /**
314 * Opens the tar archive and stores filehandle.
315 */
316 public function open() {
317 if (!$this->opened) {
318 if ($this->isZipped) $this->file = new ZipFile($this->archiveName, $this->mode);
319 else {
320 // test compression
321 $this->file = new File($this->archiveName, $this->mode);
322 if ($this->file->read(2) == "\37\213") {
323 $this->file->close();
324 $this->isZipped = true;
325 $this->file = new ZipFile($this->archiveName, $this->mode);
326 }
327 else {
328 $this->file->seek(0);
329 }
330 }
331 $this->opened = true;
332 }
333 }
334
335 /**
336 * Closes the opened file.
337 */
338 public function close() {
339 if ($this->opened) {
340 $this->file->close();
341 $this->opened = false;
342 }
343 }
344
345 /**
346 * Returns the table of contents (TOC) list for this tar archive.
347 *
348 * @return array list of content
349 */
350 public function getContentList() {
351 if (!$this->read) {
352 $this->open();
353 $this->readContent();
354 }
355 return $this->contentList;
356 }
357
358 /**
359 * Returns an associative array with information
360 * about a specific file in the archive.
361 *
362 * @param mixed $fileindex index or name of the requested file
363 * @return array $fileInfo
364 */
365 public function getFileInfo($fileIndex) {
366 if (!is_int($fileIndex)) {
367 $fileIndex = $this->getIndexByFilename($fileIndex);
368 }
369
370 if (!isset($this->contentList[$fileIndex])) {
371 throw new SystemException("Tar: could find file '$index' in archive");
372 }
373 return $this->contentList[$fileIndex];
374 }
375
376 /**
377 * Searchs a file in the tar archive
378 * and returns the numeric fileindex.
379 * Returns false if not found.
380 *
381 * @param string $filename
382 * @return integer index of the requested file
383 */
384 public function getIndexByFilename($filename) {
385 foreach ($this->contentList as $index => $file) {
386 if ($file['filename'] == $filename) {
387 return $index;
388 }
389 }
390 return false;
391 }
392
393 /**
394 * Extracts a specific file and returns the content as string.
395 * Returns false if extraction failed.
396 *
397 * @param mixed $index index or name of the requested file
398 * @return string content of the requested file
399 */
400 public function extractToString($index) {
401 if (!$this->read) {
402 $this->open();
403 $this->readContent();
404 }
405 $header = $this->getFileInfo($index);
406
407 // can not extract a folder
408 if ($header['type'] != 'file') {
409 return false;
410 }
411
412 // seek to offset
413 $this->file->seek($header['offset']);
414
415 // read data
416 $content = '';
417 $n = floor($header['size'] / 512);
418 for($i = 0; $i < $n; $i++) {
419 $content .= $this->file->read(512);
420 }
421 if(($header['size'] % 512) != 0) {
422 $buffer = $this->file->read(512);
423 $content .= substr($buffer, 0, ($header['size'] % 512));
424 }
425
426 return $content;
427 }
428
429 /**
430 * Extracts a specific file and writes it's content
431 * to the file specified with $destination.
432 *
433 * @param mixed $index index or name of the requested file
434 * @param string $destination
435 * @return boolean $success
436 */
437 public function extract($index, $destination) {
438 if (!$this->read) {
439 $this->open();
440 $this->readContent();
441 }
442 $header = $this->getFileInfo($index);
443
444 // can not extract a folder
445 if ($header['type'] != 'file') {
446 return false;
447 }
448
449 // seek to offset
450 $this->file->seek($header['offset']);
451
452 $targetFile = new File($destination);
453
454 // read data
455 $n = floor($header['size'] / 512);
456 for ($i = 0; $i < $n; $i++) {
457 $content = $this->file->read(512);
458 $targetFile->write($content, 512);
459 }
460 if (($header['size'] % 512) != 0) {
461 $content = $this->file->read(512);
462 $targetFile->write($content, ($header['size'] % 512));
463 }
464
465 $targetFile->close();
466 if (function_exists('apache_get_version') || !@$targetFile->is_writable()) {
467 @$targetFile->chmod(0777);
468 }
469 else {
470 @$targetFile->chmod(0755);
471 }
472
473 if ($header['mtime']) {
474 @$targetFile->touch($header['mtime']);
475 }
476
477 // check filesize
478 if (filesize($destination) != $header['size']) {
479 throw new SystemException("Could not untar file '".$header['filename']."' to '".$destination."'. Maybe disk quota exceeded in folder '".dirname($destination)."'.");
480 }
481
482 return true;
483 }
484
485 /**
486 * Reads table of contents (TOC) from tar archive.
487 * This does not get the entire to memory but only parts of it.
488 */
489 protected function readContent() {
490 $this->contentList = array();
491 $this->read = true;
492 $i = 0;
493
494 // Read the 512 bytes header
495 while (strlen($binaryData = $this->file->read(512)) != 0) {
496 // read header
497 $header = $this->readHeader($binaryData);
498 if ($header === false) {
499 continue;
500 }
501 $this->contentList[$i] = $header;
502 $this->contentList[$i]['index'] = $i;
503 $i++;
504
505 $this->file->seek($this->file->tell() + (512 * ceil(($header['size'] / 512))));
506 }
507 }
508
509 /**
510 * Unpacks file header for one file entry.
511 *
512 * @param string $binaryData
513 * @return array $fileheader
514 */
515 protected function readHeader($binaryData) {
516 if (strlen($binaryData) != 512) {
517 return false;
518 }
519
520 $header = array();
521 $checksum = 0;
522 // First part of the header
523 for ($i = 0; $i < 148; $i++) {
524 $checksum += ord(substr($binaryData, $i, 1));
525 }
526 // Calculate the checksum
527 // Ignore the checksum value and replace it by ' ' (space)
528 for ($i = 148; $i < 156; $i++) {
529 $checksum += ord(' ');
530 }
531 // Last part of the header
532 for ($i = 156; $i < 512; $i++) {
533 $checksum += ord(substr($binaryData, $i, 1));
534 }
535
536 // Extract the values
537 //$data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor", $binaryData);
538 $data = unpack("a100filename/a8mode/a8uid/a8gid/a12size/a12mtime/a8checksum/a1typeflag/a100link/a6magic/a2version/a32uname/a32gname/a8devmajor/a8devminor/a155prefix", $binaryData);
539
540 // Extract the properties
541 $header['checksum'] = octDec(trim($data['checksum']));
542 if ($header['checksum'] == $checksum) {
543 $header['filename'] = trim($data['filename']);
544 $header['mode'] = octDec(trim($data['mode']));
545 $header['uid'] = octDec(trim($data['uid']));
546 $header['gid'] = octDec(trim($data['gid']));
547 $header['size'] = octDec(trim($data['size']));
548 $header['mtime'] = octDec(trim($data['mtime']));
549 $header['prefix'] = trim($data['prefix']);
550 if ($header['prefix']) {
551 $header['filename'] = $header['prefix'].'/'.$header['filename'];
552 }
553 if (($header['typeflag'] = $data['typeflag']) == '5') {
554 $header['size'] = 0;
555 $header['type'] = 'folder';
556 }
557 else {
558 $header['type'] = 'file';
559 }
560 $header['offset'] = $this->file->tell();
561
562 return $header;
563 }
564 else {
565 return false;
566 }
567 }
568 }
569
570 /**
571 * The File class handles all file operations.
572 *
573 * Example:
574 * using php functions:
575 * $fp = fopen('filename', 'wb');
576 * fwrite($fp, '...');
577 * fclose($fp);
578 *
579 * using this class:
580 * $file = new File('filename');
581 * $file->write('...');
582 * $file->close();
583 *
584 * @author Marcel Werk
585 */
586 class File {
587 protected $resource = null;
588 protected $filename;
589
590 /**
591 * Opens a new file.
592 *
593 * @param string $filename
594 * @param string $mode
595 */
596 public function __construct($filename, $mode = 'wb') {
597 $this->filename = $filename;
598 $this->resource = fopen($filename, $mode);
599 if ($this->resource === false) {
600 throw new SystemException('Can not open file ' . $filename);
601 }
602 }
603
604 /**
605 * Calls the specified function on the open file.
606 * Do not call this function directly. Use $file->write('') instead.
607 *
608 * @param string $function
609 * @param array $arguments
610 */
611 public function __call($function, $arguments) {
612 if (function_exists('f' . $function)) {
613 array_unshift($arguments, $this->resource);
614 return call_user_func_array('f' . $function, $arguments);
615 }
616 else if (function_exists($function)) {
617 array_unshift($arguments, $this->filename);
618 return call_user_func_array($function, $arguments);
619 }
620 else {
621 throw new SystemException('Can not call file method ' . $function);
622 }
623 }
624 }
625
626 /**
627 * The File class handles all file operations on a zipped file.
628 *
629 * @author Marcel Werk
630 */
631 class ZipFile extends File {
632 /**
633 * Opens a new zipped file.
634 *
635 * @param string $filename
636 * @param string $mode
637 */
638 public function __construct($filename, $mode = 'wb') {
639 $this->filename = $filename;
640 if (!function_exists('gzopen')) {
641 throw new SystemException('Can not find functions of the zlib extension');
642 }
643 $this->resource = @gzopen($filename, $mode);
644 if ($this->resource === false) {
645 throw new SystemException('Can not open file ' . $filename);
646 }
647 }
648
649 /**
650 * Calls the specified function on the open file.
651 *
652 * @param string $function
653 * @param array $arguments
654 */
655 public function __call($function, $arguments) {
656 if (function_exists('gz' . $function)) {
657 array_unshift($arguments, $this->resource);
658 return call_user_func_array('gz' . $function, $arguments);
659 }
660 else if (function_exists($function)) {
661 array_unshift($arguments, $this->filename);
662 return call_user_func_array($function, $arguments);
663 }
664 else {
665 throw new SystemException('Can not call method ' . $function);
666 }
667 }
668
669 /**
670 * Returns the filesize of the unzipped file
671 */
672 public function getFileSize() {
673 $byteBlock = 1<<14;
674 $eof = $byteBlock;
675
676 // the correction is for zip files that are too small
677 // to get in the first while loop
678 $correction = 1;
679 while ($this->seek($eof) == 0) {
680 $eof += $byteBlock;
681 $correction = 0;
682 }
683
684 while ($byteBlock > 1) {
685 $byteBlock >>= 1;
686 $eof += $byteBlock * ($this->seek($eof) ? -1 : 1);
687 }
688
689 if ($this->seek($eof) == -1) $eof -= 1;
690
691 $this->rewind();
692 return $eof - $correction;
693 }
694 }
695
696 // let's go
697 // get temp file prefix
698 if (isset($_REQUEST['tmpFilePrefix'])) {
699 $prefix = preg_replace('/[^a-f0-9_]+/', '', $_REQUEST['tmpFilePrefix']);
700 }
701 else {
702 $prefix = substr(sha1(uniqid(microtime())), 0, 8);
703 }
704 define('TMP_FILE_PREFIX', $prefix);
705
706 // try to find the temp folder
707 define('TMP_DIR', BasicFileUtil::getTempFolder());
708
709 // show image from temp folder
710 if (isset($_GET['showImage'])) {
711 if (preg_match('~[\w\-]+\.(jpg|png)~', $_GET['showImage'], $match)) {
712 if ($match[1] == 'jpg') header('Content-Type: image/jpg');
713 else header('Content-Type: image/png');
714 readfile(TMP_DIR . 'install/files/acp/images/' . $_GET['showImage']);
715 }
716 exit;
717 }
718
719 // check whether setup files are already unzipped
720 if (!file_exists(TMP_DIR . 'install/files/lib/system/WCFSetup.class.php')) {
721 // try to unzip all setup files into temp folder
722 $tar = new Tar(SETUP_FILE);
723 $contentList = $tar->getContentList();
724 if (!count($contentList)) {
725 throw new SystemException("Can not unpack 'WCFSetup.tar.gz'. File is probably broken.");
726 }
727
728 foreach ($contentList as $file) {
729 foreach ($neededFilesPattern as $pattern) {
730 if (preg_match($pattern, $file['filename'])) {
731 // create directory if not exists
732 $dir = TMP_DIR . dirname($file['filename']);
733 if (!@is_dir($dir)) {
734 @mkdir($dir, 0777, true);
735 @chmod($dir, 0777);
736 }
737
738 $tar->extract($file['index'], TMP_DIR . $file['filename']);
739 }
740 }
741 }
742 $tar->close();
743
744 // create cache folders
745 @mkdir(TMP_DIR . 'setup/lang/cache/', 0777);
746 @chmod(TMP_DIR . 'setup/lang/cache/', 0777);
747
748 @mkdir(TMP_DIR . 'setup/template/compiled/', 0777);
749 @chmod(TMP_DIR . 'setup/template/compiled/', 0777);
750 }
751
752 if (!class_exists('wcf\system\WCFSetup')) {
753 throw new SystemException("Can not find class 'WCFSetup'");
754 }
755
756 // start setup
757 new \wcf\system\WCFSetup();