215 lines
4.7 KiB
Go
215 lines
4.7 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"
|
|
"encoding/base64"
|
|
"io"
|
|
"net/http"
|
|
"strings"
|
|
)
|
|
|
|
var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
|
|
|
|
func computeAcceptKey(challengeKey string) string {
|
|
h := sha1.New()
|
|
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
|
|
}
|
|
|
|
// Octet types from RFC 2616.
|
|
var octetTypes [256]byte
|
|
|
|
const (
|
|
isTokenOctet = 1 << iota
|
|
isSpaceOctet
|
|
)
|
|
|
|
func init() {
|
|
// From RFC 2616
|
|
//
|
|
// OCTET = <any 8-bit sequence of data>
|
|
// CHAR = <any US-ASCII character (octets 0 - 127)>
|
|
// CTL = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
|
|
// CR = <US-ASCII CR, carriage return (13)>
|
|
// LF = <US-ASCII LF, linefeed (10)>
|
|
// SP = <US-ASCII SP, space (32)>
|
|
// HT = <US-ASCII HT, horizontal-tab (9)>
|
|
// <"> = <US-ASCII double-quote mark (34)>
|
|
// CRLF = CR LF
|
|
// LWS = [CRLF] 1*( SP | HT )
|
|
// TEXT = <any OCTET except CTLs, but including LWS>
|
|
// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
|
|
// | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
|
|
// token = 1*<any CHAR except CTLs or separators>
|
|
// qdtext = <any TEXT except <">>
|
|
|
|
for c := 0; c < 256; c++ {
|
|
var t byte
|
|
isCtl := c <= 31 || c == 127
|
|
isChar := 0 <= c && c <= 127
|
|
isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
|
|
if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
|
|
t |= isSpaceOctet
|
|
}
|
|
if isChar && !isCtl && !isSeparator {
|
|
t |= isTokenOctet
|
|
}
|
|
octetTypes[c] = t
|
|
}
|
|
}
|
|
|
|
func skipSpace(s string) (rest string) {
|
|
i := 0
|
|
for ; i < len(s); i++ {
|
|
if octetTypes[s[i]]&isSpaceOctet == 0 {
|
|
break
|
|
}
|
|
}
|
|
return s[i:]
|
|
}
|
|
|
|
func nextToken(s string) (token, rest string) {
|
|
i := 0
|
|
for ; i < len(s); i++ {
|
|
if octetTypes[s[i]]&isTokenOctet == 0 {
|
|
break
|
|
}
|
|
}
|
|
return s[:i], s[i:]
|
|
}
|
|
|
|
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 += 1
|
|
case b == '\\':
|
|
escape = true
|
|
case b == '"':
|
|
return string(p[:j]), s[i+1:]
|
|
default:
|
|
p[j] = b
|
|
j += 1
|
|
}
|
|
}
|
|
return "", ""
|
|
}
|
|
}
|
|
return "", ""
|
|
}
|
|
|
|
// tokenListContainsValue returns true if the 1#token header with the given
|
|
// name contains token.
|
|
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 strings.EqualFold(t, value) {
|
|
return true
|
|
}
|
|
if s == "" {
|
|
continue headers
|
|
}
|
|
s = s[1:]
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
// parseExtensiosn 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
|
|
}
|