~samwhited/xmpp

xmpp/stream/error.go -rw-r--r-- 11.7 KiB
8db8f7efSam Whited .builds: use go install to fetch gotip 13 hours 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
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
// Copyright 2015 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 stream

import (
	"encoding/xml"
	"io"
	"net"

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

// A list of stream errors defined in RFC 6120 §4.9.3
var (
	// BadFormat is used when the entity has sent XML that cannot be processed.
	// This error can be used instead of the more specific XML-related errors,
	// such as <bad-namespace-prefix/>, <invalid-xml/>, <not-well-formed/>,
	// <restricted-xml/>, and <unsupported-encoding/>. However, the more specific
	// errors are RECOMMENDED.
	BadFormat = Error{Err: "bad-format"}

	// BadNamespacePrefix is sent when an entity has sent a namespace prefix that
	// is unsupported, or has sent no namespace prefix, on an element that needs
	// such a prefix.
	BadNamespacePrefix = Error{Err: "bad-namespace-prefix"}

	// Conflict is sent when the server either (1) is closing the existing stream
	// for this entity because a new stream has been initiated that conflicts with
	// the existing stream, or (2) is refusing a new stream for this entity
	// because allowing the new stream would conflict with an existing stream
	// (e.g., because the server allows only a certain number of connections from
	// the same IP address or allows only one server-to-server stream for a given
	// domain pair as a way of helping to ensure in-order processing.
	Conflict = Error{Err: "conflict"}

	// ConnectionTimeout results when one party is closing the stream because it
	// has reason to believe that the other party has permanently lost the ability
	// to communicate over the stream.
	ConnectionTimeout = Error{Err: "connection-timeout"}

	// HostGone is sent when the value of the 'to' attribute provided in the
	// initial stream header corresponds to an FQDN that is no longer serviced by
	// the receiving entity.
	HostGone = Error{Err: "host-gone"}

	// HostUnknown is sent when the value of the 'to' attribute provided in the
	// initial stream header does not correspond to an FQDN that is serviced by
	// the receiving entity.
	HostUnknown = Error{Err: "host-unknown"}

	// ImproperAddressing is used when a stanza sent between two servers lacks a
	// 'to' or 'from' attribute, the 'from' or 'to' attribute has no value, or the
	// value violates the rules for XMPP addresses.
	ImproperAddressing = Error{Err: "improper-addressing"}

	// InternalServerError is sent when the server has experienced a
	// misconfiguration or other internal error that prevents it from servicing
	// the stream.
	InternalServerError = Error{Err: "internal-server-error"}

	// InvalidFrom is sent when data provided in a 'from' attribute does not match
	// an authorized JID or validated domain as negotiated (1) between two servers
	// using SASL or Server Dialback, or (2) between a client and a server via
	// SASL authentication and resource binding.
	InvalidFrom = Error{Err: "invalid-from"}

	// InvalidNamespace may be sent when the stream namespace name is something
	// other than "http://etherx.jabber.org/streams" or the content namespace
	// declared as the default namespace is not supported (e.g., something other
	// than "jabber:client" or "jabber:server").
	InvalidNamespace = Error{Err: "invalid-namespace"}

	// InvalidXML may be sent when the entity has sent invalid XML over the stream
	// to a server that performs validation.
	InvalidXML = Error{Err: "invalid-xml"}

	// NotAuthorized may be sent when the entity has attempted to send XML stanzas
	// or other outbound data before the stream has been authenticated, or
	// otherwise is not authorized to perform an action related to stream
	// negotiation; the receiving entity MUST NOT process the offending data
	// before sending the stream error.
	NotAuthorized = Error{Err: "not-authorized"}

	// NotWellFormed may be sent when the initiating entity has sent XML that
	// violates the well-formedness rules of XML or XML namespaces.
	NotWellFormed = Error{Err: "not-well-formed"}

	// PolicyViolation may be sent when an entity has violated some local service
	// policy (e.g., a stanza exceeds a configured size limit).
	PolicyViolation = Error{Err: "policy-violation"}

	// RemoteConnectionFailed may be sent when the server is unable to properly
	// connect to a remote entity that is needed for authentication or
	// authorization
	RemoteConnectionFailed = Error{Err: "remote-connection-failed"}

	// server is closing the stream because it has new (typically
	// security-critical) features to offer, because the keys or certificates used
	// to establish a secure context for the stream have expired or have been
	// revoked during the life of the stream, because the TLS sequence number has
	// wrapped, etc. Encryption and authentication need to be negotiated again for
	// the new stream (e.g., TLS session resumption cannot be used).
	Reset = Error{Err: "reset"}

	// ResourceConstraing may be sent when the server lacks the system resources
	// necessary to service the stream.
	ResourceConstraint = Error{Err: "resource-constraint"}

	// RestrictedXML may be sent when the entity has attempted to send restricted
	// XML features such as a comment, processing instruction, DTD subset, or XML
	// entity reference.
	RestrictedXML = Error{Err: "restricted-xml"}

	// SystemShutdown may be sent when server is being shut down and all active
	// streams are being closed.
	SystemShutdown = Error{Err: "system-shutdown"}

	// UndefinedCondition may be sent when the error condition is not one of those
	// defined by the other conditions in this list; this error condition should
	// be used in conjunction with an application-specific condition.
	UndefinedCondition = Error{Err: "undefined-condition"}

	// UnsupportedEncoding may be sent when initiating entity has encoded the
	// stream in an encoding that is not UTF-8.
	UnsupportedEncoding = Error{Err: "unsupported-encoding"}

	// UnsupportedFeature may be sent when receiving entity has advertised a
	// mandatory-to-negotiate stream feature that the initiating entity does not
	// support.
	UnsupportedFeature = Error{Err: "unsupported-feature"}

	// UnsupportedStanzaType may be sent when the initiating entity has sent a
	// first-level child of the stream that is not supported by the server, either
	// because the receiving entity does not understand the namespace or because
	// the receiving entity does not understand the element name for the
	// applicable namespace (which might be the content namespace declared as the
	// default namespace).
	UnsupportedStanzaType = Error{Err: "unsupported-stanza-type"}

	// UnsupportedVersion may be sent when the 'version' attribute provided by the
	// initiating entity in the stream header specifies a version of XMPP that is
	// not supported by the server.
	UnsupportedVersion = Error{Err: "unsupported-version"}
)

// SeeOtherHostError returns a new see-other-host error with the given network
// address as the host.
func SeeOtherHostError(addr net.Addr) Error {
	cdata := addr.String()

	// If the address looks like a raw IPv6 literal, wrap it in []
	if ip := net.ParseIP(cdata); ip != nil && ip.To4() == nil && ip.To16() != nil {
		cdata = "[" + cdata + "]"
	}

	return Error{
		Err: "see-other-host",
		// This needs to return the CharData every time in case we use this error
		// multiple times, so use a custom ReaderFunc and not the stateful
		// xmlstream.Token.
		innerXML: xmlstream.ReaderFunc(func() (xml.Token, error) {
			return xml.CharData(cdata), io.EOF
		}),
	}
}

// Error represents an unrecoverable stream-level error that may include
// character data or arbitrary inner XML.
type Error struct {
	Err  string
	Text []struct {
		Lang  string
		Value string
	}

	innerXML xml.TokenReader
	payload  xml.TokenReader
}

// Is will be used by errors.Is when comparing errors.
// For more information see the errors package.
func (s Error) Is(err error) bool {
	se, ok := err.(Error)
	if !ok {
		return false
	}

	if se.Err == "" {
		return true
	}
	return se.Err == s.Err
}

// Error satisfies the builtin error interface and returns the name of the
// StreamError. For instance, given the error:
//
//     <stream:error>
//       <restricted-xml xmlns="urn:ietf:params:xml:ns:xmpp-streams"/>
//     </stream:error>
//
// Error() would return "restricted-xml".
func (s Error) Error() string {
	return s.Err
}

// UnmarshalXML satisfies the xml package's Unmarshaler interface and allows
// StreamError's to be correctly unmarshaled from XML.
func (s *Error) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	for {
		tok, err := d.Token()
		if err == io.EOF {
			err = nil
			if tok == nil {
				return nil
			}
		}
		if err != nil {
			return err
		}

		var start xml.StartElement
		switch tt := tok.(type) {
		case xml.StartElement:
			start = tt
		case xml.EndElement:
			// This is the end element, everything else has been unmarshaled or skipped.
			return nil
		default:
			continue
		}

		switch {
		case start.Name.Local == "text" && start.Name.Space == NSError:
			var lang string
			for _, attr := range start.Attr {
				if attr.Name.Local == "lang" && attr.Name.Space == ns.XML {
					lang = attr.Value
					break
				}
			}
			t := struct {
				XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-streams text"`
				Text    string   `xml:",chardata"`
			}{}
			err = d.DecodeElement(&t, &start)
			if err != nil {
				return err
			}
			s.Text = append(s.Text, struct {
				Lang  string
				Value string
			}{
				Lang:  lang,
				Value: t.Text,
			})
		case start.Name.Space == NSError:
			s.Err = start.Name.Local
		}
		if err = d.Skip(); err != nil {
			return err
		}
	}
}

