added support for quoted strings.

This commit is contained in:
Stefan Friese 2025-01-28 16:01:58 +00:00
parent 201969ef90
commit 84377914ac
1 changed files with 132 additions and 89 deletions

View File

@ -2,6 +2,8 @@ package main
import (
// "bytes"
"bufio"
"bytes"
"fmt"
"html/template"
"io"
@ -36,12 +38,13 @@ func executeCommand(command string, args []string, dir string) (string, error) {
func executeSimpleCommand(command string, currentDir string) CommandOutput {
var cmdOutput CommandOutput
parts := strings.Fields(command)
if len(parts) == 0 {
// args := strings.Fields(command)
args := parseCommandWithQuotes(command)
if len(args) == 0 {
return cmdOutput
}
cmd := exec.Command(parts[0], parts[1:]...)
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = currentDir
output, err := cmd.CombinedOutput()
cmdOutput.Command = command
@ -125,35 +128,29 @@ func fileUploadHandler(w http.ResponseWriter, r *http.Request) {
//
////////////////////////////////////////////////////////////////////////////////
func executeCommandWithRedirection(command string, currentDir string) CommandOutput {
var cmdOutput CommandOutput
// First pipe
func processCommand(command string, currentDir string) CommandOutput {
if strings.Contains(command, "|") {
cmdOutput = executeCommandWithPipe(command, currentDir)
} else {
if strings.Contains(command, ">") {
cmdOutput = handleOutputRedirection(command, currentDir)
return executeCommandWithPipe(command, currentDir)
} else if strings.Contains(command, ">") {
return handleOutputRedirection(command, currentDir)
} else if strings.Contains(command, "<") {
cmdOutput = handleInputRedirection(command, currentDir)
} else {
cmdOutput = executeSimpleCommand(command, currentDir)
}
return handleInputRedirection(command, currentDir)
}
return cmdOutput
return executeSimpleCommand(command, currentDir)
}
// Handle output redirection which is '>'
func handleOutputRedirection(command string, currentDir string) CommandOutput {
var cmdOutput CommandOutput
parts := strings.Split(command, ">")
parts := strings.SplitN(command, ">", 2)
// parts := strings.Split(command, ">")
cmdStr := strings.TrimSpace(parts[0])
outputFile := strings.TrimSpace(parts[1])
cmdParts := strings.Fields(cmdStr)
// cmdParts := strings.Fields(cmdStr)
cmdParts := parseCommandWithQuotes(cmdStr)
if len(cmdParts) == 0 {
return cmdOutput
}
@ -184,11 +181,12 @@ func handleOutputRedirection(command string, currentDir string) CommandOutput {
func handleInputRedirection(command string, currentDir string) CommandOutput {
var cmdOutput CommandOutput
parts := strings.Split(command, "<")
// parts := strings.Split(command, "<")
parts := strings.SplitN(command, "<", 2)
cmdStr := strings.TrimSpace(parts[0])
inputFile := strings.TrimSpace(parts[1])
cmdParts := strings.Fields(cmdStr)
cmdParts := parseCommandWithQuotes(cmdStr)
if len(cmdParts) == 0 {
return cmdOutput
}
@ -216,78 +214,131 @@ func handleInputRedirection(command string, currentDir string) CommandOutput {
func executeCommandWithPipe(command string, currentDir string) CommandOutput {
var cmdOutput CommandOutput
commands := strings.Split(command, "|")
cmdOutput.Command = command
// var prevCmdOutput []byte
var err error
pipeCommands := strings.Split(command, "|")
var prevOutput []byte
var cmds []*exec.Cmd // Pipeline of commands
for _, cmdPart := range commands {
for i, cmdPart := range pipeCommands {
cmdPart = strings.TrimSpace(cmdPart)
parts := strings.Fields(cmdPart)
if len(parts) == 0 {
args := parseCommandWithQuotes(cmdPart)
if len(args) == 0 {
continue
}
cmd := exec.Command(parts[0], parts[1:]...)
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = currentDir
cmds = append(cmds, cmd)
if i > 0 {
cmd.Stdin = strings.NewReader(string(prevOutput))
}
// Connect the commands through the pipeline
for i := 0; i < len(cmds)-1; i++ {
pipe, pipeErr := cmds[i].StdoutPipe()
if pipeErr != nil {
cmdOutput.Command = command
cmdOutput.Error = fmt.Sprintf("Error creating pipe: %v", pipeErr)
return cmdOutput
}
cmds[i+1].Stdin = pipe
}
// Capture the final output of the last command
var outputBuffer strings.Builder
cmds[len(cmds)-1].Stdout = &outputBuffer
cmds[len(cmds)-1].Stderr = &outputBuffer
cmd.Stdout = &outputBuffer
cmd.Stderr = &outputBuffer
// Start each command in the pipeline
for _, cmd := range cmds {
err = cmd.Start()
err := cmd.Run()
if err != nil {
cmdOutput.Command = command
cmdOutput.Error = fmt.Sprintf("Error starting command '%s': %v", cmd.Path, err)
return cmdOutput
}
}
// Wait for each command to complete
for _, cmd := range cmds {
err = cmd.Wait()
if err != nil {
cmdOutput.Command = command
cmdOutput.Error = fmt.Sprintf("Error executing command '%s': %v", cmd.Path, err)
return cmdOutput
}
}
// if i > 0 {
// cmd.Stdin = bytes.NewReader(prevCmdOutput)
// }
// prevCmdOutput, err = cmd.CombinedOutput()
// if err != nil {
// cmdOutput.Command = command
// cmdOutput.Error = fmt.Sprintf("Error executing command '%s': %v", cmdPart, err)
// return cmdOutput
// }
// }
cmdOutput.Command = command
cmdOutput.Output = outputBuffer.String()
// cmdOutput.Output = string(prevCmdOutput)
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 {
var args []string
var current strings.Builder
inSingleQuote, inDoubleQuote := false, false
for i := 0; i < len(command); i++ {
c := command[i]
switch c {
case '\'':
if !inDoubleQuote {
inSingleQuote = !inSingleQuote
continue
}
case '"':
if !inSingleQuote {
inDoubleQuote = !inDoubleQuote
continue
}
case ' ':
if !inSingleQuote && !inDoubleQuote {
if current.Len() > 0 {
args = append(args, current.String())
current.Reset()
}
continue
}
case '\\':
// Handle escaped characters
if i+1 < len(command) {
next := command[i+1]
if next == '"' || next == '\'' || next == '\\' {
current.WriteByte(next)
i++
continue
}
}
}
current.WriteByte(c)
}
// Add the last argument
if current.Len() > 0 {
args = append(args, current.String())
}
return args
}
func handler(w http.ResponseWriter, r *http.Request) {
currentDir, _ := os.Getwd()
@ -304,16 +355,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
cmdOutput := changeDirectory(command, args, &currentDir)
commandLog = append(commandLog, cmdOutput)
} else {
cmdOutput := executeCommandWithRedirection(input, currentDir)
// output, err := executeCommand(command, args, currentDir)
// cmdOutput := CommandOutput{
// Command: command + " " + strings.Join(args, " "),
// Output: output,
// }
// if err != nil {
// cmdOutput.Error = err.Error()
// }
cmdOutput := processCommand(input, currentDir)
commandLog = append(commandLog, cmdOutput)
}
}
@ -331,7 +373,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
background-color: #222;
color: #eee;
font-family: monospace;
font-size: 10pt;
font-size: 14pt;
margin: 0;
padding: 0;
height: 100vh;
@ -340,7 +382,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
}
#terminal {
flex: 1;
padding: 10px;
padding: 12px;
overflow-y: auto;
white-space: pre-wrap;
border: none;
@ -349,6 +391,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
flex-direction: column;
}
input {
font-size: 14pt;
background: #222;
color: #eee;
border: none;
@ -381,7 +424,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
<div class="error">{{.Error}}</div>
{{end}}
{{end}}
<form method="POST">
<form method="POST" autocomplete="off">
<div style="display: flex; align-items: center;">
<span class="command">{{.CurrentUsername}}:<span class="directory">{{.CurrentDir}}</span>$ </span>
<input type="text" name="command" placeholder="Type a command here..." autofocus required>