Implement Disco queries against other entities (#124)

* Improve support for XEP-0030

This commit allows the user to query information about the server
or a node belonging to the server as per XEP-0030.

* Fix broken PubSub functionality
This commit is contained in:
PapaTutuWawa 2023-03-02 05:23:29 +01:00 committed by GitHub
parent 9fc0b1236c
commit d72a0f3154
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 189 additions and 40 deletions

54
xmpp.go
View File

@ -733,18 +733,10 @@ func (c *Client) Recv() (stanza interface{}, err error) {
return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type,
Query: res}, nil Query: res}, nil
} }
case v.Type == "result" && v.ID == "unsub1": case v.Type == "result":
// Unsubscribing MAY contain a pubsub element. But it does
// not have to
return PubsubUnsubscription{
SubID: "",
JID: v.From,
Node: "",
Errors: nil,
}, nil
case v.Query.XMLName.Local == "pubsub":
switch v.ID { switch v.ID {
case "sub1": case "sub1":
if v.Query.XMLName.Local == "pubsub" {
// Subscription or unsubscription was successful // Subscription or unsubscription was successful
var sub clientPubsubSubscription var sub clientPubsubSubscription
err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub) err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub)
@ -758,7 +750,9 @@ func (c *Client) Recv() (stanza interface{}, err error) {
Node: sub.Node, Node: sub.Node,
Errors: nil, Errors: nil,
}, nil }, nil
}
case "unsub1": case "unsub1":
if v.Query.XMLName.Local == "pubsub" {
var sub clientPubsubSubscription var sub clientPubsubSubscription
err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub) err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub)
if err != nil { if err != nil {
@ -771,7 +765,44 @@ func (c *Client) Recv() (stanza interface{}, err error) {
Node: sub.Node, Node: sub.Node,
Errors: nil, Errors: nil,
}, nil }, nil
} else {
// Unsubscribing MAY contain a pubsub element. But it does
// not have to
return PubsubUnsubscription{
SubID: "",
JID: v.From,
Node: "",
Errors: nil,
}, nil
}
case "info1":
if v.Query.XMLName.Space == XMPPNS_DISCO_ITEMS {
var itemsQuery clientDiscoItemsQuery
err := xml.Unmarshal(v.InnerXML, &itemsQuery)
if err != nil {
return []DiscoItem{}, err
}
return DiscoItems{
Jid: v.From,
Items: clientDiscoItemsToReturn(itemsQuery.Items),
}, nil
}
case "info3":
if v.Query.XMLName.Space == XMPPNS_DISCO_INFO {
var disco clientDiscoQuery
err := xml.Unmarshal(v.InnerXML, &disco)
if err != nil {
return DiscoResult{}, err
}
return DiscoResult{
Features: clientFeaturesToReturn(disco.Features),
Identities: clientIdentitiesToReturn(disco.Identities),
}, nil
}
case "items1", "items3": case "items1", "items3":
if v.Query.XMLName.Local == "pubsub" {
var p clientPubsubItems var p clientPubsubItems
err := xml.Unmarshal([]byte(v.Query.InnerXML), &p) err := xml.Unmarshal([]byte(v.Query.InnerXML), &p)
if err != nil { if err != nil {
@ -800,6 +831,7 @@ func (c *Client) Recv() (stanza interface{}, err error) {
pubsubItemsToReturn(p.Items), pubsubItemsToReturn(p.Items),
}, nil }, nil
} }
// Note: XEP-0084 states that metadata and data // Note: XEP-0084 states that metadata and data
// should be fetched with an id of retrieve1. // should be fetched with an id of retrieve1.
// Since we already have PubSub implemented, we // Since we already have PubSub implemented, we
@ -1072,6 +1104,8 @@ type clientIQ struct {
Query XMLElement `xml:",any"` Query XMLElement `xml:",any"`
Error clientError Error clientError
Bind bindBind Bind bindBind
InnerXML []byte `xml:",innerxml"`
} }
type clientError struct { type clientError struct {

99
xmpp_disco.go Normal file
View File

@ -0,0 +1,99 @@
package xmpp
import (
"encoding/xml"
)
const (
XMPPNS_DISCO_ITEMS = "http://jabber.org/protocol/disco#items"
XMPPNS_DISCO_INFO = "http://jabber.org/protocol/disco#info"
)
type clientDiscoFeature struct {
XMLName xml.Name `xml:"feature"`
Var string `xml:"var,attr"`
}
type clientDiscoIdentity struct {
XMLName xml.Name `xml:"identity"`
Category string `xml:"category,attr"`
Type string `xml:"type,attr"`
Name string `xml:"name,attr"`
}
type clientDiscoQuery struct {
XMLName xml.Name `xml:"query"`
Features []clientDiscoFeature `xml:"feature"`
Identities []clientDiscoIdentity `xml:"identity"`
}
type clientDiscoItem struct {
XMLName xml.Name `xml:"item"`
Jid string `xml:"jid,attr"`
Node string `xml:"node,attr"`
Name string `xml:"name,attr"`
}
type clientDiscoItemsQuery struct {
XMLName xml.Name `xml:"query"`
Items []clientDiscoItem `xml:"item"`
}
type DiscoIdentity struct {
Category string
Type string
Name string
}
type DiscoItem struct {
Jid string
Name string
Node string
}
type DiscoResult struct {
Features []string
Identities []DiscoIdentity
}
type DiscoItems struct {
Jid string
Items []DiscoItem
}
func clientFeaturesToReturn(features []clientDiscoFeature) []string {
var ret []string
for _, feature := range features {
ret = append(ret, feature.Var)
}
return ret
}
func clientIdentitiesToReturn(identities []clientDiscoIdentity) []DiscoIdentity {
var ret []DiscoIdentity
for _, id := range identities {
ret = append(ret, DiscoIdentity{
Category: id.Category,
Type: id.Type,
Name: id.Name,
})
}
return ret
}
func clientDiscoItemsToReturn(items []clientDiscoItem) []DiscoItem {
var ret []DiscoItem
for _, item := range items {
ret = append(ret, DiscoItem{
Jid: item.Jid,
Name: item.Name,
Node: item.Node,
})
}
return ret
}

View File

@ -10,10 +10,26 @@ const IQTypeSet = "set"
const IQTypeResult = "result" const IQTypeResult = "result"
func (c *Client) Discovery() (string, error) { func (c *Client) Discovery() (string, error) {
const namespace = "http://jabber.org/protocol/disco#items"
// use getCookie for a pseudo random id. // use getCookie for a pseudo random id.
reqID := strconv.FormatUint(uint64(getCookie()), 10) reqID := strconv.FormatUint(uint64(getCookie()), 10)
return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, namespace, "") return c.RawInformationQuery(c.jid, c.domain, reqID, IQTypeGet, XMPPNS_DISCO_ITEMS, "")
}
// Discover information about a node
func (c *Client) DiscoverNodeInfo(node string) (string, error) {
query := fmt.Sprintf("<query xmlns='%s' node='%s'/>", XMPPNS_DISCO_INFO, node)
return c.RawInformation(c.jid, c.domain, "info3", IQTypeGet, query)
}
// Discover items that the server exposes
func (c *Client) DiscoverServerItems() (string, error) {
return c.DiscoverEntityItems(c.domain)
}
// Discover items that an entity exposes
func (c *Client) DiscoverEntityItems(jid string) (string, error) {
query := fmt.Sprintf("<query xmlns='%s'/>", XMPPNS_DISCO_ITEMS)
return c.RawInformation(c.jid, jid, "info1", IQTypeGet, query)
} }
// RawInformationQuery sends an information query request to the server. // RawInformationQuery sends an information query request to the server.