// MarshalXML satisfies the xml package's Marshaler interface and allows
// StreamError's to be correctly marshaled back into XML.
func (s Error) MarshalXML(e *xml.Encoder, _ xml.StartElement) error {
	_, err := s.WriteXML(e)
	return err
}

// WriteXML satisfies the xmlstream.WriterTo interface.
// It is like MarshalXML except it writes tokens to w.
func (s Error) WriteXML(w xmlstream.TokenWriter) (n int, err error) {
	return xmlstream.Copy(w, s.TokenReader())
}

// TokenReader returns a new xml.TokenReader that returns an encoding of
// the error.
func (s Error) TokenReader() xml.TokenReader {
	inner := xmlstream.Wrap(s.innerXML, xml.StartElement{Name: xml.Name{Local: s.Err, Space: NSError}})
	if s.payload != nil {
		inner = xmlstream.MultiReader(
			inner,
			s.payload,
		)
	}
	for _, txt := range s.Text {
		start := xml.StartElement{Name: xml.Name{Space: NSError, Local: "text"}}
		if txt.Lang != "" {
			start.Attr = append(start.Attr, xml.Attr{
				Name:  xml.Name{Space: ns.XML, Local: "lang"},
				Value: txt.Lang,
			})
		}
		inner = xmlstream.MultiReader(
			inner,
			xmlstream.Wrap(
				xmlstream.Token(xml.CharData(txt.Value)),
				start,
			),
		)
	}
	return xmlstream.Wrap(
		inner,
		xml.StartElement{
			Name: xml.Name{Local: "error", Space: NS},
		},
	)
}

// ApplicationError returns a copy of the Error with the provided application
// level error included alongside the error condition.
// Multiple, chained, calls to ApplicationError will  replace the payload each
// time and only the final call will have any effect.
//
// Because the TokenReader will be consumed during marshalling errors created
// with this method may only be marshaled once.
func (s Error) ApplicationError(r xml.TokenReader) Error {
	s.payload = r
	return s
}

// InnerXML returns a copy of the Error that marshals the provided reader after
// the error condition start token.
// Multiple, chained, calls to InnerXML will  replace the inner XML each time
// and only the final call will have any effect.
//
// Because the TokenReader will be consumed during marshalling errors created
// with this method may only be marshaled once.
func (s Error) InnerXML(r xml.TokenReader) Error {
	s.innerXML = r
	return s
}