2014-04-19 01:25:11 +04:00
|
|
|
// Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
|
2013-10-16 20:41:47 +04:00
|
|
|
// 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"
|
2017-11-23 08:55:10 +03:00
|
|
|
"unicode/utf8"
|
2013-10-16 20:41:47 +04:00
|
|
|
)
|
|
|
|
|
|
|
|
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
|
|
|
|
}
|
2016-05-31 19:32:45 +03:00
|
|
|
|
|
|
|
// Octet types from RFC 2616.
|
2018-08-20 15:33:17 +03:00
|
|
|
//
|
|
|
|
// 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 <">>
|
2016-05-31 19:32:45 +03:00
|
|
|
|
2018-08-20 15:33:17 +03:00
|
|
|
func skipSpace(s string) string {
|
|
|
|
for i := 0; i < len(s); i++ {
|
|
|
|
switch s[i] {
|
|
|
|
case ' ', '\t', '\r', '\n':
|
|
|
|
default:
|
|
|
|
return s[i:]
|
2016-05-31 19:32:45 +03:00
|
|
|
}
|
|
|
|
}
|
2018-08-20 15:33:17 +03:00
|
|
|
return ""
|
2016-05-31 19:32:45 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
func nextToken(s string) (token, rest string) {
|
|
|
|
i := 0
|
2018-08-20 15:33:17 +03:00
|
|
|
loop:
|
2016-05-31 19:32:45 +03:00
|
|
|
for ; i < len(s); i++ {
|
2018-08-20 15:33:17 +03:00
|
|
|
c := s[i]
|
|
|
|
if c <= 31 || c >= 127 { // control characters & non-ascii are not token octets
|
2016-05-31 19:32:45 +03:00
|
|
|
break
|
|
|
|
}
|
2018-08-20 15:33:17 +03:00
|
|
|
switch c { //separators are not token octets
|
|
|
|
case ' ', '\t', '"', '(', ')', ',', '/', ':', ';', '<',
|
|
|
|
'=', '>', '?', '@', '[', ']', '\\', '{', '}':
|
|
|
|
break loop
|
|
|
|
}
|
2016-05-31 19:32:45 +03:00
|
|
|
}
|
|
|
|
return s[:i], s[i:]
|
|
|
|
}
|
|
|
|
|
2018-08-20 15:33:17 +03:00
|
|
|
// nextTokenOrQuoted gets the next token, unescaping and unquoting quoted tokens
|
2016-05-31 19:32:45 +03:00
|
|
|
func nextTokenOrQuoted(s string) (value string, rest string) {
|
2018-08-20 15:33:17 +03:00
|
|
|
// if it isnt quoted, then regular tokenization rules apply
|
2016-05-31 19:32:45 +03:00
|
|
|
if !strings.HasPrefix(s, "\"") {
|
|
|
|
return nextToken(s)
|
|
|
|
}
|
2018-08-20 15:33:17 +03:00
|
|
|
|
|
|
|
// trim off opening quote
|
2016-05-31 19:32:45 +03:00
|
|
|
s = s[1:]
|
2018-08-20 15:33:17 +03:00
|
|
|
|
|
|
|
// find closing quote while counting escapes
|
|
|
|
escapes := 0 // count escapes
|
|
|
|
escaped := false // whether the next char is escaped
|
|
|
|
i := 0
|
|
|
|
scan:
|
|
|
|
for ; i < len(s); i++ {
|
|
|
|
// skip escaped characters
|
|
|
|
if escaped {
|
|
|
|
escaped = false
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
2016-05-31 19:32:45 +03:00
|
|
|
switch s[i] {
|
|
|
|
case '"':
|
2018-08-20 15:33:17 +03:00
|
|
|
// closing quote
|
|
|
|
break scan
|
2016-05-31 19:32:45 +03:00
|
|
|
case '\\':
|
2018-08-20 15:33:17 +03:00
|
|
|
// escape sequence
|
|
|
|
escaped = true
|
|
|
|
escapes++
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// handle unterminated quoted token
|
|
|
|
if i == len(s) {
|
|
|
|
return "", ""
|
|
|
|
}
|
|
|
|
|
|
|
|
// split out token
|
|
|
|
value, rest = s[:i], s[i+1:]
|
|
|
|
|
|
|
|
// handle token without escapes
|
|
|
|
if escapes == 0 {
|
|
|
|
return value, rest
|
|
|
|
}
|
|
|
|
|
|
|
|
// unescape token
|
|
|
|
buf := make([]byte, len(value)-escapes)
|
|
|
|
j := 0
|
|
|
|
escaped = false
|
|
|
|
for i := 0; i < len(value); i++ {
|
|
|
|
c := value[i]
|
|
|
|
|
|
|
|
// handle escape sequence
|
|
|
|
if c == '\\' && !escaped {
|
|
|
|
escaped = true
|
|
|
|
continue
|
2016-05-31 19:32:45 +03:00
|
|
|
}
|
2018-08-20 15:33:17 +03:00
|
|
|
escaped = false
|
|
|
|
|
|
|
|
// copy character
|
|
|
|
buf[j] = c
|
|
|
|
j++
|
2016-05-31 19:32:45 +03:00
|
|
|
}
|
2018-08-20 15:33:17 +03:00
|
|
|
return string(buf), rest
|
2016-05-31 19:32:45 +03:00
|
|
|
}
|
|
|
|
|
2017-11-23 08:55:10 +03:00
|
|
|
// equalASCIIFold returns true if s is equal to t with ASCII case folding.
|
|
|
|
func equalASCIIFold(s, t string) bool {
|
|
|
|
for s != "" && t != "" {
|
2018-08-20 15:33:17 +03:00
|
|
|
// get first rune from both strings
|
|
|
|
var sr, tr rune
|
|
|
|
if s[0] < utf8.RuneSelf {
|
|
|
|
sr, s = rune(s[0]), s[1:]
|
|
|
|
} else {
|
|
|
|
r, size := utf8.DecodeRuneInString(s)
|
|
|
|
sr, s = r, s[size:]
|
2017-11-23 08:55:10 +03:00
|
|
|
}
|
2018-08-20 15:33:17 +03:00
|
|
|
if t[0] < utf8.RuneSelf {
|
|
|
|
tr, t = rune(t[0]), t[1:]
|
|
|
|
} else {
|
|
|
|
r, size := utf8.DecodeRuneInString(t)
|
|
|
|
tr, t = r, t[size:]
|
2017-11-23 08:55:10 +03:00
|
|
|
}
|
2018-08-20 15:33:17 +03:00
|
|
|
|
|
|
|
// compare runes
|
|
|
|
switch {
|
|
|
|
case sr == tr:
|
|
|
|
case 'A' <= sr && sr <= 'Z':
|
|
|
|
if sr+'a'-'A' != tr {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
case 'A' <= tr && tr <= 'Z':
|
|
|
|
if tr+'a'-'A' != sr {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
default:
|
2017-11-23 08:55:10 +03:00
|
|
|
return false
|
|
|
|
}
|
|
|
|
}
|
2018-08-20 15:33:17 +03:00
|
|
|
|
2017-11-23 08:55:10 +03:00
|
|
|
return s == t
|
|
|
|
}
|
|
|
|
|
2016-05-31 19:32:45 +03:00
|
|
|
// tokenListContainsValue returns true if the 1#token header with the given
|
2017-11-23 08:55:10 +03:00
|
|
|
// name contains a token equal to value with ASCII case folding.
|
2016-05-31 19:32:45 +03:00
|
|
|
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
|
|
|
|
}
|
2017-11-23 08:55:10 +03:00
|
|
|
if equalASCIIFold(t, value) {
|
2016-05-31 19:32:45 +03:00
|
|
|
return true
|
|
|
|
}
|
|
|
|
if s == "" {
|
|
|
|
continue headers
|
|
|
|
}
|
|
|
|
s = s[1:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2018-08-20 15:33:17 +03:00
|
|
|
// parseExtensions parses WebSocket extensions from a header.
|
2016-05-31 19:32:45 +03:00
|
|
|
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
|
|
|
|
}
|