<?php
	/* ----------------------------- *\
	 *       Turbo-Installer         *
	 * By J.P. Pourrez aka Bazooka07 *
	 *          2018-01-30           *
	 \* ---------------------------- */

	/* required by last_release() */
	# Firefox version 3.6 under Windows 7
	const USER_AGENT = 'Mozilla/5.0 (Windows; U; Windows NT 6.1; fr; rv:1.9.2) Gecko/20100115 Firefox/3.6';
	const GITHUB_URL = 'https://api.github.com/repos/%s/releases/latest';

	/* -------- PluXml ----------- */
	const NAME = 'PluXml';
	const HOMEPAGE = 'PluXml'; # May be different of NAME
	# Comment the next line if you prefer to enter the URLs in a form.
	const URL = 'http://telechargements.pluxml.org/download.php';
	# const URL = 'https://github.com/pluxml/PluXml/archive/5.6.zip';
	const SIGNATURE = '/core/lib/class.plx.motor.php';
	# If no addon required, comment the next lines.
	const ADDON_URL = 'https://kazimentou.fr/pluxml-plugins2/index.php?plugin=kzUploader&download';
	const ADDON_TARGET = 'PluXml/plugins';

	const NO_CURL = false; # Forget it! It's just for testing.
	const THEY_CALL_ME = 'Turbo Installer';
	define('TOKEN', sha1(filemtime(__DIR__)));
	define('MY_KEY', sha1(filemtime(__FILE__)));

	function set_token() {
		$cname = 'constant';
		$root = mt_rand(0, 1000000);
		$_SESSION[MY_KEY] = $root;
		$_SESSION['time_to_die'] = time() + 1200; # 20 mn
		$value = sha1($root);
		echo <<< TOKEN
<input name="{$cname('TOKEN')}" value="$value" type="hidden" />
TOKEN;
	}

	/**
	 * Only post from our form is accepted.
	 * */
	function check_token() {
		if(
			!empty($_POST[TOKEN]) and
			!empty($_SESSION[MY_KEY]) and
			!empty($_SESSION['time_to_die'])
		) {
			if(time() < $_SESSION['time_to_die']) {
				return (filter_input(INPUT_POST, TOKEN, FILTER_SANITIZE_STRING) === sha1($_SESSION[MY_KEY]));
			} else {
				session_unset();
				session_destroy();
			}
		}
		return false;
	}

	/**
	 * Clean up Session and print message before leaving.
	 * */
	function bye($signature) {
		if(empty($signature)) {
			session_unset();
			session_destroy();
			$homepage = HOMEPAGE;
			echo <<< BYE
			<p><strong>That's done. Congratulations !</strong></p>
			<p><button type="button" onclick="window.location.href='$homepage'; return true;">Go to the home page</button></p>
BYE;
			# Auto-destruction if not in private range
			if(filter_input(INPUT_SERVER, 'SERVER_ADDR', FILTER_VALIDATE_IP, FILTER_FLAG_NO_PRIV_RANGE)) {
				unlink(__FILE__);
			}
		}
	}

	/**
	 * Removes directories recursively.
	 * */
	function rrmdir($dir) {
		$entries = array_diff(scandir($dir), array('.','..'));
		foreach($entries as $entry) {
			$fullpath = "$dir/$entry";
			if(is_dir($fullpath)) {
				rrmdir($fullpath);
				rmdir($fullpath);
			} else {
				unlink($fullpath);
			}
		}
	}

	/**
	 * Process the uploaded or direct downloaded by curl_exec() Zip archive.
	 * If not empty, $signature is checked. It's an entry in the Zip archive.
	 * */
	function unzip($filename, $target, $signature) {
		$zip = new ZipArchive();
		if($zip->open($filename) === true) {
			# At Github, $root change on every release.
			$root = $zip->getNameIndex(0);
			$root = substr($root, 0, strpos($root, '/'));
			if(empty($signature) or $zip->statName($root.$signature) !== false) {
				printf("<p>Unzipping to <em>%s</em> folder</p>", $target);
				$zip->extractTo($target);
				$zip->close();
				unlink($filename); # Clean up !
				$newName = preg_replace('@-?[\d.]*$@', '', $root); # for release from Github
				if($newName != $root) {
					if(file_exists("$target/$newName")) {
						$backup = "$target/$newName.bak";
						if(file_exists($backup)) {
							rrmdir($backup);
						}
						rename("$target/$newName", $backup);
					}
					rename("$target/$root", "$target/$newName");
				}
				return true;
			} else {
				$message = sprintf('Archive has no %s entry!', $signature);
				echo <<< MESSAGE
		<p class="warning">$message</p>\n
MESSAGE;
				return false;
			}
		} else {
			echo <<< MESSAGE
		<p class="warning">Bad Zip archive.</p>\n
MESSAGE;
			return false;
		}
	}

	function upload_addon() {
		if(defined('ADDON_TARGET') and !empty(ADDON_TARGET) and !empty($_FILES['addon']) and ($_FILES['addon']['error'] === UPLOAD_ERR_OK)) {
			$target = implode(DIRECTORY_SEPARATOR, array(__DIR__, ADDON_TARGET));
			upload_file('addon', $target, false);
		}
	}

	/*
	 * Upload files if curl() use is forbidden
	 * */
	function upload_file($name, $target=__DIR__, $signature=SIGNATURE) {
		if($_FILES[$name]['error'] === UPLOAD_ERR_OK) {
			$filename = $_FILES[$name]['tmp_name'];
			$finfo = finfo_open(FILEINFO_MIME_TYPE);
			if(finfo_file($finfo, $filename) === 'application/zip') {
				if(unzip($filename, $target, $signature)) {
					if(!empty($signature)) {
						upload_addon();
					} else {
						bye($signature);
					}
				}
				if(file_exists($filename)) {
					unlink($filename); # Clean up !
				}
			} else {
				throw new RuntimeException('Bad file mime/type.');
			}
		} else {
		    switch ($_FILES[$name]['error']) {
		        case UPLOAD_ERR_NO_FILE:
		            throw new RuntimeException('No file sent.');
		        case UPLOAD_ERR_INI_SIZE:
		        case UPLOAD_ERR_FORM_SIZE:
		            throw new RuntimeException('Exceeded filesize limit.');
		        default:
		            throw new RuntimeException('Unknown errors.');
		    }
		}
		exit;
	}

	/**
	 * Get an addon for the main Zip archive if required.
	 * */
	function download_addon() {
		if(defined('ADDON_TARGET') and !empty(ADDON_TARGET)) {
			$target = implode(DIRECTORY_SEPARATOR, array(__DIR__, ADDON_TARGET));
			if(!NO_CURL and function_exists('curl_init')) {
				if(defined('URL') and defined('ADDON_URL')) {
					download_file(ADDON_URL, $target, false);
				} elseif(
					!empty($_POST['addon']) and
					filter_input(INPUT_POST, 'addon', FILTER_VALIDATE_URL)
				) {
					download_file(
						filter_input(INPUT_POST, 'addon', FILTER_SANITIZE_URL),
						$target,
						false
					);
				}
			}
		}
	}

	/**
	 * Url is granted. So download file directly on this server.
	 * */
	function download_file($url, $target=__DIR__, $signature=SIGNATURE) {
		echo "<p>Downloading <em>".$url."</em></p>";
		$filename = tempnam(sys_get_temp_dir(), 'KZX');
		$fp = fopen($filename, 'w');
		$ch = curl_init();
		curl_setopt_array($ch, array(
			CURLOPT_URL				=> $url,
			CURLOPT_FILE			=> $fp,
			CURLOPT_FOLLOWLOCATION	=> true,
			CURLOPT_HEADER			=> false,
			CURLOPT_USERAGENT		=> USER_AGENT
		));
		if(curl_exec($ch)) {
			curl_close($ch);
			fclose($fp);
			if(unzip($filename, $target, $signature)) {
				if(!empty($signature)) {
					download_addon();
				} else {
					bye($signature);
				}
			}
		} else {
			echo "<p>".curl_error."</p>\n";
			curl_close();
			fclose($fp);
		}
		if(file_exists($filename)) {
			# May be dropped by unzip()
			unlink($filename);
		}
	}

	const KBYTES = '*1024';
	function unities($value, $unit='') {
		eval('$result=('.preg_replace(array('@M$@i', '@K$@i'), array(KBYTES.KBYTES, KBYTES), $value).');');
		return $result.$unit;
	}

	session_name(TOKEN);
	session_start();

	/* What can we print on the screen ? */
