~samwhited/xmpp

e6ddf712656760f9552f48c706ef40dabb0538c1 — Sam Whited 5 years ago 854931c
Remove stream package in preparation for cleanup
7 files changed, 0 insertions(+), 651 deletions(-)

D stream/doc.go
D stream/example_test.go
D stream/options.go
D stream/stream.go
D stream/stream_test.go
D stream/streamerror.go
D stream/streamerror_test.go
D stream/doc.go => stream/doc.go +0 -28
@@ 1,28 0,0 @@
// Copyright 2016 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

// The stream package manages XML streams.
//
// RFC 6120: §4.1 defines an XML stream as follows:
//
//     An XML stream is a container for the exchange of XML elements between any
//     two entities over a network. The start of an XML stream is denoted
//     unambiguously by an opening "stream header" (i.e., an XML <stream> tag
//     with appropriate attributes and namespace declarations), while the end of
//     the XML stream is denoted unambiguously by a closing XML </stream> tag.
//     During the life of the stream, the entity that initiated it can send an
//     unbounded number of XML elements over the stream, either elements used to
//     negotiate the stream (e.g., to complete TLS negotiation (Section 5) or
//     SASL negotiation (Section 6)) or XML stanzas.  The "initial stream" is
//     negotiated from the initiating entity (typically a client or server) to
//     the receiving entity (typically a server), and can be seen as
//     corresponding to the initiating entity's "connection to" or "session
//     with" the receiving entity.  The initial stream enables unidirectional
//     communication from the initiating entity to the receiving entity; in
//     order to enable exchange of stanzas from the receiving entity to the
//     initiating entity, the receiving entity MUST negotiate a stream in the
//     opposite direction (the "response stream").
//
// Be advised: This API is still unstable and is subject to change.
package stream // import "bitbucket.org/mellium/xmpp/stream"

D stream/example_test.go => stream/example_test.go +0 -34
@@ 1,34 0,0 @@
// Copyright 2015 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package stream

import (
	"bytes"
	"encoding/xml"
	"fmt"
)

func ExampleStreamError_UnmarshalXML() {
	b := bytes.NewBufferString(`<stream:error>
	<restricted-xml xmlns="urn:ietf:params:xml:ns:xmpp-streams"/>
</stream:error>`)

	d := xml.NewDecoder(b)
	s := &StreamError{}
	d.Decode(s)

	fmt.Println(s.Error())
	// Output: restricted-xml
}

func ExampleStreamError_MarshalXML() {

	b, _ := xml.MarshalIndent(NotAuthorized, "", "  ")
	fmt.Println(string(b))
	// Output:
	// <stream:error>
	//   <not-authorized xmlns="urn:ietf:params:xml:ns:xmpp-streams"></not-authorized>
	// </stream:error>
}

D stream/options.go => stream/options.go +0 -51
@@ 1,51 0,0 @@
// Copyright 2016 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package stream

import (
	"golang.org/x/text/language"
)

// Option's can be used to configure the stream.
type Option func(*options)
type options struct {
	lang          language.Tag
	noVersionAttr bool
	s2sStream     bool
}

func getOpts(o ...Option) (res options) {
	for _, f := range o {
		f(&res)
	}
	return
}

// The Language option specifies the default language for the stream. Clients
// that support multiple languages will assume that all messages, alerts,
// and other textual data in the stream is in the given language unless it is
// specifically overridden.
func Language(l language.Tag) Option {
	return func(o *options) {
		o.lang = l
	}
}

var (
	// The NoVersion option leaves the version attribute off the stream. When the
	// version attribute is missing, servers and clients treat the XMPP version as
	// if it were 0.9. This is an advanced option and generally should not be used
	// except when responding to an incomming stream that has done the same. It
	// does not change the behavior of the stream otherwise (XMPP 1.0 is still
	// used), and may cause problems.
	NoVersion Option = func(o *options) {
		o.noVersionAttr = true
	}
	// The ServerToServer option configures the stream to use the jabber:server
	// namespace.
	ServerToServer = func(o *options) {
		o.s2sStream = true
	}
)

D stream/stream.go => stream/stream.go +0 -137
@@ 1,137 0,0 @@
// Copyright 2015 Sam Whited.
// 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"
	"errors"

	"bitbucket.org/mellium/xmpp/internal"
	"bitbucket.org/mellium/xmpp/jid"
	"golang.org/x/text/language"
)

