diff --git a/lib/encryption/encryption.dart b/lib/encryption/encryption.dart index 9fe95ff2..d078d32c 100644 --- a/lib/encryption/encryption.dart +++ b/lib/encryption/encryption.dart @@ -314,7 +314,7 @@ class Encryption { } // we clone the payload as we do not want to remove 'm.relates_to' from the // original payload passed into this function - payload = Map.from(payload); + payload = payload.copy(); final Map mRelatesTo = payload.remove('m.relates_to'); final payloadContent = { 'content': payload, diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index 9c4a3eef..4c4191f9 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -49,10 +49,6 @@ const SSSS_KEY_LENGTH = 32; const PBKDF2_DEFAULT_ITERATIONS = 500000; const PBKDF2_SALT_LENGTH = 64; -Map _deepcopy(Map data) { - return json.decode(json.encode(data)); -} - /// SSSS: **S**ecure **S**ecret **S**torage and **S**haring /// Read more about SSSS at: /// https://matrix.org/docs/guides/implementing-more-advanced-e-2-ee-features-such-as-cross-signing#3-implementing-ssss @@ -310,7 +306,7 @@ class SSSS { final encrypted = encryptAes(secret, key, type); Map content; if (add && client.accountData[type] != null) { - content = _deepcopy(client.accountData[type].content); + content = client.accountData[type].content.copy(); if (!(content['encrypted'] is Map)) { content['encrypted'] = {}; } @@ -341,7 +337,7 @@ class SSSS { throw Exception('Secrets do not match up!'); } // now remove all other keys - final content = _deepcopy(client.accountData[type].content); + final content = client.accountData[type].content.copy(); final otherKeys = Set.from(content['encrypted'].keys.where((k) => k != keyId)); content['encrypted'].removeWhere((k, v) => otherKeys.contains(k)); diff --git a/lib/encryption/utils/session_key.dart b/lib/encryption/utils/session_key.dart index f2f18e1b..3d8e4cda 100644 --- a/lib/encryption/utils/session_key.dart +++ b/lib/encryption/utils/session_key.dart @@ -84,8 +84,7 @@ class SessionKey { Event.getMapFromPayload(dbEntry.allowedAtIndex); final parsedSenderClaimedKeys = Event.getMapFromPayload(dbEntry.senderClaimedKeys); - content = - parsedContent != null ? Map.from(parsedContent) : null; + content = parsedContent; // we need to try...catch as the map used to be and that will throw an error. try { indexes = parsedIndexes != null diff --git a/lib/matrix_api.dart b/lib/matrix_api.dart index 56ee7d16..2e13945f 100644 --- a/lib/matrix_api.dart +++ b/lib/matrix_api.dart @@ -20,6 +20,7 @@ library matrix_api; export 'matrix_api/matrix_api.dart'; export 'matrix_api/utils/logs.dart'; +export 'matrix_api/utils/map_copy_extension.dart'; export 'matrix_api/utils/try_get_map_extension.dart'; export 'matrix_api/model/algorithm_types.dart'; export 'matrix_api/model/basic_event.dart'; diff --git a/lib/matrix_api/model/basic_event.dart b/lib/matrix_api/model/basic_event.dart index 095b2b21..7a91f559 100644 --- a/lib/matrix_api/model/basic_event.dart +++ b/lib/matrix_api/model/basic_event.dart @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import '../utils/map_copy_extension.dart'; + class BasicEvent { String type; Map content; @@ -27,7 +29,7 @@ class BasicEvent { BasicEvent.fromJson(Map json) { type = json['type']; - content = Map.from(json['content']); + content = (json['content'] as Map).copy(); } Map toJson() { final data = {}; diff --git a/lib/matrix_api/model/keys_query_response.dart b/lib/matrix_api/model/keys_query_response.dart index b368b4f1..ed02e87d 100644 --- a/lib/matrix_api/model/keys_query_response.dart +++ b/lib/matrix_api/model/keys_query_response.dart @@ -17,6 +17,7 @@ */ import 'matrix_keys.dart'; +import '../utils/map_copy_extension.dart'; class KeysQueryResponse { Map failures; @@ -26,9 +27,7 @@ class KeysQueryResponse { Map userSigningKeys; KeysQueryResponse.fromJson(Map json) { - failures = json['failures'] != null - ? Map.from(json['failures']) - : null; + failures = (json['failures'] as Map)?.copy(); deviceKeys = json['device_keys'] != null ? (json['device_keys'] as Map).map( (k, v) => MapEntry( diff --git a/lib/matrix_api/model/matrix_event.dart b/lib/matrix_api/model/matrix_event.dart index 2f5f35f0..83fe7596 100644 --- a/lib/matrix_api/model/matrix_event.dart +++ b/lib/matrix_api/model/matrix_event.dart @@ -17,6 +17,7 @@ */ import 'stripped_state_event.dart'; +import '../utils/map_copy_extension.dart'; class MatrixEvent extends StrippedStateEvent { String eventId; @@ -38,12 +39,8 @@ class MatrixEvent extends StrippedStateEvent { roomId = json['room_id']; originServerTs = DateTime.fromMillisecondsSinceEpoch(json['origin_server_ts']); - unsigned = json['unsigned'] != null - ? Map.from(json['unsigned']) - : null; - prevContent = json['prev_content'] != null - ? Map.from(json['prev_content']) - : null; + unsigned = (json['unsigned'] as Map)?.copy(); + prevContent = (json['prev_content'] as Map)?.copy(); redacts = json['redacts']; } diff --git a/lib/matrix_api/model/matrix_keys.dart b/lib/matrix_api/model/matrix_keys.dart index c0f00386..317ef390 100644 --- a/lib/matrix_api/model/matrix_keys.dart +++ b/lib/matrix_api/model/matrix_keys.dart @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import '../utils/map_copy_extension.dart'; + class MatrixSignableKey { String userId; String identifier; @@ -33,13 +35,12 @@ class MatrixSignableKey { _json = json; userId = json['user_id']; keys = Map.from(json['keys']); + // we need to manually copy to ensure that our map is Map> signatures = json['signatures'] is Map ? Map>.from((json['signatures'] as Map) .map((k, v) => MapEntry(k, Map.from(v)))) : null; - unsigned = json['unsigned'] is Map - ? Map.from(json['unsigned']) - : null; + unsigned = (json['unsigned'] as Map)?.copy(); } Map toJson() { diff --git a/lib/matrix_api/model/one_time_keys_claim_response.dart b/lib/matrix_api/model/one_time_keys_claim_response.dart index 7834211c..7607892b 100644 --- a/lib/matrix_api/model/one_time_keys_claim_response.dart +++ b/lib/matrix_api/model/one_time_keys_claim_response.dart @@ -16,13 +16,17 @@ * along with this program. If not, see . */ +import '../utils/map_copy_extension.dart'; + class OneTimeKeysClaimResponse { Map failures; Map> oneTimeKeys; OneTimeKeysClaimResponse.fromJson(Map json) { - failures = Map.from(json['failures'] ?? {}); - oneTimeKeys = Map>.from(json['one_time_keys']); + failures = (json['failures'] as Map)?.copy() ?? {}; + // We still need a Map<...>.from(...) to ensure all second-level entries are also maps + oneTimeKeys = Map>.from( + (json['one_time_keys'] as Map).copy()); } Map toJson() { diff --git a/lib/matrix_api/model/server_capabilities.dart b/lib/matrix_api/model/server_capabilities.dart index 0e449e9d..0ae7c1ee 100644 --- a/lib/matrix_api/model/server_capabilities.dart +++ b/lib/matrix_api/model/server_capabilities.dart @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import '../utils/map_copy_extension.dart'; + enum RoomVersionStability { stable, unstable } class ServerCapabilities { @@ -30,7 +32,7 @@ class ServerCapabilities { mRoomVersions = json['m.room_versions'] != null ? MRoomVersions.fromJson(json['m.room_versions']) : null; - customCapabilities = Map.from(json); + customCapabilities = json.copy(); customCapabilities.remove('m.change_password'); customCapabilities.remove('m.room_versions'); } diff --git a/lib/matrix_api/model/third_party_location.dart b/lib/matrix_api/model/third_party_location.dart index 68083805..39546099 100644 --- a/lib/matrix_api/model/third_party_location.dart +++ b/lib/matrix_api/model/third_party_location.dart @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import '../utils/map_copy_extension.dart'; + class ThirdPartyLocation { String alias; String protocol; @@ -24,7 +26,7 @@ class ThirdPartyLocation { ThirdPartyLocation.fromJson(Map json) { alias = json['alias']; protocol = json['protocol']; - fields = Map.from(json['fields']); + fields = (json['fields'] as Map).copy(); } Map toJson() { diff --git a/lib/matrix_api/model/third_party_user.dart b/lib/matrix_api/model/third_party_user.dart index fd84f907..e19818e8 100644 --- a/lib/matrix_api/model/third_party_user.dart +++ b/lib/matrix_api/model/third_party_user.dart @@ -16,6 +16,8 @@ * along with this program. If not, see . */ +import '../utils/map_copy_extension.dart'; + class ThirdPartyUser { String userId; String protocol; @@ -24,7 +26,7 @@ class ThirdPartyUser { ThirdPartyUser.fromJson(Map json) { userId = json['userid']; protocol = json['protocol']; - fields = Map.from(json['fields']); + fields = (json['fields'] as Map).copy(); } Map toJson() { diff --git a/lib/matrix_api/utils/map_copy_extension.dart b/lib/matrix_api/utils/map_copy_extension.dart new file mode 100644 index 00000000..84ddc272 --- /dev/null +++ b/lib/matrix_api/utils/map_copy_extension.dart @@ -0,0 +1,33 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +extension MapCopyExtension on Map { + /// Deep-copies a given json map + Map copy() { + final copy = Map.from(this); + for (final entry in copy.entries) { + if (entry.value is Map) { + copy[entry.key] = (entry.value as Map).copy(); + } + if (entry.value is List) { + copy[entry.key] = List.from(entry.value); + } + } + return copy; + } +} diff --git a/lib/src/room.dart b/lib/src/room.dart index a5f0eee7..751f83d6 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -750,7 +750,7 @@ class Room { }; } if (editEventId != null) { - final newContent = Map.from(content); + final newContent = content.copy(); content['m.new_content'] = newContent; content['m.relates_to'] = { 'event_id': editEventId, diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index f4cd4bfc..96402b5b 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -145,8 +145,7 @@ abstract class SignableKey extends MatrixSignableKey { } MatrixSignableKey cloneForSigning() { - final newKey = - MatrixSignableKey.fromJson(Map.from(toJson())); + final newKey = MatrixSignableKey.fromJson(toJson().copy()); newKey.identifier = identifier; newKey.signatures ??= >{}; newKey.signatures.clear(); @@ -154,7 +153,7 @@ abstract class SignableKey extends MatrixSignableKey { } String get signingContent { - final data = Map.from(super.toJson()); + final data = super.toJson().copy(); // some old data might have the custom verified and blocked keys data.remove('verified'); data.remove('blocked'); @@ -303,7 +302,7 @@ abstract class SignableKey extends MatrixSignableKey { @override Map toJson() { - final data = Map.from(super.toJson()); + final data = super.toJson().copy(); // some old data may have the verified and blocked keys which are unneeded now data.remove('verified'); data.remove('blocked'); @@ -336,7 +335,7 @@ class CrossSigningKey extends SignableKey { } CrossSigningKey.fromMatrixCrossSigningKey(MatrixCrossSigningKey k, Client cl) - : super.fromJson(Map.from(k.toJson()), cl) { + : super.fromJson(k.toJson().copy(), cl) { final json = toJson(); identifier = k.publicKey; usage = json['usage'].cast(); @@ -352,7 +351,7 @@ class CrossSigningKey extends SignableKey { } CrossSigningKey.fromJson(Map json, Client cl) - : super.fromJson(Map.from(json), cl) { + : super.fromJson(json.copy(), cl) { final json = toJson(); usage = json['usage'].cast(); if (keys != null && keys.isNotEmpty) { @@ -409,7 +408,7 @@ class DeviceKeys extends SignableKey { } DeviceKeys.fromMatrixDeviceKeys(MatrixDeviceKeys k, Client cl) - : super.fromJson(Map.from(k.toJson()), cl) { + : super.fromJson(k.toJson().copy(), cl) { final json = toJson(); identifier = k.deviceId; algorithms = json['algorithms'].cast(); @@ -425,7 +424,7 @@ class DeviceKeys extends SignableKey { } DeviceKeys.fromJson(Map json, Client cl) - : super.fromJson(Map.from(json), cl) { + : super.fromJson(json.copy(), cl) { final json = toJson(); identifier = json['device_id']; algorithms = json['algorithms'].cast(); diff --git a/test/matrix_api/map_copy_extension_test.dart b/test/matrix_api/map_copy_extension_test.dart new file mode 100644 index 00000000..3e21cf40 --- /dev/null +++ b/test/matrix_api/map_copy_extension_test.dart @@ -0,0 +1,39 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:famedlysdk/matrix_api/utils/map_copy_extension.dart'; +import 'package:test/test.dart'; + +void main() { + group('Map-copy-extension', () { + test('it should work', () { + final original = { + 'attr': 'fox', + 'child': { + 'attr': 'bunny', + 'list': [1, 2], + }, + }; + final copy = original.copy(); + original['child']['attr'] = 'raccoon'; + expect(copy['child']['attr'], 'bunny'); + original['child']['list'].add(3); + expect(copy['child']['list'], [1, 2]); + }); + }); +} diff --git a/test/matrix_api/try_get_map_extension_test.dart b/test/matrix_api/try_get_map_extension_test.dart new file mode 100644 index 00000000..646d7348 --- /dev/null +++ b/test/matrix_api/try_get_map_extension_test.dart @@ -0,0 +1,45 @@ +/* + * Famedly Matrix SDK + * Copyright (C) 2020 Famedly GmbH + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU Affero General Public License as + * published by the Free Software Foundation, either version 3 of the + * License, or (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Affero General Public License for more details. + * + * You should have received a copy of the GNU Affero General Public License + * along with this program. If not, see . + */ + +import 'package:famedlysdk/matrix_api/utils/try_get_map_extension.dart'; +import 'package:test/test.dart'; + +void main() { + group('Try-get-map-extension', () { + test('it should work', () { + final data = { + 'str': 'foxies', + 'int': 42, + 'list': [2, 3, 4], + 'map': { + 'beep': 'boop', + }, + }; + expect(data.tryGet('str'), 'foxies'); + expect(data.tryGet('str'), null); + expect(data.tryGet('int'), 42); + expect(data.tryGet('list'), [2, 3, 4]); + expect(data.tryGet>('map')?.tryGet('beep'), + 'boop'); + expect(data.tryGet>('map')?.tryGet('meep'), + null); + expect(data.tryGet>('pam')?.tryGet('beep'), + null); + }); + }); +}