mirror of
https://github.com/FluuxIO/go-xmpp.git
synced 2024-11-21 10:02:00 -08:00
PubSub protocol support (#142)
* PubSub protocol support Added support for : - XEP-0050 (Command)) - XEP-0060 (PubSub) - XEP-0004 (Forms) Fixed the NewClient function by adding parsing of the domain from the JID if no domain is provided in transport config. Updated xmpp_jukebox example * Delete useless pubsub errors * README.md update Fixed import in echo example * Typo * Fixed raw send on client example * Fixed jukebox example and added a README.md
This commit is contained in:
parent
6e2ba9ca57
commit
947fcf0432
17
README.md
17
README.md
@ -52,6 +52,13 @@ config := xmpp.Config{
|
|||||||
- [XEP-0355: Namespace Delegation](https://xmpp.org/extensions/xep-0355.html)
|
- [XEP-0355: Namespace Delegation](https://xmpp.org/extensions/xep-0355.html)
|
||||||
- [XEP-0356: Privileged Entity](https://xmpp.org/extensions/xep-0356.html)
|
- [XEP-0356: Privileged Entity](https://xmpp.org/extensions/xep-0356.html)
|
||||||
|
|
||||||
|
### Extensions
|
||||||
|
- [XEP-0060: Publish-Subscribe](https://xmpp.org/extensions/xep-0060.html)
|
||||||
|
Note : "6.5.4 Returning Some Items" requires support for [XEP-0059: Result Set Management](https://xmpp.org/extensions/xep-0059.html),
|
||||||
|
and is therefore not supported yet.
|
||||||
|
- [XEP-0004: Data Forms](https://xmpp.org/extensions/xep-0004.html)
|
||||||
|
- [XEP-0050: Ad-Hoc Commands](https://xmpp.org/extensions/xep-0050.html)
|
||||||
|
|
||||||
## Package overview
|
## Package overview
|
||||||
|
|
||||||
### Stanza subpackage
|
### Stanza subpackage
|
||||||
@ -108,15 +115,16 @@ func main() {
|
|||||||
Address: "localhost:5222",
|
Address: "localhost:5222",
|
||||||
},
|
},
|
||||||
Jid: "test@localhost",
|
Jid: "test@localhost",
|
||||||
Credential: xmpp.Password("Test"),
|
Credential: xmpp.Password("test"),
|
||||||
StreamLogger: os.Stdout,
|
StreamLogger: os.Stdout,
|
||||||
Insecure: true,
|
Insecure: true,
|
||||||
|
// TLSConfig: tls.Config{InsecureSkipVerify: true},
|
||||||
}
|
}
|
||||||
|
|
||||||
router := xmpp.NewRouter()
|
router := xmpp.NewRouter()
|
||||||
router.HandleFunc("message", handleMessage)
|
router.HandleFunc("message", handleMessage)
|
||||||
|
|
||||||
client, err := xmpp.NewClient(config, router)
|
client, err := xmpp.NewClient(config, router, errorHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("%+v", err)
|
log.Fatalf("%+v", err)
|
||||||
}
|
}
|
||||||
@ -138,6 +146,11 @@ func handleMessage(s xmpp.Sender, p stanza.Packet) {
|
|||||||
reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body}
|
reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body}
|
||||||
_ = s.Send(reply)
|
_ = s.Send(reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func errorHandler(err error) {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
## Reference documentation
|
## Reference documentation
|
||||||
|
@ -171,7 +171,7 @@ func handleDelegation(s xmpp.Sender, p stanza.Packet) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
pubsub, ok := forwardedIQ.Payload.(*stanza.PubSub)
|
pubsub, ok := forwardedIQ.Payload.(*stanza.PubSubGeneric)
|
||||||
if !ok {
|
if !ok {
|
||||||
// We only support pubsub delegation
|
// We only support pubsub delegation
|
||||||
return
|
return
|
||||||
@ -180,7 +180,7 @@ 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 := stanza.NewIQ(stanza.Attrs{Type: "result", From: forwardedIQ.To, To: forwardedIQ.From, Id: forwardedIQ.Id})
|
||||||
payload := stanza.PubSub{
|
payload := stanza.PubSubGeneric{
|
||||||
XMLName: xml.Name{
|
XMLName: xml.Name{
|
||||||
Space: "http://jabber.org/protocol/pubsub",
|
Space: "http://jabber.org/protocol/pubsub",
|
||||||
Local: "pubsub",
|
Local: "pubsub",
|
||||||
|
@ -136,7 +136,11 @@ func writeInput(g *gocui.Gui, v *gocui.View) error {
|
|||||||
input := strings.Join(v.ViewBufferLines(), "\n")
|
input := strings.Join(v.ViewBufferLines(), "\n")
|
||||||
|
|
||||||
fmt.Fprintln(chatLogWindow, "Me : ", input)
|
fmt.Fprintln(chatLogWindow, "Me : ", input)
|
||||||
|
if viewState.input == rawInputWindow {
|
||||||
|
rawTextChan <- input
|
||||||
|
} else {
|
||||||
textChan <- input
|
textChan <- input
|
||||||
|
}
|
||||||
|
|
||||||
v.Clear()
|
v.Clear()
|
||||||
v.EditDeleteToStartOfLine()
|
v.EditDeleteToStartOfLine()
|
||||||
|
@ -28,7 +28,7 @@ func main() {
|
|||||||
router := xmpp.NewRouter()
|
router := xmpp.NewRouter()
|
||||||
router.HandleFunc("message", handleMessage)
|
router.HandleFunc("message", handleMessage)
|
||||||
|
|
||||||
client, err := xmpp.NewClient(config, router)
|
client, err := xmpp.NewClient(config, router, errorHandler)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatalf("%+v", err)
|
log.Fatalf("%+v", err)
|
||||||
}
|
}
|
||||||
@ -50,3 +50,7 @@ func handleMessage(s xmpp.Sender, p stanza.Packet) {
|
|||||||
reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body}
|
reply := stanza.Message{Attrs: stanza.Attrs{To: msg.From}, Body: msg.Body}
|
||||||
_ = s.Send(reply)
|
_ = s.Send(reply)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func errorHandler(err error) {
|
||||||
|
fmt.Println(err.Error())
|
||||||
|
}
|
||||||
|
37
_examples/xmpp_jukebox/README.md
Normal file
37
_examples/xmpp_jukebox/README.md
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
# Jukebox example
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
- You need mpg123 installed on your computer because the example runs it as a command :
|
||||||
|
[Official MPG123 website](https://mpg123.de/)
|
||||||
|
Most linux distributions have a package for it.
|
||||||
|
- You need a soundcloud ID to play a music from the website through mpg123. You currently cannot play music files with this example.
|
||||||
|
Your user ID is available in your account settings on the [soundcloud website](https://soundcloud.com/)
|
||||||
|
**One is provided for convenience.**
|
||||||
|
- You need a running jabber server. You can run your local instance of [ejabberd](https://www.ejabberd.im/) for example.
|
||||||
|
- You need a registered user on the running jabber server.
|
||||||
|
|
||||||
|
## Run
|
||||||
|
You can edit the soundcloud ID in the example file with your own, or use the provided one :
|
||||||
|
```go
|
||||||
|
const scClientID = "dde6a0075614ac4f3bea423863076b22"
|
||||||
|
```
|
||||||
|
|
||||||
|
To run the example, build it with (while in the example directory) :
|
||||||
|
```
|
||||||
|
go build xmpp_jukebox.go
|
||||||
|
```
|
||||||
|
|
||||||
|
then run it with (update the command arguments accordingly):
|
||||||
|
```
|
||||||
|
./xmpp_jukebox -jid=MY_USERE@MY_DOMAIN/jukebox -password=MY_PASSWORD -address=MY_SERVER:MY_SERVER_PORT
|
||||||
|
```
|
||||||
|
Make sure to have a resource, for instance "/jukebox", on your jid.
|
||||||
|
|
||||||
|
Then you can send the following stanza to "MY_USERE@MY_DOMAIN/jukebox" (with the resource) to play a song (update the soundcloud URL accordingly) :
|
||||||
|
```xml
|
||||||
|
<iq id="1" to="MY_USERE@MY_DOMAIN/jukebox" type="set">
|
||||||
|
<set xml:lang="en" xmlns="urn:xmpp:iot:control">
|
||||||
|
<string name="url" value="https://soundcloud.com/UPDATE/ME"/>
|
||||||
|
</set>
|
||||||
|
</iq>
|
||||||
|
```
|
@ -3,6 +3,7 @@
|
|||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"encoding/xml"
|
||||||
"flag"
|
"flag"
|
||||||
"fmt"
|
"fmt"
|
||||||
"log"
|
"log"
|
||||||
@ -19,7 +20,7 @@ import (
|
|||||||
const scClientID = "dde6a0075614ac4f3bea423863076b22"
|
const scClientID = "dde6a0075614ac4f3bea423863076b22"
|
||||||
|
|
||||||
func main() {
|
func main() {
|
||||||
jid := flag.String("jid", "", "jukebok XMPP JID, resource is optional")
|
jid := flag.String("jid", "", "jukebok XMPP Jid, resource is optional")
|
||||||
password := flag.String("password", "", "XMPP account password")
|
password := flag.String("password", "", "XMPP account password")
|
||||||
address := flag.String("address", "", "If needed, XMPP server DNSName or IP and optional port (ie myserver:5222)")
|
address := flag.String("address", "", "If needed, XMPP server DNSName or IP and optional port (ie myserver:5222)")
|
||||||
flag.Parse()
|
flag.Parse()
|
||||||
@ -48,7 +49,7 @@ func main() {
|
|||||||
handleMessage(s, p, player)
|
handleMessage(s, p, player)
|
||||||
})
|
})
|
||||||
router.NewRoute().
|
router.NewRoute().
|
||||||
Packet("message").
|
Packet("iq").
|
||||||
HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
|
HandlerFunc(func(s xmpp.Sender, p stanza.Packet) {
|
||||||
handleIQ(s, p, player)
|
handleIQ(s, p, player)
|
||||||
})
|
})
|
||||||
@ -108,11 +109,29 @@ func handleIQ(s xmpp.Sender, p stanza.Packet, player *mpg123.Player) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func sendUserTune(s xmpp.Sender, artist string, title string) {
|
func sendUserTune(s xmpp.Sender, artist string, title string) {
|
||||||
tune := stanza.Tune{Artist: artist, Title: title}
|
rq, err := stanza.NewPublishItemRq("localhost",
|
||||||
iq := stanza.NewIQ(stanza.Attrs{Type: stanza.IQTypeSet, Id: "usertune-1", Lang: "en"})
|
"http://jabber.org/protocol/tune",
|
||||||
payload := stanza.PubSub{Publish: &stanza.Publish{Node: "http://jabber.org/protocol/tune", Item: stanza.Item{Tune: &tune}}}
|
"",
|
||||||
iq.Payload = &payload
|
stanza.Item{
|
||||||
_ = s.Send(iq)
|
XMLName: xml.Name{Space: "http://jabber.org/protocol/tune", Local: "tune"},
|
||||||
|
Any: &stanza.Node{
|
||||||
|
Nodes: []stanza.Node{
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Local: "artist"},
|
||||||
|
Content: artist,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Local: "title"},
|
||||||
|
Content: title,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
fmt.Printf("failed to build the publish request : %s", err.Error())
|
||||||
|
return
|
||||||
|
}
|
||||||
|
_ = s.Send(rq)
|
||||||
}
|
}
|
||||||
|
|
||||||
func playSCURL(p *mpg123.Player, rawURL string) {
|
func playSCURL(p *mpg123.Player, rawURL string) {
|
||||||
@ -120,7 +139,7 @@ func playSCURL(p *mpg123.Player, rawURL string) {
|
|||||||
// TODO: Maybe we need to check the track itself to get the stream URL from reply ?
|
// TODO: Maybe we need to check the track itself to get the stream URL from reply ?
|
||||||
url := soundcloud.FormatStreamURL(songID)
|
url := soundcloud.FormatStreamURL(songID)
|
||||||
|
|
||||||
_ = p.Play(url)
|
_ = p.Play(strings.ReplaceAll(url, "YOUR_SOUNDCLOUD_CLIENTID", scClientID))
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO
|
// TODO
|
||||||
|
11
client.go
11
client.go
@ -107,14 +107,14 @@ Setting up the client / Checking the parameters
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
// NewClient generates a new XMPP client, based on Config passed as parameters.
|
// NewClient generates a new XMPP client, based on Config passed as parameters.
|
||||||
// If host is not specified, the DNS SRV should be used to find the host from the domainpart of the JID.
|
// If host is not specified, the DNS SRV should be used to find the host from the domainpart of the Jid.
|
||||||
// Default the port to 5222.
|
// Default the port to 5222.
|
||||||
func NewClient(config Config, r *Router, errorHandler func(error)) (c *Client, err error) {
|
func NewClient(config Config, r *Router, errorHandler func(error)) (c *Client, err error) {
|
||||||
if config.KeepaliveInterval == 0 {
|
if config.KeepaliveInterval == 0 {
|
||||||
config.KeepaliveInterval = time.Second * 30
|
config.KeepaliveInterval = time.Second * 30
|
||||||
}
|
}
|
||||||
// Parse JID
|
// Parse Jid
|
||||||
if config.parsedJid, err = NewJid(config.Jid); err != nil {
|
if config.parsedJid, err = stanza.NewJid(config.Jid); err != nil {
|
||||||
err = errors.New("missing jid")
|
err = errors.New("missing jid")
|
||||||
return nil, NewConnError(err, true)
|
return nil, NewConnError(err, true)
|
||||||
}
|
}
|
||||||
@ -142,6 +142,11 @@ func NewClient(config Config, r *Router, errorHandler func(error)) (c *Client, e
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if config.Domain == "" {
|
||||||
|
// Fallback to jid domain
|
||||||
|
config.Domain = config.parsedJid.Domain
|
||||||
|
}
|
||||||
|
|
||||||
c = new(Client)
|
c = new(Client)
|
||||||
c.config = config
|
c.config = config
|
||||||
c.router = r
|
c.router = r
|
||||||
|
@ -2,6 +2,7 @@ package main
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"bufio"
|
"bufio"
|
||||||
|
"gosrc.io/xmpp/stanza"
|
||||||
"os"
|
"os"
|
||||||
"strings"
|
"strings"
|
||||||
"sync"
|
"sync"
|
||||||
@ -48,7 +49,7 @@ func sendxmpp(cmd *cobra.Command, args []string) {
|
|||||||
wg.Add(1)
|
wg.Add(1)
|
||||||
|
|
||||||
// FIXME: Remove global variables
|
// FIXME: Remove global variables
|
||||||
var mucsToLeave []*xmpp.Jid
|
var mucsToLeave []*stanza.Jid
|
||||||
|
|
||||||
cm := xmpp.NewStreamManager(client, func(c xmpp.Sender) {
|
cm := xmpp.NewStreamManager(client, func(c xmpp.Sender) {
|
||||||
defer wg.Done()
|
defer wg.Done()
|
||||||
@ -57,7 +58,7 @@ func sendxmpp(cmd *cobra.Command, args []string) {
|
|||||||
|
|
||||||
if isMUCRecipient {
|
if isMUCRecipient {
|
||||||
for _, muc := range receiver {
|
for _, muc := range receiver {
|
||||||
jid, err := xmpp.NewJid(muc)
|
jid, err := stanza.NewJid(muc)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithField("muc", muc).Errorf("skipping invalid muc jid: %w", err)
|
log.WithField("muc", muc).Errorf("skipping invalid muc jid: %w", err)
|
||||||
continue
|
continue
|
||||||
|
@ -7,7 +7,7 @@ import (
|
|||||||
"gosrc.io/xmpp/stanza"
|
"gosrc.io/xmpp/stanza"
|
||||||
)
|
)
|
||||||
|
|
||||||
func joinMUC(c xmpp.Sender, toJID *xmpp.Jid) error {
|
func joinMUC(c xmpp.Sender, toJID *stanza.Jid) error {
|
||||||
return c.Send(stanza.Presence{Attrs: stanza.Attrs{To: toJID.Full()},
|
return c.Send(stanza.Presence{Attrs: stanza.Attrs{To: toJID.Full()},
|
||||||
Extensions: []stanza.PresExtension{
|
Extensions: []stanza.PresExtension{
|
||||||
stanza.MucPresence{
|
stanza.MucPresence{
|
||||||
@ -16,7 +16,7 @@ func joinMUC(c xmpp.Sender, toJID *xmpp.Jid) error {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
func leaveMUCs(c xmpp.Sender, mucsToLeave []*xmpp.Jid) {
|
func leaveMUCs(c xmpp.Sender, mucsToLeave []*stanza.Jid) {
|
||||||
for _, muc := range mucsToLeave {
|
for _, muc := range mucsToLeave {
|
||||||
if err := c.Send(stanza.Presence{Attrs: stanza.Attrs{
|
if err := c.Send(stanza.Presence{Attrs: stanza.Attrs{
|
||||||
To: muc.Full(),
|
To: muc.Full(),
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
package xmpp
|
package xmpp
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"gosrc.io/xmpp/stanza"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
@ -11,7 +12,7 @@ type Config struct {
|
|||||||
TransportConfiguration
|
TransportConfiguration
|
||||||
|
|
||||||
Jid string
|
Jid string
|
||||||
parsedJid *Jid // For easier manipulation
|
parsedJid *stanza.Jid // For easier manipulation
|
||||||
Credential Credential
|
Credential Credential
|
||||||
StreamLogger *os.File // Used for debugging
|
StreamLogger *os.File // Used for debugging
|
||||||
Lang string // TODO: should default to 'en'
|
Lang string // TODO: should default to 'en'
|
||||||
|
136
stanza/commands.go
Normal file
136
stanza/commands.go
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
package stanza
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
|
// Implements the XEP-0050 extension
|
||||||
|
|
||||||
|
const (
|
||||||
|
CommandActionCancel = "cancel"
|
||||||
|
CommandActionComplete = "complete"
|
||||||
|
CommandActionExecute = "execute"
|
||||||
|
CommandActionNext = "next"
|
||||||
|
CommandActionPrevious = "prev"
|
||||||
|
|
||||||
|
CommandStatusCancelled = "canceled"
|
||||||
|
CommandStatusCompleted = "completed"
|
||||||
|
CommandStatusExecuting = "executing"
|
||||||
|
|
||||||
|
CommandNoteTypeErr = "error"
|
||||||
|
CommandNoteTypeInfo = "info"
|
||||||
|
CommandNoteTypeWarn = "warn"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Command struct {
|
||||||
|
XMLName xml.Name `xml:"http://jabber.org/protocol/commands command"`
|
||||||
|
|
||||||
|
CommandElement CommandElement `xml:",any"`
|
||||||
|
|
||||||
|
BadAction *struct{} `xml:"bad-action,omitempty"`
|
||||||
|
BadLocale *struct{} `xml:"bad-locale,omitempty"`
|
||||||
|
BadPayload *struct{} `xml:"bad-payload,omitempty"`
|
||||||
|
BadSessionId *struct{} `xml:"bad-sessionid,omitempty"`
|
||||||
|
MalformedAction *struct{} `xml:"malformed-action,omitempty"`
|
||||||
|
SessionExpired *struct{} `xml:"session-expired,omitempty"`
|
||||||
|
|
||||||
|
// Attributes
|
||||||
|
Action string `xml:"action,attr,omitempty"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
SessionId string `xml:"sessionid,attr,omitempty"`
|
||||||
|
Status string `xml:"status,attr,omitempty"`
|
||||||
|
Lang string `xml:"lang,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) Namespace() string {
|
||||||
|
return c.XMLName.Space
|
||||||
|
}
|
||||||
|
|
||||||
|
type CommandElement interface {
|
||||||
|
Ref() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type Actions struct {
|
||||||
|
Prev *struct{} `xml:"prev,omitempty"`
|
||||||
|
Next *struct{} `xml:"next,omitempty"`
|
||||||
|
Complete *struct{} `xml:"complete,omitempty"`
|
||||||
|
|
||||||
|
Execute string `xml:"execute,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *Actions) Ref() string {
|
||||||
|
return "actions"
|
||||||
|
}
|
||||||
|
|
||||||
|
type Note struct {
|
||||||
|
Text string `xml:",cdata"`
|
||||||
|
Type string `xml:"type,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Note) Ref() string {
|
||||||
|
return "note"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Node) Ref() string {
|
||||||
|
return "node"
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Command) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
c.XMLName = start.Name
|
||||||
|
|
||||||
|
// Extract packet attributes
|
||||||
|
for _, attr := range start.Attr {
|
||||||
|
if attr.Name.Local == "action" {
|
||||||
|
c.Action = attr.Value
|
||||||
|
}
|
||||||
|
if attr.Name.Local == "node" {
|
||||||
|
c.Node = attr.Value
|
||||||
|
}
|
||||||
|
if attr.Name.Local == "sessionid" {
|
||||||
|
c.SessionId = attr.Value
|
||||||
|
}
|
||||||
|
if attr.Name.Local == "status" {
|
||||||
|
c.Status = attr.Value
|
||||||
|
}
|
||||||
|
if attr.Name.Local == "lang" {
|
||||||
|
c.Lang = attr.Value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// decode inner elements
|
||||||
|
for {
|
||||||
|
t, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tt := t.(type) {
|
||||||
|
|
||||||
|
case xml.StartElement:
|
||||||
|
// Decode sub-elements
|
||||||
|
var err error
|
||||||
|
switch tt.Name.Local {
|
||||||
|
|
||||||
|
case "affiliations":
|
||||||
|
a := Actions{}
|
||||||
|
d.DecodeElement(&a, &tt)
|
||||||
|
c.CommandElement = &a
|
||||||
|
case "configure":
|
||||||
|
nt := Note{}
|
||||||
|
d.DecodeElement(&nt, &tt)
|
||||||
|
c.CommandElement = &nt
|
||||||
|
default:
|
||||||
|
n := Node{}
|
||||||
|
e := d.DecodeElement(&n, &tt)
|
||||||
|
_ = e
|
||||||
|
c.CommandElement = &n
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case xml.EndElement:
|
||||||
|
if tt == start.End() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
stanza/commands_test.go
Normal file
53
stanza/commands_test.go
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
package stanza_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"gosrc.io/xmpp/stanza"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshalCommands(t *testing.T) {
|
||||||
|
input := "<command xmlns=\"http://jabber.org/protocol/commands\" node=\"list\" sessionid=\"list:20020923T213616Z-700\" status=\"completed\"><x " +
|
||||||
|
"xmlns=\"jabber:x:data\" type=\"result\"><title xmlns=\"jabber:x:data\">Available Servi" +
|
||||||
|
"ces</title><reported xmlns=\"jabber:x:data\"><field xmlns=\"jabber:x:data\" label=\"S" +
|
||||||
|
"ervice\" var=\"service\"></field><field xmlns=\"jabber:x:data\" label=\"Single-User mo" +
|
||||||
|
"de\" var=\"runlevel-1\"></field><field xmlns=\"jabber:x:data\" label=\"Non-Networked M" +
|
||||||
|
"ulti-User mode\" var=\"runlevel-2\"></field><field xmlns=\"jabber:x:data\" label=\"Ful" +
|
||||||
|
"l Multi-User mode\" var=\"runlevel-3\"></field><field xmlns=\"jabber:x:data\" label=\"" +
|
||||||
|
"X-Window mode\" var=\"runlevel-5\"></field></reported><item xmlns=\"jabber:x:data\"><" +
|
||||||
|
"field xmlns=\"jabber:x:data\" var=\"service\"><value xmlns=\"jabber:x:data\">httpd</va" +
|
||||||
|
"lue></field><field xmlns=\"jabber:x:data\" var=\"runlevel-1\"><value xmlns=\"jabber:x" +
|
||||||
|
":data\">off</value></field><field xmlns=\"jabber:x:data\" var=\"runlevel-2\"><value x" +
|
||||||
|
"mlns=\"jabber:x:data\">off</value></field><field xmlns=\"jabber:x:data\" var=\"runlev" +
|
||||||
|
"el-3\"><value xmlns=\"jabber:x:data\">on</value></field><field xmlns=\"jabber:x:data" +
|
||||||
|
"\" var=\"runlevel-5\"><value xmlns=\"jabber:x:data\">on</value></field></item><item x" +
|
||||||
|
"mlns=\"jabber:x:data\"><field xmlns=\"jabber:x:data\" var=\"service\"><value xmlns=\"ja" +
|
||||||
|
"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
|
||||||
|
err := xml.Unmarshal([]byte(input), &c)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to unmarshal initial input")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(c)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal unmarshalled input")
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := compareMarshal(input, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
@ -67,7 +67,7 @@ func TestParsingDelegationIQ(t *testing.T) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
if forwardedIQ.Payload != nil {
|
if forwardedIQ.Payload != nil {
|
||||||
if pubsub, ok := forwardedIQ.Payload.(*PubSub); ok {
|
if pubsub, ok := forwardedIQ.Payload.(*PubSubGeneric); ok {
|
||||||
node = pubsub.Publish.Node
|
node = pubsub.Publish.Node
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,6 +3,7 @@ package stanza
|
|||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
"strconv"
|
"strconv"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@ -53,11 +54,20 @@ func (x *Err) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
textName := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "text"}
|
textName := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "text"}
|
||||||
if elt.XMLName == textName {
|
// TODO : change the pubsub handling ? It kind of dilutes the information
|
||||||
|
// Handles : 6.1.3.11 Node Has Moved for XEP-0060 (PubSubGeneric)
|
||||||
|
goneName := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "gone"}
|
||||||
|
if elt.XMLName == textName || // Regular error text
|
||||||
|
elt.XMLName == goneName { // Gone text for pubsub
|
||||||
x.Text = elt.Content
|
x.Text = elt.Content
|
||||||
} else if elt.XMLName.Space == "urn:ietf:params:xml:ns:xmpp-stanzas" {
|
} else if elt.XMLName.Space == "urn:ietf:params:xml:ns:xmpp-stanzas" ||
|
||||||
|
elt.XMLName.Space == "http://jabber.org/protocol/pubsub#errors" {
|
||||||
|
if strings.TrimSpace(x.Reason) != "" {
|
||||||
|
x.Reason = strings.Join([]string{elt.XMLName.Local}, ":")
|
||||||
|
} else {
|
||||||
x.Reason = elt.XMLName.Local
|
x.Reason = elt.XMLName.Local
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
case xml.EndElement:
|
case xml.EndElement:
|
||||||
if tt == start.End() {
|
if tt == start.End() {
|
||||||
|
67
stanza/form.go
Normal file
67
stanza/form.go
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
package stanza
|
||||||
|
|
||||||
|
import "encoding/xml"
|
||||||
|
|
||||||
|
type FormType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FormTypeCancel = "cancel"
|
||||||
|
FormTypeForm = "form"
|
||||||
|
FormTypeResult = "result"
|
||||||
|
FormTypeSubmit = "submit"
|
||||||
|
)
|
||||||
|
|
||||||
|
// See XEP-0004 and XEP-0068
|
||||||
|
// Pointer semantics
|
||||||
|
type Form struct {
|
||||||
|
XMLName xml.Name `xml:"jabber:x:data x"`
|
||||||
|
Instructions []string `xml:"instructions"`
|
||||||
|
Title string `xml:"title,omitempty"`
|
||||||
|
Fields []Field `xml:"field,omitempty"`
|
||||||
|
Reported *FormItem `xml:"reported"`
|
||||||
|
Items []FormItem
|
||||||
|
Type string `xml:"type,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type FormItem struct {
|
||||||
|
Fields []Field
|
||||||
|
}
|
||||||
|
|
||||||
|
type Field struct {
|
||||||
|
XMLName xml.Name `xml:"field"`
|
||||||
|
Description string `xml:"desc,omitempty"`
|
||||||
|
Required *string `xml:"required"`
|
||||||
|
ValuesList []string `xml:"value"`
|
||||||
|
Options []Option `xml:"option,omitempty"`
|
||||||
|
Var string `xml:"var,attr,omitempty"`
|
||||||
|
Type string `xml:"type,attr,omitempty"`
|
||||||
|
Label string `xml:"label,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewForm(fields []Field, formType string) *Form {
|
||||||
|
return &Form{
|
||||||
|
Type: formType,
|
||||||
|
Fields: fields,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type FieldType string
|
||||||
|
|
||||||
|
const (
|
||||||
|
FieldTypeBool = "boolean"
|
||||||
|
FieldTypeFixed = "fixed"
|
||||||
|
FieldTypeHidden = "hidden"
|
||||||
|
FieldTypeJidMulti = "jid-multi"
|
||||||
|
FieldTypeJidSingle = "jid-single"
|
||||||
|
FieldTypeListMulti = "list-multi"
|
||||||
|
FieldTypeListSingle = "list-single"
|
||||||
|
FieldTypeTextMulti = "text-multi"
|
||||||
|
FieldTypeTextPrivate = "text-private"
|
||||||
|
FieldTypeTextSingle = "text-Single"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Option struct {
|
||||||
|
XMLName xml.Name `xml:"option"`
|
||||||
|
Label string `xml:"label,attr,omitempty"`
|
||||||
|
ValuesList []string `xml:"value"`
|
||||||
|
}
|
107
stanza/form_test.go
Normal file
107
stanza/form_test.go
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
package stanza
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
formSubmit = "<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\">" +
|
||||||
|
"<configure node=\"princely_musings\">" +
|
||||||
|
"<x xmlns=\"jabber:x:data\" type=\"submit\">" +
|
||||||
|
"<field var=\"FORM_TYPE\" type=\"hidden\">" +
|
||||||
|
"<value>http://jabber.org/protocol/pubsub#node_config</value>" +
|
||||||
|
"</field>" +
|
||||||
|
"<field var=\"pubsub#title\">" +
|
||||||
|
"<value>Princely Musings (Atom)</value>" +
|
||||||
|
"</field>" +
|
||||||
|
"<field var=\"pubsub#deliver_notifications\">" +
|
||||||
|
"<value>1</value>" +
|
||||||
|
"</field>" +
|
||||||
|
"<field var=\"pubsub#access_model\">" +
|
||||||
|
"<value>roster</value>" +
|
||||||
|
"</field>" +
|
||||||
|
"<field var=\"pubsub#roster_groups_allowed\">" +
|
||||||
|
"<value>friends</value>" +
|
||||||
|
"<value>servants</value>" +
|
||||||
|
"<value>courtiers</value>" +
|
||||||
|
"</field>" +
|
||||||
|
"<field var=\"pubsub#type\">" +
|
||||||
|
"<value>http://www.w3.org/2005/Atom</value>" +
|
||||||
|
"</field>" +
|
||||||
|
"<field var=\"pubsub#notification_type\" type=\"list-single\"" +
|
||||||
|
"label=\"Specify the delivery style for event notifications\">" +
|
||||||
|
"<value>headline</value>" +
|
||||||
|
"<option>" +
|
||||||
|
"<value>normal</value>" +
|
||||||
|
"</option>" +
|
||||||
|
"<option>" +
|
||||||
|
"<value>headline</value>" +
|
||||||
|
"</option>" +
|
||||||
|
"</field>" +
|
||||||
|
"</x>" +
|
||||||
|
"</configure>" +
|
||||||
|
"</pubsub>"
|
||||||
|
|
||||||
|
clientJid = "hamlet@denmark.lit/elsinore"
|
||||||
|
serviceJid = "pubsub.shakespeare.lit"
|
||||||
|
iqId = "config1"
|
||||||
|
serviceNode = "princely_musings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestMarshalFormSubmit(t *testing.T) {
|
||||||
|
formIQ := NewIQ(Attrs{From: clientJid, To: serviceJid, Id: iqId, Type: IQTypeSet})
|
||||||
|
formIQ.Payload = &PubSubOwner{
|
||||||
|
OwnerUseCase: &ConfigureOwner{
|
||||||
|
Node: serviceNode,
|
||||||
|
Form: &Form{
|
||||||
|
Type: FormTypeSubmit,
|
||||||
|
Fields: []Field{
|
||||||
|
{Var: "FORM_TYPE", Type: FieldTypeHidden, ValuesList: []string{"http://jabber.org/protocol/pubsub#node_config"}},
|
||||||
|
{Var: "pubsub#title", ValuesList: []string{"Princely Musings (Atom)"}},
|
||||||
|
{Var: "pubsub#deliver_notifications", ValuesList: []string{"1"}},
|
||||||
|
{Var: "pubsub#access_model", ValuesList: []string{"roster"}},
|
||||||
|
{Var: "pubsub#roster_groups_allowed", ValuesList: []string{"friends", "servants", "courtiers"}},
|
||||||
|
{Var: "pubsub#type", ValuesList: []string{"http://www.w3.org/2005/Atom"}},
|
||||||
|
{
|
||||||
|
Var: "pubsub#notification_type",
|
||||||
|
Type: "list-single",
|
||||||
|
Label: "Specify the delivery style for event notifications",
|
||||||
|
ValuesList: []string{"headline"},
|
||||||
|
Options: []Option{
|
||||||
|
{ValuesList: []string{"normal"}},
|
||||||
|
{ValuesList: []string{"headline"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
b, err := xml.Marshal(formIQ.Payload)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not marshal formIQ : %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ReplaceAll(string(b), " ", "") != strings.ReplaceAll(formSubmit, " ", "") {
|
||||||
|
t.Fatalf("Expected formIQ and marshalled one are different.\nExepected : %s\nMarshalled : %s", formSubmit, string(b))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUnmarshalFormSubmit(t *testing.T) {
|
||||||
|
var f PubSubOwner
|
||||||
|
mErr := xml.Unmarshal([]byte(formSubmit), &f)
|
||||||
|
if mErr != nil {
|
||||||
|
t.Fatalf("failed to unmarshal formSubmit ! %s", mErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(&f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("failed to marshal formSubmit")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.ReplaceAll(string(data), " ", "") != strings.ReplaceAll(formSubmit, " ", "") {
|
||||||
|
t.Fatalf("failed unmarshal/marshal for formSubmit : %s\n%s", string(data), formSubmit)
|
||||||
|
}
|
||||||
|
}
|
@ -73,11 +73,11 @@ func TestDiscoItems_Builder(t *testing.T) {
|
|||||||
{xml.Name{}, "catalog.shakespeare.lit", "clothing", "Wear your literary taste with pride"},
|
{xml.Name{}, "catalog.shakespeare.lit", "clothing", "Wear your literary taste with pride"},
|
||||||
{xml.Name{}, "catalog.shakespeare.lit", "music", "Music from the time of Shakespeare"}}
|
{xml.Name{}, "catalog.shakespeare.lit", "music", "Music from the time of Shakespeare"}}
|
||||||
if len(pp.Items) != len(items) {
|
if len(pp.Items) != len(items) {
|
||||||
t.Errorf("Items length mismatch: %#v", pp.Items)
|
t.Errorf("List length mismatch: %#v", pp.Items)
|
||||||
} else {
|
} else {
|
||||||
for i, item := range pp.Items {
|
for i, item := range pp.Items {
|
||||||
if item.JID != items[i].JID {
|
if item.JID != items[i].JID {
|
||||||
t.Errorf("JID Mismatch (expected: %s): %s", items[i].JID, item.JID)
|
t.Errorf("Jid Mismatch (expected: %s): %s", items[i].JID, item.JID)
|
||||||
}
|
}
|
||||||
if item.Node != items[i].Node {
|
if item.Node != items[i].Node {
|
||||||
t.Errorf("Node Mismatch (expected: %s): %s", items[i].JID, item.JID)
|
t.Errorf("Node Mismatch (expected: %s): %s", items[i].JID, item.JID)
|
||||||
|
@ -69,11 +69,11 @@ func TestRosterBuilder(t *testing.T) {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
if len(pp.Items) != len(items) {
|
if len(pp.Items) != len(items) {
|
||||||
t.Errorf("Items length mismatch: %#v", pp.Items)
|
t.Errorf("List length mismatch: %#v", pp.Items)
|
||||||
} else {
|
} else {
|
||||||
for i, item := range pp.Items {
|
for i, item := range pp.Items {
|
||||||
if item.Jid != items[i].Jid {
|
if item.Jid != items[i].Jid {
|
||||||
t.Errorf("JID Mismatch (expected: %s): %s", items[i].Jid, item.Jid)
|
t.Errorf("Jid Mismatch (expected: %s): %s", items[i].Jid, item.Jid)
|
||||||
}
|
}
|
||||||
if !reflect.DeepEqual(item.Groups, items[i].Groups) {
|
if !reflect.DeepEqual(item.Groups, items[i].Groups) {
|
||||||
t.Errorf("Node Mismatch (expected: %s): %s", items[i].Jid, item.Jid)
|
t.Errorf("Node Mismatch (expected: %s): %s", items[i].Jid, item.Jid)
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
package xmpp
|
package stanza
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
@ -20,9 +20,9 @@ func NewJid(sjid string) (*Jid, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
s1 := strings.SplitN(sjid, "@", 2)
|
s1 := strings.SplitN(sjid, "@", 2)
|
||||||
if len(s1) == 1 { // This is a server or component JID
|
if len(s1) == 1 { // This is a server or component Jid
|
||||||
jid.Domain = s1[0]
|
jid.Domain = s1[0]
|
||||||
} else { // JID has a local username part
|
} else { // Jid has a local username part
|
||||||
if s1[0] == "" {
|
if s1[0] == "" {
|
||||||
return jid, fmt.Errorf("invalid jid '%s", sjid)
|
return jid, fmt.Errorf("invalid jid '%s", sjid)
|
||||||
}
|
}
|
||||||
@ -41,10 +41,10 @@ func NewJid(sjid string) (*Jid, error) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !isUsernameValid(jid.Node) {
|
if !isUsernameValid(jid.Node) {
|
||||||
return jid, fmt.Errorf("invalid Node in JID '%s'", sjid)
|
return jid, fmt.Errorf("invalid Node in Jid '%s'", sjid)
|
||||||
}
|
}
|
||||||
if !isDomainValid(jid.Domain) {
|
if !isDomainValid(jid.Domain) {
|
||||||
return jid, fmt.Errorf("invalid domain in JID '%s'", sjid)
|
return jid, fmt.Errorf("invalid domain in Jid '%s'", sjid)
|
||||||
}
|
}
|
||||||
|
|
||||||
return jid, nil
|
return jid, nil
|
@ -1,4 +1,4 @@
|
|||||||
package xmpp
|
package stanza
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"testing"
|
"testing"
|
214
stanza/msg_pubsub_event.go
Normal file
214
stanza/msg_pubsub_event.go
Normal file
@ -0,0 +1,214 @@
|
|||||||
|
package stanza
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Implementation of the http://jabber.org/protocol/pubsub#event namespace
|
||||||
|
type PubSubEvent struct {
|
||||||
|
XMLName xml.Name `xml:"http://jabber.org/protocol/pubsub#event event"`
|
||||||
|
MsgExtension
|
||||||
|
EventElement EventElement
|
||||||
|
//List ItemsEvent
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
TypeRegistry.MapExtension(PKTMessage, xml.Name{Space: "http://jabber.org/protocol/pubsub#event", Local: "event"}, PubSubEvent{})
|
||||||
|
}
|
||||||
|
|
||||||
|
type EventElement interface {
|
||||||
|
Name() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********************
|
||||||
|
// Collection
|
||||||
|
// *********************
|
||||||
|
|
||||||
|
const PubSubCollectionEventName = "Collection"
|
||||||
|
|
||||||
|
type CollectionEvent struct {
|
||||||
|
AssocDisassoc AssocDisassoc
|
||||||
|
Node string `xml:"node,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c CollectionEvent) Name() string {
|
||||||
|
return PubSubCollectionEventName
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********************
|
||||||
|
// Associate/Disassociate
|
||||||
|
// *********************
|
||||||
|
type AssocDisassoc interface {
|
||||||
|
GetAssocDisassoc() string
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********************
|
||||||
|
// Associate
|
||||||
|
// *********************
|
||||||
|
const Assoc = "Associate"
|
||||||
|
|
||||||
|
type AssociateEvent struct {
|
||||||
|
XMLName xml.Name `xml:"associate"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (a *AssociateEvent) GetAssocDisassoc() string {
|
||||||
|
return Assoc
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********************
|
||||||
|
// Disassociate
|
||||||
|
// *********************
|
||||||
|
const Disassoc = "Disassociate"
|
||||||
|
|
||||||
|
type DisassociateEvent struct {
|
||||||
|
XMLName xml.Name `xml:"disassociate"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e *DisassociateEvent) GetAssocDisassoc() string {
|
||||||
|
return Disassoc
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********************
|
||||||
|
// Configuration
|
||||||
|
// *********************
|
||||||
|
|
||||||
|
const PubSubConfigEventName = "Configuration"
|
||||||
|
|
||||||
|
type ConfigurationEvent struct {
|
||||||
|
Node string `xml:"node,attr,omitempty"`
|
||||||
|
Form *Form
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c ConfigurationEvent) Name() string {
|
||||||
|
return PubSubConfigEventName
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********************
|
||||||
|
// Delete
|
||||||
|
// *********************
|
||||||
|
const PubSubDeleteEventName = "Delete"
|
||||||
|
|
||||||
|
type DeleteEvent struct {
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
Redirect *RedirectEvent `xml:"redirect"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c DeleteEvent) Name() string {
|
||||||
|
return PubSubConfigEventName
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********************
|
||||||
|
// Redirect
|
||||||
|
// *********************
|
||||||
|
type RedirectEvent struct {
|
||||||
|
URI string `xml:"uri,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********************
|
||||||
|
// List
|
||||||
|
// *********************
|
||||||
|
|
||||||
|
const PubSubItemsEventName = "List"
|
||||||
|
|
||||||
|
type ItemsEvent struct {
|
||||||
|
XMLName xml.Name `xml:"items"`
|
||||||
|
Items []ItemEvent `xml:"item,omitempty"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
Retract *RetractEvent `xml:"retract"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type ItemEvent struct {
|
||||||
|
XMLName xml.Name `xml:"item"`
|
||||||
|
Id string `xml:"id,attr,omitempty"`
|
||||||
|
Publisher string `xml:"publisher,attr,omitempty"`
|
||||||
|
Any *Node `xml:",any"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (i ItemsEvent) Name() string {
|
||||||
|
return PubSubItemsEventName
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********************
|
||||||
|
// List
|
||||||
|
// *********************
|
||||||
|
|
||||||
|
type RetractEvent struct {
|
||||||
|
XMLName xml.Name `xml:"retract"`
|
||||||
|
ID string `xml:"node,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********************
|
||||||
|
// Purge
|
||||||
|
// *********************
|
||||||
|
const PubSubPurgeEventName = "Purge"
|
||||||
|
|
||||||
|
type PurgeEvent struct {
|
||||||
|
XMLName xml.Name `xml:"purge"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p PurgeEvent) Name() string {
|
||||||
|
return PubSubPurgeEventName
|
||||||
|
}
|
||||||
|
|
||||||
|
// *********************
|
||||||
|
// Subscription
|
||||||
|
// *********************
|
||||||
|
const PubSubSubscriptionEventName = "Subscription"
|
||||||
|
|
||||||
|
type SubscriptionEvent struct {
|
||||||
|
SubStatus string `xml:"subscription,attr,omitempty"`
|
||||||
|
Expiry string `xml:"expiry,attr,omitempty"`
|
||||||
|
SubInfo `xml:",omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (s SubscriptionEvent) Name() string {
|
||||||
|
return PubSubSubscriptionEventName
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pse *PubSubEvent) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
pse.XMLName = start.Name
|
||||||
|
// decode inner elements
|
||||||
|
for {
|
||||||
|
t, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var ee EventElement
|
||||||
|
switch tt := t.(type) {
|
||||||
|
case xml.StartElement:
|
||||||
|
switch tt.Name.Local {
|
||||||
|
case "collection":
|
||||||
|
ee = &CollectionEvent{}
|
||||||
|
case "configuration":
|
||||||
|
ee = &ConfigurationEvent{}
|
||||||
|
case "delete":
|
||||||
|
ee = &DeleteEvent{}
|
||||||
|
case "items":
|
||||||
|
ee = &ItemsEvent{}
|
||||||
|
case "purge":
|
||||||
|
ee = &PurgeEvent{}
|
||||||
|
case "subscription":
|
||||||
|
ee = &SubscriptionEvent{}
|
||||||
|
default:
|
||||||
|
ee = nil
|
||||||
|
}
|
||||||
|
// known child element found, decode it
|
||||||
|
if ee != nil {
|
||||||
|
err = d.DecodeElement(ee, &tt)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
pse.EventElement = ee
|
||||||
|
}
|
||||||
|
case xml.EndElement:
|
||||||
|
if tt == start.End() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
162
stanza/msg_pubsub_event_test.go
Normal file
162
stanza/msg_pubsub_event_test.go
Normal file
@ -0,0 +1,162 @@
|
|||||||
|
package stanza_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"gosrc.io/xmpp/stanza"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestDecodeMsgEvent(t *testing.T) {
|
||||||
|
str := `<message from='pubsub.shakespeare.lit' to='francisco@denmark.lit' id='foo'>
|
||||||
|
<event xmlns='http://jabber.org/protocol/pubsub#event'>
|
||||||
|
<items node='princely_musings'>
|
||||||
|
<item id='ae890ac52d0df67ed7cfdf51b644e901'>
|
||||||
|
<entry xmlns='http://www.w3.org/2005/Atom'>
|
||||||
|
<title>Soliloquy</title>
|
||||||
|
<summary>
|
||||||
|
To be, or not to be: that is the question:
|
||||||
|
Whether 'tis nobler in the mind to suffer
|
||||||
|
The slings and arrows of outrageous fortune,
|
||||||
|
Or to take arms against a sea of troubles,
|
||||||
|
And by opposing end them?
|
||||||
|
</summary>
|
||||||
|
<link rel='alternate' type='text/html'
|
||||||
|
href='http://denmark.lit/2003/12/13/atom03'/>
|
||||||
|
<id>tag:denmark.lit,2003:entry-32397</id>
|
||||||
|
<published>2003-12-13T18:30:02Z</published>
|
||||||
|
<updated>2003-12-13T18:30:02Z</updated>
|
||||||
|
</entry>
|
||||||
|
</item>
|
||||||
|
</items>
|
||||||
|
</event>
|
||||||
|
</message>
|
||||||
|
`
|
||||||
|
parsedMessage := stanza.Message{}
|
||||||
|
if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil {
|
||||||
|
t.Errorf("message receipt unmarshall error: %v", err)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
if parsedMessage.Body != "" {
|
||||||
|
t.Errorf("Unexpected body: '%s'", parsedMessage.Body)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(parsedMessage.Extensions) < 1 {
|
||||||
|
t.Errorf("no extension found on parsed message")
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
switch ext := parsedMessage.Extensions[0].(type) {
|
||||||
|
case *stanza.PubSubEvent:
|
||||||
|
if ext.XMLName.Local != "event" {
|
||||||
|
t.Fatalf("unexpected extension: %s:%s", ext.XMLName.Space, ext.XMLName.Local)
|
||||||
|
}
|
||||||
|
tmp, ok := parsedMessage.Extensions[0].(*stanza.PubSubEvent)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unexpected extension element: %s:%s", ext.XMLName.Space, ext.XMLName.Local)
|
||||||
|
}
|
||||||
|
ie, ok := tmp.EventElement.(*stanza.ItemsEvent)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("unexpected extension element: %s:%s", ext.XMLName.Space, ext.XMLName.Local)
|
||||||
|
}
|
||||||
|
if ie.Items[0].Any.Nodes[0].Content != "Soliloquy" {
|
||||||
|
t.Fatalf("could not read title ! Read this : %s", ie.Items[0].Any.Nodes[0].Content)
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ie.Items[0].Any.Nodes) != 6 {
|
||||||
|
t.Fatalf("some nodes were not correctly parsed")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
t.Fatalf("could not find pubsub event extension")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestEncodeEvent(t *testing.T) {
|
||||||
|
expected := "<message><event xmlns=\"http://jabber.org/protocol/pubsub#event\">" +
|
||||||
|
"<items node=\"princely_musings\"><item id=\"ae890ac52d0df67ed7cfdf51b644e901\">" +
|
||||||
|
"<entry xmlns=\"http://www.w3.org/2005/Atom\"><title>My pub item title</title>" +
|
||||||
|
"<summary>My pub item content summary</summary><link rel=\"alternate\" " +
|
||||||
|
"type=\"text/html\" href=\"http://denmark.lit/2003/12/13/atom03\">" +
|
||||||
|
"</link><id>My pub item content ID</id><published>2003-12-13T18:30:02Z</published>" +
|
||||||
|
"<updated>2003-12-13T18:30:02Z</updated></entry></item></items></event></message>"
|
||||||
|
message := stanza.Message{
|
||||||
|
Extensions: []stanza.MsgExtension{
|
||||||
|
stanza.PubSubEvent{
|
||||||
|
EventElement: stanza.ItemsEvent{
|
||||||
|
Items: []stanza.ItemEvent{
|
||||||
|
{
|
||||||
|
Id: "ae890ac52d0df67ed7cfdf51b644e901",
|
||||||
|
Any: &stanza.Node{
|
||||||
|
XMLName: xml.Name{
|
||||||
|
Space: "http://www.w3.org/2005/Atom",
|
||||||
|
Local: "entry",
|
||||||
|
},
|
||||||
|
Attrs: nil,
|
||||||
|
Content: "",
|
||||||
|
Nodes: []stanza.Node{
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Space: "", Local: "title"},
|
||||||
|
Attrs: nil,
|
||||||
|
Content: "My pub item title",
|
||||||
|
Nodes: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Space: "", Local: "summary"},
|
||||||
|
Attrs: nil,
|
||||||
|
Content: "My pub item content summary",
|
||||||
|
Nodes: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Space: "", Local: "link"},
|
||||||
|
Attrs: []xml.Attr{
|
||||||
|
{
|
||||||
|
Name: xml.Name{Space: "", Local: "rel"},
|
||||||
|
Value: "alternate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: xml.Name{Space: "", Local: "type"},
|
||||||
|
Value: "text/html",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: xml.Name{Space: "", Local: "href"},
|
||||||
|
Value: "http://denmark.lit/2003/12/13/atom03",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Space: "", Local: "id"},
|
||||||
|
Attrs: nil,
|
||||||
|
Content: "My pub item content ID",
|
||||||
|
Nodes: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Space: "", Local: "published"},
|
||||||
|
Attrs: nil,
|
||||||
|
Content: "2003-12-13T18:30:02Z",
|
||||||
|
Nodes: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Space: "", Local: "updated"},
|
||||||
|
Attrs: nil,
|
||||||
|
Content: "2003-12-13T18:30:02Z",
|
||||||
|
Nodes: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Node: "princely_musings",
|
||||||
|
Retract: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
data, _ := xml.Marshal(message)
|
||||||
|
if strings.TrimSpace(string(data)) != strings.TrimSpace(expected) {
|
||||||
|
t.Errorf("event was not encoded properly : \nexpected:%s \ngot: %s", expected, string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -15,7 +15,7 @@ type Tune struct {
|
|||||||
Uri string `xml:"uri,omitempty"`
|
Uri string `xml:"uri,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mood defines deta model for XEP-0107 - User Mood
|
// Mood defines data model for XEP-0107 - User Mood
|
||||||
// See: https://xmpp.org/extensions/xep-0107.html
|
// See: https://xmpp.org/extensions/xep-0107.html
|
||||||
type Mood struct {
|
type Mood struct {
|
||||||
MsgExtension // Mood can be added as a message extension
|
MsgExtension // Mood can be added as a message extension
|
||||||
|
366
stanza/pubsub.go
366
stanza/pubsub.go
@ -2,39 +2,383 @@ package stanza
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
type PubSub struct {
|
type PubSubGeneric struct {
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/pubsub pubsub"`
|
XMLName xml.Name `xml:"http://jabber.org/protocol/pubsub pubsub"`
|
||||||
Publish *Publish
|
|
||||||
Retract *Retract
|
Create *Create `xml:"create,omitempty"`
|
||||||
// TODO <configure/>
|
Configure *Configure `xml:"configure,omitempty"`
|
||||||
|
|
||||||
|
Subscribe *SubInfo `xml:"subscribe,omitempty"`
|
||||||
|
SubOptions *SubOptions `xml:"options,omitempty"`
|
||||||
|
|
||||||
|
Publish *Publish `xml:"publish,omitempty"`
|
||||||
|
PublishOptions *PublishOptions `xml:"publish-options"`
|
||||||
|
|
||||||
|
Affiliations *Affiliations `xml:"affiliations,omitempty"`
|
||||||
|
Default *Default `xml:"default,omitempty"`
|
||||||
|
|
||||||
|
Items *Items `xml:"items,omitempty"`
|
||||||
|
Retract *Retract `xml:"retract,omitempty"`
|
||||||
|
Subscription *Subscription `xml:"subscription,omitempty"`
|
||||||
|
|
||||||
|
Subscriptions *Subscriptions `xml:"subscriptions,omitempty"`
|
||||||
|
// To use in responses to sub/unsub for instance
|
||||||
|
// Subscription options
|
||||||
|
Unsubscribe *SubInfo `xml:"unsubscribe,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (p *PubSub) Namespace() string {
|
func (p *PubSubGeneric) Namespace() string {
|
||||||
return p.XMLName.Space
|
return p.XMLName.Space
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type Affiliations struct {
|
||||||
|
List []Affiliation `xml:"affiliation"`
|
||||||
|
Node string `xml:"node,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Affiliation struct {
|
||||||
|
AffiliationStatus string `xml:"affiliation"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Create struct {
|
||||||
|
Node string `xml:"node,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubOptions struct {
|
||||||
|
SubInfo
|
||||||
|
Form *Form `xml:"x"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Configure struct {
|
||||||
|
Form *Form `xml:"x"`
|
||||||
|
}
|
||||||
|
type Default struct {
|
||||||
|
Node string `xml:"node,attr,omitempty"`
|
||||||
|
Type string `xml:"type,attr,omitempty"`
|
||||||
|
Form *Form `xml:"x"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type Subscribe struct {
|
||||||
|
XMLName xml.Name `xml:"subscribe"`
|
||||||
|
SubInfo
|
||||||
|
}
|
||||||
|
type Unsubscribe struct {
|
||||||
|
XMLName xml.Name `xml:"unsubscribe"`
|
||||||
|
SubInfo
|
||||||
|
}
|
||||||
|
|
||||||
|
// SubInfo represents information about a subscription
|
||||||
|
// Node is the node related to the subscription
|
||||||
|
// Jid is the subscription JID of the subscribed entity
|
||||||
|
// SubID is the subscription ID
|
||||||
|
type SubInfo struct {
|
||||||
|
Node string `xml:"node,attr,omitempty"`
|
||||||
|
Jid string `xml:"jid,attr,omitempty"`
|
||||||
|
// Sub ID is optional
|
||||||
|
SubId *string `xml:"subid,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// validate checks if a node and a jid are present in the sub info, and if this jid is valid.
|
||||||
|
func (si *SubInfo) validate() error {
|
||||||
|
// Requests MUST contain a valid JID
|
||||||
|
if _, err := NewJid(si.Jid); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
// SubInfo must contain both a valid JID and a node. See XEP-0060
|
||||||
|
if strings.TrimSpace(si.Node) == "" {
|
||||||
|
return errors.New("SubInfo must contain the node AND the subscriber JID in subscription config options requests")
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the "5.6 Retrieve Subscriptions" of XEP-0060
|
||||||
|
type Subscriptions struct {
|
||||||
|
XMLName xml.Name `xml:"subscriptions"`
|
||||||
|
List []Subscription `xml:"subscription,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handles the "5.6 Retrieve Subscriptions" and the 6.1 Subscribe to a Node and so on of XEP-0060
|
||||||
|
type Subscription struct {
|
||||||
|
SubStatus string `xml:"subscription,attr,omitempty"`
|
||||||
|
SubInfo `xml:",omitempty"`
|
||||||
|
// Seems like we can't marshal a self-closing tag for now : https://github.com/golang/go/issues/21399
|
||||||
|
// subscribe-options should be like this as per XEP-0060:
|
||||||
|
// <subscribe-options>
|
||||||
|
// <required/>
|
||||||
|
// </subscribe-options>
|
||||||
|
// Used to indicate if configuration options is required.
|
||||||
|
Required *struct{}
|
||||||
|
}
|
||||||
|
|
||||||
|
type PublishOptions struct {
|
||||||
|
XMLName xml.Name `xml:"publish-options"`
|
||||||
|
Form *Form
|
||||||
|
}
|
||||||
|
|
||||||
type Publish struct {
|
type Publish struct {
|
||||||
XMLName xml.Name `xml:"publish"`
|
XMLName xml.Name `xml:"publish"`
|
||||||
Node string `xml:"node,attr"`
|
Node string `xml:"node,attr"`
|
||||||
Item Item
|
Items []Item `xml:"item,omitempty"` // xsd says there can be many. See also 12.10 Batch Processing of XEP-0060
|
||||||
|
}
|
||||||
|
|
||||||
|
type Items struct {
|
||||||
|
List []Item `xml:"item,omitempty"`
|
||||||
|
MaxItems int `xml:"max_items,attr,omitempty"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
SubId string `xml:"subid,attr,omitempty"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Item struct {
|
type Item struct {
|
||||||
XMLName xml.Name `xml:"item"`
|
XMLName xml.Name `xml:"item"`
|
||||||
Id string `xml:"id,attr,omitempty"`
|
Id string `xml:"id,attr,omitempty"`
|
||||||
Tune *Tune
|
Publisher string `xml:"publisher,attr,omitempty"`
|
||||||
Mood *Mood
|
Any *Node `xml:",any"`
|
||||||
}
|
}
|
||||||
|
|
||||||
type Retract struct {
|
type Retract struct {
|
||||||
XMLName xml.Name `xml:"retract"`
|
XMLName xml.Name `xml:"retract"`
|
||||||
Node string `xml:"node,attr"`
|
Node string `xml:"node,attr"`
|
||||||
Notify string `xml:"notify,attr"`
|
Notify *bool `xml:"notify,attr,omitempty"`
|
||||||
Item Item
|
Items []Item `xml:"item"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PubSubOption struct {
|
||||||
|
XMLName xml.Name `xml:"jabber:x:data options"`
|
||||||
|
Form `xml:"x"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSubRq builds a subscription request to a node at the given service.
|
||||||
|
// It's a Set type IQ.
|
||||||
|
// Information about the subscription and the requester are separated. subInfo contains information about the subscription.
|
||||||
|
// 6.1 Subscribe to a Node
|
||||||
|
func NewSubRq(serviceId string, subInfo SubInfo) (IQ, error) {
|
||||||
|
if e := subInfo.validate(); e != nil {
|
||||||
|
return IQ{}, e
|
||||||
|
}
|
||||||
|
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
Subscribe: &subInfo,
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewUnsubRq builds an unsub request to a node at the given service.
|
||||||
|
// It's a Set type IQ
|
||||||
|
// Information about the subscription and the requester are separated. subInfo contains information about the subscription.
|
||||||
|
// 6.2 Unsubscribe from a Node
|
||||||
|
func NewUnsubRq(serviceId string, subInfo SubInfo) (IQ, error) {
|
||||||
|
if e := subInfo.validate(); e != nil {
|
||||||
|
return IQ{}, e
|
||||||
|
}
|
||||||
|
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
Unsubscribe: &subInfo,
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSubOptsRq builds a request for the subscription options.
|
||||||
|
// It's a Get type IQ
|
||||||
|
// Information about the subscription and the requester are separated. subInfo contains information about the subscription.
|
||||||
|
// 6.3 Configure Subscription Options
|
||||||
|
func NewSubOptsRq(serviceId string, subInfo SubInfo) (IQ, error) {
|
||||||
|
if e := subInfo.validate(); e != nil {
|
||||||
|
return IQ{}, e
|
||||||
|
}
|
||||||
|
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
SubOptions: &SubOptions{
|
||||||
|
SubInfo: subInfo,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewFormSubmission builds a form submission pubsub IQ
|
||||||
|
// Information about the subscription and the requester are separated. subInfo contains information about the subscription.
|
||||||
|
// 6.3.5 Form Submission
|
||||||
|
func NewFormSubmission(serviceId string, subInfo SubInfo, form *Form) (IQ, error) {
|
||||||
|
if e := subInfo.validate(); e != nil {
|
||||||
|
return IQ{}, e
|
||||||
|
}
|
||||||
|
if form.Type != FormTypeSubmit {
|
||||||
|
return IQ{}, errors.New("form type was expected to be submit but was : " + form.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
SubOptions: &SubOptions{
|
||||||
|
SubInfo: subInfo,
|
||||||
|
Form: form,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSubAndConfig builds a subscribe request that contains configuration options for the service
|
||||||
|
// From XEP-0060 : The <options/> element MUST follow the <subscribe/> element and
|
||||||
|
// MUST NOT possess a 'node' attribute or 'jid' attribute,
|
||||||
|
// 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
|
||||||
|
// 6.3.7 Subscribe and Configure
|
||||||
|
func NewSubAndConfig(serviceId string, subInfo SubInfo, form *Form) (IQ, error) {
|
||||||
|
if e := subInfo.validate(); e != nil {
|
||||||
|
return IQ{}, e
|
||||||
|
}
|
||||||
|
if form.Type != FormTypeSubmit {
|
||||||
|
return IQ{}, errors.New("form type was expected to be submit but was : " + form.Type)
|
||||||
|
}
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
Subscribe: &subInfo,
|
||||||
|
SubOptions: &SubOptions{
|
||||||
|
SubInfo: SubInfo{SubId: subInfo.SubId},
|
||||||
|
Form: form,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// 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) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
Items: &Items{Node: node},
|
||||||
|
}
|
||||||
|
|
||||||
|
if maxItems != 0 {
|
||||||
|
ps, _ := iq.Payload.(*PubSubGeneric)
|
||||||
|
ps.Items.MaxItems = maxItems
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewItemsRequest creates a request to get a specific item from a node.
|
||||||
|
// 6.5.8 Requesting a Particular Item
|
||||||
|
func NewSpecificItemRequest(serviceId, node, itemId string) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
Items: &Items{Node: node,
|
||||||
|
List: []Item{
|
||||||
|
{
|
||||||
|
Id: itemId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) {
|
||||||
|
// "The <publish/> element MUST possess a 'node' attribute, specifying the NodeID of the node."
|
||||||
|
if strings.TrimSpace(nodeID) == "" {
|
||||||
|
return IQ{}, errors.New("cannot publish without a target node ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
Publish: &Publish{Node: nodeID, Items: []Item{item}},
|
||||||
|
}
|
||||||
|
|
||||||
|
// "The <item/> element provided by the publisher MAY possess an 'id' attribute,
|
||||||
|
// specifying a unique ItemID for the item.
|
||||||
|
// If an ItemID is not provided in the publish request,
|
||||||
|
// the pubsub service MUST generate one and MUST ensure that it is unique for that node."
|
||||||
|
if strings.TrimSpace(pubItemID) != "" {
|
||||||
|
ps, _ := iq.Payload.(*PubSubGeneric)
|
||||||
|
ps.Publish.Items[0].Id = pubItemID
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
//(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) {
|
||||||
|
// "The <publish/> element MUST possess a 'node' attribute, specifying the NodeID of the node."
|
||||||
|
if strings.TrimSpace(nodeID) == "" {
|
||||||
|
return IQ{}, errors.New("cannot publish without a target node ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
Publish: &Publish{Node: nodeID, Items: items},
|
||||||
|
PublishOptions: options,
|
||||||
|
}
|
||||||
|
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDelItemFromNode creates a request to delete and item from a node, given its id.
|
||||||
|
// To delete an item, the publisher sends a retract request.
|
||||||
|
// This helper function follows 7.2 Delete an Item from a Node
|
||||||
|
func NewDelItemFromNode(serviceId, nodeID, itemId string, notify *bool) (IQ, error) {
|
||||||
|
// "The <retract/> element MUST possess a 'node' attribute, specifying the NodeID of the node."
|
||||||
|
if strings.TrimSpace(nodeID) == "" {
|
||||||
|
return IQ{}, errors.New("cannot delete item without a target node ID")
|
||||||
|
}
|
||||||
|
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
Retract: &Retract{Node: nodeID, Items: []Item{{Id: itemId}}, Notify: notify},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCreateAndConfigNode makes a request for node creation that has the desired node configuration.
|
||||||
|
// See 8.1.3 Create and Configure a Node
|
||||||
|
func NewCreateAndConfigNode(serviceId, nodeID string, confForm *Form) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
Create: &Create{Node: nodeID},
|
||||||
|
Configure: &Configure{Form: confForm},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewCreateNode builds a request to create a node on the service referenced by "serviceId"
|
||||||
|
// See 8.1 Create a Node
|
||||||
|
func NewCreateNode(serviceId, nodeName string) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
Create: &Create{Node: nodeName},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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/>
|
||||||
|
// child contains an empty <subscriptions/> element with no attributes.
|
||||||
|
func NewRetrieveAllSubsRequest(serviceId string) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
Subscriptions: &Subscriptions{},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
func NewRetrieveAllAffilsRequest(serviceId string) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubGeneric{
|
||||||
|
Affiliations: &Affiliations{},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func init() {
|
func init() {
|
||||||
TypeRegistry.MapExtension(PKTIQ, xml.Name{"http://jabber.org/protocol/pubsub", "pubsub"}, PubSub{})
|
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: "http://jabber.org/protocol/pubsub", Local: "pubsub"}, PubSubGeneric{})
|
||||||
}
|
}
|
||||||
|
377
stanza/pubsub_owner.go
Normal file
377
stanza/pubsub_owner.go
Normal file
@ -0,0 +1,377 @@
|
|||||||
|
package stanza
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
type PubSubOwner struct {
|
||||||
|
XMLName xml.Name `xml:"http://jabber.org/protocol/pubsub#owner pubsub"`
|
||||||
|
OwnerUseCase OwnerUseCase
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pso *PubSubOwner) Namespace() string {
|
||||||
|
return pso.XMLName.Space
|
||||||
|
}
|
||||||
|
|
||||||
|
type OwnerUseCase interface {
|
||||||
|
UseCase() string
|
||||||
|
}
|
||||||
|
|
||||||
|
type AffiliationsOwner struct {
|
||||||
|
XMLName xml.Name `xml:"affiliations"`
|
||||||
|
Affiliations []AffiliationOwner `xml:"affiliation,omitempty"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (AffiliationsOwner) UseCase() string {
|
||||||
|
return "affiliations"
|
||||||
|
}
|
||||||
|
|
||||||
|
type AffiliationOwner struct {
|
||||||
|
XMLName xml.Name `xml:"affiliation"`
|
||||||
|
AffiliationStatus string `xml:"affiliation,attr"`
|
||||||
|
Jid string `xml:"jid,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
AffiliationStatusMember = "member"
|
||||||
|
AffiliationStatusNone = "none"
|
||||||
|
AffiliationStatusOutcast = "outcast"
|
||||||
|
AffiliationStatusOwner = "owner"
|
||||||
|
AffiliationStatusPublisher = "publisher"
|
||||||
|
AffiliationStatusPublishOnly = "publish-only"
|
||||||
|
)
|
||||||
|
|
||||||
|
type ConfigureOwner struct {
|
||||||
|
XMLName xml.Name `xml:"configure"`
|
||||||
|
Node string `xml:"node,attr,omitempty"`
|
||||||
|
Form *Form `xml:"x,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*ConfigureOwner) UseCase() string {
|
||||||
|
return "configure"
|
||||||
|
}
|
||||||
|
|
||||||
|
type DefaultOwner struct {
|
||||||
|
XMLName xml.Name `xml:"default"`
|
||||||
|
Form *Form `xml:"x,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DefaultOwner) UseCase() string {
|
||||||
|
return "default"
|
||||||
|
}
|
||||||
|
|
||||||
|
type DeleteOwner struct {
|
||||||
|
XMLName xml.Name `xml:"delete"`
|
||||||
|
RedirectOwner *RedirectOwner `xml:"redirect,omitempty"`
|
||||||
|
Node string `xml:"node,attr,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*DeleteOwner) UseCase() string {
|
||||||
|
return "delete"
|
||||||
|
}
|
||||||
|
|
||||||
|
type RedirectOwner struct {
|
||||||
|
XMLName xml.Name `xml:"redirect"`
|
||||||
|
URI string `xml:"uri,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
type PurgeOwner struct {
|
||||||
|
XMLName xml.Name `xml:"purge"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*PurgeOwner) UseCase() string {
|
||||||
|
return "purge"
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubscriptionsOwner struct {
|
||||||
|
XMLName xml.Name `xml:"subscriptions"`
|
||||||
|
Subscriptions []SubscriptionOwner `xml:"subscription"`
|
||||||
|
Node string `xml:"node,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func (*SubscriptionsOwner) UseCase() string {
|
||||||
|
return "subscriptions"
|
||||||
|
}
|
||||||
|
|
||||||
|
type SubscriptionOwner struct {
|
||||||
|
SubscriptionStatus string `xml:"subscription"`
|
||||||
|
Jid string `xml:"jid,attr"`
|
||||||
|
}
|
||||||
|
|
||||||
|
const (
|
||||||
|
SubscriptionStatusNone = "none"
|
||||||
|
SubscriptionStatusPending = "pending"
|
||||||
|
SubscriptionStatusSubscribed = "subscribed"
|
||||||
|
SubscriptionStatusUnconfigured = "unconfigured"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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.
|
||||||
|
// See 8.2 Configure a Node
|
||||||
|
func NewConfigureNode(serviceId, nodeName string) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubOwner{
|
||||||
|
OwnerUseCase: &ConfigureOwner{Node: nodeName},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewDelNode creates a request to delete node "nodeID" from the "serviceId" service
|
||||||
|
// See 8.4 Delete a Node
|
||||||
|
func NewDelNode(serviceId, nodeID string) (IQ, error) {
|
||||||
|
if strings.TrimSpace(nodeID) == "" {
|
||||||
|
return IQ{}, errors.New("cannot delete a node without a target node ID")
|
||||||
|
}
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubOwner{
|
||||||
|
OwnerUseCase: &DeleteOwner{Node: nodeID},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewPurgeAllItems creates a new purge request for the "nodeId" node, at "serviceId" service
|
||||||
|
// See 8.5 Purge All Node Items
|
||||||
|
func NewPurgeAllItems(serviceId, nodeId string) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubOwner{
|
||||||
|
OwnerUseCase: &PurgeOwner{Node: nodeId},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewRequestDefaultConfig build a request to ask the service for the default config of its nodes
|
||||||
|
// See 8.3 Request Default Node Configuration Options
|
||||||
|
func NewRequestDefaultConfig(serviceId string) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubOwner{
|
||||||
|
OwnerUseCase: &DefaultOwner{},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewApproveSubRequest creates a new sub approval response to a request from the service to the owner of the node
|
||||||
|
// In order to approve the request, the owner shall submit the form and set the "pubsub#allow" field to a value of "1" or "true"
|
||||||
|
// For tracking purposes the message MUST reflect the 'id' attribute originally provided in the request.
|
||||||
|
// See 8.6 Manage Subscription Requests
|
||||||
|
func NewApproveSubRequest(serviceId, reqID string, apprForm *Form) (Message, error) {
|
||||||
|
if serviceId == "" {
|
||||||
|
return Message{}, errors.New("need a target service serviceId send approval serviceId")
|
||||||
|
}
|
||||||
|
if reqID == "" {
|
||||||
|
return Message{}, errors.New("the request ID is empty but must be used for the approval")
|
||||||
|
}
|
||||||
|
if apprForm == nil {
|
||||||
|
return Message{}, errors.New("approval form is nil")
|
||||||
|
}
|
||||||
|
apprMess := NewMessage(Attrs{To: serviceId})
|
||||||
|
apprMess.Extensions = []MsgExtension{apprForm}
|
||||||
|
apprMess.Id = reqID
|
||||||
|
|
||||||
|
return apprMess, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
// 8.7 Process Pending Subscription Requests
|
||||||
|
func NewGetPendingSubRequests(serviceId string) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &Command{
|
||||||
|
// 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",
|
||||||
|
Action: CommandActionExecute,
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewGetPendingSubRequests creates a new request for all pending subscriptions to be approved on a given node
|
||||||
|
// Upon receiving the data form for managing subscription requests, the owner then MAY request pending subscription
|
||||||
|
// approval requests for a given node.
|
||||||
|
// See 8.7.4 Per-Node Request
|
||||||
|
func NewApprovePendingSubRequest(serviceId, sessionId, nodeId string) (IQ, error) {
|
||||||
|
if sessionId == "" {
|
||||||
|
return IQ{}, errors.New("the sessionId must be maintained for the command")
|
||||||
|
}
|
||||||
|
|
||||||
|
form := &Form{
|
||||||
|
Type: FormTypeSubmit,
|
||||||
|
Fields: []Field{{Var: "pubsub#node", ValuesList: []string{nodeId}}},
|
||||||
|
}
|
||||||
|
data, err := xml.Marshal(form)
|
||||||
|
if err != nil {
|
||||||
|
return IQ{}, err
|
||||||
|
}
|
||||||
|
var n Node
|
||||||
|
xml.Unmarshal(data, &n)
|
||||||
|
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &Command{
|
||||||
|
// 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",
|
||||||
|
Action: CommandActionExecute,
|
||||||
|
SessionId: sessionId,
|
||||||
|
CommandElement: &n,
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewSubListRequest creates a request to list subscriptions of the client, for all nodes at the service.
|
||||||
|
// It's a Get type IQ
|
||||||
|
// 8.8.1 Retrieve Subscriptions
|
||||||
|
func NewSubListRqPl(serviceId, nodeID string) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubOwner{
|
||||||
|
OwnerUseCase: &SubscriptionsOwner{Node: nodeID},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func NewSubsForEntitiesRequest(serviceId, nodeID string, subs []SubscriptionOwner) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubOwner{
|
||||||
|
OwnerUseCase: &SubscriptionsOwner{Node: nodeID, Subscriptions: subs},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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")
|
||||||
|
func NewModifAffiliationRequest(serviceId, nodeID string, newAffils []AffiliationOwner) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeSet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubOwner{
|
||||||
|
OwnerUseCase: &AffiliationsOwner{
|
||||||
|
Node: nodeID,
|
||||||
|
Affiliations: newAffils,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewAffiliationListRequest creates a request to list all affiliated entities
|
||||||
|
// See 8.9.1 Retrieve List List
|
||||||
|
func NewAffiliationListRequest(serviceId, nodeID string) (IQ, error) {
|
||||||
|
iq := NewIQ(Attrs{Type: IQTypeGet, To: serviceId})
|
||||||
|
iq.Payload = &PubSubOwner{
|
||||||
|
OwnerUseCase: &AffiliationsOwner{
|
||||||
|
Node: nodeID,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
return iq, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFormFields gets the fields from a form in a IQ stanza of type result, as a map.
|
||||||
|
// Key is the "var" attribute of the field, and field is the value.
|
||||||
|
// The user can then select and modify the fields they want to alter, and submit a new form to the service using the
|
||||||
|
// NewFormSubmission function to build the IQ.
|
||||||
|
// TODO : remove restriction on IQ type ?
|
||||||
|
func (iq *IQ) GetFormFields() (map[string]Field, error) {
|
||||||
|
if iq.Type != IQTypeResult {
|
||||||
|
return nil, errors.New("this IQ is not a result type IQ. Cannot extract the form from it")
|
||||||
|
}
|
||||||
|
switch payload := iq.Payload.(type) {
|
||||||
|
// We support IOT Control IQ
|
||||||
|
case *PubSubGeneric:
|
||||||
|
fieldMap := make(map[string]Field)
|
||||||
|
for _, elt := range payload.Configure.Form.Fields {
|
||||||
|
fieldMap[elt.Var] = elt
|
||||||
|
}
|
||||||
|
return fieldMap, nil
|
||||||
|
case *PubSubOwner:
|
||||||
|
fieldMap := make(map[string]Field)
|
||||||
|
co, ok := payload.OwnerUseCase.(*ConfigureOwner)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("this IQ does not contain a PubSub payload with a configure tag for the owner namespace")
|
||||||
|
}
|
||||||
|
for _, elt := range co.Form.Fields {
|
||||||
|
fieldMap[elt.Var] = elt
|
||||||
|
}
|
||||||
|
return fieldMap, nil
|
||||||
|
default:
|
||||||
|
if iq.Any != nil {
|
||||||
|
fieldMap := make(map[string]Field)
|
||||||
|
if iq.Any.XMLName.Local != "command" {
|
||||||
|
return nil, errors.New("this IQ does not contain a form")
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, nde := range iq.Any.Nodes {
|
||||||
|
if nde.XMLName.Local == "x" {
|
||||||
|
for _, n := range nde.Nodes {
|
||||||
|
if n.XMLName.Local == "field" {
|
||||||
|
f := Field{}
|
||||||
|
data, err := xml.Marshal(n)
|
||||||
|
if err != nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = xml.Unmarshal(data, &f)
|
||||||
|
if err == nil {
|
||||||
|
fieldMap[f.Var] = f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fieldMap, nil
|
||||||
|
}
|
||||||
|
return nil, errors.New("this IQ does not contain a form")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (pso *PubSubOwner) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
|
||||||
|
pso.XMLName = start.Name
|
||||||
|
// decode inner elements
|
||||||
|
for {
|
||||||
|
t, err := d.Token()
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
switch tt := t.(type) {
|
||||||
|
|
||||||
|
case xml.StartElement:
|
||||||
|
// Decode sub-elements
|
||||||
|
var err error
|
||||||
|
switch tt.Name.Local {
|
||||||
|
|
||||||
|
case "affiliations":
|
||||||
|
aff := AffiliationsOwner{}
|
||||||
|
d.DecodeElement(&aff, &tt)
|
||||||
|
pso.OwnerUseCase = &aff
|
||||||
|
case "configure":
|
||||||
|
co := ConfigureOwner{}
|
||||||
|
d.DecodeElement(&co, &tt)
|
||||||
|
pso.OwnerUseCase = &co
|
||||||
|
case "default":
|
||||||
|
def := DefaultOwner{}
|
||||||
|
d.DecodeElement(&def, &tt)
|
||||||
|
pso.OwnerUseCase = &def
|
||||||
|
case "delete":
|
||||||
|
del := DeleteOwner{}
|
||||||
|
d.DecodeElement(&del, &tt)
|
||||||
|
pso.OwnerUseCase = &del
|
||||||
|
case "purge":
|
||||||
|
pu := PurgeOwner{}
|
||||||
|
d.DecodeElement(&pu, &tt)
|
||||||
|
pso.OwnerUseCase = &pu
|
||||||
|
case "subscriptions":
|
||||||
|
subs := SubscriptionsOwner{}
|
||||||
|
d.DecodeElement(&subs, &tt)
|
||||||
|
pso.OwnerUseCase = &subs
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
case xml.EndElement:
|
||||||
|
if tt == start.End() {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
TypeRegistry.MapExtension(PKTIQ, xml.Name{Space: "http://jabber.org/protocol/pubsub#owner", Local: "pubsub"}, PubSubOwner{})
|
||||||
|
}
|
833
stanza/pubsub_owner_test.go
Normal file
833
stanza/pubsub_owner_test.go
Normal file
@ -0,0 +1,833 @@
|
|||||||
|
package stanza_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"gosrc.io/xmpp/stanza"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ******************************
|
||||||
|
// * 8.2 Configure a Node
|
||||||
|
// ******************************
|
||||||
|
func TestNewConfigureNode(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"get\" id=\"config1\" to=\"pubsub.shakespeare.lit\" > " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> <configure node=\"princely_musings\"></configure> " +
|
||||||
|
"</pubsub> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewConfigureNode("pubsub.shakespeare.lit", "princely_musings")
|
||||||
|
subR.Id = "config1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubsub.OwnerUseCase == nil {
|
||||||
|
t.Fatalf("owner use case is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
ownrUsecase, ok := pubsub.OwnerUseCase.(*stanza.ConfigureOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("owner use case is not a configure tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ownrUsecase.Node == "" {
|
||||||
|
t.Fatalf("could not parse node from config tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewConfigureNodeResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq from="pubsub.shakespeare.lit" id="config1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||||
|
<configure node="princely_musings">
|
||||||
|
<x type="form" xmlns="jabber:x:data">
|
||||||
|
<field type="hidden" var="FORM_TYPE">
|
||||||
|
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||||
|
</field>
|
||||||
|
<field label="Purge all items when the relevant publisher goes offline?" type="boolean" var="pubsub#purge_offline">
|
||||||
|
<value>0</value>
|
||||||
|
</field>
|
||||||
|
<field label="Max Payload size in bytes" type="text-single" var="pubsub#max_payload_size">
|
||||||
|
<value>1028</value>
|
||||||
|
</field>
|
||||||
|
<field label="When to send the last published item" type="list-single" var="pubsub#send_last_published_item">
|
||||||
|
<option label="Never">
|
||||||
|
<value>never</value>
|
||||||
|
</option>
|
||||||
|
<option label="When a new subscription is processed">
|
||||||
|
<value>on_sub</value>
|
||||||
|
</option>
|
||||||
|
<option label="When a new subscription is processed and whenever a subscriber comes online">
|
||||||
|
<value>on_sub_and_presence</value>
|
||||||
|
</option>
|
||||||
|
<value>never</value>
|
||||||
|
</field>
|
||||||
|
<field label="Deliver event notifications only to available users" type="boolean" var="pubsub#presence_based_delivery">
|
||||||
|
<value>0</value>
|
||||||
|
</field>
|
||||||
|
<field label="Specify the delivery style for event notifications" type="list-single" var="pubsub#notification_type">
|
||||||
|
<option>
|
||||||
|
<value>normal</value>
|
||||||
|
</option>
|
||||||
|
<option>
|
||||||
|
<value>headline</value>
|
||||||
|
</option>
|
||||||
|
<value>headline</value>
|
||||||
|
</field>
|
||||||
|
<field label="Specify the type of payload data to be provided at this node" type="text-single" var="pubsub#type">
|
||||||
|
<value>http://www.w3.org/2005/Atom</value>
|
||||||
|
</field>
|
||||||
|
<field label="Payload XSLT" type="text-single" var="pubsub#dataform_xslt"/>
|
||||||
|
</x>
|
||||||
|
</configure>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
|
||||||
|
pubsub, err := getPubSubOwnerPayload(response)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
if pubsub.OwnerUseCase == nil {
|
||||||
|
t.Fatalf("owner use case is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
ownrUsecase, ok := pubsub.OwnerUseCase.(*stanza.ConfigureOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("owner use case is not a configure tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ownrUsecase.Form == nil {
|
||||||
|
t.Fatalf("form is nil in the parsed config tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ownrUsecase.Form.Fields) != 8 {
|
||||||
|
t.Fatalf("one or more fields in the response form could not be parsed correctly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// *************************************************
|
||||||
|
// * 8.3 Request Default Node Configuration Options
|
||||||
|
// *************************************************
|
||||||
|
|
||||||
|
func TestNewRequestDefaultConfig(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"get\" id=\"def1\" to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> <default></default> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewRequestDefaultConfig("pubsub.shakespeare.lit")
|
||||||
|
subR.Id = "def1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubsub.OwnerUseCase == nil {
|
||||||
|
t.Fatalf("owner use case is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = pubsub.OwnerUseCase.(*stanza.DefaultOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("owner use case is not a default tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewRequestDefaultConfigResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq from="pubsub.shakespeare.lit" id="config1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||||
|
<configure node="princely_musings">
|
||||||
|
<x type="form" xmlns="jabber:x:data">
|
||||||
|
<field type="hidden" var="FORM_TYPE">
|
||||||
|
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||||
|
</field>
|
||||||
|
<field label="Purge all items when the relevant publisher goes offline?" type="boolean" var="pubsub#purge_offline">
|
||||||
|
<value>0</value>
|
||||||
|
</field>
|
||||||
|
<field label="Max Payload size in bytes" type="text-single" var="pubsub#max_payload_size">
|
||||||
|
<value>1028</value>
|
||||||
|
</field>
|
||||||
|
<field label="When to send the last published item" type="list-single" var="pubsub#send_last_published_item">
|
||||||
|
<option label="Never">
|
||||||
|
<value>never</value>
|
||||||
|
</option>
|
||||||
|
<option label="When a new subscription is processed">
|
||||||
|
<value>on_sub</value>
|
||||||
|
</option>
|
||||||
|
<option label="When a new subscription is processed and whenever a subscriber comes online">
|
||||||
|
<value>on_sub_and_presence</value>
|
||||||
|
</option>
|
||||||
|
<value>never</value>
|
||||||
|
</field>
|
||||||
|
<field label="Deliver event notifications only to available users" type="boolean" var="pubsub#presence_based_delivery">
|
||||||
|
<value>0</value>
|
||||||
|
</field>
|
||||||
|
<field label="Specify the delivery style for event notifications" type="list-single" var="pubsub#notification_type">
|
||||||
|
<option>
|
||||||
|
<value>normal</value>
|
||||||
|
</option>
|
||||||
|
<option>
|
||||||
|
<value>headline</value>
|
||||||
|
</option>
|
||||||
|
<value>headline</value>
|
||||||
|
</field>
|
||||||
|
<field label="Specify the type of payload data to be provided at this node" type="text-single" var="pubsub#type">
|
||||||
|
<value>http://www.w3.org/2005/Atom</value>
|
||||||
|
</field>
|
||||||
|
<field label="Payload XSLT" type="text-single" var="pubsub#dataform_xslt"/>
|
||||||
|
</x>
|
||||||
|
</configure>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
|
||||||
|
pubsub, err := getPubSubOwnerPayload(response)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
if pubsub.OwnerUseCase == nil {
|
||||||
|
t.Fatalf("owner use case is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
ownrUsecase, ok := pubsub.OwnerUseCase.(*stanza.ConfigureOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("owner use case is not a configure tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ownrUsecase.Form == nil {
|
||||||
|
t.Fatalf("form is nil in the parsed config tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(ownrUsecase.Form.Fields) != 8 {
|
||||||
|
t.Fatalf("one or more fields in the response form could not be parsed correctly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***********************
|
||||||
|
// * 8.4 Delete a Node
|
||||||
|
// ***********************
|
||||||
|
|
||||||
|
func TestNewDelNode(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\" id=\"delete1\" to=\"pubsub.shakespeare.lit\" >" +
|
||||||
|
" <pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> " +
|
||||||
|
"<delete node=\"princely_musings\"></delete> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewDelNode("pubsub.shakespeare.lit", "princely_musings")
|
||||||
|
subR.Id = "delete1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubsub.OwnerUseCase == nil {
|
||||||
|
t.Fatalf("owner use case is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok = pubsub.OwnerUseCase.(*stanza.DeleteOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("owner use case is not a delete tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewDelNodeResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq id="delete1" to="pubsub.shakespeare.lit" type="set">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||||
|
<delete node="princely_musings">
|
||||||
|
<redirect uri="xmpp:hamlet@denmark.lit"/>
|
||||||
|
</delete>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
|
||||||
|
pubsub, err := getPubSubOwnerPayload(response)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
if pubsub.OwnerUseCase == nil {
|
||||||
|
t.Fatalf("owner use case is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
ownrUsecase, ok := pubsub.OwnerUseCase.(*stanza.DeleteOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("owner use case is not a configure tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ownrUsecase.RedirectOwner == nil {
|
||||||
|
t.Fatalf("redirect is nil in the delete tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
if ownrUsecase.RedirectOwner.URI == "" {
|
||||||
|
t.Fatalf("could not parse redirect uri")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ****************************
|
||||||
|
// * 8.5 Purge All Node Items
|
||||||
|
// ****************************
|
||||||
|
|
||||||
|
func TestNewPurgeAllItems(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\" id=\"purge1\" to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> " +
|
||||||
|
"<purge node=\"princely_musings\"></purge> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewPurgeAllItems("pubsub.shakespeare.lit", "princely_musings")
|
||||||
|
subR.Id = "purge1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubsub.OwnerUseCase == nil {
|
||||||
|
t.Fatalf("owner use case is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
purge, ok := pubsub.OwnerUseCase.(*stanza.PurgeOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("owner use case is not a delete tag")
|
||||||
|
}
|
||||||
|
|
||||||
|
if purge.Node == "" {
|
||||||
|
t.Fatalf("could not parse purge targer node")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ************************************
|
||||||
|
// * 8.6 Manage Subscription Requests
|
||||||
|
// ************************************
|
||||||
|
func TestNewApproveSubRequest(t *testing.T) {
|
||||||
|
expectedReq := "<message id=\"approve1\" to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<x xmlns=\"jabber:x:data\" type=\"submit\"> <field var=\"FORM_TYPE\" type=\"hidden\"> " +
|
||||||
|
"<value>http://jabber.org/protocol/pubsub#subscribe_authorization</value> </field> <field var=\"pubsub#subid\">" +
|
||||||
|
" <value>123-abc</value> </field> <field var=\"pubsub#node\"> <value>princely_musings</value> </field> " +
|
||||||
|
"<field var=\"pubsub#subscriber_jid\"> <value>horatio@denmark.lit</value> </field> <field var=\"pubsub#allow\"> " +
|
||||||
|
"<value>true</value> </field> </x> </message>"
|
||||||
|
|
||||||
|
apprForm := &stanza.Form{
|
||||||
|
Type: stanza.FormTypeSubmit,
|
||||||
|
Fields: []stanza.Field{
|
||||||
|
{Var: "FORM_TYPE", Type: stanza.FieldTypeHidden, ValuesList: []string{"http://jabber.org/protocol/pubsub#subscribe_authorization"}},
|
||||||
|
{Var: "pubsub#subid", ValuesList: []string{"123-abc"}},
|
||||||
|
{Var: "pubsub#node", ValuesList: []string{"princely_musings"}},
|
||||||
|
{Var: "pubsub#subscriber_jid", ValuesList: []string{"horatio@denmark.lit"}},
|
||||||
|
{Var: "pubsub#allow", ValuesList: []string{"true"}},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
subR, err := stanza.NewApproveSubRequest("pubsub.shakespeare.lit", "approve1", apprForm)
|
||||||
|
subR.Id = "approve1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
frm, ok := subR.Extensions[0].(*stanza.Form)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("extension is not a from !")
|
||||||
|
}
|
||||||
|
|
||||||
|
var allowField *stanza.Field
|
||||||
|
|
||||||
|
for _, f := range frm.Fields {
|
||||||
|
if f.Var == "pubsub#allow" {
|
||||||
|
allowField = &f
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if allowField == nil || allowField.ValuesList[0] != "true" {
|
||||||
|
t.Fatalf("could not correctly parse the allow field in the response from")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ********************************************
|
||||||
|
// * 8.7 Process Pending Subscription Requests
|
||||||
|
// ********************************************
|
||||||
|
|
||||||
|
func TestNewGetPendingSubRequests(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\" id=\"pending1\" to=\"pubsub.shakespeare.lit\" > " +
|
||||||
|
"<command xmlns=\"http://jabber.org/protocol/commands\" action=\"execute\" node=\"http://jabber.org/protocol/pubsub#get-pending\" >" +
|
||||||
|
"</command> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewGetPendingSubRequests("pubsub.shakespeare.lit")
|
||||||
|
subR.Id = "pending1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
command, ok := subR.Payload.(*stanza.Command)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a command !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if command.Action != stanza.CommandActionExecute {
|
||||||
|
t.Fatalf("command should be execute !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if command.Node != "http://jabber.org/protocol/pubsub#get-pending" {
|
||||||
|
t.Fatalf("command node should be http://jabber.org/protocol/pubsub#get-pending !")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewGetPendingSubRequestsResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq from="pubsub.shakespeare.lit" id="pending1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||||
|
<command action="execute" node="http://jabber.org/protocol/pubsub#get-pending" sessionid="pubsub-get-pending:20031021T150901Z-600" status="executing" xmlns="http://jabber.org/protocol/commands">
|
||||||
|
<x type="form" xmlns="jabber:x:data">
|
||||||
|
<field type="hidden" var="FORM_TYPE">
|
||||||
|
<value>http://jabber.org/protocol/pubsub#subscribe_authorization</value>
|
||||||
|
</field>
|
||||||
|
<field type="list-single" var="pubsub#node">
|
||||||
|
<option>
|
||||||
|
<value>princely_musings</value>
|
||||||
|
</option>
|
||||||
|
<option>
|
||||||
|
<value>news_from_elsinore</value>
|
||||||
|
</option>
|
||||||
|
</field>
|
||||||
|
</x>
|
||||||
|
</command>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
|
||||||
|
var respIQ stanza.IQ
|
||||||
|
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not parse iq")
|
||||||
|
}
|
||||||
|
|
||||||
|
_, ok := respIQ.Payload.(*stanza.Command)
|
||||||
|
if !ok {
|
||||||
|
errors.New("this iq payload is not a command")
|
||||||
|
}
|
||||||
|
|
||||||
|
fMap, err := respIQ.GetFormFields()
|
||||||
|
if err != nil || len(fMap) != 2 {
|
||||||
|
errors.New("could not parse command form fields")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ********************************************
|
||||||
|
// * 8.7 Process Pending Subscription Requests
|
||||||
|
// ********************************************
|
||||||
|
|
||||||
|
func TestNewApprovePendingSubRequest(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\" id=\"pending2\" to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<command xmlns=\"http://jabber.org/protocol/commands\" action=\"execute\"" +
|
||||||
|
"node=\"http://jabber.org/protocol/pubsub#get-pending\"sessionid=\"pubsub-get-pending:20031021T150901Z-600\"> " +
|
||||||
|
"<x xmlns=\"jabber:x:data\" type=\"submit\"> <field xmlns=\"jabber:x:data\" var=\"pubsub#node\"> " +
|
||||||
|
"<value xmlns=\"jabber:x:data\">princely_musings</value> </field> </x> </command> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewApprovePendingSubRequest("pubsub.shakespeare.lit",
|
||||||
|
"pubsub-get-pending:20031021T150901Z-600",
|
||||||
|
"princely_musings")
|
||||||
|
subR.Id = "pending2"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
command, ok := subR.Payload.(*stanza.Command)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a command !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if command.Action != stanza.CommandActionExecute {
|
||||||
|
t.Fatalf("command should be execute !")
|
||||||
|
}
|
||||||
|
|
||||||
|
//if command.Node != "http://jabber.org/protocol/pubsub#get-pending"{
|
||||||
|
// t.Fatalf("command node should be http://jabber.org/protocol/pubsub#get-pending !")
|
||||||
|
//}
|
||||||
|
//
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ********************************************
|
||||||
|
// * 8.8.1 Retrieve Subscriptions List
|
||||||
|
// ********************************************
|
||||||
|
|
||||||
|
func TestNewSubListRqPl(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"get\" id=\"subman1\" to=\"pubsub.shakespeare.lit\" > " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> " +
|
||||||
|
"<subscriptions node=\"princely_musings\"></subscriptions> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewSubListRqPl("pubsub.shakespeare.lit", "princely_musings")
|
||||||
|
subR.Id = "subman1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub in namespace owner !")
|
||||||
|
}
|
||||||
|
|
||||||
|
subs, ok := pubsub.OwnerUseCase.(*stanza.SubscriptionsOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("pubsub doesn not contain a subscriptions node !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if subs.Node != "princely_musings" {
|
||||||
|
t.Fatalf("subs node attribute should be princely_musings. Found %s", subs.Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewSubListRqPlResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq from="pubsub.shakespeare.lit" id="subman1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||||
|
<subscriptions node="princely_musings">
|
||||||
|
<subscription jid="hamlet@denmark.lit" subscription="subscribed"></subscription>
|
||||||
|
<subscription jid="polonius@denmark.lit" subscription="unconfigured"></subscription>
|
||||||
|
<subscription jid="bernardo@denmark.lit" subid="123-abc" subscription="subscribed"></subscription>
|
||||||
|
<subscription jid="bernardo@denmark.lit" subid="004-yyy" subscription="subscribed"></subscription>
|
||||||
|
</subscriptions>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
|
||||||
|
var respIQ stanza.IQ
|
||||||
|
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not parse iq")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner)
|
||||||
|
if !ok {
|
||||||
|
errors.New("this iq payload is not a command")
|
||||||
|
}
|
||||||
|
|
||||||
|
subs, ok := pubsub.OwnerUseCase.(*stanza.SubscriptionsOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("pubsub doesn not contain a subscriptions node !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(subs.Subscriptions) != 4 {
|
||||||
|
t.Fatalf("expected to find 4 subscriptions but got %d", len(subs.Subscriptions))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ********************************************
|
||||||
|
// * 8.9.1 Retrieve Affiliations List
|
||||||
|
// ********************************************
|
||||||
|
|
||||||
|
func TestNewAffiliationListRequest(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"get\" id=\"ent1\" to=\"pubsub.shakespeare.lit\" > " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> " +
|
||||||
|
"<affiliations node=\"princely_musings\"></affiliations> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewAffiliationListRequest("pubsub.shakespeare.lit", "princely_musings")
|
||||||
|
subR.Id = "ent1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub in namespace owner !")
|
||||||
|
}
|
||||||
|
|
||||||
|
affils, ok := pubsub.OwnerUseCase.(*stanza.AffiliationsOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("pubsub doesn not contain an affiliations node !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if affils.Node != "princely_musings" {
|
||||||
|
t.Fatalf("affils node attribute should be princely_musings. Found %s", affils.Node)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewAffiliationListRequestResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq from="pubsub.shakespeare.lit" id="ent1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||||
|
<affiliations node="princely_musings">
|
||||||
|
<affiliation affiliation="owner" jid="hamlet@denmark.lit"/>
|
||||||
|
<affiliation affiliation="outcast" jid="polonius@denmark.lit"/>
|
||||||
|
</affiliations>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
|
||||||
|
var respIQ stanza.IQ
|
||||||
|
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not parse iq")
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner)
|
||||||
|
if !ok {
|
||||||
|
errors.New("this iq payload is not a command")
|
||||||
|
}
|
||||||
|
|
||||||
|
affils, ok := pubsub.OwnerUseCase.(*stanza.AffiliationsOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("pubsub doesn not contain an affiliations node !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(affils.Affiliations) != 2 {
|
||||||
|
t.Fatalf("expected to find 2 subscriptions but got %d", len(affils.Affiliations))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ********************************************
|
||||||
|
// * 8.9.2 Modify Affiliation
|
||||||
|
// ********************************************
|
||||||
|
|
||||||
|
func TestNewModifAffiliationRequest(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\" id=\"ent3\" to=\"pubsub.shakespeare.lit\" > " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub#owner\"> <affiliations node=\"princely_musings\"> " +
|
||||||
|
"<affiliation affiliation=\"none\" jid=\"hamlet@denmark.lit\"></affiliation> " +
|
||||||
|
"<affiliation affiliation=\"none\" jid=\"polonius@denmark.lit\"></affiliation> " +
|
||||||
|
"<affiliation affiliation=\"publisher\" jid=\"bard@shakespeare.lit\"></affiliation> </affiliations> </pubsub> " +
|
||||||
|
"</iq>"
|
||||||
|
|
||||||
|
affils := []stanza.AffiliationOwner{
|
||||||
|
{
|
||||||
|
AffiliationStatus: stanza.AffiliationStatusNone,
|
||||||
|
Jid: "hamlet@denmark.lit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AffiliationStatus: stanza.AffiliationStatusNone,
|
||||||
|
Jid: "polonius@denmark.lit",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
AffiliationStatus: stanza.AffiliationStatusPublisher,
|
||||||
|
Jid: "bard@shakespeare.lit",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
subR, err := stanza.NewModifAffiliationRequest("pubsub.shakespeare.lit", "princely_musings", affils)
|
||||||
|
subR.Id = "ent3"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub in namespace owner !")
|
||||||
|
}
|
||||||
|
|
||||||
|
as, ok := pubsub.OwnerUseCase.(*stanza.AffiliationsOwner)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("pubsub doesn not contain an affiliations node !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if as.Node != "princely_musings" {
|
||||||
|
t.Fatalf("affils node attribute should be princely_musings. Found %s", as.Node)
|
||||||
|
}
|
||||||
|
if len(as.Affiliations) != 3 {
|
||||||
|
t.Fatalf("expected 3 affiliations, found %d", len(as.Affiliations))
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFormFields(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq from="pubsub.shakespeare.lit" id="config1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub#owner">
|
||||||
|
<configure node="princely_musings">
|
||||||
|
<x type="form" xmlns="jabber:x:data">
|
||||||
|
<field type="hidden" var="FORM_TYPE">
|
||||||
|
<value>http://jabber.org/protocol/pubsub#node_config</value>
|
||||||
|
</field>
|
||||||
|
<field label="Purge all items when the relevant publisher goes offline?" type="boolean" var="pubsub#purge_offline">
|
||||||
|
<value>0</value>
|
||||||
|
</field>
|
||||||
|
<field label="Max Payload size in bytes" type="text-single" var="pubsub#max_payload_size">
|
||||||
|
<value>1028</value>
|
||||||
|
</field>
|
||||||
|
<field label="When to send the last published item" type="list-single" var="pubsub#send_last_published_item">
|
||||||
|
<option label="Never">
|
||||||
|
<value>never</value>
|
||||||
|
</option>
|
||||||
|
<option label="When a new subscription is processed">
|
||||||
|
<value>on_sub</value>
|
||||||
|
</option>
|
||||||
|
<option label="When a new subscription is processed and whenever a subscriber comes online">
|
||||||
|
<value>on_sub_and_presence</value>
|
||||||
|
</option>
|
||||||
|
<value>never</value>
|
||||||
|
</field>
|
||||||
|
<field label="Deliver event notifications only to available users" type="boolean" var="pubsub#presence_based_delivery">
|
||||||
|
<value>0</value>
|
||||||
|
</field>
|
||||||
|
<field label="Specify the delivery style for event notifications" type="list-single" var="pubsub#notification_type">
|
||||||
|
<option>
|
||||||
|
<value>normal</value>
|
||||||
|
</option>
|
||||||
|
<option>
|
||||||
|
<value>headline</value>
|
||||||
|
</option>
|
||||||
|
<value>headline</value>
|
||||||
|
</field>
|
||||||
|
<field label="Specify the type of payload data to be provided at this node" type="text-single" var="pubsub#type">
|
||||||
|
<value>http://www.w3.org/2005/Atom</value>
|
||||||
|
</field>
|
||||||
|
<field label="Payload XSLT" type="text-single" var="pubsub#dataform_xslt"/>
|
||||||
|
</x>
|
||||||
|
</configure>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
var iq stanza.IQ
|
||||||
|
err := xml.Unmarshal([]byte(response), &iq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not parse IQ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fields, err := iq.GetFormFields()
|
||||||
|
if len(fields) != 8 {
|
||||||
|
t.Fatalf("could not correctly parse fields. Expected 8, found : %v", len(fields))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetFormFieldsCmd(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq from="pubsub.shakespeare.lit" id="pending1" to="hamlet@denmark.lit/elsinore" type="result">
|
||||||
|
<command action="execute" node="http://jabber.org/protocol/pubsub#get-pending" sessionid="pubsub-get-pending:20031021T150901Z-600" status="executing" xmlns="http://jabber.org/protocol/commands">
|
||||||
|
<x type="form" xmlns="jabber:x:data">
|
||||||
|
<field type="hidden" var="FORM_TYPE">
|
||||||
|
<value>http://jabber.org/protocol/pubsub#subscribe_authorization</value>
|
||||||
|
</field>
|
||||||
|
<field type="list-single" var="pubsub#node">
|
||||||
|
<option>
|
||||||
|
<value>princely_musings</value>
|
||||||
|
</option>
|
||||||
|
<option>
|
||||||
|
<value>news_from_elsinore</value>
|
||||||
|
</option>
|
||||||
|
</field>
|
||||||
|
</x>
|
||||||
|
</command>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
var iq stanza.IQ
|
||||||
|
err := xml.Unmarshal([]byte(response), &iq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not parse IQ")
|
||||||
|
}
|
||||||
|
|
||||||
|
fields, err := iq.GetFormFields()
|
||||||
|
if len(fields) != 2 {
|
||||||
|
t.Fatalf("could not correctly parse fields. Expected 2, found : %v", len(fields))
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPubSubOwnerPayload(response string) (*stanza.PubSubOwner, error) {
|
||||||
|
var respIQ stanza.IQ
|
||||||
|
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return &stanza.PubSubOwner{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := respIQ.Payload.(*stanza.PubSubOwner)
|
||||||
|
if !ok {
|
||||||
|
errors.New("this iq payload is not a pubsub of the owner namespace")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pubsub, nil
|
||||||
|
}
|
921
stanza/pubsub_test.go
Normal file
921
stanza/pubsub_test.go
Normal file
@ -0,0 +1,921 @@
|
|||||||
|
package stanza_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"gosrc.io/xmpp/stanza"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
var submitFormExample = stanza.NewForm([]stanza.Field{
|
||||||
|
{Var: "FORM_TYPE", Type: stanza.FieldTypeHidden, ValuesList: []string{"http://jabber.org/protocol/pubsub#node_config"}},
|
||||||
|
{Var: "pubsub#title", ValuesList: []string{"Princely Musings (Atom)"}},
|
||||||
|
{Var: "pubsub#deliver_notifications", ValuesList: []string{"1"}},
|
||||||
|
{Var: "pubsub#access_model", ValuesList: []string{"roster"}},
|
||||||
|
{Var: "pubsub#roster_groups_allowed", ValuesList: []string{"friends", "servants", "courtiers"}},
|
||||||
|
{Var: "pubsub#type", ValuesList: []string{"http://www.w3.org/2005/Atom"}},
|
||||||
|
{
|
||||||
|
Var: "pubsub#notification_type",
|
||||||
|
Type: "list-single",
|
||||||
|
Label: "Specify the delivery style for event notifications",
|
||||||
|
ValuesList: []string{"headline"},
|
||||||
|
Options: []stanza.Option{
|
||||||
|
{ValuesList: []string{"normal"}},
|
||||||
|
{ValuesList: []string{"headline"}},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, stanza.FormTypeSubmit)
|
||||||
|
|
||||||
|
// ***********************************
|
||||||
|
// * 6.1 Subscribe to a Node
|
||||||
|
// ***********************************
|
||||||
|
|
||||||
|
func TestNewSubRequest(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\"id=\"sub1\"to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <subscribe node=\"princely_musings\"jid=\"francisco@denmark.lit\"></subscribe>" +
|
||||||
|
" </pubsub> </iq>"
|
||||||
|
|
||||||
|
subInfo := stanza.SubInfo{
|
||||||
|
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||||
|
}
|
||||||
|
subR, err := stanza.NewSubRq("pubsub.shakespeare.lit", subInfo)
|
||||||
|
subR.Id = "sub1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create a sub request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewSubResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit/barracks" id="sub1">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
|
<subscription node="princely_musings" jid="francisco@denmark.lit"
|
||||||
|
subid="ba49252aaa4f5d320c24d3766f0bdcade78c78d3" subscription="subscribed"/>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
|
||||||
|
pubsub, err := getPubSubGenericPayload(response)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubsub.Subscription == nil {
|
||||||
|
t.Fatalf("subscription node is nil")
|
||||||
|
}
|
||||||
|
if pubsub.Subscription.Node == "" ||
|
||||||
|
pubsub.Subscription.Jid == "" ||
|
||||||
|
pubsub.Subscription.SubId == nil ||
|
||||||
|
pubsub.Subscription.SubStatus == "" {
|
||||||
|
t.Fatalf("one or more of the subscription attributes was not successfully decoded")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***********************************
|
||||||
|
// * 6.2 Unsubscribe from a Node
|
||||||
|
// ***********************************
|
||||||
|
|
||||||
|
func TestNewUnSubRequest(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\"id=\"unsub1\"to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> " +
|
||||||
|
"<unsubscribe node=\"princely_musings\"jid=\"francisco@denmark.lit\"></unsubscribe> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subInfo := stanza.SubInfo{
|
||||||
|
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||||
|
}
|
||||||
|
subR, err := stanza.NewUnsubRq("pubsub.shakespeare.lit", subInfo)
|
||||||
|
subR.Id = "unsub1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create a sub request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
if pubsub.Unsubscribe == nil {
|
||||||
|
t.Fatalf("Unsubscribe tag should be present in sub config options request")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewUnsubResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit/barracks" id="unsub1">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
|
<subscription node="princely_musings" jid="francisco@denmark.lit" subscription="none"
|
||||||
|
subid="ba49252aaa4f5d320c24d3766f0bdcade78c78d3"/>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
|
||||||
|
pubsub, err := getPubSubGenericPayload(response)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubsub.Subscription == nil {
|
||||||
|
t.Fatalf("subscription node is nil")
|
||||||
|
}
|
||||||
|
if pubsub.Subscription.Node == "" ||
|
||||||
|
pubsub.Subscription.Jid == "" ||
|
||||||
|
pubsub.Subscription.SubId == nil ||
|
||||||
|
pubsub.Subscription.SubStatus == "" {
|
||||||
|
t.Fatalf("one or more of the subscription attributes was not successfully decoded")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************
|
||||||
|
// * 6.3 Configure Subscription Options
|
||||||
|
// ***************************************
|
||||||
|
func TestNewSubOptsRq(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"get\"id=\"options1\"to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> " +
|
||||||
|
"<options node=\"princely_musings\" jid=\"francisco@denmark.lit\"></options> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subInfo := stanza.SubInfo{
|
||||||
|
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||||
|
}
|
||||||
|
subR, err := stanza.NewSubOptsRq("pubsub.shakespeare.lit", subInfo)
|
||||||
|
subR.Id = "options1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create a sub request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
if pubsub.SubOptions == nil {
|
||||||
|
t.Fatalf("Options tag should be present in sub config options request")
|
||||||
|
}
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewNewConfOptsRsp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit/barracks" id="options1">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
|
<options node="princely_musings" jid="francisco@denmark.lit">
|
||||||
|
<x xmlns="jabber:x:data" type="form">
|
||||||
|
<field var="FORM_TYPE" type="hidden">
|
||||||
|
<value>http://jabber.org/protocol/pubsub#subscribe_options</value>
|
||||||
|
</field>
|
||||||
|
<field var="pubsub#deliver" type="boolean" label="Enable delivery?">
|
||||||
|
<value>1</value>
|
||||||
|
</field>
|
||||||
|
<field var="pubsub#digest" type="boolean"
|
||||||
|
label="Receive digest notifications (approx. one per day)?">
|
||||||
|
<value>0</value>
|
||||||
|
</field>
|
||||||
|
<field var="pubsub#include_body" type="boolean"
|
||||||
|
label="Receive message body in addition to payload?">
|
||||||
|
<value>false</value>
|
||||||
|
</field>
|
||||||
|
<field var="pubsub#show-values" type="list-multi"
|
||||||
|
label="Select the presence types which are
|
||||||
|
allowed to receive event notifications">
|
||||||
|
<option label="Want to Chat">
|
||||||
|
<value>chat</value>
|
||||||
|
</option>
|
||||||
|
<option label="Available">
|
||||||
|
<value>online</value>
|
||||||
|
</option>
|
||||||
|
<option label="Away">
|
||||||
|
<value>away</value>
|
||||||
|
</option>
|
||||||
|
<option label="Extended Away">
|
||||||
|
<value>xa</value>
|
||||||
|
</option>
|
||||||
|
<option label="Do Not Disturb">
|
||||||
|
<value>dnd</value>
|
||||||
|
</option>
|
||||||
|
<value>chat</value>
|
||||||
|
<value>online</value>
|
||||||
|
</field>
|
||||||
|
</x>
|
||||||
|
</options>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
|
||||||
|
pubsub, err := getPubSubGenericPayload(response)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubsub.SubOptions == nil {
|
||||||
|
t.Fatalf("sub options node is nil")
|
||||||
|
}
|
||||||
|
if pubsub.SubOptions.Form == nil {
|
||||||
|
t.Fatalf("the response form is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pubsub.SubOptions.Form.Fields) != 5 {
|
||||||
|
t.Fatalf("one or more fields in the response form could not be parsed correctly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************
|
||||||
|
// * 6.3.5 Form Submission
|
||||||
|
// ***************************************
|
||||||
|
func TestNewFormSubmission(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\" id=\"options2\" to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <options node=\"princely_musings\" jid=\"francisco@denmark.lit\"> " +
|
||||||
|
"<x xmlns=\"jabber:x:data\" type=\"submit\"> <field var=\"FORM_TYPE\" type=\"hidden\">" +
|
||||||
|
" <value>http://jabber.org/protocol/pubsub#node_config</value> </field> <field var=\"pubsub#title\"> " +
|
||||||
|
"<value>Princely Musings (Atom)</value> </field> <field var=\"pubsub#deliver_notifications\"> " +
|
||||||
|
"<value>1</value> </field> <field var=\"pubsub#access_model\"> <value>roster</value> </field> " +
|
||||||
|
"<field var=\"pubsub#roster_groups_allowed\"> <value>friends</value> <value>servants</value>" +
|
||||||
|
" <value>courtiers</value> </field> <field var=\"pubsub#type\"> <value>http://www.w3.org/2005/Atom</value> " +
|
||||||
|
"</field> <field var=\"pubsub#notification_type\" type=\"list-single\"label=\"Specify the delivery style for event notifications\"> " +
|
||||||
|
"<value>headline</value> <option> <value>normal</value> </option> <option> <value>headline</value> </option> " +
|
||||||
|
"</field> </x> </options> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subInfo := stanza.SubInfo{
|
||||||
|
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||||
|
}
|
||||||
|
|
||||||
|
subR, err := stanza.NewFormSubmission("pubsub.shakespeare.lit", subInfo, submitFormExample)
|
||||||
|
subR.Id = "options2"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create a sub request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
if pubsub.SubOptions == nil {
|
||||||
|
t.Fatalf("Options tag should be present in sub config options request")
|
||||||
|
}
|
||||||
|
if pubsub.SubOptions.Form == nil {
|
||||||
|
t.Fatalf("No form in form submit request !")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************
|
||||||
|
// * 6.3.7 Subscribe and Configure
|
||||||
|
// ***************************************
|
||||||
|
|
||||||
|
func TestNewSubAndConfig(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\"id=\"sub1\"to=\"pubsub.shakespeare.lit\">" +
|
||||||
|
" <pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <subscribe node=\"princely_musings\" jid=\"francisco@denmark.lit\"> " +
|
||||||
|
"</subscribe>" +
|
||||||
|
"<options> <x xmlns=\"jabber:x:data\" type=\"submit\"> <field var=\"FORM_TYPE\" type=\"hidden\">" +
|
||||||
|
" <value>http://jabber.org/protocol/pubsub#node_config</value> </field> <field var=\"pubsub#title\"> " +
|
||||||
|
"<value>Princely Musings (Atom)</value> </field> <field var=\"pubsub#deliver_notifications\"> " +
|
||||||
|
"<value>1</value> </field> <field var=\"pubsub#access_model\"> <value>roster</value> </field> " +
|
||||||
|
"<field var=\"pubsub#roster_groups_allowed\"> <value>friends</value> <value>servants</value>" +
|
||||||
|
" <value>courtiers</value> </field> <field var=\"pubsub#type\"> <value>http://www.w3.org/2005/Atom</value> " +
|
||||||
|
"</field> <field var=\"pubsub#notification_type\" type=\"list-single\"label=\"Specify the delivery style for event notifications\"> " +
|
||||||
|
"<value>headline</value> <option> <value>normal</value> </option> <option> <value>headline</value> </option> " +
|
||||||
|
"</field> </x> </options> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subInfo := stanza.SubInfo{
|
||||||
|
Node: "princely_musings", Jid: "francisco@denmark.lit",
|
||||||
|
}
|
||||||
|
|
||||||
|
subR, err := stanza.NewSubAndConfig("pubsub.shakespeare.lit", subInfo, submitFormExample)
|
||||||
|
subR.Id = "sub1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create a sub request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
if pubsub.SubOptions == nil {
|
||||||
|
t.Fatalf("Options tag should be present in sub config options request")
|
||||||
|
}
|
||||||
|
if pubsub.SubOptions.Form == nil {
|
||||||
|
t.Fatalf("No form in form submit request !")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The <options/> element MUST NOT possess a 'node' attribute or 'jid' attribute
|
||||||
|
// See XEP-0060
|
||||||
|
if pubsub.SubOptions.SubInfo.Node != "" || pubsub.SubOptions.SubInfo.Jid != "" {
|
||||||
|
t.Fatalf("SubInfo node and jid should be empty for the options tag !")
|
||||||
|
}
|
||||||
|
if pubsub.Subscribe.Node == "" || pubsub.Subscribe.Jid == "" {
|
||||||
|
t.Fatalf("SubInfo node and jid should NOT be empty for the subscribe tag !")
|
||||||
|
}
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewSubAndConfigResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit/barracks" id="sub1">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
|
<subscription node="princely_musings" jid="francisco@denmark.lit"
|
||||||
|
subid="ba49252aaa4f5d320c24d3766f0bdcade78c78d3" subscription="subscribed"/>
|
||||||
|
<options>
|
||||||
|
<x xmlns="jabber:x:data" type="result">
|
||||||
|
<field var="FORM_TYPE" type="hidden">
|
||||||
|
<value>http://jabber.org/protocol/pubsub#subscribe_options</value>
|
||||||
|
</field>
|
||||||
|
<field var="pubsub#deliver">
|
||||||
|
<value>1</value>
|
||||||
|
</field>
|
||||||
|
<field var="pubsub#digest">
|
||||||
|
<value>0</value>
|
||||||
|
</field>
|
||||||
|
<field var="pubsub#include_body">
|
||||||
|
<value>false</value>
|
||||||
|
</field>
|
||||||
|
<field var="pubsub#show-values">
|
||||||
|
<value>chat</value>
|
||||||
|
<value>online</value>
|
||||||
|
<value>away</value>
|
||||||
|
</field>
|
||||||
|
</x>
|
||||||
|
</options>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
|
||||||
|
`
|
||||||
|
|
||||||
|
pubsub, err := getPubSubGenericPayload(response)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
if pubsub.Subscription == nil {
|
||||||
|
t.Fatalf("sub node is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubsub.SubOptions == nil {
|
||||||
|
t.Fatalf("sub options node is nil")
|
||||||
|
}
|
||||||
|
if pubsub.SubOptions.Form == nil {
|
||||||
|
t.Fatalf("the response form is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pubsub.SubOptions.Form.Fields) != 5 {
|
||||||
|
t.Fatalf("one or more fields in the response form could not be parsed correctly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************
|
||||||
|
// * 6.5.2 Requesting All List
|
||||||
|
// ***************************************
|
||||||
|
func TestNewItemsRequest(t *testing.T) {
|
||||||
|
subR, err := stanza.NewItemsRequest("pubsub.shakespeare.lit", "princely_musings", 0)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create an items request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
if pubsub.Items == nil {
|
||||||
|
t.Fatalf("List tag should be present to request items from a service")
|
||||||
|
}
|
||||||
|
if len(pubsub.Items.List) != 0 {
|
||||||
|
t.Fatalf("There should be no items in the <items> tag to request all items from a service")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
func TestNewItemsResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit/barracks" id="items2">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
|
<items node="princely_musings">
|
||||||
|
<item id="4e30f35051b7b8b42abe083742187228">
|
||||||
|
<entry xmlns="http://www.w3.org/2005/Atom">
|
||||||
|
<title>Alone</title>
|
||||||
|
<summary> Now I am alone. O, what a rogue and peasant slave am I! </summary>
|
||||||
|
<link rel="alternate" type="text/html"
|
||||||
|
href="http://denmark.lit/2003/12/13/atom03"/>
|
||||||
|
<id>tag:denmark.lit,2003:entry-32396</id>
|
||||||
|
<published>2003-12-13T11:09:53Z</published>
|
||||||
|
<updated>2003-12-13T11:09:53Z</updated>
|
||||||
|
</entry>
|
||||||
|
</item>
|
||||||
|
<item id="ae890ac52d0df67ed7cfdf51b644e901">
|
||||||
|
<entry xmlns="http://www.w3.org/2005/Atom">
|
||||||
|
<title>Soliloquy</title>
|
||||||
|
<summary> To be, or not to be: that is the question: Whether 'tis nobler in the
|
||||||
|
mind to suffer The slings and arrows of outrageous fortune, Or to take arms
|
||||||
|
against a sea of troubles, And by opposing end them? </summary>
|
||||||
|
<link rel="alternate" type="text/html"
|
||||||
|
href="http://denmark.lit/2003/12/13/atom03"/>
|
||||||
|
<id>tag:denmark.lit,2003:entry-32397</id>
|
||||||
|
<published>2003-12-13T18:30:02Z</published>
|
||||||
|
<updated>2003-12-13T18:30:02Z</updated>
|
||||||
|
</entry>
|
||||||
|
</item>
|
||||||
|
</items>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
|
||||||
|
pubsub, err := getPubSubGenericPayload(response)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
if pubsub.Items == nil {
|
||||||
|
t.Fatalf("sub options node is nil")
|
||||||
|
}
|
||||||
|
if pubsub.Items.List == nil {
|
||||||
|
t.Fatalf("the response form is nil")
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(pubsub.Items.List) != 2 {
|
||||||
|
t.Fatalf("one or more items in the response could not be parsed correctly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************
|
||||||
|
// * 6.5.8 Requesting a Particular Item
|
||||||
|
// ***************************************
|
||||||
|
func TestNewSpecificItemRequest(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"get\" id=\"items3\"to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <items node=\"princely_musings\"> " +
|
||||||
|
"<item id=\"ae890ac52d0df67ed7cfdf51b644e901\"></item> </items> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewSpecificItemRequest("pubsub.shakespeare.lit", "princely_musings", "ae890ac52d0df67ed7cfdf51b644e901")
|
||||||
|
subR.Id = "items3"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create an items request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
if pubsub.Items == nil {
|
||||||
|
t.Fatalf("List tag should be present to request items from a service")
|
||||||
|
}
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************
|
||||||
|
// * 7.1 Publish an Item to a Node
|
||||||
|
// ***************************************
|
||||||
|
func TestNewPublishItemRq(t *testing.T) {
|
||||||
|
item := stanza.Item{
|
||||||
|
XMLName: xml.Name{},
|
||||||
|
Id: "",
|
||||||
|
Publisher: "",
|
||||||
|
Any: &stanza.Node{
|
||||||
|
XMLName: xml.Name{
|
||||||
|
Space: "http://www.w3.org/2005/Atom",
|
||||||
|
Local: "entry",
|
||||||
|
},
|
||||||
|
Attrs: nil,
|
||||||
|
Content: "",
|
||||||
|
Nodes: []stanza.Node{
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Space: "", Local: "title"},
|
||||||
|
Attrs: nil,
|
||||||
|
Content: "My pub item title",
|
||||||
|
Nodes: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Space: "", Local: "summary"},
|
||||||
|
Attrs: nil,
|
||||||
|
Content: "My pub item content summary",
|
||||||
|
Nodes: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Space: "", Local: "link"},
|
||||||
|
Attrs: []xml.Attr{
|
||||||
|
{
|
||||||
|
Name: xml.Name{Space: "", Local: "rel"},
|
||||||
|
Value: "alternate",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: xml.Name{Space: "", Local: "type"},
|
||||||
|
Value: "text/html",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: xml.Name{Space: "", Local: "href"},
|
||||||
|
Value: "http://denmark.lit/2003/12/13/atom03",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Space: "", Local: "id"},
|
||||||
|
Attrs: nil,
|
||||||
|
Content: "My pub item content ID",
|
||||||
|
Nodes: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Space: "", Local: "published"},
|
||||||
|
Attrs: nil,
|
||||||
|
Content: "2003-12-13T18:30:02Z",
|
||||||
|
Nodes: nil,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
XMLName: xml.Name{Space: "", Local: "updated"},
|
||||||
|
Attrs: nil,
|
||||||
|
Content: "2003-12-13T18:30:02Z",
|
||||||
|
Nodes: nil,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
subR, err := stanza.NewPublishItemRq("pubsub.shakespeare.lit", "princely_musings", "bnd81g37d61f49fgn581", item)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create an item pub request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated sub request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(pubsub.Publish.Node) == "" {
|
||||||
|
t.Fatalf("the <publish/> element MUST possess a 'node' attribute, specifying the NodeID of the node.")
|
||||||
|
}
|
||||||
|
if pubsub.Publish.Items[0].Id == "" {
|
||||||
|
t.Fatalf("an id was provided for the item and it should be used")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************
|
||||||
|
// * 7.1.5 Publishing Options
|
||||||
|
// ***************************************
|
||||||
|
|
||||||
|
func TestNewPublishItemOptsRq(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\"id=\"pub1\"to=\"pubsub.shakespeare.lit\"> <pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> " +
|
||||||
|
"<publish node=\"princely_musings\"> <item id=\"ae890ac52d0df67ed7cfdf51b644e901\"> " +
|
||||||
|
"<entry xmlns=\"http://www.w3.org/2005/Atom\"> <title>Soliloquy</title> " +
|
||||||
|
"<summary> To be, or not to be: that is the question: Whether \"tis nobler in the mind to suffer The " +
|
||||||
|
"slings and arrows of outrageous fortune, Or to take arms against a sea of troubles, And by opposing end them? " +
|
||||||
|
"</summary> <link rel=\"alternate\" type=\"text/html\"href=\"http://denmark.lit/2003/12/13/atom03\"></link> " +
|
||||||
|
"<id>tag:denmark.lit,2003:entry-32397</id> <published>2003-12-13T18:30:02Z</published> " +
|
||||||
|
"<updated>2003-12-13T18:30:02Z</updated> </entry> </item> </publish> <publish-options> " +
|
||||||
|
"<x xmlns=\"jabber:x:data\" type=\"submit\"> <field var=\"FORM_TYPE\" type=\"hidden\"> " +
|
||||||
|
"<value>http://jabber.org/protocol/pubsub#publish-options</value> </field> <field var=\"pubsub#access_model\"> " +
|
||||||
|
"<value>presence</value> </field> </x> </publish-options> </pubsub> </iq>"
|
||||||
|
|
||||||
|
var iq stanza.IQ
|
||||||
|
err := xml.Unmarshal([]byte(expectedReq), &iq)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not unmarshal example request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := iq.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
if pubsub.Publish == nil {
|
||||||
|
t.Fatalf("Publish tag is empty")
|
||||||
|
}
|
||||||
|
if len(pubsub.Publish.Items) != 1 {
|
||||||
|
t.Fatalf("could not parse item properly")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************
|
||||||
|
// * 7.2 Delete an Item from a Node
|
||||||
|
// ***************************************
|
||||||
|
|
||||||
|
func TestNewDelItemFromNode(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\"id=\"retract1\"to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <retract node=\"princely_musings\"> " +
|
||||||
|
"<item id=\"ae890ac52d0df67ed7cfdf51b644e901\"></item> </retract> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewDelItemFromNode("pubsub.shakespeare.lit", "princely_musings", "ae890ac52d0df67ed7cfdf51b644e901", nil)
|
||||||
|
subR.Id = "retract1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create a del item request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated del item request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
if pubsub.Retract == nil {
|
||||||
|
t.Fatalf("Retract tag should be present to del an item from a service")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(pubsub.Retract.Items[0].Id) == "" {
|
||||||
|
t.Fatalf("Item id, for the item to delete, should be non empty")
|
||||||
|
}
|
||||||
|
if pubsub.Retract.Items[0].Any != nil {
|
||||||
|
t.Fatalf("Item node must be empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************
|
||||||
|
// * 8.1 Create a Node
|
||||||
|
// ***************************************
|
||||||
|
|
||||||
|
func TestNewCreateNode(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\"id=\"create1\"to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <create node=\"princely_musings\"></create> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewCreateNode("pubsub.shakespeare.lit", "princely_musings")
|
||||||
|
subR.Id = "create1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create a create node request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated del item request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
if pubsub.Create == nil {
|
||||||
|
t.Fatalf("Create tag should be present to create a node on a service")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(pubsub.Create.Node) == "" {
|
||||||
|
t.Fatalf("Expected node name to be present")
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNewCreateNodeResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq type="result" from="pubsub.shakespeare.lit" to="hamlet@denmark.lit/elsinore" id="create2">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
|
<create node="25e3d37dabbab9541f7523321421edc5bfeb2dae"/>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
pubsub, err := getPubSubGenericPayload(response)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
if pubsub.Create == nil {
|
||||||
|
t.Fatalf("create segment is nil")
|
||||||
|
}
|
||||||
|
if pubsub.Create.Node == "" {
|
||||||
|
t.Fatalf("could not parse generated nodeId")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ***************************************
|
||||||
|
// * 8.1.3 Create and Configure a Node
|
||||||
|
// ***************************************
|
||||||
|
|
||||||
|
func TestNewCreateAndConfigNode(t *testing.T) {
|
||||||
|
expectedReq := "<iq type=\"set\" id=\"create1\" to=\"pubsub.shakespeare.lit\" > " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <create node=\"princely_musings\"></create> " +
|
||||||
|
"<configure> <x xmlns=\"jabber:x:data\" type=\"submit\"> <field var=\"FORM_TYPE\" type=\"hidden\" > " +
|
||||||
|
"<value>http://jabber.org/protocol/pubsub#node_config</value> </field> <field var=\"pubsub#notify_retract\"> " +
|
||||||
|
"<value>0</value> </field> <field var=\"pubsub#notify_sub\"> <value>0</value> </field> " +
|
||||||
|
"<field var=\"pubsub#max_payload_size\"> <value>1028</value> </field> </x> </configure> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewCreateAndConfigNode("pubsub.shakespeare.lit",
|
||||||
|
"princely_musings",
|
||||||
|
&stanza.Form{
|
||||||
|
Type: stanza.FormTypeSubmit,
|
||||||
|
Fields: []stanza.Field{
|
||||||
|
{Var: "FORM_TYPE", Type: stanza.FieldTypeHidden, ValuesList: []string{"http://jabber.org/protocol/pubsub#node_config"}},
|
||||||
|
{Var: "pubsub#notify_retract", ValuesList: []string{"0"}},
|
||||||
|
{Var: "pubsub#notify_sub", ValuesList: []string{"0"}},
|
||||||
|
{Var: "pubsub#max_payload_size", ValuesList: []string{"1028"}},
|
||||||
|
},
|
||||||
|
})
|
||||||
|
subR.Id = "create1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create a create node request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated del item request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := subR.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("payload is not a pubsub !")
|
||||||
|
}
|
||||||
|
if pubsub.Create == nil {
|
||||||
|
t.Fatalf("Create tag should be present to create a node on a service")
|
||||||
|
}
|
||||||
|
|
||||||
|
if strings.TrimSpace(pubsub.Create.Node) == "" {
|
||||||
|
t.Fatalf("Expected node name to be present")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubsub.Configure == nil {
|
||||||
|
t.Fatalf("Configure tag should be present to configure a node during its creation on a service")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubsub.Configure.Form == nil {
|
||||||
|
t.Fatalf("Expected a form to be present, to configure the node")
|
||||||
|
}
|
||||||
|
if len(pubsub.Configure.Form.Fields) != 4 {
|
||||||
|
t.Fatalf("Expected 4 elements to be present in the config form but got : %v", len(pubsub.Configure.Form.Fields))
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expectedReq, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// ********************************
|
||||||
|
// * 5.7 Retrieve Subscriptions
|
||||||
|
// ********************************
|
||||||
|
|
||||||
|
func TestNewRetrieveAllSubsRequest(t *testing.T) {
|
||||||
|
expected := "<iq type=\"get\" id=\"subscriptions1\" to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <subscriptions></subscriptions> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewRetrieveAllSubsRequest("pubsub.shakespeare.lit")
|
||||||
|
subR.Id = "subscriptions1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create a create node request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated del item request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expected, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetrieveAllSubsResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit" id="subscriptions1">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
|
<subscriptions>
|
||||||
|
<subscription node="node1" jid="francisco@denmark.lit" subscription="subscribed"/>
|
||||||
|
<subscription node="node2" jid="francisco@denmark.lit" subscription="subscribed"/>
|
||||||
|
<subscription node="node5" jid="francisco@denmark.lit" subscription="unconfigured"/>
|
||||||
|
<subscription node="node6" jid="francisco@denmark.lit" subscription="subscribed"
|
||||||
|
subid="123-abc"/>
|
||||||
|
<subscription node="node6" jid="francisco@denmark.lit" subscription="subscribed"
|
||||||
|
subid="004-yyy"/>
|
||||||
|
</subscriptions>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
var respIQ stanza.IQ
|
||||||
|
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not unmarshal response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := respIQ.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("umarshalled payload is not a pubsub")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubsub.Subscriptions == nil {
|
||||||
|
t.Fatalf("subscriptions node is nil")
|
||||||
|
}
|
||||||
|
if len(pubsub.Subscriptions.List) != 5 {
|
||||||
|
t.Fatalf("incorrect number of decoded subscriptions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ********************************
|
||||||
|
// * 5.7 Retrieve Affiliations
|
||||||
|
// ********************************
|
||||||
|
|
||||||
|
func TestNewRetrieveAllAffilsRequest(t *testing.T) {
|
||||||
|
expected := "<iq type=\"get\"id=\"affil1\"to=\"pubsub.shakespeare.lit\"> " +
|
||||||
|
"<pubsub xmlns=\"http://jabber.org/protocol/pubsub\"> <affiliations></affiliations> </pubsub> </iq>"
|
||||||
|
|
||||||
|
subR, err := stanza.NewRetrieveAllAffilsRequest("pubsub.shakespeare.lit")
|
||||||
|
subR.Id = "affil1"
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("Could not create retreive all affiliations request : %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
if _, e := checkMarshalling(t, subR); e != nil {
|
||||||
|
t.Fatalf("Failed to check marshalling for generated retreive all affiliations request : %s", e)
|
||||||
|
}
|
||||||
|
|
||||||
|
data, err := xml.Marshal(subR)
|
||||||
|
if err := compareMarshal(expected, string(data)); err != nil {
|
||||||
|
t.Fatalf(err.Error())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRetrieveAllAffilsResp(t *testing.T) {
|
||||||
|
response := `
|
||||||
|
<iq type="result" from="pubsub.shakespeare.lit" to="francisco@denmark.lit" id="affil1">
|
||||||
|
<pubsub xmlns="http://jabber.org/protocol/pubsub">
|
||||||
|
<affiliations>
|
||||||
|
<affiliation node="node1" affiliation="owner"/>
|
||||||
|
<affiliation node="node2" affiliation="publisher"/>
|
||||||
|
<affiliation node="node5" affiliation="outcast"/>
|
||||||
|
<affiliation node="node6" affiliation="owner"/>
|
||||||
|
</affiliations>
|
||||||
|
</pubsub>
|
||||||
|
</iq>
|
||||||
|
`
|
||||||
|
var respIQ stanza.IQ
|
||||||
|
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("could not unmarshal response: %s", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := respIQ.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
t.Fatalf("umarshalled payload is not a pubsub")
|
||||||
|
}
|
||||||
|
|
||||||
|
if pubsub.Affiliations == nil {
|
||||||
|
t.Fatalf("subscriptions node is nil")
|
||||||
|
}
|
||||||
|
if len(pubsub.Affiliations.List) != 4 {
|
||||||
|
t.Fatalf("incorrect number of decoded subscriptions")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func getPubSubGenericPayload(response string) (*stanza.PubSubGeneric, error) {
|
||||||
|
var respIQ stanza.IQ
|
||||||
|
err := xml.Unmarshal([]byte(response), &respIQ)
|
||||||
|
|
||||||
|
if err != nil {
|
||||||
|
return &stanza.PubSubGeneric{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
pubsub, ok := respIQ.Payload.(*stanza.PubSubGeneric)
|
||||||
|
if !ok {
|
||||||
|
errors.New("this iq payload is not a pubsub")
|
||||||
|
}
|
||||||
|
|
||||||
|
return pubsub, nil
|
||||||
|
}
|
@ -2,12 +2,17 @@ package stanza_test
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
|
"regexp"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/go-cmp/cmp"
|
"github.com/google/go-cmp/cmp"
|
||||||
"gosrc.io/xmpp/stanza"
|
"gosrc.io/xmpp/stanza"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
var reLeadcloseWhtsp = regexp.MustCompile(`^[\s\p{Zs}]+|[\s\p{Zs}]+$`)
|
||||||
|
var reInsideWhtsp = regexp.MustCompile(`[\s\p{Zs}]`)
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Marshaller / unmarshaller test
|
// Marshaller / unmarshaller test
|
||||||
|
|
||||||
@ -63,3 +68,14 @@ func xmlOpts() cmp.Options {
|
|||||||
}
|
}
|
||||||
return opts
|
return opts
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func delSpaces(s string) string {
|
||||||
|
return reInsideWhtsp.ReplaceAllString(reLeadcloseWhtsp.ReplaceAllString(s, ""), "")
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareMarshal(expected, data string) error {
|
||||||
|
if delSpaces(expected) != delSpaces(data) {
|
||||||
|
return errors.New("failed to verify unmarshal->marshal. Expected :" + expected + "\ngot: " + data)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
@ -280,7 +280,7 @@ func bind(t *testing.T, sc *ServerConn) {
|
|||||||
<jid>%s</jid>
|
<jid>%s</jid>
|
||||||
</bind>
|
</bind>
|
||||||
</iq>`
|
</iq>`
|
||||||
fmt.Fprintf(sc.connection, result, iq.Id, "test@localhost/test") // TODO use real JID
|
fmt.Fprintf(sc.connection, result, iq.Id, "test@localhost/test") // TODO use real Jid
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user