diff options
author | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
---|---|---|
committer | upstream source tree <ports@midipix.org> | 2015-03-15 20:14:05 -0400 |
commit | 554fd8c5195424bdbcabf5de30fdc183aba391bd (patch) | |
tree | 976dc5ab7fddf506dadce60ae936f43f58787092 /libgo/go/websocket | |
download | cbb-gcc-4.6.4-554fd8c5195424bdbcabf5de30fdc183aba391bd.tar.bz2 cbb-gcc-4.6.4-554fd8c5195424bdbcabf5de30fdc183aba391bd.tar.xz |
obtained gcc-4.6.4.tar.bz2 from upstream website;upstream
verified gcc-4.6.4.tar.bz2.sig;
imported gcc-4.6.4 source tree from verified upstream tarball.
downloading a git-generated archive based on the 'upstream' tag
should provide you with a source tree that is binary identical
to the one extracted from the above tarball.
if you have obtained the source via the command 'git clone',
however, do note that line-endings of files in your working
directory might differ from line-endings of the respective
files in the upstream repository.
Diffstat (limited to 'libgo/go/websocket')
-rw-r--r-- | libgo/go/websocket/client.go | 321 | ||||
-rw-r--r-- | libgo/go/websocket/server.go | 219 | ||||
-rw-r--r-- | libgo/go/websocket/websocket.go | 189 | ||||
-rw-r--r-- | libgo/go/websocket/websocket_test.go | 272 |
4 files changed, 1001 insertions, 0 deletions
diff --git a/libgo/go/websocket/client.go b/libgo/go/websocket/client.go new file mode 100644 index 000000000..091345944 --- /dev/null +++ b/libgo/go/websocket/client.go @@ -0,0 +1,321 @@ +// Copyright 2009 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 + +import ( + "bufio" + "bytes" + "container/vector" + "crypto/tls" + "fmt" + "http" + "io" + "net" + "os" + "rand" + "strings" +) + +type ProtocolError struct { + os.ErrorString +} + +var ( + ErrBadScheme = os.ErrorString("bad scheme") + ErrBadStatus = &ProtocolError{"bad status"} + ErrBadUpgrade = &ProtocolError{"missing or bad upgrade"} + ErrBadWebSocketOrigin = &ProtocolError{"missing or bad WebSocket-Origin"} + ErrBadWebSocketLocation = &ProtocolError{"missing or bad WebSocket-Location"} + ErrBadWebSocketProtocol = &ProtocolError{"missing or bad WebSocket-Protocol"} + ErrChallengeResponse = &ProtocolError{"mismatch challange/response"} + secKeyRandomChars [0x30 - 0x21 + 0x7F - 0x3A]byte +) + +type DialError struct { + URL string + Protocol string + Origin string + Error os.Error +} + +func (e *DialError) String() string { + return "websocket.Dial " + e.URL + ": " + e.Error.String() +} + +func init() { + i := 0 + for ch := byte(0x21); ch < 0x30; ch++ { + secKeyRandomChars[i] = ch + i++ + } + for ch := byte(0x3a); ch < 0x7F; ch++ { + secKeyRandomChars[i] = ch + i++ + } +} + +type handshaker func(resourceName, host, origin, location, protocol string, br *bufio.Reader, bw *bufio.Writer) os.Error + +// newClient creates a new Web Socket client connection. +func newClient(resourceName, host, origin, location, protocol string, rwc io.ReadWriteCloser, handshake handshaker) (ws *Conn, err os.Error) { + br := bufio.NewReader(rwc) + bw := bufio.NewWriter(rwc) + err = handshake(resourceName, host, origin, location, protocol, br, bw) + if err != nil { + return + } + buf := bufio.NewReadWriter(br, bw) + ws = newConn(origin, location, protocol, buf, rwc) + return +} + +/* +Dial opens a new client connection to a Web Socket. + +A trivial example client: + + package main + + import ( + "websocket" + "strings" + ) + + func main() { + ws, err := websocket.Dial("ws://localhost/ws", "", "http://localhost/"); + if err != nil { + panic("Dial: " + err.String()) + } + if _, err := ws.Write([]byte("hello, world!\n")); err != nil { + panic("Write: " + err.String()) + } + var msg = make([]byte, 512); + if n, err := ws.Read(msg); err != nil { + panic("Read: " + err.String()) + } + // use msg[0:n] + } +*/ +func Dial(url, protocol, origin string) (ws *Conn, err os.Error) { + var client net.Conn + + parsedUrl, err := http.ParseURL(url) + if err != nil { + goto Error + } + + switch parsedUrl.Scheme { + case "ws": + client, err = net.Dial("tcp", "", parsedUrl.Host) + + case "wss": + client, err = tls.Dial("tcp", "", parsedUrl.Host, nil) + + default: + err = ErrBadScheme + } + if err != nil { + goto Error + } + + ws, err = newClient(parsedUrl.RawPath, parsedUrl.Host, origin, url, protocol, client, handshake) + if err != nil { + goto Error + } + return + +Error: + return nil, &DialError{url, protocol, origin, err} +} + +/* +Generates handshake key as described in 4.1 Opening handshake step 16 to 22. +cf. http://www.whatwg.org/specs/web-socket-protocol/ +*/ +func generateKeyNumber() (key string, number uint32) { + // 16. Let /spaces_n/ be a random integer from 1 to 12 inclusive. + spaces := rand.Intn(12) + 1 + + // 17. Let /max_n/ be the largest integer not greater than + // 4,294,967,295 divided by /spaces_n/ + max := int(4294967295 / uint32(spaces)) + + // 18. Let /number_n/ be a random integer from 0 to /max_n/ inclusive. + number = uint32(rand.Intn(max + 1)) + + // 19. Let /product_n/ be the result of multiplying /number_n/ and + // /spaces_n/ together. + product := number * uint32(spaces) + + // 20. Let /key_n/ be a string consisting of /product_n/, expressed + // in base ten using the numerals in the range U+0030 DIGIT ZERO (0) + // to U+0039 DIGIT NINE (9). + key = fmt.Sprintf("%d", product) + + // 21. Insert between one and twelve random characters from the ranges + // U+0021 to U+002F and U+003A to U+007E into /key_n/ at random + // positions. + n := rand.Intn(12) + 1 + for i := 0; i < n; i++ { + pos := rand.Intn(len(key)) + 1 + ch := secKeyRandomChars[rand.Intn(len(secKeyRandomChars))] + key = key[0:pos] + string(ch) + key[pos:] + } + + // 22. Insert /spaces_n/ U+0020 SPACE characters into /key_n/ at random + // positions other than the start or end of the string. + for i := 0; i < spaces; i++ { + pos := rand.Intn(len(key)-1) + 1 + key = key[0:pos] + " " + key[pos:] + } + + return +} + +/* +Generates handshake key_3 as described in 4.1 Opening handshake step 26. +cf. http://www.whatwg.org/specs/web-socket-protocol/ +*/ +func generateKey3() (key []byte) { + // 26. Let /key3/ be a string consisting of eight random bytes (or + // equivalently, a random 64 bit integer encoded in big-endian order). + key = make([]byte, 8) + for i := 0; i < 8; i++ { + key[i] = byte(rand.Intn(256)) + } + return +} + +/* +Web Socket protocol handshake based on +http://www.whatwg.org/specs/web-socket-protocol/ +(draft of http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol) +*/ +func handshake(resourceName, host, origin, location, protocol string, br *bufio.Reader, bw *bufio.Writer) (err os.Error) { + // 4.1. Opening handshake. + // Step 5. send a request line. + bw.WriteString("GET " + resourceName + " HTTP/1.1\r\n") + + // Step 6-14. push request headers in fields. + var fields vector.StringVector + fields.Push("Upgrade: WebSocket\r\n") + fields.Push("Connection: Upgrade\r\n") + fields.Push("Host: " + host + "\r\n") + fields.Push("Origin: " + origin + "\r\n") + if protocol != "" { + fields.Push("Sec-WebSocket-Protocol: " + protocol + "\r\n") + } + // TODO(ukai): Step 15. send cookie if any. + + // Step 16-23. generate keys and push Sec-WebSocket-Key<n> in fields. + key1, number1 := generateKeyNumber() + key2, number2 := generateKeyNumber() + fields.Push("Sec-WebSocket-Key1: " + key1 + "\r\n") + fields.Push("Sec-WebSocket-Key2: " + key2 + "\r\n") + + // Step 24. shuffle fields and send them out. + for i := 1; i < len(fields); i++ { + j := rand.Intn(i) + fields[i], fields[j] = fields[j], fields[i] + } + for i := 0; i < len(fields); i++ { + bw.WriteString(fields[i]) + } + // Step 25. send CRLF. + bw.WriteString("\r\n") + + // Step 26. genearte 8 bytes random key. + key3 := generateKey3() + // Step 27. send it out. + bw.Write(key3) + if err = bw.Flush(); err != nil { + return + } + + // Step 28-29, 32-40. read response from server. + resp, err := http.ReadResponse(br, "GET") + if err != nil { + return err + } + // Step 30. check response code is 101. + if resp.StatusCode != 101 { + return ErrBadStatus + } + + // Step 41. check websocket headers. + if resp.Header["Upgrade"] != "WebSocket" || + strings.ToLower(resp.Header["Connection"]) != "upgrade" { + return ErrBadUpgrade + } + + if resp.Header["Sec-Websocket-Origin"] != origin { + return ErrBadWebSocketOrigin + } + + if resp.Header["Sec-Websocket-Location"] != location { + return ErrBadWebSocketLocation + } + + if protocol != "" && resp.Header["Sec-Websocket-Protocol"] != protocol { + return ErrBadWebSocketProtocol + } + + // Step 42-43. get expected data from challange data. + expected, err := getChallengeResponse(number1, number2, key3) + if err != nil { + return err + } + + // Step 44. read 16 bytes from server. + reply := make([]byte, 16) + if _, err = io.ReadFull(br, reply); err != nil { + return err + } + + // Step 45. check the reply equals to expected data. + if !bytes.Equal(expected, reply) { + return ErrChallengeResponse + } + // WebSocket connection is established. + return +} + +/* +Handhake described in (soon obsolete) +draft-hixie-thewebsocket-protocol-75. +*/ +func draft75handshake(resourceName, host, origin, location, protocol string, br *bufio.Reader, bw *bufio.Writer) (err os.Error) { + bw.WriteString("GET " + resourceName + " HTTP/1.1\r\n") + bw.WriteString("Upgrade: WebSocket\r\n") + bw.WriteString("Connection: Upgrade\r\n") + bw.WriteString("Host: " + host + "\r\n") + bw.WriteString("Origin: " + origin + "\r\n") + if protocol != "" { + bw.WriteString("WebSocket-Protocol: " + protocol + "\r\n") + } + bw.WriteString("\r\n") + bw.Flush() + resp, err := http.ReadResponse(br, "GET") + if err != nil { + return + } + if resp.Status != "101 Web Socket Protocol Handshake" { + return ErrBadStatus + } + if resp.Header["Upgrade"] != "WebSocket" || + resp.Header["Connection"] != "Upgrade" { + return ErrBadUpgrade + } + if resp.Header["Websocket-Origin"] != origin { + return ErrBadWebSocketOrigin + } + if resp.Header["Websocket-Location"] != location { + return ErrBadWebSocketLocation + } + if protocol != "" && resp.Header["Websocket-Protocol"] != protocol { + return ErrBadWebSocketProtocol + } + return +} diff --git a/libgo/go/websocket/server.go b/libgo/go/websocket/server.go new file mode 100644 index 000000000..dd797f24e --- /dev/null +++ b/libgo/go/websocket/server.go @@ -0,0 +1,219 @@ +// Copyright 2009 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 + +import ( + "http" + "io" + "strings" +) + +/* +Handler is an interface to a WebSocket. + +A trivial example server: + + package main + + import ( + "http" + "io" + "websocket" + ) + + // Echo the data received on the Web Socket. + func EchoServer(ws *websocket.Conn) { + io.Copy(ws, ws); + } + + func main() { + http.Handle("/echo", websocket.Handler(EchoServer)); + err := http.ListenAndServe(":12345", nil); + if err != nil { + panic("ListenAndServe: " + err.String()) + } + } +*/ +type Handler func(*Conn) + +/* +Gets key number from Sec-WebSocket-Key<n>: field as described +in 5.2 Sending the server's opening handshake, 4. +*/ +func getKeyNumber(s string) (r uint32) { + // 4. Let /key-number_n/ be the digits (characters in the range + // U+0030 DIGIT ZERO (0) to U+0039 DIGIT NINE (9)) in /key_1/, + // interpreted as a base ten integer, ignoring all other characters + // in /key_n/. + r = 0 + for i := 0; i < len(s); i++ { + if s[i] >= '0' && s[i] <= '9' { + r = r*10 + uint32(s[i]) - '0' + } + } + return +} + +// ServeHTTP implements the http.Handler interface for a Web Socket +func (f Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + rwc, buf, err := w.Hijack() + if err != nil { + panic("Hijack failed: " + err.String()) + return + } + // The server should abort the WebSocket connection if it finds + // the client did not send a handshake that matches with protocol + // specification. + defer rwc.Close() + + if req.Method != "GET" { + return + } + // HTTP version can be safely ignored. + + if strings.ToLower(req.Header["Upgrade"]) != "websocket" || + strings.ToLower(req.Header["Connection"]) != "upgrade" { + return + } + + // TODO(ukai): check Host + origin, found := req.Header["Origin"] + if !found { + return + } + + key1, found := req.Header["Sec-Websocket-Key1"] + if !found { + return + } + key2, found := req.Header["Sec-Websocket-Key2"] + if !found { + return + } + key3 := make([]byte, 8) + if _, err := io.ReadFull(buf, key3); err != nil { + return + } + + var location string + if w.UsingTLS() { + location = "wss://" + req.Host + req.URL.RawPath + } else { + location = "ws://" + req.Host + req.URL.RawPath + } + + // Step 4. get key number in Sec-WebSocket-Key<n> fields. + keyNumber1 := getKeyNumber(key1) + keyNumber2 := getKeyNumber(key2) + + // Step 5. get number of spaces in Sec-WebSocket-Key<n> fields. + space1 := uint32(strings.Count(key1, " ")) + space2 := uint32(strings.Count(key2, " ")) + if space1 == 0 || space2 == 0 { + return + } + + // Step 6. key number must be an integral multiple of spaces. + if keyNumber1%space1 != 0 || keyNumber2%space2 != 0 { + return + } + + // Step 7. let part be key number divided by spaces. + part1 := keyNumber1 / space1 + part2 := keyNumber2 / space2 + + // Step 8. let challenge to be concatination of part1, part2 and key3. + // Step 9. get MD5 fingerprint of challenge. + response, err := getChallengeResponse(part1, part2, key3) + if err != nil { + return + } + + // Step 10. send response status line. + buf.WriteString("HTTP/1.1 101 WebSocket Protocol Handshake\r\n") + // Step 11. send response headers. + buf.WriteString("Upgrade: WebSocket\r\n") + buf.WriteString("Connection: Upgrade\r\n") + buf.WriteString("Sec-WebSocket-Location: " + location + "\r\n") + buf.WriteString("Sec-WebSocket-Origin: " + origin + "\r\n") + protocol, found := req.Header["Sec-Websocket-Protocol"] + if found { + buf.WriteString("Sec-WebSocket-Protocol: " + protocol + "\r\n") + } + // Step 12. send CRLF. + buf.WriteString("\r\n") + // Step 13. send response data. + buf.Write(response) + if err := buf.Flush(); err != nil { + return + } + ws := newConn(origin, location, protocol, buf, rwc) + f(ws) +} + + +/* +Draft75Handler is an interface to a WebSocket based on the +(soon obsolete) draft-hixie-thewebsocketprotocol-75. +*/ +type Draft75Handler func(*Conn) + +// ServeHTTP implements the http.Handler interface for a Web Socket. +func (f Draft75Handler) ServeHTTP(w http.ResponseWriter, req *http.Request) { + if req.Method != "GET" || req.Proto != "HTTP/1.1" { + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "Unexpected request") + return + } + if req.Header["Upgrade"] != "WebSocket" { + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "missing Upgrade: WebSocket header") + return + } + if req.Header["Connection"] != "Upgrade" { + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "missing Connection: Upgrade header") + return + } + origin, found := req.Header["Origin"] + if !found { + w.WriteHeader(http.StatusBadRequest) + io.WriteString(w, "missing Origin header") + return + } + + rwc, buf, err := w.Hijack() + if err != nil { + panic("Hijack failed: " + err.String()) + return + } + defer rwc.Close() + + var location string + if w.UsingTLS() { + location = "wss://" + req.Host + req.URL.RawPath + } else { + location = "ws://" + req.Host + req.URL.RawPath + } + + // TODO(ukai): verify origin,location,protocol. + + buf.WriteString("HTTP/1.1 101 Web Socket Protocol Handshake\r\n") + buf.WriteString("Upgrade: WebSocket\r\n") + buf.WriteString("Connection: Upgrade\r\n") + buf.WriteString("WebSocket-Origin: " + origin + "\r\n") + buf.WriteString("WebSocket-Location: " + location + "\r\n") + protocol, found := req.Header["Websocket-Protocol"] + // canonical header key of WebSocket-Protocol. + if found { + buf.WriteString("WebSocket-Protocol: " + protocol + "\r\n") + } + buf.WriteString("\r\n") + if err := buf.Flush(); err != nil { + return + } + ws := newConn(origin, location, protocol, buf, rwc) + f(ws) +} diff --git a/libgo/go/websocket/websocket.go b/libgo/go/websocket/websocket.go new file mode 100644 index 000000000..d5996abe1 --- /dev/null +++ b/libgo/go/websocket/websocket.go @@ -0,0 +1,189 @@ +// Copyright 2009 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. + +// The websocket package implements a client and server for the Web Socket protocol. +// The protocol is defined at http://tools.ietf.org/html/draft-hixie-thewebsocketprotocol +package websocket + +// TODO(ukai): +// better logging. + +import ( + "bufio" + "crypto/md5" + "encoding/binary" + "io" + "net" + "os" +) + +// WebSocketAddr is an implementation of net.Addr for Web Sockets. +type WebSocketAddr string + +// Network returns the network type for a Web Socket, "websocket". +func (addr WebSocketAddr) Network() string { return "websocket" } + +// String returns the network address for a Web Socket. +func (addr WebSocketAddr) String() string { return string(addr) } + +const ( + stateFrameByte = iota + stateFrameLength + stateFrameData + stateFrameTextData +) + +// Conn is a channel to communicate to a Web Socket. +// It implements the net.Conn interface. +type Conn struct { + // The origin URI for the Web Socket. + Origin string + // The location URI for the Web Socket. + Location string + // The subprotocol for the Web Socket. + Protocol string + + buf *bufio.ReadWriter + rwc io.ReadWriteCloser + + // It holds text data in previous Read() that failed with small buffer. + data []byte + reading bool +} + +// newConn creates a new Web Socket. +func newConn(origin, location, protocol string, buf *bufio.ReadWriter, rwc io.ReadWriteCloser) *Conn { + if buf == nil { + br := bufio.NewReader(rwc) + bw := bufio.NewWriter(rwc) + buf = bufio.NewReadWriter(br, bw) + } + ws := &Conn{Origin: origin, Location: location, Protocol: protocol, buf: buf, rwc: rwc} + return ws +} + +// Read implements the io.Reader interface for a Conn. +func (ws *Conn) Read(msg []byte) (n int, err os.Error) { +Frame: + for !ws.reading && len(ws.data) == 0 { + // Beginning of frame, possibly. + b, err := ws.buf.ReadByte() + if err != nil { + return 0, err + } + if b&0x80 == 0x80 { + // Skip length frame. + length := 0 + for { + c, err := ws.buf.ReadByte() + if err != nil { + return 0, err + } + length = length*128 + int(c&0x7f) + if c&0x80 == 0 { + break + } + } + for length > 0 { + _, err := ws.buf.ReadByte() + if err != nil { + return 0, err + } + } + continue Frame + } + // In text mode + if b != 0 { + // Skip this frame + for { + c, err := ws.buf.ReadByte() + if err != nil { + return 0, err + } + if c == '\xff' { + break + } + } + continue Frame + } + ws.reading = true + } + if len(ws.data) == 0 { + ws.data, err = ws.buf.ReadSlice('\xff') + if err == nil { + ws.reading = false + ws.data = ws.data[:len(ws.data)-1] // trim \xff + } + } + n = copy(msg, ws.data) + ws.data = ws.data[n:] + return n, err +} + +// Write implements the io.Writer interface for a Conn. +func (ws *Conn) Write(msg []byte) (n int, err os.Error) { + ws.buf.WriteByte(0) + ws.buf.Write(msg) + ws.buf.WriteByte(0xff) + err = ws.buf.Flush() + return len(msg), err +} + +// Close implements the io.Closer interface for a Conn. +func (ws *Conn) Close() os.Error { return ws.rwc.Close() } + +// LocalAddr returns the WebSocket Origin for the connection. +func (ws *Conn) LocalAddr() net.Addr { return WebSocketAddr(ws.Origin) } + +// RemoteAddr returns the WebSocket locations for the connection. +func (ws *Conn) RemoteAddr() net.Addr { return WebSocketAddr(ws.Location) } + +// SetTimeout sets the connection's network timeout in nanoseconds. +func (ws *Conn) SetTimeout(nsec int64) os.Error { + if conn, ok := ws.rwc.(net.Conn); ok { + return conn.SetTimeout(nsec) + } + return os.EINVAL +} + +// SetReadTimeout sets the connection's network read timeout in nanoseconds. +func (ws *Conn) SetReadTimeout(nsec int64) os.Error { + if conn, ok := ws.rwc.(net.Conn); ok { + return conn.SetReadTimeout(nsec) + } + return os.EINVAL +} + +// SetWritetTimeout sets the connection's network write timeout in nanoseconds. +func (ws *Conn) SetWriteTimeout(nsec int64) os.Error { + if conn, ok := ws.rwc.(net.Conn); ok { + return conn.SetWriteTimeout(nsec) + } + return os.EINVAL +} + +// getChallengeResponse computes the expected response from the +// challenge as described in section 5.1 Opening Handshake steps 42 to +// 43 of http://www.whatwg.org/specs/web-socket-protocol/ +func getChallengeResponse(number1, number2 uint32, key3 []byte) (expected []byte, err os.Error) { + // 41. Let /challenge/ be the concatenation of /number_1/, expressed + // a big-endian 32 bit integer, /number_2/, expressed in a big- + // endian 32 bit integer, and the eight bytes of /key_3/ in the + // order they were sent to the wire. + challenge := make([]byte, 16) + binary.BigEndian.PutUint32(challenge[0:], number1) + binary.BigEndian.PutUint32(challenge[4:], number2) + copy(challenge[8:], key3) + + // 42. Let /expected/ be the MD5 fingerprint of /challenge/ as a big- + // endian 128 bit string. + h := md5.New() + if _, err = h.Write(challenge); err != nil { + return + } + expected = h.Sum() + return +} + +var _ net.Conn = (*Conn)(nil) // compile-time check that *Conn implements net.Conn. diff --git a/libgo/go/websocket/websocket_test.go b/libgo/go/websocket/websocket_test.go new file mode 100644 index 000000000..cc4b9dc18 --- /dev/null +++ b/libgo/go/websocket/websocket_test.go @@ -0,0 +1,272 @@ +// Copyright 2009 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 + +import ( + "bufio" + "bytes" + "fmt" + "http" + "io" + "log" + "net" + "sync" + "testing" +) + +var serverAddr string +var once sync.Once + +func echoServer(ws *Conn) { io.Copy(ws, ws) } + +func startServer() { + l, e := net.Listen("tcp", "127.0.0.1:0") // any available address + if e != nil { + log.Exitf("net.Listen tcp :0 %v", e) + } + serverAddr = l.Addr().String() + log.Print("Test WebSocket server listening on ", serverAddr) + http.Handle("/echo", Handler(echoServer)) + http.Handle("/echoDraft75", Draft75Handler(echoServer)) + go http.Serve(l, nil) +} + +// Test the getChallengeResponse function with values from section +// 5.1 of the specification steps 18, 26, and 43 from +// http://www.whatwg.org/specs/web-socket-protocol/ +func TestChallenge(t *testing.T) { + var part1 uint32 = 777007543 + var part2 uint32 = 114997259 + key3 := []byte{0x47, 0x30, 0x22, 0x2D, 0x5A, 0x3F, 0x47, 0x58} + expected := []byte("0st3Rl&q-2ZU^weu") + + response, err := getChallengeResponse(part1, part2, key3) + if err != nil { + t.Errorf("getChallengeResponse: returned error %v", err) + return + } + if !bytes.Equal(expected, response) { + t.Errorf("getChallengeResponse: expected %q got %q", expected, response) + } +} + +func TestEcho(t *testing.T) { + once.Do(startServer) + + // websocket.Dial() + client, err := net.Dial("tcp", "", serverAddr) + if err != nil { + t.Fatal("dialing", err) + } + ws, err := newClient("/echo", "localhost", "http://localhost", + "ws://localhost/echo", "", client, handshake) + if err != nil { + t.Errorf("WebSocket handshake error: %v", err) + return + } + + msg := []byte("hello, world\n") + if _, err := ws.Write(msg); err != nil { + t.Errorf("Write: %v", err) + } + var actual_msg = make([]byte, 512) + n, err := ws.Read(actual_msg) + if err != nil { + t.Errorf("Read: %v", err) + } + actual_msg = actual_msg[0:n] + if !bytes.Equal(msg, actual_msg) { + t.Errorf("Echo: expected %q got %q", msg, actual_msg) + } + ws.Close() +} + +func TestEchoDraft75(t *testing.T) { + once.Do(startServer) + + // websocket.Dial() + client, err := net.Dial("tcp", "", serverAddr) + if err != nil { + t.Fatal("dialing", err) + } + ws, err := newClient("/echoDraft75", "localhost", "http://localhost", + "ws://localhost/echoDraft75", "", client, draft75handshake) + if err != nil { + t.Errorf("WebSocket handshake: %v", err) + return + } + + msg := []byte("hello, world\n") + if _, err := ws.Write(msg); err != nil { + t.Errorf("Write: error %v", err) + } + var actual_msg = make([]byte, 512) + n, err := ws.Read(actual_msg) + if err != nil { + t.Errorf("Read: error %v", err) + } + actual_msg = actual_msg[0:n] + if !bytes.Equal(msg, actual_msg) { + t.Errorf("Echo: expected %q got %q", msg, actual_msg) + } + ws.Close() +} + +func TestWithQuery(t *testing.T) { + once.Do(startServer) + + client, err := net.Dial("tcp", "", serverAddr) + if err != nil { + t.Fatal("dialing", err) + } + + ws, err := newClient("/echo?q=v", "localhost", "http://localhost", + "ws://localhost/echo?q=v", "", client, handshake) + if err != nil { + t.Errorf("WebSocket handshake: %v", err) + return + } + ws.Close() +} + +func TestWithProtocol(t *testing.T) { + once.Do(startServer) + + client, err := net.Dial("tcp", "", serverAddr) + if err != nil { + t.Fatal("dialing", err) + } + + ws, err := newClient("/echo", "localhost", "http://localhost", + "ws://localhost/echo", "test", client, handshake) + if err != nil { + t.Errorf("WebSocket handshake: %v", err) + return + } + ws.Close() +} + +func TestHTTP(t *testing.T) { + once.Do(startServer) + + // If the client did not send a handshake that matches the protocol + // specification, the server should abort the WebSocket connection. + _, _, err := http.Get(fmt.Sprintf("http://%s/echo", serverAddr)) + if err == nil { + t.Error("Get: unexpected success") + return + } + urlerr, ok := err.(*http.URLError) + if !ok { + t.Errorf("Get: not URLError %#v", err) + return + } + if urlerr.Error != io.ErrUnexpectedEOF { + t.Errorf("Get: error %#v", err) + return + } +} + +func TestHTTPDraft75(t *testing.T) { + once.Do(startServer) + + r, _, err := http.Get(fmt.Sprintf("http://%s/echoDraft75", serverAddr)) + if err != nil { + t.Errorf("Get: error %#v", err) + return + } + if r.StatusCode != http.StatusBadRequest { + t.Errorf("Get: got status %d", r.StatusCode) + } +} + +func TestTrailingSpaces(t *testing.T) { + // http://code.google.com/p/go/issues/detail?id=955 + // The last runs of this create keys with trailing spaces that should not be + // generated by the client. + once.Do(startServer) + for i := 0; i < 30; i++ { + // body + _, err := Dial(fmt.Sprintf("ws://%s/echo", serverAddr), "", + "http://localhost/") + if err != nil { + panic("Dial failed: " + err.String()) + } + } +} + +func TestSmallBuffer(t *testing.T) { + // http://code.google.com/p/go/issues/detail?id=1145 + // Read should be able to handle reading a fragment of a frame. + once.Do(startServer) + + // websocket.Dial() + client, err := net.Dial("tcp", "", serverAddr) + if err != nil { + t.Fatal("dialing", err) + } + ws, err := newClient("/echo", "localhost", "http://localhost", + "ws://localhost/echo", "", client, handshake) + if err != nil { + t.Errorf("WebSocket handshake error: %v", err) + return + } + + msg := []byte("hello, world\n") + if _, err := ws.Write(msg); err != nil { + t.Errorf("Write: %v", err) + } + var small_msg = make([]byte, 8) + n, err := ws.Read(small_msg) + if err != nil { + t.Errorf("Read: %v", err) + } + if !bytes.Equal(msg[:len(small_msg)], small_msg) { + t.Errorf("Echo: expected %q got %q", msg[:len(small_msg)], small_msg) + } + var second_msg = make([]byte, len(msg)) + n, err = ws.Read(second_msg) + if err != nil { + t.Errorf("Read: %v", err) + } + second_msg = second_msg[0:n] + if !bytes.Equal(msg[len(small_msg):], second_msg) { + t.Errorf("Echo: expected %q got %q", msg[len(small_msg):], second_msg) + } + ws.Close() + +} + +func testSkipLengthFrame(t *testing.T) { + b := []byte{'\x80', '\x01', 'x', 0, 'h', 'e', 'l', 'l', 'o', '\xff'} + buf := bytes.NewBuffer(b) + br := bufio.NewReader(buf) + bw := bufio.NewWriter(buf) + ws := newConn("http://127.0.0.1/", "ws://127.0.0.1/", "", bufio.NewReadWriter(br, bw), nil) + msg := make([]byte, 5) + n, err := ws.Read(msg) + if err != nil { + t.Errorf("Read: %v", err) + } + if !bytes.Equal(b[4:8], msg[0:n]) { + t.Errorf("Read: expected %q got %q", msg[4:8], msg[0:n]) + } +} + +func testSkipNoUTF8Frame(t *testing.T) { + b := []byte{'\x01', 'n', '\xff', 0, 'h', 'e', 'l', 'l', 'o', '\xff'} + buf := bytes.NewBuffer(b) + br := bufio.NewReader(buf) + bw := bufio.NewWriter(buf) + ws := newConn("http://127.0.0.1/", "ws://127.0.0.1/", "", bufio.NewReadWriter(br, bw), nil) + msg := make([]byte, 5) + n, err := ws.Read(msg) + if err != nil { + t.Errorf("Read: %v", err) + } + if !bytes.Equal(b[4:8], msg[0:n]) { + t.Errorf("Read: expected %q got %q", msg[4:8], msg[0:n]) + } +} |