mirror of
https://github.com/moby/moby.git
synced 2022-11-09 12:21:53 -05:00
8dd1490473
Currently, the escapeProxy works under the assumption that the underlying reader will always return 1 byte at a time. Even though this is usually true, it is not always the case, for example when using a pty and writing multiple bytes to the master before flushing it. In such cases the proxy reader doesn't work properly. For example with an escape sequence being `ctrl-p,ctrl-q`, when the underlying reader returns `ctrl-p,ctrl-q` at once, the escape sequence isn't detected. This updates the reader to support this use-case and adds unit tests. Signed-off-by: Bilal Amarni <bilal.amarni@gmail.com>
88 lines
2.3 KiB
Go
88 lines
2.3 KiB
Go
package term // import "github.com/docker/docker/pkg/term"
|
|
|
|
import (
|
|
"io"
|
|
)
|
|
|
|
// EscapeError is special error which returned by a TTY proxy reader's Read()
|
|
// method in case its detach escape sequence is read.
|
|
type EscapeError struct{}
|
|
|
|
func (EscapeError) Error() string {
|
|
return "read escape sequence"
|
|
}
|
|
|
|
// escapeProxy is used only for attaches with a TTY. It is used to proxy
|
|
// stdin keypresses from the underlying reader and look for the passed in
|
|
// escape key sequence to signal a detach.
|
|
type escapeProxy struct {
|
|
escapeKeys []byte
|
|
escapeKeyPos int
|
|
r io.Reader
|
|
buf []byte
|
|
}
|
|
|
|
// NewEscapeProxy returns a new TTY proxy reader which wraps the given reader
|
|
// and detects when the specified escape keys are read, in which case the Read
|
|
// method will return an error of type EscapeError.
|
|
func NewEscapeProxy(r io.Reader, escapeKeys []byte) io.Reader {
|
|
return &escapeProxy{
|
|
escapeKeys: escapeKeys,
|
|
r: r,
|
|
}
|
|
}
|
|
|
|
func (r *escapeProxy) Read(buf []byte) (n int, err error) {
|
|
if len(r.escapeKeys) > 0 && r.escapeKeyPos == len(r.escapeKeys) {
|
|
return 0, EscapeError{}
|
|
}
|
|
|
|
if len(r.buf) > 0 {
|
|
n = copy(buf, r.buf)
|
|
r.buf = r.buf[n:]
|
|
}
|
|
|
|
nr, err := r.r.Read(buf[n:])
|
|
n += nr
|
|
if len(r.escapeKeys) == 0 {
|
|
return n, err
|
|
}
|
|
|
|
for i := 0; i < n; i++ {
|
|
if buf[i] == r.escapeKeys[r.escapeKeyPos] {
|
|
r.escapeKeyPos++
|
|
|
|
// Check if the full escape sequence is matched.
|
|
if r.escapeKeyPos == len(r.escapeKeys) {
|
|
n = i + 1 - r.escapeKeyPos
|
|
if n < 0 {
|
|
n = 0
|
|
}
|
|
return n, EscapeError{}
|
|
}
|
|
continue
|
|
}
|
|
|
|
// If we need to prepend a partial escape sequence from the previous
|
|
// read, make sure the new buffer size doesn't exceed len(buf).
|
|
// Otherwise, preserve any extra data in a buffer for the next read.
|
|
if i < r.escapeKeyPos {
|
|
preserve := make([]byte, 0, r.escapeKeyPos+n)
|
|
preserve = append(preserve, r.escapeKeys[:r.escapeKeyPos]...)
|
|
preserve = append(preserve, buf[:n]...)
|
|
n = copy(buf, preserve)
|
|
i += r.escapeKeyPos
|
|
r.buf = append(r.buf, preserve[n:]...)
|
|
}
|
|
r.escapeKeyPos = 0
|
|
}
|
|
|
|
// If we're in the middle of reading an escape sequence, make sure we don't
|
|
// let the caller read it. If later on we find that this is not the escape
|
|
// sequence, we'll prepend it back to buf.
|
|
n -= r.escapeKeyPos
|
|
if n < 0 {
|
|
n = 0
|
|
}
|
|
return n, err
|
|
}
|