~samwhited/xmpp

be8f0ada7584ed630bbe98f9e067512f0d14894a — Sam Whited a month ago 4a5ec3f
carbons: add handler for incoming carbons

Signed-off-by: Sam Whited <sam@samwhited.com>
2 files changed, 106 insertions(+), 0 deletions(-)

A carbons/handler.go
A carbons/handler_test.go
A carbons/handler.go => carbons/handler.go +56 -0
@@ 0,0 1,56 @@
// Copyright 2021 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 carbons

import (
	"encoding/xml"

	"mellium.im/xmlstream"
	"mellium.im/xmpp/mux"
	"mellium.im/xmpp/stanza"
)

// Handle returns an option that registers a handler for carbon copied messages
// on the multiplexer.
func Handle(h Handler) mux.Option {
	return func(m *mux.ServeMux) {
		recv := xml.Name{Space: NS, Local: "received"}
		mux.Message(stanza.NormalMessage, recv, h)(m)
		mux.Message(stanza.ChatMessage, recv, h)(m)

		sent := xml.Name{Space: NS, Local: "sent"}
		mux.Message(stanza.NormalMessage, sent, h)(m)
		mux.Message(stanza.ChatMessage, sent, h)(m)
	}
}

// Handler can be used to handle incoming carbon copied messages.
type Handler struct {
	F func(m stanza.Message, sent bool, inner xml.TokenReader) error
}

// HandleMessage satisfies mux.MessageHandler.
// it is used by the multiplexer and normally does not need to be called by the
// user.
func (h Handler) HandleMessage(p stanza.Message, r xmlstream.TokenReadEncoder) error {
	// Pop the message start.
	_, err := r.Token()
	if err != nil {
		return err
	}
	iter := xmlstream.NewIter(r)
	for iter.Next() {
		start, child := iter.Current()
		if start.Name.Space == NS && (start.Name.Local == "received" || start.Name.Local == "sent") {
			// Skip the "forwarded" element.
			_, err := child.Token()
			if err != nil {
				return err
			}
			return h.F(p, start.Name.Local == "sent", xmlstream.Inner(child))
		}
	}
	return iter.Err()
}

A carbons/handler_test.go => carbons/handler_test.go +50 -0
@@ 0,0 1,50 @@
// Copyright 2021 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 carbons_test

import (
	"context"
	"encoding/xml"
	"strings"
	"testing"

	"mellium.im/xmpp/carbons"
	"mellium.im/xmpp/internal/xmpptest"
	"mellium.im/xmpp/mux"
	"mellium.im/xmpp/stanza"
)

func TestHandler(t *testing.T) {
	wait := make(chan struct{})
	h := carbons.Handler{
		F: func(m stanza.Message, sent bool, inner xml.TokenReader) error {
			defer close(wait)
			if sent {
				t.Error("expected received not to set sent")
			}
			tok, err := inner.Token()
			if err != nil {
				return err
			}
			if start, ok := tok.(xml.StartElement); !ok || start.Name.Local != "message" {
				t.Errorf("inner payload not handled correctly, got %T token: %[1]v", tok)
			}
			return nil
		},
	}
	m := mux.New(carbons.Handle(h))
	s := xmpptest.NewClientServer(xmpptest.ClientHandler(m))
	defer s.Close()
	const recv = `<message xmlns='jabber:client'
         from='romeo@montague.example'
         to='romeo@montague.example/home'
         type='chat'><received xmlns='urn:xmpp:carbons:2'><forwarded xmlns='urn:xmpp:forward:0'><message xmlns='jabber:client' from='juliet@capulet.example/balcony' to='romeo@montague.example/garden' type='chat'><body>What man art thou that, thus bescreen'd in night, so stumblest on my counsel?</body><thread>0e3141cd80894871a68e6fe6b1ec56fa</thread></message></forwarded></received></message>`
	d := xml.NewDecoder(strings.NewReader(recv))
	err := s.Server.Send(context.Background(), d)
	if err != nil {
		t.Fatalf("error sending carbon: %v", err)
	}
	<-wait
}