// 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) }