Merge pull request #148 from remicorniere/ResultSetsRework
- Changed IQ stanzas to pointer semantics - Fixed commands from v 0.4.0 and tests - Added primitive Result Sets support (XEP-0059) - Tests for Result sets are not implemented yet. Result sets seem to be fairly unused across servers and is a little weird to test without a specific implementing XEP like XEP-0313; because the implementations are different across XEPs. Therefore, as 313 is coming, I'll update the tests for XEP-0059 with it.
This commit is contained in:
commit
928c1595ef
|
@ -9,7 +9,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "service.localhost", Id: "custom-pl-1"})
|
iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "service.localhost", Id: "custom-pl-1"})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
payload := CustomPayload{XMLName: xml.Name{Space: "my:custom:payload", Local: "query"}, Node: "test"}
|
payload := CustomPayload{XMLName: xml.Name{Space: "my:custom:payload", Local: "query"}, Node: "test"}
|
||||||
iq.Payload = payload
|
iq.Payload = payload
|
||||||
|
|
||||||
|
@ -45,5 +48,5 @@ func (c CustomPayload) Namespace() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{"my:custom:payload", "query"}, CustomPayload{})
|
stanza.TypeRegistry.MapExtension(stanza.PKTIQ, xml.Name{Space: "my:custom:payload", Local: "query"}, CustomPayload{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -35,7 +35,9 @@ func main() {
|
||||||
IQNamespaces("urn:xmpp:delegation:1").
|
IQNamespaces("urn:xmpp:delegation:1").
|
||||||
HandlerFunc(handleDelegation)
|
HandlerFunc(handleDelegation)
|
||||||
|
|
||||||
component, err := xmpp.NewComponent(opts, router)
|
component, err := xmpp.NewComponent(opts, router, func(err error) {
|
||||||
|
log.Println(err)
|
||||||
|
})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("%+v", err)
|
log.Fatalf("%+v", err)
|
||||||
}
|
}
|
||||||
|
@ -78,7 +80,7 @@ const (
|
||||||
// ctx.Opts
|
// ctx.Opts
|
||||||
func discoInfo(c xmpp.Sender, p stanza.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.(stanza.IQ)
|
iq, ok := p.(*stanza.IQ)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -87,15 +89,18 @@ func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
|
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create IQ response: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
switch info.Node {
|
switch info.Node {
|
||||||
case "":
|
case "":
|
||||||
discoInfoRoot(&iqResp, opts)
|
discoInfoRoot(iqResp, opts)
|
||||||
case pubsubNode:
|
case pubsubNode:
|
||||||
discoInfoPubSub(&iqResp)
|
discoInfoPubSub(iqResp)
|
||||||
case pepNode:
|
case pepNode:
|
||||||
discoInfoPEP(&iqResp)
|
discoInfoPEP(iqResp)
|
||||||
}
|
}
|
||||||
|
|
||||||
_ = c.Send(iqResp)
|
_ = c.Send(iqResp)
|
||||||
|
@ -155,7 +160,7 @@ func discoInfoPEP(iqResp *stanza.IQ) {
|
||||||
|
|
||||||
func handleDelegation(s xmpp.Sender, p stanza.Packet) {
|
func handleDelegation(s xmpp.Sender, p stanza.Packet) {
|
||||||
// Type conversion & sanity checks
|
// Type conversion & sanity checks
|
||||||
iq, ok := p.(stanza.IQ)
|
iq, ok := p.(*stanza.IQ)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -166,7 +171,7 @@ func handleDelegation(s xmpp.Sender, p stanza.Packet) {
|
||||||
}
|
}
|
||||||
forwardedPacket := delegation.Forwarded.Stanza
|
forwardedPacket := delegation.Forwarded.Stanza
|
||||||
fmt.Println(forwardedPacket)
|
fmt.Println(forwardedPacket)
|
||||||
forwardedIQ, ok := forwardedPacket.(stanza.IQ)
|
forwardedIQ, ok := forwardedPacket.(*stanza.IQ)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -179,7 +184,10 @@ func handleDelegation(s xmpp.Sender, p stanza.Packet) {
|
||||||
|
|
||||||
if pubsub.Publish.XMLName.Local == "publish" {
|
if pubsub.Publish.XMLName.Local == "publish" {
|
||||||
// Prepare pubsub IQ reply
|
// Prepare pubsub IQ reply
|
||||||
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
|
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create iqResp: %v", err)
|
||||||
|
}
|
||||||
payload := stanza.PubSubGeneric{
|
payload := stanza.PubSubGeneric{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Space: "http://jabber.org/protocol/pubsub",
|
Space: "http://jabber.org/protocol/pubsub",
|
||||||
|
@ -188,7 +196,10 @@ func handleDelegation(s xmpp.Sender, p stanza.Packet) {
|
||||||
}
|
}
|
||||||
iqResp.Payload = &payload
|
iqResp.Payload = &payload
|
||||||
// Wrap the reply in delegation 'forward'
|
// Wrap the reply in delegation 'forward'
|
||||||
iqForward := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
|
iqForward, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create iqForward: %v", err)
|
||||||
|
}
|
||||||
delegPayload := stanza.Delegation{
|
delegPayload := stanza.Delegation{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Space: "urn:xmpp:delegation:1",
|
Space: "urn:xmpp:delegation:1",
|
||||||
|
|
|
@ -5,7 +5,7 @@ go 1.13
|
||||||
require (
|
require (
|
||||||
github.com/processone/mpg123 v1.0.0
|
github.com/processone/mpg123 v1.0.0
|
||||||
github.com/processone/soundcloud v1.0.0
|
github.com/processone/soundcloud v1.0.0
|
||||||
gosrc.io/xmpp v0.1.1
|
gosrc.io/xmpp v0.4.0
|
||||||
)
|
)
|
||||||
|
|
||||||
replace gosrc.io/xmpp => ./../
|
replace gosrc.io/xmpp => ./../
|
||||||
|
|
|
@ -6,5 +6,5 @@ require (
|
||||||
github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986
|
github.com/awesome-gocui/gocui v0.6.1-0.20191115151952-a34ffb055986
|
||||||
github.com/spf13/pflag v1.0.5
|
github.com/spf13/pflag v1.0.5
|
||||||
github.com/spf13/viper v1.6.1
|
github.com/spf13/viper v1.6.1
|
||||||
gosrc.io/xmpp v0.3.1-0.20191223080939-f8f820170e08
|
gosrc.io/xmpp v0.4.0
|
||||||
)
|
)
|
||||||
|
|
|
@ -186,7 +186,7 @@ func startClient(g *gocui.Gui, config *config) {
|
||||||
|
|
||||||
// ==========================
|
// ==========================
|
||||||
// Start working
|
// Start working
|
||||||
updateRosterFromConfig(g, config)
|
updateRosterFromConfig(config)
|
||||||
// Sending the default contact in a channel. Default value is the first contact in the list from the config.
|
// Sending the default contact in a channel. Default value is the first contact in the list from the config.
|
||||||
viewState.currentContact = strings.Split(config.Contacts, configContactSep)[0]
|
viewState.currentContact = strings.Split(config.Contacts, configContactSep)[0]
|
||||||
// Informing user of the default contact
|
// Informing user of the default contact
|
||||||
|
@ -283,7 +283,7 @@ func errorHandler(err error) {
|
||||||
|
|
||||||
// Read the client roster from the config. This does not check with the server that the roster is correct.
|
// Read the client roster from the config. This does not check with the server that the roster is correct.
|
||||||
// If user tries to send a message to someone not registered with the server, the server will return an error.
|
// If user tries to send a message to someone not registered with the server, the server will return an error.
|
||||||
func updateRosterFromConfig(g *gocui.Gui, config *config) {
|
func updateRosterFromConfig(config *config) {
|
||||||
viewState.contacts = append(strings.Split(config.Contacts, configContactSep), backFromContacts)
|
viewState.contacts = append(strings.Split(config.Contacts, configContactSep), backFromContacts)
|
||||||
// Put a "go back" button at the end of the list
|
// Put a "go back" button at the end of the list
|
||||||
viewState.contacts = append(viewState.contacts, backFromContacts)
|
viewState.contacts = append(viewState.contacts, backFromContacts)
|
||||||
|
|
|
@ -61,12 +61,16 @@ func handleMessage(_ xmpp.Sender, p stanza.Packet) {
|
||||||
|
|
||||||
func discoInfo(c xmpp.Sender, p stanza.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.(stanza.IQ)
|
iq, ok := p.(*stanza.IQ)
|
||||||
if !ok || iq.Type != stanza.IQTypeGet {
|
if !ok || iq.Type != stanza.IQTypeGet {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
||||||
|
// TODO: fix this...
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
disco := iqResp.DiscoInfo()
|
disco := iqResp.DiscoInfo()
|
||||||
disco.AddIdentity(opts.Name, opts.Category, opts.Type)
|
disco.AddIdentity(opts.Name, opts.Category, opts.Type)
|
||||||
disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
|
disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
|
||||||
|
@ -76,7 +80,7 @@ func discoInfo(c xmpp.Sender, p stanza.Packet, opts xmpp.ComponentOptions) {
|
||||||
// TODO: Handle iq error responses
|
// TODO: Handle iq error responses
|
||||||
func discoItems(c xmpp.Sender, p stanza.Packet) {
|
func discoItems(c xmpp.Sender, p stanza.Packet) {
|
||||||
// Type conversion & sanity checks
|
// Type conversion & sanity checks
|
||||||
iq, ok := p.(stanza.IQ)
|
iq, ok := p.(*stanza.IQ)
|
||||||
if !ok || iq.Type != stanza.IQTypeGet {
|
if !ok || iq.Type != stanza.IQTypeGet {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -86,7 +90,11 @@ func discoItems(c xmpp.Sender, p stanza.Packet) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
// TODO: fix this...
|
||||||
|
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
items := iqResp.DiscoItems()
|
items := iqResp.DiscoItems()
|
||||||
|
|
||||||
if discoItems.Node == "" {
|
if discoItems.Node == "" {
|
||||||
|
@ -97,12 +105,15 @@ func discoItems(c xmpp.Sender, p stanza.Packet) {
|
||||||
|
|
||||||
func handleVersion(c xmpp.Sender, p stanza.Packet) {
|
func handleVersion(c xmpp.Sender, p stanza.Packet) {
|
||||||
// Type conversion & sanity checks
|
// Type conversion & sanity checks
|
||||||
iq, ok := p.(stanza.IQ)
|
iq, ok := p.(*stanza.IQ)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
iqResp := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: iq.To, To: iq.From, Id: iq.Id, Lang: "en"})
|
||||||
|
if err != nil {
|
||||||
|
return
|
||||||
|
}
|
||||||
iqResp.Version().SetInfo("Fluux XMPP Component", "0.0.1", "")
|
iqResp.Version().SetInfo("Fluux XMPP Component", "0.0.1", "")
|
||||||
_ = c.Send(iqResp)
|
_ = c.Send(iqResp)
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,9 +9,10 @@ Connect to an XMPP server using XEP 114 protocol, perform a discovery query on t
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
xmpp "gosrc.io/xmpp"
|
"gosrc.io/xmpp"
|
||||||
"gosrc.io/xmpp/stanza"
|
"gosrc.io/xmpp/stanza"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -53,10 +54,13 @@ func main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
// make a disco iq
|
// make a disco iq
|
||||||
iqReq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet,
|
iqReq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet,
|
||||||
From: domain,
|
From: domain,
|
||||||
To: "localhost",
|
To: "localhost",
|
||||||
Id: "my-iq1"})
|
Id: "my-iq1"})
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
disco := iqReq.DiscoInfo()
|
disco := iqReq.DiscoInfo()
|
||||||
iqReq.Payload = disco
|
iqReq.Payload = disco
|
||||||
|
|
||||||
|
|
|
@ -81,7 +81,7 @@ func handleMessage(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func handleIQ(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
|
func handleIQ(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
|
||||||
iq, ok := p.(stanza.IQ)
|
iq, ok := p.(*stanza.IQ)
|
||||||
if !ok {
|
if !ok {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -100,7 +100,7 @@ func handleIQ(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
|
||||||
setResponse := new(stanza.ControlSetResponse)
|
setResponse := new(stanza.ControlSetResponse)
|
||||||
// FIXME: Broken
|
// FIXME: Broken
|
||||||
reply := stanza.IQ{Attrs: stanza.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")
|
||||||
default:
|
default:
|
||||||
|
|
|
@ -3,6 +3,7 @@ package main
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"gosrc.io/xmpp"
|
"gosrc.io/xmpp"
|
||||||
"gosrc.io/xmpp/stanza"
|
"gosrc.io/xmpp/stanza"
|
||||||
|
@ -17,6 +18,8 @@ const (
|
||||||
serviceName = "pubsub.localhost"
|
serviceName = "pubsub.localhost"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var invalidResp = errors.New("invalid response")
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
|
|
||||||
config := xmpp.Config{
|
config := xmpp.Config{
|
||||||
|
@ -52,7 +55,7 @@ func main() {
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
createNode(ctx, cancel, client)
|
createNode(ctx, cancel, client)
|
||||||
|
|
||||||
// =============================
|
// ================================================================================
|
||||||
// Configure the node. This can also be done in a single message with the creation
|
// Configure the node. This can also be done in a single message with the creation
|
||||||
configureNode(ctx, cancel, client)
|
configureNode(ctx, cancel, client)
|
||||||
|
|
||||||
|
@ -68,10 +71,17 @@ func main() {
|
||||||
// Let's purge the node :
|
// Let's purge the node :
|
||||||
purgeRq, _ := stanza.NewPurgeAllItems(serviceName, nodeName)
|
purgeRq, _ := stanza.NewPurgeAllItems(serviceName, nodeName)
|
||||||
purgeCh, err := client.SendIQ(ctx, purgeRq)
|
purgeCh, err := client.SendIQ(ctx, purgeRq)
|
||||||
|
if err != nil {
|
||||||
|
log.Fatalf("could not send purge request: %v", err)
|
||||||
|
}
|
||||||
select {
|
select {
|
||||||
case purgeResp := <-purgeCh:
|
case purgeResp := <-purgeCh:
|
||||||
if purgeResp.Error != nil {
|
|
||||||
|
if purgeResp.Type == stanza.IQTypeError {
|
||||||
cancel()
|
cancel()
|
||||||
|
if vld, err := purgeResp.IsValid(); !vld {
|
||||||
|
log.Fatalf(invalidResp.Error()+" %v"+" reason: %v", purgeResp, err)
|
||||||
|
}
|
||||||
log.Fatalf("error while purging node : %s", purgeResp.Error.Text)
|
log.Fatalf("error while purging node : %s", purgeResp.Error.Text)
|
||||||
}
|
}
|
||||||
log.Println("node successfully purged")
|
log.Println("node successfully purged")
|
||||||
|
@ -97,7 +107,10 @@ func createNode(ctx context.Context, cancel context.CancelFunc, client *xmpp.Cli
|
||||||
select {
|
select {
|
||||||
case respCr := <-createCh:
|
case respCr := <-createCh:
|
||||||
// Got response from server
|
// Got response from server
|
||||||
if respCr.Error != nil {
|
if respCr.Type == stanza.IQTypeError {
|
||||||
|
if vld, err := respCr.IsValid(); !vld {
|
||||||
|
log.Fatalf(invalidResp.Error()+" %+v"+" reason: %s", respCr, err)
|
||||||
|
}
|
||||||
if respCr.Error.Reason != "conflict" {
|
if respCr.Error.Reason != "conflict" {
|
||||||
log.Fatalf("%+v", respCr.Error.Text)
|
log.Fatalf("%+v", respCr.Error.Text)
|
||||||
}
|
}
|
||||||
|
@ -148,8 +161,11 @@ func configureNode(ctx context.Context, cancel context.CancelFunc, client *xmpp.
|
||||||
c, _ := client.SendIQ(ctx, submitConf)
|
c, _ := client.SendIQ(ctx, submitConf)
|
||||||
select {
|
select {
|
||||||
case confResp := <-c:
|
case confResp := <-c:
|
||||||
if confResp.Error != nil {
|
if confResp.Type == stanza.IQTypeError {
|
||||||
cancel()
|
cancel()
|
||||||
|
if vld, err := confResp.IsValid(); !vld {
|
||||||
|
log.Fatalf(invalidResp.Error()+" %v"+" reason: %v", confResp, err)
|
||||||
|
}
|
||||||
log.Fatalf("node configuration failed : %s", confResp.Error.Text)
|
log.Fatalf("node configuration failed : %s", confResp.Error.Text)
|
||||||
}
|
}
|
||||||
log.Println("node configuration was successful")
|
log.Println("node configuration was successful")
|
||||||
|
|
12
bi_dir_iterator.go
Normal file
12
bi_dir_iterator.go
Normal file
|
@ -0,0 +1,12 @@
|
||||||
|
package xmpp
|
||||||
|
|
||||||
|
type BiDirIterator interface {
|
||||||
|
// Next returns the next element of this iterator, if a response is available within t milliseconds
|
||||||
|
Next(t int) (BiDirIteratorElt, error)
|
||||||
|
// Previous returns the previous element of this iterator, if a response is available within t milliseconds
|
||||||
|
Previous(t int) (BiDirIteratorElt, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
type BiDirIteratorElt interface {
|
||||||
|
NoOp()
|
||||||
|
}
|
|
@ -264,7 +264,7 @@ func (c *Client) Send(packet stanza.Packet) error {
|
||||||
// ctx, _ := context.WithTimeout(context.Background(), 30 * time.Second)
|
// ctx, _ := context.WithTimeout(context.Background(), 30 * time.Second)
|
||||||
// result := <- client.SendIQ(ctx, iq)
|
// result := <- client.SendIQ(ctx, iq)
|
||||||
//
|
//
|
||||||
func (c *Client) SendIQ(ctx context.Context, iq stanza.IQ) (chan stanza.IQ, error) {
|
func (c *Client) SendIQ(ctx context.Context, iq *stanza.IQ) (chan stanza.IQ, error) {
|
||||||
if iq.Attrs.Type != stanza.IQTypeSet && iq.Attrs.Type != stanza.IQTypeGet {
|
if iq.Attrs.Type != stanza.IQTypeSet && iq.Attrs.Type != stanza.IQTypeGet {
|
||||||
return nil, ErrCanOnlySendGetOrSetIq
|
return nil, ErrCanOnlySendGetOrSetIq
|
||||||
}
|
}
|
||||||
|
|
|
@ -171,7 +171,11 @@ func TestClient_SendIQ(t *testing.T) {
|
||||||
client, mock := mockClientConnection(t, h, testClientIqPort)
|
client, mock := mockClientConnection(t, h, testClientIqPort)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
iqReq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
iqReq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create the IQ request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
disco := iqReq.DiscoInfo()
|
disco := iqReq.DiscoInfo()
|
||||||
iqReq.Payload = disco
|
iqReq.Payload = disco
|
||||||
|
|
||||||
|
@ -219,7 +223,10 @@ func TestClient_SendIQFail(t *testing.T) {
|
||||||
//==================
|
//==================
|
||||||
// Create an IQ to send
|
// Create an IQ to send
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
iqReq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
iqReq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ request: %v", err)
|
||||||
|
}
|
||||||
disco := iqReq.DiscoInfo()
|
disco := iqReq.DiscoInfo()
|
||||||
iqReq.Payload = disco
|
iqReq.Payload = disco
|
||||||
// Removing the id to make the stanza invalid. The IQ constructor makes a random one if none is specified
|
// Removing the id to make the stanza invalid. The IQ constructor makes a random one if none is specified
|
||||||
|
@ -387,7 +394,7 @@ func handlerClientConnectSuccess(t *testing.T, sc *ServerConn) {
|
||||||
checkClientOpenStream(t, sc)
|
checkClientOpenStream(t, sc)
|
||||||
sendStreamFeatures(t, sc) // Send initial features
|
sendStreamFeatures(t, sc) // Send initial features
|
||||||
readAuth(t, sc.decoder)
|
readAuth(t, sc.decoder)
|
||||||
fmt.Fprintln(sc.connection, "<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>")
|
sc.connection.Write([]byte("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"/>"))
|
||||||
|
|
||||||
checkClientOpenStream(t, sc) // Reset stream
|
checkClientOpenStream(t, sc) // Reset stream
|
||||||
sendBindFeature(t, sc) // Send post auth features
|
sendBindFeature(t, sc) // Send post auth features
|
||||||
|
|
|
@ -38,7 +38,11 @@ func sendxmpp(cmd *cobra.Command, args []string) {
|
||||||
},
|
},
|
||||||
Jid: viper.GetString("jid"),
|
Jid: viper.GetString("jid"),
|
||||||
Credential: xmpp.Password(viper.GetString("password")),
|
Credential: xmpp.Password(viper.GetString("password")),
|
||||||
}, xmpp.NewRouter())
|
},
|
||||||
|
xmpp.NewRouter(),
|
||||||
|
func(err error) {
|
||||||
|
log.Println(err)
|
||||||
|
})
|
||||||
|
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Errorf("error when starting xmpp client: %s", err)
|
log.Errorf("error when starting xmpp client: %s", err)
|
||||||
|
|
|
@ -185,7 +185,7 @@ func (c *Component) sendWithWriter(writer io.Writer, packet []byte) error {
|
||||||
// ctx, _ := context.WithTimeout(context.Background(), 30 * time.Second)
|
// ctx, _ := context.WithTimeout(context.Background(), 30 * time.Second)
|
||||||
// result := <- client.SendIQ(ctx, iq)
|
// result := <- client.SendIQ(ctx, iq)
|
||||||
//
|
//
|
||||||
func (c *Component) SendIQ(ctx context.Context, iq stanza.IQ) (chan stanza.IQ, error) {
|
func (c *Component) SendIQ(ctx context.Context, iq *stanza.IQ) (chan stanza.IQ, error) {
|
||||||
if iq.Attrs.Type != stanza.IQTypeSet && iq.Attrs.Type != stanza.IQTypeGet {
|
if iq.Attrs.Type != stanza.IQTypeSet && iq.Attrs.Type != stanza.IQTypeGet {
|
||||||
return nil, ErrCanOnlySendGetOrSetIq
|
return nil, ErrCanOnlySendGetOrSetIq
|
||||||
}
|
}
|
||||||
|
|
|
@ -93,7 +93,7 @@ func TestGenerateHandshakeId(t *testing.T) {
|
||||||
|
|
||||||
// Try connecting, and storing the resulting streamID in a map.
|
// Try connecting, and storing the resulting streamID in a map.
|
||||||
m := make(map[string]bool)
|
m := make(map[string]bool)
|
||||||
for _, _ = range uuidsArray {
|
for range uuidsArray {
|
||||||
streamId, _ := c.transport.Connect()
|
streamId, _ := c.transport.Connect()
|
||||||
m[c.handshake(streamId)] = true
|
m[c.handshake(streamId)] = true
|
||||||
}
|
}
|
||||||
|
@ -131,7 +131,10 @@ func TestSendIq(t *testing.T) {
|
||||||
c, m := mockComponentConnection(t, testSendIqPort, h)
|
c, m := mockComponentConnection(t, testSendIqPort, h)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
iqReq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
iqReq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ request: %v", err)
|
||||||
|
}
|
||||||
disco := iqReq.DiscoInfo()
|
disco := iqReq.DiscoInfo()
|
||||||
iqReq.Payload = disco
|
iqReq.Payload = disco
|
||||||
|
|
||||||
|
@ -173,7 +176,10 @@ func TestSendIqFail(t *testing.T) {
|
||||||
c, m := mockComponentConnection(t, testSendIqFailPort, h)
|
c, m := mockComponentConnection(t, testSendIqFailPort, h)
|
||||||
|
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
||||||
iqReq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
iqReq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "test1@localhost/mremond-mbp", To: defaultServerName, Id: defaultStreamID, Lang: "en"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
// Removing the id to make the stanza invalid. The IQ constructor makes a random one if none is specified
|
// Removing the id to make the stanza invalid. The IQ constructor makes a random one if none is specified
|
||||||
// so we need to overwrite it.
|
// so we need to overwrite it.
|
||||||
|
|
12
router.go
12
router.go
|
@ -42,7 +42,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 stanza.Packet) {
|
func (r *Router) route(s Sender, p stanza.Packet) {
|
||||||
iq, isIq := p.(stanza.IQ)
|
iq, isIq := p.(*stanza.IQ)
|
||||||
if isIq {
|
if isIq {
|
||||||
r.IQResultRouteLock.RLock()
|
r.IQResultRouteLock.RLock()
|
||||||
route, ok := r.IQResultRoutes[iq.Id]
|
route, ok := r.IQResultRoutes[iq.Id]
|
||||||
|
@ -51,7 +51,7 @@ func (r *Router) route(s Sender, p stanza.Packet) {
|
||||||
r.IQResultRouteLock.Lock()
|
r.IQResultRouteLock.Lock()
|
||||||
delete(r.IQResultRoutes, iq.Id)
|
delete(r.IQResultRoutes, iq.Id)
|
||||||
r.IQResultRouteLock.Unlock()
|
r.IQResultRouteLock.Unlock()
|
||||||
route.result <- iq
|
route.result <- *iq
|
||||||
close(route.result)
|
close(route.result)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -70,7 +70,7 @@ func (r *Router) route(s Sender, p stanza.Packet) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func iqNotImplemented(s Sender, iq stanza.IQ) {
|
func iqNotImplemented(s Sender, iq *stanza.IQ) {
|
||||||
err := stanza.Err{
|
err := stanza.Err{
|
||||||
XMLName: xml.Name{Local: "error"},
|
XMLName: xml.Name{Local: "error"},
|
||||||
Code: 501,
|
Code: 501,
|
||||||
|
@ -232,7 +232,7 @@ func (n nameMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
|
||||||
switch p.(type) {
|
switch p.(type) {
|
||||||
case stanza.Message:
|
case stanza.Message:
|
||||||
name = "message"
|
name = "message"
|
||||||
case stanza.IQ:
|
case *stanza.IQ:
|
||||||
name = "iq"
|
name = "iq"
|
||||||
case stanza.Presence:
|
case stanza.Presence:
|
||||||
name = "presence"
|
name = "presence"
|
||||||
|
@ -259,7 +259,7 @@ type nsTypeMatcher []string
|
||||||
func (m nsTypeMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
|
func (m nsTypeMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
|
||||||
var stanzaType stanza.StanzaType
|
var stanzaType stanza.StanzaType
|
||||||
switch packet := p.(type) {
|
switch packet := p.(type) {
|
||||||
case stanza.IQ:
|
case *stanza.IQ:
|
||||||
stanzaType = packet.Type
|
stanzaType = packet.Type
|
||||||
case stanza.Presence:
|
case stanza.Presence:
|
||||||
stanzaType = packet.Type
|
stanzaType = packet.Type
|
||||||
|
@ -291,7 +291,7 @@ func (r *Route) StanzaType(types ...string) *Route {
|
||||||
type nsIQMatcher []string
|
type nsIQMatcher []string
|
||||||
|
|
||||||
func (m nsIQMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
|
func (m nsIQMatcher) Match(p stanza.Packet, match *RouteMatch) bool {
|
||||||
iq, ok := p.(stanza.IQ)
|
iq, ok := p.(*stanza.IQ)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
@ -25,7 +25,10 @@ func TestIQResultRoutes(t *testing.T) {
|
||||||
// Check if the IQ handler was called
|
// Check if the IQ handler was called
|
||||||
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)
|
ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*100)
|
||||||
defer cancel()
|
defer cancel()
|
||||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, Id: "1234"})
|
iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, Id: "1234"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
res := router.NewIQResultRoute(ctx, "1234")
|
res := router.NewIQResultRoute(ctx, "1234")
|
||||||
go router.route(conn, iq)
|
go router.route(conn, iq)
|
||||||
select {
|
select {
|
||||||
|
@ -71,7 +74,10 @@ 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 := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
|
iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
iq.Payload = &stanza.DiscoInfo{}
|
iq.Payload = &stanza.DiscoInfo{}
|
||||||
router.route(conn, iq)
|
router.route(conn, iq)
|
||||||
if conn.String() == successFlag {
|
if conn.String() == successFlag {
|
||||||
|
@ -89,7 +95,10 @@ func TestIQNSMatcher(t *testing.T) {
|
||||||
|
|
||||||
// Check that an IQ with proper namespace does match
|
// Check that an IQ with proper namespace does match
|
||||||
conn := NewSenderMock()
|
conn := NewSenderMock()
|
||||||
iqDisco := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
|
iqDisco, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create iqDisco: %v", err)
|
||||||
|
}
|
||||||
// TODO: Add a function to generate payload with proper namespace initialisation
|
// TODO: Add a function to generate payload with proper namespace initialisation
|
||||||
iqDisco.Payload = &stanza.DiscoInfo{
|
iqDisco.Payload = &stanza.DiscoInfo{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
|
@ -103,7 +112,10 @@ func TestIQNSMatcher(t *testing.T) {
|
||||||
|
|
||||||
// Check that another namespace is not matched
|
// Check that another namespace is not matched
|
||||||
conn = NewSenderMock()
|
conn = NewSenderMock()
|
||||||
iqVersion := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
|
iqVersion, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, To: "localhost", Id: "1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create iqVersion: %v", err)
|
||||||
|
}
|
||||||
// TODO: Add a function to generate payload with proper namespace initialisation
|
// TODO: Add a function to generate payload with proper namespace initialisation
|
||||||
iqVersion.Payload = &stanza.DiscoInfo{
|
iqVersion.Payload = &stanza.DiscoInfo{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
|
@ -146,7 +158,10 @@ func TestTypeMatcher(t *testing.T) {
|
||||||
|
|
||||||
// We do not match on other types
|
// We do not match on other types
|
||||||
conn = NewSenderMock()
|
conn = NewSenderMock()
|
||||||
iqVersion := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
iqVersion, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create iqVersion: %v", err)
|
||||||
|
}
|
||||||
iqVersion.Payload = &stanza.DiscoInfo{
|
iqVersion.Payload = &stanza.DiscoInfo{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Space: "jabber:iq:version",
|
Space: "jabber:iq:version",
|
||||||
|
@ -169,22 +184,31 @@ func TestCompositeMatcher(t *testing.T) {
|
||||||
})
|
})
|
||||||
|
|
||||||
// Data set
|
// Data set
|
||||||
getVersionIq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
getVersionIq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create getVersionIq: %v", err)
|
||||||
|
}
|
||||||
getVersionIq.Payload = &stanza.Version{
|
getVersionIq.Payload = &stanza.Version{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Space: "jabber:iq:version",
|
Space: "jabber:iq:version",
|
||||||
Local: "query",
|
Local: "query",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
setVersionIq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeSet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
setVersionIq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeSet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create setVersionIq: %v", err)
|
||||||
|
}
|
||||||
setVersionIq.Payload = &stanza.Version{
|
setVersionIq.Payload = &stanza.Version{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Space: "jabber:iq:version",
|
Space: "jabber:iq:version",
|
||||||
Local: "query",
|
Local: "query",
|
||||||
}}
|
}}
|
||||||
|
|
||||||
GetDiscoIq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
getDiscoIq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||||
GetDiscoIq.Payload = &stanza.DiscoInfo{
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create getDiscoIq: %v", err)
|
||||||
|
}
|
||||||
|
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",
|
||||||
|
@ -200,7 +224,7 @@ func TestCompositeMatcher(t *testing.T) {
|
||||||
}{
|
}{
|
||||||
{name: "match get version iq", input: getVersionIq, want: true},
|
{name: "match get version iq", input: getVersionIq, want: true},
|
||||||
{name: "ignore set version iq", input: setVersionIq, want: false},
|
{name: "ignore set version iq", input: setVersionIq, want: false},
|
||||||
{name: "ignore get discoinfo iq", input: GetDiscoIq, want: false},
|
{name: "ignore get discoinfo iq", input: getDiscoIq, want: false},
|
||||||
{name: "ignore message", input: message, want: false},
|
{name: "ignore message", input: message, want: false},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -238,7 +262,10 @@ func TestCatchallMatcher(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
conn = NewSenderMock()
|
conn = NewSenderMock()
|
||||||
iqVersion := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
iqVersion, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "service.localhost", To: "test@localhost", Id: "1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create iqVersion: %v", err)
|
||||||
|
}
|
||||||
iqVersion.Payload = &stanza.DiscoInfo{
|
iqVersion.Payload = &stanza.DiscoInfo{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Space: "jabber:iq:version",
|
Space: "jabber:iq:version",
|
||||||
|
@ -274,7 +301,7 @@ func (s SenderMock) Send(packet stanza.Packet) error {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s SenderMock) SendIQ(ctx context.Context, iq stanza.IQ) (chan stanza.IQ, error) {
|
func (s SenderMock) SendIQ(ctx context.Context, iq *stanza.IQ) (chan stanza.IQ, error) {
|
||||||
out, err := xml.Marshal(iq)
|
out, err := xml.Marshal(iq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
|
@ -97,7 +97,7 @@ func (s *Session) startTlsIfSupported(o Config) {
|
||||||
|
|
||||||
if !s.transport.DoesStartTLS() {
|
if !s.transport.DoesStartTLS() {
|
||||||
if !o.Insecure {
|
if !o.Insecure {
|
||||||
s.err = errors.New("Transport does not support starttls")
|
s.err = errors.New("transport does not support starttls")
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,7 +23,7 @@ const (
|
||||||
type Command struct {
|
type Command struct {
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/commands command"`
|
XMLName xml.Name `xml:"http://jabber.org/protocol/commands command"`
|
||||||
|
|
||||||
CommandElement CommandElement `xml:",any"`
|
CommandElement CommandElement
|
||||||
|
|
||||||
BadAction *struct{} `xml:"bad-action,omitempty"`
|
BadAction *struct{} `xml:"bad-action,omitempty"`
|
||||||
BadLocale *struct{} `xml:"bad-locale,omitempty"`
|
BadLocale *struct{} `xml:"bad-locale,omitempty"`
|
||||||
|
@ -38,12 +38,19 @@ type Command struct {
|
||||||
SessionId string `xml:"sessionid,attr,omitempty"`
|
SessionId string `xml:"sessionid,attr,omitempty"`
|
||||||
Status string `xml:"status,attr,omitempty"`
|
Status string `xml:"status,attr,omitempty"`
|
||||||
Lang string `xml:"lang,attr,omitempty"`
|
Lang string `xml:"lang,attr,omitempty"`
|
||||||
|
|
||||||
|
// Result sets
|
||||||
|
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *Command) Namespace() string {
|
func (c *Command) Namespace() string {
|
||||||
return c.XMLName.Space
|
return c.XMLName.Space
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *Command) GetSet() *ResultSet {
|
||||||
|
return c.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
type CommandElement interface {
|
type CommandElement interface {
|
||||||
Ref() string
|
Ref() string
|
||||||
}
|
}
|
||||||
|
@ -68,6 +75,7 @@ type Note struct {
|
||||||
func (n *Note) Ref() string {
|
func (n *Note) Ref() string {
|
||||||
return "note"
|
return "note"
|
||||||
}
|
}
|
||||||
|
func (f *Form) Ref() string { return "form" }
|
||||||
|
|
||||||
func (n *Node) Ref() string {
|
func (n *Node) Ref() string {
|
||||||
return "node"
|
return "node"
|
||||||
|
@ -117,6 +125,10 @@ func (c *Command) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
nt := Note{}
|
nt := Note{}
|
||||||
d.DecodeElement(&nt, &tt)
|
d.DecodeElement(&nt, &tt)
|
||||||
c.CommandElement = &nt
|
c.CommandElement = &nt
|
||||||
|
case "x":
|
||||||
|
f := Form{}
|
||||||
|
d.DecodeElement(&f, &tt)
|
||||||
|
c.CommandElement = &f
|
||||||
default:
|
default:
|
||||||
n := Node{}
|
n := Node{}
|
||||||
e := d.DecodeElement(&n, &tt)
|
e := d.DecodeElement(&n, &tt)
|
||||||
|
@ -134,3 +146,7 @@ func (c *Command) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: "http://jabber.org/protocol/commands", Local: "command"}, Command{})
|
||||||
|
}
|
||||||
|
|
|
@ -7,34 +7,21 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMarshalCommands(t *testing.T) {
|
func TestMarshalCommands(t *testing.T) {
|
||||||
input := "<command xmlns=\"http://jabber.org/protocol/commands\" node=\"list\" sessionid=\"list:20020923T213616Z-700\" status=\"completed\"><x " +
|
input := "<command xmlns=\"http://jabber.org/protocol/commands\" node=\"list\" " +
|
||||||
"xmlns=\"jabber:x:data\" type=\"result\"><title xmlns=\"jabber:x:data\">Available Servi" +
|
"sessionid=\"list:20020923T213616Z-700\" status=\"completed\"><x xmlns=\"jabber:x:data\" " +
|
||||||
"ces</title><reported xmlns=\"jabber:x:data\"><field xmlns=\"jabber:x:data\" label=\"S" +
|
"type=\"result\"><title>Available Services</title><reported xmlns=\"jabber:x:data\"><field var=\"service\" " +
|
||||||
"ervice\" var=\"service\"></field><field xmlns=\"jabber:x:data\" label=\"Single-User mo" +
|
"label=\"Service\"></field><field var=\"runlevel-1\" label=\"Single-User mode\">" +
|
||||||
"de\" var=\"runlevel-1\"></field><field xmlns=\"jabber:x:data\" label=\"Non-Networked M" +
|
"</field><field var=\"runlevel-2\" label=\"Non-Networked Multi-User mode\"></field><field var=\"runlevel-3\" " +
|
||||||
"ulti-User mode\" var=\"runlevel-2\"></field><field xmlns=\"jabber:x:data\" label=\"Ful" +
|
"label=\"Full Multi-User mode\"></field><field var=\"runlevel-5\" label=\"X-Window mode\"></field></reported>" +
|
||||||
"l Multi-User mode\" var=\"runlevel-3\"></field><field xmlns=\"jabber:x:data\" label=\"" +
|
"<item xmlns=\"jabber:x:data\"><field var=\"service\"><value>httpd</value></field><field var=\"runlevel-1\">" +
|
||||||
"X-Window mode\" var=\"runlevel-5\"></field></reported><item xmlns=\"jabber:x:data\"><" +
|
"<value>off</value></field><field var=\"runlevel-2\"><value>off</value></field><field var=\"runlevel-3\">" +
|
||||||
"field xmlns=\"jabber:x:data\" var=\"service\"><value xmlns=\"jabber:x:data\">httpd</va" +
|
"<value>on</value></field><field var=\"runlevel-5\"><value>on</value></field></item>" +
|
||||||
"lue></field><field xmlns=\"jabber:x:data\" var=\"runlevel-1\"><value xmlns=\"jabber:x" +
|
"<item xmlns=\"jabber:x:data\"><field var=\"service\"><value>postgresql</value></field>" +
|
||||||
":data\">off</value></field><field xmlns=\"jabber:x:data\" var=\"runlevel-2\"><value x" +
|
"<field var=\"runlevel-1\"><value>off</value></field><field var=\"runlevel-2\"><value>off</value></field>" +
|
||||||
"mlns=\"jabber:x:data\">off</value></field><field xmlns=\"jabber:x:data\" var=\"runlev" +
|
"<field var=\"runlevel-3\"><value>on</value></field><field var=\"runlevel-5\"><value>on</value></field></item>" +
|
||||||
"el-3\"><value xmlns=\"jabber:x:data\">on</value></field><field xmlns=\"jabber:x:data" +
|
"<item xmlns=\"jabber:x:data\"><field var=\"service\"><value>jabberd</value></field><field var=\"runlevel-1\">" +
|
||||||
"\" var=\"runlevel-5\"><value xmlns=\"jabber:x:data\">on</value></field></item><item x" +
|
"<value>off</value></field><field var=\"runlevel-2\"><value>off</value></field><field var=\"runlevel-3\">" +
|
||||||
"mlns=\"jabber:x:data\"><field xmlns=\"jabber:x:data\" var=\"service\"><value xmlns=\"ja" +
|
"<value>on</value></field><field var=\"runlevel-5\"><value>on</value></field></item></x></command>"
|
||||||
"bber:x:data\">postgresql</value></field><field xmlns=\"jabber:x:data\" var=\"runleve" +
|
|
||||||
"l-1\"><value xmlns=\"jabber:x:data\">off</value></field><field xmlns=\"jabber:x:data" +
|
|
||||||
"\" var=\"runlevel-2\"><value xmlns=\"jabber:x:data\">off</value></field><field xmlns=" +
|
|
||||||
"\"jabber:x:data\" var=\"runlevel-3\"><value xmlns=\"jabber:x:data\">on</value></field>" +
|
|
||||||
"<field xmlns=\"jabber:x:data\" var=\"runlevel-5\"><value xmlns=\"jabber:x:data\">on</v" +
|
|
||||||
"alue></field></item><item xmlns=\"jabber:x:data\"><field xmlns=\"jabber:x:data\" var" +
|
|
||||||
"=\"service\"><value xmlns=\"jabber:x:data\">jabberd</value></field><field xmlns=\"jab" +
|
|
||||||
"ber:x:data\" var=\"runlevel-1\"><value xmlns=\"jabber:x:data\">off</value></field><fi" +
|
|
||||||
"eld xmlns=\"jabber:x:data\" var=\"runlevel-2\"><value xmlns=\"jabber:x:data\">off</val" +
|
|
||||||
"ue></field><field xmlns=\"jabber:x:data\" var=\"runlevel-3\"><value xmlns=\"jabber:x:" +
|
|
||||||
"data\">on</value></field><field xmlns=\"jabber:x:data\" var=\"runlevel-5\"><value xml" +
|
|
||||||
"ns=\"jabber:x:data\">on</value></field></item></x></command>"
|
|
||||||
|
|
||||||
var c stanza.Command
|
var c stanza.Command
|
||||||
err := xml.Unmarshal([]byte(input), &c)
|
err := xml.Unmarshal([]byte(input), &c)
|
||||||
|
|
||||||
|
|
|
@ -42,11 +42,16 @@ type Delegation struct {
|
||||||
XMLName xml.Name `xml:"urn:xmpp:delegation:1 delegation"`
|
XMLName xml.Name `xml:"urn:xmpp:delegation:1 delegation"`
|
||||||
Forwarded *Forwarded // This is used in iq to wrap delegated iqs
|
Forwarded *Forwarded // This is used in iq to wrap delegated iqs
|
||||||
Delegated *Delegated // This is used in a message to confirm delegated namespace
|
Delegated *Delegated // This is used in a message to confirm delegated namespace
|
||||||
|
// Result sets
|
||||||
|
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *Delegation) Namespace() string {
|
func (d *Delegation) Namespace() string {
|
||||||
return d.XMLName.Space
|
return d.XMLName.Space
|
||||||
}
|
}
|
||||||
|
func (d *Delegation) GetSet() *ResultSet {
|
||||||
|
return d.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
// Forwarded is used to wrapped forwarded stanzas.
|
// Forwarded is used to wrapped forwarded stanzas.
|
||||||
// TODO: Move it in another file, as it is not limited to components.
|
// TODO: Move it in another file, as it is not limited to components.
|
||||||
|
@ -86,6 +91,6 @@ type Delegated struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: "urn:xmpp:delegation:1", Local: "delegation"}, Delegation{})
|
||||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:delegation:1", "delegation"}, Delegation{})
|
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: "urn:xmpp:delegation:1", Local: "delegation"}, Delegation{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -61,7 +61,7 @@ func TestParsingDelegationIQ(t *testing.T) {
|
||||||
if iq.Payload != nil {
|
if iq.Payload != nil {
|
||||||
if delegation, ok := iq.Payload.(*Delegation); ok {
|
if delegation, ok := iq.Payload.(*Delegation); ok {
|
||||||
packet := delegation.Forwarded.Stanza
|
packet := delegation.Forwarded.Stanza
|
||||||
forwardedIQ, ok := packet.(IQ)
|
forwardedIQ, ok := packet.(*IQ)
|
||||||
if !ok {
|
if !ok {
|
||||||
t.Errorf("Could not extract packet IQ")
|
t.Errorf("Could not extract packet IQ")
|
||||||
return
|
return
|
||||||
|
|
|
@ -14,17 +14,18 @@ const (
|
||||||
// See XEP-0004 and XEP-0068
|
// See XEP-0004 and XEP-0068
|
||||||
// Pointer semantics
|
// Pointer semantics
|
||||||
type Form struct {
|
type Form struct {
|
||||||
XMLName xml.Name `xml:"jabber:x:data x"`
|
XMLName xml.Name `xml:"jabber:x:data x"`
|
||||||
Instructions []string `xml:"instructions"`
|
Instructions []string `xml:"instructions"`
|
||||||
Title string `xml:"title,omitempty"`
|
Title string `xml:"title,omitempty"`
|
||||||
Fields []*Field `xml:"field,omitempty"`
|
Fields []*Field `xml:"field,omitempty"`
|
||||||
Reported *FormItem `xml:"reported"`
|
Reported *FormItem `xml:"reported"`
|
||||||
Items []FormItem
|
Items []FormItem `xml:"item,omitempty"`
|
||||||
Type string `xml:"type,attr"`
|
Type string `xml:"type,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type FormItem struct {
|
type FormItem struct {
|
||||||
Fields []Field
|
XMLName xml.Name
|
||||||
|
Fields []Field `xml:"field,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Field struct {
|
type Field struct {
|
||||||
|
|
|
@ -51,7 +51,10 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestMarshalFormSubmit(t *testing.T) {
|
func TestMarshalFormSubmit(t *testing.T) {
|
||||||
formIQ := NewIQ(Attrs{From: clientJid, To: serviceJid, Id: iqId, Type: IQTypeSet})
|
formIQ, err := NewIQ(Attrs{From: clientJid, To: serviceJid, Id: iqId, Type: IQTypeSet})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create formIQ: %v", err)
|
||||||
|
}
|
||||||
formIQ.Payload = &PubSubOwner{
|
formIQ.Payload = &PubSubOwner{
|
||||||
OwnerUseCase: &ConfigureOwner{
|
OwnerUseCase: &ConfigureOwner{
|
||||||
Node: serviceNode,
|
Node: serviceNode,
|
||||||
|
|
|
@ -7,12 +7,18 @@ import (
|
||||||
type ControlSet struct {
|
type ControlSet struct {
|
||||||
XMLName xml.Name `xml:"urn:xmpp:iot:control set"`
|
XMLName xml.Name `xml:"urn:xmpp:iot:control set"`
|
||||||
Fields []ControlField `xml:",any"`
|
Fields []ControlField `xml:",any"`
|
||||||
|
// Result sets
|
||||||
|
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *ControlSet) Namespace() string {
|
func (c *ControlSet) Namespace() string {
|
||||||
return c.XMLName.Space
|
return c.XMLName.Space
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (c *ControlSet) GetSet() *ResultSet {
|
||||||
|
return c.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
type ControlGetForm struct {
|
type ControlGetForm struct {
|
||||||
XMLName xml.Name `xml:"urn:xmpp:iot:control getForm"`
|
XMLName xml.Name `xml:"urn:xmpp:iot:control getForm"`
|
||||||
}
|
}
|
||||||
|
@ -30,10 +36,13 @@ type ControlSetResponse struct {
|
||||||
func (c *ControlSetResponse) Namespace() string {
|
func (c *ControlSetResponse) Namespace() string {
|
||||||
return c.XMLName.Space
|
return c.XMLName.Space
|
||||||
}
|
}
|
||||||
|
func (c *ControlSetResponse) GetSet() *ResultSet {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Registry init
|
// Registry init
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{"urn:xmpp:iot:control", "set"}, ControlSet{})
|
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: "urn:xmpp:iot:control", Local: "set"}, ControlSet{})
|
||||||
}
|
}
|
||||||
|
|
49
stanza/iq.go
49
stanza/iq.go
|
@ -2,6 +2,7 @@ package stanza
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
@ -31,22 +32,28 @@ type IQ struct { // Info/Query
|
||||||
|
|
||||||
type IQPayload interface {
|
type IQPayload interface {
|
||||||
Namespace() string
|
Namespace() string
|
||||||
|
GetSet() *ResultSet
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewIQ(a Attrs) IQ {
|
func NewIQ(a Attrs) (*IQ, error) {
|
||||||
// TODO ensure that type is set, as it is required
|
|
||||||
if a.Id == "" {
|
if a.Id == "" {
|
||||||
if id, err := uuid.NewRandom(); err == nil {
|
if id, err := uuid.NewRandom(); err == nil {
|
||||||
a.Id = id.String()
|
a.Id = id.String()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return IQ{
|
|
||||||
|
iq := IQ{
|
||||||
XMLName: xml.Name{Local: "iq"},
|
XMLName: xml.Name{Local: "iq"},
|
||||||
Attrs: a,
|
Attrs: a,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if iq.Type.IsEmpty() {
|
||||||
|
return nil, IqTypeUnset
|
||||||
|
}
|
||||||
|
return &iq, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func (iq IQ) MakeError(xerror Err) IQ {
|
func (iq *IQ) MakeError(xerror Err) *IQ {
|
||||||
from := iq.From
|
from := iq.From
|
||||||
to := iq.To
|
to := iq.To
|
||||||
|
|
||||||
|
@ -58,18 +65,23 @@ func (iq IQ) MakeError(xerror Err) IQ {
|
||||||
return iq
|
return iq
|
||||||
}
|
}
|
||||||
|
|
||||||
func (IQ) Name() string {
|
func (*IQ) Name() string {
|
||||||
return "iq"
|
return "iq"
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// NoOp to implement BiDirIteratorElt
|
||||||
|
func (*IQ) NoOp() {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
type iqDecoder struct{}
|
type iqDecoder struct{}
|
||||||
|
|
||||||
var iq iqDecoder
|
var iq iqDecoder
|
||||||
|
|
||||||
func (iqDecoder) decode(p *xml.Decoder, se xml.StartElement) (IQ, error) {
|
func (iqDecoder) decode(p *xml.Decoder, se xml.StartElement) (*IQ, error) {
|
||||||
var packet IQ
|
var packet IQ
|
||||||
err := p.DecodeElement(&packet, &se)
|
err := p.DecodeElement(&packet, &se)
|
||||||
return packet, err
|
return &packet, err
|
||||||
}
|
}
|
||||||
|
|
||||||
// UnmarshalXML implements custom parsing for IQs
|
// UnmarshalXML implements custom parsing for IQs
|
||||||
|
@ -134,38 +146,47 @@ func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
IqTypeUnset = errors.New("iq type is not set but is mandatory")
|
||||||
|
IqIDUnset = errors.New("iq stanza ID is not set but is mandatory")
|
||||||
|
IqSGetNoPl = errors.New("iq is of type get or set but has no payload")
|
||||||
|
IqResNoPl = errors.New("iq is of type result but has no payload")
|
||||||
|
IqErrNoErrPl = errors.New("iq is of type error but has no error payload")
|
||||||
|
)
|
||||||
|
|
||||||
|
// IsValid checks if the IQ is valid. If not, return an error with the reason as a message
|
||||||
// Following RFC-3920 for IQs
|
// Following RFC-3920 for IQs
|
||||||
func (iq *IQ) IsValid() bool {
|
func (iq *IQ) IsValid() (bool, error) {
|
||||||
// ID is required
|
// ID is required
|
||||||
if len(strings.TrimSpace(iq.Id)) == 0 {
|
if len(strings.TrimSpace(iq.Id)) == 0 {
|
||||||
return false
|
return false, IqIDUnset
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type is required
|
// Type is required
|
||||||
if iq.Type.IsEmpty() {
|
if iq.Type.IsEmpty() {
|
||||||
return false
|
return false, IqTypeUnset
|
||||||
}
|
}
|
||||||
|
|
||||||
// Type get and set must contain one and only one child element that specifies the semantics
|
// Type get and set must contain one and only one child element that specifies the semantics
|
||||||
if iq.Type == IQTypeGet || iq.Type == IQTypeSet {
|
if iq.Type == IQTypeGet || iq.Type == IQTypeSet {
|
||||||
if iq.Payload == nil && iq.Any == nil {
|
if iq.Payload == nil && iq.Any == nil {
|
||||||
return false
|
return false, IqSGetNoPl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// A result must include zero or one child element
|
// A result must include zero or one child element
|
||||||
if iq.Type == IQTypeResult {
|
if iq.Type == IQTypeResult {
|
||||||
if iq.Payload != nil && iq.Any != nil {
|
if iq.Payload != nil && iq.Any != nil {
|
||||||
return false
|
return false, IqResNoPl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
//Error type must contain an "error" child element
|
//Error type must contain an "error" child element
|
||||||
if iq.Type == IQTypeError {
|
if iq.Type == IQTypeError {
|
||||||
if iq.Error == nil {
|
if iq.Error == nil {
|
||||||
return false
|
return false, IqErrNoErrPl
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
return true, nil
|
||||||
}
|
}
|
||||||
|
|
|
@ -16,10 +16,11 @@ const (
|
||||||
// Namespaces
|
// Namespaces
|
||||||
|
|
||||||
type DiscoInfo struct {
|
type DiscoInfo struct {
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
|
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
|
||||||
Node string `xml:"node,attr,omitempty"`
|
Node string `xml:"node,attr,omitempty"`
|
||||||
Identity []Identity `xml:"identity"`
|
Identity []Identity `xml:"identity"`
|
||||||
Features []Feature `xml:"feature"`
|
Features []Feature `xml:"feature"`
|
||||||
|
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namespace lets DiscoInfo implement the IQPayload interface
|
// Namespace lets DiscoInfo implement the IQPayload interface
|
||||||
|
@ -27,6 +28,10 @@ func (d *DiscoInfo) Namespace() string {
|
||||||
return d.XMLName.Space
|
return d.XMLName.Space
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DiscoInfo) GetSet() *ResultSet {
|
||||||
|
return d.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------
|
// ---------------
|
||||||
// Builder helpers
|
// Builder helpers
|
||||||
|
|
||||||
|
@ -102,12 +107,19 @@ type DiscoItems struct {
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items query"`
|
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items query"`
|
||||||
Node string `xml:"node,attr,omitempty"`
|
Node string `xml:"node,attr,omitempty"`
|
||||||
Items []DiscoItem `xml:"item"`
|
Items []DiscoItem `xml:"item"`
|
||||||
|
|
||||||
|
// Result sets
|
||||||
|
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (d *DiscoItems) Namespace() string {
|
func (d *DiscoItems) Namespace() string {
|
||||||
return d.XMLName.Space
|
return d.XMLName.Space
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (d *DiscoItems) GetSet() *ResultSet {
|
||||||
|
return d.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------
|
// ---------------
|
||||||
// Builder helpers
|
// Builder helpers
|
||||||
|
|
||||||
|
@ -146,6 +158,6 @@ type DiscoItem struct {
|
||||||
// Registry init
|
// Registry init
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{NSDiscoInfo, "query"}, DiscoInfo{})
|
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: NSDiscoInfo, Local: "query"}, DiscoInfo{})
|
||||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{NSDiscoItems, "query"}, DiscoItems{})
|
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: NSDiscoItems, Local: "query"}, DiscoItems{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,7 +9,10 @@ import (
|
||||||
|
|
||||||
// Test DiscoInfo Builder with several features
|
// Test DiscoInfo Builder with several features
|
||||||
func TestDiscoInfo_Builder(t *testing.T) {
|
func TestDiscoInfo_Builder(t *testing.T) {
|
||||||
iq := stanza.NewIQ(stanza.Attrs{Type: "get", To: "service.localhost", Id: "disco-get-1"})
|
iq, err := stanza.NewIQ(stanza.Attrs{Type: "get", To: "service.localhost", Id: "disco-get-1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
disco := iq.DiscoInfo()
|
disco := iq.DiscoInfo()
|
||||||
disco.AddIdentity("Test Component", "gateway", "service")
|
disco.AddIdentity("Test Component", "gateway", "service")
|
||||||
disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
|
disco.AddFeatures(stanza.NSDiscoInfo, stanza.NSDiscoItems, "jabber:iq:version", "urn:xmpp:delegation:1")
|
||||||
|
@ -50,8 +53,11 @@ func TestDiscoInfo_Builder(t *testing.T) {
|
||||||
// Implements XEP-0030 example 17
|
// Implements XEP-0030 example 17
|
||||||
// https://xmpp.org/extensions/xep-0030.html#example-17
|
// https://xmpp.org/extensions/xep-0030.html#example-17
|
||||||
func TestDiscoItems_Builder(t *testing.T) {
|
func TestDiscoItems_Builder(t *testing.T) {
|
||||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "catalog.shakespeare.lit",
|
iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "catalog.shakespeare.lit",
|
||||||
To: "romeo@montague.net/orchard", Id: "items-2"})
|
To: "romeo@montague.net/orchard", Id: "items-2"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
iq.DiscoItems().
|
iq.DiscoItems().
|
||||||
AddItem("catalog.shakespeare.lit", "books", "Books by and about Shakespeare").
|
AddItem("catalog.shakespeare.lit", "books", "Books by and about Shakespeare").
|
||||||
AddItem("catalog.shakespeare.lit", "clothing", "Wear your literary taste with pride").
|
AddItem("catalog.shakespeare.lit", "clothing", "Wear your literary taste with pride").
|
||||||
|
|
|
@ -35,12 +35,17 @@ const (
|
||||||
// Roster struct represents Roster IQs
|
// Roster struct represents Roster IQs
|
||||||
type Roster struct {
|
type Roster struct {
|
||||||
XMLName xml.Name `xml:"jabber:iq:roster query"`
|
XMLName xml.Name `xml:"jabber:iq:roster query"`
|
||||||
|
// Result sets
|
||||||
|
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namespace defines the namespace for the RosterIQ
|
// Namespace defines the namespace for the RosterIQ
|
||||||
func (r *Roster) Namespace() string {
|
func (r *Roster) Namespace() string {
|
||||||
return r.XMLName.Space
|
return r.XMLName.Space
|
||||||
}
|
}
|
||||||
|
func (r *Roster) GetSet() *ResultSet {
|
||||||
|
return r.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------
|
// ---------------
|
||||||
// Builder helpers
|
// Builder helpers
|
||||||
|
@ -64,6 +69,8 @@ func (iq *IQ) RosterIQ() *Roster {
|
||||||
type RosterItems struct {
|
type RosterItems struct {
|
||||||
XMLName xml.Name `xml:"jabber:iq:roster query"`
|
XMLName xml.Name `xml:"jabber:iq:roster query"`
|
||||||
Items []RosterItem `xml:"item"`
|
Items []RosterItem `xml:"item"`
|
||||||
|
// Result sets
|
||||||
|
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Namespace lets RosterItems implement the IQPayload interface
|
// Namespace lets RosterItems implement the IQPayload interface
|
||||||
|
@ -71,6 +78,10 @@ func (r *RosterItems) Namespace() string {
|
||||||
return r.XMLName.Space
|
return r.XMLName.Space
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (r *RosterItems) GetSet() *ResultSet {
|
||||||
|
return r.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
// RosterItem represents an item in the roster iq
|
// RosterItem represents an item in the roster iq
|
||||||
type RosterItem struct {
|
type RosterItem struct {
|
||||||
XMLName xml.Name `xml:"jabber:iq:roster item"`
|
XMLName xml.Name `xml:"jabber:iq:roster item"`
|
||||||
|
|
|
@ -7,7 +7,10 @@ import (
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestRosterBuilder(t *testing.T) {
|
func TestRosterBuilder(t *testing.T) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeResult, From: "romeo@montague.net/orchard"})
|
iq, err := NewIQ(Attrs{Type: IQTypeResult, From: "romeo@montague.net/orchard"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
var noGroup []string
|
var noGroup []string
|
||||||
|
|
||||||
iq.RosterItems().AddItem("xl8ceawrfu8zdneomw1h6h28d@crypho.com",
|
iq.RosterItems().AddItem("xl8ceawrfu8zdneomw1h6h28d@crypho.com",
|
||||||
|
@ -91,7 +94,7 @@ func TestRosterBuilder(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func checkMarshalling(t *testing.T, iq IQ) (*IQ, error) {
|
func checkMarshalling(t *testing.T, iq *IQ) (*IQ, error) {
|
||||||
// Marshall
|
// Marshall
|
||||||
data, err := xml.Marshal(iq)
|
data, err := xml.Marshal(iq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -36,24 +36,36 @@ func TestUnmarshalIqs(t *testing.T) {
|
||||||
|
|
||||||
func TestGenerateIqId(t *testing.T) {
|
func TestGenerateIqId(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
iq := stanza.NewIQ(stanza.Attrs{Id: "1"})
|
iq, err := stanza.NewIQ(stanza.Attrs{Id: "1", Type: "dummy type"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
if iq.Id != "1" {
|
if iq.Id != "1" {
|
||||||
t.Errorf("NewIQ replaced id with %s", iq.Id)
|
t.Errorf("NewIQ replaced id with %s", iq.Id)
|
||||||
}
|
}
|
||||||
|
|
||||||
iq = stanza.NewIQ(stanza.Attrs{})
|
iq, err = stanza.NewIQ(stanza.Attrs{Type: "dummy type"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
if iq.Id == "" {
|
if iq.Id == "" {
|
||||||
t.Error("NewIQ did not generate an Id")
|
t.Error("NewIQ did not generate an Id")
|
||||||
}
|
}
|
||||||
|
|
||||||
otherIq := stanza.NewIQ(stanza.Attrs{})
|
otherIq, err := stanza.NewIQ(stanza.Attrs{Type: "dummy type"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
if iq.Id == otherIq.Id {
|
if iq.Id == otherIq.Id {
|
||||||
t.Errorf("NewIQ generated two identical ids: %s", iq.Id)
|
t.Errorf("NewIQ generated two identical ids: %s", iq.Id)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGenerateIq(t *testing.T) {
|
func TestGenerateIq(t *testing.T) {
|
||||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"})
|
iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: "admin@localhost", To: "test@localhost", Id: "1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
payload := stanza.DiscoInfo{
|
payload := stanza.DiscoInfo{
|
||||||
Identity: []stanza.Identity{
|
Identity: []stanza.Identity{
|
||||||
{Name: "Test Gateway",
|
{Name: "Test Gateway",
|
||||||
|
@ -111,7 +123,10 @@ func TestErrorTag(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestDiscoItems(t *testing.T) {
|
func TestDiscoItems(t *testing.T) {
|
||||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "romeo@montague.net/orchard", To: "catalog.shakespeare.lit", Id: "items3"})
|
iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeGet, From: "romeo@montague.net/orchard", To: "catalog.shakespeare.lit", Id: "items3"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
payload := stanza.DiscoItems{
|
payload := stanza.DiscoItems{
|
||||||
Node: "music",
|
Node: "music",
|
||||||
}
|
}
|
||||||
|
@ -215,8 +230,9 @@ func TestIsValid(t *testing.T) {
|
||||||
t.Errorf("Unmarshal error: %#v (%s)", err, tcase.iq)
|
t.Errorf("Unmarshal error: %#v (%s)", err, tcase.iq)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if !parsedIQ.IsValid() && !tcase.shouldErr {
|
isValid, err := parsedIQ.IsValid()
|
||||||
t.Errorf("failed iq validation for : %s", tcase.iq)
|
if !isValid && !tcase.shouldErr {
|
||||||
|
t.Errorf("failed validation for iq because: %s\nin test case : %s", err, tcase.iq)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,12 +11,18 @@ type Version struct {
|
||||||
Name string `xml:"name,omitempty"`
|
Name string `xml:"name,omitempty"`
|
||||||
Version string `xml:"version,omitempty"`
|
Version string `xml:"version,omitempty"`
|
||||||
OS string `xml:"os,omitempty"`
|
OS string `xml:"os,omitempty"`
|
||||||
|
// Result sets
|
||||||
|
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (v *Version) Namespace() string {
|
func (v *Version) Namespace() string {
|
||||||
return v.XMLName.Space
|
return v.XMLName.Space
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (v *Version) GetSet() *ResultSet {
|
||||||
|
return v.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
// ---------------
|
// ---------------
|
||||||
// Builder helpers
|
// Builder helpers
|
||||||
|
|
||||||
|
@ -41,5 +47,5 @@ func (v *Version) SetInfo(name, version, os string) *Version {
|
||||||
// Registry init
|
// Registry init
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{"jabber:iq:version", "query"}, Version{})
|
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: "jabber:iq:version", Local: "query"}, Version{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,11 @@ func TestVersion_Builder(t *testing.T) {
|
||||||
name := "Exodus"
|
name := "Exodus"
|
||||||
version := "0.7.0.4"
|
version := "0.7.0.4"
|
||||||
os := "Windows-XP 5.01.2600"
|
os := "Windows-XP 5.01.2600"
|
||||||
iq := stanza.NewIQ(stanza.Attrs{Type: "result", From: "romeo@montague.net/orchard",
|
iq, err := stanza.NewIQ(stanza.Attrs{Type: "result", From: "romeo@montague.net/orchard",
|
||||||
To: "juliet@capulet.com/balcony", Id: "version_1"})
|
To: "juliet@capulet.com/balcony", Id: "version_1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
iq.Version().SetInfo(name, version, os)
|
iq.Version().SetInfo(name, version, os)
|
||||||
|
|
||||||
parsedIQ, err := checkMarshalling(t, iq)
|
parsedIQ, err := checkMarshalling(t, iq)
|
||||||
|
|
|
@ -35,8 +35,8 @@ type MarkAcknowledged struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatMarkers, "markable"}, Markable{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatMarkers, Local: "markable"}, Markable{})
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatMarkers, "received"}, MarkReceived{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatMarkers, Local: "received"}, MarkReceived{})
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatMarkers, "displayed"}, MarkDisplayed{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatMarkers, Local: "displayed"}, MarkDisplayed{})
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatMarkers, "acknowledged"}, MarkAcknowledged{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatMarkers, Local: "acknowledged"}, MarkAcknowledged{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -37,9 +37,9 @@ type StatePaused struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatStateNotifications, "active"}, StateActive{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatStateNotifications, Local: "active"}, StateActive{})
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatStateNotifications, "composing"}, StateComposing{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatStateNotifications, Local: "composing"}, StateComposing{})
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatStateNotifications, "gone"}, StateGone{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatStateNotifications, Local: "gone"}, StateGone{})
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatStateNotifications, "inactive"}, StateInactive{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatStateNotifications, Local: "inactive"}, StateInactive{})
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgChatStateNotifications, "paused"}, StatePaused{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgChatStateNotifications, Local: "paused"}, StatePaused{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -18,5 +18,5 @@ type HTMLBody struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{"http://jabber.org/protocol/xhtml-im", "html"}, HTML{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: "http://jabber.org/protocol/xhtml-im", Local: "html"}, HTML{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,5 +17,5 @@ type OOB struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{"jabber:x:oob", "x"}, OOB{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: "jabber:x:oob", Local: "x"}, OOB{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -210,5 +210,4 @@ func (pse *PubSubEvent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) err
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -24,6 +24,6 @@ type ReceiptReceived struct {
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgReceipts, "request"}, ReceiptRequest{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgReceipts, Local: "request"}, ReceiptRequest{})
|
||||||
TypeRegistry.MapExtension(PKTMessage, xml.Name{NSMsgReceipts, "received"}, ReceiptReceived{})
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: NSMsgReceipts, Local: "received"}, ReceiptReceived{})
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,7 +8,10 @@ import (
|
||||||
func TestNode_Marshal(t *testing.T) {
|
func TestNode_Marshal(t *testing.T) {
|
||||||
jsonData := []byte("{\"key\":\"value\"}")
|
jsonData := []byte("{\"key\":\"value\"}")
|
||||||
|
|
||||||
iqResp := NewIQ(Attrs{Type: "result", From: "admin@localhost", To: "test@localhost", Id: "1"})
|
iqResp, err := NewIQ(Attrs{Type: "result", From: "admin@localhost", To: "test@localhost", Id: "1"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
iqResp.Any = &Node{
|
iqResp.Any = &Node{
|
||||||
XMLName: xml.Name{Space: "myNS", Local: "space"},
|
XMLName: xml.Name{Space: "myNS", Local: "space"},
|
||||||
Content: string(jsonData),
|
Content: string(jsonData),
|
||||||
|
|
|
@ -144,5 +144,5 @@ func (h History) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error)
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TypeRegistry.MapExtension(PKTPresence, xml.Name{"http://jabber.org/protocol/muc", "x"}, MucPresence{})
|
TypeRegistry.MapExtension(PKTPresence, xml.Name{Space: "http://jabber.org/protocol/muc", Local: "x"}, MucPresence{})
|
||||||
}
|
}
|
||||||
|
|
125
stanza/pubsub.go
125
stanza/pubsub.go
|
@ -29,12 +29,19 @@ type PubSubGeneric struct {
|
||||||
// To use in responses to sub/unsub for instance
|
// To use in responses to sub/unsub for instance
|
||||||
// Subscription options
|
// Subscription options
|
||||||
Unsubscribe *SubInfo `xml:"unsubscribe,omitempty"`
|
Unsubscribe *SubInfo `xml:"unsubscribe,omitempty"`
|
||||||
|
|
||||||
|
// Result sets
|
||||||
|
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PubSubGeneric) Namespace() string {
|
func (p *PubSubGeneric) Namespace() string {
|
||||||
return p.XMLName.Space
|
return p.XMLName.Space
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (p *PubSubGeneric) GetSet() *ResultSet {
|
||||||
|
return p.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
type Affiliations struct {
|
type Affiliations struct {
|
||||||
List []Affiliation `xml:"affiliation"`
|
List []Affiliation `xml:"affiliation"`
|
||||||
Node string `xml:"node,attr,omitempty"`
|
Node string `xml:"node,attr,omitempty"`
|
||||||
|
@ -156,12 +163,15 @@ type PubSubOption struct {
|
||||||
// It's a Set type IQ.
|
// It's a Set type IQ.
|
||||||
// Information about the subscription and the requester are separated. subInfo contains information about the subscription.
|
// Information about the subscription and the requester are separated. subInfo contains information about the subscription.
|
||||||
// 6.1 Subscribe to a Node
|
// 6.1 Subscribe to a Node
|
||||||
func NewSubRq(serviceId string, subInfo SubInfo) (IQ, error) {
|
func NewSubRq(serviceId string, subInfo SubInfo) (*IQ, error) {
|
||||||
if e := subInfo.validate(); e != nil {
|
if e := subInfo.validate(); e != nil {
|
||||||
return IQ{}, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
Subscribe: &subInfo,
|
Subscribe: &subInfo,
|
||||||
}
|
}
|
||||||
|
@ -172,12 +182,15 @@ func NewSubRq(serviceId string, subInfo SubInfo) (IQ, error) {
|
||||||
// It's a Set type IQ
|
// It's a Set type IQ
|
||||||
// Information about the subscription and the requester are separated. subInfo contains information about the subscription.
|
// Information about the subscription and the requester are separated. subInfo contains information about the subscription.
|
||||||
// 6.2 Unsubscribe from a Node
|
// 6.2 Unsubscribe from a Node
|
||||||
func NewUnsubRq(serviceId string, subInfo SubInfo) (IQ, error) {
|
func NewUnsubRq(serviceId string, subInfo SubInfo) (*IQ, error) {
|
||||||
if e := subInfo.validate(); e != nil {
|
if e := subInfo.validate(); e != nil {
|
||||||
return IQ{}, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
Unsubscribe: &subInfo,
|
Unsubscribe: &subInfo,
|
||||||
}
|
}
|
||||||
|
@ -188,12 +201,15 @@ func NewUnsubRq(serviceId string, subInfo SubInfo) (IQ, error) {
|
||||||
// It's a Get type IQ
|
// It's a Get type IQ
|
||||||
// Information about the subscription and the requester are separated. subInfo contains information about the subscription.
|
// Information about the subscription and the requester are separated. subInfo contains information about the subscription.
|
||||||
// 6.3 Configure Subscription Options
|
// 6.3 Configure Subscription Options
|
||||||
func NewSubOptsRq(serviceId string, subInfo SubInfo) (IQ, error) {
|
func NewSubOptsRq(serviceId string, subInfo SubInfo) (*IQ, error) {
|
||||||
if e := subInfo.validate(); e != nil {
|
if e := subInfo.validate(); e != nil {
|
||||||
return IQ{}, e
|
return nil, e
|
||||||
}
|
}
|
||||||
|
|
||||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
SubOptions: &SubOptions{
|
SubOptions: &SubOptions{
|
||||||
SubInfo: subInfo,
|
SubInfo: subInfo,
|
||||||
|
@ -205,15 +221,18 @@ func NewSubOptsRq(serviceId string, subInfo SubInfo) (IQ, error) {
|
||||||
// NewFormSubmission builds a form submission pubsub IQ
|
// NewFormSubmission builds a form submission pubsub IQ
|
||||||
// Information about the subscription and the requester are separated. subInfo contains information about the subscription.
|
// Information about the subscription and the requester are separated. subInfo contains information about the subscription.
|
||||||
// 6.3.5 Form Submission
|
// 6.3.5 Form Submission
|
||||||
func NewFormSubmission(serviceId string, subInfo SubInfo, form *Form) (IQ, error) {
|
func NewFormSubmission(serviceId string, subInfo SubInfo, form *Form) (*IQ, error) {
|
||||||
if e := subInfo.validate(); e != nil {
|
if e := subInfo.validate(); e != nil {
|
||||||
return IQ{}, e
|
return nil, e
|
||||||
}
|
}
|
||||||
if form.Type != FormTypeSubmit {
|
if form.Type != FormTypeSubmit {
|
||||||
return IQ{}, errors.New("form type was expected to be submit but was : " + form.Type)
|
return nil, errors.New("form type was expected to be submit but was : " + form.Type)
|
||||||
}
|
}
|
||||||
|
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
SubOptions: &SubOptions{
|
SubOptions: &SubOptions{
|
||||||
SubInfo: subInfo,
|
SubInfo: subInfo,
|
||||||
|
@ -229,14 +248,17 @@ func NewFormSubmission(serviceId string, subInfo SubInfo, form *Form) (IQ, error
|
||||||
// since the value of the <subscribe/> element's 'node' attribute specifies the desired NodeID and
|
// since the value of the <subscribe/> element's 'node' attribute specifies the desired NodeID and
|
||||||
// the value of the <subscribe/> element's 'jid' attribute specifies the subscriber's JID
|
// the value of the <subscribe/> element's 'jid' attribute specifies the subscriber's JID
|
||||||
// 6.3.7 Subscribe and Configure
|
// 6.3.7 Subscribe and Configure
|
||||||
func NewSubAndConfig(serviceId string, subInfo SubInfo, form *Form) (IQ, error) {
|
func NewSubAndConfig(serviceId string, subInfo SubInfo, form *Form) (*IQ, error) {
|
||||||
if e := subInfo.validate(); e != nil {
|
if e := subInfo.validate(); e != nil {
|
||||||
return IQ{}, e
|
return nil, e
|
||||||
}
|
}
|
||||||
if form.Type != FormTypeSubmit {
|
if form.Type != FormTypeSubmit {
|
||||||
return IQ{}, errors.New("form type was expected to be submit but was : " + form.Type)
|
return nil, errors.New("form type was expected to be submit but was : " + form.Type)
|
||||||
|
}
|
||||||
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
Subscribe: &subInfo,
|
Subscribe: &subInfo,
|
||||||
SubOptions: &SubOptions{
|
SubOptions: &SubOptions{
|
||||||
|
@ -251,8 +273,11 @@ func NewSubAndConfig(serviceId string, subInfo SubInfo, form *Form) (IQ, error)
|
||||||
// NewItemsRequest creates a request to query existing items from a node.
|
// NewItemsRequest creates a request to query existing items from a node.
|
||||||
// Specify a "maxItems" value to request only the last maxItems items. If 0, requests all items.
|
// Specify a "maxItems" value to request only the last maxItems items. If 0, requests all items.
|
||||||
// 6.5.2 Requesting All List AND 6.5.7 Requesting the Most Recent List
|
// 6.5.2 Requesting All List AND 6.5.7 Requesting the Most Recent List
|
||||||
func NewItemsRequest(serviceId string, node string, maxItems int) (IQ, error) {
|
func NewItemsRequest(serviceId string, node string, maxItems int) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
Items: &Items{Node: node},
|
Items: &Items{Node: node},
|
||||||
}
|
}
|
||||||
|
@ -266,8 +291,11 @@ func NewItemsRequest(serviceId string, node string, maxItems int) (IQ, error) {
|
||||||
|
|
||||||
// NewItemsRequest creates a request to get a specific item from a node.
|
// NewItemsRequest creates a request to get a specific item from a node.
|
||||||
// 6.5.8 Requesting a Particular Item
|
// 6.5.8 Requesting a Particular Item
|
||||||
func NewSpecificItemRequest(serviceId, node, itemId string) (IQ, error) {
|
func NewSpecificItemRequest(serviceId, node, itemId string) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
Items: &Items{Node: node,
|
Items: &Items{Node: node,
|
||||||
List: []Item{
|
List: []Item{
|
||||||
|
@ -281,13 +309,16 @@ func NewSpecificItemRequest(serviceId, node, itemId string) (IQ, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewPublishItemRq creates a request to publish a single item to a node identified by its provided ID
|
// NewPublishItemRq creates a request to publish a single item to a node identified by its provided ID
|
||||||
func NewPublishItemRq(serviceId, nodeID, pubItemID string, item Item) (IQ, error) {
|
func NewPublishItemRq(serviceId, nodeID, pubItemID string, item Item) (*IQ, error) {
|
||||||
// "The <publish/> element MUST possess a 'node' attribute, specifying the NodeID of the node."
|
// "The <publish/> element MUST possess a 'node' attribute, specifying the NodeID of the node."
|
||||||
if strings.TrimSpace(nodeID) == "" {
|
if strings.TrimSpace(nodeID) == "" {
|
||||||
return IQ{}, errors.New("cannot publish without a target node ID")
|
return nil, errors.New("cannot publish without a target node ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
Publish: &Publish{Node: nodeID, Items: []Item{item}},
|
Publish: &Publish{Node: nodeID, Items: []Item{item}},
|
||||||
}
|
}
|
||||||
|
@ -306,13 +337,16 @@ func NewPublishItemRq(serviceId, nodeID, pubItemID string, item Item) (IQ, error
|
||||||
// NewPublishItemOptsRq creates a request to publish items to a node identified by its provided ID, along with configuration options
|
// NewPublishItemOptsRq creates a request to publish items to a node identified by its provided ID, along with configuration options
|
||||||
// A pubsub service MAY support the ability to specify options along with a publish request
|
// A pubsub service MAY support the ability to specify options along with a publish request
|
||||||
//(if so, it MUST advertise support for the "http://jabber.org/protocol/pubsub#publish-options" feature).
|
//(if so, it MUST advertise support for the "http://jabber.org/protocol/pubsub#publish-options" feature).
|
||||||
func NewPublishItemOptsRq(serviceId, nodeID string, items []Item, options *PublishOptions) (IQ, error) {
|
func NewPublishItemOptsRq(serviceId, nodeID string, items []Item, options *PublishOptions) (*IQ, error) {
|
||||||
// "The <publish/> element MUST possess a 'node' attribute, specifying the NodeID of the node."
|
// "The <publish/> element MUST possess a 'node' attribute, specifying the NodeID of the node."
|
||||||
if strings.TrimSpace(nodeID) == "" {
|
if strings.TrimSpace(nodeID) == "" {
|
||||||
return IQ{}, errors.New("cannot publish without a target node ID")
|
return nil, errors.New("cannot publish without a target node ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
Publish: &Publish{Node: nodeID, Items: items},
|
Publish: &Publish{Node: nodeID, Items: items},
|
||||||
PublishOptions: options,
|
PublishOptions: options,
|
||||||
|
@ -324,13 +358,16 @@ func NewPublishItemOptsRq(serviceId, nodeID string, items []Item, options *Publi
|
||||||
// NewDelItemFromNode creates a request to delete and item from a node, given its id.
|
// NewDelItemFromNode creates a request to delete and item from a node, given its id.
|
||||||
// To delete an item, the publisher sends a retract request.
|
// To delete an item, the publisher sends a retract request.
|
||||||
// This helper function follows 7.2 Delete an Item from a Node
|
// This helper function follows 7.2 Delete an Item from a Node
|
||||||
func NewDelItemFromNode(serviceId, nodeID, itemId string, notify *bool) (IQ, error) {
|
func NewDelItemFromNode(serviceId, nodeID, itemId string, notify *bool) (*IQ, error) {
|
||||||
// "The <retract/> element MUST possess a 'node' attribute, specifying the NodeID of the node."
|
// "The <retract/> element MUST possess a 'node' attribute, specifying the NodeID of the node."
|
||||||
if strings.TrimSpace(nodeID) == "" {
|
if strings.TrimSpace(nodeID) == "" {
|
||||||
return IQ{}, errors.New("cannot delete item without a target node ID")
|
return nil, errors.New("cannot delete item without a target node ID")
|
||||||
}
|
}
|
||||||
|
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
Retract: &Retract{Node: nodeID, Items: []Item{{Id: itemId}}, Notify: notify},
|
Retract: &Retract{Node: nodeID, Items: []Item{{Id: itemId}}, Notify: notify},
|
||||||
}
|
}
|
||||||
|
@ -339,8 +376,11 @@ func NewDelItemFromNode(serviceId, nodeID, itemId string, notify *bool) (IQ, err
|
||||||
|
|
||||||
// NewCreateAndConfigNode makes a request for node creation that has the desired node configuration.
|
// NewCreateAndConfigNode makes a request for node creation that has the desired node configuration.
|
||||||
// See 8.1.3 Create and Configure a Node
|
// See 8.1.3 Create and Configure a Node
|
||||||
func NewCreateAndConfigNode(serviceId, nodeID string, confForm *Form) (IQ, error) {
|
func NewCreateAndConfigNode(serviceId, nodeID string, confForm *Form) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
Create: &Create{Node: nodeID},
|
Create: &Create{Node: nodeID},
|
||||||
Configure: &Configure{Form: confForm},
|
Configure: &Configure{Form: confForm},
|
||||||
|
@ -350,8 +390,11 @@ func NewCreateAndConfigNode(serviceId, nodeID string, confForm *Form) (IQ, error
|
||||||
|
|
||||||
// NewCreateNode builds a request to create a node on the service referenced by "serviceId"
|
// NewCreateNode builds a request to create a node on the service referenced by "serviceId"
|
||||||
// See 8.1 Create a Node
|
// See 8.1 Create a Node
|
||||||
func NewCreateNode(serviceId, nodeName string) (IQ, error) {
|
func NewCreateNode(serviceId, nodeName string) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
Create: &Create{Node: nodeName},
|
Create: &Create{Node: nodeName},
|
||||||
}
|
}
|
||||||
|
@ -361,8 +404,11 @@ func NewCreateNode(serviceId, nodeName string) (IQ, error) {
|
||||||
// NewRetrieveAllSubsRequest builds a request to retrieve all subscriptions from all nodes
|
// NewRetrieveAllSubsRequest builds a request to retrieve all subscriptions from all nodes
|
||||||
// In order to make the request, the requesting entity MUST send an IQ-get whose <pubsub/>
|
// In order to make the request, the requesting entity MUST send an IQ-get whose <pubsub/>
|
||||||
// child contains an empty <subscriptions/> element with no attributes.
|
// child contains an empty <subscriptions/> element with no attributes.
|
||||||
func NewRetrieveAllSubsRequest(serviceId string) (IQ, error) {
|
func NewRetrieveAllSubsRequest(serviceId string) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
Subscriptions: &Subscriptions{},
|
Subscriptions: &Subscriptions{},
|
||||||
}
|
}
|
||||||
|
@ -371,8 +417,11 @@ func NewRetrieveAllSubsRequest(serviceId string) (IQ, error) {
|
||||||
|
|
||||||
// NewRetrieveAllAffilsRequest builds a request to retrieve all affiliations from all nodes
|
// NewRetrieveAllAffilsRequest builds a request to retrieve all affiliations from all nodes
|
||||||
// In order to make the request of the service, the requesting entity includes an empty <affiliations/> element with no attributes.
|
// In order to make the request of the service, the requesting entity includes an empty <affiliations/> element with no attributes.
|
||||||
func NewRetrieveAllAffilsRequest(serviceId string) (IQ, error) {
|
func NewRetrieveAllAffilsRequest(serviceId string) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubGeneric{
|
iq.Payload = &PubSubGeneric{
|
||||||
Affiliations: &Affiliations{},
|
Affiliations: &Affiliations{},
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,12 +9,18 @@ import (
|
||||||
type PubSubOwner struct {
|
type PubSubOwner struct {
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/pubsub#owner pubsub"`
|
XMLName xml.Name `xml:"http://jabber.org/protocol/pubsub#owner pubsub"`
|
||||||
OwnerUseCase OwnerUseCase
|
OwnerUseCase OwnerUseCase
|
||||||
|
// Result sets
|
||||||
|
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (pso *PubSubOwner) Namespace() string {
|
func (pso *PubSubOwner) Namespace() string {
|
||||||
return pso.XMLName.Space
|
return pso.XMLName.Space
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (pso *PubSubOwner) GetSet() *ResultSet {
|
||||||
|
return pso.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
type OwnerUseCase interface {
|
type OwnerUseCase interface {
|
||||||
UseCase() string
|
UseCase() string
|
||||||
}
|
}
|
||||||
|
@ -112,8 +118,11 @@ const (
|
||||||
// NewConfigureNode creates a request to configure a node on the given service.
|
// NewConfigureNode creates a request to configure a node on the given service.
|
||||||
// A form will be returned by the service, to which the user must respond using for instance the NewFormSubmission function.
|
// A form will be returned by the service, to which the user must respond using for instance the NewFormSubmission function.
|
||||||
// See 8.2 Configure a Node
|
// See 8.2 Configure a Node
|
||||||
func NewConfigureNode(serviceId, nodeName string) (IQ, error) {
|
func NewConfigureNode(serviceId, nodeName string) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubOwner{
|
iq.Payload = &PubSubOwner{
|
||||||
OwnerUseCase: &ConfigureOwner{Node: nodeName},
|
OwnerUseCase: &ConfigureOwner{Node: nodeName},
|
||||||
}
|
}
|
||||||
|
@ -122,11 +131,14 @@ func NewConfigureNode(serviceId, nodeName string) (IQ, error) {
|
||||||
|
|
||||||
// NewDelNode creates a request to delete node "nodeID" from the "serviceId" service
|
// NewDelNode creates a request to delete node "nodeID" from the "serviceId" service
|
||||||
// See 8.4 Delete a Node
|
// See 8.4 Delete a Node
|
||||||
func NewDelNode(serviceId, nodeID string) (IQ, error) {
|
func NewDelNode(serviceId, nodeID string) (*IQ, error) {
|
||||||
if strings.TrimSpace(nodeID) == "" {
|
if strings.TrimSpace(nodeID) == "" {
|
||||||
return IQ{}, errors.New("cannot delete a node without a target node ID")
|
return nil, errors.New("cannot delete a node without a target node ID")
|
||||||
|
}
|
||||||
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
}
|
}
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
|
||||||
iq.Payload = &PubSubOwner{
|
iq.Payload = &PubSubOwner{
|
||||||
OwnerUseCase: &DeleteOwner{Node: nodeID},
|
OwnerUseCase: &DeleteOwner{Node: nodeID},
|
||||||
}
|
}
|
||||||
|
@ -135,8 +147,11 @@ func NewDelNode(serviceId, nodeID string) (IQ, error) {
|
||||||
|
|
||||||
// NewPurgeAllItems creates a new purge request for the "nodeId" node, at "serviceId" service
|
// NewPurgeAllItems creates a new purge request for the "nodeId" node, at "serviceId" service
|
||||||
// See 8.5 Purge All Node Items
|
// See 8.5 Purge All Node Items
|
||||||
func NewPurgeAllItems(serviceId, nodeId string) (IQ, error) {
|
func NewPurgeAllItems(serviceId, nodeId string) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubOwner{
|
iq.Payload = &PubSubOwner{
|
||||||
OwnerUseCase: &PurgeOwner{Node: nodeId},
|
OwnerUseCase: &PurgeOwner{Node: nodeId},
|
||||||
}
|
}
|
||||||
|
@ -145,8 +160,11 @@ func NewPurgeAllItems(serviceId, nodeId string) (IQ, error) {
|
||||||
|
|
||||||
// NewRequestDefaultConfig build a request to ask the service for the default config of its nodes
|
// NewRequestDefaultConfig build a request to ask the service for the default config of its nodes
|
||||||
// See 8.3 Request Default Node Configuration Options
|
// See 8.3 Request Default Node Configuration Options
|
||||||
func NewRequestDefaultConfig(serviceId string) (IQ, error) {
|
func NewRequestDefaultConfig(serviceId string) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubOwner{
|
iq.Payload = &PubSubOwner{
|
||||||
OwnerUseCase: &DefaultOwner{},
|
OwnerUseCase: &DefaultOwner{},
|
||||||
}
|
}
|
||||||
|
@ -177,8 +195,11 @@ func NewApproveSubRequest(serviceId, reqID string, apprForm *Form) (Message, err
|
||||||
// NewGetPendingSubRequests creates a new request for all pending subscriptions to all their nodes at a service
|
// NewGetPendingSubRequests creates a new request for all pending subscriptions to all their nodes at a service
|
||||||
// This feature MUST be implemented using the Ad-Hoc Commands (XEP-0050) protocol
|
// This feature MUST be implemented using the Ad-Hoc Commands (XEP-0050) protocol
|
||||||
// 8.7 Process Pending Subscription Requests
|
// 8.7 Process Pending Subscription Requests
|
||||||
func NewGetPendingSubRequests(serviceId string) (IQ, error) {
|
func NewGetPendingSubRequests(serviceId string) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &Command{
|
iq.Payload = &Command{
|
||||||
// the command name ('node' attribute of the command element) MUST have a value of "http://jabber.org/protocol/pubsub#get-pending"
|
// the command name ('node' attribute of the command element) MUST have a value of "http://jabber.org/protocol/pubsub#get-pending"
|
||||||
Node: "http://jabber.org/protocol/pubsub#get-pending",
|
Node: "http://jabber.org/protocol/pubsub#get-pending",
|
||||||
|
@ -191,9 +212,9 @@ func NewGetPendingSubRequests(serviceId string) (IQ, error) {
|
||||||
// Upon receiving the data form for managing subscription requests, the owner then MAY request pending subscription
|
// Upon receiving the data form for managing subscription requests, the owner then MAY request pending subscription
|
||||||
// approval requests for a given node.
|
// approval requests for a given node.
|
||||||
// See 8.7.4 Per-Node Request
|
// See 8.7.4 Per-Node Request
|
||||||
func NewApprovePendingSubRequest(serviceId, sessionId, nodeId string) (IQ, error) {
|
func NewApprovePendingSubRequest(serviceId, sessionId, nodeId string) (*IQ, error) {
|
||||||
if sessionId == "" {
|
if sessionId == "" {
|
||||||
return IQ{}, errors.New("the sessionId must be maintained for the command")
|
return nil, errors.New("the sessionId must be maintained for the command")
|
||||||
}
|
}
|
||||||
|
|
||||||
form := &Form{
|
form := &Form{
|
||||||
|
@ -202,12 +223,15 @@ func NewApprovePendingSubRequest(serviceId, sessionId, nodeId string) (IQ, error
|
||||||
}
|
}
|
||||||
data, err := xml.Marshal(form)
|
data, err := xml.Marshal(form)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return IQ{}, err
|
return nil, err
|
||||||
}
|
}
|
||||||
var n Node
|
var n Node
|
||||||
xml.Unmarshal(data, &n)
|
xml.Unmarshal(data, &n)
|
||||||
|
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &Command{
|
iq.Payload = &Command{
|
||||||
// the command name ('node' attribute of the command element) MUST have a value of "http://jabber.org/protocol/pubsub#get-pending"
|
// the command name ('node' attribute of the command element) MUST have a value of "http://jabber.org/protocol/pubsub#get-pending"
|
||||||
Node: "http://jabber.org/protocol/pubsub#get-pending",
|
Node: "http://jabber.org/protocol/pubsub#get-pending",
|
||||||
|
@ -221,16 +245,22 @@ func NewApprovePendingSubRequest(serviceId, sessionId, nodeId string) (IQ, error
|
||||||
// NewSubListRequest creates a request to list subscriptions of the client, for all nodes at the service.
|
// NewSubListRequest creates a request to list subscriptions of the client, for all nodes at the service.
|
||||||
// It's a Get type IQ
|
// It's a Get type IQ
|
||||||
// 8.8.1 Retrieve Subscriptions
|
// 8.8.1 Retrieve Subscriptions
|
||||||
func NewSubListRqPl(serviceId, nodeID string) (IQ, error) {
|
func NewSubListRqPl(serviceId, nodeID string) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubOwner{
|
iq.Payload = &PubSubOwner{
|
||||||
OwnerUseCase: &SubscriptionsOwner{Node: nodeID},
|
OwnerUseCase: &SubscriptionsOwner{Node: nodeID},
|
||||||
}
|
}
|
||||||
return iq, nil
|
return iq, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewSubsForEntitiesRequest(serviceId, nodeID string, subs []SubscriptionOwner) (IQ, error) {
|
func NewSubsForEntitiesRequest(serviceId, nodeID string, subs []SubscriptionOwner) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubOwner{
|
iq.Payload = &PubSubOwner{
|
||||||
OwnerUseCase: &SubscriptionsOwner{Node: nodeID, Subscriptions: subs},
|
OwnerUseCase: &SubscriptionsOwner{Node: nodeID, Subscriptions: subs},
|
||||||
}
|
}
|
||||||
|
@ -239,8 +269,11 @@ func NewSubsForEntitiesRequest(serviceId, nodeID string, subs []SubscriptionOwne
|
||||||
|
|
||||||
// NewModifAffiliationRequest creates a request to either modify one or more affiliations, or delete one or more affiliations
|
// NewModifAffiliationRequest creates a request to either modify one or more affiliations, or delete one or more affiliations
|
||||||
// 8.9.2 Modify Affiliation & 8.9.2.4 Multiple Simultaneous Modifications & 8.9.3 Delete an Entity (just set the status to "none")
|
// 8.9.2 Modify Affiliation & 8.9.2.4 Multiple Simultaneous Modifications & 8.9.3 Delete an Entity (just set the status to "none")
|
||||||
func NewModifAffiliationRequest(serviceId, nodeID string, newAffils []AffiliationOwner) (IQ, error) {
|
func NewModifAffiliationRequest(serviceId, nodeID string, newAffils []AffiliationOwner) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubOwner{
|
iq.Payload = &PubSubOwner{
|
||||||
OwnerUseCase: &AffiliationsOwner{
|
OwnerUseCase: &AffiliationsOwner{
|
||||||
Node: nodeID,
|
Node: nodeID,
|
||||||
|
@ -252,8 +285,11 @@ func NewModifAffiliationRequest(serviceId, nodeID string, newAffils []Affiliatio
|
||||||
|
|
||||||
// NewAffiliationListRequest creates a request to list all affiliated entities
|
// NewAffiliationListRequest creates a request to list all affiliated entities
|
||||||
// See 8.9.1 Retrieve List List
|
// See 8.9.1 Retrieve List List
|
||||||
func NewAffiliationListRequest(serviceId, nodeID string) (IQ, error) {
|
func NewAffiliationListRequest(serviceId, nodeID string) (*IQ, error) {
|
||||||
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
iq, err := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
iq.Payload = &PubSubOwner{
|
iq.Payload = &PubSubOwner{
|
||||||
OwnerUseCase: &AffiliationsOwner{
|
OwnerUseCase: &AffiliationsOwner{
|
||||||
Node: nodeID,
|
Node: nodeID,
|
||||||
|
@ -265,12 +301,15 @@ func NewAffiliationListRequest(serviceId, nodeID string) (IQ, error) {
|
||||||
// NewFormSubmission builds a form submission pubsub IQ, in the Owner namespace
|
// NewFormSubmission builds a form submission pubsub IQ, in the Owner namespace
|
||||||
// This is typically used to respond to a form issued by the server when configuring a node.
|
// This is typically used to respond to a form issued by the server when configuring a node.
|
||||||
// See 8.2.4 Form Submission
|
// See 8.2.4 Form Submission
|
||||||
func NewFormSubmissionOwner(serviceId, nodeName string, fields []*Field) (IQ, error) {
|
func NewFormSubmissionOwner(serviceId, nodeName string, fields []*Field) (*IQ, error) {
|
||||||
if serviceId == "" || nodeName == "" {
|
if serviceId == "" || nodeName == "" {
|
||||||
return IQ{}, errors.New("serviceId and nodeName must be filled for this request to be valid")
|
return nil, errors.New("serviceId and nodeName must be filled for this request to be valid")
|
||||||
}
|
}
|
||||||
|
|
||||||
submitConf := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
submitConf, err := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
submitConf.Payload = &PubSubOwner{
|
submitConf.Payload = &PubSubOwner{
|
||||||
OwnerUseCase: &ConfigureOwner{
|
OwnerUseCase: &ConfigureOwner{
|
||||||
Node: nodeName,
|
Node: nodeName,
|
||||||
|
@ -308,6 +347,17 @@ func (iq *IQ) GetFormFields() (map[string]*Field, error) {
|
||||||
fieldMap[elt.Var] = elt
|
fieldMap[elt.Var] = elt
|
||||||
}
|
}
|
||||||
return fieldMap, nil
|
return fieldMap, nil
|
||||||
|
|
||||||
|
case *Command:
|
||||||
|
fieldMap := make(map[string]*Field)
|
||||||
|
co, ok := payload.CommandElement.(*Form)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("this IQ does not contain a command payload with a form")
|
||||||
|
}
|
||||||
|
for _, elt := range co.Fields {
|
||||||
|
fieldMap[elt.Var] = elt
|
||||||
|
}
|
||||||
|
return fieldMap, nil
|
||||||
default:
|
default:
|
||||||
if iq.Any != nil {
|
if iq.Any != nil {
|
||||||
fieldMap := make(map[string]*Field)
|
fieldMap := make(map[string]*Field)
|
||||||
|
|
|
@ -16,6 +16,9 @@ func TestNewConfigureNode(t *testing.T) {
|
||||||
"</pubsub> </iq>"
|
"</pubsub> </iq>"
|
||||||
|
|
||||||
subR, err := stanza.NewConfigureNode("pubsub.shakespeare.lit", "princely_musings")
|
subR, err := stanza.NewConfigureNode("pubsub.shakespeare.lit", "princely_musings")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a configure node request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "config1"
|
subR.Id = "config1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create request : %s", err)
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
@ -129,6 +132,9 @@ func TestNewRequestDefaultConfig(t *testing.T) {
|
||||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> <default></default> </pubsub> </iq>"
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> <default></default> </pubsub> </iq>"
|
||||||
|
|
||||||
subR, err := stanza.NewRequestDefaultConfig("pubsub.shakespeare.lit")
|
subR, err := stanza.NewRequestDefaultConfig("pubsub.shakespeare.lit")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a default config request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "def1"
|
subR.Id = "def1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create request : %s", err)
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
@ -239,6 +245,9 @@ func TestNewDelNode(t *testing.T) {
|
||||||
"<delete node=\"princely_musings\"></delete> </pubsub> </iq>"
|
"<delete node=\"princely_musings\"></delete> </pubsub> </iq>"
|
||||||
|
|
||||||
subR, err := stanza.NewDelNode("pubsub.shakespeare.lit", "princely_musings")
|
subR, err := stanza.NewDelNode("pubsub.shakespeare.lit", "princely_musings")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a node delete request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "delete1"
|
subR.Id = "delete1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create request : %s", err)
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
@ -311,6 +320,9 @@ func TestNewPurgeAllItems(t *testing.T) {
|
||||||
"<purge node=\"princely_musings\"></purge> </pubsub> </iq>"
|
"<purge node=\"princely_musings\"></purge> </pubsub> </iq>"
|
||||||
|
|
||||||
subR, err := stanza.NewPurgeAllItems("pubsub.shakespeare.lit", "princely_musings")
|
subR, err := stanza.NewPurgeAllItems("pubsub.shakespeare.lit", "princely_musings")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a purge all items request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "purge1"
|
subR.Id = "purge1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create request : %s", err)
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
@ -367,6 +379,9 @@ func TestNewApproveSubRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
subR, err := stanza.NewApproveSubRequest("pubsub.shakespeare.lit", "approve1", apprForm)
|
subR, err := stanza.NewApproveSubRequest("pubsub.shakespeare.lit", "approve1", apprForm)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a sub approval request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "approve1"
|
subR.Id = "approve1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create request : %s", err)
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
@ -404,6 +419,9 @@ func TestNewGetPendingSubRequests(t *testing.T) {
|
||||||
"</command> </iq>"
|
"</command> </iq>"
|
||||||
|
|
||||||
subR, err := stanza.NewGetPendingSubRequests("pubsub.shakespeare.lit")
|
subR, err := stanza.NewGetPendingSubRequests("pubsub.shakespeare.lit")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a get pending subs request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "pending1"
|
subR.Id = "pending1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create request : %s", err)
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
@ -461,12 +479,12 @@ func TestNewGetPendingSubRequestsResp(t *testing.T) {
|
||||||
|
|
||||||
_, ok := respIQ.Payload.(*stanza.Command)
|
_, ok := respIQ.Payload.(*stanza.Command)
|
||||||
if !ok {
|
if !ok {
|
||||||
errors.New("this iq payload is not a command")
|
t.Fatal("this iq payload is not a command")
|
||||||
}
|
}
|
||||||
|
|
||||||
fMap, err := respIQ.GetFormFields()
|
fMap, err := respIQ.GetFormFields()
|
||||||
if err != nil || len(fMap) != 2 {
|
if err != nil || len(fMap) != 2 {
|
||||||
errors.New("could not parse command form fields")
|
t.Fatal("could not parse command form fields")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -485,6 +503,9 @@ func TestNewApprovePendingSubRequest(t *testing.T) {
|
||||||
subR, err := stanza.NewApprovePendingSubRequest("pubsub.shakespeare.lit",
|
subR, err := stanza.NewApprovePendingSubRequest("pubsub.shakespeare.lit",
|
||||||
"pubsub-get-pending:20031021T150901Z-600",
|
"pubsub-get-pending:20031021T150901Z-600",
|
||||||
"princely_musings")
|
"princely_musings")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a approve pending sub request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "pending2"
|
subR.Id = "pending2"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create request : %s", err)
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
@ -524,6 +545,9 @@ func TestNewSubListRqPl(t *testing.T) {
|
||||||
"<subscriptions node=\"princely_musings\"></subscriptions> </pubsub> </iq>"
|
"<subscriptions node=\"princely_musings\"></subscriptions> </pubsub> </iq>"
|
||||||
|
|
||||||
subR, err := stanza.NewSubListRqPl("pubsub.shakespeare.lit", "princely_musings")
|
subR, err := stanza.NewSubListRqPl("pubsub.shakespeare.lit", "princely_musings")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a sub list request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "subman1"
|
subR.Id = "subman1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create request : %s", err)
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
@ -575,7 +599,7 @@ func TestNewSubListRqPlResp(t *testing.T) {
|
||||||
|
|
||||||
pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner)
|
pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner)
|
||||||
if !ok {
|
if !ok {
|
||||||
errors.New("this iq payload is not a command")
|
t.Fatal("this iq payload is not a command")
|
||||||
}
|
}
|
||||||
|
|
||||||
subs, ok := pubsub.OwnerUseCase.(*stanza.SubscriptionsOwner)
|
subs, ok := pubsub.OwnerUseCase.(*stanza.SubscriptionsOwner)
|
||||||
|
@ -599,6 +623,9 @@ func TestNewAffiliationListRequest(t *testing.T) {
|
||||||
"<affiliations node=\"princely_musings\"></affiliations> </pubsub> </iq>"
|
"<affiliations node=\"princely_musings\"></affiliations> </pubsub> </iq>"
|
||||||
|
|
||||||
subR, err := stanza.NewAffiliationListRequest("pubsub.shakespeare.lit", "princely_musings")
|
subR, err := stanza.NewAffiliationListRequest("pubsub.shakespeare.lit", "princely_musings")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create an affiliations list request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "ent1"
|
subR.Id = "ent1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create request : %s", err)
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
@ -648,7 +675,7 @@ func TestNewAffiliationListRequestResp(t *testing.T) {
|
||||||
|
|
||||||
pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner)
|
pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner)
|
||||||
if !ok {
|
if !ok {
|
||||||
errors.New("this iq payload is not a command")
|
t.Fatal("this iq payload is not a command")
|
||||||
}
|
}
|
||||||
|
|
||||||
affils, ok := pubsub.OwnerUseCase.(*stanza.AffiliationsOwner)
|
affils, ok := pubsub.OwnerUseCase.(*stanza.AffiliationsOwner)
|
||||||
|
@ -690,6 +717,9 @@ func TestNewModifAffiliationRequest(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
subR, err := stanza.NewModifAffiliationRequest("pubsub.shakespeare.lit", "princely_musings", affils)
|
subR, err := stanza.NewModifAffiliationRequest("pubsub.shakespeare.lit", "princely_musings", affils)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a modif affiliation request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "ent3"
|
subR.Id = "ent3"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create request : %s", err)
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
@ -833,6 +863,10 @@ func TestNewFormSubmissionOwner(t *testing.T) {
|
||||||
{Var: "pubsub#access_model", ValuesList: []string{"roster"}},
|
{Var: "pubsub#access_model", ValuesList: []string{"roster"}},
|
||||||
{Var: "pubsub#roster_groups_allowed", ValuesList: []string{"friends", "servants", "courtiers"}},
|
{Var: "pubsub#roster_groups_allowed", ValuesList: []string{"friends", "servants", "courtiers"}},
|
||||||
})
|
})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a form submission request: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
subR.Id = "config2"
|
subR.Id = "config2"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create request : %s", err)
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
@ -878,7 +912,7 @@ func getPubSubOwnerPayload(response string) (*stanza.PubSubOwner, error) {
|
||||||
|
|
||||||
pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner)
|
pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner)
|
||||||
if !ok {
|
if !ok {
|
||||||
errors.New("this iq payload is not a pubsub of the owner namespace")
|
return nil, errors.New("this iq payload is not a pubsub of the owner namespace")
|
||||||
}
|
}
|
||||||
|
|
||||||
return pubsub, nil
|
return pubsub, nil
|
||||||
|
|
|
@ -40,6 +40,9 @@ func TestNewSubRequest(t *testing.T) {
|
||||||
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||||
}
|
}
|
||||||
subR, err := stanza.NewSubRq("pubsub.shakespeare.lit", subInfo)
|
subR, err := stanza.NewSubRq("pubsub.shakespeare.lit", subInfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a sub request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "sub1"
|
subR.Id = "sub1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create a sub request : %s", err)
|
t.Fatalf("Could not create a sub request : %s", err)
|
||||||
|
@ -96,6 +99,9 @@ func TestNewUnSubRequest(t *testing.T) {
|
||||||
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||||
}
|
}
|
||||||
subR, err := stanza.NewUnsubRq("pubsub.shakespeare.lit", subInfo)
|
subR, err := stanza.NewUnsubRq("pubsub.shakespeare.lit", subInfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create an unsub request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "unsub1"
|
subR.Id = "unsub1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create a sub request : %s", err)
|
t.Fatalf("Could not create a sub request : %s", err)
|
||||||
|
@ -157,6 +163,9 @@ func TestNewSubOptsRq(t *testing.T) {
|
||||||
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||||
}
|
}
|
||||||
subR, err := stanza.NewSubOptsRq("pubsub.shakespeare.lit", subInfo)
|
subR, err := stanza.NewSubOptsRq("pubsub.shakespeare.lit", subInfo)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a sub options request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "options1"
|
subR.Id = "options1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create a sub request : %s", err)
|
t.Fatalf("Could not create a sub request : %s", err)
|
||||||
|
@ -264,6 +273,9 @@ func TestNewFormSubmission(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
subR, err := stanza.NewFormSubmission("pubsub.shakespeare.lit", subInfo, submitFormExample)
|
subR, err := stanza.NewFormSubmission("pubsub.shakespeare.lit", subInfo, submitFormExample)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a form submission request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "options2"
|
subR.Id = "options2"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create a sub request : %s", err)
|
t.Fatalf("Could not create a sub request : %s", err)
|
||||||
|
@ -313,6 +325,9 @@ func TestNewSubAndConfig(t *testing.T) {
|
||||||
}
|
}
|
||||||
|
|
||||||
subR, err := stanza.NewSubAndConfig("pubsub.shakespeare.lit", subInfo, submitFormExample)
|
subR, err := stanza.NewSubAndConfig("pubsub.shakespeare.lit", subInfo, submitFormExample)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a sub and config request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "sub1"
|
subR.Id = "sub1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create a sub request : %s", err)
|
t.Fatalf("Could not create a sub request : %s", err)
|
||||||
|
@ -482,6 +497,9 @@ func TestNewSpecificItemRequest(t *testing.T) {
|
||||||
"<item id=\"ae890ac52d0df67ed7cfdf51b644e901\"></item> </items> </pubsub> </iq>"
|
"<item id=\"ae890ac52d0df67ed7cfdf51b644e901\"></item> </items> </pubsub> </iq>"
|
||||||
|
|
||||||
subR, err := stanza.NewSpecificItemRequest("pubsub.shakespeare.lit", "princely_musings", "ae890ac52d0df67ed7cfdf51b644e901")
|
subR, err := stanza.NewSpecificItemRequest("pubsub.shakespeare.lit", "princely_musings", "ae890ac52d0df67ed7cfdf51b644e901")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a specific item request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "items3"
|
subR.Id = "items3"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create an items request : %s", err)
|
t.Fatalf("Could not create an items request : %s", err)
|
||||||
|
@ -638,6 +656,9 @@ func TestNewDelItemFromNode(t *testing.T) {
|
||||||
"<item id=\"ae890ac52d0df67ed7cfdf51b644e901\"></item> </retract> </pubsub> </iq>"
|
"<item id=\"ae890ac52d0df67ed7cfdf51b644e901\"></item> </retract> </pubsub> </iq>"
|
||||||
|
|
||||||
subR, err := stanza.NewDelItemFromNode("pubsub.shakespeare.lit", "princely_musings", "ae890ac52d0df67ed7cfdf51b644e901", nil)
|
subR, err := stanza.NewDelItemFromNode("pubsub.shakespeare.lit", "princely_musings", "ae890ac52d0df67ed7cfdf51b644e901", nil)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a delete item from node request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "retract1"
|
subR.Id = "retract1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create a del item request : %s", err)
|
t.Fatalf("Could not create a del item request : %s", err)
|
||||||
|
@ -677,6 +698,9 @@ func TestNewCreateNode(t *testing.T) {
|
||||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <create node=\"princely_musings\"></create> </pubsub> </iq>"
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <create node=\"princely_musings\"></create> </pubsub> </iq>"
|
||||||
|
|
||||||
subR, err := stanza.NewCreateNode("pubsub.shakespeare.lit", "princely_musings")
|
subR, err := stanza.NewCreateNode("pubsub.shakespeare.lit", "princely_musings")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a create node request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "create1"
|
subR.Id = "create1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create a create node request : %s", err)
|
t.Fatalf("Could not create a create node request : %s", err)
|
||||||
|
@ -748,6 +772,10 @@ func TestNewCreateAndConfigNode(t *testing.T) {
|
||||||
{Var: "pubsub#max_payload_size", ValuesList: []string{"1028"}},
|
{Var: "pubsub#max_payload_size", ValuesList: []string{"1028"}},
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a create and config node request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "create1"
|
subR.Id = "create1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create a create node request : %s", err)
|
t.Fatalf("Could not create a create node request : %s", err)
|
||||||
|
@ -796,6 +824,9 @@ func TestNewRetrieveAllSubsRequest(t *testing.T) {
|
||||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <subscriptions></subscriptions> </pubsub> </iq>"
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <subscriptions></subscriptions> </pubsub> </iq>"
|
||||||
|
|
||||||
subR, err := stanza.NewRetrieveAllSubsRequest("pubsub.shakespeare.lit")
|
subR, err := stanza.NewRetrieveAllSubsRequest("pubsub.shakespeare.lit")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a get all subs request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "subscriptions1"
|
subR.Id = "subscriptions1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create a create node request : %s", err)
|
t.Fatalf("Could not create a create node request : %s", err)
|
||||||
|
@ -856,6 +887,9 @@ func TestNewRetrieveAllAffilsRequest(t *testing.T) {
|
||||||
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <affiliations></affiliations> </pubsub> </iq>"
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <affiliations></affiliations> </pubsub> </iq>"
|
||||||
|
|
||||||
subR, err := stanza.NewRetrieveAllAffilsRequest("pubsub.shakespeare.lit")
|
subR, err := stanza.NewRetrieveAllAffilsRequest("pubsub.shakespeare.lit")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create a get all affiliations request: %v", err)
|
||||||
|
}
|
||||||
subR.Id = "affil1"
|
subR.Id = "affil1"
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("Could not create retreive all affiliations request : %s", err)
|
t.Fatalf("Could not create retreive all affiliations request : %s", err)
|
||||||
|
@ -914,7 +948,7 @@ func getPubSubGenericPayload(response string) (*stanza.PubSubGeneric, error) {
|
||||||
|
|
||||||
pubsub, ok := respIQ.Payload.(*stanza.PubSubGeneric)
|
pubsub, ok := respIQ.Payload.(*stanza.PubSubGeneric)
|
||||||
if !ok {
|
if !ok {
|
||||||
errors.New("this iq payload is not a pubsub")
|
return nil, errors.New("this iq payload is not a pubsub")
|
||||||
}
|
}
|
||||||
|
|
||||||
return pubsub, nil
|
return pubsub, nil
|
||||||
|
|
29
stanza/results_sets.go
Normal file
29
stanza/results_sets.go
Normal file
|
@ -0,0 +1,29 @@
|
||||||
|
package stanza
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Support for XEP-0059
|
||||||
|
// See https://xmpp.org/extensions/xep-0059
|
||||||
|
const (
|
||||||
|
// Common but not only possible namespace for query blocks in a result set context
|
||||||
|
NSQuerySet = "jabber:iq:search"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ResultSet struct {
|
||||||
|
XMLName xml.Name `xml:"http://jabber.org/protocol/rsm set"`
|
||||||
|
After *string `xml:"after,omitempty"`
|
||||||
|
Before *string `xml:"before,omitempty"`
|
||||||
|
Count *int `xml:"count,omitempty"`
|
||||||
|
First *First `xml:"first,omitempty"`
|
||||||
|
Index *int `xml:"index,omitempty"`
|
||||||
|
Last *string `xml:"last,omitempty"`
|
||||||
|
Max *int `xml:"max,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type First struct {
|
||||||
|
XMLName xml.Name `xml:"first"`
|
||||||
|
Content string
|
||||||
|
Index *int `xml:"index,attr,omitempty"`
|
||||||
|
}
|
28
stanza/results_sets_test.go
Normal file
28
stanza/results_sets_test.go
Normal file
|
@ -0,0 +1,28 @@
|
||||||
|
package stanza_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"gosrc.io/xmpp/stanza"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Limiting the number of items
|
||||||
|
func TestNewResultSetReq(t *testing.T) {
|
||||||
|
expectedRq := "<iq id=\"q29302\" type=\"set\"> <query xmlns=\"urn:xmpp:mam:2\"> " +
|
||||||
|
"<x type=\"submit\" xmlns=\"jabber:x:data\"> <field type=\"hidden\" var=\"FORM_TYPE\"> " +
|
||||||
|
"<value>urn:xmpp:mam:2</value> </field> <field var=\"start\"> <value>2010-08-07T00:00:00Z</value> </field> </x> " +
|
||||||
|
"<set xmlns=\"http://jabber.org/protocol/rsm\"> <max>10</max> </set> </query> </iq>"
|
||||||
|
|
||||||
|
maxVal := 10
|
||||||
|
rs := &stanza.ResultSet{
|
||||||
|
Max: &maxVal,
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO when Mam is implemented
|
||||||
|
_ = expectedRq
|
||||||
|
_ = rs
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalResultSeqReq(t *testing.T) {
|
||||||
|
// TODO when Mam is implemented
|
||||||
|
|
||||||
|
}
|
|
@ -69,12 +69,18 @@ type Bind struct {
|
||||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
|
||||||
Resource string `xml:"resource,omitempty"`
|
Resource string `xml:"resource,omitempty"`
|
||||||
Jid string `xml:"jid,omitempty"`
|
Jid string `xml:"jid,omitempty"`
|
||||||
|
// Result sets
|
||||||
|
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (b *Bind) Namespace() string {
|
func (b *Bind) Namespace() string {
|
||||||
return b.XMLName.Space
|
return b.XMLName.Space
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (b *Bind) GetSet() *ResultSet {
|
||||||
|
return b.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Session (Obsolete)
|
// Session (Obsolete)
|
||||||
|
|
||||||
|
@ -89,12 +95,18 @@ func (b *Bind) Namespace() string {
|
||||||
type StreamSession struct {
|
type StreamSession struct {
|
||||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"`
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-session session"`
|
||||||
Optional bool // If element does exist, it mean we are not required to open session
|
Optional bool // If element does exist, it mean we are not required to open session
|
||||||
|
// Result sets
|
||||||
|
ResultSet *ResultSet `xml:"set,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (s *StreamSession) Namespace() string {
|
func (s *StreamSession) Namespace() string {
|
||||||
return s.XMLName.Space
|
return s.XMLName.Space
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (s *StreamSession) GetSet() *ResultSet {
|
||||||
|
return s.ResultSet
|
||||||
|
}
|
||||||
|
|
||||||
func (s *StreamSession) IsOptional() bool {
|
func (s *StreamSession) IsOptional() bool {
|
||||||
if s.XMLName.Local == "session" {
|
if s.XMLName.Local == "session" {
|
||||||
return s.Optional
|
return s.Optional
|
||||||
|
|
|
@ -28,7 +28,10 @@ func TestSessionFeatures(t *testing.T) {
|
||||||
|
|
||||||
// Check that the Session tag can be used in IQ decoding
|
// Check that the Session tag can be used in IQ decoding
|
||||||
func TestSessionIQ(t *testing.T) {
|
func TestSessionIQ(t *testing.T) {
|
||||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeSet, Id: "session"})
|
iq, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeSet, Id: "session"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create IQ: %v", err)
|
||||||
|
}
|
||||||
iq.Payload = &stanza.StreamSession{XMLName: xml.Name{Local: "session"}, Optional: true}
|
iq.Payload = &stanza.StreamSession{XMLName: xml.Name{Local: "session"}, Optional: true}
|
||||||
|
|
||||||
data, err := xml.Marshal(iq)
|
data, err := xml.Marshal(iq)
|
||||||
|
|
|
@ -16,7 +16,7 @@ var reInsideWhtsp = regexp.MustCompile(`[\s\p{Zs}]`)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Marshaller / unmarshaller test
|
// Marshaller / unmarshaller test
|
||||||
|
|
||||||
func checkMarshalling(t *testing.T, iq stanza.IQ) (*stanza.IQ, error) {
|
func checkMarshalling(t *testing.T, iq *stanza.IQ) (*stanza.IQ, error) {
|
||||||
// Marshall
|
// Marshall
|
||||||
data, err := xml.Marshal(iq)
|
data, err := xml.Marshal(iq)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
|
@ -27,7 +27,7 @@ type StreamClient interface {
|
||||||
Connect() error
|
Connect() error
|
||||||
Resume(state SMState) error
|
Resume(state SMState) error
|
||||||
Send(packet stanza.Packet) error
|
Send(packet stanza.Packet) error
|
||||||
SendIQ(ctx context.Context, iq stanza.IQ) (chan stanza.IQ, error)
|
SendIQ(ctx context.Context, iq *stanza.IQ) (chan stanza.IQ, error)
|
||||||
SendRaw(packet string) error
|
SendRaw(packet string) error
|
||||||
Disconnect() error
|
Disconnect() error
|
||||||
SetHandler(handler EventHandler)
|
SetHandler(handler EventHandler)
|
||||||
|
@ -37,7 +37,7 @@ type StreamClient interface {
|
||||||
// It is mostly use in callback to pass a limited subset of the stream client interface
|
// It is mostly use in callback to pass a limited subset of the stream client interface
|
||||||
type Sender interface {
|
type Sender interface {
|
||||||
Send(packet stanza.Packet) error
|
Send(packet stanza.Packet) error
|
||||||
SendIQ(ctx context.Context, iq stanza.IQ) (chan stanza.IQ, error)
|
SendIQ(ctx context.Context, iq *stanza.IQ) (chan stanza.IQ, error)
|
||||||
SendRaw(packet string) error
|
SendRaw(packet string) error
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -130,13 +130,16 @@ func respondToIQ(t *testing.T, sc *ServerConn) {
|
||||||
t.Fatalf("failed to receive IQ : %s", err.Error())
|
t.Fatalf("failed to receive IQ : %s", err.Error())
|
||||||
}
|
}
|
||||||
|
|
||||||
if !iqReq.IsValid() {
|
if vld, _ := iqReq.IsValid(); !vld {
|
||||||
mockIQError(sc.connection)
|
mockIQError(sc.connection)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Crafting response
|
// Crafting response
|
||||||
iqResp := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: iqReq.To, To: iqReq.From, Id: iqReq.Id, Lang: "en"})
|
iqResp, err := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeResult, From: iqReq.To, To: iqReq.From, Id: iqReq.Id, Lang: "en"})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to create iqResp: %v", err)
|
||||||
|
}
|
||||||
disco := iqResp.DiscoInfo()
|
disco := iqResp.DiscoInfo()
|
||||||
disco.AddFeatures("vcard-temp",
|
disco.AddFeatures("vcard-temp",
|
||||||
`http://jabber.org/protocol/address`)
|
`http://jabber.org/protocol/address`)
|
||||||
|
|
|
@ -9,8 +9,8 @@ import (
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
var ErrTransportProtocolNotSupported = errors.New("Transport protocol not supported")
|
var ErrTransportProtocolNotSupported = errors.New("transport protocol not supported")
|
||||||
var ErrTLSNotSupported = errors.New("Transport does not support StartTLS")
|
var ErrTLSNotSupported = errors.New("transport does not support StartTLS")
|
||||||
|
|
||||||
// TODO: rename to transport config?
|
// TODO: rename to transport config?
|
||||||
type TransportConfiguration struct {
|
type TransportConfiguration struct {
|
||||||
|
@ -67,7 +67,7 @@ func NewClientTransport(config TransportConfiguration) Transport {
|
||||||
// will be returned.
|
// will be returned.
|
||||||
func NewComponentTransport(config TransportConfiguration) (Transport, error) {
|
func NewComponentTransport(config TransportConfiguration) (Transport, error) {
|
||||||
if strings.HasPrefix(config.Address, "ws:") || strings.HasPrefix(config.Address, "wss:") {
|
if strings.HasPrefix(config.Address, "ws:") || strings.HasPrefix(config.Address, "wss:") {
|
||||||
return nil, fmt.Errorf("Components only support XMPP transport: %w", ErrTransportProtocolNotSupported)
|
return nil, fmt.Errorf("components only support XMPP transport: %w", ErrTransportProtocolNotSupported)
|
||||||
}
|
}
|
||||||
|
|
||||||
config.Address = ensurePort(config.Address, 5222)
|
config.Address = ensurePort(config.Address, 5222)
|
||||||
|
|
|
@ -113,7 +113,7 @@ func (t *XMPPTransport) Ping() error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if n != 1 {
|
if n != 1 {
|
||||||
return errors.New("Could not write ping")
|
return errors.New("could not write ping")
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue