fix(core): Fix components' stanza matching

This commit is contained in:
PapaTutuWawa committed 2023-04-05 13:17:18 +02:00
1 parent 5c47c35a46
commit 916be1c927
51 files changed
+324 -82

No files matched your search

+9
View File
@@ -24,6 +24,15 @@ To run the example, make sure that Flutter is correctly set up and working. If y
the development shell provided by the NixOS Flake, ensure that `ANDROID_HOME` and
`ANDROID_AVD_HOME` are pointing to the correct directories.
## Examples
This repository contains 2 types of examples:
- `example_flutter`: An example of using moxxmpp using Flutter
- `examples_dart`: A collection of pure Dart examples for showing different aspects of moxxmpp
For more information, see the respective README files.
## License
See `./LICENSE`.
-6
View File
@@ -1,6 +0,0 @@
# melos_managed_dependency_overrides: moxxmpp,moxxmpp_socket_tcp
dependency_overrides:
moxxmpp:
path: ../packages/moxxmpp
moxxmpp_socket_tcp:
path: ../packages/moxxmpp_socket_tcp
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
+6
View File
@@ -0,0 +1,6 @@
# Files and directories created by pub.
.dart_tool/
.packages
# Conventional directory for build output.
build/
+7
View File
@@ -0,0 +1,7 @@
# Dart Examples
Run using `dart run bin/<example>.dart`.
## Examples
- `component.dart`: Use moxxmpp to implement a component using XEP-0114.
+30
View File
@@ -0,0 +1,30 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.
include: package:lints/recommended.yaml
# Uncomment the following section to specify additional rules.
# linter:
# rules:
# - camel_case_types
# analyzer:
# exclude:
# - path/to/excluded/files/**
# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints
# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options
+90
View File
@@ -0,0 +1,90 @@
import 'package:logging/logging.dart';
import 'package:moxxmpp/moxxmpp.dart';
import 'package:moxxmpp_socket_tcp/moxxmpp_socket_tcp.dart';
class TestingTCPSocketWrapper extends TCPSocketWrapper {
@override
bool onBadCertificate(dynamic certificate, String domain) {
return true;
}
}
class EchoMessageManager extends XmppManagerBase {
EchoMessageManager() : super('org.moxxy.example.message');
@override
Future<bool> isSupported() async => true;
@override
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: 'message',
callback: _onMessage,
priority: -100,
xmlns: null,
)
];
Future<StanzaHandlerData> _onMessage(
Stanza stanza,
StanzaHandlerData state,
) async {
final body = stanza.firstTag('body');
if (body == null) return state.copyWith(done: true);
final bodyText = body.innerText();
await getAttributes().sendStanza(
Stanza.message(
to: stanza.from,
children: [
XMLNode(
tag: 'body',
text: 'Hello, ${stanza.from}! You said "$bodyText"',
),
],
),
awaitable: false,
);
return state.copyWith(done: true);
}
}
void main(List<String> arguments) async {
Logger.root.level = Level.ALL;
Logger.root.onRecord.listen((record) {
// ignore: avoid_print
print(
'[${record.level.name}] (${record.loggerName}) ${record.time}: ${record.message}',
);
});
final conn = XmppConnection(
TestingReconnectionPolicy(),
AlwaysConnectedConnectivityManager(),
ComponentToServerNegotiator(),
TestingTCPSocketWrapper(),
)..connectionSettings = ConnectionSettings(
jid: JID.fromString('component.localhost'),
password: 'abc123',
host: '127.0.0.1',
port: 8888,
);
await conn.registerManagers([
EchoMessageManager(),
]);
final result = await conn.connect(
waitUntilLogin: true,
shouldReconnect: false,
enableReconnectOnSuccess: false,
);
if (result.isType<XmppError>()) {
print('Failed to connect as component');
return;
}
// Just block for some time to test the connection
await Future<void>.delayed(const Duration(seconds: 9999));
}
+24
View File
@@ -0,0 +1,24 @@
name: example_dart
description: A sample command-line application.
version: 1.0.0
# homepage: https://www.example.com
environment:
sdk: '>=2.18.0 <3.0.0'
dependencies:
logging: ^1.0.2
moxxmpp:
hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: 0.3.0
moxxmpp_socket_tcp:
hosted: https://git.polynom.me/api/packages/Moxxy/pub
version: 0.3.0
dependency_overrides:
moxxmpp:
path: ../packages/moxxmpp
dev_dependencies:
lints: ^2.0.0
test: ^1.16.0
+4
View File
@@ -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) {}
+69 -49
View File
@@ -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;
}
}
+3 -1
View File
@@ -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,
);
}
+16 -4
View File
@@ -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,
);
}
}
+45 -15
View File
@@ -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>",
'',
),
+11 -6
View File
@@ -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