Author | C. Torres <torr.c@mailgw.com> 2025-01-11 01:09:57 |
Committer | C. Torres <torr.c@mailgw.com> 2025-01-11 01:23:11 |
Commit | 8aaa565 (patch) |
Tree | 86daf6d |
Parent(s) |
-rw-r--r-- | go.mod | 5 | ||
-rw-r--r-- | go.sum | 2 | ||
-rw-r--r-- | ws/common.go | 45 | ||
-rw-r--r-- | ws/raw_stdin.go | 80 |
index cd905c1..9686469 | |||
old size: 69B - new size: 100B | |||
@@ -2,4 +2,7 @@ module wssrv | |||
2 | 2 | ||
3 | 3 | go 1.22.6 | |
4 | 4 | ||
5 | - | require github.com/gorilla/websocket v1.5.3 | |
5 | + | require ( | |
6 | + | github.com/gorilla/websocket v1.5.3 | |
7 | + | golang.org/x/sys v0.29.0 | |
8 | + | ) |
index 25a9fc4..478d5e3 | |||
old size: 175B - new size: 328B | |||
@@ -1,2 +1,4 @@ | |||
1 | 1 | github.com/gorilla/websocket v1.5.3 h1:saDtZ6Pbx/0u+bgYQ3q96pZgCzfhKXGPqt7kZ72aNNg= | |
2 | 2 | github.com/gorilla/websocket v1.5.3/go.mod h1:YR8l580nyteQvAITg2hZ9XVh4b55+EU/adAjf1fMHhE= | |
3 | + | golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU= | |
4 | + | golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= |
index fe8a586..57770d9 | |||
old size: 422B - new size: 1K | |||
@@ -3,16 +3,55 @@ package ws | |||
3 | 3 | import ( | |
4 | 4 | "fmt" | |
5 | 5 | "os" | |
6 | + | ||
7 | + | "golang.org/x/sys/unix" | |
6 | 8 | ) | |
7 | 9 | ||
8 | - | func checkFile(fileNa string) error { | |
10 | + | func restore(fd int, t *unix.Termios) error { | |
11 | + | return unix.IoctlSetTermios(fd, unix.TCSETS, t) | |
12 | + | } | |
13 | + | ||
14 | + | // setupTerm sets the terminal with raw mode attributes for input, whereby | |
15 | + | // 'OPOST' is not set. | |
16 | + | func setupTerm() (int, *unix.Termios, error) { | |
17 | + | var ( | |
18 | + | err error | |
19 | + | fd int | |
20 | + | term *unix.Termios | |
21 | + | ||
22 | + | oldTerm = &unix.Termios{} | |
23 | + | ) | |
24 | + | ||
25 | + | if fd, err = unix.Open("/dev/tty", unix.O_RDONLY, 0); err != nil { | |
26 | + | return 0, nil, err | |
27 | + | } | |
28 | + | if term, err = unix.IoctlGetTermios(fd, unix.TCGETS); err != nil { | |
29 | + | return 0, nil, err | |
30 | + | } | |
31 | + | *oldTerm = *term | |
32 | + | ||
33 | + | term.Iflag &^= unix.IGNBRK | unix.BRKINT | unix.PARMRK | unix.ISTRIP | | |
34 | + | unix.INLCR | unix.IGNCR | unix.ICRNL | unix.IXON | |
35 | + | term.Lflag &^= unix.ECHO | unix.ECHONL | unix.ICANON | unix.ISIG | unix.IEXTEN | |
36 | + | term.Cflag &^= unix.CSIZE | unix.PARENB | |
37 | + | term.Cflag |= unix.CS8 | |
38 | + | term.Cc[unix.VMIN] = 1 | |
39 | + | term.Cc[unix.VTIME] = 0 | |
40 | + | ||
41 | + | if err = unix.IoctlSetTermios(fd, unix.TCSETS, term); err != nil { | |
42 | + | return 0, nil, err | |
43 | + | } | |
44 | + | return fd, oldTerm, nil | |
45 | + | } | |
46 | + | ||
47 | + | func checkFile(name string) error { | |
9 | 48 | var ( | |
10 | 49 | err error | |
11 | 50 | stat os.FileInfo | |
12 | 51 | ) | |
13 | 52 | ||
14 | - | if stat, err = os.Stat(fileNa); err != nil { | |
15 | - | return fmt.Errorf("unable to check metadata for file '%s': %v", fileNa, err) | |
53 | + | if stat, err = os.Stat(name); err != nil { | |
54 | + | return fmt.Errorf("unable to check metadata for file '%s': %v", name, err) | |
16 | 55 | } | |
17 | 56 | if !stat.Mode().IsRegular() { | |
18 | 57 | return fmt.Errorf("file '%s' is not regular") |
index 0000000..430fa1e | |||
old size: 0B - new size: 2K | |||
new file mode: -rw-r--r-- |
@@ -0,0 +1,80 @@ | |||
1 | + | package ws | |
2 | + | ||
3 | + | import ( | |
4 | + | "context" | |
5 | + | "fmt" | |
6 | + | "wssrv/log" | |
7 | + | ||
8 | + | "github.com/gorilla/websocket" | |
9 | + | "golang.org/x/sys/unix" | |
10 | + | ) | |
11 | + | ||
12 | + | const ( | |
13 | + | bufSize = 8 | |
14 | + | quitKey = 17 // Key '<c-q>'. | |
15 | + | ) | |
16 | + | ||
17 | + | func sendRawStdin(ctx context.Context, cancel context.CancelCauseFunc, con *websocket.Conn, fd int) { | |
18 | + | var ( | |
19 | + | err error | |
20 | + | n int | |
21 | + | ||
22 | + | buf, clean = make([]byte, bufSize), make([]byte, bufSize) | |
23 | + | ) | |
24 | + | ||
25 | + | for { | |
26 | + | select { | |
27 | + | case <-ctx.Done(): | |
28 | + | return | |
29 | + | default: | |
30 | + | buf = clean | |
31 | + | if n, err = unix.Read(fd, buf); err != nil { | |
32 | + | cancel(fmt.Errorf("unable to read from descriptor '%d': %v", fd, err)) | |
33 | + | return | |
34 | + | } | |
35 | + | if n == 1 && buf[0] == quitKey { | |
36 | + | cancel(nil) | |
37 | + | return | |
38 | + | } | |
39 | + | fmt.Printf("> byte '%d'\n", buf[:n]) | |
40 | + | if err = con.WriteMessage(websocket.BinaryMessage, buf[:n]); err != nil { | |
41 | + | cancel(fmt.Errorf("unable write on websocket on '%s': %v", con.RemoteAddr(), err)) | |
42 | + | return | |
43 | + | } | |
44 | + | } | |
45 | + | } | |
46 | + | } | |
47 | + | ||
48 | + | func sendRawStdinStream(con *websocket.Conn) error { | |
49 | + | var ( | |
50 | + | err error | |
51 | + | fd int | |
52 | + | term *unix.Termios | |
53 | + | ||
54 | + | ctx, cancel = context.WithCancelCause(context.Background()) | |
55 | + | ) | |
56 | + | ||
57 | + | if fd, term, err = setupTerm(); err != nil { | |
58 | + | return fmt.Errorf("unable to setup terminal for raw input: %v", err) | |
59 | + | } | |
60 | + | defer func(fd int, term *unix.Termios) { | |
61 | + | var err error | |
62 | + | if err = unix.IoctlSetTermios(fd, unix.TCSETS, term); err != nil { | |
63 | + | log.Err.Printf("unable to restore terminal with descriptor '%d': %v", fd, err) | |
64 | + | } | |
65 | + | }(fd, term) | |
66 | + | ||
67 | + | go readMessage(ctx, cancel, con) | |
68 | + | go sendRawStdin(ctx, cancel, con, fd) | |
69 | + | ||
70 | + | <-ctx.Done() | |
71 | + | return context.Cause(ctx) | |
72 | + | } | |
73 | + | ||
74 | + | func SendRawStdinStream(con *websocket.Conn) error { | |
75 | + | switch { | |
76 | + | case con == nil: | |
77 | + | return fmt.Errorf("nil connection parameter") | |
78 | + | } | |
79 | + | return sendRawStdinStream(con) | |
80 | + | } |