From dbe33be2c43f2a79084c6218a98abf85544e6b47 Mon Sep 17 00:00:00 2001 From: Stefan Friese Date: Fri, 31 Jan 2025 15:43:35 +0000 Subject: [PATCH] working on upload command. added keyboard shortcuts --- gommand.go | 567 ++++++++++++++++++++++++++++++++++++++--------------- 1 file changed, 406 insertions(+), 161 deletions(-) diff --git a/gommand.go b/gommand.go index e19c7a4..07f491c 100644 --- a/gommand.go +++ b/gommand.go @@ -2,7 +2,7 @@ package main import ( // "bytes" - "bufio" + // "bufio" "bytes" "fmt" "html/template" @@ -13,6 +13,7 @@ import ( "os/user" "path/filepath" "strings" + "regexp" ) type PageData struct { @@ -27,7 +28,212 @@ type CommandOutput struct { Error string } +type CommandNode struct { + Operator string + Left *CommandNode + Right *CommandNode + Command string +} + 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) { 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) 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) + } } -//////////////////////////////////////////////////////////////////////////////// -// -// Command redirection and Piping -// -//////////////////////////////////////////////////////////////////////////////// +// func fileDownloadHandler(w http.ResponseWriter, r *http.Request) { +// if r.Method == http.MethodPost { +// file, header, err := r.FormFile("file") func processCommand(command string, currentDir string) CommandOutput { - if strings.Contains(command, "|") { - return executeCommandWithPipe(command, currentDir) - } else if strings.Contains(command, ">") { - return handleOutputRedirection(command, currentDir) - } else if strings.Contains(command, "<") { - return handleInputRedirection(command, currentDir) + var errorMsg string + + // Handle variable assignments before execution + if setVariable(command) { + return CommandOutput{Command: command, Output: "", Error: ""} } - return executeSimpleCommand(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) + // This handles command substitution which is $(...) in bash + expandedCommand, err := expandCommandSubstitution(command, currentDir) if err != nil { - cmdOutput.Error = fmt.Sprintf("Error creating output file: %v", err) - return cmdOutput + errorMsg = err.Error() + return CommandOutput{Command: command, Output: "", Error: errorMsg} } - defer file.Close() - cmd.Stdout = file - cmd.Stderr = file - - err = cmd.Run() + tree := parseCommandTree(expandedCommand) + output, err := executeCommandTree(tree, currentDir) if err != nil { - cmdOutput.Error = fmt.Sprintf("Error executing command: %v", err) + errorMsg = err.Error() } - - 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 + return CommandOutput{Command: command, Output: output, Error: errorMsg} } func parseCommandWithQuotes(command string) []string { @@ -368,6 +436,176 @@ func handler(w http.ResponseWriter, r *http.Request) { Go Web Shell + +