selfcare init
This commit is contained in:
@@ -0,0 +1,21 @@
|
||||
package telsh
|
||||
|
||||
|
||||
type internalDiscardLogger struct{}
|
||||
|
||||
func (internalDiscardLogger) Debug(...interface{}) {}
|
||||
func (internalDiscardLogger) Debugf(string, ...interface{}) {}
|
||||
func (internalDiscardLogger) Debugln(...interface{}) {}
|
||||
|
||||
func (internalDiscardLogger) Error(...interface{}) {}
|
||||
func (internalDiscardLogger) Errorf(string, ...interface{}) {}
|
||||
func (internalDiscardLogger) Errorln(...interface{}) {}
|
||||
|
||||
func (internalDiscardLogger) Trace(...interface{}) {}
|
||||
func (internalDiscardLogger) Tracef(string, ...interface{}) {}
|
||||
func (internalDiscardLogger) Traceln(...interface{}) {}
|
||||
|
||||
func (internalDiscardLogger) Warn(...interface{}) {}
|
||||
func (internalDiscardLogger) Warnf(string, ...interface{}) {}
|
||||
func (internalDiscardLogger) Warnln(...interface{}) {}
|
||||
|
||||
@@ -0,0 +1,154 @@
|
||||
/*
|
||||
Package telsh provides "middleware" (for the telnet package) that can be used to implement a TELNET or TELNETS server
|
||||
that provides a "shell" interface (also known as a "command-line interface" or "CLI").
|
||||
|
||||
Shell interfaces you may be familiar with include: "bash", "csh", "sh", "zsk", etc.
|
||||
|
||||
|
||||
TELNET Server
|
||||
|
||||
Here is an example usage:
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi"
|
||||
"github.com/reiver/go-telnet"
|
||||
"github.com/reiver/go-telnet/telsh"
|
||||
|
||||
"io"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
telnetHandler := telsh.NewShellHandler()
|
||||
|
||||
if err := telnetHandler.RegisterElse(
|
||||
telsh.ProducerFunc(
|
||||
func(ctx telnet.Context, name string, args ...string) telsh.Handler {
|
||||
return telsh.PromoteHandlerFunc(
|
||||
func(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error {
|
||||
oi.LongWrite(stdout, []byte{'w','a','t','?', '\r','\n'})
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
); nil != err {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if err := telnetHandler.Register("help",
|
||||
telsh.ProducerFunc(
|
||||
func(ctx telnet.Context, name string, args ...string) telsh.Handler {
|
||||
return telsh.PromoteHandlerFunc(
|
||||
func(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error {
|
||||
oi.LongWrite(stdout, []byte{'r','t','f','m','!', '\r','\n'})
|
||||
|
||||
return nil
|
||||
},
|
||||
)
|
||||
},
|
||||
),
|
||||
); nil != err {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err := telnet.ListenAndServe(":5555", telnetHandler)
|
||||
if nil != err {
|
||||
//@TODO: Handle this error better.
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
Here is a more "unpacked" example:
|
||||
|
||||
package main
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi"
|
||||
"github.com/reiver/go-telnet"
|
||||
"github.com/reiver/go-telnet/telsh"
|
||||
|
||||
"io"
|
||||
"time"
|
||||
)
|
||||
|
||||
|
||||
var (
|
||||
shellHandler = telsh.NewShellHandler()
|
||||
)
|
||||
|
||||
|
||||
func init() {
|
||||
|
||||
shellHandler.Register("dance", telsh.ProducerFunc(producer))
|
||||
|
||||
|
||||
shellHandler.WelcomeMessage = `
|
||||
__ __ ______ _ _____ ____ __ __ ______
|
||||
\ \ / /| ____|| | / ____| / __ \ | \/ || ____|
|
||||
\ \ /\ / / | |__ | | | | | | | || \ / || |__
|
||||
\ \/ \/ / | __| | | | | | | | || |\/| || __|
|
||||
\ /\ / | |____ | |____ | |____ | |__| || | | || |____
|
||||
\/ \/ |______||______| \_____| \____/ |_| |_||______|
|
||||
|
||||
`
|
||||
}
|
||||
|
||||
|
||||
func producer(ctx telnet.Context, name string, args ...string) telsh.Handler{
|
||||
return telsh.PromoteHandlerFunc(handler)
|
||||
}
|
||||
|
||||
|
||||
func handler(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error {
|
||||
for i:=0; i<20; i++ {
|
||||
oi.LongWriteString(stdout, "\r⠋")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠙")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠹")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠸")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠼")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠴")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠦")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠧")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠇")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
|
||||
oi.LongWriteString(stdout, "\r⠏")
|
||||
time.Sleep(50*time.Millisecond)
|
||||
}
|
||||
oi.LongWriteString(stdout, "\r \r\n")
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
||||
func main() {
|
||||
|
||||
addr := ":5555"
|
||||
if err := telnet.ListenAndServe(addr, shellHandler); nil != err {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
*/
|
||||
package telsh
|
||||
@@ -0,0 +1,140 @@
|
||||
package telsh
|
||||
|
||||
|
||||
import (
|
||||
"io"
|
||||
)
|
||||
|
||||
|
||||
// Hander is an abstraction that represents a "running" shell "command".
|
||||
//
|
||||
// Contrast this with a Producer, which is is an abstraction that
|
||||
// represents a shell "command".
|
||||
//
|
||||
// To use a metaphor, the differences between a Producer and a Handler,
|
||||
// is like the difference between a program executable and actually running
|
||||
// the program executable.
|
||||
//
|
||||
// Conceptually, anything that implements the Hander, and then has its Producer
|
||||
// registered with ShellHandler.Register() will be available as a command.
|
||||
//
|
||||
// Note that Handler was intentionally made to be compatible with
|
||||
// "os/exec", which is part of the Go standard library.
|
||||
type Handler interface {
|
||||
Run() error
|
||||
|
||||
StdinPipe() (io.WriteCloser, error)
|
||||
StdoutPipe() (io.ReadCloser, error)
|
||||
StderrPipe() (io.ReadCloser, error)
|
||||
}
|
||||
|
||||
|
||||
// HandlerFunc is useful to write inline Producers, and provides an alternative to
|
||||
// creating something that implements Handler directly.
|
||||
//
|
||||
// For example:
|
||||
//
|
||||
// shellHandler := telsh.NewShellHandler()
|
||||
//
|
||||
// shellHandler.Register("five", telsh.ProducerFunc(
|
||||
//
|
||||
// func(ctx telnet.Context, name string, args ...string) telsh.Handler{
|
||||
//
|
||||
// return telsh.PromoteHandlerFunc(
|
||||
//
|
||||
// func(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string) error {
|
||||
// oi.LongWrite(stdout, []byte{'5', '\r', '\n'})
|
||||
//
|
||||
// return nil
|
||||
// },
|
||||
// )
|
||||
// },
|
||||
// ))
|
||||
//
|
||||
// Note that PromoteHandlerFunc is used to turn a HandlerFunc into a Handler.
|
||||
type HandlerFunc func(stdin io.ReadCloser, stdout io.WriteCloser, stderr io.WriteCloser, args ...string)error
|
||||
|
||||
|
||||
type internalPromotedHandlerFunc struct {
|
||||
err error
|
||||
fn HandlerFunc
|
||||
stdin io.ReadCloser
|
||||
stdout io.WriteCloser
|
||||
stderr io.WriteCloser
|
||||
|
||||
stdinPipe io.WriteCloser
|
||||
stdoutPipe io.ReadCloser
|
||||
stderrPipe io.ReadCloser
|
||||
|
||||
args []string
|
||||
}
|
||||
|
||||
|
||||
// PromoteHandlerFunc turns a HandlerFunc into a Handler.
|
||||
func PromoteHandlerFunc(fn HandlerFunc, args ...string) Handler {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdoutPipe, stdout := io.Pipe()
|
||||
stderrPipe, stderr := io.Pipe()
|
||||
|
||||
argsCopy := make([]string, len(args))
|
||||
for i, datum := range args {
|
||||
argsCopy[i] = datum
|
||||
}
|
||||
|
||||
handler := internalPromotedHandlerFunc{
|
||||
err:nil,
|
||||
|
||||
fn:fn,
|
||||
|
||||
stdin:stdin,
|
||||
stdout:stdout,
|
||||
stderr:stderr,
|
||||
|
||||
stdinPipe:stdinPipe,
|
||||
stdoutPipe:stdoutPipe,
|
||||
stderrPipe:stderrPipe,
|
||||
|
||||
args:argsCopy,
|
||||
}
|
||||
|
||||
return &handler
|
||||
}
|
||||
|
||||
|
||||
func (handler *internalPromotedHandlerFunc) Run() error {
|
||||
if nil != handler.err {
|
||||
return handler.err
|
||||
}
|
||||
|
||||
handler.err = handler.fn(handler.stdin, handler.stdout, handler.stderr, handler.args...)
|
||||
|
||||
handler.stdin.Close()
|
||||
handler.stdout.Close()
|
||||
handler.stderr.Close()
|
||||
|
||||
return handler.err
|
||||
}
|
||||
|
||||
func (handler *internalPromotedHandlerFunc) StdinPipe() (io.WriteCloser, error) {
|
||||
if nil != handler.err {
|
||||
return nil, handler.err
|
||||
}
|
||||
|
||||
return handler.stdinPipe, nil
|
||||
}
|
||||
|
||||
func (handler *internalPromotedHandlerFunc) StdoutPipe() (io.ReadCloser, error) {
|
||||
if nil != handler.err {
|
||||
return nil, handler.err
|
||||
}
|
||||
|
||||
return handler.stdoutPipe, nil
|
||||
}
|
||||
|
||||
func (handler *internalPromotedHandlerFunc) StderrPipe() (io.ReadCloser, error) {
|
||||
if nil != handler.err {
|
||||
return nil, handler.err
|
||||
}
|
||||
|
||||
return handler.stderrPipe, nil
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
package telsh
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi"
|
||||
"github.com/reiver/go-telnet"
|
||||
|
||||
"io"
|
||||
"sort"
|
||||
)
|
||||
|
||||
|
||||
type internalHelpProducer struct {
|
||||
shellHandler *ShellHandler
|
||||
}
|
||||
|
||||
|
||||
func Help(shellHandler *ShellHandler) Producer {
|
||||
producer := internalHelpProducer{
|
||||
shellHandler:shellHandler,
|
||||
}
|
||||
|
||||
return &producer
|
||||
}
|
||||
|
||||
|
||||
func (producer *internalHelpProducer) Produce(telnet.Context, string, ...string) Handler {
|
||||
return newHelpHandler(producer)
|
||||
}
|
||||
|
||||
|
||||
type internalHelpHandler struct {
|
||||
helpProducer *internalHelpProducer
|
||||
|
||||
err error
|
||||
|
||||
stdin io.ReadCloser
|
||||
stdout io.WriteCloser
|
||||
stderr io.WriteCloser
|
||||
|
||||
stdinPipe io.WriteCloser
|
||||
stdoutPipe io.ReadCloser
|
||||
stderrPipe io.ReadCloser
|
||||
}
|
||||
|
||||
|
||||
func newHelpHandler(helpProducer *internalHelpProducer) *internalHelpHandler {
|
||||
stdin, stdinPipe := io.Pipe()
|
||||
stdoutPipe, stdout := io.Pipe()
|
||||
stderrPipe, stderr := io.Pipe()
|
||||
|
||||
handler := internalHelpHandler{
|
||||
helpProducer:helpProducer,
|
||||
|
||||
err:nil,
|
||||
|
||||
stdin:stdin,
|
||||
stdout:stdout,
|
||||
stderr:stderr,
|
||||
|
||||
stdinPipe:stdinPipe,
|
||||
stdoutPipe:stdoutPipe,
|
||||
stderrPipe:stderrPipe,
|
||||
}
|
||||
|
||||
return &handler
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
func (handler *internalHelpHandler) Run() error {
|
||||
if nil != handler.err {
|
||||
return handler.err
|
||||
}
|
||||
|
||||
//@TODO: Should this be reaching inside of ShellHandler? Maybe there should be ShellHandler public methods instead.
|
||||
keys := make([]string, 1+len(handler.helpProducer.shellHandler.producers))
|
||||
i:=0
|
||||
for key,_ := range handler.helpProducer.shellHandler.producers {
|
||||
keys[i] = key
|
||||
i++
|
||||
}
|
||||
keys[i] = handler.helpProducer.shellHandler.ExitCommandName
|
||||
sort.Strings(keys)
|
||||
for _, key := range keys {
|
||||
oi.LongWriteString(handler.stdout, key)
|
||||
oi.LongWriteString(handler.stdout, "\r\n")
|
||||
}
|
||||
|
||||
handler.stdin.Close()
|
||||
handler.stdout.Close()
|
||||
handler.stderr.Close()
|
||||
|
||||
return handler.err
|
||||
}
|
||||
|
||||
func (handler *internalHelpHandler) StdinPipe() (io.WriteCloser, error) {
|
||||
if nil != handler.err {
|
||||
return nil, handler.err
|
||||
}
|
||||
|
||||
return handler.stdinPipe, nil
|
||||
}
|
||||
|
||||
func (handler *internalHelpHandler) StdoutPipe() (io.ReadCloser, error) {
|
||||
if nil != handler.err {
|
||||
return nil, handler.err
|
||||
}
|
||||
|
||||
return handler.stdoutPipe, nil
|
||||
}
|
||||
|
||||
func (handler *internalHelpHandler) StderrPipe() (io.ReadCloser, error) {
|
||||
if nil != handler.err {
|
||||
return nil, handler.err
|
||||
}
|
||||
|
||||
return handler.stderrPipe, nil
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package telsh
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-telnet"
|
||||
)
|
||||
|
||||
|
||||
// A Producer provides a Produce method which creates a Handler.
|
||||
//
|
||||
// Producer is an abstraction that represents a shell "command".
|
||||
//
|
||||
// Contrast this with a Handler, which is is an abstraction that
|
||||
// represents a "running" shell "command".
|
||||
//
|
||||
// To use a metaphor, the differences between a Producer and a Handler,
|
||||
// is like the difference between a program executable and actually running
|
||||
// the program executable.
|
||||
type Producer interface {
|
||||
Produce(telnet.Context, string, ...string) Handler
|
||||
}
|
||||
|
||||
|
||||
// ProducerFunc is an adaptor, that can be used to turn a func with the
|
||||
// signature:
|
||||
//
|
||||
// func(telnet.Context, string, ...string) Handler
|
||||
//
|
||||
// Into a Producer
|
||||
type ProducerFunc func(telnet.Context, string, ...string) Handler
|
||||
|
||||
|
||||
// Produce makes ProducerFunc fit the Producer interface.
|
||||
func (fn ProducerFunc) Produce(ctx telnet.Context, name string, args ...string) Handler {
|
||||
return fn(ctx, name, args...)
|
||||
}
|
||||
@@ -0,0 +1,297 @@
|
||||
package telsh
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-oi"
|
||||
"github.com/reiver/go-telnet"
|
||||
|
||||
"bytes"
|
||||
"io"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
|
||||
const (
|
||||
defaultExitCommandName = "exit"
|
||||
defaultPrompt = "§ "
|
||||
defaultWelcomeMessage = "\r\nWelcome!\r\n"
|
||||
defaultExitMessage = "\r\nGoodbye!\r\n"
|
||||
)
|
||||
|
||||
|
||||
type ShellHandler struct {
|
||||
muxtex sync.RWMutex
|
||||
producers map[string]Producer
|
||||
elseProducer Producer
|
||||
|
||||
ExitCommandName string
|
||||
Prompt string
|
||||
WelcomeMessage string
|
||||
ExitMessage string
|
||||
}
|
||||
|
||||
|
||||
func NewShellHandler() *ShellHandler {
|
||||
producers := map[string]Producer{}
|
||||
|
||||
telnetHandler := ShellHandler{
|
||||
producers:producers,
|
||||
|
||||
Prompt: defaultPrompt,
|
||||
ExitCommandName: defaultExitCommandName,
|
||||
WelcomeMessage: defaultWelcomeMessage,
|
||||
ExitMessage: defaultExitMessage,
|
||||
}
|
||||
|
||||
return &telnetHandler
|
||||
}
|
||||
|
||||
|
||||
func (telnetHandler *ShellHandler) Register(name string, producer Producer) error {
|
||||
|
||||
telnetHandler.muxtex.Lock()
|
||||
telnetHandler.producers[name] = producer
|
||||
telnetHandler.muxtex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (telnetHandler *ShellHandler) MustRegister(name string, producer Producer) *ShellHandler {
|
||||
if err := telnetHandler.Register(name, producer); nil != err {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return telnetHandler
|
||||
}
|
||||
|
||||
|
||||
func (telnetHandler *ShellHandler) RegisterHandlerFunc(name string, handlerFunc HandlerFunc) error {
|
||||
|
||||
produce := func(ctx telnet.Context, name string, args ...string) Handler {
|
||||
return PromoteHandlerFunc(handlerFunc, args...)
|
||||
}
|
||||
|
||||
producer := ProducerFunc(produce)
|
||||
|
||||
return telnetHandler.Register(name, producer)
|
||||
}
|
||||
|
||||
func (telnetHandler *ShellHandler) MustRegisterHandlerFunc(name string, handlerFunc HandlerFunc) *ShellHandler {
|
||||
if err := telnetHandler.RegisterHandlerFunc(name, handlerFunc); nil != err {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return telnetHandler
|
||||
}
|
||||
|
||||
|
||||
func (telnetHandler *ShellHandler) RegisterElse(producer Producer) error {
|
||||
|
||||
telnetHandler.muxtex.Lock()
|
||||
telnetHandler.elseProducer = producer
|
||||
telnetHandler.muxtex.Unlock()
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (telnetHandler *ShellHandler) MustRegisterElse(producer Producer) *ShellHandler {
|
||||
if err := telnetHandler.RegisterElse(producer); nil != err {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
return telnetHandler
|
||||
}
|
||||
|
||||
|
||||
func (telnetHandler *ShellHandler) ServeTELNET(ctx telnet.Context, writer telnet.Writer, reader telnet.Reader) {
|
||||
|
||||
logger := ctx.Logger()
|
||||
if nil == logger {
|
||||
logger = internalDiscardLogger{}
|
||||
}
|
||||
|
||||
|
||||
colonSpaceCommandNotFoundEL := []byte(": command not found\r\n")
|
||||
|
||||
|
||||
var prompt bytes.Buffer
|
||||
var exitCommandName string
|
||||
var welcomeMessage string
|
||||
var exitMessage string
|
||||
|
||||
prompt.WriteString(telnetHandler.Prompt)
|
||||
|
||||
promptBytes := prompt.Bytes()
|
||||
|
||||
exitCommandName = telnetHandler.ExitCommandName
|
||||
welcomeMessage = telnetHandler.WelcomeMessage
|
||||
exitMessage = telnetHandler.ExitMessage
|
||||
|
||||
|
||||
if _, err := oi.LongWriteString(writer, welcomeMessage); nil != err {
|
||||
logger.Errorf("Problem long writing welcome message: %v", err)
|
||||
return
|
||||
}
|
||||
logger.Debugf("Wrote welcome message: %q.", welcomeMessage)
|
||||
if _, err := oi.LongWrite(writer, promptBytes); nil != err {
|
||||
logger.Errorf("Problem long writing prompt: %v", err)
|
||||
return
|
||||
}
|
||||
logger.Debugf("Wrote prompt: %q.", promptBytes)
|
||||
|
||||
|
||||
var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up.
|
||||
p := buffer[:]
|
||||
|
||||
var line bytes.Buffer
|
||||
|
||||
for {
|
||||
// Read 1 byte.
|
||||
n, err := reader.Read(p)
|
||||
if n <= 0 && nil == err {
|
||||
continue
|
||||
} else if n <= 0 && nil != err {
|
||||
break
|
||||
}
|
||||
|
||||
|
||||
line.WriteByte(p[0])
|
||||
//logger.Tracef("Received: %q (%d).", p[0], p[0])
|
||||
|
||||
|
||||
if '\n' == p[0] {
|
||||
lineString := line.String()
|
||||
|
||||
if "\r\n" == lineString {
|
||||
line.Reset()
|
||||
if _, err := oi.LongWrite(writer, promptBytes); nil != err {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
//@TODO: support piping.
|
||||
fields := strings.Fields(lineString)
|
||||
logger.Debugf("Have %d tokens.", len(fields))
|
||||
logger.Tracef("Tokens: %v", fields)
|
||||
if len(fields) <= 0 {
|
||||
line.Reset()
|
||||
if _, err := oi.LongWrite(writer, promptBytes); nil != err {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
field0 := fields[0]
|
||||
|
||||
if exitCommandName == field0 {
|
||||
oi.LongWriteString(writer, exitMessage)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
var producer Producer
|
||||
|
||||
telnetHandler.muxtex.RLock()
|
||||
var ok bool
|
||||
producer, ok = telnetHandler.producers[field0]
|
||||
telnetHandler.muxtex.RUnlock()
|
||||
|
||||
if !ok {
|
||||
telnetHandler.muxtex.RLock()
|
||||
producer = telnetHandler.elseProducer
|
||||
telnetHandler.muxtex.RUnlock()
|
||||
}
|
||||
|
||||
if nil == producer {
|
||||
//@TODO: Don't convert that to []byte! think this creates "garbage" (for collector).
|
||||
oi.LongWrite(writer, []byte(field0))
|
||||
oi.LongWrite(writer, colonSpaceCommandNotFoundEL)
|
||||
line.Reset()
|
||||
if _, err := oi.LongWrite(writer, promptBytes); nil != err {
|
||||
return
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
handler := producer.Produce(ctx, field0, fields[1:]...)
|
||||
if nil == handler {
|
||||
oi.LongWrite(writer, []byte(field0))
|
||||
//@TODO: Need to use a different error message.
|
||||
oi.LongWrite(writer, colonSpaceCommandNotFoundEL)
|
||||
line.Reset()
|
||||
oi.LongWrite(writer, promptBytes)
|
||||
continue
|
||||
}
|
||||
|
||||
//@TODO: Wire up the stdin, stdout, stderr of the handler.
|
||||
|
||||
if stdoutPipe, err := handler.StdoutPipe(); nil != err {
|
||||
//@TODO:
|
||||
} else if nil == stdoutPipe {
|
||||
//@TODO:
|
||||
} else {
|
||||
connect(ctx, writer, stdoutPipe)
|
||||
}
|
||||
|
||||
|
||||
if stderrPipe, err := handler.StderrPipe(); nil != err {
|
||||
//@TODO:
|
||||
} else if nil == stderrPipe {
|
||||
//@TODO:
|
||||
} else {
|
||||
connect(ctx, writer, stderrPipe)
|
||||
}
|
||||
|
||||
|
||||
if err := handler.Run(); nil != err {
|
||||
//@TODO:
|
||||
}
|
||||
line.Reset()
|
||||
if _, err := oi.LongWrite(writer, promptBytes); nil != err {
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//@TODO: Are there any special errors we should be dealing with separately?
|
||||
if nil != err {
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
oi.LongWriteString(writer, exitMessage)
|
||||
return
|
||||
}
|
||||
|
||||
|
||||
|
||||
func connect(ctx telnet.Context, writer io.Writer, reader io.Reader) {
|
||||
|
||||
logger := ctx.Logger()
|
||||
|
||||
go func(logger telnet.Logger){
|
||||
|
||||
var buffer [1]byte // Seems like the length of the buffer needs to be small, otherwise will have to wait for buffer to fill up.
|
||||
p := buffer[:]
|
||||
|
||||
for {
|
||||
// Read 1 byte.
|
||||
n, err := reader.Read(p)
|
||||
if n <= 0 && nil == err {
|
||||
continue
|
||||
} else if n <= 0 && nil != err {
|
||||
break
|
||||
}
|
||||
|
||||
//logger.Tracef("Sending: %q.", p)
|
||||
//@TODO: Should we be checking for errors?
|
||||
oi.LongWrite(writer, p)
|
||||
//logger.Tracef("Sent: %q.", p)
|
||||
}
|
||||
}(logger)
|
||||
}
|
||||
@@ -0,0 +1,110 @@
|
||||
package telsh
|
||||
|
||||
|
||||
import (
|
||||
"github.com/reiver/go-telnet"
|
||||
|
||||
"bytes"
|
||||
"strings"
|
||||
|
||||
"testing"
|
||||
)
|
||||
|
||||
|
||||
func TestServeTELNETCommandNotFound(t *testing.T) {
|
||||
|
||||
tests := []struct{
|
||||
ClientSends string
|
||||
Expected string
|
||||
}{
|
||||
{
|
||||
ClientSends: "\r\n",
|
||||
Expected: "",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
ClientSends: "apple\r\n",
|
||||
Expected: "apple: command not found\r\n",
|
||||
},
|
||||
{
|
||||
ClientSends: "banana\r\n",
|
||||
Expected: "banana: command not found\r\n",
|
||||
},
|
||||
{
|
||||
ClientSends: "cherry\r\n",
|
||||
Expected: "cherry: command not found\r\n",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
ClientSends: "\t\r\n",
|
||||
Expected: "",
|
||||
},
|
||||
{
|
||||
ClientSends: "\t\t\r\n",
|
||||
Expected: "",
|
||||
},
|
||||
{
|
||||
ClientSends: "\t\t\t\r\n",
|
||||
Expected: "",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
ClientSends: " \r\n",
|
||||
Expected: "",
|
||||
},
|
||||
{
|
||||
ClientSends: " \r\n",
|
||||
Expected: "",
|
||||
},
|
||||
{
|
||||
ClientSends: " \r\n",
|
||||
Expected: "",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
ClientSends: " \t\r\n",
|
||||
Expected: "",
|
||||
},
|
||||
{
|
||||
ClientSends: "\t \r\n",
|
||||
Expected: "",
|
||||
},
|
||||
|
||||
|
||||
|
||||
{
|
||||
ClientSends: "ls -alF\r\n",
|
||||
Expected: "ls: command not found\r\n",
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
for testNumber, test := range tests {
|
||||
|
||||
shellHandler := NewShellHandler()
|
||||
if nil == shellHandler {
|
||||
t.Errorf("For test #%d, did not expect to get nil, but actually got it: %v; for client sent: %q", testNumber, shellHandler, test.ClientSends)
|
||||
continue
|
||||
}
|
||||
|
||||
|
||||
ctx := telnet.NewContext()
|
||||
|
||||
var buffer bytes.Buffer
|
||||
|
||||
shellHandler.ServeTELNET(ctx, &buffer, strings.NewReader(test.ClientSends))
|
||||
|
||||
if expected, actual := shellHandler.WelcomeMessage+shellHandler.Prompt+test.Expected+shellHandler.Prompt+shellHandler.ExitMessage, buffer.String(); expected != actual {
|
||||
t.Errorf("For test #%d, expect %q, but actually got %q; for client sent: %q", testNumber, expected, actual, test.ClientSends)
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user