Updated for Go 1.0 + upcoming XML fixes.
--- a/Makefile Mon Jan 23 21:54:41 2012 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,22 +0,0 @@
-# Copyright 2009 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.
-
-include $(GOROOT)/src/Make.inc
-
-TARG=cjyar/xmpp
-GOFILES=\
- xmpp.go \
- roster.go \
- stream.go \
- structs.go \
-
-examples: install
- gomake -C examples all
-
-clean: clean-examples
-
-clean-examples:
- gomake -C examples clean
-
-include $(GOROOT)/src/Make.pkg
--- a/examples/Makefile Mon Jan 23 21:54:41 2012 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,11 +0,0 @@
-# Copyright 2009 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.
-
-include $(GOROOT)/src/Make.inc
-
-TARG=interact
-GOFILES=\
- interact.go \
-
-include $(GOROOT)/src/Make.cmd
--- a/roster.go Mon Jan 23 21:54:41 2012 -0700
+++ b/roster.go Sun Dec 16 13:03:03 2012 -0700
@@ -5,9 +5,8 @@
package xmpp
import (
+ "encoding/xml"
"fmt"
- "os"
- "xml"
)
// This file contains support for roster management, RFC 3921, Section 7.
@@ -17,15 +16,15 @@
// Roster query/result
type RosterQuery struct {
XMLName xml.Name `xml:"jabber:iq:roster query"`
- Item []RosterItem
+ Item []RosterItem `xml:"item"`
}
// See RFC 3921, Section 7.1.
type RosterItem struct {
- XMLName xml.Name `xml:"item"`
- Jid string `xml:"attr"`
- Subscription string `xml:"attr"`
- Name string `xml:"attr"`
+ XMLName xml.Name `xml:"jabber:iq:roster item"`
+ Jid string `xml:"jid,attr"`
+ Subscription string `xml:"subscription,attr"`
+ Name string `xml:"name,attr"`
Group []string
}
@@ -46,28 +45,33 @@
// Synchronously fetch this entity's roster from the server and cache
// that information. This is called once from a fairly deep call stack
// as part of XMPP negotiation.
-func fetchRoster(client *Client) os.Error {
+func fetchRoster(client *Client) error {
rosterUpdate := rosterClients[client.Uid].rosterUpdate
iq := &Iq{From: client.Jid.String(), Id: <-Id, Type: "get",
Nested: []interface{}{RosterQuery{}}}
- ch := make(chan os.Error)
+ ch := make(chan error)
f := func(st Stanza) bool {
defer close(ch)
+ iq, ok := st.(*Iq)
+ if !ok {
+ ch <- fmt.Errorf("response to iq wasn't iq: %s", st)
+ return false
+ }
if iq.Type == "error" {
ch <- iq.Error
return false
}
var rq *RosterQuery
- for _, ele := range st.GetNested() {
+ for _, ele := range iq.Nested {
if q, ok := ele.(*RosterQuery); ok {
rq = q
break
}
}
if rq == nil {
- ch <- os.NewError(fmt.Sprintf(
- "Roster query result not query: %v", st))
+ ch <- fmt.Errorf(
+ "Roster query result not query: %v", st)
return false
}
for _, item := range rq.Item {
@@ -105,22 +109,27 @@
}
func maybeUpdateRoster(client *Client, st Stanza) {
+ iq, ok := st.(*Iq)
+ if !ok {
+ return
+ }
+
rosterUpdate := rosterClients[client.Uid].rosterUpdate
var rq *RosterQuery
- for _, ele := range st.GetNested() {
+ for _, ele := range iq.Nested {
if q, ok := ele.(*RosterQuery); ok {
rq = q
break
}
}
- if st.GetName() == "iq" && st.GetType() == "set" && rq != nil {
+ if iq.Type == "set" && rq != nil {
for _, item := range rq.Item {
rosterUpdate <- item
}
// Send a reply.
- iq := &Iq{To: st.GetFrom(), Id: st.GetId(), Type: "result"}
- client.Out <- iq
+ reply := &Iq{To: iq.From, Id: iq.Id, Type: "result"}
+ client.Out <- reply
}
}
@@ -131,7 +140,7 @@
select {
case newIt := <-rosterUpdate:
if newIt.Subscription == "remove" {
- roster[newIt.Jid] = RosterItem{}, false
+ delete(roster, newIt.Jid)
} else {
roster[newIt.Jid] = newIt
}
--- a/roster_test.go Mon Jan 23 21:54:41 2012 -0700
+++ b/roster_test.go Sun Dec 16 13:03:03 2012 -0700
@@ -5,16 +5,16 @@
package xmpp
import (
+ "encoding/xml"
"reflect"
- "strings"
"testing"
- "xml"
)
// This is mostly just tests of the roster data structures.
func TestRosterIqMarshal(t *testing.T) {
- iq := &Iq{From: "from", Lang: "en", Nested: []interface{}{RosterQuery{}}}
+ iq := &Iq{From: "from", Lang: "en",
+ Nested: []interface{}{RosterQuery{}}}
exp := `<iq from="from" xml:lang="en"><query xmlns="` +
NsRoster + `"></query></iq>`
assertMarshal(t, exp, iq)
@@ -23,18 +23,17 @@
func TestRosterIqUnmarshal(t *testing.T) {
str := `<iq from="from" xml:lang="en"><query xmlns="` +
NsRoster + `"><item jid="a@b.c"/></query></iq>`
- r := strings.NewReader(str)
- var st Stanza = &Iq{}
- xml.Unmarshal(r, st)
+ iq := Iq{}
+ xml.Unmarshal([]byte(str), &iq)
m := map[string]func(*xml.Name) interface{}{NsRoster: newRosterQuery}
- err := parseExtended(st, m)
+ err := parseExtended(&iq, m)
if err != nil {
t.Fatalf("parseExtended: %v", err)
}
- assertEquals(t, "iq", st.GetName())
- assertEquals(t, "from", st.GetFrom())
- assertEquals(t, "en", st.GetLang())
- nested := st.GetNested()
+ assertEquals(t, "iq", iq.XMLName.Local)
+ assertEquals(t, "from", iq.From)
+ assertEquals(t, "en", iq.Lang)
+ nested := iq.Nested
if nested == nil {
t.Fatalf("nested nil")
}
--- a/stream.go Mon Jan 23 21:54:41 2012 -0700
+++ b/stream.go Sun Dec 16 13:03:03 2012 -0700
@@ -11,20 +11,19 @@
package xmpp
import (
- "big"
"crypto/md5"
"crypto/rand"
"crypto/tls"
"encoding/base64"
+ "encoding/xml"
"fmt"
"io"
+ "log/syslog"
+ "math/big"
"net"
- "os"
"regexp"
"strings"
- "syslog"
"time"
- "xml"
)
// Callback to handle a stanza with a particular id.
@@ -39,12 +38,12 @@
func (cl *Client) readTransport(w io.WriteCloser) {
defer w.Close()
- cl.socket.SetReadTimeout(1e8)
p := make([]byte, 1024)
for {
if cl.socket == nil {
cl.waitForSocket()
}
+ cl.socket.SetReadDeadline(time.Now().Add(time.Second))
nr, err := cl.socket.Read(p)
if nr == 0 {
if errno, ok := err.(*net.OpError); ok {
@@ -53,14 +52,14 @@
}
}
if Log != nil {
- Log.Err("read: " + err.String())
+ Log.Err("read: " + err.Error())
}
break
}
nw, err := w.Write(p[:nr])
if nw < nr {
if Log != nil {
- Log.Err("read: " + err.String())
+ Log.Err("read: " + err.Error())
}
break
}
@@ -74,14 +73,14 @@
nr, err := r.Read(p)
if nr == 0 {
if Log != nil {
- Log.Err("write: " + err.String())
+ Log.Err("write: " + err.Error())
}
break
}
nw, err := cl.socket.Write(p[:nr])
if nw < nr {
if Log != nil {
- Log.Err("write: " + err.String())
+ Log.Err("write: " + err.Error())
}
break
}
@@ -97,15 +96,17 @@
}
defer close(ch)
- p := xml.NewParser(r)
+ p := xml.NewDecoder(r)
+ p.Context.Map[""] = NsClient
+ p.Context.Map["stream"] = NsStream
Loop:
for {
// Sniff the next token on the stream.
t, err := p.Token()
if t == nil {
- if err != os.EOF {
+ if err != io.EOF {
if Log != nil {
- Log.Err("read: " + err.String())
+ Log.Err("read: " + err.Error())
}
}
break
@@ -124,7 +125,7 @@
if err != nil {
if Log != nil {
Log.Err("unmarshal stream: " +
- err.String())
+ err.Error())
}
break Loop
}
@@ -139,11 +140,11 @@
case NsSASL + " challenge", NsSASL + " failure",
NsSASL + " success":
obj = &auth{}
- case "jabber:client iq":
+ case NsClient + " iq":
obj = &Iq{}
- case "jabber:client message":
+ case NsClient + " message":
obj = &Message{}
- case "jabber:client presence":
+ case NsClient + " presence":
obj = &Presence{}
default:
obj = &Generic{}
@@ -154,10 +155,10 @@
}
// Read the complete XML stanza.
- err = p.Unmarshal(obj, &se)
+ err = p.DecodeElement(obj, &se)
if err != nil {
if Log != nil {
- Log.Err("unmarshal: " + err.String())
+ Log.Err("unmarshal: " + err.Error())
}
break Loop
}
@@ -170,8 +171,9 @@
if err != nil {
if Log != nil {
Log.Err("ext unmarshal: " +
- err.String())
+ err.Error())
}
+ fmt.Printf("ext: %v\n", err)
break Loop
}
}
@@ -181,14 +183,14 @@
}
}
-func parseExtended(st Stanza, extStanza map[string]func(*xml.Name) interface{}) os.Error {
+func parseExtended(st Stanza, extStanza map[string]func(*xml.Name) interface{}) error {
// Now parse the stanza's innerxml to find the string that we
// can unmarshal this nested element from.
reader := strings.NewReader(st.innerxml())
- p := xml.NewParser(reader)
+ p := xml.NewDecoder(reader)
for {
t, err := p.Token()
- if err == os.EOF {
+ if err == io.EOF {
break
}
if err != nil {
@@ -201,7 +203,7 @@
// Unmarshal the nested element and
// stuff it back into the stanza.
- err := p.Unmarshal(nested, &se)
+ err := p.DecodeElement(nested, &se)
if err != nil {
return err
}
@@ -225,13 +227,26 @@
}
}(w)
+ enc := xml.NewEncoder(w)
+ enc.Context.Map[NsClient] = ""
+ enc.Context.Map[NsStream] = "stream"
+
for obj := range ch {
- err := xml.Marshal(w, obj)
- if err != nil {
- if Log != nil {
- Log.Err("write: " + err.String())
+ if st, ok := obj.(*stream); ok {
+ _, err := w.Write([]byte(st.String()))
+ if err != nil {
+ if Log != nil {
+ Log.Err("write: " + err.Error())
+ }
}
- break
+ } else {
+ err := enc.Encode(obj)
+ if err != nil {
+ if Log != nil {
+ Log.Err("marshal: " + err.Error())
+ }
+ break
+ }
}
}
}
@@ -277,7 +292,7 @@
}
if handlers[st.GetId()] != nil {
f := handlers[st.GetId()]
- handlers[st.GetId()] = nil
+ delete(handlers, st.GetId())
send = f(st)
}
if send {
@@ -410,10 +425,6 @@
cl.socket = tls
cl.socketSync.Wait()
- // Reset the read timeout on the (underlying) socket so the
- // reader doesn't get woken up unnecessarily.
- tcp.SetReadTimeout(0)
-
if Log != nil {
Log.Info("TLS negotiation succeeded.")
}
@@ -464,7 +475,7 @@
if err != nil {
if Log != nil {
Log.Err("SASL challenge decode: " +
- err.String())
+ err.Error())
}
return
}
@@ -531,7 +542,7 @@
cnonce, err := rand.Int(rand.Reader, randSize)
if err != nil {
if Log != nil {
- Log.Err("SASL rand: " + err.String())
+ Log.Err("SASL rand: " + err.Error())
}
return
}
@@ -609,7 +620,7 @@
h := func(text string) []byte {
h := md5.New()
h.Write([]byte(text))
- return h.Sum()
+ return h.Sum(nil)
}
hex := func(bytes []byte) string {
return fmt.Sprintf("%x", bytes)
@@ -636,14 +647,20 @@
}
msg := &Iq{Type: "set", Id: <-Id, Nested: []interface{}{bindReq}}
f := func(st Stanza) bool {
- if st.GetType() == "error" {
+ iq, ok := st.(*Iq)
+ if !ok {
+ if Log != nil {
+ Log.Err("non-iq response")
+ }
+ }
+ if iq.Type == "error" {
if Log != nil {
Log.Err("Resource binding failed")
}
return false
}
var bindRepl *bindIq
- for _, ele := range st.GetNested() {
+ for _, ele := range iq.Nested {
if b, ok := ele.(*bindIq); ok {
bindRepl = b
break
@@ -652,7 +669,7 @@
if bindRepl == nil {
if Log != nil {
Log.Err(fmt.Sprintf("Bad bind reply: %v",
- st))
+ iq))
}
return false
}
@@ -664,9 +681,10 @@
return false
}
jid := new(JID)
- if !jid.Set(*jidStr) {
+ if err := jid.Set(*jidStr); err != nil {
if Log != nil {
- Log.Err("Can't parse JID " + *jidStr)
+ Log.Err(fmt.Sprintf("Can't parse JID %s: %s",
+ *jidStr, err))
}
return false
}
--- a/structs.go Mon Jan 23 21:54:41 2012 -0700
+++ b/structs.go Sun Dec 16 13:03:03 2012 -0700
@@ -8,14 +8,14 @@
import (
"bytes"
+ "encoding/xml"
+ "errors"
"flag"
"fmt"
- "go-idn.googlecode.com/hg/src/stringprep"
- "io"
- "os"
+ // BUG(cjyar): We should use stringprep
+ // "code.google.com/p/go-idn/src/stringprep"
"regexp"
"strings"
- "xml"
)
// JID represents an entity that can communicate with other
@@ -32,31 +32,28 @@
// 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"`
+ 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 _ xml.Marshaler = &stream{}
var _ fmt.Stringer = &stream{}
// <stream:error>
type streamError struct {
- Any Generic
+ XMLName xml.Name `xml:"http://etherx.jabber.org/streams error"`
+ Any Generic `xml:",any"`
Text *errText
}
-var _ xml.Marshaler = &streamError{}
-
type errText struct {
- Lang string `xml:"attr"`
- Text string `xml:"chardata"`
+ 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"`
}
-var _ xml.Marshaler = &errText{}
-
type Features struct {
Starttls *starttls
Mechanisms mechs
@@ -84,88 +81,84 @@
// One of the three core XMPP stanza types: iq, message, presence. See
// RFC3920, section 9.
type Stanza interface {
- // Returns "iq", "message", or "presence".
- GetName() string
- // The to attribute.
- GetTo() string
- // The from attribute.
- GetFrom() string
- // The id attribute.
+ // // Returns "iq", "message", or "presence".
+ // GetName() string
+ // // The to attribute.
+ // GetTo() string
+ // // The from attribute.
+ // GetFrom() string
+ // The id attribute. TODO maybe remove this.
GetId() string
- // The type attribute.
- GetType() string
- // The xml:lang attribute.
- GetLang() string
- // A nested error element, if any.
- GetError() *Error
- // Zero or more (non-error) nested elements. These will be in
- // namespaces managed by extensions.
- GetNested() []interface{}
+ // // The type attribute.
+ // GetType() string
+ // // The xml:lang attribute.
+ // GetLang() string
+ // // A nested error element, if any.
+ // GetError() *Error
+ // // Zero or more (non-error) nested elements. These will be in
+ // // namespaces managed by extensions.
+ // GetNested() []interface{}
addNested(interface{})
innerxml() string
}
// 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"`
- Innerxml string `xml:"innerxml"`
+ XMLName xml.Name `xml:"message"`
+ 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
- Subject *Generic
- Body *Generic
- Thread *Generic
+ Subject *Generic `xml:"subject"`
+ Body *Generic `xml:"body"`
+ Thread *Generic `xml:"thread"`
Nested []interface{}
}
-
-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"`
- Innerxml string `xml:"innerxml"`
+ XMLName xml.Name `xml:"presence"`
+ 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
- Show *Generic
- Status *Generic
- Priority *Generic
+ Show *Generic `xml:"show"`
+ Status *Generic `xml:"status"`
+ Priority *Generic `xml:"priority"`
Nested []interface{}
}
-
-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"`
- Innerxml string `xml:"innerxml"`
+ XMLName xml.Name `xml:"iq"`
+ 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{}
}
-
-var _ xml.Marshaler = &Iq{}
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:"attr"`
+ Type string `xml:"type,attr"`
// Any nested element, if present.
Any *Generic
}
-
-var _ os.Error = &Error{}
+var _ error = &Error{}
// Used for resource binding as a nested element inside <iq/>.
type bindIq struct {
@@ -177,10 +170,9 @@
// Holds an XML element not described by the more specific types.
type Generic struct {
XMLName xml.Name
- Any *Generic
- Chardata string `xml:"chardata"`
+ Any *Generic `xml:",any"`
+ Chardata string `xml:",chardata"`
}
-
var _ fmt.Stringer = &Generic{}
func (jid *JID) String() string {
@@ -196,39 +188,58 @@
// Set implements flag.Value. It returns true if it successfully
// parses the string.
-func (jid *JID) Set(val string) bool {
+func (jid *JID) Set(val string) error {
r := regexp.MustCompile("^(([^@/]+)@)?([^@/]+)(/([^@/]+))?$")
parts := r.FindStringSubmatch(val)
if parts == nil {
- return false
+ 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])
- 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
+ // 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 {
- result, _ := s.MarshalXML()
- return string(result)
+ 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, os.Error) {
+func parseStream(se xml.StartElement) (*stream, error) {
s := &stream{}
for _, attr := range se.Attr {
switch strings.ToLower(attr.Name.Local) {
@@ -247,38 +258,6 @@
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 {
if u == nil {
return "nil"
@@ -292,107 +271,21 @@
u.XMLName.Local)
}
-func marshalXML(st Stanza) ([]byte, os.Error) {
- buf := bytes.NewBuffer(nil)
- buf.WriteString("<")
- buf.WriteString(st.GetName())
- if st.GetTo() != "" {
- writeField(buf, "to", st.GetTo())
- }
- if st.GetFrom() != "" {
- writeField(buf, "from", st.GetFrom())
- }
- if st.GetId() != "" {
- writeField(buf, "id", st.GetId())
- }
- if st.GetType() != "" {
- writeField(buf, "type", st.GetType())
- }
- if st.GetLang() != "" {
- writeField(buf, "xml:lang", st.GetLang())
- }
- buf.WriteString(">")
-
- if m, ok := st.(*Message); ok {
- err := xml.Marshal(buf, m.Subject)
- if err != nil {
- return nil, err
- }
- err = xml.Marshal(buf, m.Body)
- if err != nil {
- return nil, err
- }
- err = xml.Marshal(buf, m.Thread)
- if err != nil {
- return nil, err
- }
- }
- if p, ok := st.(*Presence); ok {
- err := xml.Marshal(buf, p.Show)
- if err != nil {
- return nil, err
+func (er *Error) Error() string {
+ buf, err := xml.Marshal(er)
+ if err != nil {
+ if Log != nil {
+ Log.Err("double bad error couldn't marshal error")
}
- err = xml.Marshal(buf, p.Status)
- if err != nil {
- return nil, err
- }
- err = xml.Marshal(buf, p.Priority)
- if err != nil {
- return nil, err
- }
- }
- if nested := st.GetNested(); nested != nil {
- for _, n := range nested {
- xml.Marshal(buf, n)
- }
- } else if st.innerxml() != "" {
- buf.WriteString(st.innerxml())
+ return "unreadable error"
}
-
- buf.WriteString("</")
- buf.WriteString(st.GetName())
- buf.WriteString(">")
- return buf.Bytes(), nil
-}
-
-func (er *Error) String() string {
- buf := bytes.NewBuffer(nil)
- xml.Marshal(buf, er)
- return buf.String()
-}
-
-func (m *Message) GetName() string {
- return "message"
-}
-
-func (m *Message) GetTo() string {
- return m.To
-}
-
-func (m *Message) GetFrom() string {
- return m.From
+ return string(buf)
}
func (m *Message) GetId() string {
return m.Id
}
-func (m *Message) GetType() string {
- return m.Type
-}
-
-func (m *Message) GetLang() string {
- return m.Lang
-}
-
-func (m *Message) GetError() *Error {
- return m.Error
-}
-
-func (m *Message) GetNested() []interface{} {
- return m.Nested
-}
-
func (m *Message) addNested(n interface{}) {
m.Nested = append(m.Nested, n)
}
@@ -401,42 +294,10 @@
return m.Innerxml
}
-func (m *Message) MarshalXML() ([]byte, os.Error) {
- return marshalXML(m)
-}
-
-func (p *Presence) GetName() string {
- return "presence"
-}
-
-func (p *Presence) GetTo() string {
- return p.To
-}
-
-func (p *Presence) GetFrom() string {
- return p.From
-}
-
func (p *Presence) GetId() string {
return p.Id
}
-func (p *Presence) GetType() string {
- return p.Type
-}
-
-func (p *Presence) GetLang() string {
- return p.Lang
-}
-
-func (p *Presence) GetError() *Error {
- return p.Error
-}
-
-func (p *Presence) GetNested() []interface{} {
- return p.Nested
-}
-
func (p *Presence) addNested(n interface{}) {
p.Nested = append(p.Nested, n)
}
@@ -445,42 +306,10 @@
return p.Innerxml
}
-func (p *Presence) MarshalXML() ([]byte, os.Error) {
- return marshalXML(p)
-}
-
-func (iq *Iq) GetName() string {
- return "iq"
-}
-
-func (iq *Iq) GetTo() string {
- return iq.To
-}
-
-func (iq *Iq) GetFrom() string {
- return iq.From
-}
-
func (iq *Iq) GetId() string {
return iq.Id
}
-func (iq *Iq) GetType() string {
- return iq.Type
-}
-
-func (iq *Iq) GetLang() string {
- return iq.Lang
-}
-
-func (iq *Iq) GetError() *Error {
- return iq.Error
-}
-
-func (iq *Iq) GetNested() []interface{} {
- return iq.Nested
-}
-
func (iq *Iq) addNested(n interface{}) {
iq.Nested = append(iq.Nested, n)
}
@@ -489,22 +318,19 @@
return iq.Innerxml
}
-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) {
+func ParseStanza(str string) (Stanza, error) {
r := strings.NewReader(str)
- p := xml.NewParser(r)
+ p := xml.NewDecoder(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")
+ return nil, errors.New("Not a start element")
}
var stan Stanza
switch se.Name.Local {
@@ -515,9 +341,9 @@
case "presence":
stan = &Presence{}
default:
- return nil, os.NewError("Not iq, message, or presence")
+ return nil, errors.New("Not iq, message, or presence")
}
- err = p.Unmarshal(stan, &se)
+ err = p.DecodeElement(stan, &se)
if err != nil {
return nil, err
}
--- a/structs_test.go Mon Jan 23 21:54:41 2012 -0700
+++ b/structs_test.go Sun Dec 16 13:03:03 2012 -0700
@@ -6,8 +6,8 @@
import (
"bytes"
+ "encoding/xml"
"testing"
- "xml"
)
func assertEquals(t *testing.T, expected, observed string) {
@@ -20,8 +20,8 @@
func TestJid(t *testing.T) {
str := "user@domain/res"
jid := &JID{}
- if !jid.Set(str) {
- t.Errorf("Set(%s) failed\n", str)
+ if err := jid.Set(str); err != nil {
+ t.Errorf("Set(%s) failed: %s", str, err)
}
assertEquals(t, "user", jid.Node)
assertEquals(t, "domain", jid.Domain)
@@ -29,8 +29,8 @@
assertEquals(t, str, jid.String())
str = "domain.tld"
- if !jid.Set(str) {
- t.Errorf("Set(%s) failed\n", str)
+ if err := jid.Set(str); err != nil {
+ t.Errorf("Set(%s) failed: %s", str, err)
}
if jid.Node != "" {
t.Errorf("Node: %v\n", jid.Node)
@@ -43,28 +43,34 @@
}
func assertMarshal(t *testing.T, expected string, marshal interface{}) {
- buf := bytes.NewBuffer(nil)
- xml.Marshal(buf, marshal)
- observed := string(buf.Bytes())
+ var buf bytes.Buffer
+ enc := xml.NewEncoder(&buf)
+ enc.Context.Map[NsClient] = ""
+ enc.Context.Map[NsStream] = "stream"
+ err := enc.Encode(marshal)
+ if err != nil {
+ t.Errorf("Marshal error for %s: %s", marshal, err)
+ }
+ observed := buf.String()
assertEquals(t, expected, observed)
}
func TestStreamMarshal(t *testing.T) {
s := &stream{To: "bob"}
- exp := `<stream:stream xmlns="jabber:client"` +
- ` xmlns:stream="` + NsStream + `" to="bob">`
- assertMarshal(t, exp, s)
+ exp := `<stream:stream xmlns="` + NsClient +
+ `" xmlns:stream="` + NsStream + `" to="bob">`
+ assertEquals(t, exp, s.String())
s = &stream{To: "bob", From: "alice", Id: "#3", Version: "5.3"}
- exp = `<stream:stream xmlns="jabber:client"` +
- ` xmlns:stream="` + NsStream + `" to="bob" from="alice"` +
+ exp = `<stream:stream xmlns="` + NsClient +
+ `" xmlns:stream="` + NsStream + `" to="bob" from="alice"` +
` id="#3" version="5.3">`
- assertMarshal(t, exp, s)
+ assertEquals(t, exp, s.String())
s = &stream{Lang: "en_US"}
- exp = `<stream:stream xmlns="jabber:client"` +
- ` xmlns:stream="` + NsStream + `" xml:lang="en_US">`
- assertMarshal(t, exp, s)
+ exp = `<stream:stream xmlns="` + NsClient +
+ `" xmlns:stream="` + NsStream + `" xml:lang="en_US">`
+ assertEquals(t, exp, s.String())
}
func TestStreamErrorMarshal(t *testing.T) {
@@ -97,32 +103,41 @@
if err != nil {
t.Fatalf("iq: %v", err)
}
- assertEquals(t, "iq", st.GetName())
- assertEquals(t, "alice", st.GetTo())
- assertEquals(t, "bob", st.GetFrom())
- assertEquals(t, "1", st.GetId())
- assertEquals(t, "A", st.GetType())
- assertEquals(t, "en", st.GetLang())
- if st.GetError() != nil {
- t.Errorf("iq: error %v", st.GetError())
+ iq, ok := st.(*Iq)
+ if !ok {
+ t.Fatalf("not iq: %v", st)
+ }
+ assertEquals(t, "iq", iq.XMLName.Local)
+ assertEquals(t, "alice", iq.To)
+ assertEquals(t, "bob", iq.From)
+ assertEquals(t, "1", iq.Id)
+ assertEquals(t, "A", iq.Type)
+ assertEquals(t, "en", iq.Lang)
+ if iq.Error != nil {
+ t.Errorf("iq: error %v", iq.Error)
}
if st.innerxml() == "" {
t.Errorf("iq: empty child")
}
assertEquals(t, "<foo>text</foo>", st.innerxml())
+ assertEquals(t, st.innerxml(), iq.Innerxml)
str = `<message to="alice" from="bob"/>`
st, err = ParseStanza(str)
if err != nil {
t.Fatalf("message: %v", err)
}
- assertEquals(t, "message", st.GetName())
- assertEquals(t, "alice", st.GetTo())
- assertEquals(t, "bob", st.GetFrom())
- assertEquals(t, "", st.GetId())
- assertEquals(t, "", st.GetLang())
- if st.GetError() != nil {
- t.Errorf("message: error %v", st.GetError())
+ m, ok := st.(*Message)
+ if !ok {
+ t.Fatalf("not message: %v", st)
+ }
+ assertEquals(t, "message", m.XMLName.Local)
+ assertEquals(t, "alice", m.To)
+ assertEquals(t, "bob", m.From)
+ assertEquals(t, "", m.Id)
+ assertEquals(t, "", m.Lang)
+ if m.Error != nil {
+ t.Errorf("message: error %v", m.Error)
}
if st.innerxml() != "" {
t.Errorf("message: child %v", st.innerxml())
@@ -133,7 +148,10 @@
if err != nil {
t.Fatalf("presence: %v", err)
}
- assertEquals(t, "presence", st.GetName())
+ _, ok = st.(*Presence)
+ if !ok {
+ t.Fatalf("not presence: %v", st)
+ }
}
func TestMarshalEscaping(t *testing.T) {
--- a/xmpp.go Mon Jan 23 21:54:41 2012 -0700
+++ b/xmpp.go Sun Dec 16 13:03:03 2012 -0700
@@ -8,13 +8,13 @@
import (
"bytes"
+ "encoding/xml"
+ "errors"
"fmt"
"io"
+ "log/syslog"
"net"
- "os"
"sync"
- "syslog"
- "xml"
)
const (
@@ -22,6 +22,7 @@
Version = "1.0"
// Various XML namespaces.
+ NsClient = "jabber:client"
NsStreams = "urn:ietf:params:xml:ns:xmpp-streams"
NsStream = "http://etherx.jabber.org/streams"
NsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
@@ -106,8 +107,7 @@
// has completed. The negotiation will occur asynchronously, and any
// send operation to Client.Out will block until negotiation (resource
// binding) is complete.
-func NewClient(jid *JID, password string, exts []Extension) (*Client,
-os.Error) {
+func NewClient(jid *JID, password string, exts []Extension) (*Client, error) {
// Include the mandatory extensions.
exts = append(exts, rosterExt)
exts = append(exts, bindExt)
@@ -115,8 +115,8 @@
// Resolve the domain in the JID.
_, srvs, err := net.LookupSRV(clientSrv, "tcp", jid.Domain)
if err != nil {
- return nil, os.NewError("LookupSrv " + jid.Domain +
- ": " + err.String())
+ return nil, errors.New("LookupSrv " + jid.Domain +
+ ": " + err.Error())
}
var tcp *net.TCPConn
@@ -124,14 +124,14 @@
addrStr := fmt.Sprintf("%s:%d", srv.Target, srv.Port)
addr, err := net.ResolveTCPAddr("tcp", addrStr)
if err != nil {
- err = os.NewError(fmt.Sprintf("ResolveTCPAddr(%s): %s",
- addrStr, err.String()))
+ err = fmt.Errorf("ResolveTCPAddr(%s): %s",
+ addrStr, err.Error())
continue
}
tcp, err = net.DialTCP("tcp", nil, addr)
if err != nil {
- err = os.NewError(fmt.Sprintf("DialTCP(%s): %s",
- addr, err.String()))
+ err = fmt.Errorf("DialTCP(%s): %s",
+ addr, err)
continue
}
}
@@ -271,17 +271,25 @@
// session, retrieve the roster, and broadcast an initial
// presence. The presence can be as simple as a newly-initialized
// Presence struct. See RFC 3921, Section 3.
-func (cl *Client) StartSession(getRoster bool, pr *Presence) os.Error {
+func (cl *Client) StartSession(getRoster bool, pr *Presence) error {
id := <-Id
iq := &Iq{To: cl.Jid.Domain, Id: id, Type: "set", Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsSession, Local: "session"}}}}
- ch := make(chan os.Error)
+ ch := make(chan error)
f := func(st Stanza) bool {
- if st.GetType() == "error" {
+ iq, ok := st.(*Iq)
+ if !ok {
+ if Log != nil {
+ Log.Err("iq reply not iq; can't start session")
+ }
+ ch <- errors.New("bad session start reply")
+ return false
+ }
+ if iq.Type == "error" {
if Log != nil {
Log.Err(fmt.Sprintf("Can't start session: %v",
- st))
+ iq))
}
- ch <- st.GetError()
+ ch <- iq.Error
return false
}
ch <- nil
--- a/xmpp_test.go Mon Jan 23 21:54:41 2012 -0700
+++ b/xmpp_test.go Sun Dec 16 13:03:03 2012 -0700
@@ -6,29 +6,30 @@
import (
"bytes"
+ "encoding/xml"
"reflect"
"strings"
"sync"
"testing"
- "xml"
)
func TestReadError(t *testing.T) {
- r := strings.NewReader(`<stream:error><bad-foo/></stream:error>`)
+ r := strings.NewReader(`<stream:error><bad-foo xmlns="blah"/>` +
+ `</stream:error>`)
ch := make(chan interface{})
go readXml(r, ch, make(map[string]func(*xml.Name) interface{}))
x := <-ch
se, ok := x.(*streamError)
if !ok {
- t.Fatalf("not StreamError: %v", reflect.TypeOf(x))
+ t.Fatalf("not StreamError: %T", x)
}
assertEquals(t, "bad-foo", se.Any.XMLName.Local)
- assertEquals(t, "", se.Any.XMLName.Space)
+ assertEquals(t, "blah", se.Any.XMLName.Space)
if se.Text != nil {
t.Errorf("text not nil: %v", se.Text)
}
- r = strings.NewReader(`<stream:error><bad-foo/>` +
+ r = strings.NewReader(`<stream:error><bad-foo xmlns="blah"/>` +
`<text xml:lang="en" xmlns="` + NsStreams +
`">Error text</text></stream:error>`)
ch = make(chan interface{})
@@ -39,7 +40,7 @@
t.Fatalf("not StreamError: %v", reflect.TypeOf(x))
}
assertEquals(t, "bad-foo", se.Any.XMLName.Local)
- assertEquals(t, "", se.Any.XMLName.Space)
+ assertEquals(t, "blah", se.Any.XMLName.Space)
assertEquals(t, "Error text", se.Text.Text)
assertEquals(t, "en", se.Text.Lang)
}
@@ -47,7 +48,7 @@
func TestReadStream(t *testing.T) {
r := strings.NewReader(`<stream:stream to="foo.com" ` +
`from="bar.org" id="42"` +
- `xmlns="jabber:client" xmlns:stream="` + NsStream +
+ `xmlns="` + NsClient + `" xmlns:stream="` + NsStream +
`" version="1.0">`)
ch := make(chan interface{})
go readXml(r, ch, make(map[string]func(*xml.Name) interface{}))
@@ -94,8 +95,8 @@
func TestWriteStream(t *testing.T) {
ss := &stream{To: "foo.org", From: "bar.com", Id: "42", Lang: "en", Version: "1.0"}
str := testWrite(ss)
- exp := `<stream:stream xmlns="jabber:client"` +
- ` xmlns:stream="` + NsStream + `" to="foo.org"` +
+ exp := `<stream:stream xmlns="` + NsClient +
+ `" xmlns:stream="` + NsStream + `" to="foo.org"` +
` from="bar.com" id="42" xml:lang="en" version="1.0">`
assertEquals(t, exp, str)
}