// A Stream is a container for the exchange of XML elements between two
// endpoints. It maintains state about stream-level features, and handles
// decoding and routing incoming XMPP stanza's and other elements, as well as
// encoding outgoing XMPP elements. Each XMPP connection has two streams, one
// for input, and one for output.
type Stream struct {
	options
	to      *jid.JID
	from    *jid.JID
	id      string
	version internal.Version
}

// New creates a stream that will be used to initiate a new XMPP connection.
// This should always be used by clients to create a new stream, and by the
// initiating server in server-to-server connections.
func New(to, from *jid.JID, opts ...Option) Stream {
	return Stream{
		to:      to,
		from:    from,
		version: internal.DefaultVersion,
		options: getOpts(opts...),
	}
}

// FromStartElement constructs a new Stream from the given XML StartElement.
func FromStartElement(start xml.StartElement) (Stream, error) {

	stream := Stream{}
	if start.Name.Local != "stream" || start.Name.Space != "stream" {
		return stream, errors.New("Incorrect XML name on stream start element.")
	}

	for _, attr := range start.Attr {
		switch attr.Name {
		case xml.Name{"xmlns", "stream"}:
			if attr.Value != "http://etherx.jabber.org/streams" {
				return stream, errors.New("Stream name has invalid xmlns.")
			}
		case xml.Name{"", "from"}:
			j, err := jid.ParseString(attr.Value)
			if err != nil {
				return stream, err
			}
			stream.from = j
		case xml.Name{"", "to"}:
			j, err := jid.ParseString(attr.Value)
			if err != nil {
				return stream, err
			}
			stream.to = j
		case xml.Name{"", "xmlns"}:
			switch attr.Value {
			case "jabber:server":
				stream.options.s2sStream = true
			case "jabber:client":
				stream.options.s2sStream = false
			default:
				return stream, errors.New("Stream has invalid xmlns.")
			}
		case xml.Name{"xml", "lang"}:
			var err error
			stream.lang, err = language.Parse(attr.Value)
			if err != nil {
				return stream, err
			}
		case xml.Name{"", "id"}:
			stream.id = attr.Value
		case xml.Name{"", "version"}:
			v, err := internal.ParseVersion(attr.Value)
			if err != nil {
				return stream, err
			}
			stream.version = v
		}
	}

	return stream, nil
}

// StartElement creates an XML start element from the given stream which is
// suitable for encoding and transmitting over the wire.
func (s Stream) StartElement() xml.StartElement {
	var xmlns string
	if s.options.s2sStream {
		xmlns = "jabber:server"
	} else {
		xmlns = "jabber:client"
	}
	attrs := []xml.Attr{
		xml.Attr{
			xml.Name{"", "to"},
			s.to.String(),
		},
		xml.Attr{
			xml.Name{"", "from"},
			s.from.String(),
		},
		xml.Attr{
			xml.Name{"xml", "lang"},
			s.options.lang.String(),
		},
		xml.Attr{
			xml.Name{"", "id"},
			s.id,
		},
		xml.Attr{
			xml.Name{"", "xmlns"},
			xmlns,
		},
	}
	if !s.options.noVersionAttr {
		attrs = append(attrs, xml.Attr{
			xml.Name{"", "version"},
			s.version.String(),
		})
	}
	return xml.StartElement{
		Name: xml.Name{"stream", "stream"},
		Attr: attrs,
	}
}

D stream/stream_test.go => stream/stream_test.go +0 -96
@@ 1,96 0,0 @@
// Copyright 2016 Sam Whited.
// 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"
	"testing"

	"bitbucket.org/mellium/xmpp/jid"
)

var (
	validAttrs = []xml.Attr{
		{xml.Name{"", "id"}, "1234"},
		{xml.Name{"", "version"}, "1.0"},
		{xml.Name{"", "to"}, "shakespeare.lit"},
		{xml.Name{"", "from"}, "prospero@shakespeare.lit"},
		{xml.Name{"xmlns", "stream"}, "http://etherx.jabber.org/streams"},
		{xml.Name{"xml", "lang"}, "en"},
		{xml.Name{"", "xmlns"}, "jabber:client"},
	}
	validName = xml.Name{"stream", "stream"}
)

