From d72a0f31544f229f8df92a666fa2e85da9d37e68 Mon Sep 17 00:00:00 2001 From: PapaTutuWawa Date: Thu, 2 Mar 2023 05:23:29 +0100 Subject: [PATCH] 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 --- xmpp.go | 110 +++++++++++++++++++++++++------------- xmpp_disco.go | 99 ++++++++++++++++++++++++++++++++++ xmpp_information_query.go | 20 ++++++- 3 files changed, 189 insertions(+), 40 deletions(-) create mode 100644 xmpp_disco.go diff --git a/xmpp.go b/xmpp.go index 18b8aa5..b7000cc 100644 --- a/xmpp.go +++ b/xmpp.go @@ -733,50 +733,81 @@ func (c *Client) Recv() (stanza interface{}, err error) { return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type, Query: res}, nil } - case v.Type == "result" && v.ID == "unsub1": - // 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": + case v.Type == "result": switch v.ID { case "sub1": - // Subscription or unsubscription was successful - var sub clientPubsubSubscription - err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub) - if err != nil { - return PubsubSubscription{}, err - } + if v.Query.XMLName.Local == "pubsub" { + // Subscription or unsubscription was successful + var sub clientPubsubSubscription + err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub) + if err != nil { + return PubsubSubscription{}, err + } - return PubsubSubscription{ - SubID: sub.SubID, - JID: sub.JID, - Node: sub.Node, - Errors: nil, - }, nil + return PubsubSubscription{ + SubID: sub.SubID, + JID: sub.JID, + Node: sub.Node, + Errors: nil, + }, nil + } case "unsub1": - var sub clientPubsubSubscription - err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub) - if err != nil { - return PubsubUnsubscription{}, err - } + if v.Query.XMLName.Local == "pubsub" { + var sub clientPubsubSubscription + err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub) + if err != nil { + return PubsubUnsubscription{}, err + } - return PubsubUnsubscription{ - SubID: sub.SubID, - JID: v.From, - Node: sub.Node, - Errors: nil, - }, nil - case "items1", "items3": - var p clientPubsubItems - err := xml.Unmarshal([]byte(v.Query.InnerXML), &p) - if err != nil { - return PubsubItems{}, err + return PubsubUnsubscription{ + SubID: sub.SubID, + JID: v.From, + Node: sub.Node, + Errors: 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": + if v.Query.XMLName.Local == "pubsub" { + var p clientPubsubItems + err := xml.Unmarshal([]byte(v.Query.InnerXML), &p) + if err != nil { + return PubsubItems{}, err + } switch p.Node { case XMPPNS_AVATAR_PEP_DATA: @@ -800,6 +831,7 @@ func (c *Client) Recv() (stanza interface{}, err error) { pubsubItemsToReturn(p.Items), }, nil } + // Note: XEP-0084 states that metadata and data // should be fetched with an id of retrieve1. // Since we already have PubSub implemented, we @@ -1072,6 +1104,8 @@ type clientIQ struct { Query XMLElement `xml:",any"` Error clientError Bind bindBind + + InnerXML []byte `xml:",innerxml"` } type clientError struct { diff --git a/xmpp_disco.go b/xmpp_disco.go new file mode 100644 index 0000000..0bca664 --- /dev/null +++ b/xmpp_disco.go @@ -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 +} diff --git a/xmpp_information_query.go b/xmpp_information_query.go index 0154ec1..01ff089 100644 --- a/xmpp_information_query.go +++ b/xmpp_information_query.go @@ -10,10 +10,26 @@ const IQTypeSet = "set" const IQTypeResult = "result" func (c *Client) Discovery() (string, error) { - const namespace = "http://jabber.org/protocol/disco#items" // use getCookie for a pseudo random id. 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("", 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("", XMPPNS_DISCO_ITEMS) + return c.RawInformation(c.jid, jid, "info1", IQTypeGet, query) } // RawInformationQuery sends an information query request to the server.