summaryrefslogtreecommitdiff
path: root/libgo/go/websocket
diff options
context:
space:
mode:
authorupstream source tree <ports@midipix.org>2015-03-15 20:14:05 -0400
committerupstream source tree <ports@midipix.org>2015-03-15 20:14:05 -0400
commit554fd8c5195424bdbcabf5de30fdc183aba391bd (patch)
tree976dc5ab7fddf506dadce60ae936f43f58787092 /libgo/go/websocket
downloadcbb-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.go321
-rw-r--r--libgo/go/websocket/server.go219
-rw-r--r--libgo/go/websocket/websocket.go189
-rw-r--r--libgo/go/websocket/websocket_test.go272
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])
+ }
+}