xmpp/structs.go
changeset 126 367e76b3028e
parent 121 ebb86cbdd218
child 128 8342afcffc92
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/structs.go	Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,276 @@
+// Copyright 2011 The Go Authors.  All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+package xmpp
+
+// This file contains data structures.
+
+import (
+	"bytes"
+	"encoding/xml"
+	"flag"
+	"fmt"
+	// BUG(cjyar): Doesn't use stringprep. Could try the implementation at
+	// "code.google.com/p/go-idn/src/stringprep"
+	"regexp"
+	"strings"
+)
+
+// JID represents an entity that can communicate with other
+// entities. It looks like node@domain/resource. Node and resource are
+// sometimes optional.
+type JID struct {
+	Node     string
+	Domain   string
+	Resource string
+}
+
+var _ fmt.Stringer = &JID{}
+var _ flag.Value = &JID{}
+
+// XMPP's <stream:stream> XML element
+type stream struct {
+	XMLName xml.Name `xml:"stream=http://etherx.jabber.org/streams stream"`
+	To      string   `xml:"to,attr"`
+	From    string   `xml:"from,attr"`
+	Id      string   `xml:"id,attr"`
+	Lang    string   `xml:"http://www.w3.org/XML/1998/namespace lang,attr"`
+	Version string   `xml:"version,attr"`
+}
+
+var _ fmt.Stringer = &stream{}
+
+// <stream:error>
+type streamError struct {
+	XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"`
+	Any     Generic  `xml:",any"`
+	Text    *errText
+}
+
+type errText struct {
+	XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-streams text"`
+	Lang    string   `xml:"http://www.w3.org/XML/1998/namespace lang,attr"`
+	Text    string   `xml:",chardata"`
+}
+
+type Features struct {
+	Starttls   *starttls `xml:"urn:ietf:params:xml:ns:xmpp-tls starttls"`
+	Mechanisms mechs     `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanisms"`
+	Bind       *bindIq
+	Session    *Generic
+	Any        *Generic
+}
+
+type starttls struct {
+	XMLName  xml.Name
+	Required *string
+}
+
+type mechs struct {
+	Mechanism []string `xml:"urn:ietf:params:xml:ns:xmpp-sasl mechanism"`
+}
+
+type auth struct {
+	XMLName   xml.Name
+	Chardata  string `xml:",chardata"`
+	Mechanism string `xml:"mechanism,attr,omitempty"`
+	Any       *Generic
+}
+
+type Stanza interface {
+	GetHeader() *Header
+}
+
+// One of the three core XMPP stanza types: iq, message, presence. See
+// RFC3920, section 9.
+type Header struct {
+	To       string `xml:"to,attr,omitempty"`
+	From     string `xml:"from,attr,omitempty"`
+	Id       string `xml:"id,attr,omitempty"`
+	Type     string `xml:"type,attr,omitempty"`
+	Lang     string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
+	Innerxml string `xml:",innerxml"`
+	Error    *Error
+	Nested   []interface{}
+}
+
+// message stanza
+type Message struct {
+	XMLName xml.Name `xml:"jabber:client message"`
+	Header
+	Subject *Generic `xml:"jabber:client subject"`
+	Body    *Generic `xml:"jabber:client body"`
+	Thread  *Generic `xml:"jabber:client thread"`
+}
+
+var _ Stanza = &Message{}
+
+// presence stanza
+type Presence struct {
+	XMLName xml.Name `xml:"presence"`
+	Header
+	Show     *Generic `xml:"jabber:client show"`
+	Status   *Generic `xml:"jabber:client status"`
+	Priority *Generic `xml:"jabber:client priority"`
+}
+
+var _ Stanza = &Presence{}
+
+// iq stanza
+type Iq struct {
+	XMLName xml.Name `xml:"iq"`
+	Header
+}
+
+var _ Stanza = &Iq{}
+
+// Describes an XMPP stanza error. See RFC 3920, Section 9.3.
+type Error struct {
+	XMLName xml.Name `xml:"error"`
+	// The error type attribute.
+	Type string `xml:"type,attr"`
+	// Any nested element, if present.
+	Any *Generic
+}
+
+var _ error = &Error{}
+
+// Used for resource binding as a nested element inside <iq/>.
+type bindIq struct {
+	XMLName  xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
+	Resource *string  `xml:"resource"`
+	Jid      *string  `xml:"jid"`
+}
+
+// Holds an XML element not described by the more specific types.
+type Generic struct {
+	XMLName  xml.Name
+	Any      *Generic `xml:",any"`
+	Chardata string   `xml:",chardata"`
+}
+
+var _ fmt.Stringer = &Generic{}
+
+func (jid *JID) String() string {
+	result := jid.Domain
+	if jid.Node != "" {
+		result = jid.Node + "@" + result
+	}
+	if jid.Resource != "" {
+		result = result + "/" + jid.Resource
+	}
+	return result
+}
+
+// Set implements flag.Value. It returns true if it successfully
+// parses the string.
+func (jid *JID) Set(val string) error {
+	r := regexp.MustCompile("^(([^@/]+)@)?([^@/]+)(/([^@/]+))?$")
+	parts := r.FindStringSubmatch(val)
+	if parts == nil {
+		return fmt.Errorf("%s doesn't match user@domain/resource", val)
+	}
+	// jid.Node = stringprep.Nodeprep(parts[2])
+	// jid.Domain = stringprep.Nodeprep(parts[3])
+	// jid.Resource = stringprep.Resourceprep(parts[5])
+	jid.Node = parts[2]
+	jid.Domain = parts[3]
+	jid.Resource = parts[5]
+	return nil
+}
+
+func (s *stream) String() string {
+	var buf bytes.Buffer
+	buf.WriteString(`<stream:stream xmlns="`)
+	buf.WriteString(NsClient)
+	buf.WriteString(`" xmlns:stream="`)
+	buf.WriteString(NsStream)
+	buf.WriteString(`"`)
+	if s.To != "" {
+		buf.WriteString(` to="`)
+		xml.Escape(&buf, []byte(s.To))
+		buf.WriteString(`"`)
+	}
+	if s.From != "" {
+		buf.WriteString(` from="`)
+		xml.Escape(&buf, []byte(s.From))
+		buf.WriteString(`"`)
+	}
+	if s.Id != "" {
+		buf.WriteString(` id="`)
+		xml.Escape(&buf, []byte(s.Id))
+		buf.WriteString(`"`)
+	}
+	if s.Lang != "" {
+		buf.WriteString(` xml:lang="`)
+		xml.Escape(&buf, []byte(s.Lang))
+		buf.WriteString(`"`)
+	}
+	if s.Version != "" {
+		buf.WriteString(` version="`)
+		xml.Escape(&buf, []byte(s.Version))
+		buf.WriteString(`"`)
+	}
+	buf.WriteString(">")
+	return buf.String()
+}
+
+func parseStream(se xml.StartElement) (*stream, error) {
+	s := &stream{}
+	for _, attr := range se.Attr {
+		switch strings.ToLower(attr.Name.Local) {
+		case "to":
+			s.To = attr.Value
+		case "from":
+			s.From = attr.Value
+		case "id":
+			s.Id = attr.Value
+		case "lang":
+			s.Lang = attr.Value
+		case "version":
+			s.Version = attr.Value
+		}
+	}
+	return s, nil
+}
+
+func (iq *Iq) GetHeader() *Header {
+	return &iq.Header
+}
+
+func (m *Message) GetHeader() *Header {
+	return &m.Header
+}
+
+func (p *Presence) GetHeader() *Header {
+	return &p.Header
+}
+
+func (u *Generic) String() string {
+	if u == nil {
+		return "nil"
+	}
+	var sub string
+	if u.Any != nil {
+		sub = u.Any.String()
+	}
+	return fmt.Sprintf("<%s %s>%s%s</%s %s>", u.XMLName.Space,
+		u.XMLName.Local, sub, u.Chardata, u.XMLName.Space,
+		u.XMLName.Local)
+}
+
+func (er *Error) Error() string {
+	buf, err := xml.Marshal(er)
+	if err != nil {
+		Warn.Log("double bad error: couldn't marshal error")
+		return "unreadable error"
+	}
+	return string(buf)
+}
+
+var bindExt Extension = Extension{StanzaHandlers: map[string]func(*xml.Name) interface{}{NsBind: newBind}}
+
+func newBind(name *xml.Name) interface{} {
+	return &bindIq{}
+}