Compare commits
No commits in common. "master" and "master" have entirely different histories.
|
|
@ -50,7 +50,7 @@ func main() {
|
||||||
|
|
||||||
// Get disco#info for home server.
|
// Get disco#info for home server.
|
||||||
info := &DiscoInfo{}
|
info := &DiscoInfo{}
|
||||||
iq := xmpp.IQ{ID: xmpp.UUID4(), Type: "get", To: client.JID.Domain}
|
iq := xmpp.Iq{Id: xmpp.UUID4(), Type: "get", To: client.JID.Domain}
|
||||||
iq.PayloadEncode(info)
|
iq.PayloadEncode(info)
|
||||||
reply, _ := client.SendRecv(&iq)
|
reply, _ := client.SendRecv(&iq)
|
||||||
reply.PayloadDecode(info)
|
reply.PayloadDecode(info)
|
||||||
|
|
|
||||||
|
|
@ -26,10 +26,10 @@ const (
|
||||||
TypeAdHocNoteWarning = "warn"
|
TypeAdHocNoteWarning = "warn"
|
||||||
TypeAdHocNoteError = "error"
|
TypeAdHocNoteError = "error"
|
||||||
|
|
||||||
TypeAdHocFieldListMulti = "list-multi"
|
TypeAdHocFieldListMulti = "list-multi"
|
||||||
TypeAdHocFieldListSingle = "list-single"
|
TypeAdHocFieldListSingle = "list-single"
|
||||||
TypeAdHocFieldTextSingle = "text-single"
|
TypeAdHocFieldTextSingle = "text-single"
|
||||||
TypeAdHocFieldJidSingle = "jid-single"
|
TypeAdHocFieldJidSingle = "jid-single"
|
||||||
TypeAdHocFieldTextPrivate = "text-private"
|
TypeAdHocFieldTextPrivate = "text-private"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,27 +0,0 @@
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
NSChatStatesNotification = "http://jabber.org/protocol/chatstates"
|
|
||||||
)
|
|
||||||
|
|
||||||
// XEP-0085: Chat States Notification
|
|
||||||
|
|
||||||
type Active struct {
|
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/chatstates active"`
|
|
||||||
}
|
|
||||||
type Composing struct {
|
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/chatstates composing"`
|
|
||||||
}
|
|
||||||
type Paused struct {
|
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/chatstates paused"`
|
|
||||||
}
|
|
||||||
type Inactive struct {
|
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/chatstates inactive"`
|
|
||||||
}
|
|
||||||
type Gone struct {
|
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/chatstates gone"`
|
|
||||||
}
|
|
||||||
|
|
@ -93,13 +93,14 @@ func startClient(stream *Stream, jid JID) error {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
rstart, err := stream.SendStart(&start)
|
if rstart, err := stream.SendStart(&start); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
|
} else {
|
||||||
|
if rstart.Name != (xml.Name{nsStreams, "stream"}) {
|
||||||
|
return fmt.Errorf("unexpected start element: %s", rstart.Name)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
if rstart.Name != (xml.Name{nsStreams, "stream"}) {
|
|
||||||
return fmt.Errorf("unexpected start element: %s", rstart.Name)
|
|
||||||
}
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -114,7 +115,7 @@ func startTLS(stream *Stream, config *ClientConfig) error {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
tlsConfig := tls.Config{InsecureSkipVerify: config.InsecureSkipVerify, ServerName: stream.config.ConnectionDomain}
|
tlsConfig := tls.Config{InsecureSkipVerify: config.InsecureSkipVerify, ServerName: stream.connDomain}
|
||||||
return stream.UpgradeTLS(&tlsConfig)
|
return stream.UpgradeTLS(&tlsConfig)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -136,7 +137,7 @@ func authenticate(stream *Stream, mechanisms []string, user, password string) er
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return errors.New("no supported SASL mechanism found")
|
return errors.New("No supported SASL mechanism found.")
|
||||||
}
|
}
|
||||||
|
|
||||||
type authHandler struct {
|
type authHandler struct {
|
||||||
|
|
@ -157,26 +158,26 @@ func authenticatePlain(stream *Stream, user, password string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
func authenticateResponse(stream *Stream) error {
|
func authenticateResponse(stream *Stream) error {
|
||||||
se, err := stream.Next()
|
if se, err := stream.Next(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
} else {
|
||||||
|
switch se.Name.Local {
|
||||||
switch se.Name.Local {
|
case "success":
|
||||||
case "success":
|
if err := stream.Skip(); err != nil {
|
||||||
if err := stream.Skip(); err != nil {
|
return err
|
||||||
return err
|
}
|
||||||
|
return nil
|
||||||
|
case "failure":
|
||||||
|
f := new(saslFailure)
|
||||||
|
if err := stream.Decode(f, se); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return fmt.Errorf("Authentication failed: %s", f.Reason.Local)
|
||||||
|
default:
|
||||||
|
return fmt.Errorf("Unexpected: %s", se.Name)
|
||||||
}
|
}
|
||||||
return nil
|
|
||||||
case "failure":
|
|
||||||
f := new(saslFailure)
|
|
||||||
if err := stream.Decode(f, se); err != nil {
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
return fmt.Errorf("Authentication failed: %s", f.Reason.Local)
|
|
||||||
default:
|
|
||||||
return fmt.Errorf("Unexpected: %s", se.Name)
|
|
||||||
}
|
}
|
||||||
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
type saslAuth struct {
|
type saslAuth struct {
|
||||||
|
|
@ -187,29 +188,29 @@ type saslAuth struct {
|
||||||
|
|
||||||
func bindResource(stream *Stream, jid JID) (JID, error) {
|
func bindResource(stream *Stream, jid JID) (JID, error) {
|
||||||
|
|
||||||
req := IQ{ID: UUID4(), Type: "set"}
|
req := Iq{Id: UUID4(), Type: "set"}
|
||||||
if jid.Resource == "" {
|
if jid.Resource == "" {
|
||||||
req.PayloadEncode(bindIQ{})
|
req.PayloadEncode(bindIq{})
|
||||||
} else {
|
} else {
|
||||||
req.PayloadEncode(bindIQ{Resource: jid.Resource})
|
req.PayloadEncode(bindIq{Resource: jid.Resource})
|
||||||
}
|
}
|
||||||
if err := stream.Send(req); err != nil {
|
if err := stream.Send(req); err != nil {
|
||||||
return JID{}, err
|
return JID{}, err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := IQ{}
|
resp := Iq{}
|
||||||
err := stream.Decode(&resp, nil)
|
err := stream.Decode(&resp, nil)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return JID{}, err
|
return JID{}, err
|
||||||
}
|
}
|
||||||
bindResp := bindIQ{}
|
bindResp := bindIq{}
|
||||||
resp.PayloadDecode(&bindResp)
|
resp.PayloadDecode(&bindResp)
|
||||||
|
|
||||||
boundJID, err := ParseJID(bindResp.JID)
|
boundJID, err := ParseJID(bindResp.JID)
|
||||||
return boundJID, nil
|
return boundJID, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
type bindIQ struct {
|
type bindIq struct {
|
||||||
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
|
XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`
|
||||||
Resource string `xml:"resource,omitempty"`
|
Resource string `xml:"resource,omitempty"`
|
||||||
JID string `xml:"jid,omitempty"`
|
JID string `xml:"jid,omitempty"`
|
||||||
|
|
@ -217,13 +218,13 @@ type bindIQ struct {
|
||||||
|
|
||||||
func establishSession(stream *Stream, domain string) error {
|
func establishSession(stream *Stream, domain string) error {
|
||||||
|
|
||||||
req := IQ{ID: UUID4(), Type: "set", To: domain}
|
req := Iq{Id: UUID4(), Type: "set", To: domain}
|
||||||
req.PayloadEncode(&session{})
|
req.PayloadEncode(&session{})
|
||||||
if err := stream.Send(req); err != nil {
|
if err := stream.Send(req); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
resp := IQ{}
|
resp := Iq{}
|
||||||
if err := stream.Decode(&resp, nil); err != nil {
|
if err := stream.Decode(&resp, nil); err != nil {
|
||||||
return err
|
return err
|
||||||
} else if resp.Error != nil {
|
} else if resp.Error != nil {
|
||||||
|
|
|
||||||
|
|
@ -10,12 +10,12 @@ import (
|
||||||
// Create a component XMPP connection over the stream.
|
// Create a component XMPP connection over the stream.
|
||||||
func NewComponentXMPP(stream *Stream, jid JID, secret string) (*XMPP, error) {
|
func NewComponentXMPP(stream *Stream, jid JID, secret string) (*XMPP, error) {
|
||||||
|
|
||||||
streamID, err := startComponent(stream, jid)
|
streamId, err := startComponent(stream, jid)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
if err := handshake(stream, streamID, secret); err != nil {
|
if err := handshake(stream, streamId, secret); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -33,33 +33,34 @@ func startComponent(stream *Stream, jid JID) (string, error) {
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
var streamID string
|
var streamId string
|
||||||
|
|
||||||
rstart, err := stream.SendStart(&start)
|
if rstart, err := stream.SendStart(&start); err != nil {
|
||||||
if err != nil {
|
|
||||||
return "", err
|
return "", err
|
||||||
}
|
} else {
|
||||||
if rstart.Name != (xml.Name{nsStreams, "stream"}) {
|
if rstart.Name != (xml.Name{nsStreams, "stream"}) {
|
||||||
return "", fmt.Errorf("unexpected start element: %s", rstart.Name)
|
return "", fmt.Errorf("unexpected start element: %s", rstart.Name)
|
||||||
}
|
}
|
||||||
// Find the stream id.
|
// Find the stream id.
|
||||||
for _, attr := range rstart.Attr {
|
for _, attr := range rstart.Attr {
|
||||||
if attr.Name.Local == "id" {
|
if attr.Name.Local == "id" {
|
||||||
streamID = attr.Value
|
streamId = attr.Value
|
||||||
break
|
break
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if streamID == "" {
|
|
||||||
|
if streamId == "" {
|
||||||
return "", errors.New("Missing stream id")
|
return "", errors.New("Missing stream id")
|
||||||
}
|
}
|
||||||
|
|
||||||
return streamID, nil
|
return streamId, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func handshake(stream *Stream, streamID, secret string) error {
|
func handshake(stream *Stream, streamId, secret string) error {
|
||||||
|
|
||||||
hash := sha1.New()
|
hash := sha1.New()
|
||||||
hash.Write([]byte(streamID))
|
hash.Write([]byte(streamId))
|
||||||
hash.Write([]byte(secret))
|
hash.Write([]byte(secret))
|
||||||
|
|
||||||
// Send handshake.
|
// Send handshake.
|
||||||
|
|
@ -69,14 +70,18 @@ func handshake(stream *Stream, streamID, secret string) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get handshake response.
|
// Get handshake response.
|
||||||
start, err := stream.Next()
|
if start, err := stream.Next(); err != nil {
|
||||||
if err != nil {
|
return err
|
||||||
|
} else {
|
||||||
|
if start.Name != (xml.Name{nsComponentAccept, "handshake"}) {
|
||||||
|
return fmt.Errorf("Expected <handshake/>, for %s", start.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if err := stream.Skip(); err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
if start.Name != (xml.Name{nsComponentAccept, "handshake"}) {
|
|
||||||
return fmt.Errorf("Expected <handshake/>, for %s", start.Name)
|
return nil
|
||||||
}
|
|
||||||
return stream.Skip()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type saslHandshake struct {
|
type saslHandshake struct {
|
||||||
|
|
|
||||||
|
|
@ -16,7 +16,7 @@ type Disco struct {
|
||||||
XMPP *XMPP
|
XMPP *XMPP
|
||||||
}
|
}
|
||||||
|
|
||||||
// IQ get/result payload for "info" requests.
|
// Iq get/result payload for "info" requests.
|
||||||
type DiscoInfo struct {
|
type DiscoInfo struct {
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
|
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
|
||||||
Node string `xml:"node,attr"`
|
Node string `xml:"node,attr"`
|
||||||
|
|
@ -36,7 +36,7 @@ type DiscoFeature struct {
|
||||||
Var string `xml:"var,attr"`
|
Var string `xml:"var,attr"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// IQ get/result payload for "items" requests.
|
// Iq get/result payload for "items" requests.
|
||||||
type DiscoItems struct {
|
type DiscoItems struct {
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items query"`
|
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items query"`
|
||||||
Node string `xml:"node,attr"`
|
Node string `xml:"node,attr"`
|
||||||
|
|
@ -57,7 +57,7 @@ func (disco *Disco) Info(to, from string) (*DiscoInfo, error) {
|
||||||
from = disco.XMPP.JID.Full()
|
from = disco.XMPP.JID.Full()
|
||||||
}
|
}
|
||||||
|
|
||||||
req := &IQ{ID: UUID4(), Type: IQTypeGet, To: to, From: from}
|
req := &Iq{Id: UUID4(), Type: IQTypeGet, To: to, From: from}
|
||||||
req.PayloadEncode(&DiscoInfo{})
|
req.PayloadEncode(&DiscoInfo{})
|
||||||
|
|
||||||
resp, err := disco.XMPP.SendRecv(req)
|
resp, err := disco.XMPP.SendRecv(req)
|
||||||
|
|
@ -80,7 +80,7 @@ func (disco *Disco) Items(to, from, node string) (*DiscoItems, error) {
|
||||||
from = disco.XMPP.JID.Full()
|
from = disco.XMPP.JID.Full()
|
||||||
}
|
}
|
||||||
|
|
||||||
req := &IQ{ID: UUID4(), Type: IQTypeGet, To: to, From: from}
|
req := &Iq{Id: UUID4(), Type: IQTypeGet, To: to, From: from}
|
||||||
req.PayloadEncode(&DiscoItems{Node: node})
|
req.PayloadEncode(&DiscoItems{Node: node})
|
||||||
|
|
||||||
resp, err := disco.XMPP.SendRecv(req)
|
resp, err := disco.XMPP.SendRecv(req)
|
||||||
|
|
@ -101,7 +101,7 @@ var discoNamespacePrefix = strings.Split(NSDiscoInfo, "#")[0]
|
||||||
// Matcher instance to match <iq/> stanzas with a disco payload.
|
// Matcher instance to match <iq/> stanzas with a disco payload.
|
||||||
var DiscoPayloadMatcher = MatcherFunc(
|
var DiscoPayloadMatcher = MatcherFunc(
|
||||||
func(v interface{}) bool {
|
func(v interface{}) bool {
|
||||||
iq, ok := v.(*IQ)
|
iq, ok := v.(*Iq)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -3,12 +3,11 @@ package xmpp
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"net"
|
"net"
|
||||||
"strings"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
const (
|
const (
|
||||||
// Standard port for XMPP clients to connect to.
|
// Standard port for XMPP clients to connect to.
|
||||||
ClientPort = 5222
|
XMPP_CLIENT_PORT = 5222
|
||||||
)
|
)
|
||||||
|
|
||||||
// Perform a DNS SRV lookup and return an ordered list of "host:port" TCP
|
// Perform a DNS SRV lookup and return an ordered list of "host:port" TCP
|
||||||
|
|
@ -22,14 +21,24 @@ func HomeServerAddrs(jid JID) (addr []string, err error) {
|
||||||
// If there's nothing in DNS then assume the JID's domain and the standard
|
// If there's nothing in DNS then assume the JID's domain and the standard
|
||||||
// port will work.
|
// port will work.
|
||||||
if len(addrs) == 0 {
|
if len(addrs) == 0 {
|
||||||
addr = []string{fmt.Sprintf("%s:%d", jid.Domain, ClientPort)}
|
addr = []string{fmt.Sprintf("%s:%d", jid.Domain, XMPP_CLIENT_PORT)}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// Build list of "host:port" strings.
|
// Build list of "host:port" strings.
|
||||||
for _, a := range addrs {
|
for _, a := range addrs {
|
||||||
target := strings.TrimRight(a.Target, ".")
|
target := parseTargetDomainName(a.Target)
|
||||||
addr = append(addr, fmt.Sprintf("%s:%d", target, a.Port))
|
addr = append(addr, fmt.Sprintf("%s:%d", target, a.Port))
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Remove the last dot in the domain name if exist
|
||||||
|
func parseTargetDomainName(domainName string) (ret string) {
|
||||||
|
if domainName[len(domainName)-1] == '.' {
|
||||||
|
ret = parseTargetDomainName(domainName[:len(domainName)-1])
|
||||||
|
} else {
|
||||||
|
ret = domainName
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -33,7 +33,7 @@ shutdown or any other error for something unexpected). The channel is also
|
||||||
closed after an error.
|
closed after an error.
|
||||||
|
|
||||||
XMPP defines four types of stanza: <error/>, <iq/>, <message/> and <presence/>
|
XMPP defines four types of stanza: <error/>, <iq/>, <message/> and <presence/>
|
||||||
represented by Error, IQ, Message (shown below) and Presence structs
|
represented by Error, Iq, Message (shown below) and Presence structs
|
||||||
respectively.
|
respectively.
|
||||||
|
|
||||||
for i := range X.In {
|
for i := range X.In {
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ const (
|
||||||
// XEP-0070: Verifying HTTP Requests via XMPP
|
// XEP-0070: Verifying HTTP Requests via XMPP
|
||||||
type Confirm struct {
|
type Confirm struct {
|
||||||
XMLName xml.Name `xml:"http://jabber.org/protocol/http-auth confirm"`
|
XMLName xml.Name `xml:"http://jabber.org/protocol/http-auth confirm"`
|
||||||
ID string `xml:"id,attr"`
|
Id string `xml:"id,attr"`
|
||||||
Method string `xml:"method,attr"`
|
Method string `xml:"method,attr"`
|
||||||
URL string `xml:"url,attr"`
|
URL string `xml:"url,attr"`
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,13 +0,0 @@
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
NSPing = "urn:xmpp:ping"
|
|
||||||
)
|
|
||||||
|
|
||||||
type Ping struct {
|
|
||||||
XMLName xml.Name `xml:"urn:xmpp:ping ping"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
NSRegister = "jabber:iq:register"
|
|
||||||
)
|
|
||||||
|
|
||||||
// XEP-0077: In-Band Registration
|
|
||||||
|
|
||||||
type RegisterQuery struct {
|
|
||||||
XMLName xml.Name `xml:"jabber:iq:register query"`
|
|
||||||
Instructions string `xml:"instructions"`
|
|
||||||
Username string `xml:"username"`
|
|
||||||
Password string `xml:"password"`
|
|
||||||
XForm AdHocXForm `xml:"x"`
|
|
||||||
Registered *RegisterRegistered `xmp:"registered"`
|
|
||||||
Remove *RegisterRemove `xmp:"remove"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RegisterRegistered struct {
|
|
||||||
XMLName xml.Name `xml:"registered"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RegisterRemove struct {
|
|
||||||
XMLName xml.Name `xml:"remove"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
NSRemoteRosterManager = "urn:xmpp:tmp:roster-management:0"
|
|
||||||
|
|
||||||
RemoteRosterManagerTypeRequest = "request"
|
|
||||||
RemoteRosterManagerTypeAllowed = "allowed"
|
|
||||||
RemoteRosterManagerTypeRejected = "rejected"
|
|
||||||
)
|
|
||||||
|
|
||||||
// XEP-0321: Remote Roster Manager
|
|
||||||
|
|
||||||
type RemoteRosterManagerQuery struct {
|
|
||||||
XMLName xml.Name `xml:"urn:xmpp:tmp:roster-management:0 query"`
|
|
||||||
Reason string `xml:"reason,attr,omitempty"`
|
|
||||||
Type string `xml:"type,attr"`
|
|
||||||
}
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
package xmpp
|
|
||||||
|
|
||||||
import (
|
|
||||||
"encoding/xml"
|
|
||||||
)
|
|
||||||
|
|
||||||
const (
|
|
||||||
NSRoster = "jabber:iq:roster"
|
|
||||||
|
|
||||||
RosterSubscriptionBoth = "both"
|
|
||||||
RosterSubscriptionFrom = "from"
|
|
||||||
RosterSubscriptionTo = "to"
|
|
||||||
RosterSubscriptionRemove = "remove"
|
|
||||||
)
|
|
||||||
|
|
||||||
type RosterQuery struct {
|
|
||||||
XMLName xml.Name `xml:"jabber:iq:roster query"`
|
|
||||||
Items []RosterItem `xml:"item"`
|
|
||||||
}
|
|
||||||
|
|
||||||
type RosterItem struct {
|
|
||||||
JID string `xml:"jid,attr"`
|
|
||||||
Name string `xml:"name,attr,omitempty"`
|
|
||||||
Subscription string `xml:"subscription,attr"`
|
|
||||||
Groupes []string `xml:"group"`
|
|
||||||
}
|
|
||||||
|
|
@ -18,9 +18,9 @@ const (
|
||||||
)
|
)
|
||||||
|
|
||||||
// XMPP <iq/> stanza.
|
// XMPP <iq/> stanza.
|
||||||
type IQ struct {
|
type Iq struct {
|
||||||
XMLName xml.Name `xml:"iq"`
|
XMLName xml.Name `xml:"iq"`
|
||||||
ID string `xml:"id,attr"`
|
Id string `xml:"id,attr"`
|
||||||
Type string `xml:"type,attr"`
|
Type string `xml:"type,attr"`
|
||||||
To string `xml:"to,attr,omitempty"`
|
To string `xml:"to,attr,omitempty"`
|
||||||
From string `xml:"from,attr,omitempty"`
|
From string `xml:"from,attr,omitempty"`
|
||||||
|
|
@ -30,7 +30,7 @@ type IQ struct {
|
||||||
|
|
||||||
// Encode the value to an XML string and set as the payload. See xml.Marshal
|
// Encode the value to an XML string and set as the payload. See xml.Marshal
|
||||||
// for how the value is encoded.
|
// for how the value is encoded.
|
||||||
func (iq *IQ) PayloadEncode(v interface{}) error {
|
func (iq *Iq) PayloadEncode(v interface{}) error {
|
||||||
bytes, err := xml.Marshal(v)
|
bytes, err := xml.Marshal(v)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -41,12 +41,12 @@ func (iq *IQ) PayloadEncode(v interface{}) error {
|
||||||
|
|
||||||
// Decode the payload (an XML string) into the given value. See xml.Unmarshal
|
// Decode the payload (an XML string) into the given value. See xml.Unmarshal
|
||||||
// for how the value is decoded.
|
// for how the value is decoded.
|
||||||
func (iq *IQ) PayloadDecode(v interface{}) error {
|
func (iq *Iq) PayloadDecode(v interface{}) error {
|
||||||
return xml.Unmarshal([]byte(iq.Payload), v)
|
return xml.Unmarshal([]byte(iq.Payload), v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Return the name of the payload element.
|
// Return the name of the payload element.
|
||||||
func (iq *IQ) PayloadName() (name xml.Name) {
|
func (iq *Iq) PayloadName() (name xml.Name) {
|
||||||
dec := xml.NewDecoder(bytes.NewBufferString(iq.Payload))
|
dec := xml.NewDecoder(bytes.NewBufferString(iq.Payload))
|
||||||
tok, err := dec.Token()
|
tok, err := dec.Token()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
|
@ -59,43 +59,30 @@ func (iq *IQ) PayloadName() (name xml.Name) {
|
||||||
return start.Name
|
return start.Name
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a response IQ. The ID is kept, To and From are reversed, Type is set
|
// Create a response Iq. The Id is kept, To and From are reversed, Type is set
|
||||||
// to the given value.
|
// to the given value.
|
||||||
func (iq *IQ) Response(iqType string) *IQ {
|
func (iq *Iq) Response(type_ string) *Iq {
|
||||||
return &IQ{ID: iq.ID, Type: iqType, From: iq.To, To: iq.From}
|
return &Iq{Id: iq.Id, Type: type_, From: iq.To, To: iq.From}
|
||||||
}
|
}
|
||||||
|
|
||||||
// XMPP <message/> stanza.
|
// XMPP <message/> stanza.
|
||||||
type Message struct {
|
type Message struct {
|
||||||
XMLName xml.Name `xml:"message"`
|
XMLName xml.Name `xml:"message"`
|
||||||
ID string `xml:"id,attr,omitempty"`
|
Id string `xml:"id,attr,omitempty"`
|
||||||
Type string `xml:"type,attr,omitempty"`
|
Type string `xml:"type,attr,omitempty"`
|
||||||
To string `xml:"to,attr,omitempty"`
|
To string `xml:"to,attr,omitempty"`
|
||||||
From string `xml:"from,attr,omitempty"`
|
From string `xml:"from,attr,omitempty"`
|
||||||
Subject string `xml:"subject,omitempty"`
|
Subject string `xml:"subject,omitempty"`
|
||||||
Body []MessageBody `xml:"body,omitempty"`
|
Body string `xml:"body,omitempty"`
|
||||||
Thread string `xml:"thread,omitempty"`
|
Thread string `xml:"thread,omitempty"`
|
||||||
Error *Error `xml:"error"`
|
Error *Error `xml:"error"`
|
||||||
Lang string `xml:"xml:lang,attr,omitempty"`
|
Confir *Confirm `xml:"confirm"`
|
||||||
|
|
||||||
Confirm *Confirm `xml:"confirm"` // XEP-0070
|
|
||||||
|
|
||||||
Active *Active `xml:"active"` // XEP-0085
|
|
||||||
Composing *Composing `xml:"composing"` // XEP-0085
|
|
||||||
Paused *Paused `xml:"paused"` // XEP-0085
|
|
||||||
Inactive *Inactive `xml:"inactive"` // XEP-0085
|
|
||||||
Gone *Gone `xml:"gone"` // XEP-0085
|
|
||||||
}
|
|
||||||
|
|
||||||
type MessageBody struct {
|
|
||||||
Lang string `xml:"xml:lang,attr,omitempty"`
|
|
||||||
Value string `xml:",chardata"`
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// XMPP <presence/> stanza.
|
// XMPP <presence/> stanza.
|
||||||
type Presence struct {
|
type Presence struct {
|
||||||
XMLName xml.Name `xml:"presence"`
|
XMLName xml.Name `xml:"presence"`
|
||||||
ID string `xml:"id,attr,omitempty"`
|
Id string `xml:"id,attr,omitempty"`
|
||||||
Type string `xml:"type,attr,omitempty"`
|
Type string `xml:"type,attr,omitempty"`
|
||||||
To string `xml:"to,attr,omitempty"`
|
To string `xml:"to,attr,omitempty"`
|
||||||
From string `xml:"from,attr,omitempty"`
|
From string `xml:"from,attr,omitempty"`
|
||||||
|
|
@ -109,17 +96,17 @@ type Presence struct {
|
||||||
// stanza, e.g. an <iq type="error"/>.
|
// stanza, e.g. an <iq type="error"/>.
|
||||||
type Error struct {
|
type Error struct {
|
||||||
XMLName xml.Name `xml:"error"`
|
XMLName xml.Name `xml:"error"`
|
||||||
Code string `xml:"code,attr,omitempty"`
|
|
||||||
Type string `xml:"type,attr"`
|
Type string `xml:"type,attr"`
|
||||||
Payload string `xml:",innerxml"`
|
Payload string `xml:",innerxml"`
|
||||||
}
|
}
|
||||||
|
|
||||||
func (e Error) Error() string {
|
func (e Error) Error() string {
|
||||||
text := e.Text()
|
if text := e.Text(); text == "" {
|
||||||
if text == "" {
|
|
||||||
return fmt.Sprintf("[%s] %s", e.Type, e.Condition().Local)
|
return fmt.Sprintf("[%s] %s", e.Type, e.Condition().Local)
|
||||||
|
} else {
|
||||||
|
return fmt.Sprintf("[%s] %s, %s", e.Type, e.Condition().Local, text)
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("[%s] %s, %s", e.Type, e.Condition().Local, text)
|
panic("unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
type errorText struct {
|
type errorText struct {
|
||||||
|
|
@ -147,12 +134,6 @@ func NewError(errorType string, condition ErrorCondition, text string) *Error {
|
||||||
return &Error{Type: errorType, Payload: string(buf.Bytes())}
|
return &Error{Type: errorType, Payload: string(buf.Bytes())}
|
||||||
}
|
}
|
||||||
|
|
||||||
func NewErrorWithCode(code, errorType string, condition ErrorCondition, text string) *Error {
|
|
||||||
err := NewError(errorType, condition, text)
|
|
||||||
err.Code = code
|
|
||||||
return err
|
|
||||||
}
|
|
||||||
|
|
||||||
// Return the error text from the payload, or "" if not present.
|
// Return the error text from the payload, or "" if not present.
|
||||||
func (e Error) Text() string {
|
func (e Error) Text() string {
|
||||||
dec := xml.NewDecoder(bytes.NewBufferString(e.Payload))
|
dec := xml.NewDecoder(bytes.NewBufferString(e.Payload))
|
||||||
|
|
@ -188,11 +169,8 @@ type ErrorCondition xml.Name
|
||||||
|
|
||||||
// Stanza errors.
|
// Stanza errors.
|
||||||
var (
|
var (
|
||||||
ErrorFeatureNotImplemented = ErrorCondition{nsErrorStanzas, "feature-not-implemented"}
|
FeatureNotImplemented = ErrorCondition{nsErrorStanzas, "feature-not-implemented"}
|
||||||
ErrorRemoteServerNotFound = ErrorCondition{nsErrorStanzas, "remote-server-not-found"}
|
RemoteServerNotFound = ErrorCondition{nsErrorStanzas, "remote-server-not-found"}
|
||||||
ErrorServiceUnavailable = ErrorCondition{nsErrorStanzas, "service-unavailable"}
|
ServiceUnavailable = ErrorCondition{nsErrorStanzas, "service-unavailable"}
|
||||||
ErrorNotAuthorized = ErrorCondition{nsErrorStanzas, "not-authorized"}
|
NotAuthorized = ErrorCondition{nsErrorStanzas, "not-authorized"}
|
||||||
ErrorConflict = ErrorCondition{nsErrorStanzas, "conflict"}
|
|
||||||
ErrorNotAcceptable = ErrorCondition{nsErrorStanzas, "not-acceptable"}
|
|
||||||
ErrorForbidden = ErrorCondition{nsErrorStanzas, "forbidden"}
|
|
||||||
)
|
)
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,6 @@ type StreamConfig struct {
|
||||||
// are either sent to the server or delivered to the application. It also
|
// are either sent to the server or delivered to the application. It also
|
||||||
// causes incoming stanzas to be XML-parsed a second time.
|
// causes incoming stanzas to be XML-parsed a second time.
|
||||||
LogStanzas bool
|
LogStanzas bool
|
||||||
|
|
||||||
// The dommain connection for certificate validation.
|
|
||||||
ConnectionDomain string
|
|
||||||
}
|
}
|
||||||
|
|
||||||
type Stream struct {
|
type Stream struct {
|
||||||
|
|
@ -28,9 +25,10 @@ type Stream struct {
|
||||||
config *StreamConfig
|
config *StreamConfig
|
||||||
stanzaBuf string
|
stanzaBuf string
|
||||||
incomingNamespace nsMap
|
incomingNamespace nsMap
|
||||||
|
connDomain string
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create a XML stream connection. A Stream is used by an XMPP instance to
|
// Create a XML stream connection. A Steam is used by an XMPP instance to
|
||||||
// handle sending and receiving XML data over the net connection.
|
// handle sending and receiving XML data over the net connection.
|
||||||
func NewStream(addr string, config *StreamConfig) (*Stream, error) {
|
func NewStream(addr string, config *StreamConfig) (*Stream, error) {
|
||||||
|
|
||||||
|
|
@ -45,10 +43,7 @@ func NewStream(addr string, config *StreamConfig) (*Stream, error) {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
stream := &Stream{conn: conn, dec: xml.NewDecoder(conn), config: config}
|
stream := &Stream{conn: conn, dec: xml.NewDecoder(conn), config: config, connDomain: strings.SplitN(addr, ":", 2)[0]}
|
||||||
if config.ConnectionDomain == "" {
|
|
||||||
config.ConnectionDomain = strings.SplitN(addr, ":", 2)[0]
|
|
||||||
}
|
|
||||||
|
|
||||||
if err := stream.send([]byte("<?xml version='1.0' encoding='utf-8'?>")); err != nil {
|
if err := stream.send([]byte("<?xml version='1.0' encoding='utf-8'?>")); err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
|
|
@ -148,11 +143,11 @@ func (stream *Stream) Next() (*xml.StartElement, error) {
|
||||||
}
|
}
|
||||||
|
|
||||||
if stream.config.LogStanzas {
|
if stream.config.LogStanzas {
|
||||||
xml, err := collectElement(stream.dec, start, stream.incomingNamespace)
|
if xml, err := collectElement(stream.dec, start, stream.incomingNamespace); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
return nil, err
|
||||||
|
} else {
|
||||||
|
stream.stanzaBuf = xml
|
||||||
}
|
}
|
||||||
stream.stanzaBuf = xml
|
|
||||||
log.Println("recv:", stream.stanzaBuf)
|
log.Println("recv:", stream.stanzaBuf)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -170,7 +165,7 @@ func nextStartElement(dec *xml.Decoder) (*xml.StartElement, error) {
|
||||||
}
|
}
|
||||||
switch e := t.(type) {
|
switch e := t.(type) {
|
||||||
case xml.StartElement:
|
case xml.StartElement:
|
||||||
for i := range e.Attr {
|
for i, _ := range e.Attr {
|
||||||
// Replace URL namespace to xml in order to avoid error on Unmarshal
|
// Replace URL namespace to xml in order to avoid error on Unmarshal
|
||||||
// It's quite ugly, but working for now
|
// It's quite ugly, but working for now
|
||||||
if e.Attr[i].Name.Space == "http://www.w3.org/XML/1998/namespace" {
|
if e.Attr[i].Name.Space == "http://www.w3.org/XML/1998/namespace" {
|
||||||
|
|
@ -183,6 +178,7 @@ func nextStartElement(dec *xml.Decoder) (*xml.StartElement, error) {
|
||||||
return nil, io.EOF
|
return nil, io.EOF
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
panic("Unreachable")
|
||||||
}
|
}
|
||||||
|
|
||||||
// Skip reads tokens until it reaches the end element of the most recent start
|
// Skip reads tokens until it reaches the end element of the most recent start
|
||||||
|
|
@ -204,11 +200,11 @@ func (stream *Stream) Decode(v interface{}, start *xml.StartElement) error {
|
||||||
// Explicity lookup next start element to ensure stream is validated,
|
// Explicity lookup next start element to ensure stream is validated,
|
||||||
// stanza is logged, etc.
|
// stanza is logged, etc.
|
||||||
if start == nil {
|
if start == nil {
|
||||||
se, err := stream.Next()
|
if se, err := stream.Next(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
|
} else {
|
||||||
|
start = se
|
||||||
}
|
}
|
||||||
start = se
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if stream.config.LogStanzas {
|
if stream.config.LogStanzas {
|
||||||
|
|
|
||||||
|
|
@ -76,13 +76,14 @@ func writeXMLAttr(w io.Writer, attr xml.Attr) error {
|
||||||
func startElementIter(dec *xml.Decoder) func() *xml.StartElement {
|
func startElementIter(dec *xml.Decoder) func() *xml.StartElement {
|
||||||
return func() *xml.StartElement {
|
return func() *xml.StartElement {
|
||||||
for {
|
for {
|
||||||
tok, err := dec.Token()
|
if tok, err := dec.Token(); err != nil {
|
||||||
if err != nil {
|
|
||||||
return nil
|
return nil
|
||||||
}
|
} else {
|
||||||
if start, ok := tok.(xml.StartElement); ok {
|
if start, ok := tok.(xml.StartElement); ok {
|
||||||
return &start
|
return &start
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -18,19 +18,19 @@ type XMPP struct {
|
||||||
JID JID
|
JID JID
|
||||||
stream *Stream
|
stream *Stream
|
||||||
|
|
||||||
// Channel of incoming messages. Values will be one of IQ, Message,
|
// Channel of incoming messages. Values will be one of Iq, Message,
|
||||||
// Presence, Error or error. Will be closed at the end when the stream is
|
// Presence, Error or error. Will be closed at the end when the stream is
|
||||||
// closed or the stream's net connection dies.
|
// closed or the stream's net connection dies.
|
||||||
In chan interface{}
|
In chan interface{}
|
||||||
|
|
||||||
// Channel of outgoing messages. Messages must be able to be marshaled by
|
// Channel of outgoing messages. Messages must be able to be marshaled by
|
||||||
// the standard xml package, however you should try to send one of IQ,
|
// the standard xml package, however you should try to send one of Iq,
|
||||||
// Message or Presence.
|
// Message or Presence.
|
||||||
Out chan interface{}
|
Out chan interface{}
|
||||||
|
|
||||||
// Incoming stanza filters.
|
// Incoming stanza filters.
|
||||||
filterLock sync.Mutex
|
filterLock sync.Mutex
|
||||||
nextFilterID FilterID
|
nextFilterId FilterId
|
||||||
filters []filter
|
filters []filter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -46,17 +46,17 @@ func newXMPP(jid JID, stream *Stream) *XMPP {
|
||||||
return x
|
return x
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *XMPP) SendRecv(iq *IQ) (*IQ, error) {
|
func (x *XMPP) SendRecv(iq *Iq) (*Iq, error) {
|
||||||
|
|
||||||
fid, ch := x.AddFilter(IQResult(iq.ID))
|
fid, ch := x.AddFilter(IqResult(iq.Id))
|
||||||
defer x.RemoveFilter(fid)
|
defer x.RemoveFilter(fid)
|
||||||
|
|
||||||
x.Out <- iq
|
x.Out <- iq
|
||||||
|
|
||||||
stanza := <-ch
|
stanza := <-ch
|
||||||
reply, ok := stanza.(*IQ)
|
reply, ok := stanza.(*Iq)
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, fmt.Errorf("Expected IQ, for %T", stanza)
|
return nil, fmt.Errorf("Expected Iq, for %T", stanza)
|
||||||
}
|
}
|
||||||
return reply, nil
|
return reply, nil
|
||||||
}
|
}
|
||||||
|
|
@ -76,25 +76,25 @@ func (fn MatcherFunc) Match(v interface{}) bool {
|
||||||
return fn(v)
|
return fn(v)
|
||||||
}
|
}
|
||||||
|
|
||||||
// Uniquely identifies a stream filter. Used to remove a filter that's no
|
// Uniquly identifies a stream fiter. Used to remove a filter that's no longer
|
||||||
// longer needed.
|
// needed.
|
||||||
type FilterID int64
|
type FilterId int64
|
||||||
|
|
||||||
// Implements the error interface for a FilterID.
|
// Implements the error interface for a FilterId.
|
||||||
func (fid FilterID) Error() string {
|
func (fid FilterId) Error() string {
|
||||||
return fmt.Sprintf("Invalid filter id: %d", fid)
|
return fmt.Sprintf("Invalid filter id: %d", fid)
|
||||||
}
|
}
|
||||||
|
|
||||||
type filter struct {
|
type filter struct {
|
||||||
id FilterID
|
id FilterId
|
||||||
m Matcher
|
m Matcher
|
||||||
ch chan interface{}
|
ch chan interface{}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add a filter that routes matching stanzas to the returned channel. A
|
// Add a filter that routes matching stanzas to the returned channel. A
|
||||||
// FilterID is also returned and can be pased to RemoveFilter to remove the
|
// FilterId is also returned and can be pased to RemoveFilter to remove the
|
||||||
// filter again.
|
// filter again.
|
||||||
func (x *XMPP) AddFilter(m Matcher) (FilterID, chan interface{}) {
|
func (x *XMPP) AddFilter(m Matcher) (FilterId, chan interface{}) {
|
||||||
|
|
||||||
// Protect against concurrent access.
|
// Protect against concurrent access.
|
||||||
x.filterLock.Lock()
|
x.filterLock.Lock()
|
||||||
|
|
@ -102,8 +102,8 @@ func (x *XMPP) AddFilter(m Matcher) (FilterID, chan interface{}) {
|
||||||
|
|
||||||
// Allocate chan and id.
|
// Allocate chan and id.
|
||||||
ch := make(chan interface{})
|
ch := make(chan interface{})
|
||||||
id := x.nextFilterID
|
id := x.nextFilterId
|
||||||
x.nextFilterID++
|
x.nextFilterId++
|
||||||
|
|
||||||
// Insert at head of filters list.
|
// Insert at head of filters list.
|
||||||
filters := make([]filter, len(x.filters)+1)
|
filters := make([]filter, len(x.filters)+1)
|
||||||
|
|
@ -115,7 +115,7 @@ func (x *XMPP) AddFilter(m Matcher) (FilterID, chan interface{}) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// Remove a filter previously added with AddFilter.
|
// Remove a filter previously added with AddFilter.
|
||||||
func (x *XMPP) RemoveFilter(id FilterID) error {
|
func (x *XMPP) RemoveFilter(id FilterId) error {
|
||||||
|
|
||||||
// Protect against concurrent access.
|
// Protect against concurrent access.
|
||||||
x.filterLock.Lock()
|
x.filterLock.Lock()
|
||||||
|
|
@ -145,14 +145,14 @@ func (x *XMPP) RemoveFilter(id FilterID) error {
|
||||||
|
|
||||||
// Matcher to identify a <iq id="..." type="result" /> stanza with the given
|
// Matcher to identify a <iq id="..." type="result" /> stanza with the given
|
||||||
// id.
|
// id.
|
||||||
func IQResult(id string) Matcher {
|
func IqResult(id string) Matcher {
|
||||||
return MatcherFunc(
|
return MatcherFunc(
|
||||||
func(v interface{}) bool {
|
func(v interface{}) bool {
|
||||||
iq, ok := v.(*IQ)
|
iq, ok := v.(*Iq)
|
||||||
if !ok {
|
if !ok {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
if iq.ID != id {
|
if iq.Id != id {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
|
@ -169,17 +169,12 @@ func (x *XMPP) sender() {
|
||||||
|
|
||||||
// Close the stream. Note: relies on common element name for all types of
|
// Close the stream. Note: relies on common element name for all types of
|
||||||
// XMPP connection.
|
// XMPP connection.
|
||||||
log.Println("Close XMPP stream")
|
x.stream.SendEnd(&xml.EndElement{xml.Name{"stream", "stream"}})
|
||||||
x.Close()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *XMPP) receiver() {
|
func (x *XMPP) receiver() {
|
||||||
|
|
||||||
defer func() {
|
defer close(x.In)
|
||||||
log.Println("Close XMPP receiver")
|
|
||||||
x.Close()
|
|
||||||
close(x.In)
|
|
||||||
}()
|
|
||||||
|
|
||||||
for {
|
for {
|
||||||
start, err := x.stream.Next()
|
start, err := x.stream.Next()
|
||||||
|
|
@ -193,18 +188,18 @@ func (x *XMPP) receiver() {
|
||||||
case "error":
|
case "error":
|
||||||
v = &Error{}
|
v = &Error{}
|
||||||
case "iq":
|
case "iq":
|
||||||
v = &IQ{}
|
v = &Iq{}
|
||||||
case "message":
|
case "message":
|
||||||
v = &Message{}
|
v = &Message{}
|
||||||
case "presence":
|
case "presence":
|
||||||
v = &Presence{}
|
v = &Presence{}
|
||||||
default:
|
default:
|
||||||
log.Printf("Error. Unexected element: %T %v", start, start)
|
log.Fatal("Unexected element: %T %v", start, start)
|
||||||
}
|
}
|
||||||
|
|
||||||
err = x.stream.Decode(v, start)
|
err = x.stream.Decode(v, start)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Println("Error. Failed to decode element. ", err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
|
|
||||||
filtered := false
|
filtered := false
|
||||||
|
|
@ -221,9 +216,4 @@ func (x *XMPP) receiver() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (x *XMPP) Close() {
|
|
||||||
log.Println("Close XMPP")
|
|
||||||
x.stream.SendEnd(&xml.EndElement{xml.Name{"stream", "stream"}})
|
|
||||||
}
|
|
||||||
|
|
||||||
// BUG(matt): Filter channels are not closed when the stream is closed.
|
// BUG(matt): Filter channels are not closed when the stream is closed.
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue