|
1 // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. |
|
2 // Use of this source code is governed by a BSD-style |
|
3 // license that can be found in the LICENSE file. |
|
4 |
|
5 package websocket |
|
6 |
|
7 import ( |
|
8 "crypto/rand" |
|
9 "crypto/sha1" |
|
10 "encoding/base64" |
|
11 "io" |
|
12 "net/http" |
|
13 "strings" |
|
14 "unicode/utf8" |
|
15 ) |
|
16 |
|
17 var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") |
|
18 |
|
19 func computeAcceptKey(challengeKey string) string { |
|
20 h := sha1.New() |
|
21 h.Write([]byte(challengeKey)) |
|
22 h.Write(keyGUID) |
|
23 return base64.StdEncoding.EncodeToString(h.Sum(nil)) |
|
24 } |
|
25 |
|
26 func generateChallengeKey() (string, error) { |
|
27 p := make([]byte, 16) |
|
28 if _, err := io.ReadFull(rand.Reader, p); err != nil { |
|
29 return "", err |
|
30 } |
|
31 return base64.StdEncoding.EncodeToString(p), nil |
|
32 } |
|
33 |
|
34 // Octet types from RFC 2616. |
|
35 var octetTypes [256]byte |
|
36 |
|
37 const ( |
|
38 isTokenOctet = 1 << iota |
|
39 isSpaceOctet |
|
40 ) |
|
41 |
|
42 func init() { |
|
43 // From RFC 2616 |
|
44 // |
|
45 // OCTET = <any 8-bit sequence of data> |
|
46 // CHAR = <any US-ASCII character (octets 0 - 127)> |
|
47 // CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)> |
|
48 // CR = <US-ASCII CR, carriage return (13)> |
|
49 // LF = <US-ASCII LF, linefeed (10)> |
|
50 // SP = <US-ASCII SP, space (32)> |
|
51 // HT = <US-ASCII HT, horizontal-tab (9)> |
|
52 // <"> = <US-ASCII double-quote mark (34)> |
|
53 // CRLF = CR LF |
|
54 // LWS = [CRLF] 1*( SP | HT ) |
|
55 // TEXT = <any OCTET except CTLs, but including LWS> |
|
56 // separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <"> |
|
57 // | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT |
|
58 // token = 1*<any CHAR except CTLs or separators> |
|
59 // qdtext = <any TEXT except <">> |
|
60 |
|
61 for c := 0; c < 256; c++ { |
|
62 var t byte |
|
63 isCtl := c <= 31 || c == 127 |
|
64 isChar := 0 <= c && c <= 127 |
|
65 isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0 |
|
66 if strings.IndexRune(" \t\r\n", rune(c)) >= 0 { |
|
67 t |= isSpaceOctet |
|
68 } |
|
69 if isChar && !isCtl && !isSeparator { |
|
70 t |= isTokenOctet |
|
71 } |
|
72 octetTypes[c] = t |
|
73 } |
|
74 } |
|
75 |
|
76 func skipSpace(s string) (rest string) { |
|
77 i := 0 |
|
78 for ; i < len(s); i++ { |
|
79 if octetTypes[s[i]]&isSpaceOctet == 0 { |
|
80 break |
|
81 } |
|
82 } |
|
83 return s[i:] |
|
84 } |
|
85 |
|
86 func nextToken(s string) (token, rest string) { |
|
87 i := 0 |
|
88 for ; i < len(s); i++ { |
|
89 if octetTypes[s[i]]&isTokenOctet == 0 { |
|
90 break |
|
91 } |
|
92 } |
|
93 return s[:i], s[i:] |
|
94 } |
|
95 |
|
96 func nextTokenOrQuoted(s string) (value string, rest string) { |
|
97 if !strings.HasPrefix(s, "\"") { |
|
98 return nextToken(s) |
|
99 } |
|
100 s = s[1:] |
|
101 for i := 0; i < len(s); i++ { |
|
102 switch s[i] { |
|
103 case '"': |
|
104 return s[:i], s[i+1:] |
|
105 case '\\': |
|
106 p := make([]byte, len(s)-1) |
|
107 j := copy(p, s[:i]) |
|
108 escape := true |
|
109 for i = i + 1; i < len(s); i++ { |
|
110 b := s[i] |
|
111 switch { |
|
112 case escape: |
|
113 escape = false |
|
114 p[j] = b |
|
115 j++ |
|
116 case b == '\\': |
|
117 escape = true |
|
118 case b == '"': |
|
119 return string(p[:j]), s[i+1:] |
|
120 default: |
|
121 p[j] = b |
|
122 j++ |
|
123 } |
|
124 } |
|
125 return "", "" |
|
126 } |
|
127 } |
|
128 return "", "" |
|
129 } |
|
130 |
|
131 // equalASCIIFold returns true if s is equal to t with ASCII case folding. |
|
132 func equalASCIIFold(s, t string) bool { |
|
133 for s != "" && t != "" { |
|
134 sr, size := utf8.DecodeRuneInString(s) |
|
135 s = s[size:] |
|
136 tr, size := utf8.DecodeRuneInString(t) |
|
137 t = t[size:] |
|
138 if sr == tr { |
|
139 continue |
|
140 } |
|
141 if 'A' <= sr && sr <= 'Z' { |
|
142 sr = sr + 'a' - 'A' |
|
143 } |
|
144 if 'A' <= tr && tr <= 'Z' { |
|
145 tr = tr + 'a' - 'A' |
|
146 } |
|
147 if sr != tr { |
|
148 return false |
|
149 } |
|
150 } |
|
151 return s == t |
|
152 } |
|
153 |
|
154 // tokenListContainsValue returns true if the 1#token header with the given |
|
155 // name contains a token equal to value with ASCII case folding. |
|
156 func tokenListContainsValue(header http.Header, name string, value string) bool { |
|
157 headers: |
|
158 for _, s := range header[name] { |
|
159 for { |
|
160 var t string |
|
161 t, s = nextToken(skipSpace(s)) |
|
162 if t == "" { |
|
163 continue headers |
|
164 } |
|
165 s = skipSpace(s) |
|
166 if s != "" && s[0] != ',' { |
|
167 continue headers |
|
168 } |
|
169 if equalASCIIFold(t, value) { |
|
170 return true |
|
171 } |
|
172 if s == "" { |
|
173 continue headers |
|
174 } |
|
175 s = s[1:] |
|
176 } |
|
177 } |
|
178 return false |
|
179 } |
|
180 |
|
181 // parseExtensions parses WebSocket extensions from a header. |
|
182 func parseExtensions(header http.Header) []map[string]string { |
|
183 // From RFC 6455: |
|
184 // |
|
185 // Sec-WebSocket-Extensions = extension-list |
|
186 // extension-list = 1#extension |
|
187 // extension = extension-token *( ";" extension-param ) |
|
188 // extension-token = registered-token |
|
189 // registered-token = token |
|
190 // extension-param = token [ "=" (token | quoted-string) ] |
|
191 // ;When using the quoted-string syntax variant, the value |
|
192 // ;after quoted-string unescaping MUST conform to the |
|
193 // ;'token' ABNF. |
|
194 |
|
195 var result []map[string]string |
|
196 headers: |
|
197 for _, s := range header["Sec-Websocket-Extensions"] { |
|
198 for { |
|
199 var t string |
|
200 t, s = nextToken(skipSpace(s)) |
|
201 if t == "" { |
|
202 continue headers |
|
203 } |
|
204 ext := map[string]string{"": t} |
|
205 for { |
|
206 s = skipSpace(s) |
|
207 if !strings.HasPrefix(s, ";") { |
|
208 break |
|
209 } |
|
210 var k string |
|
211 k, s = nextToken(skipSpace(s[1:])) |
|
212 if k == "" { |
|
213 continue headers |
|
214 } |
|
215 s = skipSpace(s) |
|
216 var v string |
|
217 if strings.HasPrefix(s, "=") { |
|
218 v, s = nextTokenOrQuoted(skipSpace(s[1:])) |
|
219 s = skipSpace(s) |
|
220 } |
|
221 if s != "" && s[0] != ',' && s[0] != ';' { |
|
222 continue headers |
|
223 } |
|
224 ext[k] = v |
|
225 } |
|
226 if s != "" && s[0] != ',' { |
|
227 continue headers |
|
228 } |
|
229 result = append(result, ext) |
|
230 if s == "" { |
|
231 continue headers |
|
232 } |
|
233 s = s[1:] |
|
234 } |
|
235 } |
|
236 return result |
|
237 } |