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

View file

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

View file

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

View file

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

View file

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

81
auth.go
View file

@ -6,9 +6,11 @@ import (
"errors" "errors"
"fmt" "fmt"
"io" "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 // TODO: Implement other type of SASL Authentication
havePlain := false havePlain := false
for _, m := range f.Mechanisms.Mechanism { 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 raw := "\x00" + user + "\x00" + password
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw))) enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
base64.StdEncoding.Encode(enc, []byte(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. // Next message should be either success or failure.
val, err := nextPacket(decoder) val, err := stanza.NextPacket(decoder)
if err != nil { if err != nil {
return err return err
} }
switch v := val.(type) { switch v := val.(type) {
case SASLSuccess: case stanza.SASLSuccess:
case SASLFailure: case stanza.SASLFailure:
// v.Any is type of sub-element in failure, which gives a description of what failed. // v.Any is type of sub-element in failure, which gives a description of what failed.
err := errors.New("auth failure: " + v.Any.Local) err := errors.New("auth failure: " + v.Any.Local)
return NewConnError(err, true) return NewConnError(err, true)
@ -49,72 +51,3 @@ func authPlain(socket io.ReadWriter, decoder *xml.Decoder, user string, password
} }
return err 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" "net"
"strings" "strings"
"time" "time"
"gosrc.io/xmpp/stanza"
) )
// TODO: Should I move this as an extension of the client? // TODO: Should I move this as an extension of the client?
@ -49,28 +51,28 @@ func (c *ServerCheck) Check() error {
decoder := xml.NewDecoder(tcpconn) decoder := xml.NewDecoder(tcpconn)
// Send stream open tag // 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 return err
} }
// Set xml decoder and extract streamID from reply (not used for now) // Set xml decoder and extract streamID from reply (not used for now)
_, err = initStream(decoder) _, err = stanza.InitStream(decoder)
if err != nil { if err != nil {
return err return err
} }
// extract stream features // extract stream features
var f StreamFeatures var f stanza.StreamFeatures
packet, err := nextPacket(decoder) packet, err := stanza.NextPacket(decoder)
if err != nil { if err != nil {
err = fmt.Errorf("stream open decode features: %s", err) err = fmt.Errorf("stream open decode features: %s", err)
return err return err
} }
switch p := packet.(type) { switch p := packet.(type) {
case StreamFeatures: case stanza.StreamFeatures:
f = p f = p
case StreamError: case stanza.StreamError:
return errors.New("open stream error: " + p.Error.Local) return errors.New("open stream error: " + p.Error.Local)
default: default:
return errors.New("expected packet received while expecting features, got " + p.Name()) 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 { if _, ok := f.DoesStartTLS(); ok {
fmt.Fprintf(tcpconn, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>") 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 { if err = decoder.DecodeElement(&k, nil); err != nil {
return fmt.Errorf("expecting starttls proceed: %s", err) return fmt.Errorf("expecting starttls proceed: %s", err)
} }
DefaultTlsConfig.ServerName = c.domain stanza.DefaultTlsConfig.ServerName = c.domain
tlsConn := tls.Client(tcpconn, &DefaultTlsConfig) tlsConn := tls.Client(tcpconn, &stanza.DefaultTlsConfig)
// We convert existing connection to TLS // We convert existing connection to TLS
if err = tlsConn.Handshake(); err != nil { if err = tlsConn.Handshake(); err != nil {
return err return err

View file

@ -6,6 +6,8 @@ import (
"fmt" "fmt"
"net" "net"
"time" "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. // 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 conn := c.conn
if conn == nil { if conn == nil {
return errors.New("client is not connected") return errors.New("client is not connected")
@ -191,7 +193,7 @@ func (c *Client) SendRaw(packet string) error {
// Loop: Receive data from server // Loop: Receive data from server
func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) { func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) {
for { for {
val, err := nextPacket(c.Session.decoder) val, err := stanza.NextPacket(c.Session.decoder)
if err != nil { if err != nil {
close(keepaliveQuit) close(keepaliveQuit)
c.updateState(StateDisconnected) c.updateState(StateDisconnected)
@ -200,7 +202,7 @@ func (c *Client) recv(keepaliveQuit chan<- struct{}) (err error) {
// Handle stream errors // Handle stream errors
switch packet := val.(type) { switch packet := val.(type) {
case StreamError: case stanza.StreamError:
c.router.route(c, val) c.router.route(c, val)
close(keepaliveQuit) close(keepaliveQuit)
c.streamError(packet.Error.Local, packet.Text) c.streamError(packet.Error.Local, packet.Text)

View file

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

View file

@ -9,6 +9,8 @@ import (
"io" "io"
"net" "net"
"time" "time"
"gosrc.io/xmpp/stanza"
) )
const componentStreamOpen = "<?xml version='1.0'?><stream:stream to='%s' xmlns='%s' xmlns:stream='%s'>" 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 c.conn = conn
// 1. Send stream open tag // 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()) return errors.New("cannot send stream open " + err.Error())
} }
c.decoder = xml.NewDecoder(conn) c.decoder = xml.NewDecoder(conn)
// 2. Initialize xml decoder and extract streamID from reply // 2. Initialize xml decoder and extract streamID from reply
streamId, err := initStream(c.decoder) streamId, err := stanza.InitStream(c.decoder)
if err != nil { if err != nil {
return errors.New("cannot init decoder " + err.Error()) return errors.New("cannot init decoder " + err.Error())
} }
@ -89,15 +91,15 @@ func (c *Component) Connect() error {
} }
// 4. Check server response for authentication // 4. Check server response for authentication
val, err := nextPacket(c.decoder) val, err := stanza.NextPacket(c.decoder)
if err != nil { if err != nil {
return err return err
} }
switch v := val.(type) { switch v := val.(type) {
case StreamError: case stanza.StreamError:
return errors.New("handshake failed " + v.Error.Local) return errors.New("handshake failed " + v.Error.Local)
case Handshake: case stanza.Handshake:
// Start the receiver go routine // Start the receiver go routine
go c.recv() go c.recv()
return nil return nil
@ -119,7 +121,7 @@ func (c *Component) SetHandler(handler EventHandler) {
// Receiver Go routine receiver // Receiver Go routine receiver
func (c *Component) recv() (err error) { func (c *Component) recv() (err error) {
for { for {
val, err := nextPacket(c.decoder) val, err := stanza.NextPacket(c.decoder)
if err != nil { if err != nil {
c.updateState(StateDisconnected) c.updateState(StateDisconnected)
return err return err
@ -127,7 +129,7 @@ func (c *Component) recv() (err error) {
// Handle stream errors // Handle stream errors
switch p := val.(type) { switch p := val.(type) {
case StreamError: case stanza.StreamError:
c.router.route(c, val) c.router.route(c, val)
c.streamError(p.Error.Local, p.Text) c.streamError(p.Error.Local, p.Text)
return errors.New("stream error: " + p.Error.Local) 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. // 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 conn := c.conn
if conn == nil { if conn == nil {
return errors.New("component is not connected") return errors.New("component is not connected")
@ -186,90 +188,6 @@ func (c *Component) handshake(streamId string) string {
return encodedStr 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: Add support for discovery management directly in component
TODO: Support multiple identities on disco info TODO: Support multiple identities on disco info

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,4 +1,4 @@
package xmpp package stanza
import ( import (
"encoding/xml" "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 // Disco

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -1,10 +1,8 @@
package xmpp_test package stanza_test
import ( import (
"encoding/xml" "encoding/xml"
"testing" "testing"
"gosrc.io/xmpp"
) )
func TestDecodeRequest(t *testing.T) { func TestDecodeRequest(t *testing.T) {
@ -15,7 +13,7 @@ func TestDecodeRequest(t *testing.T) {
<body>My lord, dispatch; read o'er these articles.</body> <body>My lord, dispatch; read o'er these articles.</body>
<request xmlns='urn:xmpp:receipts'/> <request xmlns='urn:xmpp:receipts'/>
</message>` </message>`
parsedMessage := xmpp.Message{} parsedMessage := Message{}
if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil { if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil {
t.Errorf("message receipt unmarshall error: %v", err) t.Errorf("message receipt unmarshall error: %v", err)
return return
@ -31,7 +29,7 @@ func TestDecodeRequest(t *testing.T) {
} }
switch ext := parsedMessage.Extensions[0].(type) { switch ext := parsedMessage.Extensions[0].(type) {
case *xmpp.ReceiptRequest: case *ReceiptRequest:
if ext.XMLName.Local != "request" { if ext.XMLName.Local != "request" {
t.Errorf("unexpected extension: %s:%s", ext.XMLName.Space, ext.XMLName.Local) 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 ( const (
NSStream = "http://etherx.jabber.org/streams" NSStream = "http://etherx.jabber.org/streams"
nsTLS = "urn:ietf:params:xml:ns:xmpp-tls" nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl" NSSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
nsBind = "urn:ietf:params:xml:ns:xmpp-bind" NSBind = "urn:ietf:params:xml:ns:xmpp-bind"
nsSession = "urn:ietf:params:xml:ns:xmpp-session" NSSession = "urn:ietf:params:xml:ns:xmpp-session"
NSClient = "jabber:client" NSClient = "jabber:client"
NSComponent = "jabber:component:accept" NSComponent = "jabber:component:accept"
) )

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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