working on upload command. added keyboard shortcuts

This commit is contained in:
Stefan Friese 2025-01-31 15:43:35 +00:00
parent 84377914ac
commit dbe33be2c4
1 changed files with 406 additions and 161 deletions

View File

@ -2,7 +2,7 @@ package main
import ( import (
// "bytes" // "bytes"
"bufio" // "bufio"
"bytes" "bytes"
"fmt" "fmt"
"html/template" "html/template"
@ -13,6 +13,7 @@ import (
"os/user" "os/user"
"path/filepath" "path/filepath"
"strings" "strings"
"regexp"
) )
type PageData struct { type PageData struct {
@ -27,7 +28,212 @@ type CommandOutput struct {
Error string Error string
} }
type CommandNode struct {
Operator string
Left *CommandNode
Right *CommandNode
Command string
}
var commandLog []CommandOutput var commandLog []CommandOutput
var variables = make(map[string]string)
func expandCommandSubstitution(command string, currentDir string) (string, error) {
// Do not expand single quoted strings
reSingleQuotes := regexp.MustCompile(`'([^']*)'`)
singleQuotedStrings := reSingleQuotes.FindAllString(command, -1)
placeholder := "\x00PLACEHOLDER\x00"
command = reSingleQuotes.ReplaceAllLiteralString(command, placeholder)
// Expand variables
reVar := regexp.MustCompile(`\$(\w+)`)
command = reVar.ReplaceAllStringFunc(command, func(match string) string {
varName := match[1:]
if val, exists := variables[varName]; exists {
return val
}
if envVal, exists := os.LookupEnv(varName); exists {
return envVal
}
return match
})
// Restore single-quoted strings and avoid expansion inside them
for _, sq := range singleQuotedStrings {
command = strings.Replace(command, placeholder, sq, 1)
}
// expand $(...) command substitution
re := regexp.MustCompile(`\$\(([^()]+)\)`)
for {
match := re.FindStringSubmatch(command)
if match == nil {
break
}
subCommand := strings.TrimSpace(match[1])
subOutput, err := executeCommandTree(parseCommandTree(subCommand), currentDir)
if err != nil {
return "", err
}
command = strings.Replace(command, match[0], strings.TrimSpace(subOutput), 1)
}
return command, nil
}
func setVariable(command string) bool {
re := regexp.MustCompile(`^(\w+)=(.*)$`)
match := re.FindStringSubmatch(command)
if match == nil {
return false
}
varName := match[1]
value := strings.Trim(match[2], `"`)
if strings.HasPrefix(command, "export ") {
os.Setenv(varName, value)
} else {
variables[varName] = value
}
return true
}
func parseCommandTree(command string) *CommandNode {
if strings.Contains(command, ";") {
parts := strings.SplitN(command, ";", 2)
return &CommandNode{
Operator: ";",
Left: parseCommandTree(strings.TrimSpace(parts[0])),
Right: parseCommandTree(strings.TrimSpace(parts[1])),
}
} else if strings.Contains(command, ">") {
parts := strings.SplitN(command, ">", 2)
return &CommandNode{
Operator: ">",
Left: parseCommandTree(strings.TrimSpace(parts[0])),
Right: &CommandNode{Command: strings.TrimSpace(parts[1])},
}
} else if strings.Contains(command, "<") {
parts := strings.SplitN(command, "<", 2)
return &CommandNode{
Operator: "<",
Left: parseCommandTree(strings.TrimSpace(parts[0])),
Right: &CommandNode{Command: strings.TrimSpace(parts[1])},
}
} else if strings.Contains(command, "|") {
parts := strings.SplitN(command, "|", 2)
return &CommandNode{
Operator: "|",
Left: parseCommandTree(strings.TrimSpace(parts[0])),
Right: parseCommandTree(strings.TrimSpace(parts[1])),
}
} else if strings.Contains(command, "&&") {
parts := strings.SplitN(command, "&&", 2)
return &CommandNode{
Operator: "&&",
Left: parseCommandTree(strings.TrimSpace(parts[0])),
Right: parseCommandTree(strings.TrimSpace(parts[1])),
}
} else if strings.Contains(command, "||") {
parts := strings.SplitN(command, "||", 2)
return &CommandNode{
Operator: "||",
Left: parseCommandTree(strings.TrimSpace(parts[0])),
Right: parseCommandTree(strings.TrimSpace(parts[1])),
}
}
return &CommandNode{Command: command}
}
func executeCommandTree(node *CommandNode, currentDir string) (string, error) {
if node == nil {
return "", nil
}
switch node.Operator {
case "&&":
leftOutput, err := executeCommandTree(node.Left, currentDir)
if err != nil {
return "", err
}
if leftOutput != "" {
return executeCommandTree(node.Right, currentDir)
}
return executeCommandTree(node.Right, currentDir)
case "||":
leftOutput, err := executeCommandTree(node.Left, currentDir)
if err == nil && leftOutput != "" {
return leftOutput, nil
}
return executeCommandTree(node.Right, currentDir)
case "|":
leftOutput, err := executeCommandTree(node.Left, currentDir)
if err != nil {
return "", err
}
cmdArgs := parseCommandWithQuotes(node.Right.Command)
if len(cmdArgs) == 0 {
return "", fmt.Errorf("Invalid Command")
}
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
cmd.Dir = currentDir
cmd.Stdin = strings.NewReader(leftOutput)
var outputBuffer bytes.Buffer
cmd.Stdout = &outputBuffer
cmd.Stderr = &outputBuffer
if err := cmd.Run(); err != nil {
return "", err
}
return outputBuffer.String(), nil
case ">":
leftOutput, err := executeCommandTree(node.Left, currentDir)
if err != nil {
return "", err
}
file, err := os.Create(strings.TrimSpace(node.Right.Command))
if err != nil {
return "", err
}
defer file.Close()
_, err = file.WriteString(leftOutput)
return "", err
case "<":
file, err := os.Open(strings.TrimSpace(node.Right.Command))
if err != nil {
return "", err
}
defer file.Close()
cmdArgs := parseCommandWithQuotes(node.Left.Command)
if len(cmdArgs) == 0 {
return "", fmt.Errorf("Invalid command")
}
cmd := exec.Command(cmdArgs[0], cmdArgs[1:]...)
cmd.Dir = currentDir
cmd.Stdin = file
var outputBuffer bytes.Buffer
cmd.Stdout = &outputBuffer
cmd.Stderr = &outputBuffer
if err := cmd.Run(); err != nil {
return "", err
}
return outputBuffer.String(), nil
case ";":
leftOutput, _ := executeCommandTree(node.Left, currentDir)
rightOutput, _ := executeCommandTree(node.Right, currentDir)
return leftOutput + rightOutput, nil
}
return executeSimpleCommand(node.Command, currentDir).Output, nil
}
func executeCommand(command string, args []string, dir string) (string, error) { func executeCommand(command string, args []string, dir string) (string, error) {
cmd := exec.Command(command, args...) cmd := exec.Command(command, args...)
@ -118,176 +324,38 @@ func fileUploadHandler(w http.ResponseWriter, r *http.Request) {
http.Error(w, "Failed to write file", http.StatusInternalServerError) http.Error(w, "Failed to write file", http.StatusInternalServerError)
return return
} }
fmt.Fprintf(w, "File %s uploaded successfully to %s", header.Filename, uploadDir) cmdOutput := CommandOutput{Command: "upload", Output: "File saved successfully", Error: ""}
// fmt.Fprintf(w, "File %s uploaded successfully to %s", header.Filename, uploadDir)
commandLog = append(commandLog, cmdOutput)
} }
} }
//////////////////////////////////////////////////////////////////////////////// // func fileDownloadHandler(w http.ResponseWriter, r *http.Request) {
// // if r.Method == http.MethodPost {
// Command redirection and Piping // file, header, err := r.FormFile("file")
//
////////////////////////////////////////////////////////////////////////////////
func processCommand(command string, currentDir string) CommandOutput { func processCommand(command string, currentDir string) CommandOutput {
if strings.Contains(command, "|") { var errorMsg string
return executeCommandWithPipe(command, currentDir)
} else if strings.Contains(command, ">") { // Handle variable assignments before execution
return handleOutputRedirection(command, currentDir) if setVariable(command) {
} else if strings.Contains(command, "<") { return CommandOutput{Command: command, Output: "", Error: ""}
return handleInputRedirection(command, currentDir)
} }
return executeSimpleCommand(command, currentDir) // This handles command substitution which is $(...) in bash
} expandedCommand, err := expandCommandSubstitution(command, currentDir)
// Handle output redirection which is '>'
func handleOutputRedirection(command string, currentDir string) CommandOutput {
var cmdOutput CommandOutput
parts := strings.SplitN(command, ">", 2)
// parts := strings.Split(command, ">")
cmdStr := strings.TrimSpace(parts[0])
outputFile := strings.TrimSpace(parts[1])
// cmdParts := strings.Fields(cmdStr)
cmdParts := parseCommandWithQuotes(cmdStr)
if len(cmdParts) == 0 {
return cmdOutput
}
cmd := exec.Command(cmdParts[0], cmdParts[1:]...)
cmd.Dir = currentDir
file, err := os.Create(outputFile)
if err != nil { if err != nil {
cmdOutput.Error = fmt.Sprintf("Error creating output file: %v", err) errorMsg = err.Error()
return cmdOutput return CommandOutput{Command: command, Output: "", Error: errorMsg}
} }
defer file.Close()
cmd.Stdout = file tree := parseCommandTree(expandedCommand)
cmd.Stderr = file output, err := executeCommandTree(tree, currentDir)
err = cmd.Run()
if err != nil { if err != nil {
cmdOutput.Error = fmt.Sprintf("Error executing command: %v", err) errorMsg = err.Error()
} }
return CommandOutput{Command: command, Output: output, Error: errorMsg}
cmdOutput.Command = command
return cmdOutput
}
func handleInputRedirection(command string, currentDir string) CommandOutput {
var cmdOutput CommandOutput
// parts := strings.Split(command, "<")
parts := strings.SplitN(command, "<", 2)
cmdStr := strings.TrimSpace(parts[0])
inputFile := strings.TrimSpace(parts[1])
cmdParts := parseCommandWithQuotes(cmdStr)
if len(cmdParts) == 0 {
return cmdOutput
}
cmd := exec.Command(cmdParts[0], cmdParts[1:]...)
cmd.Dir = currentDir
file, err := os.Open(inputFile)
if err != nil {
cmdOutput.Error = fmt.Sprintf("Error opening input file: %v", err)
return cmdOutput
}
defer file.Close()
cmd.Stdin = file
output, err := cmd.CombinedOutput()
cmdOutput.Output = string(output)
if err != nil {
cmdOutput.Error = err.Error()
}
cmdOutput.Command = command
return cmdOutput
}
func executeCommandWithPipe(command string, currentDir string) CommandOutput {
var cmdOutput CommandOutput
cmdOutput.Command = command
pipeCommands := strings.Split(command, "|")
var prevOutput []byte
for i, cmdPart := range pipeCommands {
cmdPart = strings.TrimSpace(cmdPart)
args := parseCommandWithQuotes(cmdPart)
if len(args) == 0 {
continue
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = currentDir
if i > 0 {
cmd.Stdin = strings.NewReader(string(prevOutput))
}
var outputBuffer strings.Builder
cmd.Stdout = &outputBuffer
cmd.Stderr = &outputBuffer
err := cmd.Run()
if err != nil {
cmdOutput.Error = fmt.Sprintf("Error executing command '%s': %v", cmdPart, err)
return cmdOutput
}
// Store the output for the next pipe stage
prevOutput = []byte(outputBuffer.String())
}
// Final output from the last command in the pipe
cmdOutput.Output = string(prevOutput)
return cmdOutput
}
func splitCommand(input string) []string {
scanner := bufio.NewScanner(strings.NewReader(input))
scanner.Split(bufio.ScanWords)
var parts []string
var buffer bytes.Buffer
inQuotes := false
quoteChar := rune(0)
for scanner.Scan() {
token := scanner.Text()
if inQuotes {
buffer.WriteString(" ")
buffer.WriteString(token)
if strings.HasSuffix(token, string(quoteChar)) {
inQuotes = false
parts = append(parts, strings.Trim(buffer.String(), `"'`))
buffer.Reset()
}
} else {
if strings.HasPrefix(token, `"`) || strings.HasPrefix(token, `'`) {
inQuotes = true
quoteChar = rune(token[0])
buffer.WriteString(token)
} else {
parts = append(parts, token)
}
}
}
if buffer.Len() > 0 {
parts = append(parts, buffer.String())
}
return parts
} }
func parseCommandWithQuotes(command string) []string { func parseCommandWithQuotes(command string) []string {
@ -368,6 +436,176 @@ func handler(w http.ResponseWriter, r *http.Request) {
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Go Web Shell</title> <title>Go Web Shell</title>
<script>
document.addEventListener("DOMContentLoaded", function() {
const input = document.getElementById("command-input");
if (!input) return;
let commandHistory = JSON.parse(sessionStorage.getItem("commandHistory")) || [];
let historyIndex = commandHistory.length;
let tabIndex = -1;
let tabMatches = [];
document.querySelector("form").addEventListener("submit", function(event) {
const command = input.value.trim();
if (command) {
commandHistory.push(command);
sessionStorage.setItem("commandHistory", JSON.stringify(commandHistory));
historyIndex = commandHistory.length;
}
});
window.addEventListener("keydown", function(event) {
if (document.activeElement !== input) return;
const cursorPos = input.selectionStart;
const textLength = input.value.length;
// Prevent default behavior for specific Ctrl+Key shortcuts
if (event.ctrlKey && ["w", "n", "p", "h", "e", "a", "k", "u", "d", "r", "t"].includes(event.key.toLowerCase())) {
event.preventDefault();
event.stopPropagation();
}
// Ctrl+A: Move cursor to the beginning
if (event.ctrlKey && event.key === "a") {
input.setSelectionRange(0, 0);
}
// Ctrl+E: Move cursor to the end
else if (event.ctrlKey && event.key === "e") {
input.setSelectionRange(textLength, textLength);
}
// Ctrl+U: Clear the input field
else if (event.ctrlKey && event.key === "u") {
input.value = "";
}
// Ctrl+K: Delete everything after the cursor
else if (event.ctrlKey && event.key === "k") {
input.value = input.value.substring(0, cursorPos);
}
// Ctrl+W: Delete the previous word
else if (event.ctrlKey && event.key === "w") {
const beforeCursor = input.value.substring(0, cursorPos);
const afterCursor = input.value.substring(cursorPos);
const newBeforeCursor = beforeCursor.replace(/\S+\s*$/, ""); // Delete last word
input.value = newBeforeCursor + afterCursor;
input.setSelectionRange(newBeforeCursor.length, newBeforeCursor.length);
}
// Ctrl+H: Delete the previous character (Backspace)
else if (event.ctrlKey && event.key === "h") {
if (cursorPos > 0) {
input.value = input.value.substring(0, cursorPos - 1) + input.value.substring(cursorPos);
input.setSelectionRange(cursorPos - 1, cursorPos - 1);
}
}
// Ctrl+D: Delete character under cursor (or clear input if empty)
else if (event.ctrlKey && event.key === "d") {
if (textLength === 0) {
console.log("Ctrl+D: No input, simulating EOF");
} else if (cursorPos < textLength) {
input.value = input.value.substring(0, cursorPos) + input.value.substring(cursorPos + 1);
input.setSelectionRange(cursorPos, cursorPos);
}
}
// Ctrl+P: Previous command (up)
else if (event.ctrlKey && event.key === "p") {
if (historyIndex > 0) {
historyIndex--;
input.value = commandHistory[historyIndex];
} else if (historyIndex === 0) {
input.value = commandHistory[0];
}
}
// Ctrl+N: Next command (down)
else if (event.ctrlKey && event.key === "n") {
if (historyIndex < commandHistory.length - 1) {
historyIndex++;
input.value = commandHistory[historyIndex];
} else {
historyIndex = commandHistory.length;
input.value = "";
}
}
// Ctrl+R: Prevent page reload (for future reverse search)
else if (event.ctrlKey && event.key === "r") {
console.log("Reverse search triggered (not yet implemented)");
}
// Tab Completion
else if (event.key === "Tab") {
event.preventDefault();
const currentText = input.value.trim();
if (currentText === "") return;
// Find all matching commands from history
if (tabIndex === -1) {
tabMatches = commandHistory.filter(cmd => cmd.startsWith(currentText));
if (tabMatches.length === 0) return;
}
// Cycle through matches
if (event.shiftKey) {
tabIndex = tabIndex > 0 ? tabIndex - 1 : tabMatches.length - 1; // Shift+Tab goes backward
} else {
tabIndex = (tabIndex + 1) % tabMatches.length;
}
input.value = tabMatches[tabIndex];
}
// Allow Enter key to submit form
if (event.key === "Enter") {
tabIndex = -1; // Reset tab completion cycle
return;
}
}, true);
});
</script>
<script>
document.addEventListener("DOMContentLoaded", function() {
document.getElementById("command-input").addEventListener("keydown", function(event) {
if (event.key === "Enter") {
let inputVal = this.value.trim();
if (inputVal === "upload") {
document.getElementById("inputFile").click();
}
}
});
function requestUpload() {
let fileInput = document.getElementById("inputFile");
console.log("file is");
console.log(fileInput);
if (fileInput.files.length === 0) {
console.error("No file selected.");
return;
}
let file = fileInput.files[0];
let formData = new FormData();
formData.append("file", file);
fetch("http://localhost:8080/upload", {
method: "POST",
body: formData
})
.then(response => response.text())
.then(data => {
console.log("Upload succesful:", data);
document.getElementById("command-input").value = "";
})
.catch(error => console.error("Upload failed:", error));
}
});
</script>
<style> <style>
body { body {
background-color: #222; background-color: #222;
@ -385,7 +623,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
padding: 12px; padding: 12px;
overflow-y: auto; overflow-y: auto;
white-space: pre-wrap; white-space: pre-wrap;
border: none; border: 1px solid white;
margin: 10px; margin: 10px;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
@ -427,10 +665,17 @@ func handler(w http.ResponseWriter, r *http.Request) {
<form method="POST" autocomplete="off"> <form method="POST" autocomplete="off">
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<span class="command">{{.CurrentUsername}}:<span class="directory">{{.CurrentDir}}</span>$ </span> <span class="command">{{.CurrentUsername}}:<span class="directory">{{.CurrentDir}}</span>$ </span>
<input type="text" name="command" placeholder="Type a command here..." autofocus required> <input id="command-input" type="text" name="command" placeholder="Type a command here..." autofocus required>
</div> </div>
</form> </form>
</div> </div>
<div>
<form id="fileUpload" action="/upload" method="POST" enctype="multipart/form-data">
<label for="inputFile">Select a file to upload:</label>
<input name="inputFile" id="inputFile" type="file">
<button id="fileUploadButton" onclick="document.getElementById('inputFile').click()">Upload</button>
</form>
</div>
</body> </body>
</html> </html>
`)) `))