commit 38c219e18648327e5ed1afa98aa939ce37ca3cd6 Author: David Fifield david@bamsoftware.com Date: Sun Aug 11 18:41:22 2013 -0700
Move websocket into its own package. --- .../src/websocket-server/websocket-server.go | 11 +- .../src/websocket-server/websocket.go | 432 -------------------- websocket-transport/src/websocket/websocket.go | 432 ++++++++++++++++++++ 3 files changed, 438 insertions(+), 437 deletions(-)
diff --git a/websocket-transport/src/websocket-server/websocket-server.go b/websocket-transport/src/websocket-server/websocket-server.go index 3ea7481..01a6f66 100644 --- a/websocket-transport/src/websocket-server/websocket-server.go +++ b/websocket-transport/src/websocket-server/websocket-server.go @@ -20,6 +20,7 @@ import ( )
import "pt" +import "websocket"
const ptMethodName = "websocket" const requestTimeout = 10 * time.Second @@ -59,7 +60,7 @@ func Log(format string, v ...interface{}) { // io.ReadWriteCloser. It internally takes care of things like base64 encoding and // decoding. type websocketConn struct { - Ws *Websocket + Ws *websocket.Websocket Base64 bool messageBuf []byte } @@ -67,7 +68,7 @@ type websocketConn struct { // Implements io.Reader. func (conn *websocketConn) Read(b []byte) (n int, err error) { for len(conn.messageBuf) == 0 { - var m WebsocketMessage + var m websocket.WebsocketMessage m, err = conn.Ws.ReadMessage() if err != nil { return @@ -128,7 +129,7 @@ func (conn *websocketConn) Close() error { }
// Create a new websocketConn. -func NewWebsocketConn(ws *Websocket) websocketConn { +func NewWebsocketConn(ws *websocket.Websocket) websocketConn { var conn websocketConn conn.Ws = ws conn.Base64 = (ws.Subprotocol == "base64") @@ -164,7 +165,7 @@ func proxy(local *net.TCPConn, conn *websocketConn) { wg.Wait() }
-func websocketHandler(ws *Websocket) { +func websocketHandler(ws *websocket.Websocket) { // Undo timeouts on HTTP request handling. ws.Conn.SetDeadline(time.Time{}) conn := NewWebsocketConn(ws) @@ -189,7 +190,7 @@ func startListener(addr *net.TCPAddr) (*net.TCPListener, error) { return nil, err } go func() { - var config WebsocketConfig + var config websocket.WebsocketConfig config.Subprotocols = []string{"base64"} config.MaxMessageSize = maxMessageSize s := &http.Server{ diff --git a/websocket-transport/src/websocket-server/websocket.go b/websocket-transport/src/websocket-server/websocket.go deleted file mode 100644 index f195828..0000000 --- a/websocket-transport/src/websocket-server/websocket.go +++ /dev/null @@ -1,432 +0,0 @@ -// WebSocket library. Only the RFC 6455 variety of WebSocket is supported. -// -// Reading and writing is strictly per-frame (or per-message). There is no way -// to partially read a frame. WebsocketConfig.MaxMessageSize affords control of -// the maximum buffering of messages. -// -// The reason for using this custom implementation instead of -// code.google.com/p/go.net/websocket is that the latter has problems with long -// messages and does not support server subprotocols. -// "Denial of Service Protection in Go HTTP Servers" -// https://code.google.com/p/go/issues/detail?id=2093 -// "go.websocket: Read/Copy fail with long frames" -// https://code.google.com/p/go/issues/detail?id=2134 -// http://golang.org/pkg/net/textproto/#pkg-bugs -// "To let callers manage exposure to denial of service attacks, Reader should -// allow them to set and reset a limit on the number of bytes read from the -// connection." -// "websocket.Dial doesn't limit response header length as http.Get does" -// https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/2Tge6U8-QYI -// -// Example usage: -// -// func doSomething(ws *Websocket) { -// } -// var config WebsocketConfig -// config.Subprotocols = []string{"base64"} -// config.MaxMessageSize = 2500 -// http.Handle("/", config.Handler(doSomething)) -// err = http.ListenAndServe(":8080", nil) - -package main - -import ( - "bufio" - "bytes" - "crypto/rand" - "crypto/sha1" - "encoding/base64" - "encoding/binary" - "errors" - "fmt" - "io" - "net" - "net/http" - "strings" -) - -// Settings for potential WebSocket connections. Subprotocols is a list of -// supported subprotocols as in RFC 6455 section 1.9. When answering client -// requests, the first of the client's requests subprotocols that is also in -// this list (if any) will be used as the subprotocol for the connection. -// MaxMessageSize is a limit on buffering messages. -type WebsocketConfig struct { - Subprotocols []string - MaxMessageSize int -} - -// Representation of a WebSocket frame. The Payload is always without masking. -type WebsocketFrame struct { - Fin bool - Opcode byte - Payload []byte -} - -// Return true iff the frame's opcode says it is a control frame. -func (frame *WebsocketFrame) IsControl() bool { - return (frame.Opcode & 0x08) != 0 -} - -// Representation of a WebSocket message. The Payload is always without masking. -type WebsocketMessage struct { - Opcode byte - Payload []byte -} - -// A WebSocket connection after hijacking from HTTP. -type Websocket struct { - // Conn and ReadWriter from http.ResponseWriter.Hijack. - Conn net.Conn - Bufrw *bufio.ReadWriter - // Whether we are a client or a server has implications for masking. - IsClient bool - // Set from a parent WebsocketConfig. - MaxMessageSize int - // The single selected subprotocol after negotiation, or "". - Subprotocol string - // Buffer for message payloads, which may be interrupted by control - // messages. - messageBuf bytes.Buffer -} - -func applyMask(payload []byte, maskKey [4]byte) { - for i := 0; i < len(payload); i++ { - payload[i] = payload[i] ^ maskKey[i%4] - } -} - -func (ws *Websocket) maxMessageSize() int { - if ws.MaxMessageSize == 0 { - return 64000 - } - return ws.MaxMessageSize -} - -// Read a single frame from the WebSocket. -func (ws *Websocket) ReadFrame() (frame WebsocketFrame, err error) { - var b byte - err = binary.Read(ws.Bufrw, binary.BigEndian, &b) - if err != nil { - return - } - frame.Fin = (b & 0x80) != 0 - frame.Opcode = b & 0x0f - err = binary.Read(ws.Bufrw, binary.BigEndian, &b) - if err != nil { - return - } - masked := (b & 0x80) != 0 - - payloadLen := uint64(b & 0x7f) - if payloadLen == 126 { - var short uint16 - err = binary.Read(ws.Bufrw, binary.BigEndian, &short) - if err != nil { - return - } - payloadLen = uint64(short) - } else if payloadLen == 127 { - var long uint64 - err = binary.Read(ws.Bufrw, binary.BigEndian, &long) - if err != nil { - return - } - payloadLen = long - } - if payloadLen > uint64(ws.maxMessageSize()) { - err = errors.New(fmt.Sprintf("frame payload length of %d exceeds maximum of %d", payloadLen, ws.MaxMessageSize)) - return - } - - maskKey := [4]byte{} - if masked { - if ws.IsClient { - err = errors.New("client got masked frame") - return - } - err = binary.Read(ws.Bufrw, binary.BigEndian, &maskKey) - if err != nil { - return - } - } else { - if !ws.IsClient { - err = errors.New("server got unmasked frame") - return - } - } - - frame.Payload = make([]byte, payloadLen) - _, err = io.ReadFull(ws.Bufrw, frame.Payload) - if err != nil { - return - } - if masked { - applyMask(frame.Payload, maskKey) - } - - return frame, nil -} - -// Read a single message from the WebSocket. Multiple fragmented frames are -// combined into a single message before being returned. Non-control messages -// may be interrupted by control frames. The control frames are returned as -// individual messages before the message that they interrupt. -func (ws *Websocket) ReadMessage() (message WebsocketMessage, err error) { - var opcode byte = 0 - for { - var frame WebsocketFrame - frame, err = ws.ReadFrame() - if err != nil { - return - } - if frame.IsControl() { - if !frame.Fin { - err = errors.New("control frame has fin bit unset") - return - } - message.Opcode = frame.Opcode - message.Payload = frame.Payload - return message, nil - } - - if opcode == 0 { - if frame.Opcode == 0 { - err = errors.New("first frame has opcode 0") - return - } - opcode = frame.Opcode - } else { - if frame.Opcode != 0 { - err = errors.New(fmt.Sprintf("non-first frame has nonzero opcode %d", frame.Opcode)) - return - } - } - if ws.messageBuf.Len()+len(frame.Payload) > ws.MaxMessageSize { - err = errors.New(fmt.Sprintf("message payload length of %d exceeds maximum of %d", - ws.messageBuf.Len()+len(frame.Payload), ws.MaxMessageSize)) - return - } - ws.messageBuf.Write(frame.Payload) - if frame.Fin { - break - } - } - message.Opcode = opcode - message.Payload = ws.messageBuf.Bytes() - ws.messageBuf.Reset() - - return message, nil -} - -// Write a single frame to the WebSocket stream. Destructively masks payload in -// place if ws.IsClient. Frames are always unfragmented. -func (ws *Websocket) WriteFrame(opcode byte, payload []byte) (err error) { - if opcode >= 16 { - err = errors.New(fmt.Sprintf("opcode %d is >= 16", opcode)) - return - } - ws.Bufrw.WriteByte(0x80 | opcode) - - var maskBit byte - var maskKey [4]byte - if ws.IsClient { - _, err = io.ReadFull(rand.Reader, maskKey[:]) - if err != nil { - return - } - applyMask(payload, maskKey) - maskBit = 0x80 - } else { - maskBit = 0x00 - } - - if len(payload) < 126 { - ws.Bufrw.WriteByte(maskBit | byte(len(payload))) - } else if len(payload) <= 0xffff { - ws.Bufrw.WriteByte(maskBit | 126) - binary.Write(ws.Bufrw, binary.BigEndian, uint16(len(payload))) - } else { - ws.Bufrw.WriteByte(maskBit | 127) - binary.Write(ws.Bufrw, binary.BigEndian, uint64(len(payload))) - } - - if ws.IsClient { - _, err = ws.Bufrw.Write(maskKey[:]) - if err != nil { - return - } - } - _, err = ws.Bufrw.Write(payload) - if err != nil { - return - } - - ws.Bufrw.Flush() - - return -} - -// Write a single message to the WebSocket stream. Destructively masks payload -// in place if ws.IsClient. Messages are always sent as a single unfragmented -// frame. -func (ws *Websocket) WriteMessage(opcode byte, payload []byte) (err error) { - return ws.WriteFrame(opcode, payload) -} - -// Split a string on commas and trim whitespace. -func commaSplit(s string) []string { - var result []string - if strings.TrimSpace(s) == "" { - return result - } - for _, e := range strings.Split(s, ",") { - result = append(result, strings.TrimSpace(e)) - } - return result -} - -// Returns true iff one of the strings in haystack is needle. -func containsCase(haystack []string, needle string) bool { - for _, e := range haystack { - if strings.ToLower(e) == strings.ToLower(needle) { - return true - } - } - return false -} - -// One-step SHA-1 hash of a string. -func sha1Hash(data string) []byte { - h := sha1.New() - h.Write([]byte(data)) - return h.Sum(nil) -} - -func httpError(w http.ResponseWriter, bufrw *bufio.ReadWriter, code int) { - w.Header().Set("Connection", "close") - bufrw.WriteString(fmt.Sprintf("HTTP/1.0 %d %s\r\n", code, http.StatusText(code))) - w.Header().Write(bufrw) - bufrw.WriteString("\r\n") - bufrw.Flush() -} - -// An implementation of http.Handler with a WebsocketConfig. The ServeHTTP -// function calls websocketCallback assuming WebSocket HTTP negotiation is -// successful. -type WebSocketHTTPHandler struct { - Config *WebsocketConfig - WebsocketCallback func(*Websocket) -} - -// Implements the http.Handler interface. -func (handler *WebSocketHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { - conn, bufrw, err := w.(http.Hijacker).Hijack() - if err != nil { - http.Error(w, err.Error(), http.StatusInternalServerError) - return - } - defer conn.Close() - - // See RFC 6455 section 4.2.1 for this sequence of checks. - - // 1. An HTTP/1.1 or higher GET request, including a "Request-URI"... - if req.Method != "GET" { - httpError(w, bufrw, http.StatusMethodNotAllowed) - return - } - if req.URL.Path != "/" { - httpError(w, bufrw, http.StatusNotFound) - return - } - // 2. A |Host| header field containing the server's authority. - // We deliberately skip this test. - // 3. An |Upgrade| header field containing the value "websocket", - // treated as an ASCII case-insensitive value. - if !containsCase(commaSplit(req.Header.Get("Upgrade")), "websocket") { - httpError(w, bufrw, http.StatusBadRequest) - return - } - // 4. A |Connection| header field that includes the token "Upgrade", - // treated as an ASCII case-insensitive value. - if !containsCase(commaSplit(req.Header.Get("Connection")), "Upgrade") { - httpError(w, bufrw, http.StatusBadRequest) - return - } - // 5. A |Sec-WebSocket-Key| header field with a base64-encoded value - // that, when decoded, is 16 bytes in length. - websocketKey := req.Header.Get("Sec-WebSocket-Key") - key, err := base64.StdEncoding.DecodeString(websocketKey) - if err != nil || len(key) != 16 { - httpError(w, bufrw, http.StatusBadRequest) - return - } - // 6. A |Sec-WebSocket-Version| header field, with a value of 13. - // We also allow 8 from draft-ietf-hybi-thewebsocketprotocol-10. - var knownVersions = []string{"8", "13"} - websocketVersion := req.Header.Get("Sec-WebSocket-Version") - if !containsCase(knownVersions, websocketVersion) { - // "If this version does not match a version understood by the - // server, the server MUST abort the WebSocket handshake - // described in this section and instead send an appropriate - // HTTP error code (such as 426 Upgrade Required) and a - // |Sec-WebSocket-Version| header field indicating the - // version(s) the server is capable of understanding." - w.Header().Set("Sec-WebSocket-Version", strings.Join(knownVersions, ", ")) - httpError(w, bufrw, 426) - return - } - // 7. Optionally, an |Origin| header field. - // 8. Optionally, a |Sec-WebSocket-Protocol| header field, with a list of - // values indicating which protocols the client would like to speak, ordered - // by preference. - clientProtocols := commaSplit(req.Header.Get("Sec-WebSocket-Protocol")) - // 9. Optionally, a |Sec-WebSocket-Extensions| header field... - // 10. Optionally, other header fields... - - var ws Websocket - ws.Conn = conn - ws.Bufrw = bufrw - ws.IsClient = false - ws.MaxMessageSize = handler.Config.MaxMessageSize - - // See RFC 6455 section 4.2.2, item 5 for these steps. - - // 1. A Status-Line with a 101 response code as per RFC 2616. - bufrw.WriteString(fmt.Sprintf("HTTP/1.0 %d %s\r\n", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))) - // 2. An |Upgrade| header field with value "websocket" as per RFC 2616. - w.Header().Set("Upgrade", "websocket") - // 3. A |Connection| header field with value "Upgrade". - w.Header().Set("Connection", "Upgrade") - // 4. A |Sec-WebSocket-Accept| header field. The value of this header - // field is constructed by concatenating /key/, defined above in step 4 - // in Section 4.2.2, with the string - // "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of this - // concatenated value to obtain a 20-byte value and base64-encoding (see - // Section 4 of [RFC4648]) this 20-byte hash. - const magicGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" - acceptKey := base64.StdEncoding.EncodeToString(sha1Hash(websocketKey + magicGUID)) - w.Header().Set("Sec-WebSocket-Accept", acceptKey) - // 5. Optionally, a |Sec-WebSocket-Protocol| header field, with a value - // /subprotocol/ as defined in step 4 in Section 4.2.2. - for _, clientProto := range clientProtocols { - for _, serverProto := range handler.Config.Subprotocols { - if clientProto == serverProto { - ws.Subprotocol = clientProto - w.Header().Set("Sec-WebSocket-Protocol", clientProto) - break - } - } - } - // 6. Optionally, a |Sec-WebSocket-Extensions| header field... - w.Header().Write(bufrw) - bufrw.WriteString("\r\n") - bufrw.Flush() - - // Call the WebSocket-specific handler. - handler.WebsocketCallback(&ws) -} - -// Return an http.Handler with the given callback function. -func (config *WebsocketConfig) Handler(callback func(*Websocket)) http.Handler { - return &WebSocketHTTPHandler{config, callback} -} diff --git a/websocket-transport/src/websocket/websocket.go b/websocket-transport/src/websocket/websocket.go new file mode 100644 index 0000000..617b7ff --- /dev/null +++ b/websocket-transport/src/websocket/websocket.go @@ -0,0 +1,432 @@ +// WebSocket library. Only the RFC 6455 variety of WebSocket is supported. +// +// Reading and writing is strictly per-frame (or per-message). There is no way +// to partially read a frame. WebsocketConfig.MaxMessageSize affords control of +// the maximum buffering of messages. +// +// The reason for using this custom implementation instead of +// code.google.com/p/go.net/websocket is that the latter has problems with long +// messages and does not support server subprotocols. +// "Denial of Service Protection in Go HTTP Servers" +// https://code.google.com/p/go/issues/detail?id=2093 +// "go.websocket: Read/Copy fail with long frames" +// https://code.google.com/p/go/issues/detail?id=2134 +// http://golang.org/pkg/net/textproto/#pkg-bugs +// "To let callers manage exposure to denial of service attacks, Reader should +// allow them to set and reset a limit on the number of bytes read from the +// connection." +// "websocket.Dial doesn't limit response header length as http.Get does" +// https://groups.google.com/forum/?fromgroups=#!topic/golang-nuts/2Tge6U8-QYI +// +// Example usage: +// +// func doSomething(ws *Websocket) { +// } +// var config WebsocketConfig +// config.Subprotocols = []string{"base64"} +// config.MaxMessageSize = 2500 +// http.Handle("/", config.Handler(doSomething)) +// err = http.ListenAndServe(":8080", nil) + +package websocket + +import ( + "bufio" + "bytes" + "crypto/rand" + "crypto/sha1" + "encoding/base64" + "encoding/binary" + "errors" + "fmt" + "io" + "net" + "net/http" + "strings" +) + +// Settings for potential WebSocket connections. Subprotocols is a list of +// supported subprotocols as in RFC 6455 section 1.9. When answering client +// requests, the first of the client's requests subprotocols that is also in +// this list (if any) will be used as the subprotocol for the connection. +// MaxMessageSize is a limit on buffering messages. +type WebsocketConfig struct { + Subprotocols []string + MaxMessageSize int +} + +// Representation of a WebSocket frame. The Payload is always without masking. +type WebsocketFrame struct { + Fin bool + Opcode byte + Payload []byte +} + +// Return true iff the frame's opcode says it is a control frame. +func (frame *WebsocketFrame) IsControl() bool { + return (frame.Opcode & 0x08) != 0 +} + +// Representation of a WebSocket message. The Payload is always without masking. +type WebsocketMessage struct { + Opcode byte + Payload []byte +} + +// A WebSocket connection after hijacking from HTTP. +type Websocket struct { + // Conn and ReadWriter from http.ResponseWriter.Hijack. + Conn net.Conn + Bufrw *bufio.ReadWriter + // Whether we are a client or a server has implications for masking. + IsClient bool + // Set from a parent WebsocketConfig. + MaxMessageSize int + // The single selected subprotocol after negotiation, or "". + Subprotocol string + // Buffer for message payloads, which may be interrupted by control + // messages. + messageBuf bytes.Buffer +} + +func applyMask(payload []byte, maskKey [4]byte) { + for i := 0; i < len(payload); i++ { + payload[i] = payload[i] ^ maskKey[i%4] + } +} + +func (ws *Websocket) maxMessageSize() int { + if ws.MaxMessageSize == 0 { + return 64000 + } + return ws.MaxMessageSize +} + +// Read a single frame from the WebSocket. +func (ws *Websocket) ReadFrame() (frame WebsocketFrame, err error) { + var b byte + err = binary.Read(ws.Bufrw, binary.BigEndian, &b) + if err != nil { + return + } + frame.Fin = (b & 0x80) != 0 + frame.Opcode = b & 0x0f + err = binary.Read(ws.Bufrw, binary.BigEndian, &b) + if err != nil { + return + } + masked := (b & 0x80) != 0 + + payloadLen := uint64(b & 0x7f) + if payloadLen == 126 { + var short uint16 + err = binary.Read(ws.Bufrw, binary.BigEndian, &short) + if err != nil { + return + } + payloadLen = uint64(short) + } else if payloadLen == 127 { + var long uint64 + err = binary.Read(ws.Bufrw, binary.BigEndian, &long) + if err != nil { + return + } + payloadLen = long + } + if payloadLen > uint64(ws.maxMessageSize()) { + err = errors.New(fmt.Sprintf("frame payload length of %d exceeds maximum of %d", payloadLen, ws.MaxMessageSize)) + return + } + + maskKey := [4]byte{} + if masked { + if ws.IsClient { + err = errors.New("client got masked frame") + return + } + err = binary.Read(ws.Bufrw, binary.BigEndian, &maskKey) + if err != nil { + return + } + } else { + if !ws.IsClient { + err = errors.New("server got unmasked frame") + return + } + } + + frame.Payload = make([]byte, payloadLen) + _, err = io.ReadFull(ws.Bufrw, frame.Payload) + if err != nil { + return + } + if masked { + applyMask(frame.Payload, maskKey) + } + + return frame, nil +} + +// Read a single message from the WebSocket. Multiple fragmented frames are +// combined into a single message before being returned. Non-control messages +// may be interrupted by control frames. The control frames are returned as +// individual messages before the message that they interrupt. +func (ws *Websocket) ReadMessage() (message WebsocketMessage, err error) { + var opcode byte = 0 + for { + var frame WebsocketFrame + frame, err = ws.ReadFrame() + if err != nil { + return + } + if frame.IsControl() { + if !frame.Fin { + err = errors.New("control frame has fin bit unset") + return + } + message.Opcode = frame.Opcode + message.Payload = frame.Payload + return message, nil + } + + if opcode == 0 { + if frame.Opcode == 0 { + err = errors.New("first frame has opcode 0") + return + } + opcode = frame.Opcode + } else { + if frame.Opcode != 0 { + err = errors.New(fmt.Sprintf("non-first frame has nonzero opcode %d", frame.Opcode)) + return + } + } + if ws.messageBuf.Len()+len(frame.Payload) > ws.MaxMessageSize { + err = errors.New(fmt.Sprintf("message payload length of %d exceeds maximum of %d", + ws.messageBuf.Len()+len(frame.Payload), ws.MaxMessageSize)) + return + } + ws.messageBuf.Write(frame.Payload) + if frame.Fin { + break + } + } + message.Opcode = opcode + message.Payload = ws.messageBuf.Bytes() + ws.messageBuf.Reset() + + return message, nil +} + +// Write a single frame to the WebSocket stream. Destructively masks payload in +// place if ws.IsClient. Frames are always unfragmented. +func (ws *Websocket) WriteFrame(opcode byte, payload []byte) (err error) { + if opcode >= 16 { + err = errors.New(fmt.Sprintf("opcode %d is >= 16", opcode)) + return + } + ws.Bufrw.WriteByte(0x80 | opcode) + + var maskBit byte + var maskKey [4]byte + if ws.IsClient { + _, err = io.ReadFull(rand.Reader, maskKey[:]) + if err != nil { + return + } + applyMask(payload, maskKey) + maskBit = 0x80 + } else { + maskBit = 0x00 + } + + if len(payload) < 126 { + ws.Bufrw.WriteByte(maskBit | byte(len(payload))) + } else if len(payload) <= 0xffff { + ws.Bufrw.WriteByte(maskBit | 126) + binary.Write(ws.Bufrw, binary.BigEndian, uint16(len(payload))) + } else { + ws.Bufrw.WriteByte(maskBit | 127) + binary.Write(ws.Bufrw, binary.BigEndian, uint64(len(payload))) + } + + if ws.IsClient { + _, err = ws.Bufrw.Write(maskKey[:]) + if err != nil { + return + } + } + _, err = ws.Bufrw.Write(payload) + if err != nil { + return + } + + ws.Bufrw.Flush() + + return +} + +// Write a single message to the WebSocket stream. Destructively masks payload +// in place if ws.IsClient. Messages are always sent as a single unfragmented +// frame. +func (ws *Websocket) WriteMessage(opcode byte, payload []byte) (err error) { + return ws.WriteFrame(opcode, payload) +} + +// Split a string on commas and trim whitespace. +func commaSplit(s string) []string { + var result []string + if strings.TrimSpace(s) == "" { + return result + } + for _, e := range strings.Split(s, ",") { + result = append(result, strings.TrimSpace(e)) + } + return result +} + +// Returns true iff one of the strings in haystack is needle. +func containsCase(haystack []string, needle string) bool { + for _, e := range haystack { + if strings.ToLower(e) == strings.ToLower(needle) { + return true + } + } + return false +} + +// One-step SHA-1 hash of a string. +func sha1Hash(data string) []byte { + h := sha1.New() + h.Write([]byte(data)) + return h.Sum(nil) +} + +func httpError(w http.ResponseWriter, bufrw *bufio.ReadWriter, code int) { + w.Header().Set("Connection", "close") + bufrw.WriteString(fmt.Sprintf("HTTP/1.0 %d %s\r\n", code, http.StatusText(code))) + w.Header().Write(bufrw) + bufrw.WriteString("\r\n") + bufrw.Flush() +} + +// An implementation of http.Handler with a WebsocketConfig. The ServeHTTP +// function calls websocketCallback assuming WebSocket HTTP negotiation is +// successful. +type WebSocketHTTPHandler struct { + Config *WebsocketConfig + WebsocketCallback func(*Websocket) +} + +// Implements the http.Handler interface. +func (handler *WebSocketHTTPHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + conn, bufrw, err := w.(http.Hijacker).Hijack() + if err != nil { + http.Error(w, err.Error(), http.StatusInternalServerError) + return + } + defer conn.Close() + + // See RFC 6455 section 4.2.1 for this sequence of checks. + + // 1. An HTTP/1.1 or higher GET request, including a "Request-URI"... + if req.Method != "GET" { + httpError(w, bufrw, http.StatusMethodNotAllowed) + return + } + if req.URL.Path != "/" { + httpError(w, bufrw, http.StatusNotFound) + return + } + // 2. A |Host| header field containing the server's authority. + // We deliberately skip this test. + // 3. An |Upgrade| header field containing the value "websocket", + // treated as an ASCII case-insensitive value. + if !containsCase(commaSplit(req.Header.Get("Upgrade")), "websocket") { + httpError(w, bufrw, http.StatusBadRequest) + return + } + // 4. A |Connection| header field that includes the token "Upgrade", + // treated as an ASCII case-insensitive value. + if !containsCase(commaSplit(req.Header.Get("Connection")), "Upgrade") { + httpError(w, bufrw, http.StatusBadRequest) + return + } + // 5. A |Sec-WebSocket-Key| header field with a base64-encoded value + // that, when decoded, is 16 bytes in length. + websocketKey := req.Header.Get("Sec-WebSocket-Key") + key, err := base64.StdEncoding.DecodeString(websocketKey) + if err != nil || len(key) != 16 { + httpError(w, bufrw, http.StatusBadRequest) + return + } + // 6. A |Sec-WebSocket-Version| header field, with a value of 13. + // We also allow 8 from draft-ietf-hybi-thewebsocketprotocol-10. + var knownVersions = []string{"8", "13"} + websocketVersion := req.Header.Get("Sec-WebSocket-Version") + if !containsCase(knownVersions, websocketVersion) { + // "If this version does not match a version understood by the + // server, the server MUST abort the WebSocket handshake + // described in this section and instead send an appropriate + // HTTP error code (such as 426 Upgrade Required) and a + // |Sec-WebSocket-Version| header field indicating the + // version(s) the server is capable of understanding." + w.Header().Set("Sec-WebSocket-Version", strings.Join(knownVersions, ", ")) + httpError(w, bufrw, 426) + return + } + // 7. Optionally, an |Origin| header field. + // 8. Optionally, a |Sec-WebSocket-Protocol| header field, with a list of + // values indicating which protocols the client would like to speak, ordered + // by preference. + clientProtocols := commaSplit(req.Header.Get("Sec-WebSocket-Protocol")) + // 9. Optionally, a |Sec-WebSocket-Extensions| header field... + // 10. Optionally, other header fields... + + var ws Websocket + ws.Conn = conn + ws.Bufrw = bufrw + ws.IsClient = false + ws.MaxMessageSize = handler.Config.MaxMessageSize + + // See RFC 6455 section 4.2.2, item 5 for these steps. + + // 1. A Status-Line with a 101 response code as per RFC 2616. + bufrw.WriteString(fmt.Sprintf("HTTP/1.0 %d %s\r\n", http.StatusSwitchingProtocols, http.StatusText(http.StatusSwitchingProtocols))) + // 2. An |Upgrade| header field with value "websocket" as per RFC 2616. + w.Header().Set("Upgrade", "websocket") + // 3. A |Connection| header field with value "Upgrade". + w.Header().Set("Connection", "Upgrade") + // 4. A |Sec-WebSocket-Accept| header field. The value of this header + // field is constructed by concatenating /key/, defined above in step 4 + // in Section 4.2.2, with the string + // "258EAFA5-E914-47DA-95CA-C5AB0DC85B11", taking the SHA-1 hash of this + // concatenated value to obtain a 20-byte value and base64-encoding (see + // Section 4 of [RFC4648]) this 20-byte hash. + const magicGUID = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11" + acceptKey := base64.StdEncoding.EncodeToString(sha1Hash(websocketKey + magicGUID)) + w.Header().Set("Sec-WebSocket-Accept", acceptKey) + // 5. Optionally, a |Sec-WebSocket-Protocol| header field, with a value + // /subprotocol/ as defined in step 4 in Section 4.2.2. + for _, clientProto := range clientProtocols { + for _, serverProto := range handler.Config.Subprotocols { + if clientProto == serverProto { + ws.Subprotocol = clientProto + w.Header().Set("Sec-WebSocket-Protocol", clientProto) + break + } + } + } + // 6. Optionally, a |Sec-WebSocket-Extensions| header field... + w.Header().Write(bufrw) + bufrw.WriteString("\r\n") + bufrw.Flush() + + // Call the WebSocket-specific handler. + handler.WebsocketCallback(&ws) +} + +// Return an http.Handler with the given callback function. +func (config *WebsocketConfig) Handler(callback func(*Websocket)) http.Handler { + return &WebSocketHTTPHandler{config, callback} +}
tor-commits@lists.torproject.org