Refactor and move parsing and stanza to a separate package

This commit is contained in:
Mickael Remond 2019-06-26 17:14:52 +02:00
parent 0acf824217
commit 428787d7ab
No known key found for this signature in database
GPG Key ID: E6F6045D79965AA3
51 changed files with 614 additions and 580 deletions

View File

@ -32,6 +32,7 @@ import (
"os"
"gosrc.io/xmpp"
"gosrc.io/xmpp/stanza"
)
func main() {
@ -57,15 +58,15 @@ func main() {
log.Fatal(cm.Run())
}
func handleMessage(s xmpp.Sender, p xmpp.Packet) {
msg, ok := p.(xmpp.Message)
func handleMessage(s xmpp.Sender, p stanza.Packet) {
msg, ok := p.(stanza.Message)
if !ok {
_, _ = fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", p)
return
}
_, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From)
reply := xmpp.Message{Attrs: xmpp.Attrs{To: msg.From}, Body: msg.Body}
reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body}
_ = s.Send(reply)
}
```

View File

@ -6,6 +6,7 @@ import (
"log"
"gosrc.io/xmpp"
"gosrc.io/xmpp/stanza"
)
func main() {
@ -23,8 +24,8 @@ func main() {
router := xmpp.NewRouter()
router.HandleFunc("message", handleMessage)
router.NewRoute().
IQNamespaces(xmpp.NSDiscoInfo).
HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
IQNamespaces(stanza.NSDiscoInfo).
HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
discoInfo(s, p, opts)
})
router.NewRoute().
@ -43,14 +44,14 @@ func main() {
log.Fatal(cm.Run())
}
func handleMessage(_ xmpp.Sender, p xmpp.Packet) {
msg, ok := p.(xmpp.Message)
func handleMessage(_ xmpp.Sender, p stanza.Packet) {
msg, ok := p.(stanza.Message)
if !ok {
return
}
var msgProcessed bool
for _, ext := range msg.Extensions {
delegation, ok := ext.(*xmpp.Delegation)
delegation, ok := ext.(*stanza.Delegation)
if ok {
msgProcessed = true
fmt.Printf("Delegation confirmed for namespace %s\n", delegation.Delegated.Namespace)
@ -72,18 +73,18 @@ const (
// TODO: replace xmpp.Sender by ctx xmpp.Context ?
// ctx.Stream.Send / SendRaw
// ctx.Opts
func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
// Type conversion & sanity checks
iq, ok := p.(xmpp.IQ)
iq, ok := p.(stanza.IQ)
if !ok {
return
}
info, ok := iq.Payload.(*xmpp.DiscoInfo)
info, ok := iq.Payload.(*stanza.DiscoInfo)
if !ok {
return
}
iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
switch info.Node {
case "":
@ -97,22 +98,22 @@ func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
_ = c.Send(iqResp)
}
func discoInfoRoot(iqResp *xmpp.IQ, opts xmpp.ComponentOptions) {
func discoInfoRoot(iqResp *stanza.IQ, opts xmpp.ComponentOptions) {
// Higher level discovery
identity := xmpp.Identity{
identity := stanza.Identity{
Name: opts.Name,
Category: opts.Category,
Type: opts.Type,
}
payload := xmpp.DiscoInfo{
payload := stanza.DiscoInfo{
XMLName: xml.Name{
Space: xmpp.NSDiscoInfo,
Space: stanza.NSDiscoInfo,
Local: "query",
},
Identity: identity,
Features: []xmpp.Feature{
{Var: xmpp.NSDiscoInfo},
{Var: xmpp.NSDiscoItems},
Features: []stanza.Feature{
{Var: stanza.NSDiscoInfo},
{Var: stanza.NSDiscoItems},
{Var: "jabber:iq:version"},
{Var: "urn:xmpp:delegation:1"},
},
@ -120,14 +121,14 @@ func discoInfoRoot(iqResp *xmpp.IQ, opts xmpp.ComponentOptions) {
iqResp.Payload = &payload
}
func discoInfoPubSub(iqResp *xmpp.IQ) {
payload := xmpp.DiscoInfo{
func discoInfoPubSub(iqResp *stanza.IQ) {
payload := stanza.DiscoInfo{
XMLName: xml.Name{
Space: xmpp.NSDiscoInfo,
Space: stanza.NSDiscoInfo,
Local: "query",
},
Node: pubsubNode,
Features: []xmpp.Feature{
Features: []stanza.Feature{
{Var: "http://jabber.org/protocol/pubsub"},
{Var: "http://jabber.org/protocol/pubsub#publish"},
{Var: "http://jabber.org/protocol/pubsub#subscribe"},
@ -137,19 +138,19 @@ func discoInfoPubSub(iqResp *xmpp.IQ) {
iqResp.Payload = &payload
}
func discoInfoPEP(iqResp *xmpp.IQ) {
identity := xmpp.Identity{
func discoInfoPEP(iqResp *stanza.IQ) {
identity := stanza.Identity{
Category: "pubsub",
Type: "pep",
}
payload := xmpp.DiscoInfo{
payload := stanza.DiscoInfo{
XMLName: xml.Name{
Space: xmpp.NSDiscoInfo,
Space: stanza.NSDiscoInfo,
Local: "query",
},
Identity: identity,
Node: pepNode,
Features: []xmpp.Feature{
Features: []stanza.Feature{
{Var: "http://jabber.org/protocol/pubsub#access-presence"},
{Var: "http://jabber.org/protocol/pubsub#auto-create"},
{Var: "http://jabber.org/protocol/pubsub#auto-subscribe"},
@ -166,25 +167,25 @@ func discoInfoPEP(iqResp *xmpp.IQ) {
iqResp.Payload = &payload
}
func handleDelegation(s xmpp.Sender, p xmpp.Packet) {
func handleDelegation(s xmpp.Sender, p stanza.Packet) {
// Type conversion & sanity checks
iq, ok := p.(xmpp.IQ)
iq, ok := p.(stanza.IQ)
if !ok {
return
}
delegation, ok := iq.Payload.(*xmpp.Delegation)
delegation, ok := iq.Payload.(*stanza.Delegation)
if !ok {
return
}
forwardedPacket := delegation.Forwarded.Stanza
fmt.Println(forwardedPacket)
forwardedIQ, ok := forwardedPacket.(xmpp.IQ)
forwardedIQ, ok := forwardedPacket.(stanza.IQ)
if !ok {
return
}
pubsub, ok := forwardedIQ.Payload.(*xmpp.PubSub)
pubsub, ok := forwardedIQ.Payload.(*stanza.PubSub)
if !ok {
// We only support pubsub delegation
return
@ -192,8 +193,8 @@ func handleDelegation(s xmpp.Sender, p xmpp.Packet) {
if pubsub.Publish.XMLName.Local == "publish" {
// Prepare pubsub IQ reply
iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
payload := xmpp.PubSub{
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
payload := stanza.PubSub{
XMLName: xml.Name{
Space: "http://jabber.org/protocol/pubsub",
Local: "pubsub",
@ -201,13 +202,13 @@ func handleDelegation(s xmpp.Sender, p xmpp.Packet) {
}
iqResp.Payload = &payload
// Wrap the reply in delegation 'forward'
iqForward := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
delegPayload := xmpp.Delegation{
iqForward := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
delegPayload := stanza.Delegation{
XMLName: xml.Name{
Space: "urn:xmpp:delegation:1",
Local: "delegation",
},
Forwarded: &xmpp.Forwarded{
Forwarded: &stanza.Forwarded{
XMLName: xml.Name{
Space: "urn:xmpp:forward:0",
Local: "forward",

View File

@ -6,6 +6,7 @@ import (
"log"
"gosrc.io/xmpp"
"gosrc.io/xmpp/stanza"
)
func main() {
@ -21,12 +22,12 @@ func main() {
router := xmpp.NewRouter()
router.HandleFunc("message", handleMessage)
router.NewRoute().
IQNamespaces(xmpp.NSDiscoInfo).
HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
IQNamespaces(stanza.NSDiscoInfo).
HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
discoInfo(s, p, opts)
})
router.NewRoute().
IQNamespaces(xmpp.NSDiscoItems).
IQNamespaces(stanza.NSDiscoItems).
HandlerFunc(discoItems)
router.NewRoute().
IQNamespaces("jabber:iq:version").
@ -44,36 +45,36 @@ func main() {
log.Fatal(cm.Run())
}
func handleMessage(_ xmpp.Sender, p xmpp.Packet) {
msg, ok := p.(xmpp.Message)
func handleMessage(_ xmpp.Sender, p stanza.Packet) {
msg, ok := p.(stanza.Message)
if !ok {
return
}
fmt.Println("Received message:", msg.Body)
}
func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
// Type conversion & sanity checks
iq, ok := p.(xmpp.IQ)
iq, ok := p.(stanza.IQ)
if !ok || iq.Type != "get" {
return
}
iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
identity := xmpp.Identity{
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
identity := stanza.Identity{
Name: opts.Name,
Category: opts.Category,
Type: opts.Type,
}
payload := xmpp.DiscoInfo{
payload := stanza.DiscoInfo{
XMLName: xml.Name{
Space: xmpp.NSDiscoInfo,
Space: stanza.NSDiscoInfo,
Local: "query",
},
Identity: identity,
Features: []xmpp.Feature{
{Var: xmpp.NSDiscoInfo},
{Var: xmpp.NSDiscoItems},
Features: []stanza.Feature{
{Var: stanza.NSDiscoInfo},
{Var: stanza.NSDiscoItems},
{Var: "jabber:iq:version"},
{Var: "urn:xmpp:delegation:1"},
},
@ -83,24 +84,24 @@ func discoInfo(c xmpp.Sender, p xmpp.Packet, opts xmpp.ComponentOptions) {
}
// TODO: Handle iq error responses
func discoItems(c xmpp.Sender, p xmpp.Packet) {
func discoItems(c xmpp.Sender, p stanza.Packet) {
// Type conversion & sanity checks
iq, ok := p.(xmpp.IQ)
iq, ok := p.(stanza.IQ)
if !ok || iq.Type != "get" {
return
}
discoItems, ok := iq.Payload.(*xmpp.DiscoItems)
discoItems, ok := iq.Payload.(*stanza.DiscoItems)
if !ok {
return
}
iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
var payload xmpp.DiscoItems
var payload stanza.DiscoItems
if discoItems.Node == "" {
payload = xmpp.DiscoItems{
Items: []xmpp.DiscoItem{
payload = stanza.DiscoItems{
Items: []stanza.DiscoItem{
{Name: "test node", JID: "service.localhost", Node: "node1"},
},
}
@ -109,15 +110,15 @@ func discoItems(c xmpp.Sender, p xmpp.Packet) {
_ = c.Send(iqResp)
}
func handleVersion(c xmpp.Sender, p xmpp.Packet) {
func handleVersion(c xmpp.Sender, p stanza.Packet) {
// Type conversion & sanity checks
iq, ok := p.(xmpp.IQ)
iq, ok := p.(stanza.IQ)
if !ok {
return
}
iqResp := xmpp.NewIQ(xmpp.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
var payload xmpp.Version
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
var payload stanza.Version
payload.Name = "Fluux XMPP Component"
payload.Version = "0.0.1"
iq.Payload = &payload

View File

@ -10,6 +10,7 @@ import (
"os"
"gosrc.io/xmpp"
"gosrc.io/xmpp/stanza"
)
func main() {
@ -35,15 +36,15 @@ func main() {
log.Fatal(cm.Run())
}
func handleMessage(s xmpp.Sender, p xmpp.Packet) {
msg, ok := p.(xmpp.Message)
func handleMessage(s xmpp.Sender, p stanza.Packet) {
msg, ok := p.(stanza.Message)
if !ok {
_, _ = fmt.Fprintf(os.Stdout, "Ignoring packet: %T\n", p)
return
}
_, _ = fmt.Fprintf(os.Stdout, "Body = %s - from = %s\n", msg.Body, msg.From)
reply := xmpp.Message{Attrs: xmpp.Attrs{To: msg.From}, Body: msg.Body}
reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body}
_ = s.Send(reply)
}

View File

@ -12,6 +12,7 @@ import (
"github.com/processone/mpg123"
"github.com/processone/soundcloud"
"gosrc.io/xmpp"
"gosrc.io/xmpp/stanza"
)
// Get the actual song Stream URL from SoundCloud website song URL and play it with mpg123 player.
@ -41,12 +42,12 @@ func main() {
router := xmpp.NewRouter()
router.NewRoute().
Packet("message").
HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
handleMessage(s, p, player)
})
router.NewRoute().
Packet("message").
HandlerFunc(func(s xmpp.Sender, p xmpp.Packet) {
HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
handleIQ(s, p, player)
})
@ -59,8 +60,8 @@ func main() {
log.Fatal(cm.Run())
}
func handleMessage(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
msg, ok := p.(xmpp.Message)
func handleMessage(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
msg, ok := p.(stanza.Message)
if !ok {
return
}
@ -73,14 +74,14 @@ func handleMessage(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
}
}
func handleIQ(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
iq, ok := p.(xmpp.IQ)
func handleIQ(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
iq, ok := p.(stanza.IQ)
if !ok {
return
}
switch payload := iq.Payload.(type) {
// We support IOT Control IQ
case *xmpp.ControlSet:
case *stanza.ControlSet:
var url string
for _, element := range payload.Fields {
if element.XMLName.Local == "string" && element.Name == "url" {
@ -90,9 +91,9 @@ func handleIQ(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
}
playSCURL(player, url)
setResponse := new(xmpp.ControlSetResponse)
setResponse := new(stanza.ControlSetResponse)
// FIXME: Broken
reply := xmpp.IQ{Attrs: xmpp.Attrs{To: iq.From, Type: "result", Id: iq.Id}, Payload: setResponse}
reply := stanza.IQ{Attrs: stanza.Attrs{To: iq.From, Type: "result", Id: iq.Id}, Payload: setResponse}
_ = s.Send(reply)
// TODO add Soundclound artist / title retrieval
sendUserTune(s, "Radiohead", "Spectre")
@ -102,9 +103,9 @@ func handleIQ(s xmpp.Sender, p xmpp.Packet, player *mpg123.Player) {
}
func sendUserTune(s xmpp.Sender, artist string, title string) {
tune := xmpp.Tune{Artist: artist, Title: title}
iq := xmpp.NewIQ(xmpp.Attrs{Type: "set", Id: "usertune-1", Lang: "en"})
payload := xmpp.PubSub{Publish: &xmpp.Publish{Node: "http://jabber.org/protocol/tune", Item: xmpp.Item{Tune: &tune}}}
tune := stanza.Tune{Artist: artist, Title: title}
iq := stanza.NewIQ(stanza.Attrs{Type: "set", Id: "usertune-1", Lang: "en"})
payload := stanza.PubSub{Publish: &stanza.Publish{Node: "http://jabber.org/protocol/tune", Item: stanza.Item{Tune: &tune}}}
iq.Payload = &payload
_ = s.Send(iq)
}

81
auth.go
View File

@ -6,9 +6,11 @@ import (
"errors"
"fmt"
"io"
"gosrc.io/xmpp/stanza"
)
func authSASL(socket io.ReadWriter, decoder *xml.Decoder, f StreamFeatures, user string, password string) (err error) {
func authSASL(socket io.ReadWriter, decoder *xml.Decoder, f stanza.StreamFeatures, user string, password string) (err error) {
// TODO: Implement other type of SASL Authentication
havePlain := false
for _, m := range f.Mechanisms.Mechanism {
@ -30,17 +32,17 @@ func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password
raw := "\x00" + user + "\x00" + password
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
base64.StdEncoding.Encode(enc, []byte(raw))
fmt.Fprintf(socket, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>", nsSASL, enc)
fmt.Fprintf(socket, "<auth xmlns='%s' mechanism='PLAIN'>%s</auth>", stanza.NSSASL, enc)
// Next message should be either success or failure.
val, err := nextPacket(decoder)
val, err := stanza.NextPacket(decoder)
if err != nil {
return err
}
switch v := val.(type) {
case SASLSuccess:
case SASLFailure:
case stanza.SASLSuccess:
case stanza.SASLFailure:
// v.Any is type of sub-element in failure, which gives a description of what failed.
err := errors.New("auth failure: " + v.Any.Local)
return NewConnError(err, true)
@ -49,72 +51,3 @@ func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password
}
return err
}
// ============================================================================
// SASLSuccess
type SASLSuccess struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
}
func (SASLSuccess) Name() string {
return "sasl:success"
}
type saslSuccessDecoder struct{}
var saslSuccess saslSuccessDecoder
func (saslSuccessDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLSuccess, error) {
var packet SASLSuccess
err := p.DecodeElement(&packet, &se)
return packet, err
}
// ============================================================================
// SASLFailure
type SASLFailure struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
Any xml.Name // error reason is a subelement
}
func (SASLFailure) Name() string {
return "sasl:failure"
}
type saslFailureDecoder struct{}
var saslFailure saslFailureDecoder
func (saslFailureDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLFailure, error) {
var packet SASLFailure
err := p.DecodeElement(&packet, &se)
return packet, err
}
// ============================================================================
type auth struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
Mechanism string `xml:"mecanism,attr"`
Value string `xml:",innerxml"`
}
type BindBind struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
Resource string `xml:"resource,omitempty"`
Jid string `xml:"jid,omitempty"`
}
func (b *BindBind) Namespace() string {
return b.XMLName.Space
}
// Session is obsolete in RFC 6121.
// Added for compliance with RFC 3121.
// Remove when ejabberd purely conforms to RFC 6121.
type sessionSession struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"`
optional xml.Name // If it does exist, it mean we are not required to open session
}

