structs.go
author Chris Jones <chris@cjones.org>
Thu, 29 Dec 2011 11:02:21 -0700
changeset 29 a456133ed0ac
parent 28 78961db80bae
child 31 1dc47df5c99f
permissions -rw-r--r--
Don't accept data on Client.Out until resource binding is complete. StartSession() won't do its work until after this happens. That means the app can call StartSession() and wait for it to return before checking Client.Jid.

// 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"
	"flag"
	"fmt"
	"io"
	"os"
	"regexp"
	"strings"
	"xml"
)

// 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 {
	To string `xml:"attr"`
	From string `xml:"attr"`
	Id string `xml:"attr"`
	Lang string `xml:"attr"`
	Version string `xml:"attr"`
}
var _ xml.Marshaler = &stream{}
var _ fmt.Stringer = &stream{}

// <stream:error>
// BUG(cjyar) Can this be consolidated with Error? Hide it if not.
type StreamError struct {
	Any Generic
	Text *errText
}
var _ xml.Marshaler = &StreamError{}

type errText struct {
	Lang string `xml:"attr"`
	Text string `xml:"chardata"`
}
var _ xml.Marshaler = &errText{}

// BUG(cjyar) Store this in Client and make it available to the app.
type Features struct {
	Starttls *starttls
	Mechanisms mechs
	Bind *Generic
}

type starttls struct {
	XMLName xml.Name
	Required *string
}

type mechs struct {
	Mechanism []string
}

type auth struct {
	XMLName xml.Name
	Chardata string `xml:"chardata"`
	Mechanism string `xml:"attr"`
	Any *Generic
}

// One of the three core XMPP stanza types: iq, message, presence. See
// RFC3920, section 9.
type Stanza interface {
	// Returns "iq", "message", or "presence".
	XName() string
	// The to attribute.
	XTo() string
	// The from attribute.
	XFrom() string
	// The id attribute.
	XId() string
	// The type attribute.
	XType() string
	// The xml:lang attribute.
	XLang() string
	// A nested error element, if any.
	XError() *Error
	// A (non-error) nested element, if any.
	XChild() *Generic
}

// message stanza
type Message struct {
	To string `xml:"attr"`
	From string `xml:"attr"`
	Id string `xml:"attr"`
	Type string `xml:"attr"`
	Lang string `xml:"attr"`
	Error *Error
	Subject *Generic
	Body *Generic
	Thread *Generic
	Any *Generic
}
var _ xml.Marshaler = &Message{}
var _ Stanza = &Message{}

// presence stanza
type Presence struct {
	To string `xml:"attr"`
	From string `xml:"attr"`
	Id string `xml:"attr"`
	Type string `xml:"attr"`
	Lang string `xml:"attr"`
	Error *Error
	Show *Generic
	Status *Generic
	Priority *Generic
	Any *Generic
}
var _ xml.Marshaler = &Presence{}
var _ Stanza = &Presence{}

// iq stanza
type Iq struct {
	To string `xml:"attr"`
	From string `xml:"attr"`
	Id string `xml:"attr"`
	Type string `xml:"attr"`
	Lang string `xml:"attr"`
	Error *Error
	Any *Generic
}
var _ xml.Marshaler = &Iq{}
var _ Stanza = &Iq{}

// Describes an XMPP stanza error. See RFC 3920, Section 9.3.
type Error struct {
	// The error type attribute.
	Type string `xml:"attr"`
	// Any nested element, if present.
	Any *Generic
}
var _ xml.Marshaler = &Error{}
var _ os.Error = &Error{}

// Holds an XML element not described by the more specific types.
type Generic struct {
	XMLName xml.Name
	Any *Generic
	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
}

func (jid *JID) Set(val string) bool {
	r := regexp.MustCompile("^(([^@/]+)@)?([^@/]+)(/([^@/]+))?$")
	parts := r.FindStringSubmatch(val)
	if parts == nil {
		return false
	}
	jid.Node = parts[2]
	jid.Domain = parts[3]
	jid.Resource = parts[5]
	return true
}

func (s *stream) MarshalXML() ([]byte, os.Error) {
	buf := bytes.NewBuffer(nil)
	buf.WriteString("<stream:stream")
	writeField(buf, "xmlns", "jabber:client")
	writeField(buf, "xmlns:stream", nsStream)
	writeField(buf, "to", s.To)
	writeField(buf, "from", s.From)
	writeField(buf, "id", s.Id)
	writeField(buf, "xml:lang", s.Lang)
	writeField(buf, "version", s.Version)
	buf.WriteString(">")
	// We never write </stream:stream>
	return buf.Bytes(), nil
}

func (s *stream) String() string {
	result, _ := s.MarshalXML()
	return string(result)
}

