Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
216 changes: 136 additions & 80 deletions xmpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,6 @@ func cnonce() string {
}

func (c *Client) init(o *Options) error {

var domain string
var user string
a := strings.SplitN(o.User, "@", 2)
Expand Down Expand Up @@ -501,7 +500,7 @@ func (c *Client) init(o *Options) error {
c.domain = domain

if o.Session {
//if server support session, open it
// if server support session, open it
fmt.Fprintf(c.conn, "<iq to='%s' type='set' id='%x'><session xmlns='%s'/></iq>", xmlEscape(domain), cookie, nsSession)
}

Expand Down Expand Up @@ -537,7 +536,7 @@ func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string
tc := o.TLSConfig
if tc == nil {
tc = DefaultConfig.Clone()
//TODO(scott): we should consider using the server's address or reverse lookup
// TODO(scott): we should consider using the server's address or reverse lookup
tc.ServerName = domain
}
t := tls.Client(c.conn, tc)
Expand Down Expand Up @@ -608,6 +607,8 @@ type Chat struct {
Thread string
Ooburl string
Oobdesc string
ID string
ReplaceID string
Roster Roster
Other []string
OtherElem []XMLElement
Expand Down Expand Up @@ -681,6 +682,8 @@ func (c *Client) Recv() (stanza interface{}, err error) {
Text: v.Body,
Subject: v.Subject,
Thread: v.Thread,
ID: v.ID,
ReplaceID: v.ReplaceID.ID,
Other: v.OtherStrings(),
OtherElem: v.Other,
Stamp: stamp,
Expand Down Expand Up @@ -722,93 +725,127 @@ func (c *Client) Recv() (stanza interface{}, err error) {
Errors: errsStr,
}, nil
}
case v.Type == "result" && v.ID == "unsub1":
// Unsubscribing MAY contain a pubsub element. But it does
// not have to
return PubsubUnsubscription{
SubID: "",
JID: v.From,
Node: "",
Errors: nil,
}, nil
case v.Query.XMLName.Local == "pubsub":
case v.Type == "result":
switch v.ID {
case "sub1":
// Subscription or unsubscription was successful
var sub clientPubsubSubscription
err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub)
if err != nil {
return PubsubSubscription{}, err
}
if v.Query.XMLName.Local == "pubsub" {
// Subscription or unsubscription was successful
var sub clientPubsubSubscription
err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub)
if err != nil {
return PubsubSubscription{}, err
}

return PubsubSubscription{
SubID: sub.SubID,
JID: sub.JID,
Node: sub.Node,
Errors: nil,
}, nil
case "unsub1":
var sub clientPubsubSubscription
err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub)
if err != nil {
return PubsubUnsubscription{}, err
return PubsubSubscription{
SubID: sub.SubID,
JID: sub.JID,
Node: sub.Node,
Errors: nil,
}, nil
}
case "unsub1":
if v.Query.XMLName.Local == "pubsub" {
var sub clientPubsubSubscription
err := xml.Unmarshal([]byte(v.Query.InnerXML), &sub)
if err != nil {
return PubsubUnsubscription{}, err
}

return PubsubUnsubscription{
SubID: sub.SubID,
JID: v.From,
Node: sub.Node,
Errors: nil,
}, nil
case "items1", "items3":
var p clientPubsubItems
err := xml.Unmarshal([]byte(v.Query.InnerXML), &p)
if err != nil {
return PubsubItems{}, err
return PubsubUnsubscription{
SubID: sub.SubID,
JID: v.From,
Node: sub.Node,
Errors: nil,
}, nil
} else {
// Unsubscribing MAY contain a pubsub element. But it does
// not have to
return PubsubUnsubscription{
SubID: "",
JID: v.From,
Node: "",
Errors: nil,
}, nil
}

switch p.Node {
case XMPPNS_AVATAR_PEP_DATA:
if len(p.Items) == 0 {
return AvatarData{}, errors.New("No avatar data items available")
case "info1":
if v.Query.XMLName.Space == XMPPNS_DISCO_ITEMS {
var itemsQuery clientDiscoItemsQuery
err := xml.Unmarshal(v.InnerXML, &itemsQuery)
if err != nil {
return []DiscoItem{}, err
}

return handleAvatarData(p.Items[0].Body,
v.From,
p.Items[0].ID)
case XMPPNS_AVATAR_PEP_METADATA:
if len(p.Items) == 0 {
return AvatarMetadata{}, errors.New("No avatar metadata items available")
return DiscoItems{
Jid: v.From,
Items: clientDiscoItemsToReturn(itemsQuery.Items),
}, nil
}
case "info3":
if v.Query.XMLName.Space == XMPPNS_DISCO_INFO {
var disco clientDiscoQuery
err := xml.Unmarshal(v.InnerXML, &disco)
if err != nil {
return DiscoResult{}, err
}

return handleAvatarMetadata(p.Items[0].Body,
v.From)
default:
return PubsubItems{
p.Node,
pubsubItemsToReturn(p.Items),
return DiscoResult{
Features: clientFeaturesToReturn(disco.Features),
Identities: clientIdentitiesToReturn(disco.Identities),
}, nil
}
case "items1", "items3":
if v.Query.XMLName.Local == "pubsub" {
var p clientPubsubItems
err := xml.Unmarshal([]byte(v.Query.InnerXML), &p)
if err != nil {
return PubsubItems{}, err
}

switch p.Node {
case XMPPNS_AVATAR_PEP_DATA:
if len(p.Items) == 0 {
return AvatarData{}, errors.New("No avatar data items available")
}

return handleAvatarData(p.Items[0].Body,
v.From,
p.Items[0].ID)
case XMPPNS_AVATAR_PEP_METADATA:
if len(p.Items) == 0 {
return AvatarMetadata{}, errors.New("No avatar metadata items available")
}

return handleAvatarMetadata(p.Items[0].Body,
v.From)
default:
return PubsubItems{
p.Node,
pubsubItemsToReturn(p.Items),
}, nil
}
}
// Note: XEP-0084 states that metadata and data
// should be fetched with an id of retrieve1.
// Since we already have PubSub implemented, we
// can just use items1 and items3 to do the same
// as an Avatar node is just a PEP (PubSub) node.
/*case "retrieve1":
var p clientPubsubItems
err := xml.Unmarshal([]byte(v.Query.InnerXML), &p)
if err != nil {
return PubsubItems{}, err
}
if v.Query.XMLName.Local == "pubsub" {
var p clientPubsubItems
err := xml.Unmarshal([]byte(v.Query.InnerXML), &p)
if err != nil {
return PubsubItems{}, err
}

switch p.Node {
case XMPPNS_AVATAR_PEP_DATA:
return handleAvatarData(p.Items[0].Body,
v.From,
p.Items[0].ID)
case XMPPNS_AVATAR_PEP_METADATA:
return handleAvatarMetadata(p.Items[0].Body,
v
switch p.Node {
case XMPPNS_AVATAR_PEP_DATA:
return handleAvatarData(p.Items[0].Body,
v.From,
p.Items[0].ID)
case XMPPNS_AVATAR_PEP_METADATA:
return handleAvatarMetadata(p.Items[0].Body,
v.From)
}
}*/
}
case v.Query.XMLName.Local == "":
Expand All @@ -819,16 +856,18 @@ func (c *Client) Recv() (stanza interface{}, err error) {
return Chat{}, err
}

return IQ{ID: v.ID, From: v.From, To: v.To, Type: v.Type,
Query: res}, nil
return IQ{
ID: v.ID, From: v.From, To: v.To, Type: v.Type,
Query: res,
}, nil
}
}
}
}

// Send sends the message wrapped inside an XMPP message stanza body.
func (c *Client) Send(chat Chat) (n int, err error) {
var subtext, thdtext, oobtext string
var subtext, thdtext, oobtext, msgidtext, msgcorrecttext string
if chat.Subject != `` {
subtext = `<subject>` + xmlEscape(chat.Subject) + `</subject>`
}
Expand All @@ -843,10 +882,19 @@ func (c *Client) Send(chat Chat) (n int, err error) {
oobtext += `</x>`
}

stanza := "<message to='%s' type='%s' id='%s' xml:lang='en'>" + subtext + "<body>%s</body>" + oobtext + thdtext + "</message>"
if chat.ID != `` {
msgidtext = `id='` + xmlEscape(chat.ID) + `'`
} else {
msgidtext = `id='` + cnonce() + `'`
}

if chat.ReplaceID != `` {
msgcorrecttext = `<replace id='` + xmlEscape(chat.ReplaceID) + `' xmlns='urn:xmpp:message-correct:0'/>`
}

stanza := "<message to='%s' type='%s' " + msgidtext + " xml:lang='en'>" + subtext + "<body>%s</body>" + msgcorrecttext + oobtext + thdtext + "</message>"

return fmt.Fprintf(c.conn, stanza,
xmlEscape(chat.Remote), xmlEscape(chat.Type), cnonce(), xmlEscape(chat.Text))
return fmt.Fprintf(c.conn, stanza, xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
}

// SendOOB sends OOB data wrapped inside an XMPP message stanza, without actual body.
Expand Down Expand Up @@ -961,6 +1009,11 @@ type bindBind struct {
Jid string `xml:"jid"`
}

type clientMessageCorrect struct {
XMLName xml.Name `xml:"urn:xmpp:message-correct:0 replace"`
ID string `xml:"id,attr"`
}

// RFC 3921 B.1 jabber:client
type clientMessage struct {
XMLName xml.Name `xml:"jabber:client message"`
Expand All @@ -970,9 +1023,10 @@ type clientMessage struct {
Type string `xml:"type,attr"` // chat, error, groupchat, headline, or normal

// These should technically be []clientText, but string is much more convenient.
Subject string `xml:"subject"`
Body string `xml:"body"`
Thread string `xml:"thread"`
Subject string `xml:"subject"`
Body string `xml:"body"`
Thread string `xml:"thread"`
ReplaceID clientMessageCorrect

// Pubsub
Event clientPubsubEvent `xml:"event"`
Expand Down Expand Up @@ -1051,6 +1105,8 @@ type clientIQ struct {
Query XMLElement `xml:",any"`
Error clientError
Bind bindBind

InnerXML []byte `xml:",innerxml"`
}

type clientError struct {
Expand Down
Loading