| // 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++ |
| case b == '\\': |
| escape = true |
| case b == '"': |
| return string(p[:j]), s[i+1:] |
| default: |
| p[j] = b |
| j++ |
| } |
| } |
| 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 |
| } |