Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Extend SendPresence() stub func to allow send useful statuses #1

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
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
93 changes: 67 additions & 26 deletions xmpp.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,6 @@ var DefaultConfig = &tls.Config{}

// DebugWriter is the writer used to write debugging output to.
var DebugWriter io.Writer = os.Stderr
var StanzaWriter io.Writer

// Cookie is a unique XMPP session identifier
type Cookie uint64
Expand All @@ -70,10 +69,11 @@ func getCookie() Cookie {

// Client holds XMPP connection options
type Client struct {
conn net.Conn // connection to server
jid string // Jabber ID for our connection
domain string
p *xml.Decoder
conn net.Conn // connection to server
jid string // Jabber ID for our connection
domain string
p *xml.Decoder
stanzaWriter io.Writer
}

func (c *Client) JID() string {
Expand Down Expand Up @@ -378,7 +378,7 @@ func (c *Client) init(o *Options) error {
foundAnonymous := false
for _, m := range f.Mechanisms.Mechanism {
if m == "ANONYMOUS" {
fmt.Fprintf(StanzaWriter, "<auth xmlns='%s' mechanism='ANONYMOUS' />\n", nsSASL)
fmt.Fprintf(c.stanzaWriter, "<auth xmlns='%s' mechanism='ANONYMOUS' />\n", nsSASL)
foundAnonymous = true
break
}
Expand Down Expand Up @@ -436,7 +436,7 @@ func (c *Client) init(o *Options) error {
}
clientNonce := cnonce()
clientFirstMessage := "n=" + user + ",r=" + clientNonce
fmt.Fprintf(StanzaWriter, "<auth xmlns='%s' mechanism='%s'>%s</auth>",
fmt.Fprintf(c.stanzaWriter, "<auth xmlns='%s' mechanism='%s'>%s</auth>",
nsSASL, mechanism, base64.StdEncoding.EncodeToString([]byte("n,,"+
clientFirstMessage)))
var sfm string
Expand Down Expand Up @@ -536,15 +536,15 @@ func (c *Client) init(o *Options) error {
}
clientFinalMessage := base64.StdEncoding.EncodeToString([]byte(clientFinalMessageBare +
",p=" + base64.StdEncoding.EncodeToString(clientProof)))
fmt.Fprintf(StanzaWriter, "<response xmlns='%s'>%s</response>", nsSASL,
fmt.Fprintf(c.stanzaWriter, "<response xmlns='%s'>%s</response>", nsSASL,
clientFinalMessage)
}
if mechanism == "X-OAUTH2" && o.OAuthToken != "" && o.OAuthScope != "" {
// Oauth authentication: send base64-encoded \x00 user \x00 token.
raw := "\x00" + user + "\x00" + o.OAuthToken
enc := make([]byte, base64.StdEncoding.EncodedLen(len(raw)))
base64.StdEncoding.Encode(enc, []byte(raw))
fmt.Fprintf(StanzaWriter, "<auth xmlns='%s' mechanism='X-OAUTH2' auth:service='oauth2' "+
fmt.Fprintf(c.stanzaWriter, "<auth xmlns='%s' mechanism='X-OAUTH2' auth:service='oauth2' "+
"xmlns:auth='%s'>%s</auth>\n", nsSASL, o.OAuthXmlNs, enc)
}
if mechanism == "PLAIN" {
Expand All @@ -556,7 +556,7 @@ func (c *Client) init(o *Options) error {
}
if mechanism == "DIGEST-MD5" {
// Digest-MD5 authentication
fmt.Fprintf(StanzaWriter, "<auth xmlns='%s' mechanism='DIGEST-MD5'/>\n", nsSASL)
fmt.Fprintf(c.stanzaWriter, "<auth xmlns='%s' mechanism='DIGEST-MD5'/>\n", nsSASL)
var ch saslChallenge
if err = c.p.DecodeElement(&ch, nil); err != nil {
return errors.New("unmarshal <challenge>: " + err.Error())
Expand Down Expand Up @@ -586,7 +586,7 @@ func (c *Client) init(o *Options) error {
message := "username=\"" + user + "\", realm=\"" + realm + "\", nonce=\"" + nonce + "\", cnonce=\"" + cnonceStr +
"\", nc=" + nonceCount + ", qop=" + qop + ", digest-uri=\"" + digestURI + "\", response=" + digest + ", charset=" + charset

fmt.Fprintf(StanzaWriter, "<response xmlns='%s'>%s</response>\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message)))
fmt.Fprintf(c.stanzaWriter, "<response xmlns='%s'>%s</response>\n", nsSASL, base64.StdEncoding.EncodeToString([]byte(message)))

var rspauth saslRspAuth
if err = c.p.DecodeElement(&rspauth, nil); err != nil {
Expand All @@ -596,7 +596,7 @@ func (c *Client) init(o *Options) error {
if err != nil {
return err
}
fmt.Fprintf(StanzaWriter, "<response xmlns='%s'/>\n", nsSASL)
fmt.Fprintf(c.stanzaWriter, "<response xmlns='%s'/>\n", nsSASL)
}
}
if mechanism == "" {
Expand Down Expand Up @@ -649,9 +649,9 @@ func (c *Client) init(o *Options) error {

// Send IQ message asking to bind to the local user name.
if o.Resource == "" {
fmt.Fprintf(StanzaWriter, "<iq type='set' id='%x'><bind xmlns='%s'></bind></iq>\n", cookie, nsBind)
fmt.Fprintf(c.stanzaWriter, "<iq type='set' id='%x'><bind xmlns='%s'></bind></iq>\n", cookie, nsBind)
} else {
fmt.Fprintf(StanzaWriter, "<iq type='set' id='%x'><bind xmlns='%s'><resource>%s</resource></bind></iq>\n", cookie, nsBind, o.Resource)
fmt.Fprintf(c.stanzaWriter, "<iq type='set' id='%x'><bind xmlns='%s'><resource>%s</resource></bind></iq>\n", cookie, nsBind, o.Resource)
}
var iq clientIQ
if err = c.p.DecodeElement(&iq, nil); err != nil {
Expand All @@ -665,11 +665,11 @@ func (c *Client) init(o *Options) error {

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

// We're connected and can now receive and send messages.
fmt.Fprintf(StanzaWriter, "<presence xml:lang='en'><show>%s</show><status>%s</status></presence>", o.Status, o.StatusMessage)
fmt.Fprintf(c.stanzaWriter, "<presence xml:lang='en'><show>%s</show><status>%s</status></presence>", o.Status, o.StatusMessage)

return nil
}
Expand All @@ -691,7 +691,7 @@ func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string
}
var err error

fmt.Fprintf(StanzaWriter, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n")
fmt.Fprintf(c.stanzaWriter, "<starttls xmlns='urn:ietf:params:xml:ns:xmpp-tls'/>\n")
var k tlsProceed
if err = c.p.DecodeElement(&k, nil); err != nil {
return f, errors.New("unmarshal <proceed>: " + err.Error())
Expand Down Expand Up @@ -724,13 +724,13 @@ func (c *Client) startTLSIfRequired(f *streamFeatures, o *Options, domain string
func (c *Client) startStream(o *Options, domain string) (*streamFeatures, error) {
if o.Debug {
c.p = xml.NewDecoder(tee{c.conn, DebugWriter})
StanzaWriter = io.MultiWriter(c.conn, DebugWriter)
c.stanzaWriter = io.MultiWriter(c.conn, DebugWriter)
} else {
c.p = xml.NewDecoder(c.conn)
StanzaWriter = c.conn
c.stanzaWriter = c.conn
}

_, err := fmt.Fprintf(StanzaWriter, "<?xml version='1.0'?>"+
_, err := fmt.Fprintf(c.stanzaWriter, "<?xml version='1.0'?>"+
"<stream:stream to='%s' xmlns='%s'"+
" xmlns:stream='%s' version='1.0'>",
xmlEscape(domain), nsClient, nsStream)
Expand Down Expand Up @@ -1058,7 +1058,7 @@ func (c *Client) Send(chat Chat) (n int, err error) {

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

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

Expand All @@ -1075,17 +1075,58 @@ func (c *Client) SendOOB(chat Chat) (n int, err error) {
}
oobtext += `</x>`
}
return fmt.Fprintf(StanzaWriter, "<message to='%s' type='%s' id='%s' xml:lang='en'>"+oobtext+thdtext+"</message>",
return fmt.Fprintf(c.stanzaWriter, "<message to='%s' type='%s' id='%s' xml:lang='en'>"+oobtext+thdtext+"</message>",
xmlEscape(chat.Remote), xmlEscape(chat.Type), cnonce())
}

// SendOrg sends the original text without being wrapped in an XMPP message stanza.
func (c *Client) SendOrg(org string) (n int, err error) {
return fmt.Fprint(StanzaWriter, org)
return fmt.Fprint(c.stanzaWriter, org)
}

func (c *Client) SendPresence(presence Presence) (n int, err error) {
return fmt.Fprintf(StanzaWriter, "<presence from='%s' to='%s'/>", xmlEscape(presence.From), xmlEscape(presence.To))
// Forge opening presence tag
var buf string = "<presence"

if presence.From != "" {
buf = buf + fmt.Sprintf(" from='%s'", xmlEscape(presence.From))
}

if presence.To != "" {
buf = buf + fmt.Sprintf(" to='%s'", xmlEscape(presence.To))
}

if presence.Type != "" {
// https://www.ietf.org/rfc/rfc3921.txt, 2.2.1, types can only be
// unavailable, subscribe, subscribed, unsubscribe, unsubscribed, probe, error
switch presence.Type {
case "unavailable", "subscribe", "subscribed", "unsubscribe", "unsubscribed", "probe", "error":
buf = buf + fmt.Sprintf(" type='%s'", xmlEscape(presence.Type))
}
}

buf = buf + ">"

// TODO: there may be optional tag "priority", but former presence type does not take this into account
// so either we must follow std, change type xmpp.Presence and break backward compatibility
// or leave it as-is and potentially break client software

if presence.Show != "" {
// https://www.ietf.org/rfc/rfc3921.txt 2.2.2.1, show can be only
// away, chat, dnd, xa
switch presence.Show {
case "away", "chat", "dnd", "xa":
buf = buf + fmt.Sprintf("<show>%s</show>", xmlEscape(presence.Show))
}
}

if presence.Status != "" {
buf = buf + fmt.Sprintf("<status>%s</status>", xmlEscape(presence.Status))
}

buf = buf + "</presence>"

return fmt.Fprint(c.StanzaWriter, buf)
}

// SendKeepAlive sends a "whitespace keepalive" as described in chapter 4.6.1 of RFC6120.
Expand All @@ -1095,15 +1136,15 @@ func (c *Client) SendKeepAlive() (n int, err error) {

// SendHtml sends the message as HTML as defined by XEP-0071
func (c *Client) SendHtml(chat Chat) (n int, err error) {
return fmt.Fprintf(StanzaWriter, "<message to='%s' type='%s' xml:lang='en'>"+
return fmt.Fprintf(c.stanzaWriter, "<message to='%s' type='%s' xml:lang='en'>"+
"<body>%s</body>"+
"<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html></message>",
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text), chat.Text)
}

// Roster asks for the chat roster.
func (c *Client) Roster() error {
fmt.Fprintf(StanzaWriter, "<iq from='%s' type='get' id='roster1'><query xmlns='jabber:iq:roster'/></iq>\n", xmlEscape(c.jid))
fmt.Fprintf(c.stanzaWriter, "<iq from='%s' type='get' id='roster1'><query xmlns='jabber:iq:roster'/></iq>\n", xmlEscape(c.jid))
return nil
}

Expand Down
4 changes: 2 additions & 2 deletions xmpp_information_query.go
Original file line number Diff line number Diff line change
Expand Up @@ -35,13 +35,13 @@ func (c *Client) DiscoverEntityItems(jid string) (string, error) {
// RawInformationQuery sends an information query request to the server.
func (c *Client) RawInformationQuery(from, to, id, iqType, requestNamespace, body string) (string, error) {
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'><query xmlns='%s'>%s</query></iq>"
_, err := fmt.Fprintf(StanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body)
_, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, requestNamespace, body)
return id, err
}

// rawInformation send a IQ request with the payload body to the server
func (c *Client) RawInformation(from, to, id, iqType, body string) (string, error) {
const xmlIQ = "<iq from='%s' to='%s' id='%s' type='%s'>%s</iq>"
_, err := fmt.Fprintf(StanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, body)
_, err := fmt.Fprintf(c.stanzaWriter, xmlIQ, xmlEscape(from), xmlEscape(to), id, iqType, body)
return id, err
}
26 changes: 13 additions & 13 deletions xmpp_muc.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,15 +25,15 @@ const (

// Send sends room topic wrapped inside an XMPP message stanza body.
func (c *Client) SendTopic(chat Chat) (n int, err error) {
return fmt.Fprintf(StanzaWriter, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>",
return fmt.Fprintf(c.stanzaWriter, "<message to='%s' type='%s' xml:lang='en'>"+"<subject>%s</subject></message>",
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text))
}

func (c *Client) JoinMUCNoHistory(jid, nick string) (n int, err error) {
if nick == "" {
nick = c.jid
}
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>"+
"<history maxchars='0'/></x>\n"+
"</presence>",
Expand All @@ -47,31 +47,31 @@ func (c *Client) JoinMUC(jid, nick string, history_type, history int, history_da
}
switch history_type {
case NoHistory:
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s' />\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC)
case CharHistory:
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<history maxchars='%d'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
case StanzaHistory:
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<history maxstanzas='%d'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
case SecondsHistory:
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<history seconds='%d'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, history)
case SinceHistory:
if history_date != nil {
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<history since='%s'/></x>\n"+
"</presence>",
Expand All @@ -88,36 +88,36 @@ func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_typ
}
switch history_type {
case NoHistory:
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<password>%s</password>"+
"</x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password))
case CharHistory:
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<password>%s</password>\n"+
"<history maxchars='%d'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
case StanzaHistory:
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<password>%s</password>\n"+
"<history maxstanzas='%d'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
case SecondsHistory:
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<password>%s</password>\n"+
"<history seconds='%d'/></x>\n"+
"</presence>",
xmlEscape(jid), xmlEscape(nick), nsMUC, xmlEscape(password), history)
case SinceHistory:
if history_date != nil {
return fmt.Fprintf(StanzaWriter, "<presence to='%s/%s'>\n"+
return fmt.Fprintf(c.stanzaWriter, "<presence to='%s/%s'>\n"+
"<x xmlns='%s'>\n"+
"<password>%s</password>\n"+
"<history since='%s'/></x>\n"+
Expand All @@ -130,6 +130,6 @@ func (c *Client) JoinProtectedMUC(jid, nick string, password string, history_typ

// xep-0045 7.14
func (c *Client) LeaveMUC(jid string) (n int, err error) {
return fmt.Fprintf(StanzaWriter, "<presence from='%s' to='%s' type='unavailable' />",
return fmt.Fprintf(c.stanzaWriter, "<presence from='%s' to='%s' type='unavailable' />",
c.jid, xmlEscape(jid))
}
6 changes: 3 additions & 3 deletions xmpp_ping.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,23 +11,23 @@ func (c *Client) PingC2S(jid, server string) error {
if server == "" {
server = c.domain
}
_, err := fmt.Fprintf(StanzaWriter, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+
_, err := fmt.Fprintf(c.stanzaWriter, "<iq from='%s' to='%s' id='c2s1' type='get'>\n"+
"<ping xmlns='urn:xmpp:ping'/>\n"+
"</iq>",
xmlEscape(jid), xmlEscape(server))
return err
}

func (c *Client) PingS2S(fromServer, toServer string) error {
_, err := fmt.Fprintf(StanzaWriter, "<iq from='%s' to='%s' id='s2s1' type='get'>\n"+
_, err := fmt.Fprintf(c.stanzaWriter, "<iq from='%s' to='%s' id='s2s1' type='get'>\n"+
"<ping xmlns='urn:xmpp:ping'/>\n"+
"</iq>",
xmlEscape(fromServer), xmlEscape(toServer))
return err
}

func (c *Client) SendResultPing(id, toServer string) error {
_, err := fmt.Fprintf(StanzaWriter, "<iq type='result' to='%s' id='%s'/>",
_, err := fmt.Fprintf(c.stanzaWriter, "<iq type='result' to='%s' id='%s'/>",
xmlEscape(toServer), xmlEscape(id))
return err
}
6 changes: 3 additions & 3 deletions xmpp_subscription.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@ import (
)

func (c *Client) ApproveSubscription(jid string) {
fmt.Fprintf(StanzaWriter, "<presence to='%s' type='subscribed'/>",
fmt.Fprintf(c.stanzaWriter, "<presence to='%s' type='subscribed'/>",
xmlEscape(jid))
}

func (c *Client) RevokeSubscription(jid string) {
fmt.Fprintf(StanzaWriter, "<presence to='%s' type='unsubscribed'/>",
fmt.Fprintf(c.stanzaWriter, "<presence to='%s' type='unsubscribed'/>",
xmlEscape(jid))
}

Expand All @@ -20,6 +20,6 @@ func (c *Client) RetrieveSubscription(jid string) {
}

func (c *Client) RequestSubscription(jid string) {
fmt.Fprintf(StanzaWriter, "<presence to='%s' type='subscribe'/>",
fmt.Fprintf(c.stanzaWriter, "<presence to='%s' type='subscribe'/>",
xmlEscape(jid))
}