added support for quoted strings.
This commit is contained in:
parent
201969ef90
commit
84377914ac
221
gommand.go
221
gommand.go
|
@ -2,6 +2,8 @@ package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
// "bytes"
|
// "bytes"
|
||||||
|
"bufio"
|
||||||
|
"bytes"
|
||||||
"fmt"
|
"fmt"
|
||||||
"html/template"
|
"html/template"
|
||||||
"io"
|
"io"
|
||||||
|
@ -36,12 +38,13 @@ func executeCommand(command string, args []string, dir string) (string, error) {
|
||||||
|
|
||||||
func executeSimpleCommand(command string, currentDir string) CommandOutput {
|
func executeSimpleCommand(command string, currentDir string) CommandOutput {
|
||||||
var cmdOutput CommandOutput
|
var cmdOutput CommandOutput
|
||||||
parts := strings.Fields(command)
|
// args := strings.Fields(command)
|
||||||
if len(parts) == 0 {
|
args := parseCommandWithQuotes(command)
|
||||||
|
if len(args) == 0 {
|
||||||
return cmdOutput
|
return cmdOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(parts[0], parts[1:]...)
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
cmd.Dir = currentDir
|
cmd.Dir = currentDir
|
||||||
output, err := cmd.CombinedOutput()
|
output, err := cmd.CombinedOutput()
|
||||||
cmdOutput.Command = command
|
cmdOutput.Command = command
|
||||||
|
@ -125,35 +128,29 @@ func fileUploadHandler(w http.ResponseWriter, r *http.Request) {
|
||||||
//
|
//
|
||||||
////////////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////////////
|
||||||
|
|
||||||
func executeCommandWithRedirection(command string, currentDir string) CommandOutput {
|
func processCommand(command string, currentDir string) CommandOutput {
|
||||||
var cmdOutput CommandOutput
|
|
||||||
|
|
||||||
// First pipe
|
|
||||||
|
|
||||||
if strings.Contains(command, "|") {
|
if strings.Contains(command, "|") {
|
||||||
cmdOutput = executeCommandWithPipe(command, currentDir)
|
return executeCommandWithPipe(command, currentDir)
|
||||||
} else {
|
} else if strings.Contains(command, ">") {
|
||||||
if strings.Contains(command, ">") {
|
return handleOutputRedirection(command, currentDir)
|
||||||
cmdOutput = handleOutputRedirection(command, currentDir)
|
} else if strings.Contains(command, "<") {
|
||||||
} else if strings.Contains(command, "<") {
|
return handleInputRedirection(command, currentDir)
|
||||||
cmdOutput = handleInputRedirection(command, currentDir)
|
|
||||||
} else {
|
|
||||||
cmdOutput = executeSimpleCommand(command, currentDir)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return cmdOutput
|
return executeSimpleCommand(command, currentDir)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle output redirection which is '>'
|
// Handle output redirection which is '>'
|
||||||
func handleOutputRedirection(command string, currentDir string) CommandOutput {
|
func handleOutputRedirection(command string, currentDir string) CommandOutput {
|
||||||
var cmdOutput CommandOutput
|
var cmdOutput CommandOutput
|
||||||
|
|
||||||
parts := strings.Split(command, ">")
|
parts := strings.SplitN(command, ">", 2)
|
||||||
|
// parts := strings.Split(command, ">")
|
||||||
cmdStr := strings.TrimSpace(parts[0])
|
cmdStr := strings.TrimSpace(parts[0])
|
||||||
outputFile := strings.TrimSpace(parts[1])
|
outputFile := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
cmdParts := strings.Fields(cmdStr)
|
// cmdParts := strings.Fields(cmdStr)
|
||||||
|
cmdParts := parseCommandWithQuotes(cmdStr)
|
||||||
if len(cmdParts) == 0 {
|
if len(cmdParts) == 0 {
|
||||||
return cmdOutput
|
return cmdOutput
|
||||||
}
|
}
|
||||||
|
@ -184,11 +181,12 @@ func handleOutputRedirection(command string, currentDir string) CommandOutput {
|
||||||
func handleInputRedirection(command string, currentDir string) CommandOutput {
|
func handleInputRedirection(command string, currentDir string) CommandOutput {
|
||||||
var cmdOutput CommandOutput
|
var cmdOutput CommandOutput
|
||||||
|
|
||||||
parts := strings.Split(command, "<")
|
// parts := strings.Split(command, "<")
|
||||||
|
parts := strings.SplitN(command, "<", 2)
|
||||||
cmdStr := strings.TrimSpace(parts[0])
|
cmdStr := strings.TrimSpace(parts[0])
|
||||||
inputFile := strings.TrimSpace(parts[1])
|
inputFile := strings.TrimSpace(parts[1])
|
||||||
|
|
||||||
cmdParts := strings.Fields(cmdStr)
|
cmdParts := parseCommandWithQuotes(cmdStr)
|
||||||
if len(cmdParts) == 0 {
|
if len(cmdParts) == 0 {
|
||||||
return cmdOutput
|
return cmdOutput
|
||||||
}
|
}
|
||||||
|
@ -216,78 +214,131 @@ func handleInputRedirection(command string, currentDir string) CommandOutput {
|
||||||
|
|
||||||
func executeCommandWithPipe(command string, currentDir string) CommandOutput {
|
func executeCommandWithPipe(command string, currentDir string) CommandOutput {
|
||||||
var cmdOutput CommandOutput
|
var cmdOutput CommandOutput
|
||||||
commands := strings.Split(command, "|")
|
cmdOutput.Command = command
|
||||||
|
|
||||||
// var prevCmdOutput []byte
|
pipeCommands := strings.Split(command, "|")
|
||||||
var err error
|
var prevOutput []byte
|
||||||
|
|
||||||
var cmds []*exec.Cmd // Pipeline of commands
|
for i, cmdPart := range pipeCommands {
|
||||||
for _, cmdPart := range commands {
|
|
||||||
cmdPart = strings.TrimSpace(cmdPart)
|
cmdPart = strings.TrimSpace(cmdPart)
|
||||||
parts := strings.Fields(cmdPart)
|
args := parseCommandWithQuotes(cmdPart)
|
||||||
if len(parts) == 0 {
|
if len(args) == 0 {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
cmd := exec.Command(parts[0], parts[1:]...)
|
cmd := exec.Command(args[0], args[1:]...)
|
||||||
cmd.Dir = currentDir
|
cmd.Dir = currentDir
|
||||||
cmds = append(cmds, cmd)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Connect the commands through the pipeline
|
if i > 0 {
|
||||||
for i := 0; i < len(cmds)-1; i++ {
|
cmd.Stdin = strings.NewReader(string(prevOutput))
|
||||||
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
|
||||||
var outputBuffer strings.Builder
|
cmd.Stdout = &outputBuffer
|
||||||
cmds[len(cmds)-1].Stdout = &outputBuffer
|
cmd.Stderr = &outputBuffer
|
||||||
cmds[len(cmds)-1].Stderr = &outputBuffer
|
|
||||||
|
|
||||||
// Start each command in the pipeline
|
err := cmd.Run()
|
||||||
for _, cmd := range cmds {
|
|
||||||
err = cmd.Start()
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
cmdOutput.Command = command
|
cmdOutput.Error = fmt.Sprintf("Error executing command '%s': %v", cmdPart, err)
|
||||||
cmdOutput.Error = fmt.Sprintf("Error starting command '%s': %v", cmd.Path, err)
|
|
||||||
return cmdOutput
|
return cmdOutput
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Store the output for the next pipe stage
|
||||||
|
prevOutput = []byte(outputBuffer.String())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Wait for each command to complete
|
// Final output from the last command in the pipe
|
||||||
for _, cmd := range cmds {
|
cmdOutput.Output = string(prevOutput)
|
||||||
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)
|
|
||||||
return cmdOutput
|
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) {
|
func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
currentDir, _ := os.Getwd()
|
currentDir, _ := os.Getwd()
|
||||||
|
@ -304,16 +355,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
cmdOutput := changeDirectory(command, args, ¤tDir)
|
cmdOutput := changeDirectory(command, args, ¤tDir)
|
||||||
commandLog = append(commandLog, cmdOutput)
|
commandLog = append(commandLog, cmdOutput)
|
||||||
} else {
|
} else {
|
||||||
cmdOutput := executeCommandWithRedirection(input, currentDir)
|
cmdOutput := processCommand(input, currentDir)
|
||||||
|
|
||||||
// output, err := executeCommand(command, args, currentDir)
|
|
||||||
// cmdOutput := CommandOutput{
|
|
||||||
// Command: command + " " + strings.Join(args, " "),
|
|
||||||
// Output: output,
|
|
||||||
// }
|
|
||||||
// if err != nil {
|
|
||||||
// cmdOutput.Error = err.Error()
|
|
||||||
// }
|
|
||||||
commandLog = append(commandLog, cmdOutput)
|
commandLog = append(commandLog, cmdOutput)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -331,7 +373,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
background-color: #222;
|
background-color: #222;
|
||||||
color: #eee;
|
color: #eee;
|
||||||
font-family: monospace;
|
font-family: monospace;
|
||||||
font-size: 10pt;
|
font-size: 14pt;
|
||||||
margin: 0;
|
margin: 0;
|
||||||
padding: 0;
|
padding: 0;
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
@ -340,7 +382,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
}
|
}
|
||||||
#terminal {
|
#terminal {
|
||||||
flex: 1;
|
flex: 1;
|
||||||
padding: 10px;
|
padding: 12px;
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
white-space: pre-wrap;
|
white-space: pre-wrap;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -349,6 +391,7 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
flex-direction: column;
|
flex-direction: column;
|
||||||
}
|
}
|
||||||
input {
|
input {
|
||||||
|
font-size: 14pt;
|
||||||
background: #222;
|
background: #222;
|
||||||
color: #eee;
|
color: #eee;
|
||||||
border: none;
|
border: none;
|
||||||
|
@ -381,9 +424,9 @@ func handler(w http.ResponseWriter, r *http.Request) {
|
||||||
<div class="error">{{.Error}}</div>
|
<div class="error">{{.Error}}</div>
|
||||||
{{end}}
|
{{end}}
|
||||||
{{end}}
|
{{end}}
|
||||||
<form method="POST">
|
<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 type="text" name="command" placeholder="Type a command here..." autofocus required>
|
||||||
</div>
|
</div>
|
||||||
</form>
|
</form>
|
||||||
|
|
Loading…
Reference in New Issue