View File

@ -8,6 +8,8 @@ import (
"net"
"strings"
"time"
"gosrc.io/xmpp/stanza"
)
// TODO: Should I move this as an extension of the client?
@ -49,28 +51,28 @@ func (c *ServerCheck) Check() error {
decoder := xml.NewDecoder(tcpconn)
// Send stream open tag
if _, err = fmt.Fprintf(tcpconn, xmppStreamOpen, c.domain, NSClient, NSStream); err != nil {
if _, err = fmt.Fprintf(tcpconn, xmppStreamOpen, c.domain, stanza.NSClient, stanza.NSStream); err != nil {
return err
}
// Set xml decoder and extract streamID from reply (not used for now)
_, err = initStream(decoder)
_, err = stanza.InitStream(decoder)
if err != nil {
return err
}
// extract stream features
var f StreamFeatures
packet, err := nextPacket(decoder)
var f stanza.StreamFeatures
packet, err := stanza.NextPacket(decoder)
if err != nil {
err = fmt.Errorf("stream open decode features: %s", err)
return err
}
switch p := packet.(type) {
case StreamFeatures:
case stanza.StreamFeatures:
f = p
case StreamError:
case stanza.StreamError:
return errors.New("open stream error: " + p.Error.Local)
default:
return errors.New("expected packet received while expecting features, got " + p.Name())
@ -79,13 +81,13 @@ func (c *ServerCheck) Check() error {
if _, ok := f.DoesStartTLS(); ok {
fmt.Fprintf(tcpconn, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")
var k tlsProceed
var k stanza.TLSProceed
if err = decoder.DecodeElement(&k, nil); err != nil {
return fmt.Errorf("expecting starttls proceed: %s", err)
}
DefaultTlsConfig.ServerName = c.domain
tlsConn := tls.Client(tcpconn, &DefaultTlsConfig)
stanza.DefaultTlsConfig.ServerName = c.domain
tlsConn := tls.Client(tcpconn, &stanza.DefaultTlsConfig)
// We convert existing connection to TLS
if err = tlsConn.Handshake(); err != nil {
return err

View File

@ -6,6 +6,8 @@ import (
"fmt"
"net"
"time"
"gosrc.io/xmpp/stanza"
)
//=============================================================================
@ -153,7 +155,7 @@ func (c *Client) SetHandler(handler EventHandler) {
}
// Send marshals XMPP stanza and sends it to the server.
func (c *Client) Send(packet Packet) error {
func (c *Client) Send(packet stanza.Packet) error {
conn := c.conn
if conn == nil {
return errors.New("client is not connected")
@ -191,7 +193,7 @@ func (c *Client) SendRaw(packet string) error {
// Loop: Receive data from server
func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) {
for {
val, err := nextPacket(c.Session.decoder)
val, err := stanza.NextPacket(c.Session.decoder)
if err != nil {
close(keepaliveQuit)
c.updateState(StateDisconnected)
@ -200,7 +202,7 @@ func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) {
// Handle stream errors
switch packet := val.(type) {
case StreamError:
case stanza.StreamError:
c.router.route(c, val)
close(keepaliveQuit)
c.streamError(packet.Error.Local, packet.Text)

View File

@ -7,6 +7,8 @@ import (
"net"
"testing"
"time"
"gosrc.io/xmpp/stanza"
)
const (
@ -126,11 +128,11 @@ func checkOpenStream(t *testing.T, c net.Conn, decoder *xml.Decoder) {
switch elem := token.(type) {
// Wait for first startElement
case xml.StartElement:
if elem.Name.Space != NSStream || elem.Name.Local != "stream" {
if elem.Name.Space != stanza.NSStream || elem.Name.Local != "stream" {
err = errors.New("xmpp: expected <stream> but got <" + elem.Name.Local + "> in " + elem.Name.Space)
return
}
if _, err := fmt.Fprintf(c, serverStreamOpen, "localhost", "streamid1", NSClient, NSStream); err != nil {
if _, err := fmt.Fprintf(c, serverStreamOpen, "localhost", "streamid1", stanza.NSClient, stanza.NSStream); err != nil {
t.Errorf("cannot write server stream open: %s", err)
}
return
@ -152,14 +154,14 @@ func sendStreamFeatures(t *testing.T, c net.Conn, _ *xml.Decoder) {
// TODO return err in case of error reading the auth params
func readAuth(t *testing.T, decoder *xml.Decoder) string {
se, err := nextStart(decoder)
se, err := stanza.NextStart(decoder)
if err != nil {
t.Errorf("cannot read auth: %s", err)
return ""
}
var nv interface{}
nv = &auth{}
nv = &stanza.Auth{}
// Decode element into pointer storage
if err = decoder.DecodeElement(nv, &se); err != nil {
t.Errorf("cannot decode auth: %s", err)
@ -167,7 +169,7 @@ func readAuth(t *testing.T, decoder *xml.Decoder) string {
}
switch v := nv.(type) {
case *auth:
case *stanza.Auth:
return v.Value
}
return ""
@ -184,13 +186,13 @@ func sendBindFeature(t *testing.T, c net.Conn, _ *xml.Decoder) {
}
func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
se, err := nextStart(decoder)
se, err := stanza.NextStart(decoder)
if err != nil {
t.Errorf("cannot read bind: %s", err)
return
}
iq := &IQ{}
iq := &stanza.IQ{}
// Decode element into pointer storage
if err = decoder.DecodeElement(&iq, &se); err != nil {
t.Errorf("cannot decode bind iq: %s", err)
@ -199,7 +201,7 @@ func bind(t *testing.T, c net.Conn, decoder *xml.Decoder) {
// TODO Check all elements
switch iq.Payload.(type) {
case *BindBind:
case *stanza.BindBind:
result := `<iq id='%s' type='result'>
<bind xmlns='urn:ietf:params:xml:ns:xmpp-bind'>
<jid>%s</jid>

View File

@ -9,6 +9,8 @@ import (
"io"
"net"
"time"
"gosrc.io/xmpp/stanza"
)
const componentStreamOpen = "<?xml version='1.0'?><stream:stream to='%s' xmlns='%s' xmlns:stream='%s'>"
@ -72,13 +74,13 @@ func (c *Component) Connect() error {
c.conn = conn
// 1. Send stream open tag
if _, err := fmt.Fprintf(conn, componentStreamOpen, c.Domain, NSComponent, NSStream); err != nil {
if _, err := fmt.Fprintf(conn, componentStreamOpen, c.Domain, stanza.NSComponent, stanza.NSStream); err != nil {
return errors.New("cannot send stream open " + err.Error())
}
c.decoder = xml.NewDecoder(conn)
// 2. Initialize xml decoder and extract streamID from reply
streamId, err := initStream(c.decoder)
streamId, err := stanza.InitStream(c.decoder)
if err != nil {
return errors.New("cannot init decoder " + err.Error())
}
@ -89,15 +91,15 @@ func (c *Component) Connect() error {
}
// 4. Check server response for authentication
val, err := nextPacket(c.decoder)
val, err := stanza.NextPacket(c.decoder)
if err != nil {
return err
}
switch v := val.(type) {
case StreamError:
case stanza.StreamError:
return errors.New("handshake failed " + v.Error.Local)
case Handshake:
case stanza.Handshake:
// Start the receiver go routine
go c.recv()
return nil
@ -119,7 +121,7 @@ func (c *Component) SetHandler(handler EventHandler) {
// Receiver Go routine receiver
func (c *Component) recv() (err error) {
for {
val, err := nextPacket(c.decoder)
val, err := stanza.NextPacket(c.decoder)
if err != nil {
c.updateState(StateDisconnected)
return err
@ -127,7 +129,7 @@ func (c *Component) recv() (err error) {
// Handle stream errors
switch p := val.(type) {
case StreamError:
case stanza.StreamError:
c.router.route(c, val)
c.streamError(p.Error.Local, p.Text)
return errors.New("stream error: " + p.Error.Local)
@ -137,7 +139,7 @@ func (c *Component) recv() (err error) {
}
// Send marshalls XMPP stanza and sends it to the server.
func (c *Component) Send(packet Packet) error {
func (c *Component) Send(packet stanza.Packet) error {
conn := c.conn
if conn == nil {
return errors.New("component is not connected")
@ -186,90 +188,6 @@ func (c *Component) handshake(streamId string) string {
return encodedStr
}
// ============================================================================
// Handshake Stanza
// Handshake is a stanza used by XMPP components to authenticate on XMPP
// component port.
type Handshake struct {
XMLName xml.Name `xml:"jabber:component:accept handshake"`
// TODO Add handshake value with test for proper serialization
// Value string `xml:",innerxml"`
}
func (Handshake) Name() string {
return "component:handshake"
}
// Handshake decoding wrapper
type handshakeDecoder struct{}
var handshake handshakeDecoder
func (handshakeDecoder) decode(p *xml.Decoder, se xml.StartElement) (Handshake, error) {
var packet Handshake
err := p.DecodeElement(&packet, &se)
return packet, err
}
// ============================================================================
// Component delegation
// XEP-0355
// Delegation can be used both on message (for delegated) and IQ (for Forwarded),
// depending on the context.
type Delegation struct {
MsgExtension
XMLName xml.Name `xml:"urn:xmpp:delegation:1 delegation"`
Forwarded *Forwarded // This is used in iq to wrap delegated iqs
Delegated *Delegated // This is used in a message to confirm delegated namespace
}
func (d *Delegation) Namespace() string {
return d.XMLName.Space
}
type Forwarded struct {
XMLName xml.Name `xml:"urn:xmpp:forward:0 forwarded"`
Stanza Packet
}
// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
// transform generic XML content into hierarchical Node structure.
func (f *Forwarded) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
// Check subelements to extract required field as boolean
for {
t, err := d.Token()
if err != nil {
return err
}
switch tt := t.(type) {
case xml.StartElement:
if packet, err := decodeClient(d, tt); err == nil {
f.Stanza = packet
}
case xml.EndElement:
if tt == start.End() {
return nil
}
}
}
}
type Delegated struct {
XMLName xml.Name `xml:"delegated"`
Namespace string `xml:"namespace,attr,omitempty"`
}
func init() {
TypeRegistry.MapExtension(PKTMessage, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
}
/*
TODO: Add support for discovery management directly in component
TODO: Support multiple identities on disco info

View File

@ -1,7 +1,6 @@
package xmpp
import (
"encoding/xml"
"testing"
)
@ -24,76 +23,3 @@ func TestHandshake(t *testing.T) {
func TestGenerateHandshake(t *testing.T) {
// TODO
}
// We should be able to properly parse delegation confirmation messages
func TestParsingDelegationMessage(t *testing.T) {
packetStr := `<message to='service.localhost' from='localhost'>
<delegation xmlns='urn:xmpp:delegation:1'>
<delegated namespace='http://jabber.org/protocol/pubsub'/>
</delegation>
</message>`
var msg Message
data := []byte(packetStr)
if err := xml.Unmarshal(data, &msg); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
// Check that we have extracted the delegation info as MsgExtension
var nsDelegated string
for _, ext := range msg.Extensions {
if delegation, ok := ext.(*Delegation); ok {
nsDelegated = delegation.Delegated.Namespace
}
}
if nsDelegated != "http://jabber.org/protocol/pubsub" {
t.Errorf("Could not find delegated namespace in delegation: %#v\n", msg)
}
}
// Check that we can parse a delegation IQ.
// The most important thing is to be able to
func TestParsingDelegationIQ(t *testing.T) {
packetStr := `<iq to='service.localhost' from='localhost' type='set' id='1'>
<delegation xmlns='urn:xmpp:delegation:1'>
<forwarded xmlns='urn:xmpp:forward:0'>
<iq xml:lang='en' to='test1@localhost' from='test1@localhost/mremond-mbp' type='set' id='aaf3a' xmlns='jabber:client'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<publish node='http://jabber.org/protocol/mood'>
<item id='current'>
<mood xmlns='http://jabber.org/protocol/mood'>
<excited/>
</mood>
</item>
</publish>
</pubsub>
</iq>
</forwarded>
</delegation>
</iq>`
var iq IQ
data := []byte(packetStr)
if err := xml.Unmarshal(data, &iq); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
// Check that we have extracted the delegation info as IQPayload
var node string
if iq.Payload != nil {
if delegation, ok := iq.Payload.(*Delegation); ok {
packet := delegation.Forwarded.Stanza
forwardedIQ, ok := packet.(IQ)
if !ok {
t.Errorf("Could not extract packet IQ")
return
}
if forwardedIQ.Payload != nil {
if pubsub, ok := forwardedIQ.Payload.(*PubSub); ok {
node = pubsub.Publish.Node
}
}
}
}
if node != "http://jabber.org/protocol/mood" {
t.Errorf("Could not find mood node name on delegated publish: %#v\n", iq)
}
}

View File

@ -3,6 +3,8 @@ package xmpp
import (
"encoding/xml"
"strings"
"gosrc.io/xmpp/stanza"
)
/*
@ -32,7 +34,7 @@ func NewRouter() *Router {
// route is called by the XMPP client to dispatch stanza received using the set up routes.
// It is also used by test, but is not supposed to be used directly by users of the library.
func (r *Router) route(s Sender, p Packet) {
func (r *Router) route(s Sender, p stanza.Packet) {
var match RouteMatch
if r.Match(p, &match) {
@ -41,15 +43,15 @@ func (r *Router) route(s Sender, p Packet) {
return
}
// If there is no match and we receive an iq set or get, we need to send a reply
if iq, ok := p.(IQ); ok {
if iq.Type == IQTypeGet || iq.Type == IQTypeSet {
if iq, ok := p.(stanza.IQ); ok {
if iq.Type == stanza.IQTypeGet || iq.Type == stanza.IQTypeSet {
iqNotImplemented(s, iq)
}
}
}
func iqNotImplemented(s Sender, iq IQ) {
err := Err{
func iqNotImplemented(s Sender, iq stanza.IQ) {
err := stanza.Err{
XMLName: xml.Name{Local: "error"},
Code: 501,
Type: "cancel",
@ -66,7 +68,7 @@ func (r *Router) NewRoute() *Route {
return route
}
func (r *Router) Match(p Packet, match *RouteMatch) bool {
func (r *Router) Match(p stanza.Packet, match *RouteMatch) bool {
for _, route := range r.routes {
if route.Match(p, match) {
return true
@ -83,14 +85,14 @@ func (r *Router) Handle(name string, handler Handler) *Route {
// HandleFunc registers a new route with a matcher for for a given packet name (iq, message, presence)
// See Route.Path() and Route.HandlerFunc().
func (r *Router) HandleFunc(name string, f func(s Sender, p Packet)) *Route {
func (r *Router) HandleFunc(name string, f func(s Sender, p stanza.Packet)) *Route {
return r.NewRoute().Packet(name).HandlerFunc(f)
}
// ============================================================================
// Route
type Handler interface {
HandlePacket(s Sender, p Packet)
HandlePacket(s Sender, p stanza.Packet)
}
type Route struct {
@ -108,10 +110,10 @@ func (r *Route) Handler(handler Handler) *Route {
// ordinary functions as XMPP handlers. If f is a function
// with the appropriate signature, HandlerFunc(f) is a
// Handler that calls f.
type HandlerFunc func(s Sender, p Packet)
type HandlerFunc func(s Sender, p stanza.Packet)
// HandlePacket calls f(s, p)
func (f HandlerFunc) HandlePacket(s Sender, p Packet) {
func (f HandlerFunc) HandlePacket(s Sender, p stanza.Packet) {
f(s, p)
}
@ -126,7 +128,7 @@ func (r *Route) addMatcher(m matcher) *Route {
return r
}
func (r *Route) Match(p Packet, match *RouteMatch) bool {
func (r *Route) Match(p stanza.Packet, match *RouteMatch) bool {
for _, m := range r.matchers {
if matched := m.Match(p, match); !matched {
return false
@ -144,18 +146,18 @@ func (r *Route) Match(p Packet, match *RouteMatch) bool {
type nameMatcher string
func (n nameMatcher) Match(p Packet, match *RouteMatch) bool {
func (n nameMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
var name string
// TODO: To avoid type switch everywhere in matching, I think we will need to have
// to move to a concrete type for packets, to make matching and comparison more natural.
// Current code structure is probably too rigid.
// Maybe packet types should even be from an enum.
switch p.(type) {
case Message:
case stanza.Message:
name = "message"
case IQ:
case stanza.IQ:
name = "iq"
case Presence:
case stanza.Presence:
name = "presence"
}
if name == string(n) {
@ -177,14 +179,14 @@ func (r *Route) Packet(name string) *Route {
// nsTypeMather matches on a list of IQ payload namespaces
type nsTypeMatcher []string
func (m nsTypeMatcher) Match(p Packet, match *RouteMatch) bool {
var stanzaType StanzaType
func (m nsTypeMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
var stanzaType stanza.StanzaType
switch packet := p.(type) {
case IQ:
case stanza.IQ:
stanzaType = packet.Type
case Presence:
case stanza.Presence:
stanzaType = packet.Type
case Message:
case stanza.Message:
if packet.Type == "" {
// optional on message, normal is the default type
stanzaType = "normal"
@ -211,8 +213,8 @@ func (r *Route) StanzaType(types ...string) *Route {
// nsIqMather matches on a list of IQ payload namespaces
type nsIQMatcher []string
func (m nsIQMatcher) Match(p Packet, match *RouteMatch) bool {
iq, ok := p.(IQ)
func (m nsIQMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
iq, ok := p.(stanza.IQ)
if !ok {
return false
}
@ -235,7 +237,7 @@ func (r *Route) IQNamespaces(namespaces ...string) *Route {
// Matchers are used to "specialize" routes and focus on specific packet features
type matcher interface {
Match(Packet, *RouteMatch) bool
Match(stanza.Packet, *RouteMatch) bool
}
// RouteMatch extracts and gather match information

View File

@ -4,6 +4,8 @@ import (
"bytes"
"encoding/xml"
"testing"
"gosrc.io/xmpp/stanza"
)
// ============================================================================
@ -11,13 +13,13 @@ import (
func TestNameMatcher(t *testing.T) {
router := NewRouter()
router.HandleFunc("message", func(s Sender, p Packet) {
router.HandleFunc("message", func(s Sender, p stanza.Packet) {
_ = s.SendRaw(successFlag)
})
// Check that a message packet is properly matched
conn := NewSenderMock()
msg := NewMessage(Attrs{Type: MessageTypeChat, To: "test@localhost", Id: "1"})
msg := stanza.NewMessage(stanza.Attrs{Type: stanza.MessageTypeChat, To: "test@localhost", Id: "1"})
msg.Body = "Hello"
router.route(conn, msg)
if conn.String() != successFlag {
@ -26,8 +28,8 @@ func TestNameMatcher(t *testing.T) {
// Check that an IQ packet is not matched
conn = NewSenderMock()
iq := NewIQ(Attrs{Type: IQTypeGet, To: "localhost", Id: "1"})
iq.Payload = &DiscoInfo{}
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
iq.Payload = &stanza.DiscoInfo{}
router.route(conn, iq)
if conn.String() == successFlag {
t.Error("IQ should not have been matched and routed")
@ -37,18 +39,18 @@ func TestNameMatcher(t *testing.T) {
func TestIQNSMatcher(t *testing.T) {
router := NewRouter()
router.NewRoute().
IQNamespaces(NSDiscoInfo, NSDiscoItems).
HandlerFunc(func(s Sender, p Packet) {
IQNamespaces(stanza.NSDiscoInfo, stanza.NSDiscoItems).
HandlerFunc(func(s Sender, p stanza.Packet) {
_ = s.SendRaw(successFlag)
})
// Check that an IQ with proper namespace does match
conn := NewSenderMock()
iqDisco := NewIQ(Attrs{Type: IQTypeGet, To: "localhost", Id: "1"})
iqDisco := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
// TODO: Add a function to generate payload with proper namespace initialisation
iqDisco.Payload = &DiscoInfo{
iqDisco.Payload = &stanza.DiscoInfo{
XMLName: xml.Name{
Space: NSDiscoInfo,
Space: stanza.NSDiscoInfo,
Local: "query",
}}
router.route(conn, iqDisco)
@ -58,9 +60,9 @@ func TestIQNSMatcher(t *testing.T) {
// Check that another namespace is not matched
conn = NewSenderMock()
iqVersion := NewIQ(Attrs{Type: IQTypeGet, To: "localhost", Id: "1"})
iqVersion := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
// TODO: Add a function to generate payload with proper namespace initialisation
iqVersion.Payload = &DiscoInfo{
iqVersion.Payload = &stanza.DiscoInfo{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
@ -75,13 +77,13 @@ func TestTypeMatcher(t *testing.T) {
router := NewRouter()
router.NewRoute().
StanzaType("normal").
HandlerFunc(func(s Sender, p Packet) {
HandlerFunc(func(s Sender, p stanza.Packet) {
_ = s.SendRaw(successFlag)
})
// Check that a packet with the proper type matches
conn := NewSenderMock()
message := NewMessage(Attrs{Type: "normal", To: "test@localhost", Id: "1"})
message := stanza.NewMessage(stanza.Attrs{Type: "normal", To: "test@localhost", Id: "1"})
message.Body = "hello"
router.route(conn, message)
@ -91,7 +93,7 @@ func TestTypeMatcher(t *testing.T) {
// We should match on default type 'normal' for message without a type
conn = NewSenderMock()
message = NewMessage(Attrs{To: "test@localhost", Id: "1"})
message = stanza.NewMessage(stanza.Attrs{To: "test@localhost", Id: "1"})
message.Body = "hello"
router.route(conn, message)
@ -101,8 +103,8 @@ func TestTypeMatcher(t *testing.T) {
// We do not match on other types
conn = NewSenderMock()
iqVersion := NewIQ(Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
iqVersion.Payload = &DiscoInfo{
iqVersion := stanza.NewIQ(stanza.Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
iqVersion.Payload = &stanza.DiscoInfo{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
@ -119,38 +121,38 @@ func TestCompositeMatcher(t *testing.T) {
router.NewRoute().
IQNamespaces("jabber:iq:version").
StanzaType("get").
HandlerFunc(func(s Sender, p Packet) {
HandlerFunc(func(s Sender, p stanza.Packet) {
_ = s.SendRaw(successFlag)
})
// Data set
getVersionIq := NewIQ(Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
getVersionIq.Payload = &Version{
getVersionIq := stanza.NewIQ(stanza.Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
getVersionIq.Payload = &stanza.Version{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
}}
setVersionIq := NewIQ(Attrs{Type: "set", From: "service.localhost", To: "test@localhost", Id: "1"})
setVersionIq.Payload = &Version{
setVersionIq := stanza.NewIQ(stanza.Attrs{Type: "set", From: "service.localhost", To: "test@localhost", Id: "1"})
setVersionIq.Payload = &stanza.Version{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
}}
GetDiscoIq := NewIQ(Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
GetDiscoIq.Payload = &DiscoInfo{
GetDiscoIq := stanza.NewIQ(stanza.Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
GetDiscoIq.Payload = &stanza.DiscoInfo{
XMLName: xml.Name{
Space: "http://jabber.org/protocol/disco#info",
Local: "query",
}}
message := NewMessage(Attrs{Type: "normal", To: "test@localhost", Id: "1"})
message := stanza.NewMessage(stanza.Attrs{Type: "normal", To: "test@localhost", Id: "1"})
message.Body = "hello"
tests := []struct {
name string
input Packet
input stanza.Packet
want bool
}{
{name: "match get version iq", input: getVersionIq, want: true},
@ -178,13 +180,13 @@ func TestCompositeMatcher(t *testing.T) {
func TestCatchallMatcher(t *testing.T) {
router := NewRouter()
router.NewRoute().
HandlerFunc(func(s Sender, p Packet) {
HandlerFunc(func(s Sender, p stanza.Packet) {
_ = s.SendRaw(successFlag)
})
// Check that we match on several packets
conn := NewSenderMock()
message := NewMessage(Attrs{Type: "chat", To: "test@localhost", Id: "1"})
message := stanza.NewMessage(stanza.Attrs{Type: "chat", To: "test@localhost", Id: "1"})
message.Body = "hello"
router.route(conn, message)
@ -193,8 +195,8 @@ func TestCatchallMatcher(t *testing.T) {
}
conn = NewSenderMock()
iqVersion := NewIQ(Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
iqVersion.Payload = &DiscoInfo{
iqVersion := stanza.NewIQ(stanza.Attrs{Type: "get", From: "service.localhost", To: "test@localhost", Id: "1"})
iqVersion.Payload = &stanza.DiscoInfo{
XMLName: xml.Name{
Space: "jabber:iq:version",
Local: "query",
@ -219,7 +221,7 @@ func NewSenderMock() SenderMock {
return SenderMock{buffer: new(bytes.Buffer)}
}
func (s SenderMock) Send(packet Packet) error {
func (s SenderMock) Send(packet stanza.Packet) error {
out, err := xml.Marshal(packet)
if err != nil {
return err
@ -239,7 +241,7 @@ func (s SenderMock) String() string {
func TestSenderMock(t *testing.T) {
conn := NewSenderMock()
msg := NewMessage(Attrs{To: "test@localhost", Id: "1"})
msg := stanza.NewMessage(stanza.Attrs{To: "test@localhost", Id: "1"})
msg.Body = "Hello"
if err := conn.Send(msg); err != nil {
t.Error("Could not send message")

View File

@ -7,6 +7,8 @@ import (
"fmt"
"io"
"net"
"gosrc.io/xmpp/stanza"
)
const xmppStreamOpen = "<?xml version='1.0'?><stream:stream to='%s' xmlns='%s' xmlns:stream='%s' version='1.0'>"
@ -15,7 +17,7 @@ type Session struct {
// Session info
BindJid string // Jabber ID as provided by XMPP server
StreamId string
Features StreamFeatures
Features stanza.StreamFeatures
TlsEnabled bool
lastPacketId int
@ -85,14 +87,14 @@ func (s *Session) setProxy(conn net.Conn, newConn net.Conn, o Config) {
s.decoder.CharsetReader = o.CharsetReader
}
func (s *Session) open(domain string) (f StreamFeatures) {
func (s *Session) open(domain string) (f stanza.StreamFeatures) {
// Send stream open tag
if _, s.err = fmt.Fprintf(s.socketProxy, xmppStreamOpen, domain, NSClient, NSStream); s.err != nil {
if _, s.err = fmt.Fprintf(s.socketProxy, xmppStreamOpen, domain, stanza.NSClient, stanza.NSStream); s.err != nil {
return
}
// Set xml decoder and extract streamID from reply
s.StreamId, s.err = initStream(s.decoder) // TODO refactor / rename
s.StreamId, s.err = stanza.InitStream(s.decoder) // TODO refactor / rename
if s.err != nil {
return
}
@ -112,7 +114,7 @@ func (s *Session) startTlsIfSupported(conn net.Conn, domain string) net.Conn {
if _, ok := s.Features.DoesStartTLS(); ok {
fmt.Fprintf(s.socketProxy, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>")
var k tlsProceed
var k stanza.TLSProceed
if s.err = s.decoder.DecodeElement(&k, nil); s.err != nil {
s.err = errors.New("expecting starttls proceed: " + s.err.Error())
return conn
@ -120,8 +122,8 @@ func (s *Session) startTlsIfSupported(conn net.Conn, domain string) net.Conn {
s.TlsEnabled = true
// TODO: add option to accept all TLS certificates: insecureSkipTlsVerify (DefaultTlsConfig.InsecureSkipVerify)
DefaultTlsConfig.ServerName = domain
tlsConn := tls.Client(conn, &DefaultTlsConfig)
stanza.DefaultTlsConfig.ServerName = domain
tlsConn := tls.Client(conn, &stanza.DefaultTlsConfig)
// We convert existing connection to TLS
if s.err = tlsConn.Handshake(); s.err != nil {
return tlsConn
@ -153,12 +155,12 @@ func (s *Session) bind(o Config) {
var resource = o.parsedJid.Resource
if resource != "" {
fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><bind xmlns='%s'><resource>%s</resource></bind></iq>",
s.PacketId(), nsBind, resource)
s.PacketId(), stanza.NSBind, resource)
} else {
fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><bind xmlns='%s'/></iq>", s.PacketId(), nsBind)
fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><bind xmlns='%s'/></iq>", s.PacketId(), stanza.NSBind)
}
var iq IQ
var iq stanza.IQ
if s.err = s.decoder.Decode(&iq); s.err != nil {
s.err = errors.New("error decoding iq bind result: " + s.err.Error())
return
@ -166,7 +168,7 @@ func (s *Session) bind(o Config) {
// TODO Check all elements
switch payload := iq.Payload.(type) {
case *BindBind:
case *stanza.BindBind:
s.BindJid = payload.Jid // our local id (with possibly randomly generated resource
default:
s.err = errors.New("iq bind result missing")
@ -182,9 +184,9 @@ func (s *Session) rfc3921Session(o Config) {
return
}
var iq IQ
if s.Features.Session.optional.Local != "" {
fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><session xmlns='%s'/></iq>", s.PacketId(), nsSession)
var iq stanza.IQ
if s.Features.Session.Optional.Local != "" {
fmt.Fprintf(s.socketProxy, "<iq type='set' id='%s'><session xmlns='%s'/></iq>", s.PacketId(), stanza.NSSession)
if s.err = s.decoder.Decode(&iq); s.err != nil {
s.err = errors.New("expecting iq result after session open: " + s.err.Error())
return

72
stanza/auth_sasl.go Normal file
View File

@ -0,0 +1,72 @@
package stanza
import "encoding/xml"
// ============================================================================
// SASLSuccess
type SASLSuccess struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl success"`
}
func (SASLSuccess) Name() string {
return "sasl:success"
}
type saslSuccessDecoder struct{}
var saslSuccess saslSuccessDecoder
func (saslSuccessDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLSuccess, error) {
var packet SASLSuccess
err := p.DecodeElement(&packet, &se)
return packet, err
}
// ============================================================================
// SASLFailure
type SASLFailure struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl failure"`
Any xml.Name // error reason is a subelement
}
func (SASLFailure) Name() string {
return "sasl:failure"
}
type saslFailureDecoder struct{}
var saslFailure saslFailureDecoder
func (saslFailureDecoder) decode(p *xml.Decoder, se xml.StartElement) (SASLFailure, error) {
var packet SASLFailure
err := p.DecodeElement(&packet, &se)
return packet, err
}
// ============================================================================
type Auth struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-sasl auth"`
Mechanism string `xml:"mecanism,attr"`
Value string `xml:",innerxml"`
}
type BindBind struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
Resource string `xml:"resource,omitempty"`
Jid string `xml:"jid,omitempty"`
}
func (b *BindBind) Namespace() string {
return b.XMLName.Space
}
// Session is obsolete in RFC 6121.
// Added for compliance with RFC 3121.
// Remove when ejabberd purely conforms to RFC 6121.
type sessionSession struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"`
Optional xml.Name // If it does exist, it mean we are not required to open session
}

89
stanza/component.go Normal file
View File

@ -0,0 +1,89 @@
package stanza
import (
"encoding/xml"
)
// ============================================================================
// Handshake Stanza
// Handshake is a stanza used by XMPP components to authenticate on XMPP
// component port.
type Handshake struct {
XMLName xml.Name `xml:"jabber:component:accept handshake"`
// TODO Add handshake value with test for proper serialization
// Value string `xml:",innerxml"`
}
func (Handshake) Name() string {
return "component:handshake"
}
// Handshake decoding wrapper
type handshakeDecoder struct{}
var handshake handshakeDecoder
func (handshakeDecoder) decode(p *xml.Decoder, se xml.StartElement) (Handshake, error) {
var packet Handshake
err := p.DecodeElement(&packet, &se)
return packet, err
}
// ============================================================================
// Component delegation
// XEP-0355
// Delegation can be used both on message (for delegated) and IQ (for Forwarded),
// depending on the context.
type Delegation struct {
MsgExtension
XMLName xml.Name `xml:"urn:xmpp:delegation:1 delegation"`
Forwarded *Forwarded // This is used in iq to wrap delegated iqs
Delegated *Delegated // This is used in a message to confirm delegated namespace
}
func (d *Delegation) Namespace() string {
return d.XMLName.Space
}
type Forwarded struct {
XMLName xml.Name `xml:"urn:xmpp:forward:0 forwarded"`
Stanza Packet
}
// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
// transform generic XML content into hierarchical Node structure.
func (f *Forwarded) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
// Check subelements to extract required field as boolean
for {
t, err := d.Token()
if err != nil {
return err
}
switch tt := t.(type) {
case xml.StartElement:
if packet, err := decodeClient(d, tt); err == nil {
f.Stanza = packet
}
case xml.EndElement:
if tt == start.End() {
return nil
}
}
}
}
type Delegated struct {
XMLName xml.Name `xml:"delegated"`
Namespace string `xml:"namespace,attr,omitempty"`
}
func init() {
TypeRegistry.MapExtension(PKTMessage, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
}

79
stanza/component_test.go Normal file
View File

@ -0,0 +1,79 @@
package stanza
import (
"encoding/xml"
"testing"
)
// We should be able to properly parse delegation confirmation messages
func TestParsingDelegationMessage(t *testing.T) {
packetStr := `<message to='service.localhost' from='localhost'>
<delegation xmlns='urn:xmpp:delegation:1'>
<delegated namespace='http://jabber.org/protocol/pubsub'/>
</delegation>
</message>`
var msg Message
data := []byte(packetStr)
if err := xml.Unmarshal(data, &msg); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
// Check that we have extracted the delegation info as MsgExtension
var nsDelegated string
for _, ext := range msg.Extensions {
if delegation, ok := ext.(*Delegation); ok {
nsDelegated = delegation.Delegated.Namespace
}
}
if nsDelegated != "http://jabber.org/protocol/pubsub" {
t.Errorf("Could not find delegated namespace in delegation: %#v\n", msg)
}
}
// Check that we can parse a delegation IQ.
// The most important thing is to be able to
func TestParsingDelegationIQ(t *testing.T) {
packetStr := `<iq to='service.localhost' from='localhost' type='set' id='1'>
<delegation xmlns='urn:xmpp:delegation:1'>
<forwarded xmlns='urn:xmpp:forward:0'>
<iq xml:lang='en' to='test1@localhost' from='test1@localhost/mremond-mbp' type='set' id='aaf3a' xmlns='jabber:client'>
<pubsub xmlns='http://jabber.org/protocol/pubsub'>
<publish node='http://jabber.org/protocol/mood'>
<item id='current'>
<mood xmlns='http://jabber.org/protocol/mood'>
<excited/>
</mood>
</item>
</publish>
</pubsub>
</iq>
</forwarded>
</delegation>
</iq>`
var iq IQ
data := []byte(packetStr)
if err := xml.Unmarshal(data, &iq); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
// Check that we have extracted the delegation info as IQPayload
var node string
if iq.Payload != nil {
if delegation, ok := iq.Payload.(*Delegation); ok {
packet := delegation.Forwarded.Stanza
forwardedIQ, ok := packet.(IQ)
if !ok {
t.Errorf("Could not extract packet IQ")
return
}
if forwardedIQ.Payload != nil {
if pubsub, ok := forwardedIQ.Payload.(*PubSub); ok {
node = pubsub.Publish.Node
}
}
}
}
if node != "http://jabber.org/protocol/mood" {
t.Errorf("Could not find mood node name on delegated publish: %#v\n", iq)
}
}

View File

@ -1,14 +1,10 @@
package xmpp
package stanza
import (
"encoding/xml"
"strconv"
)
/*
TODO support ability to put Raw payload inside IQ
*/
// ============================================================================
// XMPP Errors

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
// ErrorType is a Enum of error attribute type
type ErrorType string

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"encoding/xml"

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"encoding/xml"

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"encoding/xml"
@ -129,54 +129,6 @@ func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
}
}
// ============================================================================
// Generic / unknown content
// Node is a generic structure to represent XML data. It is used to parse
// unreferenced or custom stanza payload.
type Node struct {
XMLName xml.Name
Attrs []xml.Attr `xml:"-"`
Content string `xml:",innerxml"`
Nodes []Node `xml:",any"`
}
func (n *Node) Namespace() string {
return n.XMLName.Space
}
// Attr represents generic XML attributes, as used on the generic XML Node
// representation.
type Attr struct {
K string
V string
}
// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
// transform generic XML content into hierarchical Node structure.
func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
// Assign "n.Attrs = start.Attr", without repeating xmlns in attributes:
for _, attr := range start.Attr {
// Do not repeat xmlns, it is already in XMLName
if attr.Name.Local != "xmlns" {
n.Attrs = append(n.Attrs, attr)
}
}
type node Node
return d.DecodeElement((*node)(n), &start)
}
// MarshalXML is a custom XML serializer used by xml.Marshal to serialize a
// Node structure to XML.
func (n Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
start.Attr = n.Attrs
start.Name = n.XMLName
err = e.EncodeToken(start)
e.EncodeElement(n.Nodes, xml.StartElement{Name: n.XMLName})
return e.EncodeToken(xml.EndElement{Name: start.Name})
}
// ============================================================================
// Disco

View File

@ -1,4 +1,4 @@
package xmpp_test
package stanza_test
import (
"encoding/xml"
@ -6,22 +6,21 @@ import (
"testing"
"github.com/google/go-cmp/cmp"
"gosrc.io/xmpp"
)
func TestUnmarshalIqs(t *testing.T) {
//var cs1 = new(iot.ControlSet)
var tests = []struct {
iqString string
parsedIQ xmpp.IQ
parsedIQ IQ
}{
{"<iq id=\"1\" type=\"set\" to=\"test@localhost\"/>",
xmpp.IQ{XMLName: xml.Name{Local: "iq"}, Attrs: xmpp.Attrs{Type: xmpp.IQTypeSet, To: "test@localhost", Id: "1"}}},
IQ{XMLName: xml.Name{Local: "iq"}, Attrs: Attrs{Type: IQTypeSet, To: "test@localhost", Id: "1"}}},
//{"<iq xmlns=\"jabber:client\" id=\"2\" type=\"set\" to=\"test@localhost\" from=\"server\"><set xmlns=\"urn:xmpp:iot:control\"/></iq>", IQ{XMLName: xml.Name{Space: "jabber:client", Local: "iq"}, PacketAttrs: PacketAttrs{To: "test@localhost", From: "server", Type: "set", Id: "2"}, Payload: cs1}},
}
for _, test := range tests {
parsedIQ := xmpp.IQ{}
parsedIQ := IQ{}
err := xml.Unmarshal([]byte(test.iqString), &parsedIQ)
if err != nil {
t.Errorf("Unmarshal(%s) returned error", test.iqString)
@ -35,16 +34,16 @@ func TestUnmarshalIqs(t *testing.T) {
}
func TestGenerateIq(t *testing.T) {
iq := xmpp.NewIQ(xmpp.Attrs{Type: xmpp.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"})
payload := xmpp.DiscoInfo{
Identity: xmpp.Identity{
iq := NewIQ(Attrs{Type: IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"})
payload := DiscoInfo{
Identity: Identity{
Name: "Test Gateway",
Category: "gateway",
Type: "mqtt",
},
Features: []xmpp.Feature{
{Var: xmpp.NSDiscoInfo},
{Var: xmpp.NSDiscoItems},
Features: []Feature{
{Var: NSDiscoInfo},
{Var: NSDiscoItems},
},
}
iq.Payload = &payload
@ -58,7 +57,7 @@ func TestGenerateIq(t *testing.T) {
t.Error("empty error should not be serialized")
}
parsedIQ := xmpp.IQ{}
parsedIQ := IQ{}
if err = xml.Unmarshal(data, &parsedIQ); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
@ -69,7 +68,7 @@ func TestGenerateIq(t *testing.T) {
}
func TestErrorTag(t *testing.T) {
xError := xmpp.Err{
xError := Err{
XMLName: xml.Name{Local: "error"},
Code: 503,
Type: "cancel",
@ -82,7 +81,7 @@ func TestErrorTag(t *testing.T) {
t.Errorf("cannot marshal xml structure: %s", err)
}
parsedError := xmpp.Err{}
parsedError := Err{}
if err = xml.Unmarshal(data, &parsedError); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
@ -93,8 +92,8 @@ func TestErrorTag(t *testing.T) {
}
func TestDiscoItems(t *testing.T) {
iq := xmpp.NewIQ(xmpp.Attrs{Type: xmpp.IQTypeGet, From: "romeo@montague.net/orchard", To: "catalog.shakespeare.lit", Id: "items3"})
payload := xmpp.DiscoItems{
iq := NewIQ(Attrs{Type: IQTypeGet, From: "romeo@montague.net/orchard", To: "catalog.shakespeare.lit", Id: "items3"})
payload := DiscoItems{
Node: "music",
}
iq.Payload = &payload
@ -104,7 +103,7 @@ func TestDiscoItems(t *testing.T) {
t.Errorf("cannot marshal xml structure")
}
parsedIQ := xmpp.IQ{}
parsedIQ := IQ{}
if err = xml.Unmarshal(data, &parsedIQ); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
@ -117,7 +116,7 @@ func TestDiscoItems(t *testing.T) {
func TestUnmarshalPayload(t *testing.T) {
query := "<iq to='service.localhost' type='get' id='1'><query xmlns='jabber:iq:version'/></iq>"
parsedIQ := xmpp.IQ{}
parsedIQ := IQ{}
err := xml.Unmarshal([]byte(query), &parsedIQ)
if err != nil {
t.Errorf("Unmarshal(%s) returned error", query)
@ -142,7 +141,7 @@ func TestPayloadWithError(t *testing.T) {
</error>
</iq>`
parsedIQ := xmpp.IQ{}
parsedIQ := IQ{}
err := xml.Unmarshal([]byte(iq), &parsedIQ)
if err != nil {
t.Errorf("Unmarshal error: %s", iq)
@ -158,7 +157,7 @@ func TestUnknownPayload(t *testing.T) {
iq := `<iq type="get" to="service.localhost" id="1" >
<query xmlns="unknown:ns"/>
</iq>`
parsedIQ := xmpp.IQ{}
parsedIQ := IQ{}
err := xml.Unmarshal([]byte(iq), &parsedIQ)
if err != nil {
t.Errorf("Unmarshal error: %#v (%s)", err, iq)

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"encoding/xml"

View File

@ -1,15 +1,14 @@
package xmpp_test
package stanza_test
import (
"encoding/xml"
"testing"
"github.com/google/go-cmp/cmp"
"gosrc.io/xmpp"
)
func TestGenerateMessage(t *testing.T) {
message := xmpp.NewMessage(xmpp.Attrs{Type: xmpp.MessageTypeChat, From: "admin@localhost", To: "test@localhost", Id: "1"})
message := NewMessage(Attrs{Type: MessageTypeChat, From: "admin@localhost", To: "test@localhost", Id: "1"})
message.Body = "Hi"
message.Subject = "Msg Subject"
@ -18,7 +17,7 @@ func TestGenerateMessage(t *testing.T) {
t.Errorf("cannot marshal xml structure")
}
parsedMessage := xmpp.Message{}
parsedMessage := Message{}
if err = xml.Unmarshal(data, &parsedMessage); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
@ -38,7 +37,7 @@ func TestDecodeError(t *testing.T) {
</error>
</message>`
parsedMessage := xmpp.Message{}
parsedMessage := Message{}
if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil {
t.Errorf("message error stanza unmarshall error: %v", err)
return
@ -50,15 +49,15 @@ func TestDecodeError(t *testing.T) {
func TestGetOOB(t *testing.T) {
image := "https://localhost/image.png"
msg := xmpp.NewMessage(xmpp.Attrs{To: "test@localhost"})
ext := xmpp.OOB{
msg := NewMessage(Attrs{To: "test@localhost"})
ext := OOB{
XMLName: xml.Name{Space: "jabber:x:oob", Local: "x"},
URL: image,
}
msg.Extensions = append(msg.Extensions, &ext)
// OOB can properly be found
var oob xmpp.OOB
var oob OOB
// Try to find and
if ok := msg.Get(&oob); !ok {
t.Error("could not find oob extension")
@ -69,7 +68,7 @@ func TestGetOOB(t *testing.T) {
}
// Markable is not found
var m xmpp.Markable
var m Markable
if ok := msg.Get(&m); ok {
t.Error("we should not have found markable extension")
}

View File

@ -1,6 +1,8 @@
package xmpp
package stanza
import "encoding/xml"
import (
"encoding/xml"
)
/*
Support for:

View File

@ -1,6 +1,8 @@
package xmpp
package stanza
import "encoding/xml"
import (
"encoding/xml"
)
/*
Support for:

View File

@ -1,6 +1,8 @@
package xmpp
package stanza
import "encoding/xml"
import (
"encoding/xml"
)
type HTML struct {
MsgExtension

View File

@ -1,20 +1,18 @@
package xmpp_test
package stanza_test
import (
"encoding/xml"
"testing"
"gosrc.io/xmpp"
)
func TestHTMLGen(t *testing.T) {
htmlBody := "<p>Hello <b>World</b></p>"
msg := xmpp.NewMessage(xmpp.Attrs{To: "test@localhost"})
msg := NewMessage(Attrs{To: "test@localhost"})
msg.Body = "Hello World"
body := xmpp.HTMLBody{
body := HTMLBody{
InnerXML: htmlBody,
}
html := xmpp.HTML{Body: body}
html := HTML{Body: body}
msg.Extensions = append(msg.Extensions, html)
result := msg.XMPPFormat()
@ -23,7 +21,7 @@ func TestHTMLGen(t *testing.T) {
t.Errorf("incorrect serialize message:\n%s", result)
}
parsedMessage := xmpp.Message{}
parsedMessage := Message{}
if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil {
t.Errorf("message HTML unmarshall error: %v", err)
return
@ -33,7 +31,7 @@ func TestHTMLGen(t *testing.T) {
t.Errorf("incorrect parsed body: '%s'", parsedMessage.Body)
}
var h xmpp.HTML
var h HTML
if ok := parsedMessage.Get(&h); !ok {
t.Error("could not extract HTML body")
}

View File

@ -1,6 +1,8 @@
package xmpp
package stanza
import "encoding/xml"
import (
"encoding/xml"
)
/*
Support for:

View File

@ -1,6 +1,8 @@
package xmpp
package stanza
import "encoding/xml"
import (
"encoding/xml"
)
/*
Support for:

View File

@ -1,10 +1,8 @@
package xmpp_test
package stanza_test
import (
"encoding/xml"
"testing"
"gosrc.io/xmpp"
)
func TestDecodeRequest(t *testing.T) {
@ -15,7 +13,7 @@ func TestDecodeRequest(t *testing.T) {
<body>My lord, dispatch; read o'er these articles.</body>
<request xmlns='urn:xmpp:receipts'/>
</message>`
parsedMessage := xmpp.Message{}
parsedMessage := Message{}
if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil {
t.Errorf("message receipt unmarshall error: %v", err)
return
@ -31,7 +29,7 @@ func TestDecodeRequest(t *testing.T) {
}
switch ext := parsedMessage.Extensions[0].(type) {
case *xmpp.ReceiptRequest:
case *ReceiptRequest:
if ext.XMLName.Local != "request" {
t.Errorf("unexpected extension: %s:%s", ext.XMLName.Space, ext.XMLName.Local)
}

51
stanza/node.go Normal file
View File

@ -0,0 +1,51 @@
package stanza
import "encoding/xml"
// ============================================================================
// Generic / unknown content
// Node is a generic structure to represent XML data. It is used to parse
// unreferenced or custom stanza payload.
type Node struct {
XMLName xml.Name
Attrs []xml.Attr `xml:"-"`
Content string `xml:",innerxml"`
Nodes []Node `xml:",any"`
}
func (n *Node) Namespace() string {
return n.XMLName.Space
}
// Attr represents generic XML attributes, as used on the generic XML Node
// representation.
type Attr struct {
K string
V string
}
// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
// transform generic XML content into hierarchical Node structure.
func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
// Assign "n.Attrs = start.Attr", without repeating xmlns in attributes:
for _, attr := range start.Attr {
// Do not repeat xmlns, it is already in XMLName
if attr.Name.Local != "xmlns" {
n.Attrs = append(n.Attrs, attr)
}
}
type node Node
return d.DecodeElement((*node)(n), &start)
}
// MarshalXML is a custom XML serializer used by xml.Marshal to serialize a
// Node structure to XML.
func (n Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
start.Attr = n.Attrs
start.Name = n.XMLName
err = e.EncodeToken(start)
e.EncodeElement(n.Nodes, xml.StartElement{Name: n.XMLName})
return e.EncodeToken(xml.EndElement{Name: start.Name})
}

View File

@ -1,11 +1,11 @@
package xmpp
package stanza
const (
NSStream = "http://etherx.jabber.org/streams"
nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
nsSession = "urn:ietf:params:xml:ns:xmpp-session"
NSSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
NSBind = "urn:ietf:params:xml:ns:xmpp-bind"
NSSession = "urn:ietf:params:xml:ns:xmpp-session"
NSClient = "jabber:client"
NSComponent = "jabber:component:accept"
)

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
type Packet interface {
Name() string

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
type StanzaType string

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"encoding/xml"
@ -14,7 +14,7 @@ import (
// reattach features (allowing to resume an existing stream at the point the connection was interrupted, without
// getting through the authentication process.
// TODO We should handle stream error from XEP-0114 ( <conflict/> or <host-unknown/> )
func initStream(p *xml.Decoder) (sessionID string, err error) {
func InitStream(p *xml.Decoder) (sessionID string, err error) {
for {
var t xml.Token
t, err = p.Token()
@ -41,31 +41,14 @@ func initStream(p *xml.Decoder) (sessionID string, err error) {
}
}
// Scan XML token stream to find next StartElement.
func nextStart(p *xml.Decoder) (xml.StartElement, error) {
for {
t, err := p.Token()
if err == io.EOF {
return xml.StartElement{}, errors.New("connection closed")
}
if err != nil {
return xml.StartElement{}, fmt.Errorf("nextStart %s", err)
}
switch t := t.(type) {
case xml.StartElement:
return t, nil
}
}
}
// nextPacket scans XML token stream for next complete XMPP stanza.
// NextPacket scans XML token stream for next complete XMPP stanza.
// Once the type of stanza has been identified, a structure is created to decode
// that stanza and returned.
// TODO Use an interface to return packets interface xmppDecoder
// TODO make auth and bind use nextPacket instead of directly nextStart
func nextPacket(p *xml.Decoder) (Packet, error) {
// TODO make auth and bind use NextPacket instead of directly NextStart
func NextPacket(p *xml.Decoder) (Packet, error) {
// Read start element to find out how we want to parse the XMPP packet
se, err := nextStart(p)
se, err := NextStart(p)
if err != nil {
return nil, err
}
@ -74,7 +57,7 @@ func nextPacket(p *xml.Decoder) (Packet, error) {
switch se.Name.Space {
case NSStream:
return decodeStream(p, se)
case nsSASL:
case NSSASL:
return decodeSASL(p, se)
case NSClient:
return decodeClient(p, se)
@ -86,6 +69,23 @@ func nextPacket(p *xml.Decoder) (Packet, error) {
}
}
// Scan XML token stream to find next StartElement.
func NextStart(p *xml.Decoder) (xml.StartElement, error) {
for {
t, err := p.Token()
if err == io.EOF {
return xml.StartElement{}, errors.New("connection closed")
}
if err != nil {
return xml.StartElement{}, fmt.Errorf("NextStart %s", err)
}
switch t := t.(type) {
case xml.StartElement:
return t, nil
}
}
}
/*
TODO: From all the decoder, we can return a pointer to the actual concrete type, instead of directly that
type.

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"encoding/xml"

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"encoding/xml"

View File

@ -1,10 +1,8 @@
package xmpp_test
package stanza_test
import (
"encoding/xml"
"testing"
"gosrc.io/xmpp"
)
// https://xmpp.org/extensions/xep-0045.html#example-27
@ -18,12 +16,12 @@ func TestMucPassword(t *testing.T) {
</x>
</presence>`
var parsedPresence xmpp.Presence
var parsedPresence Presence
if err := xml.Unmarshal([]byte(str), &parsedPresence); err != nil {
t.Errorf("Unmarshal(%s) returned error", str)
}
var muc xmpp.MucPresence
var muc MucPresence
if ok := parsedPresence.Get(&muc); !ok {
t.Error("muc presence extension was not found")
}
@ -44,12 +42,12 @@ func TestMucHistory(t *testing.T) {
</x>
</presence>`
var parsedPresence xmpp.Presence
var parsedPresence Presence
if err := xml.Unmarshal([]byte(str), &parsedPresence); err != nil {
t.Errorf("Unmarshal(%s) returned error", str)
}
var muc xmpp.MucPresence
var muc MucPresence
if ok := parsedPresence.Get(&muc); !ok {
t.Error("muc presence extension was not found")
}

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"encoding/xml"

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
// PresenceShow is a Enum of presence element show
type PresenceShow string

View File

@ -1,24 +1,22 @@
package xmpp_test
package stanza_test
import (
"encoding/xml"
"testing"
"gosrc.io/xmpp"
"github.com/google/go-cmp/cmp"
)
func TestGeneratePresence(t *testing.T) {
presence := xmpp.NewPresence(xmpp.Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
presence.Show = xmpp.PresenceShowChat
presence := NewPresence(Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
presence.Show = PresenceShowChat
data, err := xml.Marshal(presence)
if err != nil {
t.Errorf("cannot marshal xml structure")
}
var parsedPresence xmpp.Presence
var parsedPresence Presence
if err = xml.Unmarshal(data, &parsedPresence); err != nil {
t.Errorf("Unmarshal(%s) returned error", data)
}
@ -32,13 +30,13 @@ func TestPresenceSubElt(t *testing.T) {
// Test structure to ensure that show, status and priority are correctly defined as presence
// package sub-elements
type pres struct {
Show xmpp.PresenceShow `xml:"show"`
Status string `xml:"status"`
Priority int8 `xml:"priority"`
Show PresenceShow `xml:"show"`
Status string `xml:"status"`
Priority int8 `xml:"priority"`
}
presence := xmpp.NewPresence(xmpp.Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
presence.Show = xmpp.PresenceShowXA
presence := NewPresence(Attrs{From: "admin@localhost", To: "test@localhost", Id: "1"})
presence.Show = PresenceShowXA
presence.Status = "Coding"
presence.Priority = 10

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"encoding/xml"

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"encoding/xml"

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"encoding/xml"

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"crypto/tls"
@ -8,7 +8,7 @@ import (
var DefaultTlsConfig tls.Config
// Used during stream initiation / session establishment
type tlsProceed struct {
type TLSProceed struct {
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-tls proceed"`
}

View File

@ -1,4 +1,4 @@
package xmpp
package stanza
import (
"encoding/xml"

View File

@ -1,4 +1,4 @@
package xmpp_test
package stanza_test
import (
"encoding/xml"

View File

@ -6,6 +6,7 @@ import (
"time"
"golang.org/x/xerrors"
"gosrc.io/xmpp/stanza"
)
// The Fluux XMPP lib can manage client or component XMPP streams.
@ -29,7 +30,7 @@ type StreamClient interface {
// Sender is an interface provided by Stream clients to allow sending XMPP data.
type Sender interface {
Send(packet Packet) error
Send(packet stanza.Packet) error
SendRaw(packet string) error
}

View File

@ -4,14 +4,14 @@ import (
"encoding/xml"
"testing"
"gosrc.io/xmpp"
"gosrc.io/xmpp/stanza"
)
func TestNoStartTLS(t *testing.T) {
streamFeatures := `<stream:features xmlns:stream='http://etherx.jabber.org/streams'>
</stream:features>`
var parsedSF xmpp.StreamFeatures
var parsedSF stanza.StreamFeatures
if err := xml.Unmarshal([]byte(streamFeatures), &parsedSF); err != nil {
t.Errorf("Unmarshal(%s) returned error: %v", streamFeatures, err)
}
@ -32,7 +32,7 @@ func TestStartTLS(t *testing.T) {
</starttls>
</stream:features>`
var parsedSF xmpp.StreamFeatures
var parsedSF stanza.StreamFeatures
if err := xml.Unmarshal([]byte(streamFeatures), &parsedSF); err != nil {
t.Errorf("Unmarshal(%s) returned error: %v", streamFeatures, err)
}
@ -52,7 +52,7 @@ func TestStreamManagement(t *testing.T) {
<sm xmlns='urn:xmpp:sm:3'/>
</stream:features>`
var parsedSF xmpp.StreamFeatures
var parsedSF stanza.StreamFeatures
if err := xml.Unmarshal([]byte(streamFeatures), &parsedSF); err != nil {
t.Errorf("Unmarshal(%s) returned error: %v", streamFeatures, err)
}