?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
	"http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en">

<head>
	<title>Turbo-Installer</title>
	<meta http-equiv="content-type" content="text/html;charset=utf-8" />
	<style type="text/css">
		body { background-color: #444; }
		body > div { max-width: 36rem; margin: 0 auto; margin-top: 50vh; transform: translateY(-50%); padding: 0.8rem 0.3rem 0.3rem; background-color: #f0f0f0; border-radius: 0.5rem; }
		h1 { text-align: center; margin: 0 0 0.3rem; }
		form { padding: 0.3rem 0.5rem; background-color: #ddd; border: 3px inset #eee; border-radius: 0.4rem; }
		form > p { display: flex; margin: 0.3rem 0; }
		form > p label { display: inline-block; width: 10rem; text-align: right; margin-right: 0.5rem; padding-top: 0.3rem; }
		form input { flex-grow: 1; }
		div > p { text-align: center; margin: 0.3rem 0; }
		p.warning { color:red; }
		em { color: brown; }
		strong { color: firebrick; }
		#progressBar { width: 100%; }
	</style>
</head>
<body>
	<div>
		<h1><?php echo THEY_CALL_ME; ?></h1>
<?php
	if(class_exists('ZipArchive')) {
		if(!is_writable(__DIR__)) {
?>
		<p class="warning"><span>No right for writing in the folder</span> :<br /><?php echo __DIR__; ?>.</p>
<?php
		}
		elseif(!NO_CURL and function_exists('curl_init')) {
			if(defined('URL')) {
				download_file(URL);
			} elseif(
				!empty(filter_input(INPUT_POST, 'url', FILTER_VALIDATE_URL)) and
				check_token()
			) {
				download_file(filter_input(INPUT_POST, 'url', FILTER_SANITIZE_URL));
			} else {
?>
		<form method="post">
			<?php set_token(); ?>
			<p>
				<label>Url for <?php echo NAME; ?></label>
				<input type="text" name="url" required />
			</p><p>
				<label>Url for addon</label>
				<input type="text" name="addon" />
			</p><p>
				<input type="submit" />
			</p>
		</form>
<?php
			}
		} elseif(!empty($_FILES['archive']) and check_token()) {
			# Curl is disabled. Upload a file
			upload_file('archive');
		} else {
			$maxSize = ini_get('upload_max_filesize');
			$maxSizeNum = unities($maxSize);
?>
		<form method="post" enctype="multipart/form-data" id="form1">
			<?php set_token(); ?>
			<p>
				<label>File for <?php echo NAME; ?></label>
				<input type="hidden" id="MAX_FILE_SIZE" name="MAX_FILE_SIZE" value="<?php echo $maxSizeNum; ?>" />
				<input type="file" name="archive" accept="application/zip" title="<?php echo $maxSize.'o maxi'; ?>" required />
			</p><p>
				<label>File for addon</label>
				<input type="file" name="addon" accept="application/zip" />
			</p><p>
				<input type="submit" />
			</p>
			<p><progress id="progressBar" value="0" max="100"></progress></p>
		</form>
		<script type="text/javascript">
			(function() {
				'use strict';

				const form1 = document.getElementById('form1');
				const progressBar = document.getElementById('progressBar');
				if(form1 != null && progressBar != null) {
					form1.addEventListener('submit', function (event) {
						const XHR = new XMLHttpRequest();
						XHR.upload.onprogress = function(event) {
							if(event.lengthComputable) {
								const value = Math.round(100.0 * event.loaded / event.total);
								// console.log('Uploading ' + value + '%');
								if(value <= 100) {
									progressBar.value = value;
								}
							}
						};
						XHR.onloadend = function(event) {
							progressBar.innerHTML = 'Loaded !';
						};
						XHR.onreadystatechange = function(event) {
						    if (this.readyState === XMLHttpRequest.DONE) {
						        if (this.status === 200) {
									document.documentElement.remove();
									document.open();
									document.write(this.responseText);
						        } else {
						            console.log("Status de la réponse: %d (%s)", this.status, this.statusText);
						        }
						    }
						};
						XHR.open('POST', form1.action);
						XHR.send(new FormData(form1));
						event.preventDefault();
					}, false);
				}
			})();
		</script>
<?php
		}
	} else {
?>
	<p class="warning">La librairie Zip n'est pas installée sur ce serveur.</p>
<?php
	}
?>
	</div>
</body>
</html>