· 6 years ago · Mar 15, 2019, 12:24 PM
1<?php
2
3if (!defined('KB_IN_BYTES')) { define('KB_IN_BYTES', 1024); }
4if (!defined('MB_IN_BYTES')) { define('MB_IN_BYTES', 1024 * KB_IN_BYTES); }
5if (!defined('GB_IN_BYTES')) { define('GB_IN_BYTES', 1024 * MB_IN_BYTES); }
6if (!defined('DUPLICATOR_PHP_MAX_MEMORY')) { define('DUPLICATOR_PHP_MAX_MEMORY', 4096 * MB_IN_BYTES); }
7
8date_default_timezone_set('UTC'); // Some machines don’t have this set so just do it here.
9@ignore_user_abort(true);
10@set_time_limit(3600);
11@ini_set('memory_limit', DUPLICATOR_PHP_MAX_MEMORY);
12@ini_set('max_input_time', '-1');
13@ini_set('pcre.backtrack_limit', PHP_INT_MAX);
14@ini_set('default_socket_timeout', 3600);
15
16class DUPX_CSRF {
17
18 /** Session var name
19 * @var string
20 */
21 public static $prefix = '_DUPX_CSRF';
22
23 /** Generate DUPX_CSRF value for form
24 * @param string $form - Form name as session key
25 * @return string - token
26 */
27 public static function generate($form = NULL) {
28 if (!empty($_COOKIE[DUPX_CSRF::$prefix . '_' . $form])) {
29 $token = $_COOKIE[DUPX_CSRF::$prefix . '_' . $form];
30 } else {
31 $token = DUPX_CSRF::token() . DUPX_CSRF::fingerprint();
32 }
33 $cookieName = DUPX_CSRF::$prefix . '_' . $form;
34 $ret = DUPX_CSRF::setCookie($cookieName, $token);
35 return $token;
36 }
37
38 /** Check DUPX_CSRF value of form
39 * @param string $token - Token
40 * @param string $form - Form name as session key
41 * @return boolean
42 */
43 public static function check($token, $form = NULL) {
44 if (!self::isCookieEnabled()) {
45 return true;
46 }
47 if (isset($_COOKIE[DUPX_CSRF::$prefix . '_' . $form]) && $_COOKIE[DUPX_CSRF::$prefix . '_' . $form] == $token) { // token OK
48 return (substr($token, -32) == DUPX_CSRF::fingerprint()); // fingerprint OK?
49 }
50 return FALSE;
51 }
52
53 /** Generate token
54 * @param void
55 * @return string
56 */
57 protected static function token() {
58 mt_srand((double) microtime() * 10000);
59 $charid = strtoupper(md5(uniqid(rand(), TRUE)));
60 return substr($charid, 0, 8) . substr($charid, 8, 4) . substr($charid, 12, 4) . substr($charid, 16, 4) . substr($charid, 20, 12);
61 }
62
63 /** Returns "digital fingerprint" of user
64 * @param void
65 * @return string - MD5 hashed data
66 */
67 protected static function fingerprint() {
68 return strtoupper(md5(implode('|', array($_SERVER['REMOTE_ADDR'], $_SERVER['HTTP_USER_AGENT']))));
69 }
70
71 public static function setCookie($cookieName, $cookieVal) {
72 $_COOKIE[$cookieName] = $cookieVal;
73 return setcookie($cookieName, $cookieVal, time() + 10800, '/');
74 }
75
76 /**
77 * @return bool
78 */
79 protected static function isCookieEnabled() {
80 return (count($_COOKIE) > 0);
81 }
82
83 public static function resetAllTokens() {
84 foreach ($_COOKIE as $cookieName => $cookieVal) {
85 $step1Key = DUPX_CSRF::$prefix . '_step1';
86 if ($step1Key != $cookieName && (0 === strpos($cookieName, DUPX_CSRF::$prefix) || 'archive' == $cookieName || 'bootloader' == $cookieName)) {
87 setcookie($cookieName, '', time() - 86400, '/');
88 unset($_COOKIE[$cookieName]);
89 }
90 }
91 }
92}
93
94/**
95 * Bootstrap utility to exatract the core installer
96 *
97 * Standard: PSR-2
98 *
99 * @package SC\DUPX\Bootstrap
100 * @link http://www.php-fig.org/psr/psr-2/
101 *
102 * To force extraction mode:
103 * installer.php?unzipmode=auto
104 * installer.php?unzipmode=ziparchive
105 * installer.php?unzipmode=shellexec
106 */
107
108abstract class DUPX_Bootstrap_Zip_Mode
109{
110 const AutoUnzip = 0;
111 const ZipArchive = 1;
112 const ShellExec = 2;
113}
114
115abstract class DUPX_Connectivity
116{
117 const OK = 0;
118 const Error = 1;
119 const Unknown = 2;
120}
121
122class DUPX_Bootstrap
123{
124 //@@ Params get dynamically swapped when package is built
125 const ARCHIVE_FILENAME = '20190315_portfoliolauren_ff5f72aa37dff1662663_20190315115257_archive.zip';
126 const ARCHIVE_SIZE = '53254803';
127 const INSTALLER_DIR_NAME = 'dup-installer';
128 const PACKAGE_HASH = 'ff5f72a-15115257';
129 const VERSION = '1.3.8';
130
131 public $hasZipArchive = false;
132 public $hasShellExecUnzip = false;
133 public $mainInstallerURL;
134 public $installerContentsPath;
135 public $installerExtractPath;
136 public $archiveExpectedSize = 0;
137 public $archiveActualSize = 0;
138 public $activeRatio = 0;
139
140 /**
141 * Instantiate the Bootstrap Object
142 *
143 * @return null
144 */
145 public function __construct()
146 {
147 //ARCHIVE_SIZE will be blank with a root filter so we can estimate
148 //the default size of the package around 17.5MB (18088000)
149 $archiveActualSize = @filesize(self::ARCHIVE_FILENAME);
150 $archiveActualSize = ($archiveActualSize !== false) ? $archiveActualSize : 0;
151 $this->hasZipArchive = class_exists('ZipArchive');
152 $this->hasShellExecUnzip = $this->getUnzipFilePath() != null ? true : false;
153 $this->installerContentsPath = str_replace("\\", '/', (dirname(__FILE__). '/' .self::INSTALLER_DIR_NAME));
154 $this->installerExtractPath = str_replace("\\", '/', (dirname(__FILE__)));
155 $this->archiveExpectedSize = strlen(self::ARCHIVE_SIZE) ? self::ARCHIVE_SIZE : 0 ;
156 $this->archiveActualSize = $archiveActualSize;
157
158 if($this->archiveExpectedSize > 0) {
159 $this->archiveRatio = (((1.0) * $this->archiveActualSize) / $this->archiveExpectedSize) * 100;
160 } else {
161 $this->archiveRatio = 100;
162 }
163
164 $this->overwriteMode = (isset($_GET['mode']) && ($_GET['mode'] == 'overwrite'));
165 }
166
167 /**
168 * Run the bootstrap process which includes checking for requirements and running
169 * the extraction process
170 *
171 * @return null | string Returns null if the run was successful otherwise an error message
172 */
173 public function run()
174 {
175 date_default_timezone_set('UTC'); // Some machines don't have this set so just do it here
176 @unlink('./dup-installer-bootlog__'.self::PACKAGE_HASH.'.txt');
177 self::log('==DUPLICATOR INSTALLER BOOTSTRAP v1.3.8==');
178 self::log('----------------------------------------------------');
179 self::log('Installer bootstrap start');
180
181 $archive_filepath = $this->getArchiveFilePath();
182 $archive_filename = self::ARCHIVE_FILENAME;
183
184 $error = null;
185 $extract_installer = true;
186 $installer_directory = dirname(__FILE__).'/'.self::INSTALLER_DIR_NAME;
187 $extract_success = false;
188 $archiveExpectedEasy = $this->readableByteSize($this->archiveExpectedSize);
189 $archiveActualEasy = $this->readableByteSize($this->archiveActualSize);
190
191 //$archive_extension = strtolower(pathinfo($archive_filepath)['extension']);
192 $archive_extension = strtolower(pathinfo($archive_filepath, PATHINFO_EXTENSION));
193 $manual_extract_found = (
194 file_exists($installer_directory."/main.installer.php")
195 &&
196 file_exists($installer_directory."/dup-archive__".self::PACKAGE_HASH.".txt")
197 &&
198 file_exists($installer_directory."/dup-database__".self::PACKAGE_HASH.".sql")
199 );
200
201 $isZip = ($archive_extension == 'zip');
202
203 //MANUAL EXTRACTION NOT FOUND
204 if (! $manual_extract_found) {
205
206 //MISSING ARCHIVE FILE
207 if (! file_exists($archive_filepath)) {
208 self::log("ERROR: Archive file not found!");
209 $archive_candidates = ($isZip) ? $this->getFilesWithExtension('zip') : $this->getFilesWithExtension('daf');
210 $candidate_count = count($archive_candidates);
211 $candidate_html = "- No {$archive_extension} files found -";
212
213 if ($candidate_count >= 1) {
214 $candidate_html = "<ol>";
215 foreach($archive_candidates as $archive_candidate) {
216 $candidate_html .= "<li> {$archive_candidate}</li>";
217 }
218 $candidate_html .= "</ol>";
219 }
220
221 $error = "<b>Archive not found!</b> The <i>'Required File'</i> below should be present in the <i>'Extraction Path'</i>. "
222 . "The archive file name must be the <u>exact</u> name of the archive file placed in the extraction path character for character.<br/><br/> "
223 . "If the file does not have the correct name then rename it to the <i>'Required File'</i> below. When downloading the package files make "
224 . "sure both files are from the same package line in the packages view. If the archive is not finished downloading please wait for it to complete.<br/><br/>"
225 . "<b>Required File:</b> <span class='file-info'>{$archive_filename}</span> <br/>"
226 . "<b>Extraction Path:</b> <span class='file-info'>{$this->installerExtractPath}/</span><br/><br/>"
227 . "Potential archives found at extraction path: <br/>{$candidate_html}<br/><br/>";
228
229 return $error;
230 }
231
232 if (!filter_var(self::ARCHIVE_SIZE, FILTER_VALIDATE_INT) || self::ARCHIVE_SIZE > 2147483647) {
233
234 $os_first_three_chars = substr(PHP_OS, 0, 3);
235 $os_first_three_chars = strtoupper($os_first_three_chars);
236 $no_of_bits = PHP_INT_SIZE * 8;
237
238 if ($no_of_bits == 32) {
239 if ($isZip) { // ZIP
240 if ('WIN' === $os_first_three_chars) {
241 $error = "This package is currently {$archiveExpectedEasy} and it's on a Windows OS. PHP on Windows does not support files larger than 2GB. Please use the file filters to get your package lower to support this server or try the package on a Linux server.";
242 return $error;
243 }
244 } else { // DAF
245 if ('WIN' === $os_first_three_chars) {
246 $error = 'Windows PHP limitations prevents extraction of archives larger than 2GB. Please do the following: <ol><li>Download and use the <a target="_blank" href="https://snapcreek.com/duplicator/docs/faqs-tech/#faq-trouble-052-q">Windows DupArchive extractor</a> to extract all files from the archive.</li><li>Perform a <a target="_blank" href="https://snapcreek.com/duplicator/docs/faqs-tech/#faq-installer-015-q">Manual Extract Install</a> starting at step 4.</li></ol>';
247 } else {
248 $error = 'This archive is too large for 32-bit PHP. Ask your host to upgrade the server to 64-bit PHP or install on another system has 64-bit PHP.';
249 }
250 return $error;
251 }
252 }
253 }
254
255 //SIZE CHECK ERROR
256 if (($this->archiveRatio < 90) && ($this->archiveActualSize > 0) && ($this->archiveExpectedSize > 0)) {
257 $this->log("ERROR: The expected archive size should be around [{$archiveExpectedEasy}]. The actual size is currently [{$archiveActualEasy}].");
258 $this->log("The archive file may not have fully been downloaded to the server");
259 $percent = round($this->archiveRatio);
260
261 $autochecked = isset($_POST['auto-fresh']) ? "checked='true'" : '';
262 $error = "<b>Archive file size warning.</b><br/> The expected archive size should be around <b class='pass'>[{$archiveExpectedEasy}]</b>. "
263 . "The actual size is currently <b class='fail'>[{$archiveActualEasy}]</b>. The archive file may not have fully been downloaded to the server. "
264 . "Please validate that the file sizes are close to the same size and that the file has been completely downloaded to the destination server. If the archive is still "
265 . "downloading then refresh this page to get an update on the download size.<br/><br/>";
266
267 return $error;
268 }
269
270 }
271
272
273 // OLD COMPATIBILITY MODE
274 if (isset($_GET['extract-installer']) && !isset($_GET['force-extract-installer'])) {
275 $_GET['force-extract-installer'] = $_GET['extract-installer'];
276 }
277
278 if ($manual_extract_found) {
279 // INSTALL DIRECTORY: Check if its setup correctly AND we are not in overwrite mode
280 if (isset($_GET['force-extract-installer']) && ('1' == $_GET['force-extract-installer'] || 'enable' == $_GET['force-extract-installer'] || 'false' == $_GET['force-extract-installer'])) {
281
282 self::log("Manual extract found with force extract installer get parametr");
283 $extract_installer = true;
284
285 } else {
286 $extract_installer = false;
287 self::log("Manual extract found so not going to extract dup-installer dir");
288 }
289 } else {
290 $extract_installer = true;
291 self::log("Manual extract didn't found so going to extract dup-installer dir");
292 }
293
294 if ($extract_installer && file_exists($installer_directory)) {
295 $scanned_directory = array_diff(scandir($installer_directory), array('..', '.'));
296 foreach ($scanned_directory as $object) {
297 $object_file_path = $installer_directory.'/'.$object;
298 if (is_file($object_file_path)) {
299 if (unlink($object_file_path)) {
300 self::log('Successfully deleted the file '.$object_file_path);
301 } else {
302 $error .= 'Error deleting the file '.$object_file_path.' Please manually delete it and try again.';
303 self::log($error);
304 }
305 }
306 }
307 }
308
309 //ATTEMPT EXTRACTION:
310 //ZipArchive and Shell Exec
311 if ($extract_installer) {
312 self::log("Ready to extract the installer");
313
314 self::log("Checking permission of destination folder");
315 $destination = dirname(__FILE__);
316 if (!is_writable($destination)) {
317 self::log("destination folder for extraction is not writable");
318 if (@chmod($destination, 0755)) {
319 self::log("Permission of destination folder changed to 0755");
320 } else {
321 self::log("Permission of destination folder failed to change to 0755");
322 }
323 }
324
325 if (!is_writable($destination)) {
326 $error = "NOTICE: The {$destination} directory is not writable on this server please talk to your host or server admin about making ";
327 $error .= "<a target='_blank' href='https://snapcreek.com/duplicator/docs/faqs-tech/#faq-trouble-055-q'>writable {$destination} directory</a> on this server. <br/>";
328 return $error;
329 }
330
331
332 if ($isZip) {
333 $zip_mode = $this->getZipMode();
334
335 if (($zip_mode == DUPX_Bootstrap_Zip_Mode::AutoUnzip) || ($zip_mode == DUPX_Bootstrap_Zip_Mode::ZipArchive) && class_exists('ZipArchive')) {
336 if ($this->hasZipArchive) {
337 self::log("ZipArchive exists so using that");
338 $extract_success = $this->extractInstallerZipArchive($archive_filepath);
339
340 if ($extract_success) {
341 self::log('Successfully extracted with ZipArchive');
342 } else {
343 if (0 == $this->installer_files_found) {
344 $error = "This archive is not properly formatted and does not contain a dup-installer directory. Please make sure you are attempting to install the original archive and not one that has been reconstructed.";
345 self::log($error);
346 return $error;
347 } else {
348 $error = 'Error extracting with ZipArchive. ';
349 self::log($error);
350 }
351 }
352 } else {
353 self::log("WARNING: ZipArchive is not enabled.");
354 $error = "NOTICE: ZipArchive is not enabled on this server please talk to your host or server admin about enabling ";
355 $error .= "<a target='_blank' href='https://snapcreek.com/duplicator/docs/faqs-tech/#faq-trouble-060-q'>ZipArchive</a> on this server. <br/>";
356 }
357 }
358
359 if (!$extract_success) {
360 if (($zip_mode == DUPX_Bootstrap_Zip_Mode::AutoUnzip) || ($zip_mode == DUPX_Bootstrap_Zip_Mode::ShellExec)) {
361 $unzip_filepath = $this->getUnzipFilePath();
362 if ($unzip_filepath != null) {
363 $extract_success = $this->extractInstallerShellexec($archive_filepath);
364 if ($extract_success) {
365 self::log('Successfully extracted with Shell Exec');
366 $error = null;
367 } else {
368 $error .= 'Error extracting with Shell Exec. Please manually extract archive then choose Advanced > Manual Extract in installer.';
369 self::log($error);
370 }
371 } else {
372 self::log('WARNING: Shell Exec Zip is not available');
373 $error .= "NOTICE: Shell Exec is not enabled on this server please talk to your host or server admin about enabling ";
374 $error .= "<a target='_blank' href='http://php.net/manual/en/function.shell-exec.php'>Shell Exec</a> on this server or manually extract archive then choose Advanced > Manual Extract in installer.";
375 }
376 }
377 }
378
379 // If both ZipArchive and ShellZip are not available, Error message should be combined for both
380 if (!$extract_success && $zip_mode == DUPX_Bootstrap_Zip_Mode::AutoUnzip) {
381 $unzip_filepath = $this->getUnzipFilePath();
382 if (!class_exists('ZipArchive') && empty($unzip_filepath)) {
383 $error = "NOTICE: ZipArchive and Shell Exec are not enabled on this server please talk to your host or server admin about enabling ";
384 $error .= "<a target='_blank' href='https://snapcreek.com/duplicator/docs/faqs-tech/#faq-trouble-060-q'>ZipArchive</a> or <a target='_blank' href='http://php.net/manual/en/function.shell-exec.php'>Shell Exec</a> on this server or manually extract archive then choose Advanced > Manual Extract in installer.";
385 }
386 }
387 } else {
388 DupArchiveMiniExpander::init("DUPX_Bootstrap::log");
389 try {
390 DupArchiveMiniExpander::expandDirectory($archive_filepath, self::INSTALLER_DIR_NAME, dirname(__FILE__));
391 } catch (Exception $ex) {
392 self::log("Error expanding installer subdirectory:".$ex->getMessage());
393 throw $ex;
394 }
395 }
396
397 $is_apache = (strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false || strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false);
398 $is_nginx = (strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false);
399
400 $sapi_type = php_sapi_name();
401 $php_ini_data = array(
402 'max_execution_time' => 3600,
403 'max_input_time' => -1,
404 'ignore_user_abort' => 'On',
405 'post_max_size' => '4096M',
406 'upload_max_filesize' => '4096M',
407 'memory_limit' => DUPLICATOR_PHP_MAX_MEMORY,
408 'default_socket_timeout' => 3600,
409 'pcre.backtrack_limit' => 99999999999,
410 );
411 $sapi_type_first_three_chars = substr($sapi_type, 0, 3);
412 if ('fpm' === $sapi_type_first_three_chars) {
413 self::log("SAPI: FPM");
414 if ($is_apache) {
415 self::log('Server: Apache');
416 } elseif ($is_nginx) {
417 self::log('Server: Nginx');
418 }
419
420 if ($is_apache || $is_nginx) {
421 $htaccess_data = array();
422 foreach ($php_ini_data as $php_ini_key=>$php_ini_val) {
423 if ($is_apache) {
424 $htaccess_data[] = 'SetEnv PHP_VALUE "'.$php_ini_key.' = '.$php_ini_val.'"';
425 } elseif ($is_nginx) {
426 if ('On' == $php_ini_val || 'Off' == $php_ini_val) {
427 $htaccess_data[] = 'php_flag '.$php_ini_key.' '.$php_ini_val;
428 } else {
429 $htaccess_data[] = 'php_value '.$php_ini_key.' '.$php_ini_val;
430 }
431 }
432 }
433
434 $htaccess_text = implode("\n", $htaccess_data);
435 $htaccess_file_path = dirname(__FILE__).'/dup-installer/.htaccess';
436 self::log("creating {$htaccess_file_path} with the content:");
437 self::log($htaccess_text);
438 @file_put_contents($htaccess_file_path, $htaccess_text);
439 }
440 } elseif ('cgi' === $sapi_type_first_three_chars || 'litespeed' === $sapi_type) {
441 if ('cgi' === $sapi_type_first_three_chars) {
442 self::log("SAPI: CGI");
443 } else {
444 self::log("SAPI: litespeed");
445 }
446 if (version_compare(phpversion(), 5.5) >= 0 && (!$is_apache || 'litespeed' === $sapi_type)) {
447 $ini_data = array();
448 foreach ($php_ini_data as $php_ini_key=>$php_ini_val) {
449 $ini_data[] = $php_ini_key.' = '.$php_ini_val;
450 }
451 $ini_text = implode("\n", $ini_data);
452 $ini_file_path = dirname(__FILE__).'/dup-installer/.user.ini';
453 self::log("creating {$ini_file_path} with the content:");
454 self::log($ini_text);
455 @file_put_contents($ini_file_path, $ini_text);
456 } else{
457 self::log("No need to create dup-installer/.htaccess or dup-installer/.user.ini");
458 }
459 } else {
460 self::log("No need to create dup-installer/.htaccess or dup-installer/.user.ini");
461 self::log("SAPI: Unrecognized");
462 }
463 } else {
464 self::log("Didn't need to extract the installer.");
465 }
466
467 if (empty($error)) {
468 $config_files = glob('./dup-installer/dup-archive__*.txt');
469 $config_file_absolute_path = array_pop($config_files);
470 if (!file_exists($config_file_absolute_path)) {
471 $error = '<b>Archive config file not found in dup-installer folder.</b> <br><br>';
472 return $error;
473 }
474 }
475
476 $is_https = $this->isHttps();
477
478 if($is_https) {
479 $current_url = 'https://';
480 } else {
481 $current_url = 'http://';
482 }
483
484 if(($_SERVER['SERVER_PORT'] == 80) && ($is_https)) {
485 // Fixing what appears to be a bad server setting
486 $server_port = 443;
487 } else {
488 $server_port = $_SERVER['SERVER_PORT'];
489 }
490
491
492 //$current_url .= $_SERVER['HTTP_HOST'];//WAS SERVER_NAME and caused problems on some boxes
493 $current_url .= isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'];//WAS SERVER_NAME and caused problems on some boxes
494 if(strpos($current_url,':') === false) {
495 $current_url = $current_url.':'.$server_port;
496 }
497
498 $current_url .= $_SERVER['REQUEST_URI'];
499 $uri_start = dirname($current_url);
500
501 $encoded_archive_path = urlencode($archive_filepath);
502
503 if ($error === null) {
504 $error = $this->postExtractProcessing();
505
506 if($error == null) {
507
508 $bootloader_name = basename(__FILE__);
509 $this->mainInstallerURL = $uri_start.'/'.self::INSTALLER_DIR_NAME.'/main.installer.php';
510
511 $this->fixInstallerPerms($this->mainInstallerURL);
512
513 $this->archive = $archive_filepath;
514 $this->bootloader = $bootloader_name;
515
516 if (isset($_SERVER['QUERY_STRING']) && !empty($_SERVER['QUERY_STRING'])) {
517 $this->mainInstallerURL .= '?'.$_SERVER['QUERY_STRING'];
518 }
519
520 self::log("No detected errors so redirecting to the main installer. Main Installer URI = {$this->mainInstallerURL}");
521 }
522 }
523
524 return $error;
525 }
526
527 public function postExtractProcessing()
528 {
529 $dproInstallerDir = dirname(__FILE__) . '/dup-installer';
530 $libDir = $dproInstallerDir . '/lib';
531 $fileopsDir = $libDir . '/fileops';
532
533 if(!file_exists($dproInstallerDir)) {
534
535 return 'Can\'t extract installer directory. See <a target="_blank" href="https://snapcreek.com/duplicator/docs/faqs-tech/#faq-installer-022-q">this FAQ item</a> for details on how to resolve.</a>';
536 }
537
538 $sourceFilepath = "{$fileopsDir}/fileops.ppp";
539 $destFilepath = "{$fileopsDir}/fileops.php";
540
541 if(file_exists($sourceFilepath) && (!file_exists($destFilepath))) {
542 if(@rename($sourceFilepath, $destFilepath) === false) {
543 return "Error renaming {$sourceFilepath}";
544 }
545 }
546 }
547
548 /**
549 * Indicates if site is running https or not
550 *
551 * @return bool Returns true if https, false if not
552 */
553 public function isHttps()
554 {
555 $retVal = true;
556
557 if (isset($_SERVER['HTTPS'])) {
558 $retVal = ($_SERVER['HTTPS'] !== 'off');
559 } else {
560 $retVal = ($_SERVER['SERVER_PORT'] == 443);
561 }
562
563 return $retVal;
564 }
565
566 /**
567 * Attempts to set the 'dup-installer' directory permissions
568 *
569 * @return null
570 */
571 private function fixInstallerPerms()
572 {
573 $file_perms = substr(sprintf('%o', fileperms(__FILE__)), -4);
574 $file_perms = octdec($file_perms);
575 //$dir_perms = substr(sprintf('%o', fileperms(dirname(__FILE__))), -4);
576
577 // No longer using existing directory permissions since that can cause problems. Just set it to 755
578 $dir_perms = '755';
579 $dir_perms = octdec($dir_perms);
580 $installer_dir_path = $this->installerContentsPath;
581
582 $this->setPerms($installer_dir_path, $dir_perms, false);
583 $this->setPerms($installer_dir_path, $file_perms, true);
584 }
585
586 /**
587 * Set the permissions of a given directory and optionally all files
588 *
589 * @param string $directory The full path to the directory where perms will be set
590 * @param string $perms The given permission sets to use such as '0755'
591 * @param string $do_files Also set the permissions of all the files in the directory
592 *
593 * @return null
594 */
595 private function setPerms($directory, $perms, $do_files)
596 {
597 if (!$do_files) {
598 // If setting a directory hiearchy be sure to include the base directory
599 $this->setPermsOnItem($directory, $perms);
600 }
601
602 $item_names = array_diff(scandir($directory), array('.', '..'));
603
604 foreach ($item_names as $item_name) {
605 $path = "$directory/$item_name";
606 if (($do_files && is_file($path)) || (!$do_files && !is_file($path))) {
607 $this->setPermsOnItem($path, $perms);
608 }
609 }
610 }
611
612 /**
613 * Set the permissions of a single directory or file
614 *
615 * @param string $path The full path to the directory or file where perms will be set
616 * @param string $perms The given permission sets to use such as '0755'
617 *
618 * @return bool Returns true if the permission was properly set
619 */
620 private function setPermsOnItem($path, $perms)
621 {
622 $result = @chmod($path, $perms);
623 $perms_display = decoct($perms);
624 if ($result === false) {
625 self::log("Couldn't set permissions of $path to {$perms_display}<br/>");
626 } else {
627 self::log("Set permissions of $path to {$perms_display}<br/>");
628 }
629 return $result;
630 }
631
632
633 /**
634 * Logs a string to the dup-installer-bootlog__[HASH].txt file
635 *
636 * @param string $s The string to log to the log file
637 *
638 * @return null
639 */
640 public static function log($s)
641 {
642 $timestamp = date('M j H:i:s');
643 file_put_contents('./dup-installer-bootlog__'.self::PACKAGE_HASH.'.txt', "$timestamp $s\n", FILE_APPEND);
644 }
645
646 /**
647 * Extracts only the 'dup-installer' files using ZipArchive
648 *
649 * @param string $archive_filepath The path to the archive file.
650 *
651 * @return bool Returns true if the data was properly extracted
652 */
653 private function extractInstallerZipArchive($archive_filepath, $checkSubFolder = false)
654 {
655 $success = true;
656 $zipArchive = new ZipArchive();
657 $subFolderArchiveList = array();
658
659 if ($zipArchive->open($archive_filepath) === true) {
660 self::log("Successfully opened $archive_filepath");
661 $destination = dirname(__FILE__);
662 $folder_prefix = self::INSTALLER_DIR_NAME.'/';
663 self::log("Extracting all files from archive within ".self::INSTALLER_DIR_NAME);
664
665 $this->installer_files_found = 0;
666
667 for ($i = 0; $i < $zipArchive->numFiles; $i++) {
668 $stat = $zipArchive->statIndex($i);
669 if ($checkSubFolder == false) {
670 $filenameCheck = $stat['name'];
671 $filename = $stat['name'];
672 $tmpSubFolder = null;
673 } else {
674 $safePath = rtrim(self::setSafePath($stat['name']) , '/');
675 $tmpArray = explode('/' , $safePath);
676
677 if (count($tmpArray) < 2) {
678 continue;
679 }
680
681 $tmpSubFolder = $tmpArray[0];
682 array_shift($tmpArray);
683 $filenameCheck = implode('/' , $tmpArray);
684 $filename = $stat['name'];
685 }
686
687
688 if ($this->startsWith($filenameCheck , $folder_prefix)) {
689 $this->installer_files_found++;
690
691 if (!empty($tmpSubFolder) && !in_array($tmpSubFolder , $subFolderArchiveList)) {
692 $subFolderArchiveList[] = $tmpSubFolder;
693 }
694
695 if ($zipArchive->extractTo($destination, $filename) === true) {
696 self::log("Success: {$filename} >>> {$destination}");
697 } else {
698 self::log("Error extracting {$filename} from archive archive file");
699 $success = false;
700 break;
701 }
702 }
703 }
704
705 if ($checkSubFolder && count($subFolderArchiveList) !== 1) {
706 self::log("Error: Multiple dup subfolder archive");
707 $success = false;
708 } else {
709 if ($checkSubFolder) {
710 $this->moveUpfromSubFolder(dirname(__FILE__).'/'.$subFolderArchiveList[0] , true);
711 }
712
713 $lib_directory = dirname(__FILE__).'/'.self::INSTALLER_DIR_NAME.'/lib';
714 $snaplib_directory = $lib_directory.'/snaplib';
715
716 // If snaplib files aren't present attempt to extract and copy those
717 if(!file_exists($snaplib_directory))
718 {
719 $folder_prefix = 'snaplib/';
720 $destination = $lib_directory;
721
722 for ($i = 0; $i < $zipArchive->numFiles; $i++) {
723 $stat = $zipArchive->statIndex($i);
724 $filename = $stat['name'];
725
726 if ($this->startsWith($filename, $folder_prefix)) {
727 $this->installer_files_found++;
728
729 if ($zipArchive->extractTo($destination, $filename) === true) {
730 self::log("Success: {$filename} >>> {$destination}");
731 } else {
732 self::log("Error extracting {$filename} from archive archive file");
733 $success = false;
734 break;
735 }
736 }
737 }
738 }
739 }
740
741 if ($zipArchive->close() === true) {
742 self::log("Successfully closed archive file");
743 } else {
744 self::log("Problem closing archive file");
745 $success = false;
746 }
747
748 if ($success != false && $this->installer_files_found < 10) {
749 if ($checkSubFolder) {
750 self::log("Couldn't find the installer directory in the archive!");
751 $success = false;
752 } else {
753 self::log("Couldn't find the installer directory in archive root! Check subfolder");
754 $this->extractInstallerZipArchive($archive_filepath, true);
755 }
756 }
757 } else {
758 self::log("Couldn't open archive archive file with ZipArchive");
759 $success = false;
760 }
761
762 return $success;
763 }
764
765 /**
766 * move all folder content up to parent
767 *
768 * @param string $subFolderName full path
769 * @param boolean $deleteSubFolder if true delete subFolder after moved all
770 * @return boolean
771 *
772 */
773 private function moveUpfromSubFolder($subFolderName, $deleteSubFolder = false)
774 {
775 if (!is_dir($subFolderName)) {
776 return false;
777 }
778
779 $parentFolder = dirname($subFolderName);
780 if (!is_writable($parentFolder)) {
781 return false;
782 }
783
784 $success = true;
785 if (($subList = glob(rtrim($subFolderName, '/').'/*', GLOB_NOSORT)) === false) {
786 self::log("Problem glob folder ".$subFolderName);
787 return false;
788 } else {
789 foreach ($subList as $cName) {
790 $destination = $parentFolder.'/'.basename($cName);
791 if (file_exists($destination)) {
792 $success = self::deletePath($destination);
793 }
794
795 if ($success) {
796 $success = rename($cName, $destination);
797 } else {
798 break;
799 }
800 }
801
802 if ($success && $deleteSubFolder) {
803 $success = self::deleteDirectory($subFolderName, true);
804 }
805 }
806
807 if (!$success) {
808 self::log("Problem om moveUpfromSubFolder subFolder:".$subFolderName);
809 }
810
811 return $success;
812 }
813
814 /**
815 * Extracts only the 'dup-installer' files using Shell-Exec Unzip
816 *
817 * @param string $archive_filepath The path to the archive file.
818 *
819 * @return bool Returns true if the data was properly extracted
820 */
821 private function extractInstallerShellexec($archive_filepath)
822 {
823 $success = false;
824 self::log("Attempting to use Shell Exec");
825 $unzip_filepath = $this->getUnzipFilePath();
826
827 if ($unzip_filepath != null) {
828 $unzip_command = "$unzip_filepath -q $archive_filepath ".self::INSTALLER_DIR_NAME.'/* 2>&1';
829 self::log("Executing $unzip_command");
830 $stderr = shell_exec($unzip_command);
831
832 $lib_directory = dirname(__FILE__).'/'.self::INSTALLER_DIR_NAME.'/lib';
833 $snaplib_directory = $lib_directory.'/snaplib';
834
835 // If snaplib files aren't present attempt to extract and copy those
836 if(!file_exists($snaplib_directory))
837 {
838 $local_lib_directory = dirname(__FILE__).'/snaplib';
839 $unzip_command = "$unzip_filepath -q $archive_filepath snaplib/* 2>&1";
840 self::log("Executing $unzip_command");
841 $stderr .= shell_exec($unzip_command);
842 mkdir($lib_directory);
843 rename($local_lib_directory, $snaplib_directory);
844 }
845
846 if ($stderr == '') {
847 self::log("Shell exec unzip succeeded");
848 $success = true;
849 } else {
850 self::log("Shell exec unzip failed. Output={$stderr}");
851 }
852 }
853
854 return $success;
855 }
856
857 /**
858 * Attempts to get the archive file path
859 *
860 * @return string The full path to the archive file
861 */
862 private function getArchiveFilePath()
863 {
864 if (isset($_GET['archive'])) {
865 $archive_filepath = $_GET['archive'];
866 } else {
867 $archive_filename = self::ARCHIVE_FILENAME;
868 $archive_filepath = str_replace("\\", '/', dirname(__FILE__) . '/' . $archive_filename);
869 }
870
871 self::log("Using archive $archive_filepath");
872 return $archive_filepath;
873 }
874
875 /**
876 * Gets the DUPX_Bootstrap_Zip_Mode enum type that should be used
877 *
878 * @return DUPX_Bootstrap_Zip_Mode Returns the current mode of the bootstrapper
879 */
880 private function getZipMode()
881 {
882 $zip_mode = DUPX_Bootstrap_Zip_Mode::AutoUnzip;
883
884 if (isset($_GET['zipmode'])) {
885 $zipmode_string = $_GET['zipmode'];
886 self::log("Unzip mode specified in querystring: $zipmode_string");
887
888 switch ($zipmode_string) {
889 case 'autounzip':
890 $zip_mode = DUPX_Bootstrap_Zip_Mode::AutoUnzip;
891 break;
892
893 case 'ziparchive':
894 $zip_mode = DUPX_Bootstrap_Zip_Mode::ZipArchive;
895 break;
896
897 case 'shellexec':
898 $zip_mode = DUPX_Bootstrap_Zip_Mode::ShellExec;
899 break;
900 }
901 }
902
903 return $zip_mode;
904 }
905
906 /**
907 * Checks to see if a string starts with specific characters
908 *
909 * @return bool Returns true if the string starts with a specific format
910 */
911 private function startsWith($haystack, $needle)
912 {
913 return $needle === "" || strrpos($haystack, $needle, - strlen($haystack)) !== false;
914 }
915
916 /**
917 * Checks to see if the server supports issuing commands to shell_exex
918 *
919 * @return bool Returns true shell_exec can be ran on this server
920 */
921 public function hasShellExec()
922 {
923 $cmds = array('shell_exec', 'escapeshellarg', 'escapeshellcmd', 'extension_loaded');
924
925 //Function disabled at server level
926 if (array_intersect($cmds, array_map('trim', explode(',', @ini_get('disable_functions'))))) return false;
927
928 //Suhosin: http://www.hardened-php.net/suhosin/
929 //Will cause PHP to silently fail
930 if (extension_loaded('suhosin')) {
931 $suhosin_ini = @ini_get("suhosin.executor.func.blacklist");
932 if (array_intersect($cmds, array_map('trim', explode(',', $suhosin_ini)))) return false;
933 }
934 // Can we issue a simple echo command?
935 if (!@shell_exec('echo duplicator')) return false;
936
937 return true;
938 }
939
940 /**
941 * Gets the possible system commands for unzip on Linux
942 *
943 * @return string Returns unzip file path that can execute the unzip command
944 */
945 public function getUnzipFilePath()
946 {
947 $filepath = null;
948
949 if ($this->hasShellExec()) {
950 if (shell_exec('hash unzip 2>&1') == NULL) {
951 $filepath = 'unzip';
952 } else {
953 $possible_paths = array(
954 '/usr/bin/unzip',
955 '/opt/local/bin/unzip',
956 '/bin/unzip',
957 '/usr/local/bin/unzip',
958 '/usr/sfw/bin/unzip',
959 '/usr/xdg4/bin/unzip',
960 '/opt/bin/unzip',
961 // RSR TODO put back in when we support shellexec on windows,
962 );
963
964 foreach ($possible_paths as $path) {
965 if (file_exists($path)) {
966 $filepath = $path;
967 break;
968 }
969 }
970 }
971 }
972
973 return $filepath;
974 }
975
976 /**
977 * Display human readable byte sizes such as 150MB
978 *
979 * @param int $size The size in bytes
980 *
981 * @return string A readable byte size format such as 100MB
982 */
983 public function readableByteSize($size)
984 {
985 try {
986 $units = array('B', 'KB', 'MB', 'GB', 'TB');
987 for ($i = 0; $size >= 1024 && $i < 4; $i++)
988 $size /= 1024;
989 return round($size, 2).$units[$i];
990 } catch (Exception $e) {
991 return "n/a";
992 }
993 }
994
995 /**
996 * Returns an array of zip files found in the current executing directory
997 *
998 * @return array of zip files
999 */
1000 public static function getFilesWithExtension($extension)
1001 {
1002 $files = array();
1003 foreach (glob("*.{$extension}") as $name) {
1004 if (file_exists($name)) {
1005 $files[] = $name;
1006 }
1007 }
1008
1009 if (count($files) > 0) {
1010 return $files;
1011 }
1012
1013 //FALL BACK: Windows XP has bug with glob,
1014 //add secondary check for PHP lameness
1015 if ($dh = opendir('.')) {
1016 while (false !== ($name = readdir($dh))) {
1017 $ext = substr($name, strrpos($name, '.') + 1);
1018 if (in_array($ext, array($extension))) {
1019 $files[] = $name;
1020 }
1021 }
1022 closedir($dh);
1023 }
1024
1025 return $files;
1026 }
1027
1028 /**
1029 * Safely remove a directory and recursively if needed
1030 *
1031 * @param string $directory The full path to the directory to remove
1032 * @param string $recursive recursively remove all items
1033 *
1034 * @return bool Returns true if all content was removed
1035 */
1036 public static function deleteDirectory($directory, $recursive)
1037 {
1038 $success = true;
1039
1040 $filenames = array_diff(scandir($directory), array('.', '..'));
1041
1042 foreach ($filenames as $filename) {
1043 $fullPath = $directory.'/'.$filename;
1044
1045 if (is_dir($fullPath)) {
1046 if ($recursive) {
1047 $success = self::deleteDirectory($fullPath, true);
1048 }
1049 } else {
1050 $success = @unlink($fullPath);
1051 if ($success === false) {
1052 self::log( __FUNCTION__.": Problem deleting file:".$fullPath);
1053 }
1054 }
1055
1056 if ($success === false) {
1057 self::log("Problem deleting dir:".$directory);
1058 break;
1059 }
1060 }
1061
1062 return $success && rmdir($directory);
1063 }
1064
1065 /**
1066 * Safely remove a file or directory and recursively if needed
1067 *
1068 * @param string $directory The full path to the directory to remove
1069 *
1070 * @return bool Returns true if all content was removed
1071 */
1072 public static function deletePath($path)
1073 {
1074 $success = true;
1075
1076 if (is_dir($path)) {
1077 $success = self::deleteDirectory($path, true);
1078 } else {
1079 $success = @unlink($path);
1080
1081 if ($success === false) {
1082 self::log( __FUNCTION__.": Problem deleting file:".$path);
1083 }
1084 }
1085
1086 return $success;
1087 }
1088
1089 /**
1090 * Makes path safe for any OS for PHP
1091 *
1092 * Paths should ALWAYS READ be "/"
1093 * uni: /home/path/file.txt
1094 * win: D:/home/path/file.txt
1095 *
1096 * @param string $path The path to make safe
1097 *
1098 * @return string The original $path with a with all slashes facing '/'.
1099 */
1100 public static function setSafePath($path)
1101 {
1102 return str_replace("\\", "/", $path);
1103 }
1104}
1105
1106try {
1107 $boot = new DUPX_Bootstrap();
1108 $boot_error = $boot->run();
1109 $auto_refresh = isset($_POST['auto-fresh']) ? true : false;
1110 DUPX_CSRF::resetAllTokens();
1111} catch (Exception $e) {
1112 $boot_error = $e->getMessage();
1113}
1114
1115if ($boot_error == null) {
1116 $step1_csrf_token = DUPX_CSRF::generate('step1');
1117 DUPX_CSRF::setCookie('archive', $boot->archive);
1118 DUPX_CSRF::setCookie('bootloader', $boot->bootloader);
1119}
1120?>
1121
1122<html>
1123<?php if ($boot_error == null) :?>
1124 <head>
1125 <meta name="robots" content="noindex,nofollow">
1126 <title>Duplicator Installer</title>
1127 </head>
1128 <body>
1129 <?php
1130 $id = uniqid();
1131 $html = "<form id='{$id}' method='post' action='{$boot->mainInstallerURL}' />\n";
1132 $data = array(
1133 'archive' => $boot->archive,
1134 'bootloader' => $boot->bootloader,
1135 'csrf_token' => $step1_csrf_token,
1136 );
1137 foreach ($data as $name => $value) {
1138 $html .= "<input type='hidden' name='{$name}' value='{$value}' />\n";
1139 }
1140 $html .= "</form>\n";
1141 $html .= "<script>window.onload = function() { document.getElementById('{$id}').submit(); }</script>";
1142 echo $html;
1143 ?>
1144 </body>
1145<?php else :?>
1146 <head>
1147 <style>
1148 body {font-family:Verdana,Arial,sans-serif; line-height:18px; font-size: 12px}
1149 h2 {font-size:20px; margin:5px 0 5px 0; border-bottom:1px solid #dfdfdf; padding:3px}
1150 div#content {border:1px solid #CDCDCD; width:750px; min-height:550px; margin:auto; margin-top:18px; border-radius:5px; box-shadow:0 8px 6px -6px #333; font-size:13px}
1151 div#content-inner {padding:10px 30px; min-height:550px}
1152
1153 /* Header */
1154 table.header-wizard {border-top-left-radius:5px; border-top-right-radius:5px; width:100%; box-shadow:0 5px 3px -3px #999; background-color:#F1F1F1; font-weight:bold}
1155 table.header-wizard td.header {font-size:24px; padding:7px 0 7px 0; width:100%;}
1156 div.dupx-logfile-link {float:right; font-weight:normal; font-size:12px}
1157 .dupx-version {white-space:nowrap; color:#999; font-size:11px; font-style:italic; text-align:right; padding:0 15px 5px 0; line-height:14px; font-weight:normal}
1158 .dupx-version a { color:#999; }
1159
1160 div.errror-notice {text-align:center; font-style:italic; font-size:11px}
1161 div.errror-msg { color:maroon; padding: 10px 0 5px 0}
1162 .pass {color:green}
1163 .fail {color:red}
1164 span.file-info {font-size: 11px; font-style: italic}
1165 div.skip-not-found {padding:10px 0 5px 0;}
1166 div.skip-not-found label {cursor: pointer}
1167 table.settings {width:100%; font-size:12px}
1168 table.settings td {padding: 4px}
1169 table.settings td:first-child {font-weight: bold}
1170 .w3-light-grey,.w3-hover-light-grey:hover,.w3-light-gray,.w3-hover-light-gray:hover{color:#000!important;background-color:#f1f1f1!important}
1171 .w3-container:after,.w3-container:before,.w3-panel:after,.w3-panel:before,.w3-row:after,.w3-row:before,.w3-row-padding:after,.w3-row-padding:before,
1172 .w3-cell-row:before,.w3-cell-row:after,.w3-clear:after,.w3-clear:before,.w3-bar:before,.w3-bar:after
1173 {content:"";display:table;clear:both}
1174 .w3-green,.w3-hover-green:hover{color:#fff!important;background-color:#4CAF50!important}
1175 .w3-container{padding:0.01em 16px}
1176 .w3-center{display:inline-block;width:auto; text-align: center !important}
1177 </style>
1178 </head>
1179 <body>
1180 <div id="content">
1181
1182 <table cellspacing="0" class="header-wizard">
1183 <tr>
1184 <td class="header"> Duplicator - Bootloader</td>
1185 <td class="dupx-version">
1186 version: <?php echo DUPX_Bootstrap::VERSION ?> <br/>
1187 </td>
1188 </tr>
1189 </table>
1190
1191 <form id="error-form" method="post">
1192 <div id="content-inner">
1193 <h2 style="color:maroon">Setup Notice:</h2>
1194 <div class="errror-notice">An error has occurred. In order to load the full installer please resolve the issue below.</div>
1195 <div class="errror-msg">
1196 <?php echo $boot_error ?>
1197 </div>
1198 <br/><br/>
1199
1200 <h2>Server Settings:</h2>
1201 <table class='settings'>
1202 <tr>
1203 <td>ZipArchive:</td>
1204 <td><?php echo $boot->hasZipArchive ? '<i class="pass">Enabled</i>' : '<i class="fail">Disabled</i>'; ?> </td>
1205 </tr>
1206 <tr>
1207 <td>ShellExec Unzip:</td>
1208 <td><?php echo $boot->hasShellExecUnzip ? '<i class="pass">Enabled</i>' : '<i class="fail">Disabled</i>'; ?> </td>
1209 </tr>
1210 <tr>
1211 <td>Extraction Path:</td>
1212 <td><?php echo $boot->installerExtractPath; ?></td>
1213 </tr>
1214 <tr>
1215 <td>Installer Path:</td>
1216 <td><?php echo $boot->installerContentsPath; ?></td>
1217 </tr>
1218 <tr>
1219 <td>Archive Name:</td>
1220 <td>
1221 [HASH]_archive.zip or [HASH]_archive.daf<br/>
1222 <small>This is based on the format used to build the archive</small>
1223 </td>
1224 </tr>
1225 <tr>
1226 <td>Archive Size:</td>
1227 <td>
1228 <b>Expected Size:</b> <?php echo $boot->readableByteSize($boot->archiveExpectedSize); ?>
1229 <b>Actual Size:</b> <?php echo $boot->readableByteSize($boot->archiveActualSize); ?>
1230 </td>
1231 </tr>
1232 <tr>
1233 <td>Boot Log</td>
1234 <td>dup-installer-bootlog__[HASH].txt</td>
1235 </tr>
1236 </table>
1237 <br/><br/>
1238
1239 <div style="font-size:11px">
1240 Please Note: Either ZipArchive or Shell Exec will need to be enabled for the installer to run automatically otherwise a manual extraction
1241 will need to be performed. In order to run the installer manually follow the instructions to
1242 <a href='https://snapcreek.com/duplicator/docs/faqs-tech/#faq-installer-015-q' target='_blank'>manually extract</a> before running the installer.
1243 </div>
1244 <br/><br/>
1245
1246 </div>
1247 </form>
1248
1249 </div>
1250 </body>
1251
1252 <script>
1253 function AutoFresh() {
1254 document.getElementById('error-form').submit();
1255 }
1256 <?php if ($auto_refresh) :?>
1257 var duration = 10000; //10 seconds
1258 var counter = 10;
1259 var countElement = document.getElementById('count-down');
1260
1261 setTimeout(function(){window.location.reload(1);}, duration);
1262 setInterval(function() {
1263 counter--;
1264 countElement.innerHTML = (counter > 0) ? counter.toString() : "0";
1265 }, 1000);
1266
1267 <?php endif; ?>
1268 </script>
1269
1270
1271<?php endif; ?>
1272
1273
1274
1275<!--
1276Used for integrity check do not remove:
1277DUPLICATOR_INSTALLER_EOF -->
1278</html>