|  | // Copyright 2011 The Go Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style | 
|  | // license that can be found in the LICENSE file. | 
|  |  | 
|  | package ssh | 
|  |  | 
|  | import "io" | 
|  |  | 
|  | // ServerShell contains the state for running a VT100 terminal that is capable | 
|  | // of reading lines of input. | 
|  | type ServerShell struct { | 
|  | c      Channel | 
|  | prompt string | 
|  |  | 
|  | // line is the current line being entered. | 
|  | line []byte | 
|  | // pos is the logical position of the cursor in line | 
|  | pos int | 
|  |  | 
|  | // cursorX contains the current X value of the cursor where the left | 
|  | // edge is 0. cursorY contains the row number where the first row of | 
|  | // the current line is 0. | 
|  | cursorX, cursorY int | 
|  | // maxLine is the greatest value of cursorY so far. | 
|  | maxLine int | 
|  |  | 
|  | termWidth, termHeight int | 
|  |  | 
|  | // outBuf contains the terminal data to be sent. | 
|  | outBuf []byte | 
|  | // remainder contains the remainder of any partial key sequences after | 
|  | // a read. It aliases into inBuf. | 
|  | remainder []byte | 
|  | inBuf     [256]byte | 
|  | } | 
|  |  | 
|  | // NewServerShell runs a VT100 terminal on the given channel. prompt is a | 
|  | // string that is written at the start of each input line. For example: "> ". | 
|  | func NewServerShell(c Channel, prompt string) *ServerShell { | 
|  | return &ServerShell{ | 
|  | c:          c, | 
|  | prompt:     prompt, | 
|  | termWidth:  80, | 
|  | termHeight: 24, | 
|  | } | 
|  | } | 
|  |  | 
|  | const ( | 
|  | keyCtrlD     = 4 | 
|  | keyEnter     = '\r' | 
|  | keyEscape    = 27 | 
|  | keyBackspace = 127 | 
|  | keyUnknown   = 256 + iota | 
|  | keyUp | 
|  | keyDown | 
|  | keyLeft | 
|  | keyRight | 
|  | keyAltLeft | 
|  | keyAltRight | 
|  | ) | 
|  |  | 
|  | // bytesToKey tries to parse a key sequence from b. If successful, it returns | 
|  | // the key and the remainder of the input. Otherwise it returns -1. | 
|  | func bytesToKey(b []byte) (int, []byte) { | 
|  | if len(b) == 0 { | 
|  | return -1, nil | 
|  | } | 
|  |  | 
|  | if b[0] != keyEscape { | 
|  | return int(b[0]), b[1:] | 
|  | } | 
|  |  | 
|  | if len(b) >= 3 && b[0] == keyEscape && b[1] == '[' { | 
|  | switch b[2] { | 
|  | case 'A': | 
|  | return keyUp, b[3:] | 
|  | case 'B': | 
|  | return keyDown, b[3:] | 
|  | case 'C': | 
|  | return keyRight, b[3:] | 
|  | case 'D': | 
|  | return keyLeft, b[3:] | 
|  | } | 
|  | } | 
|  |  | 
|  | if len(b) >= 6 && b[0] == keyEscape && b[1] == '[' && b[2] == '1' && b[3] == ';' && b[4] == '3' { | 
|  | switch b[5] { | 
|  | case 'C': | 
|  | return keyAltRight, b[6:] | 
|  | case 'D': | 
|  | return keyAltLeft, b[6:] | 
|  | } | 
|  | } | 
|  |  | 
|  | // If we get here then we have a key that we don't recognise, or a | 
|  | // partial sequence. It's not clear how one should find the end of a | 
|  | // sequence without knowing them all, but it seems that [a-zA-Z] only | 
|  | // appears at the end of a sequence. | 
|  | for i, c := range b[0:] { | 
|  | if c >= 'a' && c <= 'z' || c >= 'A' && c <= 'Z' { | 
|  | return keyUnknown, b[i+1:] | 
|  | } | 
|  | } | 
|  |  | 
|  | return -1, b | 
|  | } | 
|  |  | 
|  | // queue appends data to the end of ss.outBuf | 
|  | func (ss *ServerShell) queue(data []byte) { | 
|  | if len(ss.outBuf)+len(data) > cap(ss.outBuf) { | 
|  | newOutBuf := make([]byte, len(ss.outBuf), 2*(len(ss.outBuf)+len(data))) | 
|  | copy(newOutBuf, ss.outBuf) | 
|  | ss.outBuf = newOutBuf | 
|  | } | 
|  |  | 
|  | oldLen := len(ss.outBuf) | 
|  | ss.outBuf = ss.outBuf[:len(ss.outBuf)+len(data)] | 
|  | copy(ss.outBuf[oldLen:], data) | 
|  | } | 
|  |  | 
|  | var eraseUnderCursor = []byte{' ', keyEscape, '[', 'D'} | 
|  |  | 
|  | func isPrintable(key int) bool { | 
|  | return key >= 32 && key < 127 | 
|  | } | 
|  |  | 
|  | // moveCursorToPos appends data to ss.outBuf which will move the cursor to the | 
|  | // given, logical position in the text. | 
|  | func (ss *ServerShell) moveCursorToPos(pos int) { | 
|  | x := len(ss.prompt) + pos | 
|  | y := x / ss.termWidth | 
|  | x = x % ss.termWidth | 
|  |  | 
|  | up := 0 | 
|  | if y < ss.cursorY { | 
|  | up = ss.cursorY - y | 
|  | } | 
|  |  | 
|  | down := 0 | 
|  | if y > ss.cursorY { | 
|  | down = y - ss.cursorY | 
|  | } | 
|  |  | 
|  | left := 0 | 
|  | if x < ss.cursorX { | 
|  | left = ss.cursorX - x | 
|  | } | 
|  |  | 
|  | right := 0 | 
|  | if x > ss.cursorX { | 
|  | right = x - ss.cursorX | 
|  | } | 
|  |  | 
|  | movement := make([]byte, 3*(up+down+left+right)) | 
|  | m := movement | 
|  | for i := 0; i < up; i++ { | 
|  | m[0] = keyEscape | 
|  | m[1] = '[' | 
|  | m[2] = 'A' | 
|  | m = m[3:] | 
|  | } | 
|  | for i := 0; i < down; i++ { | 
|  | m[0] = keyEscape | 
|  | m[1] = '[' | 
|  | m[2] = 'B' | 
|  | m = m[3:] | 
|  | } | 
|  | for i := 0; i < left; i++ { | 
|  | m[0] = keyEscape | 
|  | m[1] = '[' | 
|  | m[2] = 'D' | 
|  | m = m[3:] | 
|  | } | 
|  | for i := 0; i < right; i++ { | 
|  | m[0] = keyEscape | 
|  | m[1] = '[' | 
|  | m[2] = 'C' | 
|  | m = m[3:] | 
|  | } | 
|  |  | 
|  | ss.cursorX = x | 
|  | ss.cursorY = y | 
|  | ss.queue(movement) | 
|  | } | 
|  |  | 
|  | const maxLineLength = 4096 | 
|  |  | 
|  | // handleKey processes the given key and, optionally, returns a line of text | 
|  | // that the user has entered. | 
|  | func (ss *ServerShell) handleKey(key int) (line string, ok bool) { | 
|  | switch key { | 
|  | case keyBackspace: | 
|  | if ss.pos == 0 { | 
|  | return | 
|  | } | 
|  | ss.pos-- | 
|  |  | 
|  | copy(ss.line[ss.pos:], ss.line[1+ss.pos:]) | 
|  | ss.line = ss.line[:len(ss.line)-1] | 
|  | ss.writeLine(ss.line[ss.pos:]) | 
|  | ss.moveCursorToPos(ss.pos) | 
|  | ss.queue(eraseUnderCursor) | 
|  | case keyAltLeft: | 
|  | // move left by a word. | 
|  | if ss.pos == 0 { | 
|  | return | 
|  | } | 
|  | ss.pos-- | 
|  | for ss.pos > 0 { | 
|  | if ss.line[ss.pos] != ' ' { | 
|  | break | 
|  | } | 
|  | ss.pos-- | 
|  | } | 
|  | for ss.pos > 0 { | 
|  | if ss.line[ss.pos] == ' ' { | 
|  | ss.pos++ | 
|  | break | 
|  | } | 
|  | ss.pos-- | 
|  | } | 
|  | ss.moveCursorToPos(ss.pos) | 
|  | case keyAltRight: | 
|  | // move right by a word. | 
|  | for ss.pos < len(ss.line) { | 
|  | if ss.line[ss.pos] == ' ' { | 
|  | break | 
|  | } | 
|  | ss.pos++ | 
|  | } | 
|  | for ss.pos < len(ss.line) { | 
|  | if ss.line[ss.pos] != ' ' { | 
|  | break | 
|  | } | 
|  | ss.pos++ | 
|  | } | 
|  | ss.moveCursorToPos(ss.pos) | 
|  | case keyLeft: | 
|  | if ss.pos == 0 { | 
|  | return | 
|  | } | 
|  | ss.pos-- | 
|  | ss.moveCursorToPos(ss.pos) | 
|  | case keyRight: | 
|  | if ss.pos == len(ss.line) { | 
|  | return | 
|  | } | 
|  | ss.pos++ | 
|  | ss.moveCursorToPos(ss.pos) | 
|  | case keyEnter: | 
|  | ss.moveCursorToPos(len(ss.line)) | 
|  | ss.queue([]byte("\r\n")) | 
|  | line = string(ss.line) | 
|  | ok = true | 
|  | ss.line = ss.line[:0] | 
|  | ss.pos = 0 | 
|  | ss.cursorX = 0 | 
|  | ss.cursorY = 0 | 
|  | ss.maxLine = 0 | 
|  | default: | 
|  | if !isPrintable(key) { | 
|  | return | 
|  | } | 
|  | if len(ss.line) == maxLineLength { | 
|  | return | 
|  | } | 
|  | if len(ss.line) == cap(ss.line) { | 
|  | newLine := make([]byte, len(ss.line), 2*(1+len(ss.line))) | 
|  | copy(newLine, ss.line) | 
|  | ss.line = newLine | 
|  | } | 
|  | ss.line = ss.line[:len(ss.line)+1] | 
|  | copy(ss.line[ss.pos+1:], ss.line[ss.pos:]) | 
|  | ss.line[ss.pos] = byte(key) | 
|  | ss.writeLine(ss.line[ss.pos:]) | 
|  | ss.pos++ | 
|  | ss.moveCursorToPos(ss.pos) | 
|  | } | 
|  | return | 
|  | } | 
|  |  | 
|  | func (ss *ServerShell) writeLine(line []byte) { | 
|  | for len(line) != 0 { | 
|  | if ss.cursorX == ss.termWidth { | 
|  | ss.queue([]byte("\r\n")) | 
|  | ss.cursorX = 0 | 
|  | ss.cursorY++ | 
|  | if ss.cursorY > ss.maxLine { | 
|  | ss.maxLine = ss.cursorY | 
|  | } | 
|  | } | 
|  |  | 
|  | remainingOnLine := ss.termWidth - ss.cursorX | 
|  | todo := len(line) | 
|  | if todo > remainingOnLine { | 
|  | todo = remainingOnLine | 
|  | } | 
|  | ss.queue(line[:todo]) | 
|  | ss.cursorX += todo | 
|  | line = line[todo:] | 
|  | } | 
|  | } | 
|  |  | 
|  | // parsePtyRequest parses the payload of the pty-req message and extracts the | 
|  | // dimensions of the terminal. See RFC 4254, section 6.2. | 
|  | func parsePtyRequest(s []byte) (width, height int, ok bool) { | 
|  | _, s, ok = parseString(s) | 
|  | if !ok { | 
|  | return | 
|  | } | 
|  | width32, s, ok := parseUint32(s) | 
|  | if !ok { | 
|  | return | 
|  | } | 
|  | height32, _, ok := parseUint32(s) | 
|  | width = int(width32) | 
|  | height = int(height32) | 
|  | if width < 1 { | 
|  | ok = false | 
|  | } | 
|  | if height < 1 { | 
|  | ok = false | 
|  | } | 
|  | return | 
|  | } | 
|  |  | 
|  | func (ss *ServerShell) Write(buf []byte) (n int, err error) { | 
|  | return ss.c.Write(buf) | 
|  | } | 
|  |  | 
|  | // ReadLine returns a line of input from the terminal. | 
|  | func (ss *ServerShell) ReadLine() (line string, err error) { | 
|  | ss.writeLine([]byte(ss.prompt)) | 
|  | ss.c.Write(ss.outBuf) | 
|  | ss.outBuf = ss.outBuf[:0] | 
|  |  | 
|  | for { | 
|  | // ss.remainder is a slice at the beginning of ss.inBuf | 
|  | // containing a partial key sequence | 
|  | readBuf := ss.inBuf[len(ss.remainder):] | 
|  | var n int | 
|  | n, err = ss.c.Read(readBuf) | 
|  | if err == nil { | 
|  | ss.remainder = ss.inBuf[:n+len(ss.remainder)] | 
|  | rest := ss.remainder | 
|  | lineOk := false | 
|  | for !lineOk { | 
|  | var key int | 
|  | key, rest = bytesToKey(rest) | 
|  | if key < 0 { | 
|  | break | 
|  | } | 
|  | if key == keyCtrlD { | 
|  | return "", io.EOF | 
|  | } | 
|  | line, lineOk = ss.handleKey(key) | 
|  | } | 
|  | if len(rest) > 0 { | 
|  | n := copy(ss.inBuf[:], rest) | 
|  | ss.remainder = ss.inBuf[:n] | 
|  | } else { | 
|  | ss.remainder = nil | 
|  | } | 
|  | ss.c.Write(ss.outBuf) | 
|  | ss.outBuf = ss.outBuf[:0] | 
|  | if lineOk { | 
|  | return | 
|  | } | 
|  | continue | 
|  | } | 
|  |  | 
|  | if req, ok := err.(ChannelRequest); ok { | 
|  | ok := false | 
|  | switch req.Request { | 
|  | case "pty-req": | 
|  | ss.termWidth, ss.termHeight, ok = parsePtyRequest(req.Payload) | 
|  | if !ok { | 
|  | ss.termWidth = 80 | 
|  | ss.termHeight = 24 | 
|  | } | 
|  | case "shell": | 
|  | ok = true | 
|  | if len(req.Payload) > 0 { | 
|  | // We don't accept any commands, only the default shell. | 
|  | ok = false | 
|  | } | 
|  | case "env": | 
|  | ok = true | 
|  | } | 
|  | if req.WantReply { | 
|  | ss.c.AckRequest(ok) | 
|  | } | 
|  | } else { | 
|  | return "", err | 
|  | } | 
|  | } | 
|  | panic("unreachable") | 
|  | } |