diff --git a/xmpp.go b/xmpp.go index 43199f7..0d30d9f 100644 --- a/xmpp.go +++ b/xmpp.go @@ -46,13 +46,14 @@ import ( ) const ( - 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" - nsSASLCB = "urn:xmpp:sasl-cb:0" - nsClient = "jabber:client" - nsSession = "urn:ietf:params:xml:ns:xmpp-session" + 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" + nsSASLCB = "urn:xmpp:sasl-cb:0" + nsClient = "jabber:client" + nsSession = "urn:ietf:params:xml:ns:xmpp-session" + nsStreamLimits = "urn:xmpp:stream-limits:0" ) // Default TLS configuration options @@ -74,13 +75,15 @@ 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 - nextMutex sync.Mutex // Mutex to prevent multiple access to xml.Decoder - p *xml.Decoder - stanzaWriter io.Writer - Mechanism string + conn net.Conn // connection to server + jid string // Jabber ID for our connection + domain string + nextMutex sync.Mutex // Mutex to prevent multiple access to xml.Decoder + p *xml.Decoder + stanzaWriter io.Writer + LimitMaxBytes int // Maximum stanza size (XEP-0478: Stream Limits Advertisement) + LimitIdleSeconds int // Maximum idle seconds (XEP-0478: Stream Limits Advertisement) + Mechanism string } func (c *Client) JID() string { @@ -392,6 +395,20 @@ func (c *Client) init(o *Options) error { if err != nil { return err } + // Make the max. stanza size limit available. + if f.Limits.MaxBytes != "" { + c.LimitMaxBytes, err = strconv.Atoi(f.Limits.MaxBytes) + if err != nil { + c.LimitMaxBytes = 0 + } + } + // Make the servers time limit after which it might consider the stream idle available. + if f.Limits.IdleSeconds != "" { + c.LimitIdleSeconds, err = strconv.Atoi(f.Limits.IdleSeconds) + if err != nil { + c.LimitIdleSeconds = 0 + } + } // If the server requires we STARTTLS, attempt to do so. if f, err = c.startTLSIfRequired(f, o, domain); err != nil { @@ -1179,11 +1196,14 @@ func (c *Client) Send(chat Chat) (n int, err error) { oobtext += `` } - stanza := "" + subtext + "%s" + oobtext + thdtext + "\n" - chat.Text = validUTF8(chat.Text) - return fmt.Fprintf(c.stanzaWriter, stanza, + stanza := fmt.Sprintf(""+subtext+"%s"+oobtext+thdtext+"\n", xmlEscape(chat.Remote), xmlEscape(chat.Type), cnonce(), xmlEscape(chat.Text)) + if c.LimitMaxBytes != 0 && len(stanza) > c.LimitMaxBytes { + return 0, errors.New("max. stanza size exceeded") + } + + return fmt.Fprint(c.stanzaWriter, stanza) } // SendOOB sends OOB data wrapped inside an XMPP message stanza, without actual body. @@ -1199,13 +1219,21 @@ func (c *Client) SendOOB(chat Chat) (n int, err error) { } oobtext += `` } - return fmt.Fprintf(c.stanzaWriter, ""+oobtext+thdtext+"\n", + stanza := fmt.Sprintf(""+oobtext+thdtext+"\n", xmlEscape(chat.Remote), xmlEscape(chat.Type), cnonce()) + if c.LimitMaxBytes != 0 && len(stanza) > c.LimitMaxBytes { + return 0, errors.New("max. stanza size exceeded") + } + return fmt.Fprint(c.stanzaWriter, stanza) } // 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(c.stanzaWriter, org+"\n") + stanza := fmt.Sprint(org + "\n") + if c.LimitMaxBytes != 0 && len(stanza) > c.LimitMaxBytes { + return 0, errors.New("max. stanza size exceeded") + } + return fmt.Fprint(c.stanzaWriter, stanza) } // SendPresence sends Presence wrapped inside XMPP presence stanza. @@ -1249,9 +1277,11 @@ func (c *Client) SendPresence(presence Presence) (n int, err error) { buf = buf + fmt.Sprintf("%s", xmlEscape(presence.Status)) } - buf = buf + "" - - return fmt.Fprint(c.stanzaWriter, buf) + stanza := fmt.Sprintf(buf + "") + if c.LimitMaxBytes != 0 && len(stanza) > c.LimitMaxBytes { + return 0, errors.New("max. stanza size exceeded") + } + return fmt.Fprint(c.stanzaWriter, stanza) } // SendKeepAlive sends a "whitespace keepalive" as described in chapter 4.6.1 of RFC6120. @@ -1261,10 +1291,13 @@ 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(c.stanzaWriter, ""+ - "%s"+ + stanza := fmt.Sprintf("%s"+ "%s\n", xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text), chat.Text) + if c.LimitMaxBytes != 0 && len(stanza) > c.LimitMaxBytes { + return 0, errors.New("max. stanza size exceeded") + } + return fmt.Fprint(c.stanzaWriter, stanza) } // Roster asks for the chat roster. @@ -1281,6 +1314,7 @@ type streamFeatures struct { ChannelBindings saslChannelBindings Bind bindBind Session bool + Limits streamLimits } type streamError struct { @@ -1343,6 +1377,14 @@ type saslChallenge struct { Text string `xml:",chardata"` } +type streamLimits struct { + XMLName xml.Name `xml:"limits"` + Text string `xml:",chardata"` + Xmlns string `xml:"xmlns,attr"` + MaxBytes string `xml:"max-bytes"` + IdleSeconds string `xml:"idle-seconds"` +} + // RFC 3920 C.5 Resource binding name space type bindBind struct { XMLName xml.Name `xml:"urn:ietf:params:xml:ns:xmpp-bind bind"`