~samwhited/xmpp

ref: de40e6f9107a5d25ae8c8edad9289fd0c4b12e2f xmpp/disco/info.go -rw-r--r-- 4.4 KiB
de40e6f9Sam Whited roster: add type to IQ response in test 10 months ago
                                                                                
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// Copyright 2021 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.

package disco

import (
	"context"
	"encoding/xml"

	"mellium.im/xmlstream"
	"mellium.im/xmpp"
	"mellium.im/xmpp/form"
	"mellium.im/xmpp/internal/ns"
	"mellium.im/xmpp/jid"
	"mellium.im/xmpp/stanza"
)

// InfoQuery is the payload of a query for a node's identities and features.
type InfoQuery struct {
	XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
	Node    string   `xml:"node,attr,omitempty"`
}

func (q InfoQuery) wrap(r xml.TokenReader) xml.TokenReader {
	start := xml.StartElement{Name: xml.Name{Space: NSInfo, Local: "query"}}
	if q.Node != "" {
		start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "node"}, Value: q.Node})
	}
	return xmlstream.Wrap(r, start)
}

// TokenReader implements xmlstream.Marshaler.
func (q InfoQuery) TokenReader() xml.TokenReader {
	return q.wrap(nil)
}

// WriteXML implements xmlstream.WriterTo.
func (q InfoQuery) WriteXML(w xmlstream.TokenWriter) (int, error) {
	return xmlstream.Copy(w, q.TokenReader())
}

// Identity is the type and category of a node on the network.
// Normally one of the pre-defined Identity types should be used.
type Identity struct {
	XMLName  xml.Name `xml:"http://jabber.org/protocol/disco#info identity"`
	Category string   `xml:"category,attr"`
	Type     string   `xml:"type,attr"`
	Name     string   `xml:"name,attr,omitempty"`
	Lang     string   `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
}

// TokenReader implements xmlstream.Marshaler.
func (i Identity) TokenReader() xml.TokenReader {
	start := xml.StartElement{
		Name: xml.Name{Space: NSInfo, Local: "query"},
		Attr: []xml.Attr{{
			Name:  xml.Name{Local: "category"},
			Value: i.Category,
		}, {
			Name:  xml.Name{Local: "type"},
			Value: i.Type,
		}},
	}
	if i.Name != "" {
		start.Attr = append(start.Attr, xml.Attr{
			Name: xml.Name{Local: "name"}, Value: i.Name,
		})
	}
	if i.Lang != "" {
		start.Attr = append(start.Attr, xml.Attr{
			Name: xml.Name{Space: ns.XML, Local: "lang"}, Value: i.Lang,
		})
	}
	return xmlstream.Wrap(nil, start)
}

// WriteXML implements xmlstream.WriterTo.
func (i Identity) WriteXML(w xmlstream.TokenWriter) (int, error) {
	return xmlstream.Copy(w, i.TokenReader())
}

// Feature represents a feature supported by an entity on the network.
type Feature struct {
	XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info feature"`
	Var     string   `xml:"var,attr"`
}

// TokenReader implements xmlstream.Marshaler.
func (f Feature) TokenReader() xml.TokenReader {
	return xmlstream.Wrap(nil, xml.StartElement{
		Name: xml.Name{Space: NSInfo, Local: "feature"},
		Attr: []xml.Attr{{
			Name:  xml.Name{Local: "var"},
			Value: f.Var,
		}},
	})
}

// WriteXML implements xmlstream.WriterTo.
func (f Feature) WriteXML(w xmlstream.TokenWriter) (int, error) {
	return xmlstream.Copy(w, f.TokenReader())
}

// Info is a response to a disco info query.
type Info struct {
	InfoQuery
	Identity []Identity `xml:"identity"`
	Features []Feature  `xml:"feature"`
	Form     *form.Data `xml:"jabber:x:data x,omitempty"`
}

// TokenReader implements xmlstream.Marshaler.
func (i Info) TokenReader() xml.TokenReader {
	var payloads []xml.TokenReader
	for _, f := range i.Features {
		payloads = append(payloads, f.TokenReader())
	}
	for _, ident := range i.Identity {
		payloads = append(payloads, ident.TokenReader())
	}
	return i.InfoQuery.wrap(xmlstream.MultiReader(payloads...))
}

// WriteXML implements xmlstream.WriterTo.
func (i Info) WriteXML(w xmlstream.TokenWriter) (int, error) {
	return xmlstream.Copy(w, i.TokenReader())
}

// GetInfo discovers a set of features and identities associated with a JID and
// optional node.
// An empty Node means to query the root items for the JID.
// It blocks until a response is received.
func GetInfo(ctx context.Context, node string, to jid.JID, s *xmpp.Session) (Info, error) {
	return GetInfoIQ(ctx, node, stanza.IQ{To: to}, s)
}

// GetInfoIQ is like GetInfo but it allows you to customize the IQ.
// Changing the type of the provided IQ has no effect.
func GetInfoIQ(ctx context.Context, node string, iq stanza.IQ, s *xmpp.Session) (Info, error) {
	if iq.Type != stanza.GetIQ {
		iq.Type = stanza.GetIQ
	}
	query := InfoQuery{
		Node: node,
	}
	var info Info
	err := s.UnmarshalIQElement(ctx, query.TokenReader(), iq, &info)
	return info, err
}