Commit 9295a3eb authored by Eric's avatar Eric

fix commmand parse

parents 2e4496e2 50388696
package proxy package proxy
import ( import (
"io" "bytes"
"github.com/jumpserver/koko/pkg/logger"
"github.com/jumpserver/koko/pkg/utils"
"regexp" "regexp"
"strings" "strings"
"sync" "sync"
"github.com/jumpserver/koko/pkg/logger"
"github.com/jumpserver/koko/pkg/utils"
) )
var ps1Pattern = regexp.MustCompile(`^\[?.*@.*\]?[\\$#]\s|mysql>\s`) var ps1Pattern = regexp.MustCompile(`^\[?.*@.*\]?[\\$#]\s|mysql>\s`)
...@@ -21,91 +20,29 @@ func NewCmdParser(sid, name string) *CmdParser { ...@@ -21,91 +20,29 @@ func NewCmdParser(sid, name string) *CmdParser {
type CmdParser struct { type CmdParser struct {
id string id string
name string name string
buf bytes.Buffer
term *utils.Terminal
reader io.ReadCloser
writer io.WriteCloser
currentLines []string
lock *sync.Mutex lock *sync.Mutex
maxLength int maxLength int
currentLength int currentLength int
closed chan struct{}
} }
func (cp *CmdParser) WriteData(p []byte) (int, error) { func (cp *CmdParser) WriteData(p []byte) (int, error) {
select { cp.lock.Lock()
case <-cp.closed: defer cp.lock.Unlock()
return 0, io.EOF if cp.buf.Len() >= 1024 {
default: return 0, nil
}
return cp.writer.Write(p)
}
func (cp *CmdParser) Write(p []byte) (int, error) {
select {
case <-cp.closed:
return 0, io.EOF
default:
}
return len(p), nil
}
func (cp *CmdParser) Read(p []byte) (int, error) {
select {
case <-cp.closed:
return 0, io.EOF
default:
} }
return cp.reader.Read(p) return cp.buf.Write(p)
} }
func (cp *CmdParser) Close() error { func (cp *CmdParser) Close() error {
select { logger.Infof("session ID: %s, parser name: %s", cp.id, cp.name)
case <-cp.closed:
return nil return nil
default:
close(cp.closed)
}
_ = cp.reader.Close()
return cp.writer.Close()
} }
func (cp *CmdParser) initial() { func (cp *CmdParser) initial() {
cp.reader, cp.writer = io.Pipe()
cp.currentLines = make([]string, 0)
cp.lock = new(sync.Mutex) cp.lock = new(sync.Mutex)
cp.maxLength = 1024
cp.currentLength = 0
cp.closed = make(chan struct{})
cp.term = utils.NewTerminal(cp, "")
cp.term.SetEcho(false)
go func() {
logger.Infof("Session %s: %s start", cp.id, cp.name)
defer logger.Infof("Session %s: %s close", cp.id, cp.name)
loop:
for {
line, err := cp.term.ReadLine()
if err != nil {
select {
case <-cp.closed:
logger.Debugf("Session %s %s term err: %s break loop", cp.id, cp.name, err)
break loop
default:
}
logger.Debugf("Session %s %s term err: %s,loop continue", cp.id, cp.name, err)
goto loop
}
cp.lock.Lock()
cp.currentLength += len(line)
if cp.currentLength < cp.maxLength {
cp.currentLines = append(cp.currentLines, line)
}
cp.lock.Unlock()
}
}()
} }
func (cp *CmdParser) parsePS1(s string) string { func (cp *CmdParser) parsePS1(s string) string {
...@@ -114,16 +51,11 @@ func (cp *CmdParser) parsePS1(s string) string { ...@@ -114,16 +51,11 @@ func (cp *CmdParser) parsePS1(s string) string {
// Parse 解析命令或输出 // Parse 解析命令或输出
func (cp *CmdParser) Parse() string { func (cp *CmdParser) Parse() string {
select {
case <-cp.closed:
default:
cp.writer.Write([]byte("\r"))
}
cp.lock.Lock() cp.lock.Lock()
defer cp.lock.Unlock() defer cp.lock.Unlock()
output := strings.TrimSpace(strings.Join(cp.currentLines, "\r\n")) lines := utils.ParseTerminalData(cp.buf.Bytes())
output := strings.TrimSpace(strings.Join(lines, "\r\n"))
output = cp.parsePS1(output) output = cp.parsePS1(output)
cp.currentLines = make([]string, 0) cp.buf.Reset()
cp.currentLength = 0
return output return output
} }
// 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 utils package utils
import ( import (
"bytes" "bytes"
"fmt"
"unicode/utf8" "unicode/utf8"
"github.com/jumpserver/koko/pkg/logger"
) )
func ParseTerminalData(p []byte) (lines []string, ok bool) {
c := bytes.NewReader(p) type terminalParser struct {
pasteActive := false
ok = true // line is the current line being entered.
var line []rune line []rune
var pos int // pos is the logical position of the cursor in line
var remainder []byte pos int
var inBuf [256]byte // pasteActive is true iff there is a bracketed paste operation in
for { // progress.
rest := remainder pasteActive bool
lineOk := false
for !lineOk { // maxLine is the greatest value of cursorY so far.
var key rune maxLine int
key, rest = bytesToKey(rest, pasteActive)
if key == utf8.RuneError { // remainder contains the remainder of any partial key sequences after
// a read. It aliases into inBuf.
remainder []byte
inBuf [256]byte
// history contains previously entered commands so that they can be
// accessed with the up and down keys.
history stRingBuffer
// historyIndex stores the currently accessed history entry, where zero
// means the immediately previous entry.
historyIndex int
// When navigating up and down the history it's possible to return to
// the incomplete, initial line. That value is stored in
// historyPending.
historyPending string
}
func (t *terminalParser) setLine(newLine []rune, newPos int) {
t.line = newLine
t.pos = newPos
}
func (t *terminalParser) eraseNPreviousChars(n int) {
if n == 0 {
return
}
if t.pos < n {
n = t.pos
}
t.pos -= n
copy(t.line[t.pos:], t.line[n+t.pos:])
t.line = t.line[:len(t.line)-n]
}
// countToLeftWord returns then number of characters from the cursor to the
// start of the previous word.
func (t *terminalParser) countToLeftWord() int {
if t.pos == 0 {
return 0
}
pos := t.pos - 1
for pos > 0 {
if t.line[pos] != ' ' {
break break
} }
if !pasteActive { pos--
if key == keyPasteStart {
pasteActive = true
if len(line) == 0 {
} }
continue for pos > 0 {
if t.line[pos] == ' ' {
pos++
break
} }
} else if key == keyPasteEnd { pos--
pasteActive = false }
continue
return t.pos - pos
}
// countToRightWord returns then number of characters from the cursor to the
// start of the next word.
func (t *terminalParser) countToRightWord() int {
pos := t.pos
for pos < len(t.line) {
if t.line[pos] == ' ' {
break
}
pos++
}
for pos < len(t.line) {
if t.line[pos] != ' ' {
break
}
pos++
}
return pos - t.pos
}
// handleKey processes the given key and, optionally, returns a line of text
// that the user has entered.
func (t *terminalParser) handleKey(key rune) (line string, ok bool) {
if t.pasteActive && key != keyEnter {
t.addKeyToLine(key)
return
} }
switch key { switch key {
case keyBackspace: case keyBackspace:
if pos == 0 { if t.pos == 0 {
continue return
} }
line, pos = EraseNPreviousChars(1, pos, line) t.eraseNPreviousChars(1)
case keyAltLeft: case keyAltLeft:
// move left by a word. // move left by a word.
pos -= CountToLeftWord(pos, line) t.pos -= t.countToLeftWord()
case keyAltRight: case keyAltRight:
// move right by a word. // move right by a word.
pos += CountToRightWord(pos, line) t.pos += t.countToRightWord()
case keyLeft: case keyLeft:
if pos == 0 { if t.pos == 0 {
continue return
} }
pos-- t.pos--
case keyRight: case keyRight:
if pos == len(line) { if t.pos == len(t.line) {
continue return
} }
pos++ t.pos++
case keyHome: case keyHome:
if pos == 0 { if t.pos == 0 {
continue return
} }
pos = 0 t.pos = 0
case keyEnd: case keyEnd:
if pos == len(line) { if t.pos == len(t.line) {
continue return
} }
pos = len(line) t.pos = len(t.line)
case keyUp: case keyUp:
line = []rune{} entry, ok := t.history.NthPreviousEntry(t.historyIndex + 1)
pos = 0 if !ok {
ok = false return "", false
}
if t.historyIndex == -1 {
t.historyPending = string(t.line)
}
t.historyIndex++
runes := []rune(entry)
t.setLine(runes, len(runes))
case keyDown: case keyDown:
line = []rune{} switch t.historyIndex {
pos = 0 case -1:
ok = false return
case 0:
runes := []rune(t.historyPending)
t.setLine(runes, len(runes))
t.historyIndex--
default:
entry, ok := t.history.NthPreviousEntry(t.historyIndex - 1)
if ok {
t.historyIndex--
runes := []rune(entry)
t.setLine(runes, len(runes))
}
}
case keyEnter: case keyEnter:
lines = append(lines, string(line)) line = string(t.line)
line = line[:0] ok = true
pos = 0 t.line = t.line[:0]
lineOk = true t.pos = 0
t.maxLine = 0
case keyDeleteWord: case keyDeleteWord:
// Delete zero or more spaces and then one or more characters. // Delete zero or more spaces and then one or more characters.
line, pos = EraseNPreviousChars(CountToLeftWord(pos, line), pos, line) t.eraseNPreviousChars(t.countToLeftWord())
case keyDeleteLine: case keyDeleteLine:
line = line[:pos] t.line = t.line[:t.pos]
case keyCtrlD: case keyCtrlD:
// Erase the character under the current position. // Erase the character under the current position.
// The EOF case when the line is empty is handled in // The EOF case when the line is empty is handled in
// readLine(). // readLine().
if pos < len(line) { if t.pos < len(t.line) {
pos++ t.pos++
line, pos = EraseNPreviousChars(1, pos, line) t.eraseNPreviousChars(1)
} }
case keyCtrlU: case keyCtrlU:
line = line[:0] t.eraseNPreviousChars(t.pos)
case keyClearScreen: case keyClearScreen:
// Erases the screen and moves the cursor to the home position.
t.setLine(t.line, t.pos)
default: default:
if !isPrintable(key) { if !isPrintable(key) {
fmt.Println("could not printable: ", []byte(string(key)), " ", key) return
ok = false
continue
} }
line, pos = AddKeyToLine(key, pos, line) if len(t.line) == maxLineLength {
return
} }
t.addKeyToLine(key)
} }
if len(rest) > 0 { return
n := copy(inBuf[:], rest) }
remainder = inBuf[:n]
} else { // addKeyToLine inserts the given key at the current position in the current
remainder = nil // line.
func (t *terminalParser) addKeyToLine(key rune) {
if len(t.line) == cap(t.line) {
newLine := make([]rune, len(t.line), 2*(1+len(t.line)))
copy(newLine, t.line)
t.line = newLine
} }
t.line = t.line[:len(t.line)+1]
copy(t.line[t.pos+1:], t.line[t.pos:])
t.line[t.pos] = key
t.pos++
}
// remainder is a slice at the beginning of t.inBuf func (t *terminalParser) parseLines(p []byte) (lines []string) {
// containing a partial key sequence var err error
readBuf := inBuf[len(remainder):]
var n int lines = make([]string, 0, 3)
n, err := c.Read(readBuf) lineIsPasted := t.pasteActive
if err != nil { reader := bytes.NewBuffer(p)
if len(line) > 0 { for {
lines = append(lines, string(line)) rest := t.remainder
} else if len(rest) > 0 { line := ""
lines = append(lines, string(rest)) lineOk := false
for !lineOk {
var key rune
key, rest = bytesToKey(rest, t.pasteActive)
if key == utf8.RuneError {
break
} }
if !t.pasteActive {
return if key == keyCtrlD {
if len(t.line) == 0 {
// as key has already handled, we need update remainder data,
t.remainder = rest
return lines
} }
remainder = inBuf[:n+len(remainder)]
} }
} if key == keyPasteStart {
t.pasteActive = true
func EraseNPreviousChars(n, cPos int, line []rune) ([]rune, int) { if len(t.line) == 0 {
if n == 0 { lineIsPasted = true
return line, cPos
} }
if cPos < n { continue
n = cPos
} }
cPos -= n } else if key == keyPasteEnd {
copy(line[cPos:], line[n+cPos:]) t.pasteActive = false
return line[:len(line)-n], cPos continue
}
func CountToLeftWord(currentPos int, line []rune) int {
if currentPos == 0 {
return 0
} }
if !t.pasteActive {
pos := currentPos - 1 lineIsPasted = false
for pos > 0 {
if line[pos] != ' ' {
break
} }
pos-- line, lineOk = t.handleKey(key)
} }
for pos > 0 { if len(rest) > 0 {
if line[pos] == ' ' { n := copy(t.inBuf[:], rest)
pos++ t.remainder = t.inBuf[:n]
break } else {
t.remainder = nil
} }
pos-- if lineOk {
if lineIsPasted {
err = ErrPasteIndicator
}
lines = append(lines, line)
} }
return currentPos - pos // t.remainder is a slice at the beginning of t.inBuf
} // containing a partial key sequence
readBuf := t.inBuf[len(t.remainder):]
var n int
func CountToRightWord(currentPos int, line []rune) int { n, err = reader.Read(readBuf)
pos := currentPos if err != nil && n == 0 {
for pos < len(line) { if len(t.line) > 0 {
if line[pos] == ' ' { lines = append(lines, string(t.line))
break
} }
pos++ if len(t.remainder) > 0{
continue
} }
for pos < len(line) { return
if line[pos] != ' ' { } else if err == nil && n == 0 {
break if len(t.remainder) == len(t.inBuf) {
logger.Errorf("~~ 发生卡顿问题 ~~")
t.remainder = t.remainder[1:]
continue
} }
pos++
} }
return pos - currentPos
t.remainder = t.inBuf[:n+len(t.remainder)]
}
} }
func AddKeyToLine(key rune, pos int, line []rune) ([]rune, int) { func ParseTerminalData(p []byte) (lines []string) {
if len(line) == cap(line) { t := terminalParser{
newLine := make([]rune, len(line), 2*(1+len(line))) historyIndex: -1,
copy(newLine, line)
line = newLine
} }
line = line[:len(line)+1] return t.parseLines(p)
copy(line[pos+1:], line[pos:])
line[pos] = key
pos++
return line, pos
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment