2023-02-20 11:55:50 +01:00
|
|
|
//go:build !js || !wasm
|
|
|
|
|
|
|
|
package zocket
|
2020-03-11 16:07:14 +01:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bufio"
|
|
|
|
"bytes"
|
|
|
|
"crypto/sha1"
|
|
|
|
"encoding/base64"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
2024-08-31 13:08:44 +02:00
|
|
|
"udico.de/util/log"
|
2020-03-11 16:07:14 +01:00
|
|
|
)
|
|
|
|
|
|
|
|
// A Listener as defined in net.Listener for accepting WebSocket connections.
|
|
|
|
type Listener struct {
|
|
|
|
cConn chan net.Conn
|
|
|
|
}
|
|
|
|
|
|
|
|
// NewListener creates a new listener for WebSocket connections.
|
|
|
|
// You need to register the Listener as a HTTP handler in order for it to become operative.
|
|
|
|
func NewListener() Listener {
|
|
|
|
return Listener{
|
|
|
|
cConn: make(chan net.Conn),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implement the net.Listener interface
|
|
|
|
|
|
|
|
// Accept waits for and returns the next connection to the listener.
|
|
|
|
//
|
|
|
|
// The Connection may be treated like any other streaming network connection,
|
|
|
|
// but wraps the traffic internally to WebSocket frames.
|
|
|
|
//
|
|
|
|
// This function blocks.
|
|
|
|
func (l Listener) Accept() (net.Conn, error) {
|
|
|
|
tConn, ok := <-l.cConn
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("socket closed")
|
|
|
|
}
|
|
|
|
ret := ServerConnection{
|
|
|
|
conn: tConn,
|
|
|
|
}
|
|
|
|
return ret, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Close closes the listener.
|
2024-08-31 13:08:44 +02:00
|
|
|
// Any blocked Accept() operations will be unblocked and return errors.
|
2020-03-11 16:07:14 +01:00
|
|
|
func (l Listener) Close() error {
|
|
|
|
select {
|
2023-02-20 11:55:50 +01:00
|
|
|
case _, ok := <-l.cConn:
|
|
|
|
// either the channel closed or we just read a connection from it.
|
|
|
|
// we discard the connection, since we're about to close the channel anyways
|
|
|
|
if !ok {
|
|
|
|
return errors.New("channel already closed")
|
|
|
|
}
|
|
|
|
default:
|
|
|
|
// something blocks. the channel is alive
|
2020-03-11 16:07:14 +01:00
|
|
|
}
|
|
|
|
close(l.cConn)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Addr returns the listener's network address.
|
|
|
|
// By their nature, WebSocket connections are upgraded HTTP connections and don't have
|
|
|
|
// a real end point address. So a default address is always returned.
|
|
|
|
func (l Listener) Addr() net.Addr {
|
|
|
|
return addr
|
|
|
|
}
|
|
|
|
|
|
|
|
// Implement http handler interface
|
|
|
|
|
|
|
|
// ServeHTTP handles a WebSocket upgrade request.
|
|
|
|
// You should make sure, a call to this function is really done on an update request,
|
|
|
|
// since it would respond with a http error otherwise.
|
|
|
|
func (l Listener) ServeHTTP(resp http.ResponseWriter, req *http.Request) {
|
|
|
|
// Sanity checks:
|
|
|
|
// - method is HTTP GET
|
|
|
|
// - has request URI
|
2024-08-31 14:37:31 +02:00
|
|
|
// - has Host header -> no! this check should be omitted
|
2020-03-11 16:07:14 +01:00
|
|
|
// - has Connection: Upgrade
|
|
|
|
// - has Upgrade: websocket
|
|
|
|
// - has Sec-WebSocket-Version: 13
|
|
|
|
// - has Sec-WebSocket-Key
|
|
|
|
// - Origin: is optional
|
|
|
|
// - Sec-WebSocket-Protocol is optional
|
|
|
|
// - Sec-WebSocket-Extensions is optional
|
|
|
|
|
|
|
|
if req.Method != "GET" {
|
2024-08-31 13:08:44 +02:00
|
|
|
log.ERROR.To(logger).Msg("Invalid method!")
|
2020-03-11 16:07:14 +01:00
|
|
|
resp.WriteHeader(http.StatusMethodNotAllowed)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
if req.RequestURI == "" {
|
2024-08-31 13:08:44 +02:00
|
|
|
log.ERROR.To(logger).Msg("Invalid Request URI: '%v'", req.RequestURI)
|
2020-03-11 16:07:14 +01:00
|
|
|
resp.WriteHeader(http.StatusNotAcceptable) // ???
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
//if req.Header.Get("Host") == "" {
|
|
|
|
// log.Error("No Host")
|
|
|
|
// resp.WriteHeader(http.StatusExpectationFailed) // ???
|
|
|
|
// return
|
|
|
|
//}
|
|
|
|
|
2024-08-31 14:01:30 +02:00
|
|
|
head := &zocketHeader{req.Header}
|
|
|
|
if !head.Contains("Connection", "Upgrade") {
|
2024-08-31 13:08:44 +02:00
|
|
|
log.ERROR.To(logger).Msg("Connection != Upgrade")
|
2020-03-11 16:07:14 +01:00
|
|
|
resp.WriteHeader(http.StatusUpgradeRequired)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-31 14:01:30 +02:00
|
|
|
if head.Get("Upgrade") != "websocket" {
|
2024-08-31 13:08:44 +02:00
|
|
|
log.ERROR.To(logger).Msg("Upgrade != websocket")
|
2020-03-11 16:07:14 +01:00
|
|
|
resp.WriteHeader(http.StatusUpgradeRequired)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-31 14:01:30 +02:00
|
|
|
if head.Get("Sec-WebSocket-Version") != "13" {
|
2024-08-31 13:08:44 +02:00
|
|
|
log.ERROR.To(logger).Msg("Invalid WebSocket Version")
|
2020-03-11 16:07:14 +01:00
|
|
|
resp.WriteHeader(http.StatusUpgradeRequired)
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
2024-08-31 14:37:31 +02:00
|
|
|
log.TRACE.To(logger).If(func(msg log.Fn) {
|
|
|
|
msg("Dumping HTTP headers:")
|
|
|
|
for k, v := range req.Header {
|
|
|
|
msg(" %v: %v", k, v[0])
|
|
|
|
}
|
|
|
|
})
|
2020-03-11 16:07:14 +01:00
|
|
|
|
2024-08-31 14:01:30 +02:00
|
|
|
wsKey := head.Get("Sec-Websocket-Key")
|
2020-03-11 16:07:14 +01:00
|
|
|
wsKey += "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"
|
|
|
|
shakey := sha1.Sum([]byte(wsKey))
|
|
|
|
wsKey = base64.StdEncoding.EncodeToString(shakey[:])
|
2024-08-31 14:37:31 +02:00
|
|
|
log.DEBUG.To(logger).Msg("Generated WebSocket-Accept: %v", wsKey)
|
2020-03-11 16:07:14 +01:00
|
|
|
// NOPE: we need to hijack the connection and send it manually!
|
|
|
|
//resp.Header().Set("Upgrade", "websocket")
|
|
|
|
//resp.Header().Set("Connection", "Upgrade")
|
|
|
|
//resp.Header().Set("Sec-WebSocket-Accept", wsKey)
|
|
|
|
//resp.WriteHeader(101)
|
|
|
|
|
|
|
|
h, ok := resp.(http.Hijacker)
|
|
|
|
if !ok {
|
2024-08-31 13:08:44 +02:00
|
|
|
log.ERROR.To(logger).Msg("Missing Hijacker extension on responsewriter")
|
2020-03-11 16:07:14 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
var brw *bufio.ReadWriter // only used to check for data sent by the client (which is disallowed)
|
|
|
|
conn, brw, err := h.Hijack() // returns the connection and the readwriter to operate on
|
|
|
|
if err != nil {
|
2024-08-31 13:08:44 +02:00
|
|
|
log.ERROR.To(logger).Err(err).Msg("cannot hijack connection")
|
2020-03-11 16:07:14 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
buf := &bytes.Buffer{}
|
|
|
|
buf.WriteString(fmt.Sprintf("%v 101 Switching Protocols\r\n", req.Proto)) // per RFC: HTTP/1.1 or higher
|
|
|
|
buf.WriteString("Upgrade: websocket\r\n")
|
|
|
|
buf.WriteString("Connection: Upgrade\r\n")
|
|
|
|
buf.WriteString(fmt.Sprintf("Sec-WebSocket-Accept: %v\r\n\r\n", wsKey))
|
|
|
|
|
|
|
|
// the client must not've sent any data before the handshake is complete
|
|
|
|
if brw.Reader.Buffered() > 0 {
|
|
|
|
conn.Close()
|
2024-08-31 14:37:31 +02:00
|
|
|
log.ERROR.To(logger).Msg("client sent data before handshake!")
|
2020-03-11 16:07:14 +01:00
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
// fire!
|
|
|
|
conn.Write(buf.Bytes())
|
|
|
|
|
|
|
|
l.cConn <- conn
|
|
|
|
}
|