~samwhited/xmpp

ref: 9075f1bc91fd56c102d605d4342a5af004a1f734 xmpp/stanza/iq.go -rw-r--r-- 4.8 KiB
9075f1bcSam Whited xmpp: fix docs showing old API 4 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
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
// Copyright 2016 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 stanza

import (
	"encoding/xml"

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

// IQ ("Information Query") is used as a general request response mechanism.
// IQ's are one-to-one, provide get and set semantics, and always require a
// response in the form of a result or an error.
type IQ struct {
	XMLName xml.Name `xml:"iq"`
	ID      string   `xml:"id,attr"`
	To      jid.JID  `xml:"to,attr,omitempty"`
	From    jid.JID  `xml:"from,attr,omitempty"`
	Lang    string   `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
	Type    IQType   `xml:"type,attr"`
}

// UnmarshalIQError converts the provided XML token into an IQ.
// If the type of the IQ is "error" it unmarshals the entire payload and returns
// the error along with the original IQ.
func UnmarshalIQError(r xml.TokenReader, start xml.StartElement) (IQ, error) {
	iqStart, err := NewIQ(start)
	if err != nil {
		return iqStart, err
	}
	if iqStart.Type != ErrorIQ {
		return iqStart, nil
	}

	iter := xmlstream.NewIter(r)
	for iter.Next() {
		start, p := iter.Current()
		if start.Name.Local != "error" {
			continue
		}

		d := xml.NewTokenDecoder(xmlstream.Wrap(p, *start))
		var stanzaErr Error
		decodeErr := d.Decode(&stanzaErr)
		if decodeErr != nil {
			return iqStart, decodeErr
		}
		return iqStart, stanzaErr
	}
	if err = iter.Err(); err != nil {
		return iqStart, err
	}

	return iqStart, Error{}
}

// NewIQ unmarshals an XML token into a IQ.
func NewIQ(start xml.StartElement) (IQ, error) {
	v := IQ{
		XMLName: start.Name,
	}
	for _, attr := range start.Attr {
		if attr.Name.Local == "lang" && attr.Name.Space == ns.XML {
			v.Lang = attr.Value
			continue
		}
		if attr.Name.Space != "" && attr.Name.Space != start.Name.Space {
			continue
		}

		var err error
		switch attr.Name.Local {
		case "id":
			v.ID = attr.Value
		case "to":
			if attr.Value != "" {
				v.To, err = jid.Parse(attr.Value)
				if err != nil {
					return v, err
				}
			}
		case "from":
			if attr.Value != "" {
				v.From, err = jid.Parse(attr.Value)
				if err != nil {
					return v, err
				}
			}
		case "type":
			v.Type = IQType(attr.Value)
		}
	}
	return v, nil
}

// StartElement converts the IQ into an XML token.
func (iq IQ) StartElement() xml.StartElement {
	// Keep whatever namespace we're already using but make sure the localname is
	// "iq".
	name := iq.XMLName
	name.Local = "iq"

	attr := make([]xml.Attr, 0, 5)
	attr = append(attr, xml.Attr{Name: xml.Name{Local: "type"}, Value: string(iq.Type)})
	if !iq.To.Equal(jid.JID{}) {
		attr = append(attr, xml.Attr{Name: xml.Name{Local: "to"}, Value: iq.To.String()})
	}
	if !iq.From.Equal(jid.JID{}) {
		attr = append(attr, xml.Attr{Name: xml.Name{Local: "from"}, Value: iq.From.String()})
	}
	if iq.ID != "" {
		attr = append(attr, xml.Attr{Name: xml.Name{Local: "id"}, Value: iq.ID})
	}
	if iq.Lang != "" {
		attr = append(attr, xml.Attr{Name: xml.Name{Space: ns.XML, Local: "lang"}, Value: iq.Lang})
	}

	return xml.StartElement{
		Name: name,
		Attr: attr,
	}
}

// Wrap wraps the payload in a stanza.
//
// The resulting IQ may not contain an id or from attribute and thus may not be
// valid without further processing.
func (iq IQ) Wrap(payload xml.TokenReader) xml.TokenReader {
	return xmlstream.Wrap(payload, iq.StartElement())
}

// Result returns a token reader that wraps the first element from payload in an
// IQ stanza with the to and from attributes switched and the type set to
// ResultIQ.
func (iq IQ) Result(payload xml.TokenReader) xml.TokenReader {
	iq.Type = ResultIQ
	iq.From, iq.To = iq.To, iq.From
	return iq.Wrap(payload)
}

// Error returns a token reader that wraps the first element from payload in an
// IQ stanza with the to and from attributes switched and the type set to
// ErrorIQ.
func (iq IQ) Error(err Error) xml.TokenReader {
	iq.Type = ErrorIQ
	iq.From, iq.To = iq.To, iq.From
	return iq.Wrap(err.TokenReader())
}

// IQType is the type of an IQ stanza.
// It should normally be one of the constants defined in this package.
type IQType string

const (
	// GetIQ is used to query another entity for information.
	GetIQ IQType = "get"

	// SetIQ is used to provide data to another entity, set new values, and
	// replace existing values.
	SetIQ IQType = "set"

	// ResultIQ is sent in response to a successful get or set IQ.
	ResultIQ IQType = "result"

	// ErrorIQ is sent to report that an error occurred during the delivery or
	// processing of a get or set IQ.
	ErrorIQ IQType = "error"
)

// MarshalText ensures that the zero value for IQType is marshaled to XML as a
// valid IQ get request.
// It satisfies the encoding.TextMarshaler interface for IQType.
func (t IQType) MarshalText() ([]byte, error) {
	if t == "" {
		t = GetIQ
	}
	return []byte(t), nil
}