fix(core): Fix components' stanza matching
This commit is contained in:
@@ -446,6 +446,9 @@ class XmppConnection {
|
||||
break;
|
||||
}
|
||||
}
|
||||
stanza_ = stanza_.copyWith(
|
||||
xmlns: _negotiationsHandler.getStanzaNamespace(),
|
||||
);
|
||||
|
||||
_log.fine('Running pre stanza handlers..');
|
||||
final data = await _runOutgoingPreStanzaHandlers(
|
||||
@@ -731,6 +734,7 @@ class XmppConnection {
|
||||
),
|
||||
);
|
||||
if (!incomingHandlers.done) {
|
||||
_log.warning('Returning error for unhandled stanza');
|
||||
await handleUnhandledStanza(this, incomingPreHandlers);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -81,6 +81,9 @@ abstract class NegotiationsHandler {
|
||||
log = Logger(toString());
|
||||
}
|
||||
|
||||
/// Returns the xmlns attribute that stanzas should have.
|
||||
String getStanzaNamespace();
|
||||
|
||||
/// Registers the negotiator [negotiator] against this negotiations handler.
|
||||
void registerNegotiator(XmppFeatureNegotiatorBase negotiator);
|
||||
|
||||
|
||||
@@ -35,6 +35,9 @@ class ClientToServerNegotiator extends NegotiationsHandler {
|
||||
/// The currently active negotiator.
|
||||
XmppFeatureNegotiatorBase? _currentNegotiator;
|
||||
|
||||
@override
|
||||
String getStanzaNamespace() => stanzaXmlns;
|
||||
|
||||
@override
|
||||
void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {
|
||||
negotiators[negotiator.id] = negotiator;
|
||||
|
||||
@@ -41,6 +41,9 @@ class ComponentToServerNegotiator extends NegotiationsHandler {
|
||||
/// The state the negotiation handler is currently in
|
||||
ComponentToServerState _state = ComponentToServerState.idle;
|
||||
|
||||
@override
|
||||
String getStanzaNamespace() => componentAcceptXmlns;
|
||||
|
||||
@override
|
||||
void registerNegotiator(XmppFeatureNegotiatorBase negotiator) {}
|
||||
|
||||
|
||||
@@ -4,88 +4,108 @@ import 'package:moxxmpp/src/namespaces.dart';
|
||||
import 'package:moxxmpp/src/stanza.dart';
|
||||
import 'package:moxxmpp/src/stringxml.dart';
|
||||
|
||||
/// A Handler is responsible for matching any kind of toplevel item in the XML stream
|
||||
/// (stanzas and Nonzas). For that, its [matches] method is called. What happens
|
||||
/// next depends on the subclass.
|
||||
// ignore: one_member_abstracts
|
||||
abstract class Handler {
|
||||
const Handler(this.matchStanzas, {this.nonzaTag, this.nonzaXmlns});
|
||||
final String? nonzaTag;
|
||||
final String? nonzaXmlns;
|
||||
final bool matchStanzas;
|
||||
|
||||
/// Returns true if the node matches the description provided by this [Handler].
|
||||
bool matches(XMLNode node);
|
||||
}
|
||||
|
||||
/// A Handler that specialises in matching Nonzas (and stanzas).
|
||||
class NonzaHandler extends Handler {
|
||||
NonzaHandler({
|
||||
required this.callback,
|
||||
this.nonzaTag,
|
||||
this.nonzaXmlns,
|
||||
});
|
||||
|
||||
/// The function to call when a nonza matches the description.
|
||||
final Future<bool> Function(XMLNode) callback;
|
||||
|
||||
/// The expected tag of a matching nonza.
|
||||
final String? nonzaTag;
|
||||
|
||||
// The expected xmlns attribute of a matching nonza.
|
||||
final String? nonzaXmlns;
|
||||
|
||||
@override
|
||||
bool matches(XMLNode node) {
|
||||
var matches = false;
|
||||
|
||||
var matches = true;
|
||||
if (nonzaTag == null && nonzaXmlns == null) {
|
||||
matches = true;
|
||||
}
|
||||
|
||||
if (nonzaXmlns != null && nonzaTag != null) {
|
||||
matches = (node.attributes['xmlns'] ?? '') == nonzaXmlns! &&
|
||||
node.tag == nonzaTag!;
|
||||
}
|
||||
|
||||
if (matchStanzas && nonzaTag == null) {
|
||||
matches = ['iq', 'presence', 'message'].contains(node.tag);
|
||||
return true;
|
||||
} else {
|
||||
if (nonzaXmlns != null) {
|
||||
matches &= node.attributes['xmlns'] == nonzaXmlns;
|
||||
}
|
||||
if (nonzaTag != null) {
|
||||
matches &= node.tag == nonzaTag;
|
||||
}
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
|
||||
class NonzaHandler extends Handler {
|
||||
NonzaHandler({
|
||||
required this.callback,
|
||||
String? nonzaTag,
|
||||
String? nonzaXmlns,
|
||||
}) : super(
|
||||
false,
|
||||
nonzaTag: nonzaTag,
|
||||
nonzaXmlns: nonzaXmlns,
|
||||
);
|
||||
final Future<bool> Function(XMLNode) callback;
|
||||
}
|
||||
|
||||
/// A Handler that only matches stanzas.
|
||||
class StanzaHandler extends Handler {
|
||||
StanzaHandler({
|
||||
required this.callback,
|
||||
this.tagXmlns,
|
||||
this.tagName,
|
||||
this.priority = 0,
|
||||
String? stanzaTag,
|
||||
}) : super(
|
||||
true,
|
||||
nonzaTag: stanzaTag,
|
||||
nonzaXmlns: stanzaXmlns,
|
||||
);
|
||||
this.stanzaTag,
|
||||
this.xmlns = stanzaXmlns,
|
||||
});
|
||||
|
||||
/// If specified, then the stanza must contain a direct child with a tag equal to
|
||||
/// [tagName].
|
||||
final String? tagName;
|
||||
|
||||
/// If specified, then the stanza must contain a direct child with a xmlns attribute
|
||||
/// equal to [tagXmlns]. If [tagName] is also non-null, then the element must also
|
||||
/// have a tag equal to [tagName].
|
||||
final String? tagXmlns;
|
||||
|
||||
/// If specified, the matching stanza must have a tag equal to [stanzaTag].
|
||||
final String? stanzaTag;
|
||||
|
||||
/// If specified, then the stanza must have a xmlns attribute equal to [xmlns].
|
||||
/// This defaults to [stanzaXmlns], but can be set to any other value or null. This
|
||||
/// is useful, for example, for components.
|
||||
final String? xmlns;
|
||||
|
||||
/// The priority after which [StanzaHandler]s are sorted.
|
||||
final int priority;
|
||||
|
||||
/// The function to call when a stanza matches the description.
|
||||
final Future<StanzaHandlerData> Function(Stanza, StanzaHandlerData) callback;
|
||||
|
||||
@override
|
||||
bool matches(XMLNode node) {
|
||||
var matches = super.matches(node);
|
||||
|
||||
if (matches == false) {
|
||||
return false;
|
||||
var matches = ['iq', 'message', 'presence'].contains(node.tag);
|
||||
if (stanzaTag != null) {
|
||||
matches &= node.tag == stanzaTag;
|
||||
}
|
||||
if (xmlns != null) {
|
||||
matches &= node.xmlns == xmlns;
|
||||
}
|
||||
|
||||
if (tagName != null) {
|
||||
final firstTag = node.firstTag(tagName!, xmlns: tagXmlns);
|
||||
matches &= firstTag != null;
|
||||
|
||||
matches = firstTag != null;
|
||||
if (tagXmlns != null) {
|
||||
matches &= firstTag?.xmlns == tagXmlns;
|
||||
}
|
||||
} else if (tagXmlns != null) {
|
||||
return listContains(
|
||||
matches &= listContains(
|
||||
node.children,
|
||||
(XMLNode node_) =>
|
||||
node_.attributes.containsKey('xmlns') &&
|
||||
node_.attributes['xmlns'] == tagXmlns,
|
||||
(XMLNode node_) => node_.attributes['xmlns'] == tagXmlns,
|
||||
);
|
||||
}
|
||||
|
||||
if (tagName == null && tagXmlns == null) {
|
||||
matches = true;
|
||||
}
|
||||
|
||||
return matches;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -107,11 +107,13 @@ class PresenceManager extends XmppManagerBase {
|
||||
}
|
||||
|
||||
final attrs = getAttributes();
|
||||
attrs.sendNonza(
|
||||
await attrs.sendStanza(
|
||||
Stanza.presence(
|
||||
from: attrs.getFullJID().toString(),
|
||||
children: children,
|
||||
),
|
||||
awaitable: false,
|
||||
addId: false,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -33,6 +33,7 @@ class Stanza extends XMLNode {
|
||||
List<XMLNode> children = const [],
|
||||
required String tag,
|
||||
Map<String, String> attributes = const {},
|
||||
String? xmlns,
|
||||
}) : super(
|
||||
tag: tag,
|
||||
attributes: <String, dynamic>{
|
||||
@@ -45,7 +46,7 @@ class Stanza extends XMLNode {
|
||||
...from != null
|
||||
? <String, dynamic>{'from': from}
|
||||
: <String, dynamic>{},
|
||||
'xmlns': stanzaXmlns
|
||||
if (xmlns != null) 'xmlns': xmlns,
|
||||
},
|
||||
children: children,
|
||||
);
|
||||
@@ -57,6 +58,7 @@ class Stanza extends XMLNode {
|
||||
String? id,
|
||||
List<XMLNode> children = const [],
|
||||
Map<String, String>? attributes = const {},
|
||||
String? xmlns,
|
||||
}) {
|
||||
return Stanza(
|
||||
tag: 'iq',
|
||||
@@ -64,8 +66,9 @@ class Stanza extends XMLNode {
|
||||
to: to,
|
||||
id: id,
|
||||
type: type,
|
||||
attributes: <String, String>{...attributes!, 'xmlns': stanzaXmlns},
|
||||
attributes: <String, String>{...attributes!},
|
||||
children: children,
|
||||
xmlns: xmlns,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -76,6 +79,7 @@ class Stanza extends XMLNode {
|
||||
String? id,
|
||||
List<XMLNode> children = const [],
|
||||
Map<String, String>? attributes = const {},
|
||||
String? xmlns,
|
||||
}) {
|
||||
return Stanza(
|
||||
tag: 'presence',
|
||||
@@ -83,8 +87,9 @@ class Stanza extends XMLNode {
|
||||
to: to,
|
||||
id: id,
|
||||
type: type,
|
||||
attributes: <String, String>{...attributes!, 'xmlns': stanzaXmlns},
|
||||
attributes: <String, String>{...attributes!},
|
||||
children: children,
|
||||
xmlns: xmlns,
|
||||
);
|
||||
}
|
||||
factory Stanza.message({
|
||||
@@ -94,6 +99,7 @@ class Stanza extends XMLNode {
|
||||
String? id,
|
||||
List<XMLNode> children = const [],
|
||||
Map<String, String>? attributes = const {},
|
||||
String? xmlns,
|
||||
}) {
|
||||
return Stanza(
|
||||
tag: 'message',
|
||||
@@ -101,8 +107,9 @@ class Stanza extends XMLNode {
|
||||
to: to,
|
||||
id: id,
|
||||
type: type,
|
||||
attributes: <String, String>{...attributes!, 'xmlns': stanzaXmlns},
|
||||
attributes: <String, String>{...attributes!},
|
||||
children: children,
|
||||
xmlns: xmlns,
|
||||
);
|
||||
}
|
||||
|
||||
@@ -134,6 +141,7 @@ class Stanza extends XMLNode {
|
||||
String? to,
|
||||
String? type,
|
||||
List<XMLNode>? children,
|
||||
String? xmlns,
|
||||
}) {
|
||||
return Stanza(
|
||||
tag: tag,
|
||||
@@ -142,6 +150,10 @@ class Stanza extends XMLNode {
|
||||
id: id ?? this.id,
|
||||
type: type ?? this.type,
|
||||
children: children ?? this.children,
|
||||
attributes: {
|
||||
...attributes.cast<String, String>(),
|
||||
},
|
||||
xmlns: xmlns ?? this.xmlns,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,9 +3,11 @@ import 'package:test/test.dart';
|
||||
|
||||
final stanza1 = Stanza.iq(
|
||||
children: [XMLNode.xmlns(tag: 'tag', xmlns: 'owo')],
|
||||
xmlns: stanzaXmlns,
|
||||
);
|
||||
final stanza2 = Stanza.message(
|
||||
children: [XMLNode.xmlns(tag: 'some-other-tag', xmlns: 'owo')],
|
||||
xmlns: stanzaXmlns,
|
||||
);
|
||||
|
||||
void main() {
|
||||
@@ -19,11 +21,17 @@ void main() {
|
||||
),
|
||||
);
|
||||
|
||||
expect(handler.matches(Stanza.iq()), true);
|
||||
expect(handler.matches(Stanza.message()), true);
|
||||
expect(handler.matches(Stanza.presence()), true);
|
||||
expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), true);
|
||||
expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), true);
|
||||
expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), true);
|
||||
expect(handler.matches(stanza1), true);
|
||||
expect(handler.matches(stanza2), true);
|
||||
expect(
|
||||
handler.matches(
|
||||
XMLNode.xmlns(tag: 'active', xmlns: csiXmlns),
|
||||
),
|
||||
false,
|
||||
);
|
||||
});
|
||||
test('xmlns matching', () {
|
||||
final handler = StanzaHandler(
|
||||
@@ -36,12 +44,13 @@ void main() {
|
||||
tagXmlns: 'owo',
|
||||
);
|
||||
|
||||
expect(handler.matches(Stanza.iq()), false);
|
||||
expect(handler.matches(Stanza.message()), false);
|
||||
expect(handler.matches(Stanza.presence()), false);
|
||||
expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(stanza1), true);
|
||||
expect(handler.matches(stanza2), true);
|
||||
});
|
||||
|
||||
test('stanzaTag matching', () {
|
||||
var run = false;
|
||||
final handler = StanzaHandler(
|
||||
@@ -57,9 +66,9 @@ void main() {
|
||||
stanzaTag: 'iq',
|
||||
);
|
||||
|
||||
expect(handler.matches(Stanza.iq()), true);
|
||||
expect(handler.matches(Stanza.message()), false);
|
||||
expect(handler.matches(Stanza.presence()), false);
|
||||
expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), true);
|
||||
expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(stanza1), true);
|
||||
expect(handler.matches(stanza2), false);
|
||||
|
||||
@@ -74,6 +83,7 @@ void main() {
|
||||
);
|
||||
expect(run, true);
|
||||
});
|
||||
|
||||
test('tagName matching', () {
|
||||
final handler = StanzaHandler(
|
||||
callback: (stanza, _) async => StanzaHandlerData(
|
||||
@@ -85,12 +95,13 @@ void main() {
|
||||
tagName: 'tag',
|
||||
);
|
||||
|
||||
expect(handler.matches(Stanza.iq()), false);
|
||||
expect(handler.matches(Stanza.message()), false);
|
||||
expect(handler.matches(Stanza.presence()), false);
|
||||
expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(stanza1), true);
|
||||
expect(handler.matches(stanza2), false);
|
||||
});
|
||||
|
||||
test('combined matching', () {
|
||||
final handler = StanzaHandler(
|
||||
callback: (stanza, _) async => StanzaHandlerData(
|
||||
@@ -104,13 +115,32 @@ void main() {
|
||||
tagXmlns: 'owo',
|
||||
);
|
||||
|
||||
expect(handler.matches(Stanza.iq()), false);
|
||||
expect(handler.matches(Stanza.message()), false);
|
||||
expect(handler.matches(Stanza.presence()), false);
|
||||
expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(stanza1), true);
|
||||
expect(handler.matches(stanza2), false);
|
||||
});
|
||||
|
||||
test('Test matching stanzas with a different xmlns', () {
|
||||
final handler = StanzaHandler(
|
||||
callback: (stanza, _) async => StanzaHandlerData(
|
||||
true,
|
||||
false,
|
||||
null,
|
||||
stanza,
|
||||
),
|
||||
xmlns: componentAcceptXmlns,
|
||||
);
|
||||
|
||||
expect(handler.matches(Stanza.iq(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(Stanza.message(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(Stanza.presence(xmlns: stanzaXmlns)), false);
|
||||
expect(handler.matches(Stanza.iq(xmlns: componentAcceptXmlns)), true);
|
||||
expect(handler.matches(stanza1), false);
|
||||
expect(handler.matches(stanza2), false);
|
||||
});
|
||||
|
||||
test('sorting', () {
|
||||
final handlerList = [
|
||||
StanzaHandler(
|
||||
|
||||
@@ -56,7 +56,7 @@ void main() {
|
||||
'<iq xmlns="jabber:client" type="result" id="a"><bind xmlns="urn:ietf:params:xml:ns:xmpp-bind"><jid>polynomdivision@test.server/MU29eEZn</jid></bind></iq>',
|
||||
ignoreId: true,
|
||||
),
|
||||
StringExpectation(
|
||||
StanzaExpectation(
|
||||
"<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show><c xmlns='http://jabber.org/protocol/caps' hash='sha-1' node='http://moxxmpp.example' ver='3QvQ2RAy45XBDhArjxy/vEWMl+E=' /></presence>",
|
||||
'',
|
||||
),
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import 'dart:async';
|
||||
import 'package:moxxmpp/moxxmpp.dart';
|
||||
import 'package:test/test.dart';
|
||||
import '../helpers/logging.dart';
|
||||
@@ -88,6 +89,7 @@ void main() {
|
||||
final stanza = Stanza(
|
||||
to: 'some.user@server.example',
|
||||
tag: 'message',
|
||||
xmlns: stanzaXmlns,
|
||||
);
|
||||
|
||||
test('Test stream with SM enablement', () async {
|
||||
@@ -388,10 +390,14 @@ void main() {
|
||||
"<enable xmlns='urn:xmpp:sm:3' resume='true' />",
|
||||
'<enabled xmlns="urn:xmpp:sm:3" id="some-long-sm-id" resume="true" />',
|
||||
),
|
||||
StringExpectation(
|
||||
StanzaExpectation(
|
||||
"<presence xmlns='jabber:client' from='polynomdivision@test.server/MU29eEZn'><show>chat</show></presence>",
|
||||
'<iq type="result" />',
|
||||
),
|
||||
StringExpectation(
|
||||
"<r xmlns='urn:xmpp:sm:3' />",
|
||||
"<a xmlns='urn:xmpp:sm:3' h='1' />",
|
||||
),
|
||||
StanzaExpectation(
|
||||
"<iq to='user@example.com' type='get' id='a' xmlns='jabber:client' />",
|
||||
"<iq from='user@example.com' type='result' id='a' />",
|
||||
@@ -425,7 +431,8 @@ void main() {
|
||||
waitUntilLogin: true,
|
||||
);
|
||||
|
||||
expect(fakeSocket.getState(), 6);
|
||||
await Future<void>.delayed(const Duration(seconds: 3));
|
||||
expect(fakeSocket.getState(), 7);
|
||||
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
||||
expect(
|
||||
conn
|
||||
@@ -502,9 +509,6 @@ void main() {
|
||||
password: 'aaaa',
|
||||
);
|
||||
await conn.registerManagers([
|
||||
PresenceManager(),
|
||||
RosterManager(TestingRosterStateManager('', [])),
|
||||
DiscoManager([]),
|
||||
StreamManagementManager(),
|
||||
]);
|
||||
await conn.registerFeatureNegotiators([
|
||||
@@ -523,7 +527,8 @@ void main() {
|
||||
await conn.connect(
|
||||
waitUntilLogin: true,
|
||||
);
|
||||
expect(fakeSocket.getState(), 7);
|
||||
|
||||
expect(fakeSocket.getState(), 6);
|
||||
expect(await conn.getConnectionState(), XmppConnectionState.connected);
|
||||
expect(
|
||||
conn
|
||||
|
||||
Reference in New Issue
Block a user