diff --git a/message.go b/message.go index a9e108e..af815e1 100644 --- a/message.go +++ b/message.go @@ -3,6 +3,7 @@ package xmpp // import "gosrc.io/xmpp" import ( "encoding/xml" "fmt" + "reflect" ) // ============================================================================ @@ -11,10 +12,11 @@ import ( type Message struct { XMLName xml.Name `xml:"message"` PacketAttrs - Subject string `xml:"subject,omitempty"` - Body string `xml:"body,omitempty"` - Thread string `xml:"thread,omitempty"` - Error Err `xml:"error,omitempty"` + Subject string `xml:"subject,omitempty"` + Body string `xml:"body,omitempty"` + Thread string `xml:"thread,omitempty"` + Error Err `xml:"error,omitempty"` + Extensions []MsgExtension `xml:",omitempty"` } func (Message) Name() string { @@ -44,9 +46,109 @@ func (messageDecoder) decode(p *xml.Decoder, se xml.StartElement) (Message, erro return packet, err } +// TODO: Support missing element (thread, extensions) by using proper marshaller func (msg *Message) XMPPFormat() string { return fmt.Sprintf(""+ "%s", msg.To, xmlEscape(msg.Body)) } + +// UnmarshalXML implements custom parsing for IQs +func (msg *Message) UnmarshalXML(d *xml.Decoder, start xml.StartElement) error { + msg.XMLName = start.Name + + // Extract packet attributes + for _, attr := range start.Attr { + if attr.Name.Local == "id" { + msg.Id = attr.Value + } + if attr.Name.Local == "type" { + msg.Type = attr.Value + } + if attr.Name.Local == "to" { + msg.To = attr.Value + } + if attr.Name.Local == "from" { + msg.From = attr.Value + } + if attr.Name.Local == "lang" { + msg.Lang = attr.Value + } + } + + // decode inner elements + for { + t, err := d.Token() + if err != nil { + return err + } + + switch tt := t.(type) { + + case xml.StartElement: + var elt interface{} + elementType := tt.Name.Space + + if extensionType := msgTypeRegistry[elementType]; extensionType != nil { + val := reflect.New(extensionType) + elt = val.Interface() + if msgExt, ok := elt.(MsgExtension); ok { + err = d.DecodeElement(elt, &tt) + if err != nil { + return err + } + msg.Extensions = append(msg.Extensions, msgExt) + } + } else { + // Decode default message elements + var err error + switch tt.Name.Local { + case "body": + err = d.DecodeElement(&msg.Body, &tt) + case "thread": + err = d.DecodeElement(&msg.Thread, &tt) + case "subject": + err = d.DecodeElement(&msg.Subject, &tt) + case "error": + err = d.DecodeElement(&msg.Error, &tt) + } + if err != nil { + return err + } + } + + case xml.EndElement: + if tt == start.End() { + return nil + } + } + } +} + +// ============================================================================ +// Message extensions +// Provide ability to add support to XMPP extension tags on messages + +type MsgExtension interface { + IsMsgExtension() +} + +// XEP-0184 +type Receipt struct { + // xmlns: urn:xmpp:receipts + XMLName xml.Name + Id string +} + +func (*Receipt) IsMsgExtension() {} + +// ============================================================================ +// TODO: Make it configurable at to be able to easily add new XMPP extensions +// in separate modules + +var msgTypeRegistry = make(map[string]reflect.Type) + +func init() { + msgTypeRegistry["urn:xmpp:receipts"] = reflect.TypeOf(Receipt{}) +} diff --git a/message_test.go b/message_test.go index 21ee882..39738ae 100644 --- a/message_test.go +++ b/message_test.go @@ -27,3 +27,57 @@ func TestGenerateMessage(t *testing.T) { t.Errorf("non matching items\n%s", cmp.Diff(parsedMessage, message)) } } + +func TestDecodeError(t *testing.T) { + str := ` + + + +` + + parsedMessage := xmpp.Message{} + if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil { + t.Errorf("message error stanza unmarshall error: %v", err) + return + } + if parsedMessage.Error.Type != "cancel" { + t.Errorf("incorrect error type: %s", parsedMessage.Error.Type) + } +} + +func TestDecodeXEP0184(t *testing.T) { + str := ` + My lord, dispatch; read o'er these articles. + +` + parsedMessage := xmpp.Message{} + if err := xml.Unmarshal([]byte(str), &parsedMessage); err != nil { + t.Errorf("message receipt unmarshall error: %v", err) + return + } + + if parsedMessage.Body != "My lord, dispatch; read o'er these articles." { + t.Errorf("Unexpected body: '%s'", parsedMessage.Body) + } + + if len(parsedMessage.Extensions) < 1 { + t.Errorf("no extension found on parsed message") + return + } + + switch ext := parsedMessage.Extensions[0].(type) { + case *xmpp.Receipt: + if ext.XMLName.Local != "request" { + t.Errorf("unexpected extension: %s:%s", ext.XMLName.Space, ext.XMLName.Local) + } + default: + t.Errorf("could not find receipt extension") + } + +}