vendor/github.com/gorilla/websocket/util.go
changeset 242 2a9ec03fe5a1
child 251 1c52a0eeb952
equal deleted inserted replaced
241:e77dad242f4c 242:2a9ec03fe5a1
       
     1 // Copyright 2013 The Gorilla WebSocket Authors. All rights reserved.
       
     2 // Use of this source code is governed by a BSD-style
       
     3 // license that can be found in the LICENSE file.
       
     4 
       
     5 package websocket
       
     6 
       
     7 import (
       
     8 	"crypto/rand"
       
     9 	"crypto/sha1"
       
    10 	"encoding/base64"
       
    11 	"io"
       
    12 	"net/http"
       
    13 	"strings"
       
    14 	"unicode/utf8"
       
    15 )
       
    16 
       
    17 var keyGUID = []byte("258EAFA5-E914-47DA-95CA-C5AB0DC85B11")
       
    18 
       
    19 func computeAcceptKey(challengeKey string) string {
       
    20 	h := sha1.New()
       
    21 	h.Write([]byte(challengeKey))
       
    22 	h.Write(keyGUID)
       
    23 	return base64.StdEncoding.EncodeToString(h.Sum(nil))
       
    24 }
       
    25 
       
    26 func generateChallengeKey() (string, error) {
       
    27 	p := make([]byte, 16)
       
    28 	if _, err := io.ReadFull(rand.Reader, p); err != nil {
       
    29 		return "", err
       
    30 	}
       
    31 	return base64.StdEncoding.EncodeToString(p), nil
       
    32 }
       
    33 
       
    34 // Octet types from RFC 2616.
       
    35 var octetTypes [256]byte
       
    36 
       
    37 const (
       
    38 	isTokenOctet = 1 << iota
       
    39 	isSpaceOctet
       
    40 )
       
    41 
       
    42 func init() {
       
    43 	// From RFC 2616
       
    44 	//
       
    45 	// OCTET      = <any 8-bit sequence of data>
       
    46 	// CHAR       = <any US-ASCII character (octets 0 - 127)>
       
    47 	// CTL        = <any US-ASCII control character (octets 0 - 31) and DEL (127)>
       
    48 	// CR         = <US-ASCII CR, carriage return (13)>
       
    49 	// LF         = <US-ASCII LF, linefeed (10)>
       
    50 	// SP         = <US-ASCII SP, space (32)>
       
    51 	// HT         = <US-ASCII HT, horizontal-tab (9)>
       
    52 	// <">        = <US-ASCII double-quote mark (34)>
       
    53 	// CRLF       = CR LF
       
    54 	// LWS        = [CRLF] 1*( SP | HT )
       
    55 	// TEXT       = <any OCTET except CTLs, but including LWS>
       
    56 	// separators = "(" | ")" | "<" | ">" | "@" | "," | ";" | ":" | "\" | <">
       
    57 	//              | "/" | "[" | "]" | "?" | "=" | "{" | "}" | SP | HT
       
    58 	// token      = 1*<any CHAR except CTLs or separators>
       
    59 	// qdtext     = <any TEXT except <">>
       
    60 
       
    61 	for c := 0; c < 256; c++ {
       
    62 		var t byte
       
    63 		isCtl := c <= 31 || c == 127
       
    64 		isChar := 0 <= c && c <= 127
       
    65 		isSeparator := strings.IndexRune(" \t\"(),/:;<=>?@[]\\{}", rune(c)) >= 0
       
    66 		if strings.IndexRune(" \t\r\n", rune(c)) >= 0 {
       
    67 			t |= isSpaceOctet
       
    68 		}
       
    69 		if isChar && !isCtl && !isSeparator {
       
    70 			t |= isTokenOctet
       
    71 		}
       
    72 		octetTypes[c] = t
       
    73 	}
       
    74 }
       
    75 
       
    76 func skipSpace(s string) (rest string) {
       
    77 	i := 0
       
    78 	for ; i < len(s); i++ {
       
    79 		if octetTypes[s[i]]&isSpaceOctet == 0 {
       
    80 			break
       
    81 		}
       
    82 	}
       
    83 	return s[i:]
       
    84 }
       
    85 
       
    86 func nextToken(s string) (token, rest string) {
       
    87 	i := 0
       
    88 	for ; i < len(s); i++ {
       
    89 		if octetTypes[s[i]]&isTokenOctet == 0 {
       
    90 			break
       
    91 		}
       
    92 	}
       
    93 	return s[:i], s[i:]
       
    94 }
       
    95 
       
    96 func nextTokenOrQuoted(s string) (value string, rest string) {
       
    97 	if !strings.HasPrefix(s, "\"") {
       
    98 		return nextToken(s)
       
    99 	}
       
   100 	s = s[1:]
       
   101 	for i := 0; i < len(s); i++ {
       
   102 		switch s[i] {
       
   103 		case '"':
       
   104 			return s[:i], s[i+1:]
       
   105 		case '\\':
       
   106 			p := make([]byte, len(s)-1)
       
   107 			j := copy(p, s[:i])
       
   108 			escape := true
       
   109 			for i = i + 1; i < len(s); i++ {
       
   110 				b := s[i]
       
   111 				switch {
       
   112 				case escape:
       
   113 					escape = false
       
   114 					p[j] = b
       
   115 					j++
       
   116 				case b == '\\':
       
   117 					escape = true
       
   118 				case b == '"':
       
   119 					return string(p[:j]), s[i+1:]
       
   120 				default:
       
   121 					p[j] = b
       
   122 					j++
       
   123 				}
       
   124 			}
       
   125 			return "", ""
       
   126 		}
       
   127 	}
       
   128 	return "", ""
       
   129 }
       
   130 
       
   131 // equalASCIIFold returns true if s is equal to t with ASCII case folding.
       
   132 func equalASCIIFold(s, t string) bool {
       
   133 	for s != "" && t != "" {
       
   134 		sr, size := utf8.DecodeRuneInString(s)
       
   135 		s = s[size:]
       
   136 		tr, size := utf8.DecodeRuneInString(t)
       
   137 		t = t[size:]
       
   138 		if sr == tr {
       
   139 			continue
       
   140 		}
       
   141 		if 'A' <= sr && sr <= 'Z' {
       
   142 			sr = sr + 'a' - 'A'
       
   143 		}
       
   144 		if 'A' <= tr && tr <= 'Z' {
       
   145 			tr = tr + 'a' - 'A'
       
   146 		}
       
   147 		if sr != tr {
       
   148 			return false
       
   149 		}
       
   150 	}
       
   151 	return s == t
       
   152 }
       
   153 
       
   154 // tokenListContainsValue returns true if the 1#token header with the given
       
   155 // name contains a token equal to value with ASCII case folding.
       
   156 func tokenListContainsValue(header http.Header, name string, value string) bool {
       
   157 headers:
       
   158 	for _, s := range header[name] {
       
   159 		for {
       
   160 			var t string
       
   161 			t, s = nextToken(skipSpace(s))
       
   162 			if t == "" {
       
   163 				continue headers
       
   164 			}
       
   165 			s = skipSpace(s)
       
   166 			if s != "" && s[0] != ',' {
       
   167 				continue headers
       
   168 			}
       
   169 			if equalASCIIFold(t, value) {
       
   170 				return true
       
   171 			}
       
   172 			if s == "" {
       
   173 				continue headers
       
   174 			}
       
   175 			s = s[1:]
       
   176 		}
       
   177 	}
       
   178 	return false
       
   179 }
       
   180 
       
   181 // parseExtensions parses WebSocket extensions from a header.
       
   182 func parseExtensions(header http.Header) []map[string]string {
       
   183 	// From RFC 6455:
       
   184 	//
       
   185 	//  Sec-WebSocket-Extensions = extension-list
       
   186 	//  extension-list = 1#extension
       
   187 	//  extension = extension-token *( ";" extension-param )
       
   188 	//  extension-token = registered-token
       
   189 	//  registered-token = token
       
   190 	//  extension-param = token [ "=" (token | quoted-string) ]
       
   191 	//     ;When using the quoted-string syntax variant, the value
       
   192 	//     ;after quoted-string unescaping MUST conform to the
       
   193 	//     ;'token' ABNF.
       
   194 
       
   195 	var result []map[string]string
       
   196 headers:
       
   197 	for _, s := range header["Sec-Websocket-Extensions"] {
       
   198 		for {
       
   199 			var t string
       
   200 			t, s = nextToken(skipSpace(s))
       
   201 			if t == "" {
       
   202 				continue headers
       
   203 			}
       
   204 			ext := map[string]string{"": t}
       
   205 			for {
       
   206 				s = skipSpace(s)
       
   207 				if !strings.HasPrefix(s, ";") {
       
   208 					break
       
   209 				}
       
   210 				var k string
       
   211 				k, s = nextToken(skipSpace(s[1:]))
       
   212 				if k == "" {
       
   213 					continue headers
       
   214 				}
       
   215 				s = skipSpace(s)
       
   216 				var v string
       
   217 				if strings.HasPrefix(s, "=") {
       
   218 					v, s = nextTokenOrQuoted(skipSpace(s[1:]))
       
   219 					s = skipSpace(s)
       
   220 				}
       
   221 				if s != "" && s[0] != ',' && s[0] != ';' {
       
   222 					continue headers
       
   223 				}
       
   224 				ext[k] = v
       
   225 			}
       
   226 			if s != "" && s[0] != ',' {
       
   227 				continue headers
       
   228 			}
       
   229 			result = append(result, ext)
       
   230 			if s == "" {
       
   231 				continue headers
       
   232 			}
       
   233 			s = s[1:]
       
   234 		}
       
   235 	}
       
   236 	return result
       
   237 }