Skip to content

Commit f9f7919

Browse files
committed
xmpp: add Message and Presence send methods
Signed-off-by: Sam Whited <[email protected]>
1 parent 31cc8d6 commit f9f7919

7 files changed

+719
-229
lines changed

Diff for: doc.go

+9-3
Original file line numberDiff line numberDiff line change
@@ -91,11 +91,17 @@
9191
// The stanza package contains functions and structs that aid in the
9292
// construction of message, presence and info/query (IQ) elements which have
9393
// special semantics in XMPP and are known as "stanzas".
94-
// There are 8 methods on Session used for transmitting stanzas and other events
95-
// over the output stream.
94+
// There are 16 methods on Session used for transmitting stanzas and other
95+
// events over the output stream.
9696
// Their names are matched by the regular expression:
9797
//
98-
// (Send|Encode)(IQ)?(Element)?
98+
// (Send|Encode)(Message|Presence|IQ)?(Element)?
99+
//
100+
// There are also four methods specifically for sending IQs and handling their
101+
// responses.
102+
// Their names are matched by:
103+
//
104+
// (Unmarshal|Iter)IQ(Element)?
99105
//
100106
// If "Send" is present it means that the method copies one XML token stream
101107
// into the output stream, while "Encode" indicates that it takes a value and

Diff for: session.go

+31-226
Original file line numberDiff line numberDiff line change
@@ -536,23 +536,15 @@ func handleInputStream(s *Session, handler Handler) (err error) {
536536
}
537537
}
538538

539-
var id string
540-
var needsResp bool
541-
if isIQ(start.Name) {
542-
_, id = attr.Get(start.Attr, "id")
543-
544-
// If this is a response IQ (ie. an "error" or "result") check if we're
545-
// handling it as part of a SendIQ call.
546-
// If not, record this so that we can check if the user sends a response
547-
// later.
548-
if !iqNeedsResp(start.Attr) {
549-
s.sentIQMutex.Lock()
550-
c := s.sentIQs[id]
551-
s.sentIQMutex.Unlock()
552-
if c == nil {
553-
goto noreply
554-
}
539+
iqOk := isIQ(start.Name)
540+
_, _, id, typ := getIDTyp(start.Attr)
541+
iqNeedsResp := typ == string(stanza.GetIQ) || typ == string(stanza.SetIQ)
555542

543+
if !iqNeedsResp {
544+
s.sentIQMutex.Lock()
545+
c := s.sentIQs[id]
546+
s.sentIQMutex.Unlock()
547+
if c != nil {
556548
inner := xmlstream.Inner(r)
557549
c <- iqResponder{
558550
r: xmlstream.MultiReader(xmlstream.Token(start), inner, xmlstream.Token(start.End())),
@@ -566,11 +558,8 @@ func handleInputStream(s *Session, handler Handler) (err error) {
566558
}
567559
return nil
568560
}
569-
needsResp = true
570561
}
571562

572-
noreply:
573-
574563
w := s.TokenWriter()
575564
defer w.Close()
576565
rw := &responseChecker{
@@ -583,7 +572,7 @@ noreply:
583572
}
584573

585574
// If the user did not write a response to an IQ, send a default one.
586-
if needsResp && !rw.wroteResp {
575+
if iqOk && iqNeedsResp && !rw.wroteResp {
587576
_, toAttr := attr.Get(start.Attr, "to")
588577
var to jid.JID
589578
if toAttr != "" {
@@ -615,6 +604,26 @@ noreply:
615604
return err
616605
}
617606

607+
func getIDTyp(attrs []xml.Attr) (int, int, string, string) {
608+
var id, typ string
609+
idIdx := -1
610+
typIdx := -1
611+
for idx, attr := range attrs {
612+
switch attr.Name.Local {
613+
case "id":
614+
id = attr.Value
615+
idIdx = idx
616+
case "type":
617+
typ = attr.Value
618+
typIdx = idx
619+
}
620+
if idIdx > -1 && typIdx > -1 {
621+
break
622+
}
623+
}
624+
return idIdx, typIdx, id, typ
625+
}
626+
618627
type responseChecker struct {
619628
xml.TokenReader
620629
xmlstream.TokenWriter
@@ -626,8 +635,8 @@ type responseChecker struct {
626635
func (rw *responseChecker) EncodeToken(t xml.Token) error {
627636
switch tok := t.(type) {
628637
case xml.StartElement:
629-
_, id := attr.Get(tok.Attr, "id")
630-
if rw.level < 1 && isIQEmptySpace(tok.Name) && id == rw.id && !iqNeedsResp(tok.Attr) {
638+
_, _, id, typ := getIDTyp(tok.Attr)
639+
if rw.level < 1 && isIQEmptySpace(tok.Name) && id == rw.id && (typ != string(stanza.GetIQ) && typ != string(stanza.SetIQ)) {
631640
rw.wroteResp = true
632641
}
633642
rw.level++
@@ -846,32 +855,6 @@ func (s *Session) SetCloseDeadline(t time.Time) error {
846855
return s.Conn().SetReadDeadline(t)
847856
}
848857

849-
// EncodeIQ is like Encode except that it returns an error if v does not marshal
850-
// to an IQ stanza and like SendIQ it blocks until a response is received.
851-
// For more information see SendIQ.
852-
//
853-
// EncodeIQ is safe for concurrent use by multiple goroutines.
854-
func (s *Session) EncodeIQ(ctx context.Context, v interface{}) (xmlstream.TokenReadCloser, error) {
855-
r, err := marshal.TokenReader(v)
856-
if err != nil {
857-
return nil, err
858-
}
859-
return s.SendIQ(ctx, r)
860-
}
861-
862-
// EncodeIQElement is like EncodeIQ except that it wraps the payload in an
863-
// Info/Query (IQ) element.
864-
// For more information see SendIQ.
865-
//
866-
// EncodeIQElement is safe for concurrent use by multiple goroutines.
867-
func (s *Session) EncodeIQElement(ctx context.Context, payload interface{}, iq stanza.IQ) (xmlstream.TokenReadCloser, error) {
868-
r, err := marshal.TokenReader(payload)
869-
if err != nil {
870-
return nil, err
871-
}
872-
return s.SendIQElement(ctx, r, iq)
873-
}
874-
875858
// Encode writes the XML encoding of v to the stream.
876859
//
877860
// For more information see "encoding/xml".Encode.
@@ -945,18 +928,6 @@ func send(ctx context.Context, s *Session, r xml.TokenReader, start *xml.StartEl
945928
return s.out.e.Flush()
946929
}
947930

948-
func iqNeedsResp(attrs []xml.Attr) bool {
949-
var typ string
950-
for _, attr := range attrs {
951-
if attr.Name.Local == "type" {
952-
typ = attr.Value
953-
break
954-
}
955-
}
956-
957-
return typ == string(stanza.GetIQ) || typ == string(stanza.SetIQ)
958-
}
959-
960931
func isIQ(name xml.Name) bool {
961932
return name.Local == "iq" && (name.Space == ns.Client || name.Space == ns.Server)
962933
}
@@ -970,172 +941,6 @@ func isStanzaEmptySpace(name xml.Name) bool {
970941
(name.Space == ns.Client || name.Space == ns.Server || name.Space == "")
971942
}
972943

973-
// SendIQ is like Send except that it returns an error if the first token read
974-
// from the stream is not an Info/Query (IQ) start element and blocks until a
975-
// response is received.
976-
//
977-
// If the input stream is not being processed (a call to Serve is not running),
978-
// SendIQ will never receive a response and will block until the provided
979-
// context is canceled.
980-
// If the response is non-nil, it does not need to be consumed in its entirety,
981-
// but it must be closed before stream processing will resume.
982-
// If the IQ type does not require a response—ie. it is a result or error IQ,
983-
// meaning that it is a response itself—SendIQElemnt does not block and the
984-
// response is nil.
985-
//
986-
// If the context is closed before the response is received, SendIQ immediately
987-
// returns the context error.
988-
// Any response received at a later time will not be associated with the
989-
// original request but can still be handled by the Serve handler.
990-
//
991-
// If an error is returned, the response will be nil; the converse is not
992-
// necessarily true.
993-
// SendIQ is safe for concurrent use by multiple goroutines.
994-
func (s *Session) SendIQ(ctx context.Context, r xml.TokenReader) (xmlstream.TokenReadCloser, error) {
995-
tok, err := r.Token()
996-
if err != nil {
997-
return nil, err
998-
}
999-
start, ok := tok.(xml.StartElement)
1000-
if !ok {
1001-
return nil, fmt.Errorf("expected IQ start element, got %T", tok)
1002-
}
1003-
if !isIQEmptySpace(start.Name) {
1004-
return nil, fmt.Errorf("expected start element to be an IQ")
1005-
}
1006-
1007-
// If there's no ID, add one.
1008-
idx, id := attr.Get(start.Attr, "id")
1009-
if idx == -1 {
1010-
idx = len(start.Attr)
1011-
start.Attr = append(start.Attr, xml.Attr{Name: xml.Name{Local: "id"}, Value: ""})
1012-
}
1013-
if id == "" {
1014-
id = attr.RandomID()
1015-
start.Attr[idx].Value = id
1016-
}
1017-
1018-
// If this an IQ of type "set" or "get" we expect a response.
1019-
if iqNeedsResp(start.Attr) {
1020-
return s.sendResp(ctx, id, xmlstream.Inner(r), start)
1021-
}
1022-
1023-
// If this is an IQ of type result or error, we don't expect a response so
1024-
// just send it normally.
1025-
return nil, s.SendElement(ctx, xmlstream.Inner(r), start)
1026-
}
1027-
1028-
// SendIQElement is like SendIQ except that it wraps the payload in an
1029-
// Info/Query (IQ) element.
1030-
// For more information see SendIQ.
1031-
//
1032-
// SendIQElement is safe for concurrent use by multiple goroutines.
1033-
func (s *Session) SendIQElement(ctx context.Context, payload xml.TokenReader, iq stanza.IQ) (xmlstream.TokenReadCloser, error) {
1034-
return s.SendIQ(ctx, iq.Wrap(payload))
1035-
}
1036-
1037-
// UnmarshalIQ is like SendIQ except that error replies are unmarshaled into a
1038-
// stanza.Error and returned and otherwise the response payload is unmarshaled
1039-
// into v.
1040-
// For more information see SendIQ.
1041-
//
1042-
// UnmarshalIQ is safe for concurrent use by multiple goroutines.
1043-
func (s *Session) UnmarshalIQ(ctx context.Context, iq xml.TokenReader, v interface{}) error {
1044-
return unmarshalIQ(ctx, iq, v, s)
1045-
}
1046-
1047-
// UnmarshalIQElement is like UnmarshalIQ but it wraps a payload in the provided IQ.
1048-
// For more information see SendIQ.
1049-
//
1050-
// UnmarshalIQElement is safe for concurrent use by multiple goroutines.
1051-
func (s *Session) UnmarshalIQElement(ctx context.Context, payload xml.TokenReader, iq stanza.IQ, v interface{}) error {
1052-
return unmarshalIQ(ctx, iq.Wrap(payload), v, s)
1053-
}
1054-
1055-
// IterIQ is like SendIQ except that error replies are unmarshaled into a
1056-
// stanza.Error and returned and otherwise an iterator over the children of the
1057-
// response payload is returned.
1058-
// For more information see SendIQ.
1059-
//
1060-
// IterIQ is safe for concurrent use by multiple goroutines.
1061-
func (s *Session) IterIQ(ctx context.Context, iq xml.TokenReader) (*xmlstream.Iter, error) {
1062-
return iterIQ(ctx, iq, s)
1063-
}
1064-
1065-
// IterIQElement is like IterIQ but it wraps a payload in the provided IQ.
1066-
// For more information see SendIQ.
1067-
//
1068-
// IterIQElement is safe for concurrent use by multiple goroutines.
1069-
func (s *Session) IterIQElement(ctx context.Context, payload xml.TokenReader, iq stanza.IQ) (*xmlstream.Iter, error) {
1070-
return iterIQ(ctx, iq.Wrap(payload), s)
1071-
}
1072-
1073-
func iterIQ(ctx context.Context, iq xml.TokenReader, s *Session) (_ *xmlstream.Iter, e error) {
1074-
resp, err := s.SendIQ(ctx, iq)
1075-
if err != nil {
1076-
return nil, err
1077-
}
1078-
defer func() {
1079-
if e != nil {
1080-
/* #nosec */
1081-
resp.Close()
1082-
}
1083-
}()
1084-
1085-
tok, err := resp.Token()
1086-
if err != nil {
1087-
return nil, err
1088-
}
1089-
start, ok := tok.(xml.StartElement)
1090-
if !ok {
1091-
return nil, fmt.Errorf("stanza: expected IQ start token, got %T %[1]v", tok)
1092-
}
1093-
_, err = stanza.UnmarshalIQError(resp, start)
1094-
if err != nil {
1095-
return nil, err
1096-
}
1097-
1098-
// Pop the payload start token, we want to iterate over its children.
1099-
_, err = resp.Token()
1100-
// Discard early EOF so that the iterator doesn't end up returning it.
1101-
if err != nil && err != io.EOF {
1102-
return nil, err
1103-
}
1104-
return xmlstream.NewIter(resp), nil
1105-
}
1106-
1107-
func unmarshalIQ(ctx context.Context, iq xml.TokenReader, v interface{}, s *Session) (e error) {
1108-
resp, err := s.SendIQ(ctx, iq)
1109-
if err != nil {
1110-
return err
1111-
}
1112-
defer func() {
1113-
ee := resp.Close()
1114-
if e == nil {
1115-
e = ee
1116-
}
1117-
}()
1118-
1119-
tok, err := resp.Token()
1120-
if err != nil {
1121-
return err
1122-
}
1123-
start, ok := tok.(xml.StartElement)
1124-
if !ok {
1125-
return fmt.Errorf("stanza: expected IQ start token, got %T %[1]v", tok)
1126-
}
1127-
1128-
_, err = stanza.UnmarshalIQError(resp, start)
1129-
if err != nil {
1130-
return err
1131-
}
1132-
d := xml.NewTokenDecoder(resp)
1133-
if v == nil {
1134-
return nil
1135-
}
1136-
return d.Decode(v)
1137-
}
1138-
1139944
func (s *Session) sendResp(ctx context.Context, id string, payload xml.TokenReader, start xml.StartElement) (xmlstream.TokenReadCloser, error) {
1140945
c := make(chan xmlstream.TokenReadCloser)
1141946

0 commit comments

Comments
 (0)