| // 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" |
| "unicode/utf8" |
| ) |
| |
| 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 |
| } |
| |
| // 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 |
| } |