diff --git a/src/xmpp/ns.go b/src/xmpp/ns.go new file mode 100644 index 0000000..638122e --- /dev/null +++ b/src/xmpp/ns.go @@ -0,0 +1,6 @@ +package xmpp + +const ( + nsErrorStanzas = "urn:ietf:params:xml:ns:xmpp-stanzas" + nsErrorStreams = "urn:ietf:params:xml:ns:xmpp-streams" +) diff --git a/src/xmpp/stanza.go b/src/xmpp/stanza.go index ad03e7e..aa08bcb 100644 --- a/src/xmpp/stanza.go +++ b/src/xmpp/stanza.go @@ -73,9 +73,77 @@ type Presence struct { type Error struct { XMLName xml.Name `xml:"error"` Type string `xml:"type,attr"` - Text string `xml:"text"` + Payload string `xml:",innerxml"` } func (e Error) Error() string { - return fmt.Sprintf("%s: %s", e.Type, e.Text) + 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"} +) diff --git a/src/xmpp/xml.go b/src/xmpp/xml.go index fbbb9c8..76993ef 100644 --- a/src/xmpp/xml.go +++ b/src/xmpp/xml.go @@ -72,3 +72,18 @@ func writeXMLAttr(w io.Writer, attr xml.Attr) error { } return nil } + +func startElementIter(dec *xml.Decoder) func() *xml.StartElement { + return func() *xml.StartElement { + for { + if tok, err := dec.Token(); err != nil { + return nil + } else { + if start, ok := tok.(xml.StartElement); ok { + return &start + } + } + } + return nil + } +}