// FromStartElement should validate attributes.
func TestStreamFromStartElement(t *testing.T) {
	var data = []struct {
		start       xml.StartElement
		shouldError bool
	}{
		{xml.StartElement{validName, validAttrs}, false},
		{xml.StartElement{xml.Name{"stream", "wrong"}, validAttrs}, true},
		{xml.StartElement{xml.Name{"wrong", "stream"}, validAttrs}, true},
		{xml.StartElement{validName, []xml.Attr{
			{xml.Name{"", "id"}, "1234"},
			{xml.Name{"", "version"}, "1.0"},
			{xml.Name{"", "to"}, "shakespeare.lit"},
			{xml.Name{"", "from"}, "prospero@shakespeare.lit"},
			{xml.Name{"xmlns", "stream"}, "http://etherx.jabber.org/streams"},
			{xml.Name{"xml", "lang"}, "en"},
			{xml.Name{"", "xmlns"}, "jabber:wrong"},
		}}, true},
		{xml.StartElement{validName, []xml.Attr{
			{xml.Name{"", "id"}, "1234"},
			{xml.Name{"", "version"}, "1.0"},
			{xml.Name{"", "to"}, "shakespeare.lit"},
			{xml.Name{"", "from"}, "prospero@shakespeare.lit"},
			{xml.Name{"xmlns", "stream"}, "urn:jabber:wrong"},
			{xml.Name{"xml", "lang"}, "en"},
			{xml.Name{"", "xmlns"}, "jabber:client"},
		}}, true},
	}

	for _, d := range data {
		if _, err := FromStartElement(d.start); (err != nil) != d.shouldError {
			t.Log(err)
			t.Fail()
		}
	}
}

func TestStreamStartElement(t *testing.T) {
	to, _ := jid.ParseString("shakespeare.lit")
	from, _ := jid.ParseString("example.net")

	// The default stream namespace should be jabber:client.
	s := New(to, from)
	se := s.StartElement()
	xmlnsname := xml.Name{"", "xmlns"}
	for _, attr := range se.Attr {
		if attr.Name == xmlnsname && attr.Value != "jabber:client" {
			t.Log("Default stream xmlns should be jabber:client.")
			t.Fail()
		}
	}

	// Server to Server streams should have jabber:server namespace.
	se = New(to, from, ServerToServer).StartElement()
	for _, attr := range se.Attr {
		if attr.Name == xmlnsname && attr.Value != "jabber:server" {
			t.Log("ServerToServer option should set namespace to jabber:server.")
			t.Fail()
		}
	}

	// The NoVersion option should result in no stream version attribute.
	se = New(to, from, NoVersion).StartElement()
	for _, attr := range se.Attr {
		if attr.Name.Local == "version" {
			t.Log("NoVersion option should result in no version attribute.")
			t.Fail()
		}
	}
}

D stream/streamerror.go => stream/streamerror.go +0 -223
@@ 1,223 0,0 @@
// Copyright 2015 Sam Whited.
// 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"
	"net"
)

