# HG changeset patch # User Chris Jones # Date 1378573484 25200 # Node ID 367e76b3028e51626971e9fb542981874086ec8a # Parent f464f14e39a7100a0c8c1506aea9d25cff9d4864 Moved the library code into an xmpp directory. diff -r f464f14e39a7 -r 367e76b3028e filter.go --- a/filter.go Mon Sep 02 20:46:23 2013 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,64 +0,0 @@ -package xmpp - -// Manages the stack of filters that can read and modify stanzas on -// their way from the remote to the application. - -// Receive new filters on filterAdd; those new filters get added to -// the top of the stack. Receive stanzas at the bottom of the stack on -// input. Send stanzas out the top of the stack on output. -func filterMgr(filterAdd <-chan Filter, input <-chan Stanza, output chan<- Stanza) { - botFiltIn := output - topFiltOut := input - -loop: - for { - select { - case stan, ok := <-input: - if !ok { - break loop - } - botFiltIn <- stan - - case stan, ok := <-topFiltOut: - if !ok { - break loop - } - output <- stan - - case filt := <-filterAdd: - newTop := make(chan Stanza) - go filt(topFiltOut, newTop) - topFiltOut = newTop - } - } - close(botFiltIn) -} - -// Starts the filter chain. Filters will all interpose themselves -// between srvIn and cliOut. -func (cl *Client) startFilters(srvIn, cliIn <-chan Stanza) (<-chan Stanza, <-chan Stanza) { - cliOut := make(chan Stanza) - srvOut := make(chan Stanza) - go filterMgr(cl.sendFilterAdd, srvIn, cliOut) - go filterMgr(cl.recvFilterAdd, cliIn, srvOut) - return cliOut, srvOut -} - -// AddRecvFilter adds a new filter to the top of the stack through which -// incoming stanzas travel on their way up to the client. -func (cl *Client) AddRecvFilter(filt Filter) { - if filt == nil { - return - } - cl.recvFilterAdd <- filt -} - -// AddSendFilter adds a new filter to the top of the stack through -// which outgoing stanzas travel on their way down from the client to -// the network. -func (cl *Client) AddSendFilter(filt Filter) { - if filt == nil { - return - } - cl.sendFilterAdd <- filt -} diff -r f464f14e39a7 -r 367e76b3028e id.go --- a/id.go Mon Sep 02 20:46:23 2013 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,29 +0,0 @@ -package xmpp - -// Code to generate unique IDs for outgoing messages. - -import ( - "fmt" -) - -var id <-chan string - -func init() { - // Start the unique id generator. - idCh := make(chan string) - id = idCh - go func(ch chan<- string) { - id := int64(1) - for { - str := fmt.Sprintf("id_%d", id) - ch <- str - id++ - } - }(idCh) -} - -// This function may be used as a convenient way to generate a unique -// id for an outgoing iq, message, or presence stanza. -func NextId() string { - return <-id -} diff -r f464f14e39a7 -r 367e76b3028e log.go --- a/log.go Mon Sep 02 20:46:23 2013 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,37 +0,0 @@ -// 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. - -// Control over logging from the XMPP library. - -package xmpp - -var ( - // If any of these are non-nil when NewClient() is called, - // they will be used to log messages of the indicated - // severity. - Warn Logger = &noLog{} - Info Logger = &noLog{} - Debug Logger = &noLog{} -) - -// Anything implementing Logger can receive log messages from the XMPP -// library. The default implementation doesn't log anything; it -// efficiently discards all messages. -type Logger interface { - Log(v ...interface{}) - Logf(fmt string, v ...interface{}) -} - -type noLog struct { - flags int - prefix string -} - -var _ Logger = &noLog{} - -func (l *noLog) Log(v ...interface{}) { -} - -func (l *noLog) Logf(fmt string, v ...interface{}) { -} diff -r f464f14e39a7 -r 367e76b3028e roster.go --- a/roster.go Mon Sep 02 20:46:23 2013 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,155 +0,0 @@ -// 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 support for roster management, RFC 3921, Section 7. - -import ( - "encoding/xml" -) - -// Roster query/result -type RosterQuery struct { - XMLName xml.Name `xml:"jabber:iq:roster query"` - Item []RosterItem `xml:"item"` -} - -// See RFC 3921, Section 7.1. -type RosterItem struct { - 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 -} - -type rosterCb struct { - id string - cb func() -} - -type Roster struct { - Extension - get chan []RosterItem - callbacks chan rosterCb - toServer chan Stanza -} - -type rosterClient struct { - rosterChan <-chan []RosterItem - rosterUpdate chan<- RosterItem -} - -// Implicitly becomes part of NewClient's extStanza arg. -func newRosterQuery(name *xml.Name) interface{} { - return &RosterQuery{} -} - -func (r *Roster) rosterMgr(upd <-chan Stanza) { - roster := make(map[string]RosterItem) - waits := make(map[string]func()) - var snapshot []RosterItem - for { - select { - case stan, ok := <- upd: - if !ok { - return - } - hdr := stan.GetHeader() - if f := waits[hdr.Id] ; f != nil { - delete(waits, hdr.Id) - f() - } - iq, ok := stan.(*Iq) - if iq.Type != "set" { - continue - } - var rq *RosterQuery - for _, ele := range iq.Nested { - if q, ok := ele.(*RosterQuery); ok { - rq = q - break - } - } - if rq == nil { - continue - } - for _, item := range rq.Item { - roster[item.Jid] = item - } - snapshot = []RosterItem{} - for _, ri := range roster { - snapshot = append(snapshot, ri) - } - case r.get <- snapshot: - case cb := <- r.callbacks: - waits[cb.id] = cb.cb - } - } -} - -func (r *Roster) makeFilters() (Filter, Filter) { - rosterUpdate := make(chan Stanza) - go r.rosterMgr(rosterUpdate) - recv := func(in <-chan Stanza, out chan<- Stanza) { - defer close(out) - for stan := range in { - rosterUpdate <- stan - out <- stan - } - } - send := func(in <-chan Stanza, out chan<- Stanza) { - defer close(out) - for { - select { - case stan, ok := <- in: - if !ok { - return - } - out <- stan - case stan := <- r.toServer: - out <- stan - } - } - } - return recv, send -} - -func newRosterExt() *Roster { - r := Roster{} - r.StanzaHandlers = make(map[string]func(*xml.Name) interface{}) - r.StanzaHandlers[NsRoster] = newRosterQuery - r.RecvFilter, r.SendFilter = r.makeFilters() - r.get = make(chan []RosterItem) - r.callbacks = make(chan rosterCb) - r.toServer = make(chan Stanza) - return &r -} - -// Return the most recent snapshot of the roster status. This is -// updated automatically as roster updates are received from the -// server, but especially in response to calls to Update(). -func (r *Roster) Get() []RosterItem { - return <-r.get -} - -// Synchronously fetch this entity's roster from the server and cache -// that information. The client can access the roster by watching for -// RosterQuery objects or by calling Get(). -func (r *Roster) Update() { - iq := &Iq{Header: Header{Type: "get", Id: NextId(), - Nested: []interface{}{RosterQuery{}}}} - waitchan := make(chan int) - done := func() { - close(waitchan) - } - r.waitFor(iq.Id, done) - r.toServer <- iq - <-waitchan -} - -func (r *Roster) waitFor(id string, cb func()) { - r.callbacks <- rosterCb{id: id, cb: cb} -} diff -r f464f14e39a7 -r 367e76b3028e roster_test.go --- a/roster_test.go Mon Sep 02 20:46:23 2013 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,55 +0,0 @@ -// 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 ( - "encoding/xml" - "reflect" - "testing" -) - -// This is mostly just tests of the roster data structures. - -func TestRosterIqMarshal(t *testing.T) { - iq := &Iq{Header: Header{From: "from", Lang: "en", - Nested: []interface{}{RosterQuery{}}}} - exp := `` - assertMarshal(t, exp, iq) -} - -func TestRosterIqUnmarshal(t *testing.T) { - str := `` - iq := Iq{} - xml.Unmarshal([]byte(str), &iq) - m := map[string]func(*xml.Name) interface{}{NsRoster: newRosterQuery} - err := parseExtended(&iq.Header, m) - if err != nil { - t.Fatalf("parseExtended: %v", err) - } - 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") - } - if len(nested) != 1 { - t.Fatalf("wrong size nested(%d): %v", len(nested), - nested) - } - var rq *RosterQuery - rq, ok := nested[0].(*RosterQuery) - if !ok { - t.Fatalf("nested not RosterQuery: %v", - reflect.TypeOf(nested)) - } - if len(rq.Item) != 1 { - t.Fatalf("Wrong # items: %v", rq.Item) - } - item := rq.Item[0] - assertEquals(t, "a@b.c", item.Jid) -} diff -r f464f14e39a7 -r 367e76b3028e stream.go --- a/stream.go Mon Sep 02 20:46:23 2013 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,609 +0,0 @@ -// 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. - -// This file contains the three layers of processing for the -// communication with the server: transport (where TLS happens), XML -// (where strings are converted to go structures), and Stream (where -// we respond to XMPP events on behalf of the library client), or send -// those events to the client. - -package xmpp - -import ( - "crypto/md5" - "crypto/rand" - "crypto/tls" - "encoding/base64" - "encoding/xml" - "fmt" - "io" - "math/big" - "net" - "regexp" - "strings" - "time" -) - -// Callback to handle a stanza with a particular id. -type stanzaHandler struct { - id string - // Return true means pass this to the application - f func(Stanza) bool -} - -func (cl *Client) readTransport(w io.WriteCloser) { - defer w.Close() - 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 { - if errno.Timeout() { - continue - } - } - Warn.Logf("read: %s", err) - break - } - nw, err := w.Write(p[:nr]) - if nw < nr { - Warn.Logf("read: %s", err) - break - } - } -} - -func (cl *Client) writeTransport(r io.Reader) { - defer cl.socket.Close() - p := make([]byte, 1024) - for { - nr, err := r.Read(p) - if nr == 0 { - Warn.Logf("write: %s", err) - break - } - nw, err := cl.socket.Write(p[:nr]) - if nw < nr { - Warn.Logf("write: %s", err) - break - } - } -} - -func readXml(r io.Reader, ch chan<- interface{}, - extStanza map[string]func(*xml.Name) interface{}) { - if _, ok := Debug.(*noLog); !ok { - pr, pw := io.Pipe() - go tee(r, pw, "S: ") - r = pr - } - defer close(ch) - - // This trick loads our namespaces into the parser. - nsstr := fmt.Sprintf(``, - NsClient, NsStream) - nsrdr := strings.NewReader(nsstr) - p := xml.NewDecoder(io.MultiReader(nsrdr, r)) - p.Token() - -Loop: - for { - // Sniff the next token on the stream. - t, err := p.Token() - if t == nil { - if err != io.EOF { - Warn.Logf("read: %s", err) - } - break - } - var se xml.StartElement - var ok bool - if se, ok = t.(xml.StartElement); !ok { - continue - } - - // Allocate the appropriate structure for this token. - var obj interface{} - switch se.Name.Space + " " + se.Name.Local { - case NsStream + " stream": - st, err := parseStream(se) - if err != nil { - Warn.Logf("unmarshal stream: %s", err) - break Loop - } - ch <- st - continue - case "stream error", NsStream + " error": - obj = &streamError{} - case NsStream + " features": - obj = &Features{} - case NsTLS + " proceed", NsTLS + " failure": - obj = &starttls{} - case NsSASL + " challenge", NsSASL + " failure", - NsSASL + " success": - obj = &auth{} - case NsClient + " iq": - obj = &Iq{} - case NsClient + " message": - obj = &Message{} - case NsClient + " presence": - obj = &Presence{} - default: - obj = &Generic{} - Info.Logf("Ignoring unrecognized: %s %s", se.Name.Space, - se.Name.Local) - } - - // Read the complete XML stanza. - err = p.DecodeElement(obj, &se) - if err != nil { - Warn.Logf("unmarshal: %s", err) - break Loop - } - - // If it's a Stanza, we try to unmarshal its innerxml - // into objects of the appropriate respective - // types. This is specified by our extensions. - if st, ok := obj.(Stanza); ok { - err = parseExtended(st.GetHeader(), extStanza) - if err != nil { - Warn.Logf("ext unmarshal: %s", err) - break Loop - } - } - - // Put it on the channel. - ch <- obj - } -} - -func parseExtended(st *Header, 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.NewDecoder(reader) - for { - t, err := p.Token() - if err == io.EOF { - break - } - if err != nil { - return err - } - if se, ok := t.(xml.StartElement); ok { - if con, ok := extStanza[se.Name.Space]; ok { - // Call the indicated constructor. - nested := con(&se.Name) - - // Unmarshal the nested element and - // stuff it back into the stanza. - err := p.DecodeElement(nested, &se) - if err != nil { - return err - } - st.Nested = append(st.Nested, nested) - } - } - } - - return nil -} - -func writeXml(w io.Writer, ch <-chan interface{}) { - if _, ok := Debug.(*noLog); !ok { - pr, pw := io.Pipe() - go tee(pr, w, "C: ") - w = pw - } - defer func(w io.Writer) { - if c, ok := w.(io.Closer); ok { - c.Close() - } - }(w) - - enc := xml.NewEncoder(w) - - for obj := range ch { - if st, ok := obj.(*stream); ok { - _, err := w.Write([]byte(st.String())) - if err != nil { - Warn.Logf("write: %s", err) - } - } else { - err := enc.Encode(obj) - if err != nil { - Warn.Logf("marshal: %s", err) - break - } - } - } -} - -func (cl *Client) readStream(srvIn <-chan interface{}, cliOut chan<- Stanza) { - defer close(cliOut) - - handlers := make(map[string]func(Stanza) bool) -Loop: - for { - select { - case h := <-cl.handlers: - handlers[h.id] = h.f - case x, ok := <-srvIn: - if !ok { - break Loop - } - switch obj := x.(type) { - case *stream: - handleStream(obj) - case *streamError: - cl.handleStreamError(obj) - case *Features: - cl.handleFeatures(obj) - case *starttls: - cl.handleTls(obj) - case *auth: - cl.handleSasl(obj) - case Stanza: - send := true - id := obj.GetHeader().Id - if handlers[id] != nil { - f := handlers[id] - delete(handlers, id) - send = f(obj) - } - if send { - cliOut <- obj - } - default: - Warn.Logf("Unhandled non-stanza: %T %#v", x, x) - } - } - } -} - -// This loop is paused until resource binding is complete. Otherwise -// the app might inject something inappropriate into our negotiations -// with the server. The control channel controls this loop's -// activity. -func writeStream(srvOut chan<- interface{}, cliIn <-chan Stanza, - control <-chan int) { - defer close(srvOut) - - var input <-chan Stanza -Loop: - for { - select { - case status := <-control: - switch status { - case 0: - input = nil - case 1: - input = cliIn - case -1: - break Loop - } - case x, ok := <-input: - if !ok { - break Loop - } - if x == nil { - Info.Log("Refusing to send nil stanza") - continue - } - srvOut <- x - } - } -} - -func handleStream(ss *stream) { -} - -func (cl *Client) handleStreamError(se *streamError) { - Info.Logf("Received stream error: %v", se) - close(cl.Out) -} - -func (cl *Client) handleFeatures(fe *Features) { - cl.Features = fe - if fe.Starttls != nil { - start := &starttls{XMLName: xml.Name{Space: NsTLS, - Local: "starttls"}} - cl.xmlOut <- start - return - } - - if len(fe.Mechanisms.Mechanism) > 0 { - cl.chooseSasl(fe) - return - } - - if fe.Bind != nil { - cl.bind(fe.Bind) - return - } -} - -// readTransport() is running concurrently. We need to stop it, -// negotiate TLS, then start it again. It calls waitForSocket() in -// its inner loop; see below. -func (cl *Client) handleTls(t *starttls) { - tcp := cl.socket - - // Set the socket to nil, and wait for the reader routine to - // signal that it's paused. - cl.socket = nil - cl.socketSync.Add(1) - cl.socketSync.Wait() - - // Negotiate TLS with the server. - tls := tls.Client(tcp, &TlsConfig) - - // Make the TLS connection available to the reader, and wait - // for it to signal that it's working again. - cl.socketSync.Add(1) - cl.socket = tls - cl.socketSync.Wait() - - Info.Log("TLS negotiation succeeded.") - cl.Features = nil - - // Now re-send the initial handshake message to start the new - // session. - hsOut := &stream{To: cl.Jid.Domain, Version: XMPPVersion} - cl.xmlOut <- hsOut -} - -// Synchronize with handleTls(). Called from readTransport() when -// cl.socket is nil. -func (cl *Client) waitForSocket() { - // Signal that we've stopped reading from the socket. - cl.socketSync.Done() - - // Wait until the socket is available again. - for cl.socket == nil { - time.Sleep(1e8) - } - - // Signal that we're going back to the read loop. - cl.socketSync.Done() -} - -// BUG(cjyar): Doesn't implement TLS/SASL EXTERNAL. -func (cl *Client) chooseSasl(fe *Features) { - var digestMd5 bool - for _, m := range fe.Mechanisms.Mechanism { - switch strings.ToLower(m) { - case "digest-md5": - digestMd5 = true - } - } - - if digestMd5 { - auth := &auth{XMLName: xml.Name{Space: NsSASL, Local: "auth"}, Mechanism: "DIGEST-MD5"} - cl.xmlOut <- auth - } -} - -func (cl *Client) handleSasl(srv *auth) { - switch strings.ToLower(srv.XMLName.Local) { - case "challenge": - b64 := base64.StdEncoding - str, err := b64.DecodeString(srv.Chardata) - if err != nil { - Warn.Logf("SASL challenge decode: %s", err) - return - } - srvMap := parseSasl(string(str)) - - if cl.saslExpected == "" { - cl.saslDigest1(srvMap) - } else { - cl.saslDigest2(srvMap) - } - case "failure": - Info.Log("SASL authentication failed") - case "success": - Info.Log("Sasl authentication succeeded") - cl.Features = nil - ss := &stream{To: cl.Jid.Domain, Version: XMPPVersion} - cl.xmlOut <- ss - } -} - -func (cl *Client) saslDigest1(srvMap map[string]string) { - // Make sure it supports qop=auth - var hasAuth bool - for _, qop := range strings.Fields(srvMap["qop"]) { - if qop == "auth" { - hasAuth = true - } - } - if !hasAuth { - Warn.Log("Server doesn't support SASL auth") - return - } - - // Pick a realm. - var realm string - if srvMap["realm"] != "" { - realm = strings.Fields(srvMap["realm"])[0] - } - - passwd := cl.password - nonce := srvMap["nonce"] - digestUri := "xmpp/" + cl.Jid.Domain - nonceCount := int32(1) - nonceCountStr := fmt.Sprintf("%08x", nonceCount) - - // Begin building the response. Username is - // user@domain or just domain. - var username string - if cl.Jid.Node == "" { - username = cl.Jid.Domain - } else { - username = cl.Jid.Node - } - - // Generate our own nonce from random data. - randSize := big.NewInt(0) - randSize.Lsh(big.NewInt(1), 64) - cnonce, err := rand.Int(rand.Reader, randSize) - if err != nil { - Warn.Logf("SASL rand: %s", err) - return - } - cnonceStr := fmt.Sprintf("%016x", cnonce) - - /* Now encode the actual password response, as well as the - * expected next challenge from the server. */ - response := saslDigestResponse(username, realm, passwd, nonce, - cnonceStr, "AUTHENTICATE", digestUri, nonceCountStr) - next := saslDigestResponse(username, realm, passwd, nonce, - cnonceStr, "", digestUri, nonceCountStr) - cl.saslExpected = next - - // Build the map which will be encoded. - clMap := make(map[string]string) - clMap["realm"] = `"` + realm + `"` - clMap["username"] = `"` + username + `"` - clMap["nonce"] = `"` + nonce + `"` - clMap["cnonce"] = `"` + cnonceStr + `"` - clMap["nc"] = nonceCountStr - clMap["qop"] = "auth" - clMap["digest-uri"] = `"` + digestUri + `"` - clMap["response"] = response - if srvMap["charset"] == "utf-8" { - clMap["charset"] = "utf-8" - } - - // Encode the map and send it. - clStr := packSasl(clMap) - b64 := base64.StdEncoding - clObj := &auth{XMLName: xml.Name{Space: NsSASL, Local: "response"}, Chardata: b64.EncodeToString([]byte(clStr))} - cl.xmlOut <- clObj -} - -func (cl *Client) saslDigest2(srvMap map[string]string) { - if cl.saslExpected == srvMap["rspauth"] { - clObj := &auth{XMLName: xml.Name{Space: NsSASL, Local: "response"}} - cl.xmlOut <- clObj - } else { - clObj := &auth{XMLName: xml.Name{Space: NsSASL, Local: "failure"}, Any: &Generic{XMLName: xml.Name{Space: NsSASL, - Local: "abort"}}} - cl.xmlOut <- clObj - } -} - -// Takes a string like `key1=value1,key2="value2"...` and returns a -// key/value map. -func parseSasl(in string) map[string]string { - re := regexp.MustCompile(`([^=]+)="?([^",]+)"?,?`) - strs := re.FindAllStringSubmatch(in, -1) - m := make(map[string]string) - for _, pair := range strs { - key := strings.ToLower(string(pair[1])) - value := string(pair[2]) - m[key] = value - } - return m -} - -// Inverse of parseSasl(). -func packSasl(m map[string]string) string { - var terms []string - for key, value := range m { - if key == "" || value == "" || value == `""` { - continue - } - terms = append(terms, key+"="+value) - } - return strings.Join(terms, ",") -} - -// Computes the response string for digest authentication. -func saslDigestResponse(username, realm, passwd, nonce, cnonceStr, - authenticate, digestUri, nonceCountStr string) string { - h := func(text string) []byte { - h := md5.New() - h.Write([]byte(text)) - return h.Sum(nil) - } - hex := func(bytes []byte) string { - return fmt.Sprintf("%x", bytes) - } - kd := func(secret, data string) []byte { - return h(secret + ":" + data) - } - - a1 := string(h(username+":"+realm+":"+passwd)) + ":" + - nonce + ":" + cnonceStr - a2 := authenticate + ":" + digestUri - response := hex(kd(hex(h(a1)), nonce+":"+ - nonceCountStr+":"+cnonceStr+":auth:"+ - hex(h(a2)))) - return response -} - -// Send a request to bind a resource. RFC 3920, section 7. -func (cl *Client) bind(bindAdv *bindIq) { - res := cl.Jid.Resource - bindReq := &bindIq{} - if res != "" { - bindReq.Resource = &res - } - msg := &Iq{Header: Header{Type: "set", Id: NextId(), - Nested: []interface{}{bindReq}}} - f := func(st Stanza) bool { - iq, ok := st.(*Iq) - if !ok { - Warn.Log("non-iq response") - } - if iq.Type == "error" { - Warn.Log("Resource binding failed") - return false - } - var bindRepl *bindIq - for _, ele := range iq.Nested { - if b, ok := ele.(*bindIq); ok { - bindRepl = b - break - } - } - if bindRepl == nil { - Warn.Logf("Bad bind reply: %#v", iq) - return false - } - jidStr := bindRepl.Jid - if jidStr == nil || *jidStr == "" { - Warn.Log("Can't bind empty resource") - return false - } - jid := new(JID) - if err := jid.Set(*jidStr); err != nil { - Warn.Logf("Can't parse JID %s: %s", *jidStr, err) - return false - } - cl.Jid = *jid - Info.Logf("Bound resource: %s", cl.Jid.String()) - cl.bindDone() - return false - } - cl.HandleStanza(msg.Id, f) - cl.xmlOut <- msg -} - -// Register a callback to handle the next XMPP stanza (iq, message, or -// presence) with a given id. The provided function will not be called -// more than once. If it returns false, the stanza will not be made -// available on the normal Client.In channel. The stanza handler -// must not read from that channel, as deliveries on it cannot proceed -// until the handler returns true or false. -func (cl *Client) HandleStanza(id string, f func(Stanza) bool) { - h := &stanzaHandler{id: id, f: f} - cl.handlers <- h -} diff -r f464f14e39a7 -r 367e76b3028e stream_test.go --- a/stream_test.go Mon Sep 02 20:46:23 2013 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,19 +0,0 @@ -// 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" -) - -func TestSaslDigest(t *testing.T) { - // These values are from RFC2831, section 4. - obs := saslDigestResponse("chris", "elwood.innosoft.com", - "secret", "OA6MG9tEQGm2hh", "OA6MHXh6VqTrRk", - "AUTHENTICATE", "imap/elwood.innosoft.com", - "00000001") - exp := "d388dad90d4bbd760a152321f2143af7" - assertEquals(t, exp, obs) -} diff -r f464f14e39a7 -r 367e76b3028e structs.go --- a/structs.go Mon Sep 02 20:46:23 2013 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,276 +0,0 @@ -// 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 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{} - -// -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 . -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(`") - 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", 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{} -} diff -r f464f14e39a7 -r 367e76b3028e structs_test.go --- a/structs_test.go Mon Sep 02 20:46:23 2013 -0700 +++ /dev/null Thu Jan 01 00:00:00 1970 +0000 @@ -1,143 +0,0 @@ -// 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 ( - "bytes" - "encoding/xml" - "fmt" - "os" - "reflect" - "runtime" - "strings" - "testing" -) - -func assertEquals(t *testing.T, expected, observed string) { - if expected != observed { - file := "unknown" - line := 0 - _, file, line, _ = runtime.Caller(1) - fmt.Fprintf(os.Stderr, "%s:%d: Expected:\n%s\nObserved:\n%s\n", - file, line, expected, observed) - t.Fail() - } -} - -func TestJid(t *testing.T) { - str := "user@domain/res" - jid := &JID{} - 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) - assertEquals(t, "res", jid.Resource) - assertEquals(t, str, jid.String()) - - str = "domain.tld" - 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) - } - assertEquals(t, "domain.tld", jid.Domain) - if jid.Resource != "" { - t.Errorf("Resource: %v\n", jid.Resource) - } - assertEquals(t, str, jid.String()) -} - -func assertMarshal(t *testing.T, expected string, marshal interface{}) { - var buf bytes.Buffer - enc := xml.NewEncoder(&buf) - err := enc.Encode(marshal) - if err != nil { - t.Errorf("Marshal error for %s: %s", marshal, err) - } - observed := buf.String() - if expected != observed { - file := "unknown" - line := 0 - _, file, line, _ = runtime.Caller(1) - fmt.Fprintf(os.Stderr, "%s:%d: Expected:\n%s\nObserved:\n%s\n", - file, line, expected, observed) - t.Fail() - } -} - -func TestStreamMarshal(t *testing.T) { - s := &stream{To: "bob"} - exp := `` - assertEquals(t, exp, s.String()) - - s = &stream{To: "bob", From: "alice", Id: "#3", Version: "5.3"} - exp = `` - assertEquals(t, exp, s.String()) - - s = &stream{Lang: "en_US"} - exp = `` - assertEquals(t, exp, s.String()) -} - -func TestStreamErrorMarshal(t *testing.T) { - name := xml.Name{Space: NsStreams, Local: "ack"} - e := &streamError{Any: Generic{XMLName: name}} - exp := `` - assertMarshal(t, exp, e) - - txt := errText{Lang: "pt", Text: "things happen"} - e = &streamError{Any: Generic{XMLName: name}, Text: &txt} - exp = `things happen` - assertMarshal(t, exp, e) -} - -func TestIqMarshal(t *testing.T) { - iq := &Iq{Header: Header{Type: "set", Id: "3", - Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsBind, - Local: "bind"}}}}} - exp := `` - assertMarshal(t, exp, iq) -} - -func TestMarshalEscaping(t *testing.T) { - msg := &Message{Body: &Generic{XMLName: xml.Name{Local: "body"}, - Chardata: `&