From a18807f19e98c1f2cafddc28ab3f838b44b68fa1 Mon Sep 17 00:00:00 2001 From: Wim Date: Sun, 29 Mar 2020 17:35:40 +0200 Subject: Update matterbridge/go-xmpp to add xmpp avatar support (#1070) --- vendor/github.com/matterbridge/go-xmpp/xmpp.go | 54 ++++++++- .../github.com/matterbridge/go-xmpp/xmpp_avatar.go | 125 +++++++++++++++++++++ 2 files changed, 174 insertions(+), 5 deletions(-) create mode 100644 vendor/github.com/matterbridge/go-xmpp/xmpp_avatar.go (limited to 'vendor/github.com/matterbridge/go-xmpp') diff --git a/vendor/github.com/matterbridge/go-xmpp/xmpp.go b/vendor/github.com/matterbridge/go-xmpp/xmpp.go index cc13218e..bb1438e9 100644 --- a/vendor/github.com/matterbridge/go-xmpp/xmpp.go +++ b/vendor/github.com/matterbridge/go-xmpp/xmpp.go @@ -644,7 +644,20 @@ func (c *Client) Recv() (stanza interface{}, err error) { case *clientMessage: if v.Event.XMLNS == XMPPNS_PUBSUB_EVENT { // Handle Pubsub notifications - return pubsubClientToReturn(v.Event), nil + switch v.Event.Items.Node { + case XMPPNS_AVATAR_PEP_METADATA: + return handleAvatarMetadata(v.Event.Items.Items[0].Body, + v.From) + // I am not sure whether this can even happen. + // XEP-0084 only specifies a subscription to + // the metadata node. + /*case XMPPNS_AVATAR_PEP_DATA: + return handleAvatarData(v.Event.Items.Items[0].Body, + v.From, + v.Event.Items.Items[0].ID)*/ + default: + return pubsubClientToReturn(v.Event), nil + } } stamp, _ := time.Parse( @@ -745,10 +758,41 @@ func (c *Client) Recv() (stanza interface{}, err error) { return PubsubItems{}, err } - return PubsubItems{ - p.Node, - pubsubItemsToReturn(p.Items), - }, nil + switch p.Node { + case XMPPNS_AVATAR_PEP_DATA: + return handleAvatarData(p.Items[0].Body, + v.From, + p.Items[0].ID) + case XMPPNS_AVATAR_PEP_METADATA: + return handleAvatarMetadata(p.Items[0].Body, + v.From) + default: + return PubsubItems{ + p.Node, + 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 + // can just use items1 and items3 to do the same + // as an Avatar node is just a PEP (PubSub) node. + /*case "retrieve1": + 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: + return handleAvatarData(p.Items[0].Body, + v.From, + p.Items[0].ID) + case XMPPNS_AVATAR_PEP_METADATA: + return handleAvatarMetadata(p.Items[0].Body, + v + }*/ } case v.Query.XMLName.Local == "": return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type}, nil diff --git a/vendor/github.com/matterbridge/go-xmpp/xmpp_avatar.go b/vendor/github.com/matterbridge/go-xmpp/xmpp_avatar.go new file mode 100644 index 00000000..7303dba5 --- /dev/null +++ b/vendor/github.com/matterbridge/go-xmpp/xmpp_avatar.go @@ -0,0 +1,125 @@ +package xmpp + +import ( + "crypto/sha1" + "encoding/base64" + "encoding/hex" + "encoding/xml" + "errors" + "strconv" +) + +const ( + XMPPNS_AVATAR_PEP_DATA = "urn:xmpp:avatar:data" + XMPPNS_AVATAR_PEP_METADATA = "urn:xmpp:avatar:metadata" +) + +type clientAvatarData struct { + XMLName xml.Name `xml:"data"` + Data []byte `xml:",innerxml"` +} + +type clientAvatarInfo struct { + XMLName xml.Name `xml:"info"` + Bytes string `xml:"bytes,attr"` + Width string `xml:"width,attr"` + Height string `xml:"height,attr"` + ID string `xml:"id,attr"` + Type string `xml:"type,attr"` + URL string `xml:"url,attr"` +} + +type clientAvatarMetadata struct { + XMLName xml.Name `xml:"metadata"` + XMLNS string `xml:"xmlns,attr"` + Info clientAvatarInfo `xml:"info"` +} + +type AvatarData struct { + Data []byte + From string +} + +type AvatarMetadata struct { + From string + Bytes int + Width int + Height int + ID string + Type string + URL string +} + +func handleAvatarData(itemsBody []byte, from, id string) (AvatarData, error) { + var data clientAvatarData + err := xml.Unmarshal(itemsBody, &data) + if err != nil { + return AvatarData{}, err + } + + // Base64-decode the avatar data to check its SHA1 hash + dataRaw, err := base64.StdEncoding.DecodeString( + string(data.Data)) + if err != nil { + return AvatarData{}, err + } + + hash := sha1.Sum(dataRaw) + hashStr := hex.EncodeToString(hash[:]) + if hashStr != id { + return AvatarData{}, errors.New("SHA1 hashes do not match") + } + + return AvatarData{ + Data: dataRaw, + From: from, + }, nil +} + +func handleAvatarMetadata(body []byte, from string) (AvatarMetadata, error) { + var meta clientAvatarMetadata + err := xml.Unmarshal(body, &meta) + if err != nil { + return AvatarMetadata{}, err + } + + return AvatarMetadata{ + From: from, + Bytes: atoiw(meta.Info.Bytes), + Width: atoiw(meta.Info.Width), + Height: atoiw(meta.Info.Height), + ID: meta.Info.ID, + Type: meta.Info.Type, + URL: meta.Info.URL, + }, nil +} + +// A wrapper for atoi which just returns -1 if an error occurs +func atoiw(str string) int { + i, err := strconv.Atoi(str) + if err != nil { + return -1 + } + + return i +} + +func (c *Client) AvatarSubscribeMetadata(jid string) { + c.PubsubSubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid) +} + +func (c *Client) AvatarUnsubscribeMetadata(jid string) { + c.PubsubUnsubscribeNode(XMPPNS_AVATAR_PEP_METADATA, jid) +} + +func (c *Client) AvatarRequestData(jid string) { + c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_DATA, jid) +} + +func (c *Client) AvatarRequestDataByID(jid, id string) { + c.PubsubRequestItem(XMPPNS_AVATAR_PEP_DATA, jid, id) +} + +func (c *Client) AvatarRequestMetadata(jid string) { + c.PubsubRequestLastItems(XMPPNS_AVATAR_PEP_METADATA, jid) +} -- cgit v1.2.3