# HG changeset patch # User Chris Jones # Date 1325306940 25200 # Node ID 9fe022261dccfe2bf6d9ec7e17d5867552055516 # Parent 569833f08780c33c4a8d6837a83cdd82a22f2ff7 Added a capability to use extensions. There are still some bugs with marshaling involving receiver functions on embedded structs. diff -r 569833f08780 -r 9fe022261dcc Makefile --- a/Makefile Fri Dec 30 18:25:08 2011 -0700 +++ b/Makefile Fri Dec 30 21:49:00 2011 -0700 @@ -7,6 +7,7 @@ TARG=cjyar/xmpp GOFILES=\ xmpp.go \ + roster.go \ stream.go \ structs.go \ diff -r 569833f08780 -r 9fe022261dcc examples/interact.go --- a/examples/interact.go Fri Dec 30 18:25:08 2011 -0700 +++ b/examples/interact.go Fri Dec 30 21:49:00 2011 -0700 @@ -24,7 +24,7 @@ os.Exit(2) } - c, err := xmpp.NewClient(&jid, *pw) + c, err := xmpp.NewClient(&jid, *pw, nil) if err != nil { log.Fatalf("NewClient(%v): %v", jid, err) } diff -r 569833f08780 -r 9fe022261dcc roster.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roster.go Fri Dec 30 21:49:00 2011 -0700 @@ -0,0 +1,91 @@ +// 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 + +import ( + "fmt" + "io" + "os" + "xml" +) + +// This file contains support for roster management, RFC 3921, Section 7. + +type RosterIq struct { + Iq + Query RosterQuery +} +var _ ExtendedStanza = &RosterIq{} + +// Roster query/result +type RosterQuery struct { + // Should always be NsRoster, "query" + XMLName xml.Name + Item []RosterItem +} + +// See RFC 3921, Section 7.1. +type RosterItem struct { + // Should always be "item" + XMLName xml.Name + Jid string `xml:"attr"` + Subscription string `xml:"attr"` + Name string `xml:"attr"` + Group []string +} + +func (riq *RosterIq) InnerMarshal(w io.Writer) os.Error { + return xml.Marshal(w, riq.Query) +} + +// Implicitly becomes part of NewClient's extStanza arg. +func rosterStanza(name *xml.Name) ExtendedStanza { + return &RosterIq{} +} + +// Synchronously fetch this entity's roster from the server and cache +// that information. +func (cl *Client) fetchRoster() os.Error { + iq := &RosterIq{Iq: Iq{From: cl.Jid.String(), Id: <- cl.Id, + Type: "get"}, Query: RosterQuery{XMLName: + xml.Name{Local: "query", Space: NsRoster}}} + ch := make(chan os.Error) + f := func(st Stanza) bool { + iq, ok := st.(*RosterIq) + if !ok { + ch <- os.NewError(fmt.Sprintf( + "Roster query result not iq: %v", st)) + return false + } + if iq.Type == "error" { + ch <- iq.Error + return false + } + q := iq.Query + cl.roster = make(map[string] *RosterItem, len(q.Item)) + for _, item := range(q.Item) { + cl.roster[item.Jid] = &item + } + ch <- nil + return false + } + cl.HandleStanza(iq.Id, f) + cl.Out <- iq + // Wait for f to complete. + return <- ch +} + +// BUG(cjyar) The roster isn't actually updated when things change. + +// Returns the current roster of other entities which this one has a +// relationship with. Changes to the roster will be signaled by an +// appropriate Iq appearing on Client.In. See RFC 3921, Section 7.4. +func (cl *Client) Roster() map[string] *RosterItem { + r := make(map[string] *RosterItem) + for key, val := range(cl.roster) { + r[key] = val + } + return r +} diff -r 569833f08780 -r 9fe022261dcc roster_test.go --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/roster_test.go Fri Dec 30 21:49:00 2011 -0700 @@ -0,0 +1,25 @@ +// 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 + +import ( + "testing" + "xml" +) + +// This is mostly just tests of the roster data structures. + +func TestRosterIqMarshal(t *testing.T) { + iq := &RosterIq{Iq: Iq{From: "from", Lang: "en"}, Query: + RosterQuery{XMLName: xml.Name{Space: NsRoster, Local: + "query"}, Item: []RosterItem{}}} + var s Stanza = iq + if _, ok := s.(ExtendedStanza) ; !ok { + t.Errorf("Not an ExtendedStanza") + } + exp := `` + assertMarshal(t, exp, iq) +} diff -r 569833f08780 -r 9fe022261dcc stream.go --- a/stream.go Fri Dec 30 18:25:08 2011 -0700 +++ b/stream.go Fri Dec 30 21:49:00 2011 -0700 @@ -12,6 +12,7 @@ import ( "big" + "bytes" "crypto/md5" "crypto/rand" "crypto/tls" @@ -80,7 +81,8 @@ } } -func readXml(r io.Reader, ch chan<- interface{}) { +func readXml(r io.Reader, ch chan<- interface{}, + extStanza map[string] func(*xml.Name) ExtendedStanza) { if debug { pr, pw := io.Pipe() go tee(r, pw, "S: ") @@ -144,9 +146,26 @@ break } - // BUG(cjyar) If it's a Stanza, use reflection to - // search for any Generic elements and fill in - // their attributes. + // If it's a Stanza, we check its "Any" element for a + // namespace that's registered with one of our + // extensions. If so, we need to re-unmarshal into an + // object of the correct type. + if st, ok := obj.(Stanza) ; ok && st.XChild() != nil { + name := st.XChild().XMLName + ns := name.Space + con := extStanza[ns] + if con != nil { + obj = con(&name) + xmlStr, _ := marshalXML(st) + r := bytes.NewBuffer(xmlStr) + err = xml.Unmarshal(r, &obj) + if err != nil { + log.Printf("ext unmarshal: %v", + err) + break + } + } + } // Put it on the channel. ch <- obj diff -r 569833f08780 -r 9fe022261dcc structs.go --- a/structs.go Fri Dec 30 18:25:08 2011 -0700 +++ b/structs.go Fri Dec 30 21:49:00 2011 -0700 @@ -95,6 +95,11 @@ XError() *Error // A (non-error) nested element, if any. XChild() *Generic + innerxml() string +} + +type ExtendedStanza interface { + InnerMarshal(io.Writer) os.Error } // message stanza @@ -104,6 +109,7 @@ Id string `xml:"attr"` Type string `xml:"attr"` Lang string `xml:"attr"` + Innerxml string `xml:"innerxml"` Error *Error Subject *Generic Body *Generic @@ -112,6 +118,7 @@ } var _ xml.Marshaler = &Message{} var _ Stanza = &Message{} +var _ ExtendedStanza = &Message{} // presence stanza type Presence struct { @@ -120,6 +127,7 @@ Id string `xml:"attr"` Type string `xml:"attr"` Lang string `xml:"attr"` + Innerxml string `xml:"innerxml"` Error *Error Show *Generic Status *Generic @@ -128,6 +136,7 @@ } var _ xml.Marshaler = &Presence{} var _ Stanza = &Presence{} +var _ ExtendedStanza = &Presence{} // iq stanza type Iq struct { @@ -136,30 +145,13 @@ Id string `xml:"attr"` Type string `xml:"attr"` Lang string `xml:"attr"` + Innerxml string `xml:"innerxml"` Error *Error Any *Generic - Query *RosterQuery } var _ xml.Marshaler = &Iq{} var _ Stanza = &Iq{} -// Roster query/result -type RosterQuery struct { - // Should always be query in the NsRoster namespace - XMLName xml.Name - Item []RosterItem -} - -// See RFC 3921, Section 7.1. -type RosterItem struct { - // Should always be "item" - XMLName xml.Name - Jid string `xml:"attr"` - Subscription string `xml:"attr"` - Name string `xml:"attr"` - Group []string -} - // Describes an XMPP stanza error. See RFC 3920, Section 9.3. type Error struct { // The error type attribute. @@ -282,8 +274,6 @@ u.XMLName.Local) } -// BUG(cjyar) This is fragile. We should find a way to use go's native -// XML marshaling. func marshalXML(st Stanza) ([]byte, os.Error) { buf := bytes.NewBuffer(nil) buf.WriteString("<") @@ -304,15 +294,22 @@ 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()) - } - if iq, ok := st.(*Iq) ; ok && iq.Query != nil { - xml.Marshal(buf, iq.Query) + if ext, ok := st.(ExtendedStanza) ; ok { + if st.XError() != nil { + bytes, _ := st.XError().MarshalXML() + buf.WriteString(string(bytes)) + } + err := ext.InnerMarshal(buf) + if err != nil { + return nil, err + } + } else { + inner := st.innerxml() + if inner == "" { + xml.Marshal(buf, st.XChild()) + } else { + buf.WriteString(st.innerxml()) + } } buf.WriteString("`) ch := make(chan interface{}) - go readXml(r, ch) + go readXml(r, ch, make(map[string] func(*xml.Name) ExtendedStanza)) x := <- ch se, ok := x.(*streamError) if !ok { @@ -32,7 +32,7 @@ `Error text`) ch = make(chan interface{}) - go readXml(r, ch) + go readXml(r, ch, make(map[string] func(*xml.Name) ExtendedStanza)) x = <- ch se, ok = x.(*streamError) if !ok { @@ -50,7 +50,7 @@ `xmlns="jabber:client" xmlns:stream="` + NsStream + `" version="1.0">`) ch := make(chan interface{}) - go readXml(r, ch) + go readXml(r, ch, make(map[string] func(*xml.Name) ExtendedStanza)) x := <- ch ss, ok := x.(*stream) if !ok {