working on upload command. added keyboard shortcuts
This commit is contained in:
parent
84377914ac
commit
dbe33be2c4
567
gommand.go
567
gommand.go
|
@ -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>
|
||||||
`))
|
`))
|
||||||
|
|
Loading…
Reference in New Issue