go-xmpp/src/xmpp/stanza.go

176 lines
4.7 KiB
Go

package xmpp
import (
"bytes"
"encoding/xml"
"fmt"
)
const (
IQTypeGet = "get"
IQTypeSet = "set"
IQTypeResult = "result"
IQTypeError = "error"
MessageTypeNormal = "normal"
MessageTypeChat = "chat"
MessageTypeError = "error"
)
// XMPP <iq/> stanza.
type Iq struct {
XMLName xml.Name `xml:"iq"`
Id string `xml:"id,attr"`
Type string `xml:"type,attr"`
To string `xml:"to,attr,omitempty"`
From string `xml:"from,attr,omitempty"`
Payload string `xml:",innerxml"`
Error *Error `xml:"error"`
}
// Encode the value to an XML string and set as the payload. See xml.Marshal
// for how the value is encoded.
func (iq *Iq) PayloadEncode(v interface{}) error {
bytes, err := xml.Marshal(v)
if err != nil {
return err
}
iq.Payload = string(bytes)
return nil
}
// Decode the payload (an XML string) into the given value. See xml.Unmarshal
// for how the value is decoded.
func (iq *Iq) PayloadDecode(v interface{}) error {
return xml.Unmarshal([]byte(iq.Payload), v)
}
// Return the name of the payload element.
func (iq *Iq) PayloadName() (name xml.Name) {
dec := xml.NewDecoder(bytes.NewBufferString(iq.Payload))
tok, err := dec.Token()
if err != nil {
return
}
start, ok := tok.(xml.StartElement)
if !ok {
return
}
return start.Name
}
// Create a response Iq. The Id is kept, To and From are reversed, Type is set
// to the given value.
func (iq *Iq) Response(type_ string) *Iq {
return &Iq{Id: iq.Id, Type: type_, From: iq.To, To: iq.From}
}
// XMPP <message/> stanza.
type Message struct {
XMLName xml.Name `xml:"message"`
Id string `xml:"id,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
To string `xml:"to,attr,omitempty"`
From string `xml:"from,attr,omitempty"`
Subject string `xml:"subject,omitempty"`
Body string `xml:"body,omitempty"`
Thread string `xml:"thread,omitempty"`
Error *Error `xml:"error"`
Confir *Confirm `xml:"confirm"`
}
// XMPP <presence/> stanza.
type Presence struct {
XMLName xml.Name `xml:"presence"`
Id string `xml:"id,attr,omitempty"`
Type string `xml:"type,attr,omitempty"`
To string `xml:"to,attr,omitempty"`
From string `xml:"from,attr,omitempty"`
Show string `xml:"show"` // away, chat, dnd, xa
Status string `xml:"status"` // sb []clientText
Photo string `xml:"photo,omitempty"` // Avatar
Nick string `xml:"nick,omitempty"` // Nickname
}
// XMPP <error/>. May occur as a top-level stanza or embedded in another
// stanza, e.g. an <iq type="error"/>.
type Error struct {
XMLName xml.Name `xml:"error"`
Type string `xml:"type,attr"`
Payload string `xml:",innerxml"`
}
func (e Error) Error() string {
if text := e.Text(); text == "" {
return fmt.Sprintf("[%s] %s", e.Type, e.Condition().Local)
} else {
return fmt.Sprintf("[%s] %s, %s", e.Type, e.Condition().Local, text)
}
panic("unreachable")
}
type errorText struct {
XMLName xml.Name
Text string `xml:",chardata"`
}
// Create a new Error instance using the args as the payload.
func NewError(errorType string, condition ErrorCondition, text string) *Error {
// Build payload.
buf := new(bytes.Buffer)
writeXMLStartElement(buf, &xml.StartElement{
Name: xml.Name{"", condition.Local},
Attr: []xml.Attr{
{xml.Name{"", "xmlns"}, condition.Space},
},
})
writeXMLEndElement(buf, &xml.EndElement{Name: xml.Name{"", condition.Local}})
enc := xml.NewEncoder(buf)
if text != "" {
enc.Encode(errorText{xml.Name{condition.Space, "text"}, text})
}
return &Error{Type: errorType, Payload: string(buf.Bytes())}
}
// Return the error text from the payload, or "" if not present.
func (e Error) Text() string {
dec := xml.NewDecoder(bytes.NewBufferString(e.Payload))
next := startElementIter(dec)
for start := next(); start != nil; {
if start.Name.Local == "text" {
text := errorText{}
dec.DecodeElement(&text, start)
return text.Text
}
dec.Skip()
start = next()
}
return ""
}
// Return the error condition from the payload.
func (e Error) Condition() ErrorCondition {
dec := xml.NewDecoder(bytes.NewBufferString(e.Payload))
next := startElementIter(dec)
for start := next(); start != nil; {
if start.Name.Local != "text" && (start.Name.Space == nsErrorStanzas || start.Name.Space == nsErrorStreams) {
return ErrorCondition(start.Name)
}
dec.Skip()
start = next()
}
return ErrorCondition{}
}
// Error condition.
type ErrorCondition xml.Name
// Stanza errors.
var (
FeatureNotImplemented = ErrorCondition{nsErrorStanzas, "feature-not-implemented"}
RemoteServerNotFound = ErrorCondition{nsErrorStanzas, "remote-server-not-found"}
ServiceUnavailable = ErrorCondition{nsErrorStanzas, "service-unavailable"}
)