M CHANGELOG.md => CHANGELOG.md +2 -0
@@ 57,6 57,8 @@ All notable changes to this project will be documented in this file.
now done concurrently to make the initial connection faster
- dial: the fallback for dialing an XMPP connection when no SRV records exist is
now more robust
+- xmpp: stanzas sent over S2S connections now always have the "from" attribute
+ set
### Fixed
M session.go => session.go +37 -12
@@ 185,7 185,11 @@ func NegotiateSession(ctx context.Context, location, origin jid.JID, rw io.ReadW
}
s.in.d = intstream.Reader(s.in.d)
- s.out.e = &stanzaEncoder{TokenWriteFlusher: s.out.e}
+ se := &stanzaEncoder{TokenWriteFlusher: s.out.e}
+ if s.state&S2S == S2S {
+ se.from = s.LocalAddr()
+ }
+ s.out.e = se
return s, nil
}
@@ 946,27 950,48 @@ func (s *Session) closeInputStream() {
type stanzaEncoder struct {
xmlstream.TokenWriteFlusher
depth int
+ from jid.JID
}
func (se *stanzaEncoder) EncodeToken(t xml.Token) error {
-tokswitch:
switch tok := t.(type) {
case xml.StartElement:
se.depth++
- // RFC6120 §8.1.3
- // For <message/> and <presence/> stanzas, it is RECOMMENDED for the
- // originating entity to include an 'id' attribute; for <iq/> stanzas, it is
- // REQUIRED.
+ // Add required attributes if missing:
if se.depth == 1 && isStanzaEmptySpace(tok.Name) {
+ var foundID, foundFrom bool
for _, attr := range tok.Attr {
- if attr.Name.Local == "id" {
- break tokswitch
+ switch attr.Name.Local {
+ case "id":
+ // RFC6120 § 8.1.3
+ // For <message/> and <presence/> stanzas, it is RECOMMENDED for the
+ // originating entity to include an 'id' attribute; for <iq/> stanzas,
+ // it is REQUIRED.
+ foundID = true
+ case "from":
+ // RFC6120 § 4.7.1
+ // the 'to' and 'from' attributes are OPTIONAL on stanzas sent over
+ // XML streams qualified by the 'jabber:client' namespace, whereas
+ // they are REQUIRED on stanzas sent over XML streams qualified by the
+ // 'jabber: server' namespace
+ foundFrom = true
+ }
+ if foundID && foundFrom {
+ break
}
}
- tok.Attr = append(tok.Attr, xml.Attr{
- Name: xml.Name{Local: "id"},
- Value: attr.RandomID(),
- })
+ if f := se.from.String(); f != "" && !foundFrom {
+ tok.Attr = append(tok.Attr, xml.Attr{
+ Name: xml.Name{Local: "from"},
+ Value: se.from.String(),
+ })
+ }
+ if !foundID {
+ tok.Attr = append(tok.Attr, xml.Attr{
+ Name: xml.Name{Local: "id"},
+ Value: attr.RandomID(),
+ })
+ }
t = tok
}
case xml.EndElement:
M session_test.go => session_test.go +29 -1
@@ 172,6 172,7 @@ var serveTests = [...]struct {
in string
err error
errStringCmp bool
+ state xmpp.SessionState
}{
0: {
in: `<test></test>`,
@@ 324,6 325,33 @@ var serveTests = [...]struct {
in: `<a>test</a><b></b>`,
out: `<a xmlns="jabber:client">test</a></stream:stream>`,
},
+ 15: {
+ // S2S stanzas always have "from" set if not already set.
+ handler: xmpp.HandlerFunc(func(rw xmlstream.TokenReadEncoder, start *xml.StartElement) error {
+ _, err := xmlstream.Copy(rw, stanza.IQ{
+ ID: "1234",
+ Type: stanza.ResultIQ,
+ }.Wrap(nil))
+ return err
+ }),
+ in: `<iq type="get" id="1234"><unknownpayload xmlns="unknown"/></iq>`,
+ out: `<iq type="result" id="1234" from="test@example.net"></iq></stream:stream>`,
+ state: xmpp.S2S,
+ },
+ 16: {
+ // S2S stanzas always have "from" set, unless it was already set.
+ handler: xmpp.HandlerFunc(func(rw xmlstream.TokenReadEncoder, start *xml.StartElement) error {
+ _, err := xmlstream.Copy(rw, stanza.IQ{
+ ID: "1234",
+ From: jid.MustParse("from@example.net"),
+ Type: stanza.ResultIQ,
+ }.Wrap(nil))
+ return err
+ }),
+ in: `<iq type="get" id="1234"><unknownpayload xmlns="unknown"/></iq>`,
+ out: `<iq type="result" from="from@example.net" id="1234"></iq></stream:stream>`,
+ state: xmpp.S2S,
+ },
}
func TestServe(t *testing.T) {
@@ 331,7 359,7 @@ func TestServe(t *testing.T) {
t.Run(strconv.Itoa(i), func(t *testing.T) {
out := &bytes.Buffer{}
in := strings.NewReader(tc.in)
- s := xmpptest.NewSession(0, struct {
+ s := xmpptest.NewSession(tc.state, struct {
io.Reader
io.Writer
}{