Moved the library code into an xmpp directory.
--- 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
-}
--- 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
-}
--- 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{}) {
-}
--- 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}
-}
--- 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 := `<iq from="from" xml:lang="en"><query xmlns="` +
- NsRoster + `"></query></iq>`
- assertMarshal(t, exp, iq)
-}
-
-func TestRosterIqUnmarshal(t *testing.T) {
- str := `<iq from="from" xml:lang="en"><query xmlns="` +
- NsRoster + `"><item jid="a@b.c"/></query></iq>`
- 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)
-}
--- 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(`<a xmlns="%s" xmlns:stream="%s">`,
- 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
-}
--- 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)
-}
--- 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 <stream:stream> 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{}
-
-// <stream:error>
-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 <iq/>.
-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(`<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, 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</%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{}
-}
--- 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 := `<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="` + NsClient +
- `" xmlns:stream="` + NsStream + `" to="bob" from="alice"` +
- ` id="#3" version="5.3">`
- assertEquals(t, exp, s.String())
-
- s = &stream{Lang: "en_US"}
- exp = `<stream:stream xmlns="` + NsClient +
- `" xmlns:stream="` + NsStream + `" xml:lang="en_US">`
- assertEquals(t, exp, s.String())
-}
-
-func TestStreamErrorMarshal(t *testing.T) {
- name := xml.Name{Space: NsStreams, Local: "ack"}
- e := &streamError{Any: Generic{XMLName: name}}
- exp := `<error xmlns="` + NsStream + `"><ack xmlns="` + NsStreams +
- `"></ack></error>`
- assertMarshal(t, exp, e)
-
- txt := errText{Lang: "pt", Text: "things happen"}
- e = &streamError{Any: Generic{XMLName: name}, Text: &txt}
- exp = `<error xmlns="` + NsStream + `"><ack xmlns="` + NsStreams +
- `"></ack><text xmlns="` + NsStreams +
- `" xml:lang="pt">things happen</text></error>`
- 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 := `<iq id="3" type="set"><bind xmlns="` + NsBind +
- `"></bind></iq>`
- assertMarshal(t, exp, iq)
-}
-
-func TestMarshalEscaping(t *testing.T) {
- msg := &Message{Body: &Generic{XMLName: xml.Name{Local: "body"},
- Chardata: `&<!-- "`}}
- exp := `<message xmlns="jabber:client"><body>&<!-- "</body></message>`
- assertMarshal(t, exp, msg)
-}
-
-func TestUnmarshalMessage(t *testing.T) {
- str := `<message to="a@b.c"><body>foo!</body></message>`
- r := strings.NewReader(str)
- ch := make(chan interface{})
- go readXml(r, ch, make(map[string]func(*xml.Name) interface{}))
- obs := <-ch
- exp := &Message{XMLName: xml.Name{Local: "message", Space: "jabber:client"},
- Header: Header{To: "a@b.c", Innerxml: "<body>foo!</body>"},
- Body: &Generic{XMLName: xml.Name{Local: "body", Space: "jabber:client"},
- Chardata: "foo!"}}
- if !reflect.DeepEqual(obs, exp) {
- t.Errorf("read %s\ngot: %#v\nwant: %#v\n", str, obs, exp)
- }
- obsMsg, ok := obs.(*Message)
- if !ok {
- t.Fatalf("Not a Message: %T", obs)
- }
- obsBody := obsMsg.Body
- expBody := exp.Body
- if !reflect.DeepEqual(obsBody, expBody) {
- t.Errorf("body\ngot: %#v\nwant: %#v\n", obsBody, expBody)
- }
-}
--- a/xmpp.go Mon Sep 02 20:46:23 2013 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,261 +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 package implements a simple XMPP client according to RFCs 3920
-// and 3921, plus the various XEPs at http://xmpp.org/protocols/. The
-// implementation is structured as a stack of layers, with TCP at the
-// bottom and the application at the top. The application receives and
-// sends structures representing XMPP stanzas. Additional stanza
-// parsers can be inserted into the stack of layers as extensions.
-package xmpp
-
-import (
- "bytes"
- "crypto/tls"
- "encoding/xml"
- "errors"
- "fmt"
- "io"
- "net"
- "sync"
-)
-
-const (
- // Version of RFC 3920 that we implement.
- XMPPVersion = "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"
- NsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
- NsBind = "urn:ietf:params:xml:ns:xmpp-bind"
- NsSession = "urn:ietf:params:xml:ns:xmpp-session"
- NsRoster = "jabber:iq:roster"
-
- // DNS SRV names
- serverSrv = "xmpp-server"
- clientSrv = "xmpp-client"
-)
-
-// A filter can modify the XMPP traffic to or from the remote
-// server. It's part of an Extension. The filter function will be
-// called in a new goroutine, so it doesn't need to return. The filter
-// should close its output when its input is closed.
-type Filter func(in <-chan Stanza, out chan<- Stanza)
-
-// Extensions can add stanza filters and/or new XML element types.
-type Extension struct {
- // Maps from an XML namespace to a function which constructs a
- // structure to hold the contents of stanzas in that
- // namespace.
- StanzaHandlers map[string]func(*xml.Name) interface{}
- // If non-nil, will be called once to start the filter
- // running. RecvFilter intercepts incoming messages on their
- // way from the remote server to the application; SendFilter
- // intercepts messages going the other direction.
- RecvFilter Filter
- SendFilter Filter
-}
-
-// Allows the user to override the TLS configuration.
-var TlsConfig tls.Config
-
-// The client in a client-server XMPP connection.
-type Client struct {
- // This client's JID. This will be updated asynchronously by
- // the time StartSession() returns.
- Jid JID
- password string
- socket net.Conn
- socketSync sync.WaitGroup
- saslExpected string
- authDone bool
- handlers chan *stanzaHandler
- inputControl chan int
- // Incoming XMPP stanzas from the remote will be published on
- // this channel. Information which is used by this library to
- // set up the XMPP stream will not appear here.
- In <-chan Stanza
- // Outgoing XMPP stanzas to the server should be sent to this
- // channel.
- Out chan<- Stanza
- xmlOut chan<- interface{}
- // The client's roster is also known as the buddy list. It's
- // the set of contacts which are known to this JID, or which
- // this JID is known to.
- Roster Roster
- // Features advertised by the remote. This will be updated
- // asynchronously as new features are received throughout the
- // connection process. It should not be updated once
- // StartSession() returns.
- Features *Features
- sendFilterAdd, recvFilterAdd chan Filter
-}
-
-// Connect to the appropriate server and authenticate as the given JID
-// with the given password. This function will return as soon as a TCP
-// connection has been established, but before XMPP stream negotiation
-// 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, error) {
- // Include the mandatory extensions.
- roster := newRosterExt()
- exts = append(exts, roster.Extension)
- exts = append(exts, bindExt)
-
- // Resolve the domain in the JID.
- _, srvs, err := net.LookupSRV(clientSrv, "tcp", jid.Domain)
- if err != nil {
- return nil, errors.New("LookupSrv " + jid.Domain +
- ": " + err.Error())
- }
-
- var tcp *net.TCPConn
- for _, srv := range srvs {
- addrStr := fmt.Sprintf("%s:%d", srv.Target, srv.Port)
- addr, err := net.ResolveTCPAddr("tcp", addrStr)
- if err != nil {
- err = fmt.Errorf("ResolveTCPAddr(%s): %s",
- addrStr, err.Error())
- continue
- }
- tcp, err = net.DialTCP("tcp", nil, addr)
- if err == nil {
- break
- }
- err = fmt.Errorf("DialTCP(%s): %s", addr, err)
- }
- if tcp == nil {
- return nil, err
- }
-
- cl := new(Client)
- cl.Roster = *roster
- cl.password = password
- cl.Jid = *jid
- cl.socket = tcp
- cl.handlers = make(chan *stanzaHandler, 100)
- cl.inputControl = make(chan int)
-
- extStanza := make(map[string]func(*xml.Name) interface{})
- for _, ext := range exts {
- for k, v := range ext.StanzaHandlers {
- extStanza[k] = v
- }
- }
-
- // Start the transport handler, initially unencrypted.
- recvReader, recvWriter := io.Pipe()
- sendReader, sendWriter := io.Pipe()
- go cl.readTransport(recvWriter)
- go cl.writeTransport(sendReader)
-
- // Start the reader and writer that convert to and from XML.
- recvXml := make(chan interface{})
- go readXml(recvReader, recvXml, extStanza)
- sendXml := make(chan interface{})
- cl.xmlOut = sendXml
- go writeXml(sendWriter, sendXml)
-
- // Start the reader and writer that convert between XML and
- // XMPP stanzas.
- recvRawXmpp := make(chan Stanza)
- go cl.readStream(recvXml, recvRawXmpp)
- sendRawXmpp := make(chan Stanza)
- go writeStream(sendXml, sendRawXmpp, cl.inputControl)
-
- // Start the manager for the filters that can modify what the
- // app sees.
- recvFiltXmpp := make(chan Stanza)
- cl.In = recvFiltXmpp
- go filterMgr(cl.recvFilterAdd, recvRawXmpp, recvFiltXmpp)
- sendFiltXmpp := make(chan Stanza)
- cl.Out = sendFiltXmpp
- go filterMgr(cl.sendFilterAdd, sendFiltXmpp, sendFiltXmpp)
-
- // Initial handshake.
- hsOut := &stream{To: jid.Domain, Version: XMPPVersion}
- cl.xmlOut <- hsOut
-
- return cl, nil
-}
-
-func tee(r io.Reader, w io.Writer, prefix string) {
- defer func(w io.Writer) {
- if c, ok := w.(io.Closer); ok {
- c.Close()
- }
- }(w)
-
- buf := bytes.NewBuffer([]uint8(prefix))
- for {
- var c [1]byte
- n, _ := r.Read(c[:])
- if n == 0 {
- break
- }
- n, _ = w.Write(c[:n])
- if n == 0 {
- break
- }
- buf.Write(c[:n])
- if c[0] == '\n' || c[0] == '>' {
- Debug.Log(buf)
- buf = bytes.NewBuffer([]uint8(prefix))
- }
- }
- leftover := buf.String()
- if leftover != "" {
- Debug.Log(buf)
- }
-}
-
-// bindDone is called when we've finished resource binding (and all
-// the negotiations that precede it). Now we can start accepting
-// traffic from the app.
-func (cl *Client) bindDone() {
- cl.inputControl <- 1
-}
-
-// Start an XMPP session. A typical XMPP client should call this
-// immediately after creating the Client in order to start the
-// 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. After calling this, a
-// normal client will want to call Roster.Update().
-func (cl *Client) StartSession(pr *Presence) error {
- id := NextId()
- iq := &Iq{Header: Header{To: cl.Jid.Domain, Id: id, Type: "set",
- Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsSession, Local: "session"}}}}}
- ch := make(chan error)
- f := func(st Stanza) bool {
- iq, ok := st.(*Iq)
- if !ok {
- Warn.Log("iq reply not iq; can't start session")
- ch <- errors.New("bad session start reply")
- return false
- }
- if iq.Type == "error" {
- Warn.Logf("Can't start session: %v", iq)
- ch <- iq.Error
- return false
- }
- ch <- nil
- return false
- }
- cl.HandleStanza(id, f)
- cl.Out <- iq
-
- // Now wait until the callback is called.
- if err := <-ch; err != nil {
- return err
- }
- if pr != nil {
- cl.Out <- pr
- }
- return nil
-}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/filter.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,64 @@
+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
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/id.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,29 @@
+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
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/log.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,37 @@
+// 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{}) {
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/roster.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,155 @@
+// 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}
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/roster_test.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,55 @@
+// 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 := `<iq from="from" xml:lang="en"><query xmlns="` +
+ NsRoster + `"></query></iq>`
+ assertMarshal(t, exp, iq)
+}
+
+func TestRosterIqUnmarshal(t *testing.T) {
+ str := `<iq from="from" xml:lang="en"><query xmlns="` +
+ NsRoster + `"><item jid="a@b.c"/></query></iq>`
+ 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)
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/stream.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,609 @@
+// 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(`<a xmlns="%s" xmlns:stream="%s">`,
+ 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
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/stream_test.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,19 @@
+// 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)
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/structs.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,276 @@
+// 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 <stream:stream> 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{}
+
+// <stream:error>
+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 <iq/>.
+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(`<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, 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</%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{}
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/structs_test.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,143 @@
+// 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 := `<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="` + NsClient +
+ `" xmlns:stream="` + NsStream + `" to="bob" from="alice"` +
+ ` id="#3" version="5.3">`
+ assertEquals(t, exp, s.String())
+
+ s = &stream{Lang: "en_US"}
+ exp = `<stream:stream xmlns="` + NsClient +
+ `" xmlns:stream="` + NsStream + `" xml:lang="en_US">`
+ assertEquals(t, exp, s.String())
+}
+
+func TestStreamErrorMarshal(t *testing.T) {
+ name := xml.Name{Space: NsStreams, Local: "ack"}
+ e := &streamError{Any: Generic{XMLName: name}}
+ exp := `<error xmlns="` + NsStream + `"><ack xmlns="` + NsStreams +
+ `"></ack></error>`
+ assertMarshal(t, exp, e)
+
+ txt := errText{Lang: "pt", Text: "things happen"}
+ e = &streamError{Any: Generic{XMLName: name}, Text: &txt}
+ exp = `<error xmlns="` + NsStream + `"><ack xmlns="` + NsStreams +
+ `"></ack><text xmlns="` + NsStreams +
+ `" xml:lang="pt">things happen</text></error>`
+ 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 := `<iq id="3" type="set"><bind xmlns="` + NsBind +
+ `"></bind></iq>`
+ assertMarshal(t, exp, iq)
+}
+
+func TestMarshalEscaping(t *testing.T) {
+ msg := &Message{Body: &Generic{XMLName: xml.Name{Local: "body"},
+ Chardata: `&<!-- "`}}
+ exp := `<message xmlns="jabber:client"><body>&<!-- "</body></message>`
+ assertMarshal(t, exp, msg)
+}
+
+func TestUnmarshalMessage(t *testing.T) {
+ str := `<message to="a@b.c"><body>foo!</body></message>`
+ r := strings.NewReader(str)
+ ch := make(chan interface{})
+ go readXml(r, ch, make(map[string]func(*xml.Name) interface{}))
+ obs := <-ch
+ exp := &Message{XMLName: xml.Name{Local: "message", Space: "jabber:client"},
+ Header: Header{To: "a@b.c", Innerxml: "<body>foo!</body>"},
+ Body: &Generic{XMLName: xml.Name{Local: "body", Space: "jabber:client"},
+ Chardata: "foo!"}}
+ if !reflect.DeepEqual(obs, exp) {
+ t.Errorf("read %s\ngot: %#v\nwant: %#v\n", str, obs, exp)
+ }
+ obsMsg, ok := obs.(*Message)
+ if !ok {
+ t.Fatalf("Not a Message: %T", obs)
+ }
+ obsBody := obsMsg.Body
+ expBody := exp.Body
+ if !reflect.DeepEqual(obsBody, expBody) {
+ t.Errorf("body\ngot: %#v\nwant: %#v\n", obsBody, expBody)
+ }
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/xmpp.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,261 @@
+// 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 package implements a simple XMPP client according to RFCs 3920
+// and 3921, plus the various XEPs at http://xmpp.org/protocols/. The
+// implementation is structured as a stack of layers, with TCP at the
+// bottom and the application at the top. The application receives and
+// sends structures representing XMPP stanzas. Additional stanza
+// parsers can be inserted into the stack of layers as extensions.
+package xmpp
+
+import (
+ "bytes"
+ "crypto/tls"
+ "encoding/xml"
+ "errors"
+ "fmt"
+ "io"
+ "net"
+ "sync"
+)
+
+const (
+ // Version of RFC 3920 that we implement.
+ XMPPVersion = "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"
+ NsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
+ NsBind = "urn:ietf:params:xml:ns:xmpp-bind"
+ NsSession = "urn:ietf:params:xml:ns:xmpp-session"
+ NsRoster = "jabber:iq:roster"
+
+ // DNS SRV names
+ serverSrv = "xmpp-server"
+ clientSrv = "xmpp-client"
+)
+
+// A filter can modify the XMPP traffic to or from the remote
+// server. It's part of an Extension. The filter function will be
+// called in a new goroutine, so it doesn't need to return. The filter
+// should close its output when its input is closed.
+type Filter func(in <-chan Stanza, out chan<- Stanza)
+
+// Extensions can add stanza filters and/or new XML element types.
+type Extension struct {
+ // Maps from an XML namespace to a function which constructs a
+ // structure to hold the contents of stanzas in that
+ // namespace.
+ StanzaHandlers map[string]func(*xml.Name) interface{}
+ // If non-nil, will be called once to start the filter
+ // running. RecvFilter intercepts incoming messages on their
+ // way from the remote server to the application; SendFilter
+ // intercepts messages going the other direction.
+ RecvFilter Filter
+ SendFilter Filter
+}
+
+// Allows the user to override the TLS configuration.
+var TlsConfig tls.Config
+
+// The client in a client-server XMPP connection.
+type Client struct {
+ // This client's JID. This will be updated asynchronously by
+ // the time StartSession() returns.
+ Jid JID
+ password string
+ socket net.Conn
+ socketSync sync.WaitGroup
+ saslExpected string
+ authDone bool
+ handlers chan *stanzaHandler
+ inputControl chan int
+ // Incoming XMPP stanzas from the remote will be published on
+ // this channel. Information which is used by this library to
+ // set up the XMPP stream will not appear here.
+ In <-chan Stanza
+ // Outgoing XMPP stanzas to the server should be sent to this
+ // channel.
+ Out chan<- Stanza
+ xmlOut chan<- interface{}
+ // The client's roster is also known as the buddy list. It's
+ // the set of contacts which are known to this JID, or which
+ // this JID is known to.
+ Roster Roster
+ // Features advertised by the remote. This will be updated
+ // asynchronously as new features are received throughout the
+ // connection process. It should not be updated once
+ // StartSession() returns.
+ Features *Features
+ sendFilterAdd, recvFilterAdd chan Filter
+}
+
+// Connect to the appropriate server and authenticate as the given JID
+// with the given password. This function will return as soon as a TCP
+// connection has been established, but before XMPP stream negotiation
+// 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, error) {
+ // Include the mandatory extensions.
+ roster := newRosterExt()
+ exts = append(exts, roster.Extension)
+ exts = append(exts, bindExt)
+
+ // Resolve the domain in the JID.
+ _, srvs, err := net.LookupSRV(clientSrv, "tcp", jid.Domain)
+ if err != nil {
+ return nil, errors.New("LookupSrv " + jid.Domain +
+ ": " + err.Error())
+ }
+
+ var tcp *net.TCPConn
+ for _, srv := range srvs {
+ addrStr := fmt.Sprintf("%s:%d", srv.Target, srv.Port)
+ addr, err := net.ResolveTCPAddr("tcp", addrStr)
+ if err != nil {
+ err = fmt.Errorf("ResolveTCPAddr(%s): %s",
+ addrStr, err.Error())
+ continue
+ }
+ tcp, err = net.DialTCP("tcp", nil, addr)
+ if err == nil {
+ break
+ }
+ err = fmt.Errorf("DialTCP(%s): %s", addr, err)
+ }
+ if tcp == nil {
+ return nil, err
+ }
+
+ cl := new(Client)
+ cl.Roster = *roster
+ cl.password = password
+ cl.Jid = *jid
+ cl.socket = tcp
+ cl.handlers = make(chan *stanzaHandler, 100)
+ cl.inputControl = make(chan int)
+
+ extStanza := make(map[string]func(*xml.Name) interface{})
+ for _, ext := range exts {
+ for k, v := range ext.StanzaHandlers {
+ extStanza[k] = v
+ }
+ }
+
+ // Start the transport handler, initially unencrypted.
+ recvReader, recvWriter := io.Pipe()
+ sendReader, sendWriter := io.Pipe()
+ go cl.readTransport(recvWriter)
+ go cl.writeTransport(sendReader)
+
+ // Start the reader and writer that convert to and from XML.
+ recvXml := make(chan interface{})
+ go readXml(recvReader, recvXml, extStanza)
+ sendXml := make(chan interface{})
+ cl.xmlOut = sendXml
+ go writeXml(sendWriter, sendXml)
+
+ // Start the reader and writer that convert between XML and
+ // XMPP stanzas.
+ recvRawXmpp := make(chan Stanza)
+ go cl.readStream(recvXml, recvRawXmpp)
+ sendRawXmpp := make(chan Stanza)
+ go writeStream(sendXml, sendRawXmpp, cl.inputControl)
+
+ // Start the manager for the filters that can modify what the
+ // app sees.
+ recvFiltXmpp := make(chan Stanza)
+ cl.In = recvFiltXmpp
+ go filterMgr(cl.recvFilterAdd, recvRawXmpp, recvFiltXmpp)
+ sendFiltXmpp := make(chan Stanza)
+ cl.Out = sendFiltXmpp
+ go filterMgr(cl.sendFilterAdd, sendFiltXmpp, sendFiltXmpp)
+
+ // Initial handshake.
+ hsOut := &stream{To: jid.Domain, Version: XMPPVersion}
+ cl.xmlOut <- hsOut
+
+ return cl, nil
+}
+
+func tee(r io.Reader, w io.Writer, prefix string) {
+ defer func(w io.Writer) {
+ if c, ok := w.(io.Closer); ok {
+ c.Close()
+ }
+ }(w)
+
+ buf := bytes.NewBuffer([]uint8(prefix))
+ for {
+ var c [1]byte
+ n, _ := r.Read(c[:])
+ if n == 0 {
+ break
+ }
+ n, _ = w.Write(c[:n])
+ if n == 0 {
+ break
+ }
+ buf.Write(c[:n])
+ if c[0] == '\n' || c[0] == '>' {
+ Debug.Log(buf)
+ buf = bytes.NewBuffer([]uint8(prefix))
+ }
+ }
+ leftover := buf.String()
+ if leftover != "" {
+ Debug.Log(buf)
+ }
+}
+
+// bindDone is called when we've finished resource binding (and all
+// the negotiations that precede it). Now we can start accepting
+// traffic from the app.
+func (cl *Client) bindDone() {
+ cl.inputControl <- 1
+}
+
+// Start an XMPP session. A typical XMPP client should call this
+// immediately after creating the Client in order to start the
+// 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. After calling this, a
+// normal client will want to call Roster.Update().
+func (cl *Client) StartSession(pr *Presence) error {
+ id := NextId()
+ iq := &Iq{Header: Header{To: cl.Jid.Domain, Id: id, Type: "set",
+ Nested: []interface{}{Generic{XMLName: xml.Name{Space: NsSession, Local: "session"}}}}}
+ ch := make(chan error)
+ f := func(st Stanza) bool {
+ iq, ok := st.(*Iq)
+ if !ok {
+ Warn.Log("iq reply not iq; can't start session")
+ ch <- errors.New("bad session start reply")
+ return false
+ }
+ if iq.Type == "error" {
+ Warn.Logf("Can't start session: %v", iq)
+ ch <- iq.Error
+ return false
+ }
+ ch <- nil
+ return false
+ }
+ cl.HandleStanza(id, f)
+ cl.Out <- iq
+
+ // Now wait until the callback is called.
+ if err := <-ch; err != nil {
+ return err
+ }
+ if pr != nil {
+ cl.Out <- pr
+ }
+ return nil
+}
--- /dev/null Thu Jan 01 00:00:00 1970 +0000
+++ b/xmpp/xmpp_test.go Sat Sep 07 10:04:44 2013 -0700
@@ -0,0 +1,102 @@
+// 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"
+ "reflect"
+ "strings"
+ "sync"
+ "testing"
+)
+
+func TestReadError(t *testing.T) {
+ 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: %T", x)
+ }
+ assertEquals(t, "bad-foo", se.Any.XMLName.Local)
+ 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 xmlns="blah"/>` +
+ `<text xml:lang="en" xmlns="` + NsStreams +
+ `">Error text</text></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))
+ }
+ assertEquals(t, "bad-foo", se.Any.XMLName.Local)
+ assertEquals(t, "blah", se.Any.XMLName.Space)
+ assertEquals(t, "Error text", se.Text.Text)
+ assertEquals(t, "en", se.Text.Lang)
+}
+
+func TestReadStream(t *testing.T) {
+ r := strings.NewReader(`<stream:stream to="foo.com" ` +
+ `from="bar.org" id="42"` +
+ `xmlns="` + NsClient + `" xmlns:stream="` + NsStream +
+ `" version="1.0">`)
+ ch := make(chan interface{})
+ go readXml(r, ch, make(map[string]func(*xml.Name) interface{}))
+ x := <-ch
+ ss, ok := x.(*stream)
+ if !ok {
+ t.Fatalf("not stream: %v", reflect.TypeOf(x))
+ }
+ assertEquals(t, "foo.com", ss.To)
+ assertEquals(t, "bar.org", ss.From)
+ assertEquals(t, "42", ss.Id)
+ assertEquals(t, "1.0", ss.Version)
+}
+
+func testWrite(obj interface{}) string {
+ w := bytes.NewBuffer(nil)
+ ch := make(chan interface{})
+ var wg sync.WaitGroup
+ wg.Add(1)
+ go func() {
+ defer wg.Done()
+ writeXml(w, ch)
+ }()
+ ch <- obj
+ close(ch)
+ wg.Wait()
+ return w.String()
+}
+
+func TestWriteError(t *testing.T) {
+ se := &streamError{Any: Generic{XMLName: xml.Name{Local: "blah"}}}
+ str := testWrite(se)
+ exp := `<error xmlns="` + NsStream + `"><blah></blah></error>`
+ assertEquals(t, exp, str)
+
+ se = &streamError{Any: Generic{XMLName: xml.Name{Space: NsStreams, Local: "foo"}}, Text: &errText{Lang: "ru", Text: "Пошёл ты"}}
+ str = testWrite(se)
+ exp = `<error xmlns="` + NsStream + `"><foo xmlns="` + NsStreams +
+ `"></foo><text xmlns="` + NsStreams +
+ `" xml:lang="ru">Пошёл ты</text></error>`
+ assertEquals(t, exp, str)
+}
+
+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="` + NsClient +
+ `" xmlns:stream="` + NsStream + `" to="foo.org"` +
+ ` from="bar.com" id="42" xml:lang="en" version="1.0">`
+ assertEquals(t, exp, str)
+}
--- a/xmpp_test.go Mon Sep 02 20:46:23 2013 -0700
+++ /dev/null Thu Jan 01 00:00:00 1970 +0000
@@ -1,102 +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"
- "reflect"
- "strings"
- "sync"
- "testing"
-)
-
-func TestReadError(t *testing.T) {
- 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: %T", x)
- }
- assertEquals(t, "bad-foo", se.Any.XMLName.Local)
- 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 xmlns="blah"/>` +
- `<text xml:lang="en" xmlns="` + NsStreams +
- `">Error text</text></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))
- }
- assertEquals(t, "bad-foo", se.Any.XMLName.Local)
- assertEquals(t, "blah", se.Any.XMLName.Space)
- assertEquals(t, "Error text", se.Text.Text)
- assertEquals(t, "en", se.Text.Lang)
-}
-
-func TestReadStream(t *testing.T) {
- r := strings.NewReader(`<stream:stream to="foo.com" ` +
- `from="bar.org" id="42"` +
- `xmlns="` + NsClient + `" xmlns:stream="` + NsStream +
- `" version="1.0">`)
- ch := make(chan interface{})
- go readXml(r, ch, make(map[string]func(*xml.Name) interface{}))
- x := <-ch
- ss, ok := x.(*stream)
- if !ok {
- t.Fatalf("not stream: %v", reflect.TypeOf(x))
- }
- assertEquals(t, "foo.com", ss.To)
- assertEquals(t, "bar.org", ss.From)
- assertEquals(t, "42", ss.Id)
- assertEquals(t, "1.0", ss.Version)
-}
-
-func testWrite(obj interface{}) string {
- w := bytes.NewBuffer(nil)
- ch := make(chan interface{})
- var wg sync.WaitGroup
- wg.Add(1)
- go func() {
- defer wg.Done()
- writeXml(w, ch)
- }()
- ch <- obj
- close(ch)
- wg.Wait()
- return w.String()
-}
-
-func TestWriteError(t *testing.T) {
- se := &streamError{Any: Generic{XMLName: xml.Name{Local: "blah"}}}
- str := testWrite(se)
- exp := `<error xmlns="` + NsStream + `"><blah></blah></error>`
- assertEquals(t, exp, str)
-
- se = &streamError{Any: Generic{XMLName: xml.Name{Space: NsStreams, Local: "foo"}}, Text: &errText{Lang: "ru", Text: "Пошёл ты"}}
- str = testWrite(se)
- exp = `<error xmlns="` + NsStream + `"><foo xmlns="` + NsStreams +
- `"></foo><text xmlns="` + NsStreams +
- `" xml:lang="ru">Пошёл ты</text></error>`
- assertEquals(t, exp, str)
-}
-
-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="` + NsClient +
- `" xmlns:stream="` + NsStream + `" to="foo.org"` +
- ` from="bar.com" id="42" xml:lang="en" version="1.0">`
- assertEquals(t, exp, str)
-}