go-xmpp/iq.go

344 lines
8 KiB
Go
Raw Normal View History

package xmpp // import "gosrc.io/xmpp"
import (
"encoding/xml"
2018-01-16 21:33:21 +00:00
"reflect"
2018-01-20 17:09:13 +00:00
"strconv"
2018-12-26 18:10:30 +00:00
"gosrc.io/xmpp/iot"
)
2018-01-15 11:28:34 +00:00
/*
2018-02-13 21:07:15 +00:00
TODO support ability to put Raw payload inside IQ
2018-01-15 11:28:34 +00:00
*/
2018-01-20 17:09:13 +00:00
// ============================================================================
// XMPP Errors
2018-01-25 16:04:19 +00:00
// Err is an XMPP stanza payload that is used to report error on message,
// presence or iq stanza.
// It is intended to be added in the payload of the erroneous stanza.
2018-01-20 17:09:13 +00:00
type Err struct {
XMLName xml.Name `xml:"error"`
2018-01-20 17:56:07 +00:00
Code int `xml:"code,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
2018-01-20 17:09:13 +00:00
Reason string
2018-01-20 17:56:07 +00:00
Text string `xml:"urn:ietf:params:xml:ns:xmpp-stanzas text,omitempty"`
2018-01-20 17:09:13 +00:00
}
2018-01-20 17:56:07 +00:00
func (*Err) IsIQPayload() {}
2018-01-20 17:09:13 +00:00
// UnmarshalXML implements custom parsing for IQs
func (x *Err) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
x.XMLName = start.Name
// Extract attributes
for _, attr := range start.Attr {
if attr.Name.Local == "type" {
x.Type = attr.Value
}
if attr.Name.Local == "code" {
if code, err := strconv.Atoi(attr.Value); err == nil {
x.Code = code
}
}
}
2018-02-13 21:07:15 +00:00
// Check subelements to extract error text and reason (from local namespace).
2018-01-20 17:09:13 +00:00
for {
t, err := d.Token()
if err != nil {
return err
}
switch tt := t.(type) {
case xml.StartElement:
elt := new(Node)
err = d.DecodeElement(elt, &tt)
if err != nil {
return err
}
textName := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "text"}
if elt.XMLName == textName {
x.Text = string(elt.Content)
} else if elt.XMLName.Space == "urn:ietf:params:xml:ns:xmpp-stanzas" {
x.Reason = elt.XMLName.Local
}
case xml.EndElement:
if tt == start.End() {
return nil
}
}
}
}
func (x Err) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
2018-01-26 10:16:04 +00:00
if x.Code == 0 {
return nil
}
// Encode start element and attributes
start.Name = xml.Name{Local: "error"}
2018-01-20 17:09:13 +00:00
code := xml.Attr{
Name: xml.Name{Local: "code"},
Value: strconv.Itoa(x.Code),
}
2018-01-26 10:16:04 +00:00
start.Attr = append(start.Attr, code)
if len(x.Type) > 0 {
typ := xml.Attr{
Name: xml.Name{Local: "type"},
Value: x.Type,
}
start.Attr = append(start.Attr, typ)
2018-01-20 17:09:13 +00:00
}
err = e.EncodeToken(start)
2018-01-22 22:33:16 +00:00
// SubTags
2018-01-20 17:09:13 +00:00
// Reason
2018-01-20 17:56:07 +00:00
if x.Reason != "" {
reason := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: x.Reason}
e.EncodeToken(xml.StartElement{Name: reason})
e.EncodeToken(xml.EndElement{Name: reason})
}
2018-01-20 17:09:13 +00:00
// Text
2018-01-20 17:56:07 +00:00
if x.Text != "" {
text := xml.Name{Space: "urn:ietf:params:xml:ns:xmpp-stanzas", Local: "text"}
e.EncodeToken(xml.StartElement{Name: text})
e.EncodeToken(xml.CharData(x.Text))
e.EncodeToken(xml.EndElement{Name: text})
}
2018-01-20 17:09:13 +00:00
return e.EncodeToken(xml.EndElement{Name: start.Name})
}
2018-01-13 17:50:17 +00:00
// ============================================================================
// IQ Packet
type IQ struct { // Info/Query
XMLName xml.Name `xml:"iq"`
2018-01-13 16:54:07 +00:00
PacketAttrs
2018-01-15 11:28:34 +00:00
Payload []IQPayload `xml:",omitempty"`
RawXML string `xml:",innerxml"`
2018-01-20 17:09:13 +00:00
Error Err `xml:"error,omitempty"`
}
2018-01-15 11:28:34 +00:00
func NewIQ(iqtype, from, to, id, lang string) IQ {
return IQ{
XMLName: xml.Name{Local: "iq"},
PacketAttrs: PacketAttrs{
Id: id,
From: from,
To: to,
Type: iqtype,
Lang: lang,
},
}
}
func (iq *IQ) AddPayload(payload IQPayload) {
iq.Payload = append(iq.Payload, payload)
}
2018-01-20 17:56:07 +00:00
func (iq IQ) MakeError(xerror Err) IQ {
from := iq.From
to := iq.To
iq.Type = "error"
iq.From = to
iq.To = from
iq.Error = xerror
return iq
}
2018-01-13 17:50:17 +00:00
func (IQ) Name() string {
return "iq"
}
type iqDecoder struct{}
var iq iqDecoder
func (iqDecoder) decode(p *xml.Decoder, se xml.StartElement) (IQ, error) {
var packet IQ
err := p.DecodeElement(&packet, &se)
return packet, err
}
// UnmarshalXML implements custom parsing for IQs
2018-01-13 17:50:17 +00:00
func (iq *IQ) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
iq.XMLName = start.Name
2018-01-18 16:03:54 +00:00
// Extract IQ attributes
for _, attr := range start.Attr {
if attr.Name.Local == "id" {
iq.Id = attr.Value
}
2016-02-15 17:33:51 +00:00
if attr.Name.Local == "type" {
iq.Type = attr.Value
}
if attr.Name.Local == "to" {
iq.To = attr.Value
}
if attr.Name.Local == "from" {
iq.From = attr.Value
}
if attr.Name.Local == "lang" {
iq.Lang = attr.Value
}
}
// decode inner elements
2018-01-16 21:33:21 +00:00
level := 0
for {
t, err := d.Token()
if err != nil {
return err
}
switch tt := t.(type) {
case xml.StartElement:
2018-01-16 21:33:21 +00:00
level++
if level <= 1 {
var elt interface{}
payloadType := tt.Name.Space + " " + tt.Name.Local
if payloadType := typeRegistry[payloadType]; payloadType != nil {
val := reflect.New(payloadType)
elt = val.Interface()
} else {
elt = new(Node)
}
if iqPl, ok := elt.(IQPayload); ok {
err = d.DecodeElement(elt, &tt)
if err != nil {
return err
}
2018-01-17 17:47:34 +00:00
iq.Payload = append(iq.Payload, iqPl)
}
}
case xml.EndElement:
2018-01-16 21:33:21 +00:00
level--
if tt == start.End() {
return nil
}
}
}
}
2018-01-13 18:14:26 +00:00
// ============================================================================
2018-01-15 11:28:34 +00:00
// Generic IQ Payload
type IQPayload interface {
IsIQPayload()
}
2018-01-13 18:14:26 +00:00
2018-01-25 16:04:19 +00:00
// Node is a generic structure to represent XML data. It is used to parse
// unreferenced or custom stanza payload.
2018-01-13 18:27:46 +00:00
type Node struct {
XMLName xml.Name
2018-01-15 11:28:34 +00:00
Attrs []xml.Attr `xml:"-"`
2018-01-20 17:09:13 +00:00
Content string `xml:",innerxml"`
Nodes []Node `xml:",any"`
2018-01-15 11:28:34 +00:00
}
2018-01-25 16:04:19 +00:00
// Attr represents generic XML attributes, as used on the generic XML Node
// representation.
2018-01-16 21:33:21 +00:00
type Attr struct {
K string
V string
}
2018-01-25 16:04:19 +00:00
// UnmarshalXML is a custom unmarshal function used by xml.Unmarshal to
// transform generic XML content into hierarchical Node structure.
2018-01-15 11:28:34 +00:00
func (n *Node) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error {
2018-01-16 21:33:21 +00:00
// Assign "n.Attrs = start.Attr", without repeating xmlns in attributes:
for _, attr := range start.Attr {
2018-01-16 21:33:21 +00:00
// Do not repeat xmlns, it is already in XMLName
if attr.Name.Local != "xmlns" {
n.Attrs = append(n.Attrs, attr)
}
}
2018-01-15 11:28:34 +00:00
type node Node
return d.DecodeElement((*node)(n), &start)
}
2018-01-25 16:04:19 +00:00
// MarshalXML is a custom XML serializer used by xml.Marshal to serialize a
// Node structure to XML.
2018-01-20 17:09:13 +00:00
func (n Node) MarshalXML(e *xml.Encoder, start xml.StartElement) (err error) {
2018-01-15 11:28:34 +00:00
start.Attr = n.Attrs
start.Name = n.XMLName
err = e.EncodeToken(start)
e.EncodeElement(n.Nodes, xml.StartElement{Name: n.XMLName})
return e.EncodeToken(xml.EndElement{Name: start.Name})
2018-01-13 18:14:26 +00:00
}
2018-01-13 18:27:46 +00:00
func (*Node) IsIQPayload() {}
2018-01-16 21:33:21 +00:00
// ============================================================================
// Disco
2018-01-20 17:09:13 +00:00
const (
NSDiscoInfo = "http://jabber.org/protocol/disco#info"
NSDiscoItems = "http://jabber.org/protocol/disco#items"
2018-01-20 17:09:13 +00:00
)
2018-01-16 21:33:21 +00:00
type DiscoInfo struct {
2018-01-17 17:47:34 +00:00
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#info query"`
2018-01-26 10:40:34 +00:00
Node string `xml:"node,attr,omitempty"`
2018-01-17 17:47:34 +00:00
Identity Identity `xml:"identity"`
Features []Feature `xml:"feature"`
2018-01-16 21:33:21 +00:00
}
func (*DiscoInfo) IsIQPayload() {}
type Identity struct {
2018-01-17 17:47:34 +00:00
XMLName xml.Name `xml:"identity,omitempty"`
Name string `xml:"name,attr,omitempty"`
Category string `xml:"category,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
}
type Feature struct {
XMLName xml.Name `xml:"feature"`
Var string `xml:"var,attr"`
2018-01-16 21:33:21 +00:00
}
// ============================================================================
type DiscoItems struct {
XMLName xml.Name `xml:"http://jabber.org/protocol/disco#items query"`
Node string `xml:"node,attr,omitempty"`
Items []DiscoItem `xml:"item"`
}
func (*DiscoItems) IsIQPayload() {}
type DiscoItem struct {
XMLName xml.Name `xml:"item"`
Name string `xml:"name,attr,omitempty"`
JID string `xml:"jid,attr,omitempty"`
Node string `xml:"node,attr,omitempty"`
}
// ============================================================================
2018-01-16 21:33:21 +00:00
var typeRegistry = make(map[string]reflect.Type)
func init() {
typeRegistry["http://jabber.org/protocol/disco#info query"] = reflect.TypeOf(DiscoInfo{})
typeRegistry["http://jabber.org/protocol/disco#items query"] = reflect.TypeOf(DiscoItems{})
2018-01-16 21:33:21 +00:00
typeRegistry["urn:ietf:params:xml:ns:xmpp-bind bind"] = reflect.TypeOf(BindBind{})
typeRegistry["urn:xmpp:iot:control set"] = reflect.TypeOf(iot.ControlSet{})
}