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"`