|
@@ -0,0 +1,583 @@
|
|
|
+// Copyright 2011 The Go 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
|
|
|
+
|
|
|
+// This file implements a protocol of hybi draft.
|
|
|
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17
|
|
|
+
|
|
|
+import (
|
|
|
+ "bufio"
|
|
|
+ "bytes"
|
|
|
+ "crypto/rand"
|
|
|
+ "crypto/sha1"
|
|
|
+ "encoding/base64"
|
|
|
+ "encoding/binary"
|
|
|
+ "fmt"
|
|
|
+ "io"
|
|
|
+ "io/ioutil"
|
|
|
+ "net/http"
|
|
|
+ "net/url"
|
|
|
+ "strings"
|
|
|
+)
|
|
|
+
|
|
|
+const (
|
|
|
+ websocketGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
|
+
|
|
|
+ closeStatusNormal = 1000
|
|
|
+ closeStatusGoingAway = 1001
|
|
|
+ closeStatusProtocolError = 1002
|
|
|
+ closeStatusUnsupportedData = 1003
|
|
|
+ closeStatusFrameTooLarge = 1004
|
|
|
+ closeStatusNoStatusRcvd = 1005
|
|
|
+ closeStatusAbnormalClosure = 1006
|
|
|
+ closeStatusBadMessageData = 1007
|
|
|
+ closeStatusPolicyViolation = 1008
|
|
|
+ closeStatusTooBigData = 1009
|
|
|
+ closeStatusExtensionMismatch = 1010
|
|
|
+
|
|
|
+ maxControlFramePayloadLength = 125
|
|
|
+)
|
|
|
+
|
|
|
+var (
|
|
|
+ ErrBadMaskingKey = &ProtocolError{"bad masking key"}
|
|
|
+ ErrBadPongMessage = &ProtocolError{"bad pong message"}
|
|
|
+ ErrBadClosingStatus = &ProtocolError{"bad closing status"}
|
|
|
+ ErrUnsupportedExtensions = &ProtocolError{"unsupported extensions"}
|
|
|
+ ErrNotImplemented = &ProtocolError{"not implemented"}
|
|
|
+
|
|
|
+ handshakeHeader = map[string]bool{
|
|
|
+ "Host": true,
|
|
|
+ "Upgrade": true,
|
|
|
+ "Connection": true,
|
|
|
+ "Sec-Websocket-Key": true,
|
|
|
+ "Sec-Websocket-Origin": true,
|
|
|
+ "Sec-Websocket-Version": true,
|
|
|
+ "Sec-Websocket-Protocol": true,
|
|
|
+ "Sec-Websocket-Accept": true,
|
|
|
+ }
|
|
|
+)
|
|
|
+
|
|
|
+// A hybiFrameHeader is a frame header as defined in hybi draft.
|
|
|
+type hybiFrameHeader struct {
|
|
|
+ Fin bool
|
|
|
+ Rsv [3]bool
|
|
|
+ OpCode byte
|
|
|
+ Length int64
|
|
|
+ MaskingKey []byte
|
|
|
+
|
|
|
+ data *bytes.Buffer
|
|
|
+}
|
|
|
+
|
|
|
+// A hybiFrameReader is a reader for hybi frame.
|
|
|
+type hybiFrameReader struct {
|
|
|
+ reader io.Reader
|
|
|
+
|
|
|
+ header hybiFrameHeader
|
|
|
+ pos int64
|
|
|
+ length int
|
|
|
+}
|
|
|
+
|
|
|
+func (frame *hybiFrameReader) Read(msg []byte) (n int, err error) {
|
|
|
+ n, err = frame.reader.Read(msg)
|
|
|
+ if frame.header.MaskingKey != nil {
|
|
|
+ for i := 0; i < n; i++ {
|
|
|
+ msg[i] = msg[i] ^ frame.header.MaskingKey[frame.pos%4]
|
|
|
+ frame.pos++
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return n, err
|
|
|
+}
|
|
|
+
|
|
|
+func (frame *hybiFrameReader) PayloadType() byte { return frame.header.OpCode }
|
|
|
+
|
|
|
+func (frame *hybiFrameReader) HeaderReader() io.Reader {
|
|
|
+ if frame.header.data == nil {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ if frame.header.data.Len() == 0 {
|
|
|
+ return nil
|
|
|
+ }
|
|
|
+ return frame.header.data
|
|
|
+}
|
|
|
+
|
|
|
+func (frame *hybiFrameReader) TrailerReader() io.Reader { return nil }
|
|
|
+
|
|
|
+func (frame *hybiFrameReader) Len() (n int) { return frame.length }
|
|
|
+
|
|
|
+// A hybiFrameReaderFactory creates new frame reader based on its frame type.
|
|
|
+type hybiFrameReaderFactory struct {
|
|
|
+ *bufio.Reader
|
|
|
+}
|
|
|
+
|
|
|
+// NewFrameReader reads a frame header from the connection, and creates new reader for the frame.
|
|
|
+// See Section 5.2 Base Framing protocol for detail.
|
|
|
+// http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-17#section-5.2
|
|
|
+func (buf hybiFrameReaderFactory) NewFrameReader() (frame frameReader, err error) {
|
|
|
+ hybiFrame := new(hybiFrameReader)
|
|
|
+ frame = hybiFrame
|
|
|
+ var header []byte
|
|
|
+ var b byte
|
|
|
+ // First byte. FIN/RSV1/RSV2/RSV3/OpCode(4bits)
|
|
|
+ b, err = buf.ReadByte()
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ header = append(header, b)
|
|
|
+ hybiFrame.header.Fin = ((header[0] >> 7) & 1) != 0
|
|
|
+ for i := 0; i < 3; i++ {
|
|
|
+ j := uint(6 - i)
|
|
|
+ hybiFrame.header.Rsv[i] = ((header[0] >> j) & 1) != 0
|
|
|
+ }
|
|
|
+ hybiFrame.header.OpCode = header[0] & 0x0f
|
|
|
+
|
|
|
+ // Second byte. Mask/Payload len(7bits)
|
|
|
+ b, err = buf.ReadByte()
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ header = append(header, b)
|
|
|
+ mask := (b & 0x80) != 0
|
|
|
+ b &= 0x7f
|
|
|
+ lengthFields := 0
|
|
|
+ switch {
|
|
|
+ case b <= 125: // Payload length 7bits.
|
|
|
+ hybiFrame.header.Length = int64(b)
|
|
|
+ case b == 126: // Payload length 7+16bits
|
|
|
+ lengthFields = 2
|
|
|
+ case b == 127: // Payload length 7+64bits
|
|
|
+ lengthFields = 8
|
|
|
+ }
|
|
|
+ for i := 0; i < lengthFields; i++ {
|
|
|
+ b, err = buf.ReadByte()
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if lengthFields == 8 && i == 0 { // MSB must be zero when 7+64 bits
|
|
|
+ b &= 0x7f
|
|
|
+ }
|
|
|
+ header = append(header, b)
|
|
|
+ hybiFrame.header.Length = hybiFrame.header.Length*256 + int64(b)
|
|
|
+ }
|
|
|
+ if mask {
|
|
|
+ // Masking key. 4 bytes.
|
|
|
+ for i := 0; i < 4; i++ {
|
|
|
+ b, err = buf.ReadByte()
|
|
|
+ if err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ header = append(header, b)
|
|
|
+ hybiFrame.header.MaskingKey = append(hybiFrame.header.MaskingKey, b)
|
|
|
+ }
|
|
|
+ }
|
|
|
+ hybiFrame.reader = io.LimitReader(buf.Reader, hybiFrame.header.Length)
|
|
|
+ hybiFrame.header.data = bytes.NewBuffer(header)
|
|
|
+ hybiFrame.length = len(header) + int(hybiFrame.header.Length)
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// A HybiFrameWriter is a writer for hybi frame.
|
|
|
+type hybiFrameWriter struct {
|
|
|
+ writer *bufio.Writer
|
|
|
+
|
|
|
+ header *hybiFrameHeader
|
|
|
+}
|
|
|
+
|
|
|
+func (frame *hybiFrameWriter) Write(msg []byte) (n int, err error) {
|
|
|
+ var header []byte
|
|
|
+ var b byte
|
|
|
+ if frame.header.Fin {
|
|
|
+ b |= 0x80
|
|
|
+ }
|
|
|
+ for i := 0; i < 3; i++ {
|
|
|
+ if frame.header.Rsv[i] {
|
|
|
+ j := uint(6 - i)
|
|
|
+ b |= 1 << j
|
|
|
+ }
|
|
|
+ }
|
|
|
+ b |= frame.header.OpCode
|
|
|
+ header = append(header, b)
|
|
|
+ if frame.header.MaskingKey != nil {
|
|
|
+ b = 0x80
|
|
|
+ } else {
|
|
|
+ b = 0
|
|
|
+ }
|
|
|
+ lengthFields := 0
|
|
|
+ length := len(msg)
|
|
|
+ switch {
|
|
|
+ case length <= 125:
|
|
|
+ b |= byte(length)
|
|
|
+ case length < 65536:
|
|
|
+ b |= 126
|
|
|
+ lengthFields = 2
|
|
|
+ default:
|
|
|
+ b |= 127
|
|
|
+ lengthFields = 8
|
|
|
+ }
|
|
|
+ header = append(header, b)
|
|
|
+ for i := 0; i < lengthFields; i++ {
|
|
|
+ j := uint((lengthFields - i - 1) * 8)
|
|
|
+ b = byte((length >> j) & 0xff)
|
|
|
+ header = append(header, b)
|
|
|
+ }
|
|
|
+ if frame.header.MaskingKey != nil {
|
|
|
+ if len(frame.header.MaskingKey) != 4 {
|
|
|
+ return 0, ErrBadMaskingKey
|
|
|
+ }
|
|
|
+ header = append(header, frame.header.MaskingKey...)
|
|
|
+ frame.writer.Write(header)
|
|
|
+ data := make([]byte, length)
|
|
|
+ for i := range data {
|
|
|
+ data[i] = msg[i] ^ frame.header.MaskingKey[i%4]
|
|
|
+ }
|
|
|
+ frame.writer.Write(data)
|
|
|
+ err = frame.writer.Flush()
|
|
|
+ return length, err
|
|
|
+ }
|
|
|
+ frame.writer.Write(header)
|
|
|
+ frame.writer.Write(msg)
|
|
|
+ err = frame.writer.Flush()
|
|
|
+ return length, err
|
|
|
+}
|
|
|
+
|
|
|
+func (frame *hybiFrameWriter) Close() error { return nil }
|
|
|
+
|
|
|
+type hybiFrameWriterFactory struct {
|
|
|
+ *bufio.Writer
|
|
|
+ needMaskingKey bool
|
|
|
+}
|
|
|
+
|
|
|
+func (buf hybiFrameWriterFactory) NewFrameWriter(payloadType byte) (frame frameWriter, err error) {
|
|
|
+ frameHeader := &hybiFrameHeader{Fin: true, OpCode: payloadType}
|
|
|
+ if buf.needMaskingKey {
|
|
|
+ frameHeader.MaskingKey, err = generateMaskingKey()
|
|
|
+ if err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return &hybiFrameWriter{writer: buf.Writer, header: frameHeader}, nil
|
|
|
+}
|
|
|
+
|
|
|
+type hybiFrameHandler struct {
|
|
|
+ conn *Conn
|
|
|
+ payloadType byte
|
|
|
+}
|
|
|
+
|
|
|
+func (handler *hybiFrameHandler) HandleFrame(frame frameReader) (frameReader, error) {
|
|
|
+ if handler.conn.IsServerConn() {
|
|
|
+ // The client MUST mask all frames sent to the server.
|
|
|
+ if frame.(*hybiFrameReader).header.MaskingKey == nil {
|
|
|
+ handler.WriteClose(closeStatusProtocolError)
|
|
|
+ return nil, io.EOF
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ // The server MUST NOT mask all frames.
|
|
|
+ if frame.(*hybiFrameReader).header.MaskingKey != nil {
|
|
|
+ handler.WriteClose(closeStatusProtocolError)
|
|
|
+ return nil, io.EOF
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if header := frame.HeaderReader(); header != nil {
|
|
|
+ io.Copy(ioutil.Discard, header)
|
|
|
+ }
|
|
|
+ switch frame.PayloadType() {
|
|
|
+ case ContinuationFrame:
|
|
|
+ frame.(*hybiFrameReader).header.OpCode = handler.payloadType
|
|
|
+ case TextFrame, BinaryFrame:
|
|
|
+ handler.payloadType = frame.PayloadType()
|
|
|
+ case CloseFrame:
|
|
|
+ return nil, io.EOF
|
|
|
+ case PingFrame, PongFrame:
|
|
|
+ b := make([]byte, maxControlFramePayloadLength)
|
|
|
+ n, err := io.ReadFull(frame, b)
|
|
|
+ if err != nil && err != io.EOF && err != io.ErrUnexpectedEOF {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ io.Copy(ioutil.Discard, frame)
|
|
|
+ if frame.PayloadType() == PingFrame {
|
|
|
+ if _, err := handler.WritePong(b[:n]); err != nil {
|
|
|
+ return nil, err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ return frame, nil
|
|
|
+}
|
|
|
+
|
|
|
+func (handler *hybiFrameHandler) WriteClose(status int) (err error) {
|
|
|
+ handler.conn.wio.Lock()
|
|
|
+ defer handler.conn.wio.Unlock()
|
|
|
+ w, err := handler.conn.frameWriterFactory.NewFrameWriter(CloseFrame)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ msg := make([]byte, 2)
|
|
|
+ binary.BigEndian.PutUint16(msg, uint16(status))
|
|
|
+ _, err = w.Write(msg)
|
|
|
+ w.Close()
|
|
|
+ return err
|
|
|
+}
|
|
|
+
|
|
|
+func (handler *hybiFrameHandler) WritePong(msg []byte) (n int, err error) {
|
|
|
+ handler.conn.wio.Lock()
|
|
|
+ defer handler.conn.wio.Unlock()
|
|
|
+ w, err := handler.conn.frameWriterFactory.NewFrameWriter(PongFrame)
|
|
|
+ if err != nil {
|
|
|
+ return 0, err
|
|
|
+ }
|
|
|
+ n, err = w.Write(msg)
|
|
|
+ w.Close()
|
|
|
+ return n, err
|
|
|
+}
|
|
|
+
|
|
|
+// newHybiConn creates a new WebSocket connection speaking hybi draft protocol.
|
|
|
+func newHybiConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
|
|
+ if buf == nil {
|
|
|
+ br := bufio.NewReader(rwc)
|
|
|
+ bw := bufio.NewWriter(rwc)
|
|
|
+ buf = bufio.NewReadWriter(br, bw)
|
|
|
+ }
|
|
|
+ ws := &Conn{config: config, request: request, buf: buf, rwc: rwc,
|
|
|
+ frameReaderFactory: hybiFrameReaderFactory{buf.Reader},
|
|
|
+ frameWriterFactory: hybiFrameWriterFactory{
|
|
|
+ buf.Writer, request == nil},
|
|
|
+ PayloadType: TextFrame,
|
|
|
+ defaultCloseStatus: closeStatusNormal}
|
|
|
+ ws.frameHandler = &hybiFrameHandler{conn: ws}
|
|
|
+ return ws
|
|
|
+}
|
|
|
+
|
|
|
+// generateMaskingKey generates a masking key for a frame.
|
|
|
+func generateMaskingKey() (maskingKey []byte, err error) {
|
|
|
+ maskingKey = make([]byte, 4)
|
|
|
+ if _, err = io.ReadFull(rand.Reader, maskingKey); err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// generateNonce generates a nonce consisting of a randomly selected 16-byte
|
|
|
+// value that has been base64-encoded.
|
|
|
+func generateNonce() (nonce []byte) {
|
|
|
+ key := make([]byte, 16)
|
|
|
+ if _, err := io.ReadFull(rand.Reader, key); err != nil {
|
|
|
+ panic(err)
|
|
|
+ }
|
|
|
+ nonce = make([]byte, 24)
|
|
|
+ base64.StdEncoding.Encode(nonce, key)
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// removeZone removes IPv6 zone identifer from host.
|
|
|
+// E.g., "[fe80::1%en0]:8080" to "[fe80::1]:8080"
|
|
|
+func removeZone(host string) string {
|
|
|
+ if !strings.HasPrefix(host, "[") {
|
|
|
+ return host
|
|
|
+ }
|
|
|
+ i := strings.LastIndex(host, "]")
|
|
|
+ if i < 0 {
|
|
|
+ return host
|
|
|
+ }
|
|
|
+ j := strings.LastIndex(host[:i], "%")
|
|
|
+ if j < 0 {
|
|
|
+ return host
|
|
|
+ }
|
|
|
+ return host[:j] + host[i:]
|
|
|
+}
|
|
|
+
|
|
|
+// getNonceAccept computes the base64-encoded SHA-1 of the concatenation of
|
|
|
+// the nonce ("Sec-WebSocket-Key" value) with the websocket GUID string.
|
|
|
+func getNonceAccept(nonce []byte) (expected []byte, err error) {
|
|
|
+ h := sha1.New()
|
|
|
+ if _, err = h.Write(nonce); err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ if _, err = h.Write([]byte(websocketGUID)); err != nil {
|
|
|
+ return
|
|
|
+ }
|
|
|
+ expected = make([]byte, 28)
|
|
|
+ base64.StdEncoding.Encode(expected, h.Sum(nil))
|
|
|
+ return
|
|
|
+}
|
|
|
+
|
|
|
+// Client handshake described in draft-ietf-hybi-thewebsocket-protocol-17
|
|
|
+func hybiClientHandshake(config *Config, br *bufio.Reader, bw *bufio.Writer) (err error) {
|
|
|
+ bw.WriteString("GET " + config.Location.RequestURI() + " HTTP/1.1\r\n")
|
|
|
+
|
|
|
+ // According to RFC 6874, an HTTP client, proxy, or other
|
|
|
+ // intermediary must remove any IPv6 zone identifier attached
|
|
|
+ // to an outgoing URI.
|
|
|
+ bw.WriteString("Host: " + removeZone(config.Location.Host) + "\r\n")
|
|
|
+ bw.WriteString("Upgrade: websocket\r\n")
|
|
|
+ bw.WriteString("Connection: Upgrade\r\n")
|
|
|
+ nonce := generateNonce()
|
|
|
+ if config.handshakeData != nil {
|
|
|
+ nonce = []byte(config.handshakeData["key"])
|
|
|
+ }
|
|
|
+ bw.WriteString("Sec-WebSocket-Key: " + string(nonce) + "\r\n")
|
|
|
+ bw.WriteString("Origin: " + strings.ToLower(config.Origin.String()) + "\r\n")
|
|
|
+
|
|
|
+ if config.Version != ProtocolVersionHybi13 {
|
|
|
+ return ErrBadProtocolVersion
|
|
|
+ }
|
|
|
+
|
|
|
+ bw.WriteString("Sec-WebSocket-Version: " + fmt.Sprintf("%d", config.Version) + "\r\n")
|
|
|
+ if len(config.Protocol) > 0 {
|
|
|
+ bw.WriteString("Sec-WebSocket-Protocol: " + strings.Join(config.Protocol, ", ") + "\r\n")
|
|
|
+ }
|
|
|
+ // TODO(ukai): send Sec-WebSocket-Extensions.
|
|
|
+ err = config.Header.WriteSubset(bw, handshakeHeader)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ bw.WriteString("\r\n")
|
|
|
+ if err = bw.Flush(); err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+
|
|
|
+ resp, err := http.ReadResponse(br, &http.Request{Method: "GET"})
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if resp.StatusCode != 101 {
|
|
|
+ return ErrBadStatus
|
|
|
+ }
|
|
|
+ if strings.ToLower(resp.Header.Get("Upgrade")) != "websocket" ||
|
|
|
+ strings.ToLower(resp.Header.Get("Connection")) != "upgrade" {
|
|
|
+ return ErrBadUpgrade
|
|
|
+ }
|
|
|
+ expectedAccept, err := getNonceAccept(nonce)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ if resp.Header.Get("Sec-WebSocket-Accept") != string(expectedAccept) {
|
|
|
+ return ErrChallengeResponse
|
|
|
+ }
|
|
|
+ if resp.Header.Get("Sec-WebSocket-Extensions") != "" {
|
|
|
+ return ErrUnsupportedExtensions
|
|
|
+ }
|
|
|
+ offeredProtocol := resp.Header.Get("Sec-WebSocket-Protocol")
|
|
|
+ if offeredProtocol != "" {
|
|
|
+ protocolMatched := false
|
|
|
+ for i := 0; i < len(config.Protocol); i++ {
|
|
|
+ if config.Protocol[i] == offeredProtocol {
|
|
|
+ protocolMatched = true
|
|
|
+ break
|
|
|
+ }
|
|
|
+ }
|
|
|
+ if !protocolMatched {
|
|
|
+ return ErrBadWebSocketProtocol
|
|
|
+ }
|
|
|
+ config.Protocol = []string{offeredProtocol}
|
|
|
+ }
|
|
|
+
|
|
|
+ return nil
|
|
|
+}
|
|
|
+
|
|
|
+// newHybiClientConn creates a client WebSocket connection after handshake.
|
|
|
+func newHybiClientConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn {
|
|
|
+ return newHybiConn(config, buf, rwc, nil)
|
|
|
+}
|
|
|
+
|
|
|
+// A HybiServerHandshaker performs a server handshake using hybi draft protocol.
|
|
|
+type hybiServerHandshaker struct {
|
|
|
+ *Config
|
|
|
+ accept []byte
|
|
|
+}
|
|
|
+
|
|
|
+func (c *hybiServerHandshaker) ReadHandshake(buf *bufio.Reader, req *http.Request) (code int, err error) {
|
|
|
+ c.Version = ProtocolVersionHybi13
|
|
|
+ if req.Method != "GET" {
|
|
|
+ return http.StatusMethodNotAllowed, ErrBadRequestMethod
|
|
|
+ }
|
|
|
+ // HTTP version can be safely ignored.
|
|
|
+
|
|
|
+ if strings.ToLower(req.Header.Get("Upgrade")) != "websocket" ||
|
|
|
+ !strings.Contains(strings.ToLower(req.Header.Get("Connection")), "upgrade") {
|
|
|
+ return http.StatusBadRequest, ErrNotWebSocket
|
|
|
+ }
|
|
|
+
|
|
|
+ key := req.Header.Get("Sec-Websocket-Key")
|
|
|
+ if key == "" {
|
|
|
+ return http.StatusBadRequest, ErrChallengeResponse
|
|
|
+ }
|
|
|
+ version := req.Header.Get("Sec-Websocket-Version")
|
|
|
+ switch version {
|
|
|
+ case "13":
|
|
|
+ c.Version = ProtocolVersionHybi13
|
|
|
+ default:
|
|
|
+ return http.StatusBadRequest, ErrBadWebSocketVersion
|
|
|
+ }
|
|
|
+ var scheme string
|
|
|
+ if req.TLS != nil {
|
|
|
+ scheme = "wss"
|
|
|
+ } else {
|
|
|
+ scheme = "ws"
|
|
|
+ }
|
|
|
+ c.Location, err = url.ParseRequestURI(scheme + "://" + req.Host + req.URL.RequestURI())
|
|
|
+ if err != nil {
|
|
|
+ return http.StatusBadRequest, err
|
|
|
+ }
|
|
|
+ protocol := strings.TrimSpace(req.Header.Get("Sec-Websocket-Protocol"))
|
|
|
+ if protocol != "" {
|
|
|
+ protocols := strings.Split(protocol, ",")
|
|
|
+ for i := 0; i < len(protocols); i++ {
|
|
|
+ c.Protocol = append(c.Protocol, strings.TrimSpace(protocols[i]))
|
|
|
+ }
|
|
|
+ }
|
|
|
+ c.accept, err = getNonceAccept([]byte(key))
|
|
|
+ if err != nil {
|
|
|
+ return http.StatusInternalServerError, err
|
|
|
+ }
|
|
|
+ return http.StatusSwitchingProtocols, nil
|
|
|
+}
|
|
|
+
|
|
|
+// Origin parses the Origin header in req.
|
|
|
+// If the Origin header is not set, it returns nil and nil.
|
|
|
+func Origin(config *Config, req *http.Request) (*url.URL, error) {
|
|
|
+ var origin string
|
|
|
+ switch config.Version {
|
|
|
+ case ProtocolVersionHybi13:
|
|
|
+ origin = req.Header.Get("Origin")
|
|
|
+ }
|
|
|
+ if origin == "" {
|
|
|
+ return nil, nil
|
|
|
+ }
|
|
|
+ return url.ParseRequestURI(origin)
|
|
|
+}
|
|
|
+
|
|
|
+func (c *hybiServerHandshaker) AcceptHandshake(buf *bufio.Writer) (err error) {
|
|
|
+ if len(c.Protocol) > 0 {
|
|
|
+ if len(c.Protocol) != 1 {
|
|
|
+ // You need choose a Protocol in Handshake func in Server.
|
|
|
+ return ErrBadWebSocketProtocol
|
|
|
+ }
|
|
|
+ }
|
|
|
+ buf.WriteString("HTTP/1.1 101 Switching Protocols\r\n")
|
|
|
+ buf.WriteString("Upgrade: websocket\r\n")
|
|
|
+ buf.WriteString("Connection: Upgrade\r\n")
|
|
|
+ buf.WriteString("Sec-WebSocket-Accept: " + string(c.accept) + "\r\n")
|
|
|
+ if len(c.Protocol) > 0 {
|
|
|
+ buf.WriteString("Sec-WebSocket-Protocol: " + c.Protocol[0] + "\r\n")
|
|
|
+ }
|
|
|
+ // TODO(ukai): send Sec-WebSocket-Extensions.
|
|
|
+ if c.Header != nil {
|
|
|
+ err := c.Header.WriteSubset(buf, handshakeHeader)
|
|
|
+ if err != nil {
|
|
|
+ return err
|
|
|
+ }
|
|
|
+ }
|
|
|
+ buf.WriteString("\r\n")
|
|
|
+ return buf.Flush()
|
|
|
+}
|
|
|
+
|
|
|
+func (c *hybiServerHandshaker) NewServerConn(buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
|
|
+ return newHybiServerConn(c.Config, buf, rwc, request)
|
|
|
+}
|
|
|
+
|
|
|
+// newHybiServerConn returns a new WebSocket connection speaking hybi draft protocol.
|
|
|
+func newHybiServerConn(config *Config, buf *bufio.ReadWriter, rwc io.ReadWriteCloser, request *http.Request) *Conn {
|
|
|
+ return newHybiConn(config, buf, rwc, request)
|
|
|
+}
|