2019-10-06 11:15:26 -07:00
|
|
|
package xmpp
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
2019-10-18 11:29:54 -07:00
|
|
|
"encoding/xml"
|
2019-10-16 05:44:22 -07:00
|
|
|
"errors"
|
2019-10-18 11:29:54 -07:00
|
|
|
"fmt"
|
|
|
|
"io"
|
2019-10-06 11:15:26 -07:00
|
|
|
"net"
|
|
|
|
"strings"
|
|
|
|
"time"
|
|
|
|
|
2019-10-18 11:29:54 -07:00
|
|
|
"gosrc.io/xmpp/stanza"
|
2019-10-06 11:15:26 -07:00
|
|
|
"nhooyr.io/websocket"
|
|
|
|
)
|
|
|
|
|
2019-10-15 11:56:11 -07:00
|
|
|
const pingTimeout = time.Duration(5) * time.Second
|
|
|
|
|
2019-10-16 05:44:22 -07:00
|
|
|
var ServerDoesNotSupportXmppOverWebsocket = errors.New("The websocket server does not support the xmpp subprotocol")
|
|
|
|
|
2019-10-06 11:15:26 -07:00
|
|
|
type WebsocketTransport struct {
|
2019-10-10 21:41:15 -07:00
|
|
|
Config TransportConfiguration
|
2019-10-18 11:29:54 -07:00
|
|
|
decoder *xml.Decoder
|
2019-10-06 11:15:26 -07:00
|
|
|
wsConn *websocket.Conn
|
|
|
|
netConn net.Conn
|
2019-10-18 11:29:54 -07:00
|
|
|
logFile io.Writer
|
2019-10-06 11:15:26 -07:00
|
|
|
}
|
|
|
|
|
2019-10-18 11:29:54 -07:00
|
|
|
func (t *WebsocketTransport) Connect() (string, error) {
|
|
|
|
ctx := context.Background()
|
2019-10-06 11:15:26 -07:00
|
|
|
|
2019-10-12 08:50:00 -07:00
|
|
|
if t.Config.ConnectTimeout > 0 {
|
2019-10-18 11:29:54 -07:00
|
|
|
var cancel context.CancelFunc
|
|
|
|
ctx, cancel = context.WithTimeout(ctx, time.Duration(t.Config.ConnectTimeout)*time.Second)
|
2019-10-12 08:50:00 -07:00
|
|
|
defer cancel()
|
|
|
|
}
|
2019-10-06 11:15:26 -07:00
|
|
|
|
2019-10-18 11:29:54 -07:00
|
|
|
wsConn, response, err := websocket.Dial(ctx, t.Config.Address, &websocket.DialOptions{
|
2019-10-16 05:44:22 -07:00
|
|
|
Subprotocols: []string{"xmpp"},
|
|
|
|
})
|
2019-10-12 08:46:53 -07:00
|
|
|
if err != nil {
|
2019-10-18 11:29:54 -07:00
|
|
|
return "", NewConnError(err, true)
|
2019-10-06 11:15:26 -07:00
|
|
|
}
|
2019-10-16 05:44:22 -07:00
|
|
|
if response.Header.Get("Sec-WebSocket-Protocol") != "xmpp" {
|
2019-10-18 11:29:54 -07:00
|
|
|
_ = wsConn.Close(websocket.StatusBadGateway, "Could not negotiate XMPP subprotocol")
|
|
|
|
return "", NewConnError(ServerDoesNotSupportXmppOverWebsocket, true)
|
2019-10-16 05:44:22 -07:00
|
|
|
}
|
2019-10-18 11:29:54 -07:00
|
|
|
|
2019-10-12 08:46:53 -07:00
|
|
|
t.wsConn = wsConn
|
2019-10-18 11:29:54 -07:00
|
|
|
t.netConn = websocket.NetConn(ctx, t.wsConn, websocket.MessageText)
|
|
|
|
|
|
|
|
handshake := fmt.Sprintf("<open xmlns=\"urn:ietf:params:xml:ns:xmpp-framing\" to=\"%s\" version=\"1.0\" />", t.Config.Domain)
|
|
|
|
if _, err = t.Write([]byte(handshake)); err != nil {
|
|
|
|
_ = wsConn.Close(websocket.StatusBadGateway, "XMPP handshake error")
|
|
|
|
return "", NewConnError(err, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
handshakeResponse := make([]byte, 2048)
|
|
|
|
if _, err = t.Read(handshakeResponse); err != nil {
|
|
|
|
_ = wsConn.Close(websocket.StatusBadGateway, "XMPP handshake error")
|
|
|
|
return "", NewConnError(err, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
var openResponse = stanza.WebsocketOpen{}
|
|
|
|
if err = xml.Unmarshal(handshakeResponse, &openResponse); err != nil {
|
|
|
|
_ = wsConn.Close(websocket.StatusBadGateway, "XMPP handshake error")
|
|
|
|
return "", NewConnError(err, false)
|
|
|
|
}
|
|
|
|
|
|
|
|
t.decoder = xml.NewDecoder(t)
|
|
|
|
t.decoder.CharsetReader = t.Config.CharsetReader
|
|
|
|
|
|
|
|
return openResponse.Id, nil
|
2019-10-06 11:15:26 -07:00
|
|
|
}
|
|
|
|
|
2019-10-18 11:29:54 -07:00
|
|
|
func (t WebsocketTransport) StartTLS() error {
|
2019-10-10 22:15:47 -07:00
|
|
|
return TLSNotSupported
|
|
|
|
}
|
|
|
|
|
2019-10-06 11:15:26 -07:00
|
|
|
func (t WebsocketTransport) DoesStartTLS() bool {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2019-10-18 11:29:54 -07:00
|
|
|
func (t WebsocketTransport) GetDecoder() *xml.Decoder {
|
|
|
|
return t.decoder
|
|
|
|
}
|
|
|
|
|
2019-10-10 22:15:47 -07:00
|
|
|
func (t WebsocketTransport) IsSecure() bool {
|
|
|
|
return strings.HasPrefix(t.Config.Address, "wss:")
|
|
|
|
}
|
|
|
|
|
2019-10-15 11:56:11 -07:00
|
|
|
func (t WebsocketTransport) Ping() error {
|
|
|
|
ctx, cancel := context.WithTimeout(context.Background(), pingTimeout)
|
|
|
|
defer cancel()
|
2019-10-18 11:29:54 -07:00
|
|
|
return t.wsConn.Ping(ctx)
|
2019-10-15 11:56:11 -07:00
|
|
|
}
|
|
|
|
|
2019-10-18 11:29:54 -07:00
|
|
|
func (t *WebsocketTransport) Read(p []byte) (n int, err error) {
|
|
|
|
n, err = t.netConn.Read(p)
|
|
|
|
if t.logFile != nil && n > 0 {
|
|
|
|
_, _ = fmt.Fprintf(t.logFile, "RECV:\n%s\n\n", p)
|
|
|
|
}
|
|
|
|
return
|
2019-10-06 11:15:26 -07:00
|
|
|
}
|
|
|
|
|
|
|
|
func (t WebsocketTransport) Write(p []byte) (n int, err error) {
|
2019-10-18 11:29:54 -07:00
|
|
|
if t.logFile != nil {
|
|
|
|
_, _ = fmt.Fprintf(t.logFile, "SEND:\n%s\n\n", p)
|
|
|
|
}
|
2019-10-06 11:15:26 -07:00
|
|
|
return t.netConn.Write(p)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (t WebsocketTransport) Close() error {
|
2019-10-18 11:29:54 -07:00
|
|
|
t.Write([]byte("<close xmlns=\"urn:ietf:params:xml:ns:xmpp-framing\" />"))
|
2019-10-06 11:15:26 -07:00
|
|
|
return t.netConn.Close()
|
|
|
|
}
|
2019-10-18 11:29:54 -07:00
|
|
|
|
|
|
|
func (t *WebsocketTransport) LogTraffic(logFile io.Writer) {
|
|
|
|
t.logFile = logFile
|
|
|
|
}
|