From a7818bbd0f3e99f34f5c133367eab91e20a467c8 Mon Sep 17 00:00:00 2001 From: Lukas Lihotzki Date: Thu, 23 Sep 2021 18:24:43 +0200 Subject: [PATCH] refactor: null safety for all utils --- lib/encryption/key_manager.dart | 24 ++++++++------- lib/encryption/olm_manager.dart | 5 ++-- lib/encryption/ssss.dart | 5 ++-- lib/src/utils/crypto/ffi.dart | 1 - lib/src/utils/event_localizations.dart | 29 ++++++++---------- lib/src/utils/html_to_text.dart | 30 +++++++++---------- lib/src/utils/image_pack_extension.dart | 28 +++++++++--------- lib/src/utils/map_copy_extension.dart | 1 - lib/src/utils/markdown.dart | 39 ++++++++++++------------- lib/src/utils/to_device_event.dart | 28 +++++++----------- 10 files changed, 89 insertions(+), 101 deletions(-) diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index b9497e67..3a5a69e8 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -864,7 +864,8 @@ class KeyManager { } } else if (event.type == EventTypes.ForwardedRoomKey) { // we *received* an incoming key request - if (event.encryptedContent == null) { + final encryptedContent = event.encryptedContent; + if (encryptedContent == null) { return; // event wasn't encrypted, this is a security risk } final request = outgoingShareRequests.values.firstWhereOrNull((r) => @@ -876,7 +877,7 @@ class KeyManager { } final device = request.devices.firstWhereOrNull((d) => d.userId == event.sender && - d.curve25519Key == event.encryptedContent['sender_key']); + d.curve25519Key == encryptedContent['sender_key']); if (device == null) { return; // someone we didn't send our request to replied....better ignore this } @@ -885,7 +886,7 @@ class KeyManager { event.content['forwarding_curve25519_key_chain'] = []; } event.content['forwarding_curve25519_key_chain'] - .add(event.encryptedContent['sender_key']); + .add(encryptedContent['sender_key']); // TODO: verify that the keys work to decrypt a message // alright, all checks out, let's go ahead and store this session setInboundGroupSession( @@ -920,7 +921,8 @@ class KeyManager { } else if (event.type == EventTypes.RoomKey) { Logs().v( '[KeyManager] Received room key with session ${event.content['session_id']}'); - if (event.encryptedContent == null) { + final encryptedContent = event.encryptedContent; + if (encryptedContent == null) { Logs().v('[KeyManager] not encrypted, ignoring...'); return; // the event wasn't encrypted, this is a security risk; } @@ -932,8 +934,8 @@ class KeyManager { event.content['sender_claimed_ed25519_key'] = sender_ed25519; } Logs().v('[KeyManager] Keeping room key'); - setInboundGroupSession(roomId, sessionId, - event.encryptedContent['sender_key'], event.content, + setInboundGroupSession( + roomId, sessionId, encryptedContent['sender_key'], event.content, forwarded: false); } } @@ -972,11 +974,11 @@ class RoomKeyRequest extends ToDeviceEvent { KeyManager keyManager; KeyManagerKeyShareRequest request; RoomKeyRequest.fromToDeviceEvent( - ToDeviceEvent toDeviceEvent, this.keyManager, this.request) { - sender = toDeviceEvent.sender; - content = toDeviceEvent.content; - type = toDeviceEvent.type; - } + ToDeviceEvent toDeviceEvent, this.keyManager, this.request) + : super( + sender: toDeviceEvent.sender, + content: toDeviceEvent.content, + type: toDeviceEvent.type); Room get room => request.room; diff --git a/lib/encryption/olm_manager.dart b/lib/encryption/olm_manager.dart index 711c9d64..fbfc40fc 100644 --- a/lib/encryption/olm_manager.dart +++ b/lib/encryption/olm_manager.dart @@ -653,11 +653,12 @@ class OlmManager { if (event.type == EventTypes.Dummy) { // We receive dan encrypted m.dummy. This means that the other end was not able to // decrypt our last message. So, we re-send it. - if (event.encryptedContent == null || client.database == null) { + final encryptedContent = event.encryptedContent; + if (encryptedContent == null || client.database == null) { return; } final device = client.getUserDeviceKeysByCurve25519Key( - event.encryptedContent.tryGet('sender_key') ?? ''); + encryptedContent.tryGet('sender_key') ?? ''); if (device == null) { return; // device not found } diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index 79c1309d..e741dbb2 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -509,9 +509,10 @@ class SSSS { } else if (event.type == EventTypes.SecretSend) { // receiving a secret we asked for Logs().i('[SSSS] Received shared secret...'); + final encryptedContent = event.encryptedContent; if (event.sender != client.userID || !pendingShareRequests.containsKey(event.content['request_id']) || - event.encryptedContent == null) { + encryptedContent == null) { Logs().i('[SSSS] Not by us or unknown request'); return; // we have no idea what we just received } @@ -519,7 +520,7 @@ class SSSS { // alright, as we received a known request id, let's check if the sender is valid final device = request.devices.firstWhereOrNull((d) => d.userId == event.sender && - d.curve25519Key == event.encryptedContent['sender_key']); + d.curve25519Key == encryptedContent['sender_key']); if (device == null) { Logs().i('[SSSS] Someone else replied?'); return; // someone replied whom we didn't send the share request to diff --git a/lib/src/utils/crypto/ffi.dart b/lib/src/utils/crypto/ffi.dart index e5f787de..0cb79266 100644 --- a/lib/src/utils/crypto/ffi.dart +++ b/lib/src/utils/crypto/ffi.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020, 2021 Famedly GmbH diff --git a/lib/src/utils/event_localizations.dart b/lib/src/utils/event_localizations.dart index 2a9ec34b..f33c5c05 100644 --- a/lib/src/utils/event_localizations.dart +++ b/lib/src/utils/event_localizations.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2019, 2020, 2021 Famedly GmbH @@ -17,6 +16,8 @@ * along with this program. If not, see . */ +import 'package:collection/collection.dart'; + import '../../encryption.dart'; import '../../matrix.dart'; import '../event.dart'; @@ -77,7 +78,7 @@ abstract class EventLocalizations { // This map holds how to localize event types, and thus which event types exist. // If an event exists but it does not have a localized body, set its callback to null static final Map + String Function(Event event, MatrixLocalizations i18n, String body)?> localizationsMap = { EventTypes.Sticker: (event, i18n, body) => i18n.sentASticker(event.sender.calcDisplayname()), @@ -91,11 +92,9 @@ abstract class EventLocalizations { i18n.createdTheChat(event.sender.calcDisplayname()), EventTypes.RoomTombstone: (event, i18n, body) => i18n.roomHasBeenUpgraded, EventTypes.RoomJoinRules: (event, i18n, body) { - final joinRules = JoinRules.values.firstWhere( - (r) => - r.toString().replaceAll('JoinRules.', '') == - event.content['join_rule'], - orElse: () => null); + final joinRules = JoinRules.values.firstWhereOrNull((r) => + r.toString().replaceAll('JoinRules.', '') == + event.content['join_rule']); if (joinRules == null) { return i18n.changedTheJoinRules(event.sender.calcDisplayname()); } else { @@ -177,11 +176,9 @@ abstract class EventLocalizations { EventTypes.RoomAvatar: (event, i18n, body) => i18n.changedTheChatAvatar(event.sender.calcDisplayname()), EventTypes.GuestAccess: (event, i18n, body) { - final guestAccess = GuestAccess.values.firstWhere( - (r) => - r.toString().replaceAll('GuestAccess.', '') == - event.content['guest_access'], - orElse: () => null); + final guestAccess = GuestAccess.values.firstWhereOrNull((r) => + r.toString().replaceAll('GuestAccess.', '') == + event.content['guest_access']); if (guestAccess == null) { return i18n.changedTheGuestAccessRules(event.sender.calcDisplayname()); } else { @@ -190,11 +187,9 @@ abstract class EventLocalizations { } }, EventTypes.HistoryVisibility: (event, i18n, body) { - final historyVisibility = HistoryVisibility.values.firstWhere( - (r) => - r.toString().replaceAll('HistoryVisibility.', '') == - event.content['history_visibility'], - orElse: () => null); + final historyVisibility = HistoryVisibility.values.firstWhereOrNull((r) => + r.toString().replaceAll('HistoryVisibility.', '') == + event.content['history_visibility']); if (historyVisibility == null) { return i18n.changedTheHistoryVisibility(event.sender.calcDisplayname()); } else { diff --git a/lib/src/utils/html_to_text.dart b/lib/src/utils/html_to_text.dart index dcdabdcc..2abbf0a4 100644 --- a/lib/src/utils/html_to_text.dart +++ b/lib/src/utils/html_to_text.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2021 Famedly GmbH @@ -17,6 +16,8 @@ * along with this program. If not, see . */ +import 'package:collection/collection.dart'; + import 'package:html/parser.dart'; import 'package:html/dom.dart'; import 'package:html_unescape/html_unescape.dart'; @@ -71,9 +72,9 @@ class HtmlToText { } final language = RegExp(r'language-(\w+)', multiLine: false, caseSensitive: false) - .firstMatch(match.group(1)); + .firstMatch(match.group(1)!); if (language != null) { - text = language.group(1) + text; + text = language.group(1)! + text; } return text; } @@ -116,17 +117,16 @@ class HtmlToText { opts.listDepth++; final entries = _listChildNodes(opts, node, {'li'}); opts.listDepth--; - var entry = 1; - if (node.attributes['start'] is String && - RegExp(r'^[0-9]+$', multiLine: false) - .hasMatch(node.attributes['start'])) { - entry = int.parse(node.attributes['start']); - } + final startStr = node.attributes['start']; + final start = (startStr is String && + RegExp(r'^[0-9]+$', multiLine: false).hasMatch(startStr)) + ? int.parse(startStr) + : 1; return entries - .map((s) => + .mapIndexed((index, s) => (' ' * opts.listDepth) + - '${entry++}. ' + + '${start + index}. ' + s.replaceAll('\n', '\n' + (' ' * opts.listDepth) + ' ')) .join('\n'); } @@ -134,14 +134,14 @@ class HtmlToText { static const _listBulletPoints = ['●', '○', '■', '‣']; static List _listChildNodes(_ConvertOpts opts, Element node, - [Iterable types]) { + [Iterable? types]) { final replies = []; for (final child in node.nodes) { if (types != null && types.isNotEmpty && ((child is Text) || ((child is Element) && - !types.contains(child.localName.toLowerCase())))) { + !types.contains(child.localName!.toLowerCase())))) { continue; } replies.add(_walkNode(opts, child)); @@ -166,7 +166,7 @@ class HtmlToText { var reply = ''; var lastTag = ''; for (final child in node.nodes) { - final thisTag = child is Element ? child.localName.toLowerCase() : ''; + final thisTag = child is Element ? child.localName!.toLowerCase() : ''; if (thisTag == 'p' && lastTag == 'p') { reply += '\n\n'; } else if (_blockTags.contains(thisTag) && @@ -187,7 +187,7 @@ class HtmlToText { // ignore \n between single nodes return node.text == '\n' ? '' : node.text; } else if (node is Element) { - final tag = node.localName.toLowerCase(); + final tag = node.localName!.toLowerCase(); switch (tag) { case 'em': case 'i': diff --git a/lib/src/utils/image_pack_extension.dart b/lib/src/utils/image_pack_extension.dart index 19284c10..03dc544e 100644 --- a/lib/src/utils/image_pack_extension.dart +++ b/lib/src/utils/image_pack_extension.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020, 2021 Famedly GmbH @@ -24,10 +23,10 @@ import '../room.dart'; extension ImagePackRoomExtension on Room { /// Get all the active image packs for the specified [usage], mapped by their slug - Map getImagePacks([ImagePackUsage usage]) { + Map getImagePacks([ImagePackUsage? usage]) { final allMxcs = {}; // used for easy deduplication final packs = {}; - final addImagePack = (BasicEvent event, {Room room, String slug}) { + final addImagePack = (BasicEvent? event, {Room? room, String? slug}) { if (event == null) return; final imagePack = event.parsedImagePackContent; final finalSlug = slugify(slug ?? 'pack'); @@ -42,17 +41,16 @@ extension ImagePackRoomExtension on Room { !imageUsage.contains(usage)) { continue; } - if (!packs.containsKey(finalSlug)) { - packs[finalSlug] = ImagePackContent.fromJson({}); - packs[finalSlug].pack.displayName = imagePack.pack.displayName ?? - room?.displayname ?? - finalSlug ?? - ''; - packs[finalSlug].pack.avatarUrl = - imagePack.pack.avatarUrl ?? room?.avatar; - packs[finalSlug].pack.attribution = imagePack.pack.attribution; - } - packs[finalSlug].images[entry.key] = image; + packs + .putIfAbsent( + finalSlug, + () => ImagePackContent.fromJson({}) + ..pack.displayName = imagePack.pack.displayName ?? + room?.displayname ?? + finalSlug + ..pack.avatarUrl = imagePack.pack.avatarUrl ?? room?.avatar + ..pack.attribution = imagePack.pack.attribution) + .images[entry.key] = image; allMxcs.add(image.url); } }; @@ -89,7 +87,7 @@ extension ImagePackRoomExtension on Room { /// Get a flat view of all the image packs of a specified [usage], that is a map of all /// slugs to a map of the image code to their mxc url - Map> getImagePacksFlat([ImagePackUsage usage]) => + Map> getImagePacksFlat([ImagePackUsage? usage]) => getImagePacks(usage).map((k, v) => MapEntry(k, v.images.map((k, v) => MapEntry(k, v.url.toString())))); } diff --git a/lib/src/utils/map_copy_extension.dart b/lib/src/utils/map_copy_extension.dart index d57c625a..520294f6 100644 --- a/lib/src/utils/map_copy_extension.dart +++ b/lib/src/utils/map_copy_extension.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020, 2021 Famedly GmbH diff --git a/lib/src/utils/markdown.dart b/lib/src/utils/markdown.dart index 10389b70..857cd3f4 100644 --- a/lib/src/utils/markdown.dart +++ b/lib/src/utils/markdown.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020, 2021 Famedly GmbH @@ -37,7 +36,7 @@ class SpoilerSyntax extends TagSyntax { @override Node close(InlineParser parser, Delimiter opener, Delimiter closer, - {List Function() getChildren}) { + {required List Function() getChildren}) { final children = getChildren(); final newChildren = []; var searchingForReason = true; @@ -72,16 +71,16 @@ class SpoilerSyntax extends TagSyntax { } class EmoteSyntax extends InlineSyntax { - final Map> Function() getEmotePacks; - Map> emotePacks; + final Map> Function()? getEmotePacks; + Map>? emotePacks; EmoteSyntax(this.getEmotePacks) : super(r':(?:([-\w]+)~)?([-\w]+):'); @override bool onMatch(InlineParser parser, Match match) { - emotePacks ??= getEmotePacks?.call() ?? >{}; + final emotePacks = this.emotePacks ??= getEmotePacks?.call() ?? {}; final pack = match[1] ?? ''; final emote = match[2]; - String mxc; + String? mxc; if (pack.isEmpty) { // search all packs for (final emotePack in emotePacks.values) { @@ -91,11 +90,11 @@ class EmoteSyntax extends InlineSyntax { } } } else { - mxc = emotePacks[pack] != null ? emotePacks[pack][emote] : null; + mxc = emotePacks[pack]?[emote]; } if (mxc == null) { // emote not found. Insert the whole thing as plain text - parser.addNode(Text(match[0])); + parser.addNode(Text(match[0]!)); return true; } final element = Element.empty('img'); @@ -116,8 +115,8 @@ class InlineLatexSyntax extends TagSyntax { @override bool onMatch(InlineParser parser, Match match) { final element = - Element('span', [Element.text('code', htmlEscape.convert(match[1]))]); - element.attributes['data-mx-maths'] = htmlAttrEscape.convert(match[1]); + Element('span', [Element.text('code', htmlEscape.convert(match[1]!))]); + element.attributes['data-mx-maths'] = htmlAttrEscape.convert(match[1]!); parser.addNode(element); return true; } @@ -136,11 +135,11 @@ class BlockLatexSyntax extends BlockSyntax { var first = true; while (!parser.isDone) { final match = endPattern.firstMatch(parser.current); - if (match == null || (first && match.group(1).trim().isEmpty)) { + if (match == null || (first && match[1]!.trim().isEmpty)) { childLines.add(parser.current); parser.advance(); } else { - childLines.add(match.group(1)); + childLines.add(match[1]!); parser.advance(); break; } @@ -171,10 +170,10 @@ class PillSyntax extends InlineSyntax { bool onMatch(InlineParser parser, Match match) { if (match.start > 0 && !RegExp(r'[\s.!?:;\(]').hasMatch(match.input[match.start - 1])) { - parser.addNode(Text(match[0])); + parser.addNode(Text(match[0]!)); return true; } - final identifier = match[1]; + final identifier = match[1]!; final element = Element.text('a', htmlEscape.convert(identifier)); element.attributes['href'] = htmlAttrEscape.convert('https://matrix.to/#/$identifier'); @@ -184,19 +183,19 @@ class PillSyntax extends InlineSyntax { } class MentionSyntax extends InlineSyntax { - final String Function(String) getMention; + final String Function(String)? getMention; MentionSyntax(this.getMention) : super(r'(@(?:\[[^\]:]+\]|\w+)(?:#\w+)?)'); @override bool onMatch(InlineParser parser, Match match) { - final mention = getMention?.call(match[1]); + final mention = getMention?.call(match[1]!); if ((match.start > 0 && !RegExp(r'[\s.!?:;\(]').hasMatch(match.input[match.start - 1])) || mention == null) { - parser.addNode(Text(match[0])); + parser.addNode(Text(match[0]!)); return true; } - final element = Element.text('a', htmlEscape.convert(match[1])); + final element = Element.text('a', htmlEscape.convert(match[1]!)); element.attributes['href'] = htmlAttrEscape.convert('https://matrix.to/#/$mention'); parser.addNode(element); @@ -206,8 +205,8 @@ class MentionSyntax extends InlineSyntax { String markdown( String text, { - Map> Function() getEmotePacks, - String Function(String) getMention, + Map> Function()? getEmotePacks, + String Function(String)? getMention, }) { var ret = markdownToHtml( text, diff --git a/lib/src/utils/to_device_event.dart b/lib/src/utils/to_device_event.dart index d6028312..137906a5 100644 --- a/lib/src/utils/to_device_event.dart +++ b/lib/src/utils/to_device_event.dart @@ -1,4 +1,3 @@ -// @dart=2.9 /* * Famedly Matrix SDK * Copyright (C) 2020, 2021 Famedly GmbH @@ -20,36 +19,31 @@ import '../../matrix.dart'; class ToDeviceEvent extends BasicEventWithSender { - Map encryptedContent; + Map? encryptedContent; String get sender => senderId; set sender(String sender) => senderId = sender; ToDeviceEvent({ - String sender, - String type, - Map content, + required String sender, + required String type, + required Map content, this.encryptedContent, - }) { - senderId = sender; - this.type = type; - this.content = content; - } + }) : super(senderId: sender, type: type, content: content); - ToDeviceEvent.fromJson(Map json) { + factory ToDeviceEvent.fromJson(Map json) { final event = BasicEventWithSender.fromJson(json); - senderId = event.senderId; - type = event.type; - content = event.content; + return ToDeviceEvent( + sender: event.senderId, type: event.type, content: event.content); } } class ToDeviceEventDecryptionError extends ToDeviceEvent { Exception exception; - StackTrace stackTrace; + StackTrace? stackTrace; ToDeviceEventDecryptionError({ - ToDeviceEvent toDeviceEvent, - this.exception, + required ToDeviceEvent toDeviceEvent, + required this.exception, this.stackTrace, }) : super( sender: toDeviceEvent.senderId,