xmpp: Rework the stanza handler

This commit is contained in:
PapaTutuWawa 2022-03-11 21:39:42 +01:00
parent 648b83fc08
commit ae643e7009
27 changed files with 663 additions and 307 deletions

View File

@ -5,7 +5,7 @@ import "package:moxxyv2/xmpp/roster.dart";
import "package:get_it/get_it.dart";
class MoxxyRosterManger extends RosterManager {
class MoxxyRosterManager extends RosterManager {
@override
Future<void> commitLastRosterVersion(String version) async {
await GetIt.I.get<XmppService>().modifyXmppState((state) => state.copyWith(

View File

@ -11,10 +11,16 @@ import "package:moxxyv2/xmpp/presence.dart";
import "package:moxxyv2/xmpp/message.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/xeps/xep_0054.dart";
import "package:moxxyv2/xmpp/xeps/xep_0280.dart";
import "package:moxxyv2/xmpp/xeps/xep_0352.dart";
import "package:moxxyv2/xmpp/xeps/xep_0060.dart";
import "package:moxxyv2/xmpp/xeps/xep_0066.dart";
import "package:moxxyv2/xmpp/xeps/xep_0084.dart";
import "package:moxxyv2/xmpp/xeps/xep_0184.dart";
import "package:moxxyv2/xmpp/xeps/xep_0280.dart";
import "package:moxxyv2/xmpp/xeps/xep_0333.dart";
import "package:moxxyv2/xmpp/xeps/xep_0352.dart";
import "package:moxxyv2/xmpp/xeps/xep_0359.dart";
import "package:moxxyv2/xmpp/xeps/xep_0385.dart";
import "package:moxxyv2/xmpp/xeps/xep_0447.dart";
import "package:moxxyv2/xmpp/xeps/xep_0030/cachemanager.dart";
import "package:moxxyv2/service/managers/roster.dart";
import "package:moxxyv2/service/managers/disco.dart";
@ -209,17 +215,25 @@ void onStart() {
GetIt.I.registerSingleton<RosterService>(RosterService(sendData: middleware));
final connection = XmppConnection();
connection.registerManager(MoxxyStreamManagementManager());
connection.registerManager(MoxxyDiscoManager());
connection.registerManager(MessageManager());
connection.registerManager(MoxxyRosterManger());
connection.registerManager(PresenceManager());
connection.registerManager(CSIManager());
connection.registerManager(DiscoCacheManager());
connection.registerManager(CarbonsManager());
connection.registerManager(PubSubManager());
connection.registerManager(vCardManager());
connection.registerManager(UserAvatarManager());
connection.registerManagers([
MoxxyStreamManagementManager(),
MoxxyDiscoManager(),
MoxxyRosterManager(),
MessageManager(),
PresenceManager(),
CSIManager(),
DiscoCacheManager(),
CarbonsManager(),
PubSubManager(),
vCardManager(),
UserAvatarManager(),
StableIdManager(),
SIMSManager(),
MessageDeliveryReceiptManager(),
ChatMarkerManager(),
OOBManager(),
SFSManager()
]);
GetIt.I.registerSingleton<XmppConnection>(connection);
final account = await xmpp.getAccountData();

View File

@ -15,7 +15,9 @@ import "package:moxxyv2/xmpp/roster.dart";
import "package:moxxyv2/xmpp/sasl/authenticator.dart";
import "package:moxxyv2/xmpp/sasl/authenticators.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/managers/attributes.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/xeps/xep_0030/xep_0030.dart";
import "package:moxxyv2/xmpp/xeps/xep_0030/cachemanager.dart";
@ -56,33 +58,46 @@ class StartTLSNonza extends XMLNode {
}
class XmppConnection {
late ConnectionSettings _connectionSettings;
final StreamController<XmppEvent> _eventStreamController;
final Map<String, Completer<XMLNode>> _awaitingResponse;
final Map<String, XmppManagerBase> _xmppManagers;
final List<StanzaHandler> _incomingStanzaHandlers;
final List<StanzaHandler> _outgoingStanzaHandlers;
final BaseSocketWrapper _socket;
XmppConnectionState _connectionState;
late final Stream<String> _socketStream;
final StreamController<XmppEvent> _eventStreamController;
final Map<String, Completer<XMLNode>> _awaitingResponse = {};
final Map<String, XmppManagerBase> _xmppManagers = {};
late ConnectionSettings _connectionSettings;
// Stream properties
//
// Stream feature XMLNS
/// Stream properties
///
/// Features we got after SASL auth (xmlns)
final List<String> _streamFeatures = List.empty(growable: true);
/// Disco info we got after binding a resource (xmlns)
final List<String> _serverFeatures = List.empty(growable: true);
RoutingState _routingState;
String _resource;
/// The buffer object to keep split up stanzas together
final XmlStreamBuffer _streamBuffer;
Timer? _connectionPingTimer;
int _currentBackoffAttempt;
Timer? _backoffTimer;
/// UUID object to generate stanza and origin IDs
final Uuid _uuid;
bool _resuming; // For indicating in a [ConnectionStateChangedEvent] that the event occured because we did a reconnection
/// The time between sending a ping to keep the connection open
// TODO: Only start the timer if we did not send a stanza after n seconds
final Duration connectionPingDuration;
/// The current state of the connection handling state machine.
RoutingState _routingState;
/// The currently bound resource or "" if none has been bound yet.
String _resource;
/// Counter for how manyy we have tried to reconnect.
int _currentBackoffAttempt;
/// For indicating in a [ConnectionStateChangedEvent] that the event occured because we
/// did a reconnection.
bool _resuming;
/// Timers for the keep-alive ping and the backoff connection process.
Timer? _connectionPingTimer;
Timer? _backoffTimer;
// Negotiators
/// Negotiators
late AuthenticationNegotiator _authenticator;
// Misc
/// Misc
final Logger _log;
/// [socket] is for debugging purposes.
@ -102,6 +117,10 @@ class XmppConnection {
_uuid = const Uuid(),
// NOTE: For testing
_socket = socket ?? TCPSocketWrapper(),
_awaitingResponse = {},
_xmppManagers = {},
_incomingStanzaHandlers = List.empty(growable: true),
_outgoingStanzaHandlers = List.empty(growable: true),
_log = Logger("XmppConnection") {
_socketStream = _socket.getDataStream();
// TODO: Handle on done
@ -109,8 +128,11 @@ class XmppConnection {
_socket.getErrorStream().listen(_handleError);
}
/// Registers an [XmppManagerBase] subclass as a manager on this connection
void registerManager(XmppManagerBase manager) {
/// Registers an [XmppManagerBase] sub-class as a manager on this connection.
/// [sortHandlers] should NOT be touched. It specified if the handler priorities
/// should be set up. The only time this should be false is when called via
/// [registerManagers].
void registerManager(XmppManagerBase manager, { bool sortHandlers = true }) {
_log.finest("Registering ${manager.getId()}");
manager.register(XmppManagerAttributes(
sendStanza: sendStanza,
@ -136,8 +158,27 @@ class XmppConnection {
} else if (_xmppManagers.containsKey(discoManager)) {
(_xmppManagers[discoManager] as DiscoManager).addDiscoFeatures(manager.getDiscoFeatures());
}
_incomingStanzaHandlers.addAll(manager.getIncomingStanzaHandlers());
_outgoingStanzaHandlers.addAll(manager.getOutgoingStanzaHandlers());
if (sortHandlers) {
_incomingStanzaHandlers.sort(stanzaHandlerSortComparator);
_outgoingStanzaHandlers.sort(stanzaHandlerSortComparator);
}
}
/// Like [registerManager], but for a list of managers.
void registerManagers(List<XmppManagerBase> managers) {
for (final manager in managers) {
registerManager(manager, sortHandlers: false);
}
// Sort them
_incomingStanzaHandlers.sort(stanzaHandlerSortComparator);
_outgoingStanzaHandlers.sort(stanzaHandlerSortComparator);
}
/// Generate an Id suitable for an origin-id or stanza id
String generateId() {
return _uuid.v4();
@ -256,7 +297,7 @@ class XmppConnection {
/// [stanza] has none.
/// If addId is true, then an "id" attribute will be added to the stanza if [stanza] has
/// none.
Future<XMLNode> sendStanza(Stanza stanza, { bool addFrom = true, bool addId = true, bool awaitable = true }) {
Future<XMLNode> sendStanza(Stanza stanza, { bool addFrom = true, bool addId = true, bool awaitable = true }) async {
// Add extra data in case it was not set
if (addId && (stanza.id == null || stanza.id == "")) {
stanza = stanza.copyWith(id: generateId());
@ -271,6 +312,9 @@ class XmppConnection {
_awaitingResponse[stanza.id!] = Completer();
}
// Tell the SM manager that we're about to send a stanza
await _runOutoingStanzaHandlers(stanza);
// This uses the StreamManager to behave like a send queue
if (_canSendData()) {
_socket.write(stanzaString);
@ -279,9 +323,6 @@ class XmppConnection {
// NOTE: Here we have send an Ack request nonza. This is now done by StreamManagementManager when receiving the StanzaSentEvent
}
// Tell the SM manager that we're about to send a stanza
_sendEvent(StanzaSentEvent(stanza: stanza));
if (awaitable) {
return _awaitingResponse[stanza.id!]!.future;
} else {
@ -385,6 +426,34 @@ class XmppConnection {
_log.warning("Failed to discover server items using XEP-0030");
}
}
/// Iterate over [handlers] and check if the handler matches [stanza]. If it does,
/// call its callback and end the processing if the callback returned true; continue
/// if it returned false.
Future<bool> _runStanzaHandlers(List<StanzaHandler> handlers, Stanza stanza) async {
StanzaHandlerData state = StanzaHandlerData(false, stanza);
for (final handler in handlers) {
if (handler.matches(stanza)) {
state = await handler.callback(stanza, state);
if (state.done) return true;
}
}
return false;
}
Future<bool> _runIncomingStanzaHandlers(Stanza stanza) async {
return await _runStanzaHandlers(
_incomingStanzaHandlers,
stanza
);
}
Future<bool> _runOutoingStanzaHandlers(Stanza stanza) async {
return await _runStanzaHandlers(
_outgoingStanzaHandlers,
stanza
);
}
/// Called whenever we receive a stanza after resource binding or stream resumption.
Future<void> _handleStanza(XMLNode nonza) async {
@ -413,16 +482,8 @@ class XmppConnection {
_awaitingResponse.remove(id);
return;
}
bool stanzaHandled = false;
await Future.forEach(
_xmppManagers.values,
(XmppManagerBase manager) async {
final handled = await manager.runStanzaHandlers(stanza);
if (!stanzaHandled && handled) stanzaHandled = true;
}
);
final stanzaHandled = await _runIncomingStanzaHandlers(stanza);
if (!stanzaHandled) {
handleUnhandledStanza(this, stanza);

View File

@ -33,13 +33,6 @@ class AuthenticationFailedEvent extends XmppEvent {
AuthenticationFailedEvent({ required this.saslError });
}
/// Triggered when we send a stanza to the socket
class StanzaSentEvent extends XmppEvent {
final Stanza stanza;
StanzaSentEvent({ required this.stanza });
}
/// Triggered when we want to ping the connection open
class SendPingEvent extends XmppEvent {}

View File

@ -1,6 +1,5 @@
import "package:moxxyv2/xmpp/events.dart";
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/managers/attributes.dart";
@ -22,9 +21,14 @@ abstract class XmppManagerBase {
return _managerAttributes;
}
/// Return the [StanzaHandler]s associated with this manager.
List<StanzaHandler> getStanzaHandlers() => [];
/// Return the [StanzaHandler]s associated with this manager that deal with stanzas we
/// send.
List<StanzaHandler> getOutgoingStanzaHandlers() => [];
/// Return the [StanzaHandler]s associated with this manager that deal with stanzas we
/// receive.
List<StanzaHandler> getIncomingStanzaHandlers() => [];
/// Return the [NonzaHandler]s associated with this manager.
List<NonzaHandler> getNonzaHandlers() => [];
@ -59,21 +63,4 @@ abstract class XmppManagerBase {
return handled;
}
/// Runs all [StanzaHandlers] of this Manager which match the nonza. Resolves to true if
/// the nonza has been handled by one of the handlers. Resolves to false otherwise.
Future<bool> runStanzaHandlers(Stanza stanza) async {
bool handled = false;
await Future.forEach(
getStanzaHandlers(),
(StanzaHandler handler) async {
if (handler.matches(stanza)) {
handled = true;
await handler.callback(stanza);
}
}
);
return handled;
}
}

View File

@ -0,0 +1,29 @@
import "package:freezed_annotation/freezed_annotation.dart";
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/xeps/xep_0066.dart";
import "package:moxxyv2/xmpp/xeps/xep_0359.dart";
import "package:moxxyv2/xmpp/xeps/xep_0385.dart";
import "package:moxxyv2/xmpp/xeps/xep_0447.dart";
part "data.freezed.dart";
@freezed
class StanzaHandlerData with _$StanzaHandlerData {
factory StanzaHandlerData(
// Indicates to the runner that processing is now done. This means that all
// pre-processing is done and no other handlers should be consulted.
bool done,
// The stanza that is being dealt with
Stanza stanza,
{
StatelessMediaSharingData? sims,
StatelessFileSharingData? sfs,
OOBData? oob,
StableStanzaId? stableId,
@Default(false) bool isCarbon,
@Default(false) bool deliveryReceiptRequested,
@Default(false) bool isMarkable
}
) = _StanzaHandlerData;
}

View File

@ -2,16 +2,16 @@ import "package:moxxyv2/shared/helpers.dart";
import "package:moxxyv2/xmpp/namespaces.dart";
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
class NonzaHandler {
abstract class Handler {
final String? nonzaTag;
final String? nonzaXmlns;
final bool matchStanzas;
final Future<bool> Function(XMLNode) callback;
NonzaHandler({ this.nonzaTag, this.nonzaXmlns, required this.callback, this.matchStanzas = false });
const Handler(this.matchStanzas, { this.nonzaTag, this.nonzaXmlns });
/// Returns true if the node matches the description provided by this NonzaHandler
/// Returns true if the node matches the description provided by this [Handler].
bool matches(XMLNode node) {
bool matches = false;
@ -31,17 +31,38 @@ class NonzaHandler {
}
}
class StanzaHandler extends NonzaHandler {
final String? tagXmlns;
class NonzaHandler extends Handler {
final Future<bool> Function(XMLNode) callback;
NonzaHandler({
required this.callback,
String? nonzaTag,
String? nonzaXmlns
}) : super(
false,
nonzaTag: nonzaTag,
nonzaXmlns: nonzaXmlns
);
}
class StanzaHandler extends Handler {
final String? tagName;
final String? tagXmlns;
final int priority;
final Future<StanzaHandlerData> Function(Stanza, StanzaHandlerData) callback;
StanzaHandler({ this.tagXmlns, this.tagName, String? stanzaTag, required Future<bool> Function(Stanza) callback }) : super(
matchStanzas: true,
StanzaHandler({
required this.callback,
this.tagXmlns,
this.tagName,
this.priority = 0,
String? stanzaTag,
}) : super(
true,
nonzaTag: stanzaTag,
nonzaXmlns: stanzaXmlns,
callback: (XMLNode node) async => await callback(Stanza.fromXMLNode(node))
nonzaXmlns: stanzaXmlns
);
@override
bool matches(XMLNode node) {
bool matches = super.matches(node);
@ -68,3 +89,5 @@ class StanzaHandler extends NonzaHandler {
return matches;
}
}
int stanzaHandlerSortComparator(StanzaHandler a, StanzaHandler b) => b.priority.compareTo(a.priority);

View File

@ -9,3 +9,9 @@ const carbonsManager = "im.moxxy.carbonsmanager";
const vcardManager = "im.moxxy.vcardmanager";
const pubsubManager = "im.moxxy.pubsubmanager";
const userAvatarManager = "im.moxxy.useravatarmanager";
const stableIdManager = "im.moxxy.stableidmanager";
const simsManager = "im.moxxy.simsmanager";
const messageDeliveryReceiptManager = "im.moxxy.messagedeliveryreceiptmanager";
const chatMarkerManager = "im.moxxy.chatmarkermanager";
const oobManager = "im.moxxy.oobmanager";
const sfsManager = "im.moxxy.sfsmanager";

View File

View File

@ -4,17 +4,12 @@ import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/jid.dart";
import "package:moxxyv2/xmpp/namespaces.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/xeps/xep_0030/cachemanager.dart";
import "package:moxxyv2/xmpp/xeps/xep_0066.dart";
import "package:moxxyv2/xmpp/xeps/xep_0184.dart";
import "package:moxxyv2/xmpp/xeps/xep_0280.dart";
import "package:moxxyv2/xmpp/xeps/xep_0297.dart";
import "package:moxxyv2/xmpp/xeps/xep_0333.dart";
import "package:moxxyv2/xmpp/xeps/xep_0359.dart";
import "package:moxxyv2/xmpp/xeps/xep_0385.dart";
import "package:moxxyv2/xmpp/xeps/xep_0447.dart";
class MessageDetails {
final String to;
@ -48,162 +43,34 @@ class MessageManager extends XmppManagerBase {
String getName() => "MessageManager";
@override
List<StanzaHandler> getStanzaHandlers() => [
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: "message",
callback: _onMessage
callback: _onMessage,
priority: -100
)
];
@override
List<String> getDiscoFeatures() => [ chatMarkersXmlns, oobDataXmlns, deliveryXmlns, stableIdXmlns ];
/// Helper function to extract and verify the origin and stanza Id according to
/// XEP-0359.
/// Requires a [DiscoCacheManager] to be registered in order to provide anything
/// other than [null].
Future<StableStanzaId> _getStanzaId(Stanza message) async {
final from = JID.fromString(message.attributes["from"]!);
String? originId;
String? stanzaId;
String? stanzaIdBy;
final originIdTag = message.firstTag("origin-id", xmlns: stableIdXmlns);
final stanzaIdTag = message.firstTag("stanza-id", xmlns: stableIdXmlns);
if (originIdTag != null || stanzaIdTag != null) {
logger.finest("Found Unique and Stable Stanza Id tag");
final attrs = getAttributes();
final cache = attrs.getManagerById(discoCacheManager) as DiscoCacheManager?;
if (cache != null) {
final info = await cache.getInfoByJid(from.toString());
if (info != null) {
logger.finest("Got info for ${from.toString()}");
if (info.features.contains(stableIdXmlns)) {
logger.finest("${from.toString()} supports $stableIdXmlns.");
if (originIdTag != null) {
originId = originIdTag.attributes["id"]!;
}
if (stanzaIdTag != null) {
stanzaId = stanzaIdTag.attributes["id"]!;
stanzaIdBy = stanzaIdTag.attributes["by"]!;
}
} else {
logger.finest("${from.toString()} does not support $stableIdXmlns. Ignoring... ");
}
}
}
}
return StableStanzaId(
originId: originId,
stanzaId: stanzaId,
stanzaIdBy: stanzaIdBy
);
}
Future<void> _handleChatMarker(Stanza message, XMLNode marker) async {
final attrs = getAttributes();
if (!["received", "displayed", "acknowledged"].contains(marker.tag)) {
logger.warning("Unknown message marker '${marker.tag}' found.");
return;
}
attrs.sendEvent(ChatMarkerEvent(
from: JID.fromString(message.from!),
type: marker.tag,
id: marker.attributes["id"]!,
));
}
StatelessMediaSharingData? _getSIMS(Stanza message) {
final references = message.findTags("reference", xmlns: referenceXmlns);
for (final ref in references) {
final sims = ref.firstTag("media-sharing", xmlns: simsXmlns);
if (sims != null) return parseSIMSElement(sims);
}
return null;
}
String? _isDeliveryReceiptResponse(Stanza message) {
final received = message.firstTag("received", xmlns: deliveryXmlns);
if (received == null) return null;
for (final item in message.children) {
if (!["origin-id", "stanza-id", "delay"].contains(item.tag)) {
logger.info("Won't handle stanza as delivery receipt because we found an '${item.tag}' element");
return null;
}
}
return received.attributes["id"]!;
}
Future<bool> _onMessage(Stanza message) async {
Future<StanzaHandlerData> _onMessage(Stanza message, StanzaHandlerData state) async {
// First check if it's a carbon
final from = JID.fromString(message.attributes["from"]!);
final received = message.firstTag("received", xmlns: carbonsXmlns);
final attrs = getAttributes();
bool isCarbon = false;
if (received != null) {
final cm = attrs.getManagerById(carbonsManager) as CarbonsManager?;
// Ignore invalid carbons
if (cm == null || !cm.isCarbonValid(from)) return true;
final forwarded = received.firstTag("forwarded", xmlns: forwardedXmlns)!;
message = unpackForwarded(forwarded);
isCarbon = true;
}
final did = _isDeliveryReceiptResponse(message);
if (did != null) {
attrs.sendEvent(DeliveryReceiptReceivedEvent(from: from, id: did));
return true;
}
final sfs = message.firstTag("file-sharing", xmlns: sfsXmlns);
final sims = _getSIMS(message);
final body = message.firstTag("body");
if (body == null && sfs == null && sims == null) {
final marker = message.firstTagByXmlns(chatMarkersXmlns);
if (marker != null) {
// Response to a marker
await _handleChatMarker(message, marker);
return true;
}
return false;
}
OOBData? oob;
final oobTag = message.firstTag("x", xmlns: oobDataXmlns);
if (oobTag != null) {
final url = oobTag.firstTag("url");
final desc = oobTag.firstTag("desc");
oob = OOBData(
url: url?.innerText(),
desc: desc?.innerText()
);
}
getAttributes().sendEvent(MessageEvent(
body: body != null ? body.innerText() : "",
fromJid: from,
sid: message.attributes["id"]!,
stanzaId: state.stableId ?? StableStanzaId(),
isCarbon: state.isCarbon,
deliveryReceiptRequested: state.deliveryReceiptRequested,
isMarkable: state.isMarkable,
type: message.attributes["type"],
stanzaId: await _getStanzaId(message),
isMarkable: message.firstTag("markable", xmlns: chatMarkersXmlns) != null,
isCarbon: isCarbon,
deliveryReceiptRequested: message.firstTag("request", xmlns: deliveryXmlns) != null,
oob: oob,
sfs: sfs != null ? parseSFSElement(sfs) : null,
sims: sims
oob: state.oob,
sfs: state.sfs,
sims: state.sims
));
return true;
return state.copyWith(done: true);
}
/// Send a message to [to] with the content [body]. If [deliveryRequest] is true, then

View File

@ -5,6 +5,7 @@ import "package:moxxyv2/xmpp/jid.dart";
import "package:moxxyv2/xmpp/events.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/xeps/xep_0030/xep_0030.dart";
import "package:moxxyv2/xmpp/xeps/xep_0030/helpers.dart";
@ -23,7 +24,7 @@ class PresenceManager extends XmppManagerBase {
String getName() => "PresenceManager";
@override
List<StanzaHandler> getStanzaHandlers() => [
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: "presence",
callback: _onPresence
@ -33,14 +34,14 @@ class PresenceManager extends XmppManagerBase {
@override
List<String> getDiscoFeatures() => [ capsXmlns ];
Future<bool> _onPresence(Stanza presence) async {
Future<StanzaHandlerData> _onPresence(Stanza presence, StanzaHandlerData state) async {
final attrs = getAttributes();
switch (presence.type) {
case "subscribed": {
attrs.sendEvent(
SubscriptionRequestReceivedEvent(from: JID.fromString(presence.from!))
);
return true;
return state.copyWith(done: true);
}
default: break;
}
@ -51,7 +52,7 @@ class PresenceManager extends XmppManagerBase {
getAttributes().sendEvent(PresenceReceivedEvent(JID.fromString(presence.from!), presence));
}
return false;
return state.copyWith(done: false);
}
/// Returns the capability hash.

View File

@ -5,6 +5,7 @@ import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/jid.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
class XmppRosterItem {
@ -48,7 +49,7 @@ class RosterManager extends XmppManagerBase {
String getName() => "RosterManager";
@override
List<StanzaHandler> getStanzaHandlers() => [
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: "iq",
tagName: "query",
@ -67,7 +68,7 @@ class RosterManager extends XmppManagerBase {
_rosterVersion = ver;
}
Future<bool> _onRosterPush(Stanza stanza) async {
Future<StanzaHandlerData> _onRosterPush(Stanza stanza, StanzaHandlerData state) async {
final attrs = getAttributes();
final from = stanza.attributes["from"];
final selfJid = attrs.getConnectionSettings().jid;
@ -79,7 +80,7 @@ class RosterManager extends XmppManagerBase {
// - a full JID of our own
if (from != null && JID.fromString(stanza.attributes["from"]).toBare() != selfJid) {
logger.warning("Roster push invalid! Unexpected from attribute: ${stanza.toXml()}");
return true;
return state.copyWith(done: true);
}
final query = stanza.firstTag("query", xmlns: rosterXmlns)!;
@ -87,7 +88,7 @@ class RosterManager extends XmppManagerBase {
if (item == null) {
logger.warning("Received empty roster push");
return true;
return state.copyWith(done: true);
}
if (query.attributes["ver"] != null) {
@ -105,7 +106,7 @@ class RosterManager extends XmppManagerBase {
));
attrs.sendStanza(stanza.reply());
return true;
return state.copyWith(done: true);
}
/// Requests the roster from the server. [lastVersion] refers to the last version

View File

@ -64,13 +64,10 @@ class TCPSocketWrapper extends BaseSocketWrapper {
bool isSecure() => _secure;
bool _onBadCertificate(certificate, String domain) {
_log.fine("Bad certificate: ${certificate.toString()}");
final isExpired = certificate.endValidity.isAfter(DateTime.now());
// TODO: Remove the kDebugMode once I am sure this works as it should
return !isExpired && certificate.domain == domain && kDebugMode;
_log.fine("Bad certificate: ${certificate.toString()}");
return false;
}
Future<bool> _xep368Connect(String domain) async {

View File

@ -5,6 +5,7 @@ import "package:moxxyv2/xmpp/presence.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/xeps/xep_0030/helpers.dart";
class DiscoManager extends XmppManagerBase {
@ -14,7 +15,7 @@ class DiscoManager extends XmppManagerBase {
DiscoManager() : _features = List.empty(growable: true), super();
@override
List<StanzaHandler> getStanzaHandlers() => [
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
tagName: "query",
tagXmlns: discoInfoXmlns,
@ -54,8 +55,8 @@ class DiscoManager extends XmppManagerBase {
/// May be overriden. Specifies the identities which will be returned in a disco info response.
List<Identity> getIdentities() => const [ Identity(category: "client", type: "pc", name: "moxxmpp", lang: "en") ];
Future<bool> _onDiscoInfoRequest(Stanza stanza) async {
if (stanza.type != "get") return false;
Future<StanzaHandlerData> _onDiscoInfoRequest(Stanza stanza, StanzaHandlerData state) async {
if (stanza.type != "get") return state;
final presence = getAttributes().getManagerById(presenceManager)! as PresenceManager;
final query = stanza.firstTag("query")!;
@ -93,7 +94,7 @@ class DiscoManager extends XmppManagerBase {
)
));
return true;
return state.copyWith(done: true);
}
getAttributes().sendStanza(stanza.reply(
@ -114,11 +115,11 @@ class DiscoManager extends XmppManagerBase {
]
));
return true;
return state.copyWith(done: true);
}
Future<bool> _onDiscoItemsRequest(Stanza stanza) async {
if (stanza.type != "get") return false;
Future<StanzaHandlerData> _onDiscoItemsRequest(Stanza stanza, StanzaHandlerData state) async {
if (stanza.type != "get") return state;
final query = stanza.firstTag("query")!;
if (query.attributes["node"] != null) {
@ -152,7 +153,7 @@ class DiscoManager extends XmppManagerBase {
)
));
return true;
return state.copyWith(done: true);
}
getAttributes().sendStanza(stanza.reply(
@ -163,7 +164,7 @@ class DiscoManager extends XmppManagerBase {
)
]
));
return true;
return state.copyWith(done: true);
}
/// Sends a disco info query to the (full) jid [entity], optionally with node=[node].

View File

@ -5,6 +5,7 @@ import "package:moxxyv2/xmpp/jid.dart";
import "package:moxxyv2/xmpp/events.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
class vCardPhoto {
@ -33,8 +34,13 @@ class vCardManager extends XmppManagerBase {
String getName() => "vCardManager";
@override
List<StanzaHandler> getStanzaHandlers() => [
StanzaHandler(stanzaTag: "presence", tagName: "x", tagXmlns: vCardTempUpdate, callback: _onPresence)
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: "presence",
tagName: "x",
tagXmlns: vCardTempUpdate,
callback: _onPresence,
)
];
/// In case we get the avatar hash some other way.
@ -42,7 +48,7 @@ class vCardManager extends XmppManagerBase {
_lastHash[jid] = hash;
}
Future<bool> _onPresence(Stanza presence) async {
Future<StanzaHandlerData> _onPresence(Stanza presence, StanzaHandlerData state) async {
final x = presence.firstTag("x", xmlns: vCardTempUpdate)!;
final hash = x.firstTag("photo")!.innerText();
@ -64,7 +70,7 @@ class vCardManager extends XmppManagerBase {
}
}
return true;
return state.copyWith(done: true);
}
vCardPhoto? _parseVCardPhoto(XMLNode? node) {

View File

@ -1,10 +1,11 @@
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/namespaces.dart";
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/events.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
class PubSubItem {
final String id;
@ -25,11 +26,16 @@ class PubSubManager extends XmppManagerBase {
String getName() => "pubsubManager";
@override
List<StanzaHandler> getStanzaHandlers() => [
StanzaHandler(stanzaTag: "message", tagName: "event", tagXmlns: pubsubEventXmlns, callback: _onPubsubMessage)
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: "message",
tagName: "event",
tagXmlns: pubsubEventXmlns,
callback: _onPubsubMessage
)
];
Future<bool> _onPubsubMessage(Stanza message) async {
Future<StanzaHandlerData> _onPubsubMessage(Stanza message, StanzaHandlerData state) async {
logger.finest("Received PubSub event");
final event = message.firstTag("event", xmlns: pubsubEventXmlns)!;
final items = event.firstTag("items")!;
@ -44,7 +50,7 @@ class PubSubManager extends XmppManagerBase {
from: message.attributes["from"]!
));
return true;
return state.copyWith(done: true);
}
Future<bool> subscribe(String jid, String node) async {

View File

@ -1,3 +1,10 @@
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/namespaces.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
/// A data class representing the jabber:x:oob tag.
class OOBData {
final String? url;
@ -5,3 +12,39 @@ class OOBData {
const OOBData({ this.url, this.desc });
}
class OOBManager extends XmppManagerBase {
@override
String getName() => "OOBName";
@override
String getId() => oobManager;
@override
List<String> getDiscoFeatures() => [ oobDataXmlns ];
@override
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: "message",
tagName: "x",
tagXmlns: oobDataXmlns,
callback: _onMessage,
// Before the message manager
priority: -99
)
];
Future<StanzaHandlerData> _onMessage(Stanza message, StanzaHandlerData state) async {
final x = message.firstTag("x", xmlns: oobDataXmlns)!;
final url = x.firstTag("url");
final desc = x.firstTag("desc");
return state.copyWith(
oob: OOBData(
url: url?.innerText(),
desc: desc?.innerText()
)
);
}
}

View File

@ -1,5 +1,12 @@
import "package:moxxyv2/xmpp/events.dart";
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/namespaces.dart";
import "package:moxxyv2/xmpp/jid.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
XMLNode makeMessageDeliveryRequest() {
return XMLNode.xmlns(
@ -15,3 +22,57 @@ XMLNode makeMessageDeliveryResponse(String id) {
attributes: { "id": id }
);
}
class MessageDeliveryReceiptManager extends XmppManagerBase {
@override
List<String> getDiscoFeatures() => [ deliveryXmlns ];
@override
String getName() => "MessageDeliveryReceiptManager";
@override
String getId() => messageDeliveryReceiptManager;
@override
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: "message",
tagName: "received",
tagXmlns: deliveryXmlns,
callback: _onDeliveryReceiptReceived,
// Before the message handler
priority: -99
),
StanzaHandler(
stanzaTag: "message",
tagName: "request",
tagXmlns: deliveryXmlns,
callback: _onDeliveryRequestReceived,
// Before the message handler
priority: -99
)
];
Future<StanzaHandlerData> _onDeliveryRequestReceived(Stanza message, StanzaHandlerData state) async {
return state.copyWith(deliveryReceiptRequested: true);
}
Future<StanzaHandlerData> _onDeliveryReceiptReceived(Stanza message, StanzaHandlerData state) async {
final received = message.firstTag("received", xmlns: deliveryXmlns)!;
for (final item in message.children) {
if (!["origin-id", "stanza-id", "delay"].contains(item.tag)) {
logger.info("Won't handle stanza as delivery receipt because we found an '${item.tag}' element");
return state;
}
}
getAttributes().sendEvent(
DeliveryReceiptReceivedEvent(
from: JID.fromString(message.attributes["from"]!),
id: received.attributes["id"]!
)
);
return state.copyWith(done: true);
}
}

View File

@ -6,6 +6,7 @@ import "package:moxxyv2/xmpp/namespaces.dart";
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/xeps/xep_0198/state.dart";
import "package:moxxyv2/xmpp/xeps/xep_0198/nonzas.dart";
@ -123,17 +124,22 @@ class StreamManagementManager extends XmppManagerBase {
];
@override
List<StanzaHandler> getStanzaHandlers() => [
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
callback: _serverStanzaReceived
callback: _onServerStanzaReceived
)
];
@override
List<StanzaHandler> getOutgoingStanzaHandlers() => [
StanzaHandler(
callback: _onClientStanzaSent
)
];
@override
void onXmppEvent(XmppEvent event) {
if (event is StanzaSentEvent) {
_onClientStanzaSent(event.stanza);
} else if (event is SendPingEvent) {
if (event is SendPingEvent) {
if (isStreamManagementEnabled()) {
_sendAckRequestPing();
} else {
@ -220,13 +226,13 @@ class StreamManagementManager extends XmppManagerBase {
}
/// Called whenever we receive a stanza from the server.
Future<bool> _serverStanzaReceived(stanza) async {
Future<StanzaHandlerData> _onServerStanzaReceived(Stanza stanza, StanzaHandlerData state) async {
_incrementS2C();
return false;
return state;
}
/// Called whenever we send a stanza.
void _onClientStanzaSent(Stanza stanza) {
Future<StanzaHandlerData> _onClientStanzaSent(Stanza stanza, StanzaHandlerData state) async {
_startTimer();
_incrementC2S();
@ -237,6 +243,8 @@ class StreamManagementManager extends XmppManagerBase {
if (isStreamManagementEnabled()) {
getAttributes().sendNonza(StreamManagementRequestNonza());
}
return state;
}
/// Removes all stanzas in the unacked queue that have a sequence number less-than or

View File

@ -1,10 +1,14 @@
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/events.dart";
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/namespaces.dart";
import "package:moxxyv2/xmpp/jid.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/xeps/xep_0297.dart";
class CarbonsManager extends XmppManagerBase {
bool _isEnabled;
@ -17,6 +21,32 @@ class CarbonsManager extends XmppManagerBase {
@override
String getName() => "CarbonsManager";
@override
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: "message",
tagName: "received",
tagXmlns: carbonsXmlns,
callback: _onMessage,
// Before all managers the message manager depends on
priority: -98
)
];
Future<StanzaHandlerData> _onMessage(Stanza message, StanzaHandlerData state) async {
final from = JID.fromString(message.attributes["from"]!);
final received = message.firstTag("received", xmlns: carbonsXmlns)!;
if (!isCarbonValid(from)) return state.copyWith(done: true);
final forwarded = received.firstTag("forwarded", xmlns: forwardedXmlns)!;
final carbon = unpackForwarded(forwarded);
return state.copyWith(
isCarbon: true,
stanza: carbon
);
}
Future<bool> enableCarbons() async {
final result = await getAttributes().sendStanza(
Stanza.iq(

View File

@ -1,5 +1,12 @@
import "package:moxxyv2/xmpp/events.dart";
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/namespaces.dart";
import "package:moxxyv2/xmpp/jid.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
XMLNode makeChatMarkerMarkable() {
return XMLNode.xmlns(
@ -16,3 +23,44 @@ XMLNode makeChatMarker(String tag, String id) {
attributes: { "id": id }
);
}
class ChatMarkerManager extends XmppManagerBase {
@override
String getName() => "ChatMarkerManager";
@override
String getId() => chatMarkerManager;
@override
List<String> getDiscoFeatures() => [ chatMarkersXmlns ];
@override
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: "message",
tagXmlns: chatMarkersXmlns,
callback: _onMessage,
// Before the message handler
priority: -99,
)
];
Future<StanzaHandlerData> _onMessage(Stanza message, StanzaHandlerData state) async {
final marker = message.firstTagByXmlns(chatMarkersXmlns)!;
// Handle the <markable /> explicitly
if (marker.tag == "markable") return state.copyWith(isMarkable: true);
if (!["received", "displayed", "acknowledged"].contains(marker.tag)) {
logger.warning("Unknown message marker '${marker.tag}' found.");
} else {
getAttributes().sendEvent(ChatMarkerEvent(
from: JID.fromString(message.from!),
type: marker.tag,
id: marker.attributes["id"]!,
));
}
return state.copyWith(done: true);
}
}

View File

@ -1,5 +1,12 @@
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/jid.dart";
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/namespaces.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/xeps/xep_0030/cachemanager.dart";
/// Represents data provided by XEP-0359.
/// NOTE: [StableStanzaId.stanzaId] must not be confused with the actual id attribute of
@ -19,3 +26,66 @@ XMLNode makeOriginIdElement(String id) {
attributes: { "id": id }
);
}
class StableIdManager extends XmppManagerBase {
@override
String getName() => "StableIdManager";
@override
String getId() => stableIdManager;
@override
List<String> getDiscoFeatures() => [ stableIdXmlns ];
@override
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: "message",
callback: _onMessage,
// Before the MessageManager
priority: -99
)
];
Future<StanzaHandlerData> _onMessage(Stanza message, StanzaHandlerData state) async {
final from = JID.fromString(message.attributes["from"]!);
String? originId;
String? stanzaId;
String? stanzaIdBy;
final originIdTag = message.firstTag("origin-id", xmlns: stableIdXmlns);
final stanzaIdTag = message.firstTag("stanza-id", xmlns: stableIdXmlns);
if (originIdTag != null || stanzaIdTag != null) {
logger.finest("Found Unique and Stable Stanza Id tag");
final attrs = getAttributes();
final cache = attrs.getManagerById(discoCacheManager) as DiscoCacheManager?;
if (cache != null) {
final info = await cache.getInfoByJid(from.toString());
if (info != null) {
logger.finest("Got info for ${from.toString()}");
if (info.features.contains(stableIdXmlns)) {
logger.finest("${from.toString()} supports $stableIdXmlns.");
if (originIdTag != null) {
originId = originIdTag.attributes["id"]!;
}
if (stanzaIdTag != null) {
stanzaId = stanzaIdTag.attributes["id"]!;
stanzaIdBy = stanzaIdTag.attributes["by"]!;
}
} else {
logger.finest("${from.toString()} does not support $stableIdXmlns. Ignoring... ");
}
}
}
}
return state.copyWith(
stableId: StableStanzaId(
originId: originId,
stanzaId: stanzaId,
stanzaIdBy: stanzaIdBy
)
);
}
}

View File

@ -1,5 +1,10 @@
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/namespaces.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/xeps/staging/file_thumbnails.dart";
class StatelessMediaSharingData {
@ -53,3 +58,36 @@ StatelessMediaSharingData parseSIMSElement(XMLNode node) {
thumbnails: thumbnails
);
}
class SIMSManager extends XmppManagerBase {
@override
String getName() => "SIMSManager";
@override
String getId() => simsManager;
@override
List<String> getDiscoFeatures() => [ simsXmlns ];
@override
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: "message",
callback: _onMessage,
tagName: "reference",
tagXmlns: referenceXmlns,
// Before the message handler
priority: -99
)
];
Future<StanzaHandlerData> _onMessage(Stanza message, StanzaHandlerData state) async {
final references = message.findTags("reference", xmlns: referenceXmlns);
for (final ref in references) {
final sims = ref.firstTag("media-sharing", xmlns: simsXmlns);
if (sims != null) return state.copyWith(sims: parseSIMSElement(sims));
}
return state;
}
}

View File

@ -1,5 +1,10 @@
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/namespaces.dart";
import "package:moxxyv2/xmpp/managers/base.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/managers/namespaces.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/xeps/xep_0446.dart";
class StatelessFileSharingData {
@ -23,3 +28,31 @@ StatelessFileSharingData parseSFSElement(XMLNode node) {
url: url
);
}
class SFSManager extends XmppManagerBase {
@override
String getName() => "SFSManager";
@override
String getId() => sfsManager;
@override
List<StanzaHandler> getIncomingStanzaHandlers() => [
StanzaHandler(
stanzaTag: "message",
tagName: "file-sharing",
tagXmlns: sfsXmlns,
callback: _onMessage,
// Before the message handler
priority: -99,
)
];
Future<StanzaHandlerData> _onMessage(Stanza message, StanzaHandlerData state) async {
final sfs = message.firstTag("file-sharing", xmlns: sfsXmlns)!;
return state.copyWith(
sfs: parseSFSElement(sfs)
);
}
}

View File

@ -1,8 +1,9 @@
import "package:test/test.dart";
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
final stanza1 = Stanza.iq(children: [
XMLNode.xmlns(tag: "tag", xmlns: "owo")
@ -13,7 +14,7 @@ final stanza2 = Stanza.message(children: [
void main() {
test("match all", () {
final handler = StanzaHandler(callback: (_) async => true);
final handler = StanzaHandler(callback: (stanza, _) async => StanzaHandlerData(true,stanza));
expect(handler.matches(Stanza.iq()), true);
expect(handler.matches(Stanza.message()), true);
@ -22,7 +23,10 @@ void main() {
expect(handler.matches(stanza2), true);
});
test("xmlns matching", () {
final handler = StanzaHandler(callback: (_) async => true, tagXmlns: "owo");
final handler = StanzaHandler(
callback: (stanza, _) async => StanzaHandlerData(true, stanza),
tagXmlns: "owo"
);
expect(handler.matches(Stanza.iq()), false);
expect(handler.matches(Stanza.message()), false);
@ -32,9 +36,9 @@ void main() {
});
test("stanzaTag matching", () {
bool run = false;
final handler = StanzaHandler(callback: (_) async {
final handler = StanzaHandler(callback: (stanza, _) async {
run = true;
return true;
return StanzaHandlerData(true, stanza);
}, stanzaTag: "iq");
expect(handler.matches(Stanza.iq()), true);
@ -43,11 +47,14 @@ void main() {
expect(handler.matches(stanza1), true);
expect(handler.matches(stanza2), false);
handler.callback(stanza2);
handler.callback(stanza2, StanzaHandlerData(false, stanza2));
expect(run, true);
});
test("tagName matching", () {
final handler = StanzaHandler(callback: (_) async => true, tagName: "tag");
final handler = StanzaHandler(
callback: (stanza, _) async => StanzaHandlerData(true, stanza),
tagName: "tag"
);
expect(handler.matches(Stanza.iq()), false);
expect(handler.matches(Stanza.message()), false);
@ -56,7 +63,12 @@ void main() {
expect(handler.matches(stanza2), false);
});
test("combined matching", () {
final handler = StanzaHandler(callback: (_) async => true, tagName: "tag", stanzaTag: "iq", tagXmlns: "owo");
final handler = StanzaHandler(
callback: (stanza, _) async => StanzaHandlerData(true, stanza),
tagName: "tag",
stanzaTag: "iq",
tagXmlns: "owo"
);
expect(handler.matches(Stanza.iq()), false);
expect(handler.matches(Stanza.message()), false);
@ -64,4 +76,18 @@ void main() {
expect(handler.matches(stanza1), true);
expect(handler.matches(stanza2), false);
});
test("sorting", () {
final handlerList = [
StanzaHandler(callback: (stanza, _) async => StanzaHandlerData(true, stanza), tagName: "1", priority: 100),
StanzaHandler(callback: (stanza, _) async => StanzaHandlerData(true, stanza), tagName: "2"),
StanzaHandler(callback: (stanza, _) async => StanzaHandlerData(true, stanza), tagName: "3", priority: 50)
];
handlerList.sort(stanzaHandlerSortComparator);
expect(handlerList[0].tagName, "1");
expect(handlerList[1].tagName, "3");
expect(handlerList[2].tagName, "2");
});
}

View File

@ -1,9 +1,10 @@
import "package:moxxyv2/xmpp/managers/attributes.dart";
import "package:moxxyv2/xmpp/stringxml.dart";
import "package:moxxyv2/xmpp/events.dart";
import "package:moxxyv2/xmpp/stanza.dart";
import "package:moxxyv2/xmpp/settings.dart";
import "package:moxxyv2/xmpp/jid.dart";
import "package:moxxyv2/xmpp/managers/attributes.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/xeps/xep_0198/xep_0198.dart";
import "package:moxxyv2/xmpp/xeps/xep_0198/state.dart";
@ -11,6 +12,18 @@ import "../helpers/xml.dart";
import "package:test/test.dart";
Future<void> runIncomingStanzaHandlers(StreamManagementManager man, Stanza stanza) async {
for (final handler in man.getIncomingStanzaHandlers()) {
if (handler.matches(stanza)) await handler.callback(stanza, StanzaHandlerData(false, stanza));
}
}
Future<void> runOutgoingStanzaHandlers(StreamManagementManager man, Stanza stanza) async {
for (final handler in man.getOutgoingStanzaHandlers()) {
if (handler.matches(stanza)) await handler.callback(stanza, StanzaHandlerData(false, stanza));
}
}
void main() {
final stanza = Stanza(
to: "some.user@server.example",
@ -46,14 +59,14 @@ void main() {
manager.onXmppEvent(StreamManagementEnabledEvent(id: "0", resource: "h"));
// Receive a fake stanza
await manager.runStanzaHandlers(stanza);
await manager.runStanzaHandlers(stanza);
await runIncomingStanzaHandlers(manager, stanza);
await runIncomingStanzaHandlers(manager, stanza);
expect(manager.state.s2c, 2, reason: "The S2C counter must count correctly");
// Send some fake stanzas
manager.onXmppEvent(StanzaSentEvent(stanza: stanza));
manager.onXmppEvent(StanzaSentEvent(stanza: stanza));
manager.onXmppEvent(StanzaSentEvent(stanza: stanza));
runOutgoingStanzaHandlers(manager, stanza);
runOutgoingStanzaHandlers(manager, stanza);
runOutgoingStanzaHandlers(manager, stanza);
expect(manager.state.c2s, 3, reason: "The C2S counter must count correctly");
final ack = XMLNode.xmlns(tag: "a", xmlns: "urn:xmpp:sm:3", attributes: { "h": "3" });
@ -63,7 +76,7 @@ void main() {
expect(manager.state.s2c, 2, reason: "Sending stanzas must not change the S2C counter");
// Send a stanza which we will not acknowledge
manager.onXmppEvent(StanzaSentEvent(stanza: stanza));
runOutgoingStanzaHandlers(manager, stanza);
expect(manager.state.c2s, 4, reason: "Sending a stanza must increment the C2S counter");
await manager.runNonzaHandlers(ack);
manager.onTimerElapsed(null, ignoreTimestamps: true);
@ -101,9 +114,9 @@ void main() {
manager.register(attributes);
// Send some stanzas
manager.onXmppEvent(StanzaSentEvent(stanza: stanza));
manager.onXmppEvent(StanzaSentEvent(stanza: stanza));
manager.onXmppEvent(StanzaSentEvent(stanza: stanza));
runOutgoingStanzaHandlers(manager, stanza);
runOutgoingStanzaHandlers(manager, stanza);
runOutgoingStanzaHandlers(manager, stanza);
// Simulate a resumption
manager.onXmppEvent(StreamResumedEvent(h: 2));

View File

@ -9,8 +9,9 @@ import "package:moxxyv2/xmpp/presence.dart";
import "package:moxxyv2/xmpp/roster.dart";
import "package:moxxyv2/xmpp/events.dart";
import "package:moxxyv2/xmpp/managers/attributes.dart";
import "package:moxxyv2/xmpp/managers/handlers.dart";
import "package:moxxyv2/xmpp/managers/data.dart";
import "package:moxxyv2/xmpp/xeps/xep_0030/xep_0030.dart";
import "package:moxxyv2/xmpp/xeps/xep_0030/cachemanager.dart";
import "helpers/xmpp.dart";
@ -40,14 +41,9 @@ Future<bool> testRosterManager(String bareJid, String resource, String stanzaStr
));
final stanza = Stanza.fromXMLNode(XMLNode.fromString(stanzaString));
await Future.forEach(
roster.getStanzaHandlers(),
(StanzaHandler handler) async {
if (handler.matches(stanza)) {
await handler.callback(stanza);
}
}
);
for (final handler in roster.getIncomingStanzaHandlers()) {
if (handler.matches(stanza)) await handler.callback(stanza, StanzaHandlerData(false, stanza));
}
return eventTriggered;
}
@ -216,6 +212,7 @@ void main() {
),
]
);
// TODO: This test is broken since we query the server and enable carbons
final XmppConnection conn = XmppConnection(socket: fakeSocket);
conn.setConnectionSettings(ConnectionSettings(
jid: JID.fromString("polynomdivision@test.server"),
@ -225,6 +222,7 @@ void main() {
));
conn.registerManager(RosterManager());
conn.registerManager(DiscoManager());
conn.registerManager(DiscoCacheManager());
conn.registerManager(PresenceManager());
await conn.connect();
@ -509,14 +507,10 @@ void main() {
// NOTE: Based on https://gultsch.de/gajim_roster_push_and_message_interception.html
// NOTE: Added a from attribute as a server would add it itself.
final maliciousStanza = Stanza.fromXMLNode(XMLNode.fromString("<iq type=\"set\" from=\"eve@siacs.eu/bbbbb\" to=\"some.user@example.server/aaaaa\"><query xmlns='jabber:iq:roster'><item subscription=\"both\" jid=\"eve@siacs.eu\" name=\"Bob\" /></query></iq>"));
await Future.forEach(
roster.getStanzaHandlers(),
(StanzaHandler handler) async {
if (handler.matches(maliciousStanza)) {
await handler.callback(maliciousStanza);
}
}
);
for (final handler in roster.getIncomingStanzaHandlers()) {
if (handler.matches(maliciousStanza)) await handler.callback(maliciousStanza, StanzaHandlerData(false, maliciousStanza));
}
expect(eventTriggered, false, reason: "Was able to inject a malicious roster push");
});