1/*
2   Copyright The containerd Authors.
3
4   Licensed under the Apache License, Version 2.0 (the "License");
5   you may not use this file except in compliance with the License.
6   You may obtain a copy of the License at
7
8       http://www.apache.org/licenses/LICENSE-2.0
9
10   Unless required by applicable law or agreed to in writing, software
11   distributed under the License is distributed on an "AS IS" BASIS,
12   WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13   See the License for the specific language governing permissions and
14   limitations under the License.
15*/
16
17/*
18Copyright 2016 The Kubernetes Authors.
19
20Licensed under the Apache License, Version 2.0 (the "License");
21you may not use this file except in compliance with the License.
22You may obtain a copy of the License at
23
24    http://www.apache.org/licenses/LICENSE-2.0
25
26Unless required by applicable law or agreed to in writing, software
27distributed under the License is distributed on an "AS IS" BASIS,
28WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
29See the License for the specific language governing permissions and
30limitations under the License.
31*/
32
33package remotecommand
34
35import (
36	"fmt"
37	"net/http"
38	"time"
39
40	"k8s.io/apimachinery/pkg/util/runtime"
41	"k8s.io/apiserver/pkg/server/httplog"
42	"k8s.io/apiserver/pkg/util/wsstream"
43)
44
45const (
46	stdinChannel = iota
47	stdoutChannel
48	stderrChannel
49	errorChannel
50	resizeChannel
51
52	preV4BinaryWebsocketProtocol = wsstream.ChannelWebSocketProtocol
53	preV4Base64WebsocketProtocol = wsstream.Base64ChannelWebSocketProtocol
54	v4BinaryWebsocketProtocol    = "v4." + wsstream.ChannelWebSocketProtocol
55	v4Base64WebsocketProtocol    = "v4." + wsstream.Base64ChannelWebSocketProtocol
56)
57
58// createChannels returns the standard channel types for a shell connection (STDIN 0, STDOUT 1, STDERR 2)
59// along with the approximate duplex value. It also creates the error (3) and resize (4) channels.
60func createChannels(opts *Options) []wsstream.ChannelType {
61	// open the requested channels, and always open the error channel
62	channels := make([]wsstream.ChannelType, 5)
63	channels[stdinChannel] = readChannel(opts.Stdin)
64	channels[stdoutChannel] = writeChannel(opts.Stdout)
65	channels[stderrChannel] = writeChannel(opts.Stderr)
66	channels[errorChannel] = wsstream.WriteChannel
67	channels[resizeChannel] = wsstream.ReadChannel
68	return channels
69}
70
71// readChannel returns wsstream.ReadChannel if real is true, or wsstream.IgnoreChannel.
72func readChannel(real bool) wsstream.ChannelType {
73	if real {
74		return wsstream.ReadChannel
75	}
76	return wsstream.IgnoreChannel
77}
78
79// writeChannel returns wsstream.WriteChannel if real is true, or wsstream.IgnoreChannel.
80func writeChannel(real bool) wsstream.ChannelType {
81	if real {
82		return wsstream.WriteChannel
83	}
84	return wsstream.IgnoreChannel
85}
86
87// createWebSocketStreams returns a context containing the websocket connection and
88// streams needed to perform an exec or an attach.
89func createWebSocketStreams(req *http.Request, w http.ResponseWriter, opts *Options, idleTimeout time.Duration) (*context, bool) {
90	channels := createChannels(opts)
91	conn := wsstream.NewConn(map[string]wsstream.ChannelProtocolConfig{
92		"": {
93			Binary:   true,
94			Channels: channels,
95		},
96		preV4BinaryWebsocketProtocol: {
97			Binary:   true,
98			Channels: channels,
99		},
100		preV4Base64WebsocketProtocol: {
101			Binary:   false,
102			Channels: channels,
103		},
104		v4BinaryWebsocketProtocol: {
105			Binary:   true,
106			Channels: channels,
107		},
108		v4Base64WebsocketProtocol: {
109			Binary:   false,
110			Channels: channels,
111		},
112	})
113	conn.SetIdleTimeout(idleTimeout)
114	negotiatedProtocol, streams, err := conn.Open(httplog.Unlogged(req, w), req)
115	if err != nil {
116		runtime.HandleError(fmt.Errorf("unable to upgrade websocket connection: %v", err))
117		return nil, false
118	}
119
120	// Send an empty message to the lowest writable channel to notify the client the connection is established
121	// TODO: make generic to SPDY and WebSockets and do it outside of this method?
122	switch {
123	case opts.Stdout:
124		streams[stdoutChannel].Write([]byte{})
125	case opts.Stderr:
126		streams[stderrChannel].Write([]byte{})
127	default:
128		streams[errorChannel].Write([]byte{})
129	}
130
131	ctx := &context{
132		conn:         conn,
133		stdinStream:  streams[stdinChannel],
134		stdoutStream: streams[stdoutChannel],
135		stderrStream: streams[stderrChannel],
136		tty:          opts.TTY,
137		resizeStream: streams[resizeChannel],
138	}
139
140	switch negotiatedProtocol {
141	case v4BinaryWebsocketProtocol, v4Base64WebsocketProtocol:
142		ctx.writeStatus = v4WriteStatusFunc(streams[errorChannel])
143	default:
144		ctx.writeStatus = v1WriteStatusFunc(streams[errorChannel])
145	}
146
147	return ctx, true
148}
149