# HG changeset patch # User Chris Jones # Date 1355688183 25200 # Node ID c9cc4eda6dce72e44b86b27cc80f3477c5fd2862 # Parent d2ec96c80efe96b04fb4845bd43ae1259729dc25 Updated for Go 1.0 + upcoming XML fixes. diff -r d2ec96c80efe -r c9cc4eda6dce Makefile --- 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 diff -r d2ec96c80efe -r c9cc4eda6dce examples/Makefile --- 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 diff -r d2ec96c80efe -r c9cc4eda6dce roster.go --- 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 } diff -r d2ec96c80efe -r c9cc4eda6dce roster_test.go --- 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 := `` assertMarshal(t, exp, iq) @@ -23,18 +23,17 @@ func TestRosterIqUnmarshal(t *testing.T) { str := `` - 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") } diff -r d2ec96c80efe -r c9cc4eda6dce stream.go --- 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 } diff -r d2ec96c80efe -r c9cc4eda6dce structs.go --- 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 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{} // 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 . 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("") - // We never write - 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(`") + 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("") - xml.Marshal(buf, s.Any) - if s.Text != nil { - xml.Marshal(buf, s.Text) - } - buf.WriteString("") - return buf.Bytes(), nil -} - -func (e *errText) MarshalXML() ([]byte, os.Error) { - buf := bytes.NewBuffer(nil) - buf.WriteString("") - xml.Escape(buf, []byte(e.Text)) - buf.WriteString("") - 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("") - 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 } diff -r d2ec96c80efe -r c9cc4eda6dce structs_test.go --- 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 := `` - assertMarshal(t, exp, s) + exp := `` + assertEquals(t, exp, s.String()) s = &stream{To: "bob", From: "alice", Id: "#3", Version: "5.3"} - exp = `` - assertMarshal(t, exp, s) + assertEquals(t, exp, s.String()) s = &stream{Lang: "en_US"} - exp = `` - assertMarshal(t, exp, s) + exp = `` + 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, "text", st.innerxml()) + assertEquals(t, st.innerxml(), iq.Innerxml) str = `` 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) { diff -r d2ec96c80efe -r c9cc4eda6dce xmpp.go --- 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 diff -r d2ec96c80efe -r c9cc4eda6dce xmpp_test.go --- 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(``) + r := strings.NewReader(`` + + ``) 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(`` + + r = strings.NewReader(`` + `Error text`) 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(``) 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 := `` assertEquals(t, exp, str) }