// 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 = StreamError{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 = StreamError{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 = StreamError{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 = StreamError{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 = StreamError{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 = StreamError{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 = StreamError{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 = StreamError{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 = StreamError{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 = StreamError{Err: "invalid-namespace"}

	// InvalidXML may be sent when the entity has sent invalid XML over the stream
	// to a server that performs validation.
	InvalidXML = StreamError{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 = StreamError{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 = StreamError{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 = StreamError{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 = StreamError{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 = StreamError{Err: "reset"}

	// ResourceConstraing may be sent when the server lacks the system resources
	// necessary to service the stream.
	ResourceConstraint = StreamError{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 = StreamError{Err: "restricted-xml"}

	// SystemShutdown may be sent when server is being shut down and all active
	// streams are being closed.
	SystemShutdown = StreamError{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 = StreamError{Err: "undefined-condition"}

	// UnsupportedEncoding may be sent when initiating entity has encoded the
	// stream in an encoding that is not UTF-8.
	UnsupportedEncoding = StreamError{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 = StreamError{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 = StreamError{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 = StreamError{Err: "unsupported-version"}
)

// SeeOtherHostError returns a new see-other-host error with the given network
// address as the host. If the address appears to be a raw IPv6 address (eg.
// "::1"), the error wraps it in brackets ("[::1]").
func SeeOtherHostError(addr net.Addr) StreamError {
	var cdata string

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

	return StreamError{"see-other-host", []byte(cdata)}
}

// A StreamError represents an unrecoverable stream-level error that may include
// character data or arbitrary inner XML.
type StreamError struct {
	Err      string
	InnerXML []byte
}

// 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 (e StreamError) Error() string {
	return e.Err
}

// UnmarshalXML satisfies the xml package's Unmarshaler interface and allows
// StreamError's to be correctly unmarshaled from XML.
func (s *StreamError) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
	se := struct {
		XMLName xml.Name
		Err     struct {
			XMLName  xml.Name
			InnerXML []byte `xml:",innerxml"`
		} `xml:",any"`
	}{}
	err := d.DecodeElement(&se, &start)
	if err != nil {
		return err
	}
	s.Err = se.Err.XMLName.Local
	s.InnerXML = se.Err.InnerXML
	return nil
}

// MarshalXML satisfies the xml package's Marshaler interface and allows
// StreamError's to be correctly marshaled back into XML.
func (s StreamError) MarshalXML(e *xml.Encoder, start xml.StartElement) error {
	e.EncodeElement(
		struct {
			Err struct {
				XMLName  xml.Name
				InnerXML []byte `xml:",innerxml"`
			}
		}{
			struct {
				XMLName  xml.Name
				InnerXML []byte `xml:",innerxml"`
			}{
				XMLName:  xml.Name{"urn:ietf:params:xml:ns:xmpp-streams", s.Err},
				InnerXML: s.InnerXML,
			},
		},
		xml.StartElement{
			xml.Name{"", "stream:error"},
			[]xml.Attr{},
		},
	)
	return nil
}

D stream/streamerror_test.go => stream/streamerror_test.go +0 -82
@@ 1,82 0,0 @@
// Copyright 2015 Sam Whited.
// Use of this source code is governed by the BSD 2-clause license that can be
// found in the LICENSE file.

package stream

import (
	"bytes"
	"encoding/xml"
	"net"
	"testing"
)

var _ error = (*StreamError)(nil)
var _ error = StreamError{}
var _ xml.Marshaler = (*StreamError)(nil)
var _ xml.Marshaler = StreamError{}
var _ xml.Unmarshaler = (*StreamError)(nil)

// Both pointers and normal errors should marshal to the same thing.
func TestMarshalPointerAndNormal(t *testing.T) {
	xb, err := xml.Marshal(BadFormat)
	if err != nil {
		t.Log(err)
		t.FailNow()
	}
	xb2, err := xml.Marshal(&BadFormat)
	if err != nil {
		t.Log(err)
		t.FailNow()
	}

	if len(xb) != len(xb2) {
		t.Log("BadFormat and &BadFormat should marshal identically")
		t.Fail()
	}
}

// see-other-host errors should wrap IPv6 addresses in brackets.
func TestMarshalSeeOtherHostV6(t *testing.T) {
	ipaddr := net.IPAddr{IP: net.ParseIP("::1")}
	soh := SeeOtherHostError(&ipaddr)
	xb, err := xml.Marshal(soh)
	if err != nil {
		t.Log(err)
		t.FailNow()
	}

	if xbs := string(xb); xbs != `<stream:error><see-other-host xmlns="urn:ietf:params:xml:ns:xmpp-streams">[::1]</see-other-host></stream:error>` {
		t.Logf("Expected [::1] but got %s", xbs)
		t.Fail()
	}
}

// Stream errors should be marshalable and unmarshalable.
func TestUnmarshalMarshalSteamError(t *testing.T) {
	b := []byte(`<stream:error>
	<restricted-xml xmlns="urn:ietf:params:xml:ns:xmpp-streams">a</restricted-xml>
</stream:error>`)
	mb := bytes.NewBuffer(b)
	d := xml.NewDecoder(mb)
	s := &StreamError{}
	err := d.Decode(s)
	if err != nil {
		t.Log(err)
		t.FailNow()
	}
	if s.Error() != "restricted-xml" {
		t.Logf("Expected restricted-xml but got %+v\n", s)
		t.FailNow()
	}

	xb, err := xml.MarshalIndent(s, "", "\t")
	if err != nil {
		t.Log(err)
		t.FailNow()
	}
	if string(b) != string(xb) {
		t.Logf("Expected %s but got %s", string(b), string(xb))
		t.Fail()
	}
}