~samwhited/xmpp

4a5ec3fa1e7a41fb1e58a4f7c303cb30ac4841dc — Sam Whited a month ago e4937be
xmpp: fix an error in UnmarshalIQ

Previously UnmarshalIQ would fail if there was no payload in the IQ (eg.
an empty IQ result, indicating success). Instead, skip unmarshaling in
this case.

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

M CHANGELOG.md
M send_test.go
M session_iq.go
M CHANGELOG.md => CHANGELOG.md +2 -0
@@ 40,6 40,8 @@ All notable changes to this project will be documented in this file.
  child in the payload
- styling: pre-block start tokens with no newline had nonsensical formatting
- xmpp: empty IQ iters no longer return EOF when there is no payload
- xmpp: `UnmarshalIQ` and `UnmarshalIQElement` no longer return an XML error
  on responses with no payload


[XEP-0045: Multi-User Chat]: https://xmpp.org/extensions/xep-0045.html

M send_test.go => send_test.go +65 -0
@@ 204,6 204,71 @@ func TestEncodeIQ(t *testing.T) {
			t.Errorf("Expected response, but got none")
		}
	})
	t.Run("UnmarshalIQ", func(t *testing.T) {
		testName := xml.Name{Space: "space", Local: "local"}
		s := xmpptest.NewClientServer(xmpptest.ServerHandlerFunc(func(t xmlstream.TokenReadEncoder, start *xml.StartElement) error {
			_, err := xmlstream.Copy(t, stanza.IQ{ID: testIQID, Type: stanza.ResultIQ}.Wrap(xmlstream.Wrap(
				nil,
				xml.StartElement{Name: testName},
			)))
			return err
		}))

		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()

		v := struct {
			XMLName xml.Name
		}{}
		err := s.Client.UnmarshalIQ(ctx, stanza.IQ{
			ID:   testIQID,
			Type: stanza.GetIQ,
		}.Wrap(nil), &v)
		if err != nil {
			t.Errorf("got unexpected error encoding: %v", err)
		}
		if v.XMLName != testName {
			t.Errorf("wrong payload: want=%v, got=%v", testName, v.XMLName)
		}
	})
	t.Run("UnmarshalIQErr", func(t *testing.T) {
		s := xmpptest.NewClientServer()

		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()

		err := s.Client.UnmarshalIQ(ctx, stanza.IQ{
			ID:   testIQID,
			Type: stanza.GetIQ,
		}.Wrap(nil), nil)
		if !errors.Is(err, stanza.Error{Condition: stanza.ServiceUnavailable}) {
			t.Errorf("got unexpected error encoding: %v", err)
		}
	})
	t.Run("UnmarshalIQEmpty", func(t *testing.T) {
		s := xmpptest.NewClientServer(xmpptest.ServerHandlerFunc(func(t xmlstream.TokenReadEncoder, start *xml.StartElement) error {
			_, err := xmlstream.Copy(t, stanza.IQ{ID: testIQID, Type: stanza.ResultIQ}.Wrap(nil))
			return err
		}))

		ctx, cancel := context.WithTimeout(context.Background(), time.Second)
		defer cancel()

		v := struct {
			XMLName xml.Name
		}{}
		err := s.Client.UnmarshalIQ(ctx, stanza.IQ{
			ID:   testIQID,
			Type: stanza.GetIQ,
		}.Wrap(nil), &v)
		if err != nil {
			t.Errorf("got unexpected error encoding: %v", err)
		}
		emptyName := xml.Name{}
		if v.XMLName != emptyName {
			t.Errorf("wrong payload: want=%v, got=%v", emptyName, v.XMLName)
		}
	})
}

// zeroID will be the ID of stanzas that have an empty ID in the send tests.

M session_iq.go => session_iq.go +12 -2
@@ 201,9 201,19 @@ func unmarshalIQ(ctx context.Context, iq xml.TokenReader, v interface{}, s *Sess
	if err != nil {
		return err
	}
	d := xml.NewTokenDecoder(resp)
	if v == nil {
		return nil
	}
	return d.Decode(v)
	payload := xmlstream.Inner(resp)
	d := xml.NewTokenDecoder(payload)
	startTok, err := d.Token()
	switch err {
	case io.EOF:
		return nil
	case nil:
	default:
		return err
	}
	start = startTok.(xml.StartElement)
	return d.DecodeElement(v, &start)
}