Add support for XEP-0478: Stream Limits Advertisement.

This commit is contained in:
Martin Dosch 2024-03-28 15:49:08 +01:00
parent bbd90cc04b
commit 07196efcf3

90
xmpp.go
View File

@ -46,13 +46,14 @@ import (
) )
const ( const (
nsStream = "http://etherx.jabber.org/streams" nsStream = "http://etherx.jabber.org/streams"
nsTLS = "urn:ietf:params:xml:ns:xmpp-tls" nsTLS = "urn:ietf:params:xml:ns:xmpp-tls"
nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl" nsSASL = "urn:ietf:params:xml:ns:xmpp-sasl"
nsBind = "urn:ietf:params:xml:ns:xmpp-bind" nsBind = "urn:ietf:params:xml:ns:xmpp-bind"
nsSASLCB = "urn:xmpp:sasl-cb:0" nsSASLCB = "urn:xmpp:sasl-cb:0"
nsClient = "jabber:client" nsClient = "jabber:client"
nsSession = "urn:ietf:params:xml:ns:xmpp-session" nsSession = "urn:ietf:params:xml:ns:xmpp-session"
nsStreamLimits = "urn:xmpp:stream-limits:0"
) )
// Default TLS configuration options // Default TLS configuration options
@ -74,13 +75,15 @@ func getCookie() Cookie {
// Client holds XMPP connection options // Client holds XMPP connection options
type Client struct { type Client struct {
conn net.Conn // connection to server conn net.Conn // connection to server
jid string // Jabber ID for our connection jid string // Jabber ID for our connection
domain string domain string
nextMutex sync.Mutex // Mutex to prevent multiple access to xml.Decoder nextMutex sync.Mutex // Mutex to prevent multiple access to xml.Decoder
p *xml.Decoder p *xml.Decoder
stanzaWriter io.Writer stanzaWriter io.Writer
Mechanism string 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 { func (c *Client) JID() string {
@ -392,6 +395,20 @@ func (c *Client) init(o *Options) error {
if err != nil { if err != nil {
return err 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 the server requires we STARTTLS, attempt to do so.
if f, err = c.startTLSIfRequired(f, o, domain); err != nil { 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 += `</x>` oobtext += `</x>`
} }
stanza := "<message to='%s' type='%s' id='%s' xml:lang='en'>" + subtext + "<body>%s</body>" + oobtext + thdtext + "</message>\n"
chat.Text = validUTF8(chat.Text) chat.Text = validUTF8(chat.Text)
return fmt.Fprintf(c.stanzaWriter, stanza, stanza := fmt.Sprintf("<message to='%s' type='%s' id='%s' xml:lang='en'>"+subtext+"<body>%s</body>"+oobtext+thdtext+"</message>\n",
xmlEscape(chat.Remote), xmlEscape(chat.Type), cnonce(), xmlEscape(chat.Text)) 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. // 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 += `</x>` oobtext += `</x>`
} }
return fmt.Fprintf(c.stanzaWriter, "<message to='%s' type='%s' id='%s' xml:lang='en'>"+oobtext+thdtext+"</message>\n", stanza := fmt.Sprintf("<message to='%s' type='%s' id='%s' xml:lang='en'>"+oobtext+thdtext+"</message>\n",
xmlEscape(chat.Remote), xmlEscape(chat.Type), cnonce()) 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. // SendOrg sends the original text without being wrapped in an XMPP message stanza.
func (c *Client) SendOrg(org string) (n int, err error) { 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. // 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("<status>%s</status>", xmlEscape(presence.Status)) buf = buf + fmt.Sprintf("<status>%s</status>", xmlEscape(presence.Status))
} }
buf = buf + "</presence>" stanza := fmt.Sprintf(buf + "</presence>")
if c.LimitMaxBytes != 0 && len(stanza) > c.LimitMaxBytes {
return fmt.Fprint(c.stanzaWriter, buf) 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. // 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 // SendHtml sends the message as HTML as defined by XEP-0071
func (c *Client) SendHtml(chat Chat) (n int, err error) { func (c *Client) SendHtml(chat Chat) (n int, err error) {
return fmt.Fprintf(c.stanzaWriter, "<message to='%s' type='%s' xml:lang='en'>"+ stanza := fmt.Sprintf("<message to='%s' type='%s' xml:lang='en'><body>%s</body>"+
"<body>%s</body>"+
"<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html></message>\n", "<html xmlns='http://jabber.org/protocol/xhtml-im'><body xmlns='http://www.w3.org/1999/xhtml'>%s</body></html></message>\n",
xmlEscape(chat.Remote), xmlEscape(chat.Type), xmlEscape(chat.Text), chat.Text) 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. // Roster asks for the chat roster.
@ -1281,6 +1314,7 @@ type streamFeatures struct {
ChannelBindings saslChannelBindings ChannelBindings saslChannelBindings
Bind bindBind Bind bindBind
Session bool Session bool
Limits streamLimits
} }
type streamError struct { type streamError struct {
@ -1343,6 +1377,14 @@ type saslChallenge struct {
Text string `xml:",chardata"` 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 // RFC 3920 C.5 Resource binding name space
type bindBind struct { type bindBind 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"`