tclient/reader.go

202 lines
4.8 KiB
Go
Raw Permalink Normal View History

2018-08-07 15:54:08 +00:00
package tclient
import (
"time"
"fmt"
"bytes"
"github.com/pkg/errors"
"regexp"
)
2018-08-07 22:20:31 +00:00
// ReadUntil reads tcp stream from server until 'waitfor' regex matches.
// Returns gathered output and error, if any.
// Any escape sequences are cutted out during reading for providing clean output for parsing/reading.
2018-08-07 15:54:08 +00:00
func (c *TelnetClient) ReadUntil(waitfor string) (string, error) {
var err error
2018-08-21 11:59:01 +00:00
// one more dirty hack =\ After dlink paging, it MAY BE, or MAY NOT BE one unnecessary empty string with pages + \n. We should strip it.
var paged bool
2018-08-07 15:54:08 +00:00
var b byte
2018-08-21 11:29:28 +00:00
//var prev byte
2018-08-16 12:57:54 +00:00
c.buf.Reset()
//var buf bytes.Buffer
2018-08-07 15:54:08 +00:00
var lastLine bytes.Buffer
if waitfor == "" {
2018-08-16 12:57:54 +00:00
return c.buf.String(), fmt.Errorf(`Empty "waitfor" string given`)
2018-08-07 15:54:08 +00:00
}
rePrompt, err := regexp.Compile(waitfor)
if err != nil {
2018-08-16 12:57:54 +00:00
return c.buf.String(), fmt.Errorf(`Cannot compile "waitfor" regexp`)
2018-08-07 15:54:08 +00:00
}
// run reading cycle
2018-08-07 21:57:47 +00:00
inSequence := false
//skipCurLine := false
2018-08-07 15:54:08 +00:00
globalTout := time.After(time.Second * time.Duration(c.TimeoutGlobal))
for {
select {
//case <- c.ctx.Done():
// return c.cutEscapes(buf), nil
case <- globalTout:
2018-08-16 12:57:54 +00:00
return c.buf.String() + lastLine.String(), fmt.Errorf("Operation timeout reached during read")
2018-08-07 15:54:08 +00:00
default:
2018-08-21 11:29:28 +00:00
//prev = b
2018-08-07 15:54:08 +00:00
b, err = c.readByte()
if err != nil {
2018-08-16 12:57:54 +00:00
return c.buf.String() + lastLine.String(), errors.Wrap(err, "Error during read")
2018-08-07 15:54:08 +00:00
}
// catch escape sequences
if b == TELNET_IAC {
seq := []byte{b}
b2, err := c.readByte()
if err != nil {
2018-08-16 13:01:27 +00:00
return c.buf.String(), errors.Wrap(err, "Error while reading escape sequence")
2018-08-07 15:54:08 +00:00
}
seq = append(seq, b2)
if b2 == TELNET_SB { // subnegotiation
// read all until subneg. end.
for {
bn, err := c.readByte()
if err != nil {
2018-08-16 13:01:27 +00:00
return c.buf.String(), errors.Wrap(err, "Error while reading escape subnegotiation sequence")
2018-08-07 15:54:08 +00:00
}
seq = append(seq, bn)
if bn == TELNET_SE {
break
}
}
} else {
// not subsequence.
bn, err := c.readByte()
if err != nil {
2018-08-16 13:01:27 +00:00
return c.buf.String(), errors.Wrap(err, "Error while reading IAC sequence")
2018-08-07 15:54:08 +00:00
}
seq = append(seq, bn)
}
// Sequence finished, do something with it:
err = c.negotiate(seq)
if err != nil {
2018-08-16 13:01:27 +00:00
return c.buf.String(), errors.Wrap(err, "Failed to negotiate connection")
2018-08-07 15:54:08 +00:00
//c.errChan <- fmt.Sprintf("Failed to negotiate connection: %s", err.Error())
}
}
2018-08-07 21:57:47 +00:00
// cut out escape sequences
if b == 27 {
inSequence = true
continue
}
if inSequence {
// 2) 0-?, @-~, ' ' - / === 48-63, 32-47, finish with 64-126
if b == 91 {
continue
}
if b >= 32 && b <= 63 {
// just skip it
continue
}
if b >= 64 && b <= 126 {
// finish sequence
inSequence = false
continue
}
}
2018-08-26 13:31:05 +00:00
// not IAC sequence, but IAC char =\
if b == TELNET_IAC {
continue
}
// remove \r ; remove backspaces
if b == 8 {
if lastLine.Len() > 0 {
lastLine.Truncate(lastLine.Len() - 1)
}
continue
}
if b == '\r' {
continue
}
//fmt.Printf("%s | %d\n", string(b), b)
//fmt.Printf("%s", string(b))
2018-08-07 15:54:08 +00:00
// this is not escape sequence, so write this byte to buffer
2018-08-16 12:30:42 +00:00
// update: strip '\r'
2018-08-19 22:02:39 +00:00
/*if b != '\r' {
2018-08-16 12:57:54 +00:00
c.buf.Write([]byte{b})
2018-08-19 22:02:39 +00:00
}*/
2018-08-07 15:54:08 +00:00
2018-08-15 12:41:20 +00:00
// check for regex matching. Execute callback if matched.
2018-08-07 21:57:47 +00:00
if len(c.patterns) > 0 {
for i := range c.patterns {
if c.patterns[i].Re.Match(lastLine.Bytes()) {
c.patterns[i].Cb()
lastLine.Reset()
2018-08-21 11:41:53 +00:00
// if last 2 chars in buffer are '\n\n', remove last '\n'
bts := c.buf.Bytes()
if len(bts) > 2 && bts[len(bts)-1] == '\n' && bts[len(bts)-2] == '\n' {
c.buf.Truncate(c.buf.Len()-1)
}
2018-08-21 11:59:01 +00:00
paged = true
2018-08-07 21:57:47 +00:00
}
}
}
2018-08-07 15:54:08 +00:00
// check for CRLF.
// We need last line to compare with prompt.
2018-08-21 11:29:28 +00:00
//if b == '\n' && prev == '\r' {
if b == '\n' {
2018-08-19 22:15:37 +00:00
lastLine.Write([]byte{b})
2018-08-21 11:59:01 +00:00
if paged {
2018-08-21 12:14:19 +00:00
paged = false
// only spaces without any other chars, \n at the end
2018-09-08 07:46:49 +00:00
if match, err := regexp.Match(`(?msi:^[\s^\n]+$)`, lastLine.Bytes()); match && err == nil {
2018-08-21 11:59:01 +00:00
lastLine.Reset()
2018-08-21 12:14:19 +00:00
continue
2018-08-21 11:59:01 +00:00
}
}
2018-08-21 12:14:19 +00:00
c.buf.Write(lastLine.Bytes())
lastLine.Reset()
2018-08-07 15:54:08 +00:00
} else {
lastLine.Write([]byte{b})
}
// After reading, we should check for regexp every time.
// Unfortunately, we cant wait only CRLF, because prompt usually comes without CRLF.
if rePrompt.Match(lastLine.Bytes()) {
2018-08-16 12:57:54 +00:00
return c.buf.String(), nil
2018-08-07 15:54:08 +00:00
}
}
}
}
// read one byte from tcp stream
func (c *TelnetClient) readByte() (byte, error) {
var err error
var buffer [1]byte
p := buffer[:]
err = c.conn.SetReadDeadline(time.Now().Add(time.Second * time.Duration(c.Timeout)))
if err != nil {
return p[0], err
}
_, err = c.conn.Read(p)
// error during read
if err != nil {
return p[0], errors.Wrap(err, "Error during readByte")
}
return p[0], nil
}