diff --git a/gommand.go b/gommand.go index e474f71..e19c7a4 100644 --- a/gommand.go +++ b/gommand.go @@ -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) - } else if strings.Contains(command, "<") { - cmdOutput = handleInputRedirection(command, currentDir) - } else { - cmdOutput = executeSimpleCommand(command, currentDir) - } + return executeCommandWithPipe(command, currentDir) + } else if strings.Contains(command, ">") { + return handleOutputRedirection(command, currentDir) + } else if strings.Contains(command, "<") { + 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) - } - // 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 + if i > 0 { + cmd.Stdin = strings.NewReader(string(prevOutput)) } - 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 + var outputBuffer strings.Builder + 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) + 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()) } - // 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) + // 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, ¤tDir) 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,9 +424,9 @@ func handler(w http.ResponseWriter, r *http.Request) {
{{.Error}}
{{end}} {{end}} -
+
- {{.CurrentUsername}}:{{.CurrentDir}} $ + {{.CurrentUsername}}:{{.CurrentDir}}$