-
Notifications
You must be signed in to change notification settings - Fork 0
/
dialer_utils.go
223 lines (196 loc) · 6.58 KB
/
dialer_utils.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
package websocket
import (
"encoding/base64"
"errors"
"net/http"
"net/url"
"regexp"
"strings"
)
// validateResponse is used to determine whether the servers handshake request
// conforms with the WebSocket spec. When it doesn't the client fails the
// websocket connection.
//
// Ref Spec: https://tools.ietf.org/html/rfc6455#section-4.1
func validateResponse(r *http.Response) *OpenError {
validations := []func(*http.Response) *OpenError{
validateResponseStatus,
validateResponseUpgradeHeader,
validateResponseConnectionHeader,
validateResponseSecWebsocketAcceptHeader,
}
for _, v := range validations {
if err := v(r); err != nil {
return err
}
}
return nil
}
// validateResponseStatus verifies that status code of the server's opening
// handshake response is '101'. If it is not, it means that the handshake has
// been rejected and thus the endpoints are still communicating using http.
//
// Ref Spec: https://tools.ietf.org/html/rfc6455#section-4.1
func validateResponseStatus(r *http.Response) *OpenError {
if r.StatusCode != 101 {
return &OpenError{
Reason: "http status not 101",
}
}
return nil
}
// validateResponseUpgradeHeader verifies that the Upgrade HTTP Header value
// in the servers's opening handshake response is "websocket".
//
// Ref Spec: https://tools.ietf.org/html/rfc6455#section-4.1
func validateResponseUpgradeHeader(r *http.Response) *OpenError {
if strings.ToLower(r.Header.Get("Upgrade")) != "websocket" {
return &OpenError{
Reason: `"Upgrade" Header should have the value of "websocket"`,
}
}
return nil
}
// validateResponseConnectionHeader verifies that the Connection HTTP Header
// value in the servers's opening handshake response is "upgrade".
//
// Ref Spec: https://tools.ietf.org/html/rfc6455#section-4.1
func validateResponseConnectionHeader(r *http.Response) *OpenError {
if strings.ToLower(r.Header.Get("Connection")) != "upgrade" {
return &OpenError{
Reason: `"Connection" Header should have the value of "upgrade"`,
}
}
return nil
}
// validateResponseSecWebsocketAcceptHeader verifies that the
// Sec-WebSocket-Accept HTTP Header value in the server's opening handshake
// response is the base64-encoded SHA-1 of the concatenation of the
// Sec-WebSocket-Key value (sent with the opening handshake request) (as a
// string, not base64-decoded) with the websocket accept key.
//
// Ref Spec: https://tools.ietf.org/html/rfc6455#section-4.1
func validateResponseSecWebsocketAcceptHeader(r *http.Response) *OpenError {
if r.Header.Get("Sec-WebSocket-Accept") != makeAcceptKey(r.Request.Header.Get("Sec-Websocket-Key")) {
return &OpenError{
Reason: `challenge key failure`,
}
}
return nil
}
// validateResponseSecWebsocketProtocol verifies that the sub protocol the
// server has agreed to use (Sec-WebSocket-Protocol Header) was in the list the
// client has sent in the opening handshake request.
//
// Ref Spec: https://tools.ietf.org/html/rfc6455#section-4.1
func validateResponseSecWebsocketProtocol(r *http.Response) *OpenError {
// Sub protocols sent by the client.
c := headerToSlice(r.Request.Header.Get("Sec-WebSocket-Protocol"))
// Sub protocol the server has agreed to use.
s := r.Header.Get("Sec-WebSocket-Protocol")
// If the server hasn't agreed to use anything, stop process.
if len(s) == 0 {
return nil
}
// Loop through the lists of sub protocols the client has sent in its
// opening handshake request and if the sub protocol the server argeed to
// use is found stop the process.
for _, cv := range c {
if cv == s {
return nil
}
}
// At this point the server has agreed to use a sub protocol which the
// client doesn't support and thus return an error.
return &OpenError{
Reason: `server choose a sub protocol which was not in the list sent by the client`,
}
}
// makeChallengeKey is used to generate the key to be sent with the client's
// opening handshake using the Sec-Websocket-Key header field.
//
// Ref Spec: https://tools.ietf.org/html/rfc6455#section-4.1
func makeChallengeKey() string {
// return Base64 encode version of the byte generated.
return base64.StdEncoding.EncodeToString(randomByteSlice(4))
}
// parseURL is used to parse the URL string provided and verifies that it
// conforms with the websocket spec. If it does it will create and return a URL
// instance representing the URL string provided.
//
// Ref Spec: https://tools.ietf.org/html/rfc6455#section-3
func parseURL(u string) (*url.URL, error) {
// Parse scheme.
if err := parseURLScheme(&u); err != nil {
return nil, err
}
// Create URL Instance.
l, err := url.Parse(u)
if err != nil {
return nil, err
}
// Parse Host.
if err := parseURLHost(l); err != nil {
return nil, err
}
return l, nil
}
// parseURLScheme is used to parse the Scheme portion of a URL string. If the
// scheme provided is not a valid websocket scheme an error is returned. If no
// scheme is given it will be defaulted to "ws".
//
// Ref Spec: https://tools.ietf.org/html/rfc6455#section-3
func parseURLScheme(u *string) error {
// Regex to retrieve Scheme portion of a URL string.
re := regexp.MustCompile("^([a-zA-Z]+)://")
m := re.FindStringSubmatch(*u)
// If m is smaller than 2 it means that the user hasn't provided one and
// thus the default sheme (ws) is used.
if len(m) < 2 {
*u = "ws://" + *u
return nil
}
// If a sheme was captured, make sure it is valid.
if !schemeValid(m[1]) {
return errors.New("invalid scheme: " + m[1])
}
return nil
}
// parseURLHost is used to parse the Host portion of a URL instance to
// determine whether it has a port or not. When no port is found this method
// will assign a port based on the URL instance scheme (ws = 22, wss = 443). If
// the scheme is not a valid scheme for websocket an error is returned.
//
// Ref Spec: https://tools.ietf.org/html/rfc6455#section-3
func parseURLHost(u *url.URL) error {
// If scheme is invalid throw an error
if !schemeValid(u.Scheme) {
return errors.New("invalid scheme: " + u.Scheme)
}
// Regex to retrieve the Port portion of the URL.
re := regexp.MustCompile(":(\\d+)")
m := re.FindStringSubmatch(u.Host)
// If the length of m is greater than or equals to 2 it means that there is
// a submatch, meaning that the user has provided a port and thus there is
// no need to include the default ports.
if len(m) >= 2 {
return nil
}
// Based on the scheme, set the port.
switch u.Scheme {
case "ws":
{
u.Host += ":22"
}
case "wss":
{
u.Host += ":443"
}
}
return nil
}
// schemeValid is used to determine whether the scheme provided is a valid
// scheme for the websocket protocol.
func schemeValid(s string) bool {
return s == "ws" || s == "wss"
}