Skip to content
Projects
Groups
Snippets
Help
Loading...
Sign in
Toggle navigation
K
koko
Project
Project
Details
Activity
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
ops
koko
Commits
9295a3eb
Commit
9295a3eb
authored
Jan 08, 2020
by
Eric
Browse files
Options
Browse Files
Download
Plain Diff
fix commmand parse
parents
2e4496e2
50388696
Show whitespace changes
Inline
Side-by-side
Showing
2 changed files
with
253 additions
and
210 deletions
+253
-210
parsercmd.go
pkg/proxy/parsercmd.go
+13
-81
parser.go
pkg/utils/parser.go
+240
-129
No files found.
pkg/proxy/parsercmd.go
View file @
9295a3eb
package
proxy
import
(
"io"
"bytes"
"github.com/jumpserver/koko/pkg/logger"
"github.com/jumpserver/koko/pkg/utils"
"regexp"
"strings"
"sync"
"github.com/jumpserver/koko/pkg/logger"
"github.com/jumpserver/koko/pkg/utils"
)
var
ps1Pattern
=
regexp
.
MustCompile
(
`^\[?.*@.*\]?[\\$#]\s|mysql>\s`
)
...
...
@@ -21,91 +20,29 @@ func NewCmdParser(sid, name string) *CmdParser {
type
CmdParser
struct
{
id
string
name
string
buf
bytes
.
Buffer
term
*
utils
.
Terminal
reader
io
.
ReadCloser
writer
io
.
WriteCloser
currentLines
[]
string
lock
*
sync
.
Mutex
maxLength
int
currentLength
int
closed
chan
struct
{}
}
func
(
cp
*
CmdParser
)
WriteData
(
p
[]
byte
)
(
int
,
error
)
{
select
{
case
<-
cp
.
closed
:
return
0
,
io
.
EOF
default
:
}
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
:
cp
.
lock
.
Lock
()
defer
cp
.
lock
.
Unlock
()
if
cp
.
buf
.
Len
()
>=
1024
{
return
0
,
nil
}
return
cp
.
reader
.
Read
(
p
)
return
cp
.
buf
.
Write
(
p
)
}
func
(
cp
*
CmdParser
)
Close
()
error
{
select
{
case
<-
cp
.
closed
:
logger
.
Infof
(
"session ID: %s, parser name: %s"
,
cp
.
id
,
cp
.
name
)
return
nil
default
:
close
(
cp
.
closed
)
}
_
=
cp
.
reader
.
Close
()
return
cp
.
writer
.
Close
()
}
func
(
cp
*
CmdParser
)
initial
()
{
cp
.
reader
,
cp
.
writer
=
io
.
Pipe
()
cp
.
currentLines
=
make
([]
string
,
0
)
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
{
...
...
@@ -114,16 +51,11 @@ func (cp *CmdParser) parsePS1(s string) string {
// Parse 解析命令或输出
func
(
cp
*
CmdParser
)
Parse
()
string
{
select
{
case
<-
cp
.
closed
:
default
:
cp
.
writer
.
Write
([]
byte
(
"
\r
"
))
}
cp
.
lock
.
Lock
()
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
)
cp
.
currentLines
=
make
([]
string
,
0
)
cp
.
currentLength
=
0
cp
.
buf
.
Reset
()
return
output
}
pkg/utils/parser.go
View file @
9295a3eb
// 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
import
(
"bytes"
"fmt"
"unicode/utf8"
"github.com/jumpserver/koko/pkg/logger"
)
func
ParseTerminalData
(
p
[]
byte
)
(
lines
[]
string
,
ok
bool
)
{
c
:=
bytes
.
NewReader
(
p
)
pasteActive
:=
false
ok
=
true
var
line
[]
rune
var
pos
int
var
remainder
[]
byte
var
inBuf
[
256
]
byte
for
{
rest
:=
remainder
lineOk
:=
false
for
!
lineOk
{
var
key
rune
key
,
rest
=
bytesToKey
(
rest
,
pasteActive
)
if
key
==
utf8
.
RuneError
{
type
terminalParser
struct
{
// line is the current line being entered.
line
[]
rune
// pos is the logical position of the cursor in line
pos
int
// pasteActive is true iff there is a bracketed paste operation in
// progress.
pasteActive
bool
// maxLine is the greatest value of cursorY so far.
maxLine
int
// 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
}
if
!
pasteActive
{
if
key
==
keyPasteStart
{
pasteActive
=
true
if
len
(
line
)
==
0
{
pos
--
}
continue
for
pos
>
0
{
if
t
.
line
[
pos
]
==
' '
{
pos
++
break
}
}
else
if
key
==
keyPasteEnd
{
pasteActive
=
false
continue
pos
--
}
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
{
case
keyBackspace
:
if
pos
==
0
{
continue
if
t
.
pos
==
0
{
return
}
line
,
pos
=
EraseNPreviousChars
(
1
,
pos
,
line
)
t
.
eraseNPreviousChars
(
1
)
case
keyAltLeft
:
// move left by a word.
pos
-=
CountToLeftWord
(
pos
,
line
)
t
.
pos
-=
t
.
countToLeftWord
(
)
case
keyAltRight
:
// move right by a word.
pos
+=
CountToRightWord
(
pos
,
line
)
t
.
pos
+=
t
.
countToRightWord
(
)
case
keyLeft
:
if
pos
==
0
{
continue
if
t
.
pos
==
0
{
return
}
pos
--
t
.
pos
--
case
keyRight
:
if
pos
==
len
(
line
)
{
continue
if
t
.
pos
==
len
(
t
.
line
)
{
return
}
pos
++
t
.
pos
++
case
keyHome
:
if
pos
==
0
{
continue
if
t
.
pos
==
0
{
return
}
pos
=
0
t
.
pos
=
0
case
keyEnd
:
if
pos
==
len
(
line
)
{
continue
if
t
.
pos
==
len
(
t
.
line
)
{
return
}
pos
=
len
(
line
)
t
.
pos
=
len
(
t
.
line
)
case
keyUp
:
line
=
[]
rune
{}
pos
=
0
ok
=
false
entry
,
ok
:=
t
.
history
.
NthPreviousEntry
(
t
.
historyIndex
+
1
)
if
!
ok
{
return
""
,
false
}
if
t
.
historyIndex
==
-
1
{
t
.
historyPending
=
string
(
t
.
line
)
}
t
.
historyIndex
++
runes
:=
[]
rune
(
entry
)
t
.
setLine
(
runes
,
len
(
runes
))
case
keyDown
:
line
=
[]
rune
{}
pos
=
0
ok
=
false
switch
t
.
historyIndex
{
case
-
1
:
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
:
lines
=
append
(
lines
,
string
(
line
))
line
=
line
[
:
0
]
pos
=
0
lineOk
=
true
line
=
string
(
t
.
line
)
ok
=
true
t
.
line
=
t
.
line
[
:
0
]
t
.
pos
=
0
t
.
maxLine
=
0
case
keyDeleteWord
:
// 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
:
line
=
line
[
:
pos
]
t
.
line
=
t
.
line
[
:
t
.
pos
]
case
keyCtrlD
:
// Erase the character under the current position.
// The EOF case when the line is empty is handled in
// readLine().
if
pos
<
len
(
line
)
{
pos
++
line
,
pos
=
EraseNPreviousChars
(
1
,
pos
,
line
)
if
t
.
pos
<
len
(
t
.
line
)
{
t
.
pos
++
t
.
eraseNPreviousChars
(
1
)
}
case
keyCtrlU
:
line
=
line
[
:
0
]
t
.
eraseNPreviousChars
(
t
.
pos
)
case
keyClearScreen
:
// Erases the screen and moves the cursor to the home position.
t
.
setLine
(
t
.
line
,
t
.
pos
)
default
:
if
!
isPrintable
(
key
)
{
fmt
.
Println
(
"could not printable: "
,
[]
byte
(
string
(
key
)),
" "
,
key
)
ok
=
false
continue
return
}
line
,
pos
=
AddKeyToLine
(
key
,
pos
,
line
)
if
len
(
t
.
line
)
==
maxLineLength
{
return
}
t
.
addKeyToLine
(
key
)
}
if
len
(
rest
)
>
0
{
n
:=
copy
(
inBuf
[
:
],
rest
)
remainder
=
inBuf
[
:
n
]
}
else
{
remainder
=
nil
return
}
// addKeyToLine inserts the given key at the current position in the current
// 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
// containing a partial key sequence
readBuf
:=
inBuf
[
len
(
remainder
)
:
]
func
(
t
*
terminalParser
)
parseLines
(
p
[]
byte
)
(
lines
[]
string
)
{
var
err
error
var
n
int
n
,
err
:=
c
.
Read
(
readBuf
)
if
err
!=
nil
{
if
len
(
line
)
>
0
{
lines
=
append
(
lines
,
string
(
line
))
}
else
if
len
(
rest
)
>
0
{
lines
=
append
(
lines
,
string
(
rest
))
lines
=
make
([]
string
,
0
,
3
)
lineIsPasted
:=
t
.
pasteActive
reader
:=
bytes
.
NewBuffer
(
p
)
for
{
rest
:=
t
.
remainder
line
:=
""
lineOk
:=
false
for
!
lineOk
{
var
key
rune
key
,
rest
=
bytesToKey
(
rest
,
t
.
pasteActive
)
if
key
==
utf8
.
RuneError
{
break
}
return
if
!
t
.
pasteActive
{
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
)]
}
}
func
EraseNPreviousChars
(
n
,
cPos
int
,
line
[]
rune
)
([]
rune
,
int
)
{
if
n
==
0
{
return
line
,
cPos
if
key
==
keyPasteStart
{
t
.
pasteActive
=
true
if
len
(
t
.
line
)
==
0
{
lineIsPasted
=
true
}
if
cPos
<
n
{
n
=
cPos
continue
}
cPos
-=
n
copy
(
line
[
cPos
:
],
line
[
n
+
cPos
:
])
return
line
[
:
len
(
line
)
-
n
],
cPos
}
func
CountToLeftWord
(
currentPos
int
,
line
[]
rune
)
int
{
if
currentPos
==
0
{
return
0
}
else
if
key
==
keyPasteEnd
{
t
.
pasteActive
=
false
continue
}
pos
:=
currentPos
-
1
for
pos
>
0
{
if
line
[
pos
]
!=
' '
{
break
if
!
t
.
pasteActive
{
lineIsPasted
=
false
}
pos
--
line
,
lineOk
=
t
.
handleKey
(
key
)
}
for
pos
>
0
{
if
line
[
pos
]
==
' '
{
pos
++
break
if
len
(
rest
)
>
0
{
n
:=
copy
(
t
.
inBuf
[
:
],
rest
)
t
.
remainder
=
t
.
inBuf
[
:
n
]
}
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
{
pos
:=
currentPos
for
pos
<
len
(
line
)
{
if
line
[
pos
]
==
' '
{
break
n
,
err
=
reader
.
Read
(
readBuf
)
if
err
!=
nil
&&
n
==
0
{
if
len
(
t
.
line
)
>
0
{
lines
=
append
(
lines
,
string
(
t
.
line
))
}
pos
++
if
len
(
t
.
remainder
)
>
0
{
continue
}
for
pos
<
len
(
line
)
{
if
line
[
pos
]
!=
' '
{
break
return
}
else
if
err
==
nil
&&
n
==
0
{
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
)
{
if
len
(
line
)
==
cap
(
line
)
{
newLine
:=
make
([]
rune
,
len
(
line
),
2
*
(
1
+
len
(
line
)))
copy
(
newLine
,
line
)
line
=
newLine
func
ParseTerminalData
(
p
[]
byte
)
(
lines
[]
string
)
{
t
:=
terminalParser
{
historyIndex
:
-
1
,
}
line
=
line
[
:
len
(
line
)
+
1
]
copy
(
line
[
pos
+
1
:
],
line
[
pos
:
])
line
[
pos
]
=
key
pos
++
return
line
,
pos
return
t
.
parseLines
(
p
)
}
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment