// 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 xmpp import ( "context" "encoding/xml" "fmt" "io" "mellium.im/xmlstream" "mellium.im/xmpp/internal/attr" "mellium.im/xmpp/internal/marshal" "mellium.im/xmpp/stanza" ) // SendIQ is like Send except that it returns an error if the first token read // from the stream is not an Info/Query (IQ) start element and blocks until a // response is received. // // If the input stream is not being processed (a call to Serve is not running), // SendIQ will never receive a response and will block until the provided // context is canceled. // If the response is non-nil, it does not need to be consumed in its entirety, // but it must be closed before stream processing will resume. // If the IQ type does not require a response—ie. it is a result or error IQ, // meaning that it is a response itself—SendIQElement does not block and the // response is nil. // // If the context is closed before the response is received, SendIQ immediately // returns the context error. // Any response received at a later time will not be associated with the // original request but can still be handled by the Serve handler. // // If an error is returned, the response will be nil; the converse is not // necessarily true. // SendIQ is safe for concurrent use by multiple goroutines. func (s *Session) SendIQ(ctx context.Context, r xml.TokenReader) (xmlstream.TokenReadCloser, error) { tok, err := r.Token() if err != nil { return nil, err } start, ok := tok.(xml.StartElement) if !ok { return nil, fmt.Errorf("expected IQ start element, got %T", tok) } if !isIQEmptySpace(start.Name) { return nil, fmt.Errorf("expected start element to be an IQ") } // If there's no ID, add one. idx, _, id, typ := getIDTyp(start.Attr) if idx == -1 { idx = len(start.Attr) start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "id"}, Value: ""}) } if id == "" { id = attr.RandomID() start.Attr[idx].Value = id } // If this an IQ of type "set" or "get" we expect a response. if typ == string(stanza.GetIQ) || typ == string(stanza.SetIQ) { return s.sendResp(ctx, id, xmlstream.Inner(r), start) } // If this is an IQ of type result or error, we don't expect a response so // just send it normally. return nil, s.SendElement(ctx, xmlstream.Inner(r), start) } // SendIQElement is like SendIQ except that it wraps the payload in an // Info/Query (IQ) element. // For more information see SendIQ. // // SendIQElement is safe for concurrent use by multiple goroutines. func (s *Session) SendIQElement(ctx context.Context, payload xml.TokenReader, iq stanza.IQ) (xmlstream.TokenReadCloser, error) { return s.SendIQ(ctx, iq.Wrap(payload)) } // EncodeIQ is like Encode except that it returns an error if v does not marshal // to an IQ stanza and like SendIQ it blocks until a response is received. // For more information see SendIQ. // // EncodeIQ is safe for concurrent use by multiple goroutines. func (s *Session) EncodeIQ(ctx context.Context, v interface{}) (xmlstream.TokenReadCloser, error) { r, err := marshal.TokenReader(v) if err != nil { return nil, err } return s.SendIQ(ctx, r) } // EncodeIQElement is like EncodeIQ except that it wraps the payload in an // Info/Query (IQ) element. // For more information see SendIQ. // // EncodeIQElement is safe for concurrent use by multiple goroutines. func (s *Session) EncodeIQElement(ctx context.Context, payload interface{}, iq stanza.IQ) (xmlstream.TokenReadCloser, error) { r, err := marshal.TokenReader(payload) if err != nil { return nil, err } return s.SendIQElement(ctx, r, iq) } // UnmarshalIQ is like SendIQ except that error replies are unmarshaled into a // stanza.Error and returned and otherwise the response payload is unmarshaled // into v. // For more information see SendIQ. // // UnmarshalIQ is safe for concurrent use by multiple goroutines. func (s *Session) UnmarshalIQ(ctx context.Context, iq xml.TokenReader, v interface{}) error { return unmarshalIQ(ctx, iq, v, s) } // UnmarshalIQElement is like UnmarshalIQ but it wraps a payload in the provided IQ. // For more information see SendIQ. // // UnmarshalIQElement is safe for concurrent use by multiple goroutines. func (s *Session) UnmarshalIQElement(ctx context.Context, payload xml.TokenReader, iq stanza.IQ, v interface{}) error { return unmarshalIQ(ctx, iq.Wrap(payload), v, s) } // IterIQ is like SendIQ except that error replies are unmarshaled into a // stanza.Error and returned and otherwise an iterator over the children of the // response payload is returned. // For more information see SendIQ. // // IterIQ is safe for concurrent use by multiple goroutines. func (s *Session) IterIQ(ctx context.Context, iq xml.TokenReader) (*xmlstream.Iter, error) { return iterIQ(ctx, iq, s) } // IterIQElement is like IterIQ but it wraps a payload in the provided IQ. // For more information see SendIQ. // // IterIQElement is safe for concurrent use by multiple goroutines. func (s *Session) IterIQElement(ctx context.Context, payload xml.TokenReader, iq stanza.IQ) (*xmlstream.Iter, error) { return iterIQ(ctx, iq.Wrap(payload), s) } func iterIQ(ctx context.Context, iq xml.TokenReader, s *Session) (_ *xmlstream.Iter, e error) { resp, err := s.SendIQ(ctx, iq) if err != nil { return nil, err } defer func() { if e != nil { /* #nosec */ resp.Close() } }() tok, err := resp.Token() if err != nil { return nil, err } start, ok := tok.(xml.StartElement) if !ok { return nil, fmt.Errorf("stanza: expected IQ start token, got %T %[1]v", tok) } _, err = stanza.UnmarshalIQError(resp, start) if err != nil { return nil, err } // Pop the payload start token, we want to iterate over its children. _, err = resp.Token() // Discard early EOF so that the iterator doesn't end up returning it. if err != nil && err != io.EOF { return nil, err } return xmlstream.NewIter(resp), nil } func unmarshalIQ(ctx context.Context, iq xml.TokenReader, v interface{}, s *Session) (e error) { resp, err := s.SendIQ(ctx, iq) if err != nil { return err } defer func() { ee := resp.Close() if e == nil { e = ee } }() tok, err := resp.Token() if err != nil { return err } start, ok := tok.(xml.StartElement) if !ok { return fmt.Errorf("stanza: expected IQ start token, got %T %[1]v", tok) } _, err = stanza.UnmarshalIQError(resp, start) if err != nil { return err } d := xml.NewTokenDecoder(resp) if v == nil { return nil } return d.Decode(v) }