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