mirror of https://github.com/gorilla/websocket
Mirror of https://github.com/gorilla/websocket
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
298 lines
5.9 KiB
298 lines
5.9 KiB
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved. |
|
// Use of this source code is governed by a BSD-style |
|
// license that can be found in the LICENSE file. |
|
|
|
package websocket |
|
|
|
import ( |
|
"crypto/rand" |
|
"crypto/sha1" //#nosec G505 -- (CWE-327) https://datatracker.ietf.org/doc/html/rfc6455#page-54 |
|
"encoding/base64" |
|
"io" |
|
"net/http" |
|
"strings" |
|
"unicode/utf8" |
|
) |
|
|
|
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11") |
|
|
|
func computeAcceptKey(challengeKey string) string { |
|
h := sha1.New() //#nosec G401 -- (CWE-326) https://datatracker.ietf.org/doc/html/rfc6455#page-54 |
|
h.Write([]byte(challengeKey)) |
|
h.Write(keyGUID) |
|
return base64.StdEncoding.EncodeToString(h.Sum(nil)) |
|
} |
|
|
|
func generateChallengeKey() (string, error) { |
|
p := make([]byte, 16) |
|
if _, err := io.ReadFull(rand.Reader, p); err != nil { |
|
return "", err |
|
} |
|
return base64.StdEncoding.EncodeToString(p), nil |
|
} |
|
|
|
// Token octets per RFC 2616. |
|
var isTokenOctet = [256]bool{ |
|
'!': true, |
|
'#': true, |
|
'$': true, |
|
'%': true, |
|
'&': true, |
|
'\'': true, |
|
'*': true, |
|
'+': true, |
|
'-': true, |
|
'.': true, |
|
'0': true, |
|
'1': true, |
|
'2': true, |
|
'3': true, |
|
'4': true, |
|
'5': true, |
|
'6': true, |
|
'7': true, |
|
'8': true, |
|
'9': true, |
|
'A': true, |
|
'B': true, |
|
'C': true, |
|
'D': true, |
|
'E': true, |
|
'F': true, |
|
'G': true, |
|
'H': true, |
|
'I': true, |
|
'J': true, |
|
'K': true, |
|
'L': true, |
|
'M': true, |
|
'N': true, |
|
'O': true, |
|
'P': true, |
|
'Q': true, |
|
'R': true, |
|
'S': true, |
|
'T': true, |
|
'U': true, |
|
'W': true, |
|
'V': true, |
|
'X': true, |
|
'Y': true, |
|
'Z': true, |
|
'^': true, |
|
'_': true, |
|
'`': true, |
|
'a': true, |
|
'b': true, |
|
'c': true, |
|
'd': true, |
|
'e': true, |
|
'f': true, |
|
'g': true, |
|
'h': true, |
|
'i': true, |
|
'j': true, |
|
'k': true, |
|
'l': true, |
|
'm': true, |
|
'n': true, |
|
'o': true, |
|
'p': true, |
|
'q': true, |
|
'r': true, |
|
's': true, |
|
't': true, |
|
'u': true, |
|
'v': true, |
|
'w': true, |
|
'x': true, |
|
'y': true, |
|
'z': true, |
|
'|': true, |
|
'~': true, |
|
} |
|
|
|
// skipSpace returns a slice of the string s with all leading RFC 2616 linear |
|
// whitespace removed. |
|
func skipSpace(s string) (rest string) { |
|
i := 0 |
|
for ; i < len(s); i++ { |
|
if b := s[i]; b != ' ' && b != '\t' { |
|
break |
|
} |
|
} |
|
return s[i:] |
|
} |
|
|
|
// nextToken returns the leading RFC 2616 token of s and the string following |
|
// the token. |
|
func nextToken(s string) (token, rest string) { |
|
i := 0 |
|
for ; i < len(s); i++ { |
|
if !isTokenOctet[s[i]] { |
|
break |
|
} |
|
} |
|
return s[:i], s[i:] |
|
} |
|
|
|
// nextTokenOrQuoted returns the leading token or quoted string per RFC 2616 |
|
// and the string following the token or quoted string. |
|
func nextTokenOrQuoted(s string) (value string, rest string) { |
|
if !strings.HasPrefix(s, "\"") { |
|
return nextToken(s) |
|
} |
|
s = s[1:] |
|
for i := 0; i < len(s); i++ { |
|
switch s[i] { |
|
case '"': |
|
return s[:i], s[i+1:] |
|
case '\\': |
|
p := make([]byte, len(s)-1) |
|
j := copy(p, s[:i]) |
|
escape := true |
|
for i = i + 1; i < len(s); i++ { |
|
b := s[i] |
|
switch { |
|
case escape: |
|
escape = false |
|
p[j] = b |
|
j++ |
|
case b == '\\': |
|
escape = true |
|
case b == '"': |
|
return string(p[:j]), s[i+1:] |
|
default: |
|
p[j] = b |
|
j++ |
|
} |
|
} |
|
return "", "" |
|
} |
|
} |
|
return "", "" |
|
} |
|
|
|
// equalASCIIFold returns true if s is equal to t with ASCII case folding as |
|
// defined in RFC 4790. |
|
func equalASCIIFold(s, t string) bool { |
|
for s != "" && t != "" { |
|
sr, size := utf8.DecodeRuneInString(s) |
|
s = s[size:] |
|
tr, size := utf8.DecodeRuneInString(t) |
|
t = t[size:] |
|
if sr == tr { |
|
continue |
|
} |
|
if 'A' <= sr && sr <= 'Z' { |
|
sr = sr + 'a' - 'A' |
|
} |
|
if 'A' <= tr && tr <= 'Z' { |
|
tr = tr + 'a' - 'A' |
|
} |
|
if sr != tr { |
|
return false |
|
} |
|
} |
|
return s == t |
|
} |
|
|
|
// tokenListContainsValue returns true if the 1#token header with the given |
|
// name contains a token equal to value with ASCII case folding. |
|
func tokenListContainsValue(header http.Header, name string, value string) bool { |
|
headers: |
|
for _, s := range header[name] { |
|
for { |
|
var t string |
|
t, s = nextToken(skipSpace(s)) |
|
if t == "" { |
|
continue headers |
|
} |
|
s = skipSpace(s) |
|
if s != "" && s[0] != ',' { |
|
continue headers |
|
} |
|
if equalASCIIFold(t, value) { |
|
return true |
|
} |
|
if s == "" { |
|
continue headers |
|
} |
|
s = s[1:] |
|
} |
|
} |
|
return false |
|
} |
|
|
|
// parseExtensions parses WebSocket extensions from a header. |
|
func parseExtensions(header http.Header) []map[string]string { |
|
// From RFC 6455: |
|
// |
|
// Sec-WebSocket-Extensions = extension-list |
|
// extension-list = 1#extension |
|
// extension = extension-token *( ";" extension-param ) |
|
// extension-token = registered-token |
|
// registered-token = token |
|
// extension-param = token [ "=" (token | quoted-string) ] |
|
// ;When using the quoted-string syntax variant, the value |
|
// ;after quoted-string unescaping MUST conform to the |
|
// ;'token' ABNF. |
|
|
|
var result []map[string]string |
|
headers: |
|
for _, s := range header["Sec-Websocket-Extensions"] { |
|
for { |
|
var t string |
|
t, s = nextToken(skipSpace(s)) |
|
if t == "" { |
|
continue headers |
|
} |
|
ext := map[string]string{"": t} |
|
for { |
|
s = skipSpace(s) |
|
if !strings.HasPrefix(s, ";") { |
|
break |
|
} |
|
var k string |
|
k, s = nextToken(skipSpace(s[1:])) |
|
if k == "" { |
|
continue headers |
|
} |
|
s = skipSpace(s) |
|
var v string |
|
if strings.HasPrefix(s, "=") { |
|
v, s = nextTokenOrQuoted(skipSpace(s[1:])) |
|
s = skipSpace(s) |
|
} |
|
if s != "" && s[0] != ',' && s[0] != ';' { |
|
continue headers |
|
} |
|
ext[k] = v |
|
} |
|
if s != "" && s[0] != ',' { |
|
continue headers |
|
} |
|
result = append(result, ext) |
|
if s == "" { |
|
continue headers |
|
} |
|
s = s[1:] |
|
} |
|
} |
|
return result |
|
} |
|
|
|
// isValidChallengeKey checks if the argument meets RFC6455 specification. |
|
func isValidChallengeKey(s string) bool { |
|
// From RFC6455: |
|
// |
|
// A |Sec-WebSocket-Key| header field with a base64-encoded (see |
|
// Section 4 of [RFC4648]) value that, when decoded, is 16 bytes in |
|
// length. |
|
|
|
if s == "" { |
|
return false |
|
} |
|
decoded, err := base64.StdEncoding.DecodeString(s) |
|
return err == nil && len(decoded) == 16 |
|
}
|
|
|