-
Notifications
You must be signed in to change notification settings - Fork 42
Expand file tree
/
Copy pathterm.go
More file actions
294 lines (260 loc) · 12 KB
/
term.go
File metadata and controls
294 lines (260 loc) · 12 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
package term
import "fmt"
type TerminalCommandPayloadParser func(b []byte) []byte
const ASCII_NULL = 0
const ASCII_BACKSPACE = 8
const ASCII_SPACE = 32
const ASCII_DELETE = 127
const ASCII_TAB = 9
const ASCII_CR = 13
const ASCII_LF = 10
var (
CRLF = []byte{13, 10}
CRLFStr = string(CRLF)
BELL = []byte{13, 7} // may beep, may flash the window, may bounce from the bar on macos
BELLStr = string(BELL)
// alternative mode (No scrollback)
// Hide Cursor
// UTF8 mode
// Request resolution
/*
c.Conn.Write([]byte(util.ANSI_ALTMODE_START + util.ANSI_CUSOR_HIDE + util.ANSI_UTF8 + util.ANSI_REQ_RESOLUTION + util.ANSI_REPORT_MOUSE_CLICK))
*/
///////////////////////////
// Useful sequences
///////////////////////////
// Move cursor back, print a space, move cursor back again.
BACKSPACE_SEQUENCE = []byte{ASCII_BACKSPACE, ASCII_SPACE, ASCII_BACKSPACE}
///////////////////////////
// TELNET COMMANDS
///////////////////////////
//
// SCREEN RESOLUTION
//
// // Request resolution from the client
TelnetScreenSizeRequest = TerminalCommand{[]byte{TELNET_IAC, TELNET_DO, TELNET_OPT_NAWS}, []byte{}}
// // Client response with their resolution
TelnetScreenSizeResponse = TerminalCommand{[]byte{TELNET_IAC, TELNET_SB, TELNET_OPT_NAWS}, []byte{}}
//
// GENERAL SETUP
//
// // Do/Don't Suppress Go Ahead
TelnetSuppressGoAhead = TerminalCommand{[]byte{TELNET_IAC, TELNET_WILL, TELNET_OPT_SUP_GO_AHD}, []byte{}}
TelnetDontSuppressGoAhead = TerminalCommand{[]byte{TELNET_IAC, TELNET_DONT, TELNET_OPT_SUP_GO_AHD}, []byte{}}
// // Echo On
TelnetEchoOn = TerminalCommand{[]byte{TELNET_IAC, TELNET_WILL, TELNET_OPT_ECHO}, []byte{}}
// // Echo Off
TelnetEchoOff = TerminalCommand{[]byte{TELNET_IAC, TELNET_WONT, TELNET_OPT_ECHO}, []byte{}}
// // Line Mode Off
TelnetLineModeOff = TerminalCommand{[]byte{TELNET_IAC, TELNET_WONT, TELNET_OPT_LINE_MODE}, []byte{}}
//
// Handshake example:
// Server (TelnetRequestChangeCharset) -> Client
// Server <- (TelnetAgreeChangeCharset) Client
// Server (TelnetCharset) -> Client
// Server <- (TelnetAcceptedChangeCharset) Client
//
// Indicate wish to change charset
TelnetRequestChangeCharset = TerminalCommand{[]byte{TELNET_IAC, TELNET_WILL, TELNET_OPT_CHARSET}, []byte{}}
// Client agreed to accept a change
TelnetAgreeChangeCharset = TerminalCommand{[]byte{TELNET_IAC, TELNET_DO, TELNET_OPT_CHARSET}, []byte{}}
// Send actual charset change
// Can separate with a space multiple charsets:
// " UTF-8 ISO-8859-1"
TelnetCharset = TerminalCommand{[]byte{TELNET_IAC, TELNET_SB, TELNET_OPT_CHARSET, 1}, []byte{TELNET_IAC, TELNET_SE}}
// Client accepted change
TelnetAcceptedChangeCharset = TerminalCommand{[]byte{TELNET_IAC, TELNET_SB, 2}, []byte{TELNET_IAC, TELNET_SE}}
// Client rejectected change
TelnetRejectedChangeCharset = TerminalCommand{[]byte{TELNET_IAC, TELNET_SB, 3, TELNET_IAC, TELNET_SE}, []byte{}}
// Go Ahead
TelnetGoAhead = TerminalCommand{[]byte{TELNET_IAC, TELNET_GA}, []byte{}}
///////////////////////////
// ANSI COMMANDS
///////////////////////////
// Did they hit the escape key?
AnsiEscapeKey = TerminalCommand{[]byte{ANSI_ESC}, []byte{}}
// 4-bit color - Can contain fg, bg, bold, etc.
AnsiColor4Bit = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'m'}}
// 8 Bit color
AnsiColor8BitFG = TerminalCommand{[]byte{ANSI_ESC, '[', '3', '8', ';', '5', ';'}, []byte{'m'}}
AnsiColor8BitBG = TerminalCommand{[]byte{ANSI_ESC, '[', '4', '8', ';', '5', ';'}, []byte{'m'}}
// 24 Bit color - RGB
AnsiColor24BitFG = TerminalCommand{[]byte{ANSI_ESC, '[', '3', '8', ';', '2', ';'}, []byte{'m'}}
AnsiColor24BitBG = TerminalCommand{[]byte{ANSI_ESC, '[', '4', '8', ';', '2', ';'}, []byte{'m'}}
// Reset colors back to client default
AnsiColorReset = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'0', 'm'}}
// Enable alternative screen buffer
AnsiAltModeStart = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'?', '1', '0', '4', '9', 'h'}}
// Disable alternative screen buffer
AnsiAltModeEnd = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'?', '1', '0', '4', '9', 'l'}}
// Hide Cursor
AnsiCursorHide = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'?', '2', '5', 'l'}} // DECTCEM
// Show Cursor
AnsiCursorShow = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'?', '2', '5', 'h'}} // DECTCEM
// Clear from cursor to end of screen
AnsiClearForward = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'0', 'J'}}
// Clear from cursor to beginning of screen
AnsiClearBack = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'1', 'J'}}
// Clear Scrollback
AnsiClearScreen = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'2', 'J'}}
// Clear Screen and scrollback Buffer
AnsiScreenAndScrollbackClear = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'3', 'J'}}
// Clear from the cursor to the end of the line
AnsiEraseLineForward = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'0', 'K'}}
// Clear from the cursor to the start of the line
AnsiEraseLineBackward = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'1', 'K'}}
// Clear enter line
AnsiEraseLine = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'2', 'K'}}
// Hide Text
AnsiTextHide = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'8', 'm'}}
// Show Text
AnsiTextShow = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'2', '8', 'm'}}
// Request client report mouse clicks
AnsiReportMouseClick = TerminalCommand{[]byte{ANSI_ESC, '[', '?', '1', '0', '0', '2', 'h'}, []byte{ANSI_ESC, '[', '?', '1', '0', '0', '6', 'h'}}
AnsiSaveCursorPosition = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'s'}}
AnsiRestoreCursorPosition = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'u'}}
AnsiRequestCursorPosition = TerminalCommand{[]byte{ANSI_ESC, '[', '6'}, []byte{'n'}}
AnsiCharSetUTF8 = TerminalCommand{[]byte{ANSI_ESC, '%'}, []byte{'G'}}
// Client is reporting a mouse click
// ESC [ < 0 ; 1 ; 1 m
// [27 91 60 48 59 49 59 49 109]
// ESC [ < 0 ; 1 4 0 ; 4 8 m
// [27 91 60 48 59 49 52 48 59 52 56 109]
AnsiClientMouseDown = TerminalCommand{[]byte{ANSI_ESC, '[', '<', '0', ';'}, []byte{'M'}}
AnsiClientMouseUp = TerminalCommand{[]byte{ANSI_ESC, '[', '<', '0', ';'}, []byte{'m'}}
// To request the client terminal dimensions (hacky way):
// 1. Save the cursor position
// 2. Move the cursor to the bottom right corner
// 3. Request the cursor position
// 4. Restore the cursor position
AnsiRequestResolution = TerminalCommand{[]byte(AnsiSaveCursorPosition.String() + AnsiMoveCursorBottomRight.String() + AnsiRequestCursorPosition.String() + AnsiRestoreCursorPosition.String()), []byte{}}
// Client is reporting screen size
// Looks like: ESC [ 40 ; 80 R
AnsiClientScreenSize = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'R'}}
// Move the cursor - any number after '[' is how many spaces to move
AnsiMoveCursor = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'H'}}
AnsiMoveCursorUp = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'A'}}
AnsiMoveCursorDown = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'B'}}
AnsiMoveCursorForward = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'C'}}
AnsiMoveCursorBackward = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'D'}}
// Move the cursor to a specific column - any number after '[' is column. Default 1.
AnsiMoveCursorColumn = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'G'}}
AnsiMoveCursorBottomRight = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'9', '9', '9', ';', '9', '9', '9', 'H'}}
AnsiMoveCursorTopLeft = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'1', ';', '1', 'H'}}
// \033[<65;9(xPos);19(yPos)M
AnsiMouseWheelUp = TerminalCommand{[]byte{ANSI_ESC, '[', '<', '6', '5', ';'}, []byte{'M'}}
AnsiMouseWheelDown = TerminalCommand{[]byte{ANSI_ESC, '[', '<', '6', '4', ';'}, []byte{'M'}}
// F keys
AnsiF1 = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'1', '1', '~'}} // Putty sends this
AnsiF2 = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'1', '2', '~'}} // Putty sends this
AnsiF3 = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'1', '3', '~'}} // Putty sends this
AnsiF4 = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'1', '4', '~'}} // Putty sends this
AnsiF5 = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'1', '5', '~'}} // Putty sends this
AnsiF6 = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'1', '7', '~'}} // Putty sends this
AnsiF7 = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'1', '8', '~'}} // Putty sends this
AnsiF8 = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'1', '9', '~'}} // Putty sends this
AnsiF9 = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'2', '0', '~'}} // Putty sends this
AnsiF10 = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'2', '1', '~'}} // Putty sends this
AnsiF11 = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'2', '3', '~'}} // Putty sends this
AnsiF12 = TerminalCommand{[]byte{ANSI_ESC, '['}, []byte{'2', '4', '~'}} // Putty sends this
AnsiF1b = TerminalCommand{[]byte{ANSI_ESC, 'O'}, []byte{'P'}} // macos terminal telnet
AnsiF2b = TerminalCommand{[]byte{ANSI_ESC, 'O'}, []byte{'Q'}} // macos terminal telnet
AnsiF3b = TerminalCommand{[]byte{ANSI_ESC, 'O'}, []byte{'R'}} // macos terminal telnet
AnsiF4b = TerminalCommand{[]byte{ANSI_ESC, 'O'}, []byte{'S'}} // macos terminal telnet
// Payload is the window title to set it to
AnsiSetWindowTitle = TerminalCommand{[]byte{ANSI_ESC, ']', '2', ';'}, []byte{'S', 'T'}}
// Payload is frequency in Hz
AnsiSetBellFrequency = TerminalCommand{[]byte{ANSI_ESC, '[', '1', '0', ';'}, []byte{']'}}
// Payload is bell duration in msec
AnsiSetBellDuration = TerminalCommand{[]byte{ANSI_ESC, '[', '1', '1', ';'}, []byte{']'}}
)
func IsTelnetCommand(b []byte) bool {
return len(b) > 0 && b[0] == TELNET_IAC
}
func IsAnsiCommand(b []byte) bool {
return len(b) > 0 && b[0] == ANSI_ESC
}
type TerminalCommand struct {
chars []byte
endChars []byte
}
func (cmd *TerminalCommand) BytesWithPayload(payload []byte) []byte {
result := []byte{}
result = append(result, cmd.chars...)
if len(payload) > 0 {
result = append(result, payload...)
}
result = append(result, cmd.endChars...)
return result
}
func (cmd *TerminalCommand) ExtractBody(input []byte) []byte {
if len(cmd.chars) == 0 {
return input
}
return input[len(cmd.chars) : len(input)-len(cmd.endChars)]
}
func Matches(input []byte, cmd TerminalCommand) (ok bool, payload []byte) {
// The length of the testBytes should at least be the same as the chars plus endchars
if len(input) < len(cmd.chars)+len(cmd.endChars) {
return false, nil
}
// Check the start chars
for i, b := range cmd.chars {
if b != input[i] {
return false, nil
}
}
if len(cmd.endChars) == 0 {
if len(input) == len(cmd.chars) {
return true, nil
}
// Return any remaining ("payload") bytes
return true, input[len(cmd.chars):]
}
// Check the end chars
for i, b := range cmd.endChars {
if b != input[len(input)-len(cmd.endChars)+i] {
return false, nil
}
}
if len(input) == len(cmd.chars)+len(cmd.endChars) {
return true, nil
}
// Return any "payload" bytes
return true, input[len(cmd.chars) : len(input)-len(cmd.endChars)]
}
func (c *TerminalCommand) String() string {
return string(c.chars) + string(c.endChars)
}
func (c *TerminalCommand) StringWithPayload(payload string) string {
return string(c.chars) + payload + string(c.endChars)
}
func (c *TerminalCommand) DebugString() string {
if c.chars[0] == TELNET_IAC {
return TelnetCommandToString(c.chars) + TelnetCommandToString(c.endChars)
} else if c.chars[0] == ANSI_ESC {
return AnsiCommandToString(c.chars) + AnsiCommandToString(c.endChars)
}
return "???"
}
func TelnetCommandToString(cmdBytes []byte) string {
var retStr string = ""
for _, b := range cmdBytes {
retStr += fmt.Sprintf("[%v %s]", b, iacByteString(b))
}
return retStr
}
func AnsiCommandToString(cmdBytes []byte) string {
var retStr string = ""
for _, b := range cmdBytes {
retStr += fmt.Sprintf("[%v %s]", b, string(b))
}
return retStr
}
func BytesString(cmdBytes []byte) string {
var retStr string = "[]byte{ "
for _, b := range cmdBytes {
retStr += fmt.Sprintf("%v, ", b)
}
return retStr + " }"
}