299 lines
5.9 KiB
Go
299 lines
5.9 KiB
Go
// 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
|
|
}
|