wrappers.go (162 lines of code) (raw):
package terminal
import (
"encoding/base64"
"io"
"net"
"sync"
"time"
"github.com/gorilla/websocket"
"golang.org/x/text/encoding"
"golang.org/x/text/encoding/unicode"
)
func Wrap(conn Connection, subprotocol string) Connection {
switch subprotocol {
case "channel.k8s.io":
return &kubeWrapper{base64: false, conn: conn}
case "base64.channel.k8s.io":
return &kubeWrapper{base64: true, conn: conn}
case "terminal.gitlab.com":
return &gitlabWrapper{base64: false, conn: conn}
case "base64.terminal.gitlab.com":
return &gitlabWrapper{base64: true, conn: conn}
}
return conn
}
func NewIOWrapper(conn Connection) *ioWrapper {
return &ioWrapper{
Connection: conn,
messageType: websocket.BinaryMessage,
encoder: unicode.UTF8.NewEncoder(),
decoder: unicode.UTF8.NewDecoder(),
}
}
type kubeWrapper struct {
base64 bool
conn Connection
}
type gitlabWrapper struct {
base64 bool
conn Connection
}
type ioWrapper struct {
Connection
mu sync.Mutex
messageType int
encoder *encoding.Encoder
decoder *encoding.Decoder
}
func (w *gitlabWrapper) ReadMessage() (int, []byte, error) {
mt, data, err := w.conn.ReadMessage()
if err != nil {
return mt, data, err
}
if isData(mt) {
mt = websocket.BinaryMessage
if w.base64 {
data, err = decodeBase64(data)
if err != nil {
}
}
}
return mt, data, err
}
func (w *gitlabWrapper) WriteMessage(mt int, data []byte) error {
if isData(mt) {
if w.base64 {
mt = websocket.TextMessage
data = encodeBase64(data)
} else {
mt = websocket.BinaryMessage
}
}
return w.conn.WriteMessage(mt, data)
}
func (w *gitlabWrapper) WriteControl(mt int, data []byte, deadline time.Time) error {
return w.conn.WriteControl(mt, data, deadline)
}
func (w *gitlabWrapper) Close() error {
return w.conn.UnderlyingConn().Close()
}
func (w *gitlabWrapper) UnderlyingConn() net.Conn {
return w.conn.UnderlyingConn()
}
// Coalesces all wsstreams into a single stream. In practice, we should only
// receive data on stream 1.
func (w *kubeWrapper) ReadMessage() (int, []byte, error) {
mt, data, err := w.conn.ReadMessage()
if err != nil {
return mt, data, err
}
if isData(mt) {
mt = websocket.BinaryMessage
// Remove the WSStream channel number, decode to raw
if len(data) > 0 {
data = data[1:]
if w.base64 {
data, err = decodeBase64(data)
}
}
}
return mt, data, err
}
// Always sends to wsstream 0
func (w *kubeWrapper) WriteMessage(mt int, data []byte) error {
if isData(mt) {
if w.base64 {
mt = websocket.TextMessage
data = append([]byte{'0'}, encodeBase64(data)...)
} else {
mt = websocket.BinaryMessage
data = append([]byte{0}, data...)
}
}
return w.conn.WriteMessage(mt, data)
}
func (w *kubeWrapper) WriteControl(mt int, data []byte, deadline time.Time) error {
return w.conn.WriteControl(mt, data, deadline)
}
func (w *kubeWrapper) UnderlyingConn() net.Conn {
return w.conn.UnderlyingConn()
}
// encodes the given data as utf-8 and writes it to the websocket
func (w *ioWrapper) Write(data []byte) (n int, err error) {
n = len(data)
w.mu.Lock()
mt := w.messageType
w.mu.Unlock()
if mt != websocket.BinaryMessage {
utf8, err := w.encoder.String(string(data))
if err != nil {
return 0, err
}
data = []byte(utf8)
}
err = w.WriteMessage(mt, data)
return n, err
}
// decodes utf-8 encoded data from the websocket
func (w *ioWrapper) Read(out []byte) (n int, err error) {
mt, data, err := w.ReadMessage()
if mt != websocket.BinaryMessage {
switch err {
case nil:
data, err = w.decoder.Bytes(data)
case io.EOF:
return 0, io.EOF
}
}
if err != nil {
return 0, err
}
w.mu.Lock()
w.messageType = mt
w.mu.Unlock()
return copy(out, data), nil
}
func isData(mt int) bool {
return mt == websocket.BinaryMessage || mt == websocket.TextMessage
}
func encodeBase64(data []byte) []byte {
buf := make([]byte, base64.StdEncoding.EncodedLen(len(data)))
base64.StdEncoding.Encode(buf, data)
return buf
}
func decodeBase64(data []byte) ([]byte, error) {
buf := make([]byte, base64.StdEncoding.DecodedLen(len(data)))
n, err := base64.StdEncoding.Decode(buf, data)
return buf[:n], err
}