func parseStream(se xml.StartElement) (*stream, os.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 (s *StreamError) MarshalXML() ([]byte, os.Error) {
	buf := bytes.NewBuffer(nil)
	buf.WriteString("<stream:error>")
	xml.Marshal(buf, s.Any)
	if s.Text != nil {
		xml.Marshal(buf, s.Text)
	}
	buf.WriteString("</stream:error>")
	return buf.Bytes(), nil
}

func (e *errText) MarshalXML() ([]byte, os.Error) {
	buf := bytes.NewBuffer(nil)
	buf.WriteString("<text")
	writeField(buf, "xmlns", nsStreams)
	writeField(buf, "xml:lang", e.Lang)
	buf.WriteString(">")
	xml.Escape(buf, []byte(e.Text))
	buf.WriteString("</text>")
	return buf.Bytes(), nil
}

func writeField(w io.Writer, field, value string) {
	if value != "" {
		io.WriteString(w, " ")
		io.WriteString(w, field)
		io.WriteString(w, `="`)
		xml.Escape(w, []byte(value))
		io.WriteString(w, `"`)
	}
}

func (u *Generic) String() string {
	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 marshalXML(st Stanza) ([]byte, os.Error) {
	buf := bytes.NewBuffer(nil)
	buf.WriteString("<")
	buf.WriteString(st.XName())
	if st.XTo() != "" {
		writeField(buf, "to", st.XTo())
	}
	if st.XFrom() != "" {
		writeField(buf, "from", st.XFrom())
	}
	if st.XId() != "" {
		writeField(buf, "id", st.XId())
	}
	if st.XType() != "" {
		writeField(buf, "type", st.XType())
	}
	if st.XLang() != "" {
		writeField(buf, "xml:lang", st.XLang())
	}
	buf.WriteString(">")
	if st.XError() != nil {
		bytes, _ := st.XError().MarshalXML()
		buf.WriteString(string(bytes))
	}
	if st.XChild() != nil {
		xml.Marshal(buf, st.XChild())
	}
	buf.WriteString("</")
	buf.WriteString(st.XName())
	buf.WriteString(">")
	return buf.Bytes(), nil
}

func (er *Error) MarshalXML() ([]byte, os.Error) {
	buf := bytes.NewBuffer(nil)
	buf.WriteString("<error")
	writeField(buf, "type", er.Type)
	buf.WriteString(">")
	if er.Any != nil {
		xml.Marshal(buf, er.Any)
	}
	buf.WriteString("</error>")
	return buf.Bytes(), nil
}

func (er *Error) String() string {
	bytes, _ := er.MarshalXML()
	return string(bytes)
}

func (m *Message) XName() string {
	return "message"
}

func (m *Message) XTo() string {
	return m.To
}

func (m *Message) XFrom() string {
	return m.From
}

func (m *Message) XId() string {
	return m.Id
}

func (m *Message) XType() string {
	return m.Type
	}

func (m *Message) XLang() string {
	return m.Lang
}

func (m *Message) XError() *Error {
	return m.Error
}

func (m *Message) XChild() *Generic {
	return m.Any
}

func (m *Message) MarshalXML() ([]byte, os.Error) {
	return marshalXML(m)
}

func (p *Presence) XName() string {
	return "presence"
}

func (p *Presence) XTo() string {
	return p.To
}

func (p *Presence) XFrom() string {
	return p.From
}

func (p *Presence) XId() string {
	return p.Id
}

func (p *Presence) XType() string {
	return p.Type
	}

func (p *Presence) XLang() string {
	return p.Lang
}

func (p *Presence) XError() *Error {
	return p.Error
}

func (p *Presence) XChild() *Generic {
	return p.Any
}

func (p *Presence) MarshalXML() ([]byte, os.Error) {
	return marshalXML(p)
}

func (iq *Iq) XName() string {
	return "iq"
}

func (iq *Iq) XTo() string {
	return iq.To
}

func (iq *Iq) XFrom() string {
	return iq.From
}

func (iq *Iq) XId() string {
	return iq.Id
}

func (iq *Iq) XType() string {
	return iq.Type
	}

func (iq *Iq) XLang() string {
	return iq.Lang
}

func (iq *Iq) XError() *Error {
	return iq.Error
}

func (iq *Iq) XChild() *Generic {
	return iq.Any
}

func (iq *Iq) MarshalXML() ([]byte, os.Error) {
	return marshalXML(iq)
}

// Parse a string into a struct implementing Stanza -- this will be
// either an Iq, a Message, or a Presence.
func ParseStanza(str string) (Stanza, os.Error) {
	r := strings.NewReader(str)
	p := xml.NewParser(r)
	tok, err := p.Token()
	if err != nil {
		return nil, err
	}
	se, ok := tok.(xml.StartElement)
	if !ok {
		return nil, os.NewError("Not a start element")
	}
	var stan Stanza
	switch se.Name.Local {
	case "iq":
		stan = &Iq{}
	case "message":
		stan = &Message{}
	case "presence":
		stan = &Presence{}
	default:
		return nil, os.NewError("Not iq, message, or presence")
	}
	err = p.Unmarshal(stan, &se)
	if err != nil {
		return nil, err
	}
	return stan, nil
}