2025-01-28 09:02:18 +01:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
|
|
|
// "bytes"
|
2025-01-31 16:43:35 +01:00
|
|
|
// "bufio"
|
2025-01-28 17:01:58 +01:00
|
|
|
"bytes"
|
2025-02-05 12:59:26 +01:00
|
|
|
"embed"
|
2025-01-28 09:02:18 +01:00
|
|
|
"fmt"
|
|
|
|
"html/template"
|
|
|
|
"io"
|
2025-02-11 15:11:22 +01:00
|
|
|
"log"
|
2025-01-28 09:02:18 +01:00
|
|
|
"net/http"
|
2025-02-04 16:29:05 +01:00
|
|
|
"net/url"
|
2025-01-28 09:02:18 +01:00
|
|
|
"os"
|
|
|
|
"os/exec"
|
|
|
|
"os/user"
|
|
|
|
"path/filepath"
|
2025-01-31 16:43:35 +01:00
|
|
|
"regexp"
|
2025-02-04 16:29:05 +01:00
|
|
|
"strings"
|
2025-02-11 15:11:22 +01:00
|
|
|
|
|
|
|
"github.com/creack/pty"
|
|
|
|
"github.com/gorilla/websocket"
|
2025-01-28 09:02:18 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
type PageData struct {
|
|
|
|
CurrentDir string
|
2025-02-06 16:20:17 +01:00
|
|
|
Hostname string
|
2025-01-28 09:02:18 +01:00
|
|
|
CurrentUsername string
|
|
|
|
CommandLog []CommandOutput
|
|
|
|
}
|
|
|
|
|
|
|
|
type CommandOutput struct {
|
|
|
|
Command string
|
|
|
|
Output string
|
|
|
|
Error string
|
|
|
|
}
|
|
|
|
|
2025-01-31 16:43:35 +01:00
|
|
|
type CommandNode struct {
|
|
|
|
Operator string
|
|
|
|
Left *CommandNode
|
|
|
|
Right *CommandNode
|
|
|
|
Command string
|
|
|
|
}
|
|
|
|
|
2025-02-05 12:59:26 +01:00
|
|
|
//go:embed templates/*
|
|
|
|
var templateFiles embed.FS
|
|
|
|
//go:embed static/*
|
|
|
|
var staticFiles embed.FS
|
|
|
|
|
2025-01-28 09:02:18 +01:00
|
|
|
var commandLog []CommandOutput
|
2025-01-31 16:43:35 +01:00
|
|
|
var variables = make(map[string]string)
|
|
|
|
|
2025-02-05 12:59:26 +01:00
|
|
|
var tmpl = template.Must(template.ParseFS(templateFiles, "templates/index.html"))
|
|
|
|
|
2025-01-31 16:43:35 +01:00
|
|
|
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)
|
|
|
|
|
2025-02-11 15:11:22 +01:00
|
|
|
// TODO: or operator does not work correctly, fix it
|
2025-01-31 16:43:35 +01:00
|
|
|
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
|
|
|
|
}
|
2025-01-28 09:02:18 +01:00
|
|
|
|
|
|
|
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
|
2025-01-28 17:01:58 +01:00
|
|
|
// args := strings.Fields(command)
|
|
|
|
args := parseCommandWithQuotes(command)
|
|
|
|
if len(args) == 0 {
|
2025-01-28 09:02:18 +01:00
|
|
|
return cmdOutput
|
|
|
|
}
|
|
|
|
|
2025-01-28 17:01:58 +01:00
|
|
|
cmd := exec.Command(args[0], args[1:]...)
|
2025-01-28 09:02:18 +01:00
|
|
|
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) {
|
2025-02-04 16:29:05 +01:00
|
|
|
if r.Method != http.MethodPost {
|
|
|
|
http.Error(w, "Invalid request method", http.StatusMethodNotAllowed)
|
|
|
|
return
|
|
|
|
}
|
2025-01-28 09:02:18 +01:00
|
|
|
|
2025-02-04 16:29:05 +01:00
|
|
|
file, header, err := r.FormFile("file")
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to upload file", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer file.Close()
|
2025-01-28 09:02:18 +01:00
|
|
|
|
2025-02-04 16:29:05 +01:00
|
|
|
uploadDir := r.FormValue("uploadDir")
|
|
|
|
if uploadDir == "" {
|
|
|
|
uploadDir = "./"
|
|
|
|
}
|
2025-01-28 09:02:18 +01:00
|
|
|
|
2025-02-04 16:29:05 +01:00
|
|
|
os.MkdirAll(uploadDir, os.ModePerm)
|
|
|
|
destPath := filepath.Join(uploadDir, header.Filename)
|
|
|
|
out, err := os.Create(destPath)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to save file", http.StatusInternalServerError)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer out.Close()
|
2025-01-28 09:02:18 +01:00
|
|
|
|
2025-02-04 16:29:05 +01:00
|
|
|
_, err = io.Copy(out, file)
|
|
|
|
if err != nil {
|
|
|
|
http.Error(w, "Failed to write file", http.StatusInternalServerError)
|
|
|
|
return
|
2025-01-28 09:02:18 +01:00
|
|
|
}
|
2025-02-04 16:29:05 +01:00
|
|
|
|
|
|
|
w.Write([]byte("File uploaded successfully"))
|
2025-01-28 09:02:18 +01:00
|
|
|
}
|
|
|
|
|
2025-02-04 16:29:05 +01:00
|
|
|
func fileDownloadHandler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
filePath := r.URL.Query().Get("filePath")
|
|
|
|
if filePath == ""{
|
|
|
|
http.Error(w, "Filepath is required", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
absPath, err := filepath.Abs(filePath)
|
|
|
|
if err != nil || !fileExists(absPath) {
|
|
|
|
http.Error(w, "File not found", http.StatusNotFound)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
w.Header().Set("Content-Disposition", "attachement; filename=\""+filepath.Base(absPath)+"\"")
|
|
|
|
w.Header().Set("Content-Type", "application/octet-stream")
|
|
|
|
http.ServeFile(w, r, absPath)
|
|
|
|
}
|
|
|
|
|
|
|
|
func fileExists(filePath string) bool {
|
|
|
|
_, err := os.Stat(filePath)
|
|
|
|
return !os.IsNotExist(err)
|
|
|
|
}
|
2025-01-28 09:02:18 +01:00
|
|
|
|
2025-01-31 16:43:35 +01:00
|
|
|
func processCommand(command string, currentDir string) CommandOutput {
|
|
|
|
var errorMsg string
|
2025-01-28 09:02:18 +01:00
|
|
|
|
2025-01-31 16:43:35 +01:00
|
|
|
// Handle variable assignments before execution
|
|
|
|
if setVariable(command) {
|
|
|
|
return CommandOutput{Command: command, Output: "", Error: ""}
|
2025-01-28 09:02:18 +01:00
|
|
|
}
|
|
|
|
|
2025-01-31 16:43:35 +01:00
|
|
|
// This handles command substitution which is $(...) in bash
|
|
|
|
expandedCommand, err := expandCommandSubstitution(command, currentDir)
|
2025-01-28 09:02:18 +01:00
|
|
|
if err != nil {
|
2025-01-31 16:43:35 +01:00
|
|
|
errorMsg = err.Error()
|
|
|
|
return CommandOutput{Command: command, Output: "", Error: errorMsg}
|
2025-01-28 09:02:18 +01:00
|
|
|
}
|
|
|
|
|
2025-01-31 16:43:35 +01:00
|
|
|
tree := parseCommandTree(expandedCommand)
|
|
|
|
output, err := executeCommandTree(tree, currentDir)
|
2025-01-28 09:02:18 +01:00
|
|
|
if err != nil {
|
2025-01-31 16:43:35 +01:00
|
|
|
errorMsg = err.Error()
|
2025-01-28 09:02:18 +01:00
|
|
|
}
|
2025-01-31 16:43:35 +01:00
|
|
|
return CommandOutput{Command: command, Output: output, Error: errorMsg}
|
2025-01-28 09:02:18 +01:00
|
|
|
}
|
|
|
|
|
2025-01-28 17:01:58 +01:00
|
|
|
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
|
|
|
|
}
|
2025-01-28 09:02:18 +01:00
|
|
|
|
|
|
|
func handler(w http.ResponseWriter, r *http.Request) {
|
|
|
|
currentDir, _ := os.Getwd()
|
2025-02-06 16:20:17 +01:00
|
|
|
displayDir := filepath.Base(currentDir)
|
2025-01-28 09:02:18 +01:00
|
|
|
currentUser, _ := user.Current()
|
|
|
|
currentUsername := currentUser.Username
|
2025-02-06 16:20:17 +01:00
|
|
|
hostname, _ := os.Hostname()
|
2025-01-28 09:02:18 +01:00
|
|
|
|
|
|
|
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, ¤tDir)
|
2025-02-06 16:20:17 +01:00
|
|
|
displayDir = filepath.Base(currentDir)
|
2025-01-28 09:02:18 +01:00
|
|
|
commandLog = append(commandLog, cmdOutput)
|
2025-02-04 16:29:05 +01:00
|
|
|
} else if command == "download" {
|
|
|
|
if len(args) < 1{
|
|
|
|
http.Error(w, "Usage: download <filePath>", http.StatusBadRequest)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
http.Redirect(w, r, "/download?filePath="+url.QueryEscape(args[0]), http.StatusFound)
|
|
|
|
return
|
2025-01-28 09:02:18 +01:00
|
|
|
} else {
|
2025-01-28 17:01:58 +01:00
|
|
|
cmdOutput := processCommand(input, currentDir)
|
2025-01-28 09:02:18 +01:00
|
|
|
commandLog = append(commandLog, cmdOutput)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
data := PageData{
|
2025-02-06 16:20:17 +01:00
|
|
|
CurrentDir: displayDir,
|
|
|
|
Hostname: hostname,
|
2025-01-28 09:02:18 +01:00
|
|
|
CurrentUsername: currentUsername,
|
|
|
|
CommandLog: commandLog,
|
|
|
|
}
|
|
|
|
tmpl.Execute(w, data)
|
|
|
|
}
|
|
|
|
|
|
|
|
func main() {
|
|
|
|
http.HandleFunc("/", handler)
|
|
|
|
http.HandleFunc("/upload", fileUploadHandler)
|
2025-02-04 16:29:05 +01:00
|
|
|
http.HandleFunc("/download", fileDownloadHandler)
|
2025-02-11 15:11:22 +01:00
|
|
|
http.HandleFunc("/terminal", terminalHandler)
|
2025-02-05 12:59:26 +01:00
|
|
|
http.Handle("/static/", http.FileServer(http.FS(staticFiles)))
|
2025-01-28 09:02:18 +01:00
|
|
|
fmt.Println("Starting server on :8080")
|
|
|
|
http.ListenAndServe(":8080", nil)
|
|
|
|
}
|
2025-02-11 15:11:22 +01:00
|
|
|
|
|
|
|
var upgrader = websocket.Upgrader {
|
|
|
|
ReadBufferSize: 1024,
|
|
|
|
WriteBufferSize: 1024,
|
|
|
|
CheckOrigin: func(r *http.Request) bool { return true },
|
|
|
|
}
|
|
|
|
|
|
|
|
func terminalHandler (w http.ResponseWriter, r *http.Request) {
|
|
|
|
ws, err := upgrader.Upgrade(w, r, nil)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Websocket upgrade error: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer ws.Close()
|
|
|
|
|
|
|
|
cmd := exec.Command("bash")
|
|
|
|
ptmx, err := pty.Start(cmd)
|
|
|
|
if err != nil {
|
|
|
|
log.Printf("Error starting PTY: %v", err)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
defer func() { _ = ptmx.Close() }()
|
|
|
|
ptmx.Write([]byte("stty raw -echo\n"))
|
|
|
|
// ptmx.Write([]byte("export SHELL=bash 1>&2 2>/dev/null; export TERM=xterm-256color 1>&2 2>/dev/null"))
|
|
|
|
|
|
|
|
go func() {
|
|
|
|
buf := make([]byte, 1024)
|
|
|
|
for {
|
|
|
|
n, err := ptmx.Read(buf)
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if err := ws.WriteMessage(websocket.BinaryMessage, buf[:n]); err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}()
|
|
|
|
|
|
|
|
for {
|
|
|
|
_, message, err := ws.ReadMessage()
|
|
|
|
if err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
if _, err := ptmx.Write(message); err != nil {
|
|
|
|
break
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|