Resync with Master

Support NullableInt on MUC presence history element
This commit is contained in:
Mickael Remond 2019-06-26 18:42:40 +02:00
parent 5ed66de79e
commit 781b875cf1
No known key found for this signature in database
GPG Key ID: E6F6045D79965AA3
2 changed files with 163 additions and 7 deletions

View File

@ -2,6 +2,7 @@ package stanza
import ( import (
"encoding/xml" "encoding/xml"
"strconv"
"time" "time"
) )
@ -16,14 +17,132 @@ type MucPresence struct {
History History `xml:"history,omitempty"` History History `xml:"history,omitempty"`
} }
const timeLayout = "2006-01-02T15:04:05Z"
// History implements XEP-0045: Multi-User Chat - 19.1 // History implements XEP-0045: Multi-User Chat - 19.1
type History struct { type History struct {
MaxChars int `xml:"maxchars,attr,omitempty"` XMLName xml.Name
MaxStanzas int `xml:"maxstanzas,attr,omitempty"` MaxChars NullableInt `xml:"maxchars,attr,omitempty"`
Seconds int `xml:"seconds,attr,omitempty"` MaxStanzas NullableInt `xml:"maxstanzas,attr,omitempty"`
Seconds NullableInt `xml:"seconds,attr,omitempty"`
Since time.Time `xml:"since,attr,omitempty"` Since time.Time `xml:"since,attr,omitempty"`
} }
type NullableInt struct {
Value int
isSet bool
}
func NewNullableInt(val int) NullableInt {
return NullableInt{val, true}
}
func (n NullableInt) Get() (v int, ok bool) {
return n.Value, n.isSet
}
// UnmarshalXML implements custom parsing for history element
func (h *History) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
h.XMLName = start.Name
// Extract attributes
for _, attr := range start.Attr {
switch attr.Name.Local {
case "maxchars":
v, err := strconv.Atoi(attr.Value)
if err != nil {
return err
}
h.MaxChars = NewNullableInt(v)
case "maxstanzas":
v, err := strconv.Atoi(attr.Value)
if err != nil {
return err
}
h.MaxStanzas = NewNullableInt(v)
case "seconds":
v, err := strconv.Atoi(attr.Value)
if err != nil {
return err
}
h.Seconds = NewNullableInt(v)
case "since":
t, err := time.Parse(timeLayout, attr.Value)
if err != nil {
return err
}
h.Since = t
}
}
// Consume remaining data until element end
for {
t, err := d.Token()
if err != nil {
return err
}
switch tt := t.(type) {
case xml.EndElement:
if tt == start.End() {
return nil
}
}
}
}
func (h History) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
mc, isMcSet := h.MaxChars.Get()
ms, isMsSet := h.MaxStanzas.Get()
s, isSSet := h.Seconds.Get()
// We do not have any value, ignore history element
if h.Since.IsZero() && !isMcSet && !isMsSet && !isSSet {
return nil
}
// Encode start element and attributes
start.Name = xml.Name{Local: "history"}
if isMcSet {
attr := xml.Attr{
Name: xml.Name{Local: "maxchars"},
Value: strconv.Itoa(mc),
}
start.Attr = append(start.Attr, attr)
}
if isMsSet {
attr := xml.Attr{
Name: xml.Name{Local: "maxstanzas"},
Value: strconv.Itoa(ms),
}
start.Attr = append(start.Attr, attr)
}
if isSSet {
attr := xml.Attr{
Name: xml.Name{Local: "seconds"},
Value: strconv.Itoa(s),
}
start.Attr = append(start.Attr, attr)
}
if !h.Since.IsZero() {
attr := xml.Attr{
Name: xml.Name{Local: "since"},
Value: h.Since.Format(timeLayout),
}
start.Attr = append(start.Attr, attr)
}
if err := e.EncodeToken(start); err != nil {
return err
}
return e.EncodeToken(xml.EndElement{Name: start.Name})
}
func init() { func init() {
TypeRegistry.MapExtension(PKTPresence, xml.Name{"http://jabber.org/protocol/muc", "x"}, MucPresence{}) TypeRegistry.MapExtension(PKTPresence, xml.Name{"http://jabber.org/protocol/muc", "x"}, MucPresence{})
} }

View File

@ -46,15 +46,52 @@ func TestMucHistory(t *testing.T) {
var parsedPresence stanza.Presence var parsedPresence stanza.Presence
if err := xml.Unmarshal([]byte(str), &parsedPresence); err != nil { if err := xml.Unmarshal([]byte(str), &parsedPresence); err != nil {
t.Errorf("Unmarshal(%s) returned error", str) t.Errorf("Unmarshal(%s) returned error: %s", str, err)
return
} }
var muc stanza.MucPresence var muc stanza.MucPresence
if ok := parsedPresence.Get(&muc); !ok { if ok := parsedPresence.Get(&muc); !ok {
t.Error("muc presence extension was not found") t.Error("muc presence extension was not found")
return
} }
if muc.History.MaxStanzas != 20 { if v, ok := muc.History.MaxStanzas.Get(); !ok || v != 20 {
t.Errorf("incorrect max stanza: '%d'", muc.History.MaxStanzas) t.Errorf("incorrect MaxStanzas: '%#v'", muc.History.MaxStanzas)
}
}
// https://xmpp.org/extensions/xep-0045.html#example-37
func TestMucNoHistory(t *testing.T) {
str := "<presence" +
" id=\"n13mt3l\"" +
" from=\"hag66@shakespeare.lit/pda\"" +
" to=\"coven@chat.shakespeare.lit/thirdwitch\">" +
"<x xmlns=\"http://jabber.org/protocol/muc\">" +
"<history maxstanzas=\"0\"></history>" +
"</x>" +
"</presence>"
maxstanzas := 0
pres := stanza.Presence{Attrs: stanza.Attrs{
From: "hag66@shakespeare.lit/pda",
Id: "n13mt3l",
To: "coven@chat.shakespeare.lit/thirdwitch",
},
Extensions: []stanza.PresExtension{
stanza.MucPresence{
History: stanza.History{MaxStanzas: stanza.NewNullableInt(maxstanzas)},
},
},
}
data, err := xml.Marshal(&pres)
if err != nil {
t.Error("error on encode:", err)
return
}
if string(data) != str {
t.Errorf("incorrect stanza: \n%s\n%s", str, data)
} }
} }