witty/main.go

176 lines
3.2 KiB
Go
Raw Normal View History

2022-01-04 21:41:41 +01:00
package main
import (
"fmt"
"net/http"
"net/url"
2022-01-05 18:38:52 +01:00
"os"
"os/exec"
2022-01-05 21:08:27 +01:00
"strings"
2022-01-05 18:38:52 +01:00
"github.com/creack/pty"
2022-01-05 21:08:27 +01:00
"github.com/gin-gonic/gin"
"github.com/gorilla/websocket"
2022-01-05 18:38:52 +01:00
"golang.org/x/term"
2022-01-04 21:41:41 +01:00
)
2022-01-05 18:38:52 +01:00
func createPty(cmdline string) (*os.File, *term.State, error) {
// Create a shell command.
cmd := exec.Command(cmdline)
// Start the command with a pty.
ptmx, err := pty.Start(cmd)
if err != nil {
return nil, nil, err
}
// Use fixed size, the xterm is initalized as 122x37,
// But we set pty to 120x36. Using fullsize will lead
// some program to misbehaive.
pty.Setsize(ptmx, &pty.Winsize{
Cols: 120,
Rows: 36,
})
// Set stdin in raw mode. This might cause problems in ssh.
// ignore the error if it so happens
termState, err := term.MakeRaw(int(os.Stdin.Fd()))
if err != nil {
fmt.Println(err)
return ptmx, nil, err
}
return ptmx, termState, nil
}
2022-01-04 21:41:41 +01:00
var host *string = nil
var upgrader = websocket.Upgrader{
2022-01-05 03:21:21 +01:00
ReadBufferSize: 4096,
2022-01-04 21:41:41 +01:00
WriteBufferSize: 4096,
CheckOrigin: func(r *http.Request) bool {
org := r.Header.Get("Origin")
2022-01-05 03:21:21 +01:00
h, err := url.Parse(org)
2022-01-04 21:41:41 +01:00
if err != nil {
return false
}
if (host == nil) || (*host != h.Host) {
fmt.Println("failed origin check of ", org, "against", *host)
}
return (host != nil) && (*host == h.Host)
},
}
// handle websockets
func wsHandler(w http.ResponseWriter, r *http.Request) {
2022-01-05 03:21:21 +01:00
conn, err := upgrader.Upgrade(w, r, nil)
2022-01-04 21:41:41 +01:00
2022-01-05 03:21:21 +01:00
if err != nil {
2022-01-04 21:41:41 +01:00
fmt.Println(err)
return
}
fmt.Println("Created the websocket")
2022-01-05 18:38:52 +01:00
ptmx, termState, err := createPty("bash")
defer func() {
//close the terminal and restore the terminal state
if termState != nil {
term.Restore(int(os.Stdin.Fd()), termState)
}
}()
if err != nil {
fmt.Println("failed to create PTY", err)
return
}
// pipe the msgs from WS to pty, we need to use goroutine here
go func() {
for {
_, buf, err := conn.ReadMessage()
if err != nil {
fmt.Println(err)
// We need to close pty so the goroutine and this one can end
// using defer will cause problems
ptmx.Close()
return
}
_, err = ptmx.Write(buf)
if err != nil {
fmt.Println(err)
ptmx.Close()
return
}
}
}()
readBuf := make([]byte, 4096)
2022-01-04 21:41:41 +01:00
for {
2022-01-05 18:38:52 +01:00
n, err := ptmx.Read(readBuf)
2022-01-04 21:41:41 +01:00
2022-01-05 03:21:21 +01:00
if err != nil {
2022-01-04 21:41:41 +01:00
fmt.Println(err)
2022-01-05 18:38:52 +01:00
ptmx.Close()
2022-01-04 21:41:41 +01:00
return
}
2022-01-05 18:38:52 +01:00
if err = conn.WriteMessage(websocket.BinaryMessage, readBuf[:n]); err != nil {
ptmx.Close()
2022-01-04 21:41:41 +01:00
fmt.Println(err)
return
}
}
}
// return files
2022-01-05 03:21:21 +01:00
func fileHandler(c *gin.Context, fname string) {
2022-01-04 21:41:41 +01:00
// if the URL has no fname, c.Param returns "/"
if fname == "/" {
fname = "/index.html"
host = &c.Request.Host
}
fname = fname[1:] //fname always starts with /
fmt.Println(fname)
if strings.HasSuffix(fname, "html") {
c.HTML(200, fname, nil)
} else {
//c.HTML interprets the file as HTML file
//we do not need that for regular files
c.File(fmt.Sprint("assets/", fname))
}
}
2022-01-05 03:21:21 +01:00
func main() {
2022-01-04 21:41:41 +01:00
rt := gin.Default()
rt.SetTrustedProxies(nil)
rt.LoadHTMLGlob("assets/*.html")
2022-01-05 03:21:21 +01:00
rt.GET("/*fname", func(c *gin.Context) {
2022-01-04 21:41:41 +01:00
fname := c.Param("fname")
// ws is a special case to create a new websocket
switch fname {
2022-01-05 03:21:21 +01:00
case "/ws":
2022-01-04 21:41:41 +01:00
wsHandler(c.Writer, c.Request)
default:
fileHandler(c, fname)
}
})
rt.Run(":8080")
2022-01-05 03:21:21 +01:00
}