xmpp/stanza/iq.go

167 lines
4.5 KiB
Go

// Copyright 2016 The Mellium Contributors.
// Use of this source code is governed by the BSD 2-clause
// license that can be found in the LICENSE file.
package stanza
import (
"encoding/xml"
"mellium.im/xmlstream"
"mellium.im/xmpp/internal/ns"
"mellium.im/xmpp/jid"
)
// IQ ("Information Query") is used as a general request response mechanism.
// IQ's are one-to-one, provide get and set semantics, and always require a
// response in the form of a result or an error.
type IQ struct {
XMLName xml.Name `xml:"iq"`
ID string `xml:"id,attr"`
To jid.JID `xml:"to,attr,omitempty"`
From jid.JID `xml:"from,attr,omitempty"`
Lang string `xml:"http://www.w3.org/XML/1998/namespace lang,attr,omitempty"`
Type IQType `xml:"type,attr"`
}
// UnmarshalIQError converts the provided XML token into an IQ.
// If the type of the IQ is "error" it unmarshals the entire payload and returns
// the error along with the original IQ.
func UnmarshalIQError(r xml.TokenReader, start xml.StartElement) (IQ, error) {
iqStart, err := NewIQ(start)
if err != nil {
return iqStart, err
}
if iqStart.Type != ErrorIQ {
return iqStart, nil
}
stanzaErr, err := UnmarshalError(r)
if err != nil {
return iqStart, err
}
return iqStart, stanzaErr
}
// NewIQ unmarshals an XML token into a IQ.
func NewIQ(start xml.StartElement) (IQ, error) {
v := IQ{
XMLName: start.Name,
}
for _, attr := range start.Attr {
if attr.Name.Local == "lang" && attr.Name.Space == ns.XML {
v.Lang = attr.Value
continue
}
if attr.Name.Space != "" && attr.Name.Space != start.Name.Space {
continue
}
var err error
switch attr.Name.Local {
case "id":
v.ID = attr.Value
case "to":
if attr.Value != "" {
v.To, err = jid.Parse(attr.Value)
if err != nil {
return v, err
}
}
case "from":
if attr.Value != "" {
v.From, err = jid.Parse(attr.Value)
if err != nil {
return v, err
}
}
case "type":
v.Type = IQType(attr.Value)
}
}
return v, nil
}
// StartElement converts the IQ into an XML token.
func (iq IQ) StartElement() xml.StartElement {
// Keep whatever namespace we're already using but make sure the localname is
// "iq".
name := iq.XMLName
name.Local = "iq"
attr := make([]xml.Attr, 0, 5)
attr = append(attr, xml.Attr{Name: xml.Name{Local: "type"}, Value: string(iq.Type)})
if !iq.To.Equal(jid.JID{}) {
attr = append(attr, xml.Attr{Name: xml.Name{Local: "to"}, Value: iq.To.String()})
}
if !iq.From.Equal(jid.JID{}) {
attr = append(attr, xml.Attr{Name: xml.Name{Local: "from"}, Value: iq.From.String()})
}
if iq.ID != "" {
attr = append(attr, xml.Attr{Name: xml.Name{Local: "id"}, Value: iq.ID})
}
if iq.Lang != "" {
attr = append(attr, xml.Attr{Name: xml.Name{Space: ns.XML, Local: "lang"}, Value: iq.Lang})
}
return xml.StartElement{
Name: name,
Attr: attr,
}
}
// Wrap wraps the payload in a stanza.
//
// The resulting IQ may not contain an id or from attribute and thus may not be
// valid without further processing.
func (iq IQ) Wrap(payload xml.TokenReader) xml.TokenReader {
return xmlstream.Wrap(payload, iq.StartElement())
}
// Result returns a token reader that wraps the first element from payload in an
// IQ stanza with the to and from attributes switched and the type set to
// ResultIQ.
func (iq IQ) Result(payload xml.TokenReader) xml.TokenReader {
iq.Type = ResultIQ
iq.From, iq.To = iq.To, iq.From
return iq.Wrap(payload)
}
// Error returns a token reader that wraps the provided Error in an IQ stanza
// with the to and from attributes switched and the type set to ErrorIQ.
func (iq IQ) Error(err Error) xml.TokenReader {
iq.Type = ErrorIQ
iq.From, iq.To = iq.To, iq.From
return iq.Wrap(err.TokenReader())
}
// IQType is the type of an IQ stanza.
// It should normally be one of the constants defined in this package.
type IQType string
const (
// GetIQ is used to query another entity for information.
GetIQ IQType = "get"
// SetIQ is used to provide data to another entity, set new values, and
// replace existing values.
SetIQ IQType = "set"
// ResultIQ is sent in response to a successful get or set IQ.
ResultIQ IQType = "result"
// ErrorIQ is sent to report that an error occurred during the delivery or
// processing of a get or set IQ.
ErrorIQ IQType = "error"
)
// MarshalText ensures that the zero value for IQType is marshaled to XML as a
// valid IQ get request.
// It satisfies the encoding.TextMarshaler interface for IQType.
func (t IQType) MarshalText() ([]byte, error) {
if t == "" {
t = GetIQ
}
return []byte(t), nil
}