diff --git a/lib/encryption/key_manager.dart b/lib/encryption/key_manager.dart index 1dc9501f..2aefd18d 100644 --- a/lib/encryption/key_manager.dart +++ b/lib/encryption/key_manager.dart @@ -69,11 +69,14 @@ class KeyManager { if (lastEvent != null && lastEvent.type == EventTypes.Encrypted && lastEvent.content['can_request_session'] == true) { - try { - maybeAutoRequest(room.id, lastEvent.content['session_id'], - lastEvent.content['sender_key']); - } catch (_) { - // dispose + final sessionId = lastEvent.content.tryGet('session_id'); + final senderKey = lastEvent.content.tryGet('sender_key'); + if (sessionId != null && senderKey != null) { + maybeAutoRequest( + room.id, + sessionId, + senderKey, + ); } } } @@ -638,23 +641,25 @@ class KeyManager { final sessionId = sessionEntry.key; final session = sessionEntry.value; final sessionData = session.sessionData; - Map? decrypted; + Map? decrypted; try { - decrypted = json.decode(decryption.decrypt(sessionData['ephemeral'], - sessionData['mac'], sessionData['ciphertext'])); + decrypted = json.decode(decryption.decrypt( + sessionData['ephemeral'] as String, + sessionData['mac'] as String, + sessionData['ciphertext'] as String)); } catch (e, s) { Logs().e('[LibOlm] Error decrypting room key', e, s); } - if (decrypted != null) { + final senderKey = decrypted?.tryGet('sender_key'); + if (decrypted != null && senderKey != null) { decrypted['session_id'] = sessionId; decrypted['room_id'] = roomId; await setInboundGroupSession( - roomId, sessionId, decrypted['sender_key'], decrypted, + roomId, sessionId, senderKey, decrypted, forwarded: true, - senderClaimedKeys: decrypted['sender_claimed_keys'] != null - ? Map.from( - decrypted['sender_claimed_keys']!) - : {}, + senderClaimedKeys: decrypted + .tryGetMap('sender_claimed_keys') ?? + {}, uploaded: true); } } @@ -831,13 +836,24 @@ class KeyManager { Logs().i( '[KeyManager] Received key sharing request from ${event.sender}:${event.content['requesting_device_id']}...'); if (!event.content.containsKey('body')) { - Logs().i('[KeyManager] No body, doing nothing'); + Logs().w('[KeyManager] No body, doing nothing'); return; // no body } + final body = event.content.tryGetMap('body'); + if (body == null) { + Logs().w('[KeyManager] Wrong type for body, doing nothing'); + return; // wrong type for body + } + final roomId = body.tryGet('room_id'); + if (roomId == null) { + Logs().w( + '[KeyManager] Wrong type for room_id or no room_id, doing nothing'); + return; // wrong type for roomId or no roomId found + } final device = client.userDeviceKeys[event.sender] ?.deviceKeys[event.content['requesting_device_id']]; if (device == null) { - Logs().i('[KeyManager] Device not found, doing nothing'); + Logs().w('[KeyManager] Device not found, doing nothing'); return; // device not found } if (device.userId == client.userID && @@ -845,20 +861,30 @@ class KeyManager { Logs().i('[KeyManager] Request is by ourself, ignoring'); return; // ignore requests by ourself } - final room = client.getRoomById(event.content['body']['room_id']); + final room = client.getRoomById(roomId); if (room == null) { Logs().i('[KeyManager] Unknown room, ignoring'); return; // unknown room } - final sessionId = event.content['body']['session_id']; + final sessionId = body.tryGet('session_id'); + if (sessionId == null) { + Logs().w( + '[KeyManager] Wrong type for session_id or no session_id, doing nothing'); + return; // wrong type for session_id + } // okay, let's see if we have this session at all final session = await loadInboundGroupSession(room.id, sessionId); if (session == null) { Logs().i('[KeyManager] Unknown session, ignoring'); return; // we don't have this session anyways } + if (event.content['request_id'] is! String) { + Logs().w( + '[KeyManager] Wrong type for request_id or no request_id, doing nothing'); + return; // wrong type for request_id + } final request = KeyManagerKeyShareRequest( - requestId: event.content['request_id'], + requestId: event.content.tryGet('request_id')!, devices: [device], room: room, sessionId: sessionId, @@ -878,7 +904,7 @@ class KeyManager { await roomKeyRequest.forwardKey(); } else if (device.encryptToDevice && session.allowedAtIndex - .tryGet>(device.userId) + .tryGet>(device.userId) ?.tryGet(device.curve25519Key!) != null) { // if we know the user may see the message, then we can just forward the key. @@ -931,15 +957,19 @@ class KeyManager { if (event.content['forwarding_curve25519_key_chain'] is! List) { event.content['forwarding_curve25519_key_chain'] = []; } - event.content['forwarding_curve25519_key_chain'] + (event.content['forwarding_curve25519_key_chain'] as List) .add(encryptedContent['sender_key']); + if (event.content['sender_claimed_ed25519_key'] is! String) { + Logs().w('sender_claimed_ed255519_key has wrong type'); + return; // wrong type + } // TODO: verify that the keys work to decrypt a message // alright, all checks out, let's go ahead and store this session await setInboundGroupSession(request.room.id, request.sessionId, device.curve25519Key!, event.content, forwarded: true, senderClaimedKeys: { - 'ed25519': event.content['sender_claimed_ed25519_key'], + 'ed25519': event.content['sender_claimed_ed25519_key'] as String, }); request.devices.removeWhere( (k) => k.userId == device.userId && k.deviceId == device.deviceId); @@ -972,8 +1002,13 @@ class KeyManager { Logs().v('[KeyManager] not encrypted, ignoring...'); return; // the event wasn't encrypted, this is a security risk; } - final String roomId = event.content['room_id']; - final String sessionId = event.content['session_id']; + final roomId = event.content.tryGet('room_id'); + final sessionId = event.content.tryGet('session_id'); + if (roomId == null || sessionId == null) { + Logs().w( + 'Either room_id or session_id are not the expected type or missing'); + return; + } final sender_ed25519 = client.userDeviceKeys[event.sender] ?.deviceKeys[event.content['requesting_device_id']]?.ed25519Key; if (sender_ed25519 != null) { diff --git a/lib/encryption/olm_manager.dart b/lib/encryption/olm_manager.dart index e17618b2..054aa850 100644 --- a/lib/encryption/olm_manager.dart +++ b/lib/encryption/olm_manager.dart @@ -597,11 +597,12 @@ class OlmManager { client.userDeviceKeys[userId]!.deviceKeys[deviceId]!.ed25519Key; final identityKey = client.userDeviceKeys[userId]!.deviceKeys[deviceId]!.curve25519Key; - for (final Map deviceKey - in deviceKeysEntry.value.values) { + for (final deviceKey in deviceKeysEntry.value.values) { if (fingerprintKey == null || identityKey == null || - !deviceKey.checkJsonSignature(fingerprintKey, userId, deviceId)) { + deviceKey is! Map || + !deviceKey.checkJsonSignature(fingerprintKey, userId, deviceId) || + deviceKey['key'] is! String) { Logs().w( 'Skipping invalid device key from $userId:$deviceId', deviceKey, @@ -612,7 +613,7 @@ class OlmManager { final session = olm.Session(); try { session.create_outbound( - _olmAccount!, identityKey, deviceKey['key']); + _olmAccount!, identityKey, deviceKey.tryGet('key')!); await storeOlmSession(OlmSession( key: client.userID!, identityKey: identityKey, diff --git a/lib/encryption/ssss.dart b/lib/encryption/ssss.dart index 65b14f47..e6dab387 100644 --- a/lib/encryption/ssss.dart +++ b/lib/encryption/ssss.dart @@ -280,8 +280,7 @@ class SSSS { } bool isSecret(String type) => - client.accountData[type] != null && - client.accountData[type]!.content['encrypted'] is Map; + client.accountData[type]?.content['encrypted'] is Map; Future getCached(String type) async { if (client.database == null) { @@ -295,8 +294,11 @@ class SSSS { bool isValid(SSSSCache dbEntry) => keys.contains(dbEntry.keyId) && dbEntry.ciphertext != null && - client.accountData[type]?.content['encrypted'][dbEntry.keyId] - ['ciphertext'] == + dbEntry.keyId != null && + client.accountData[type]?.content + .tryGetMap('encrypted') + ?.tryGetMap(dbEntry.keyId!) + ?.tryGet('ciphertext') == dbEntry.ciphertext; final fromCache = _cache[type]; @@ -319,20 +321,31 @@ class SSSS { if (secretInfo == null) { throw Exception('Not found'); } - if (secretInfo.content['encrypted'] is! Map) { + final encryptedContent = + secretInfo.content.tryGetMap('encrypted'); + if (encryptedContent == null) { throw Exception('Content is not encrypted'); } - if (secretInfo.content['encrypted'][keyId] is! Map) { + final enc = encryptedContent.tryGetMap(keyId); + if (enc == null) { throw Exception('Wrong / unknown key'); } - final enc = secretInfo.content['encrypted'][keyId]; + final ciphertext = enc.tryGet('ciphertext'); + final iv = enc.tryGet('iv'); + final mac = enc.tryGet('mac'); + if (ciphertext == null || iv == null || mac == null) { + throw Exception('Wrong types for encrypted content or missing keys.'); + } final encryptInfo = EncryptedContent( - iv: enc['iv'], ciphertext: enc['ciphertext'], mac: enc['mac']); + iv: iv, + ciphertext: ciphertext, + mac: mac, + ); final decrypted = await decryptAes(encryptInfo, key, type); final db = client.database; if (cacheTypes.contains(type) && db != null) { // cache the thing - await db.storeSSSSCache(type, keyId, enc['ciphertext'], decrypted); + await db.storeSSSSCache(type, keyId, ciphertext, decrypted); onSecretStored.add(keyId); if (_cacheCallbacks.containsKey(type) && await getCached(type) == null) { _cacheCallbacks[type]!(decrypted); @@ -382,10 +395,14 @@ class SSSS { if (content == null) { throw InvalidPassphraseException('Key has no content!'); } + final encryptedContent = content.tryGetMap('encrypted'); + if (encryptedContent == null) { + throw Exception('Wrong type for encrypted content!'); + } final otherKeys = - Set.from(content['encrypted'].keys.where((k) => k != keyId)); - content['encrypted'].removeWhere((k, v) => otherKeys.contains(k)); + Set.from(encryptedContent.keys.where((k) => k != keyId)); + encryptedContent.removeWhere((k, v) => otherKeys.contains(k)); // yes, we are paranoid... if (await getStored(type, keyId, key) != secret) { throw Exception('Secrets do not match up!'); @@ -394,8 +411,13 @@ class SSSS { await client.setAccountData(client.userID!, type, content); if (cacheTypes.contains(type)) { // cache the thing - await client.database?.storeSSSSCache( - type, keyId, content['encrypted'][keyId]['ciphertext'], secret); + final ciphertext = encryptedContent + .tryGetMap(keyId) + ?.tryGet('ciphertext'); + if (ciphertext == null) { + throw Exception('Wrong type for ciphertext!'); + } + await client.database?.storeSSSSCache(type, keyId, ciphertext, secret); onSecretStored.add(keyId); } } @@ -501,7 +523,11 @@ class SSSS { return; // nope....unknown or untrusted device } // alright, all seems fine...let's check if we actually have the secret they are asking for - final type = event.content['name']; + final type = event.content.tryGet('name'); + if (type == null) { + Logs().i('[SSSS] Wrong data type for type param, ignoring'); + return; + } final secret = await getCached(type); if (secret == null) { Logs() @@ -536,8 +562,8 @@ class SSSS { Logs().i('[SSSS] Someone else replied?'); return; // someone replied whom we didn't send the share request to } - final secret = event.content['secret']; - if (event.content['secret'] is! String) { + final secret = event.content.tryGet('secret'); + if (secret == null) { Logs().i('[SSSS] Secret wasn\'t a string'); return; // the secret wasn't a string....wut? } @@ -557,8 +583,14 @@ class SSSS { if (db != null) { final keyId = keyIdFromType(request.type); if (keyId != null) { - final ciphertext = client.accountData[request.type]! - .content['encrypted'][keyId]['ciphertext']; + final ciphertext = (client.accountData[request.type]!.content + .tryGetMap('encrypted')) + ?.tryGetMap(keyId) + ?.tryGet('ciphertext'); + if (ciphertext == null) { + Logs().i('[SSSS] Ciphertext is empty or not a String'); + return; + } await db.storeSSSSCache(request.type, keyId, ciphertext, secret); if (_cacheCallbacks.containsKey(request.type)) { _cacheCallbacks[request.type]!(secret); @@ -574,8 +606,10 @@ class SSSS { if (data == null) { return null; } - if (data.content['encrypted'] is Map) { - return data.content['encrypted'].keys.toSet(); + final contentEncrypted = + data.content.tryGetMap('encrypted'); + if (contentEncrypted != null) { + return contentEncrypted.keys.toSet(); } return null; } diff --git a/lib/encryption/utils/bootstrap.dart b/lib/encryption/utils/bootstrap.dart index c6c91a24..dc728b63 100644 --- a/lib/encryption/utils/bootstrap.dart +++ b/lib/encryption/utils/bootstrap.dart @@ -104,12 +104,14 @@ class Bootstrap { for (final entry in client.accountData.entries) { final type = entry.key; final event = entry.value; - if (event.content['encrypted'] is! Map) { + final encryptedContent = + event.content.tryGetMap('encrypted'); + if (encryptedContent == null) { continue; } final validKeys = {}; final invalidKeys = {}; - for (final keyEntry in event.content['encrypted'].entries) { + for (final keyEntry in encryptedContent.entries) { final key = keyEntry.key; final value = keyEntry.value; if (value is! Map) { diff --git a/lib/msc_extensions/msc_3814_dehydrated_devices/api.dart b/lib/msc_extensions/msc_3814_dehydrated_devices/api.dart index e815bf45..1f4b262a 100644 --- a/lib/msc_extensions/msc_3814_dehydrated_devices/api.dart +++ b/lib/msc_extensions/msc_3814_dehydrated_devices/api.dart @@ -47,7 +47,9 @@ extension DehydratedDeviceMatrixApi on MatrixApi { }, }, ); - return Map.from(response['one_time_key_counts']); + return Map.from( + response.tryGetMap('one_time_key_counts') ?? + {}); } /// uploads a dehydrated device. diff --git a/lib/src/client.dart b/lib/src/client.dart index 4f2df19c..e21458a3 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -298,7 +298,7 @@ class Client extends MatrixApi { final ruleset = TryGetPushRule.tryFromJson( _accountData[EventTypes.PushRules] ?.content - .tryGetMap('global') ?? + .tryGetMap('global') ?? {}); _pushruleEvaluator = PushruleEvaluator.fromRuleset(ruleset); } @@ -1068,7 +1068,7 @@ class Client extends MatrixApi { PushRuleSet? get globalPushRules { final pushrules = _accountData['m.push_rules'] ?.content - .tryGetMap('global'); + .tryGetMap('global'); return pushrules != null ? TryGetPushRule.tryFromJson(pushrules) : null; } @@ -1076,7 +1076,7 @@ class Client extends MatrixApi { PushRuleSet? get devicePushRules { final pushrules = _accountData['m.push_rules'] ?.content - .tryGetMap('device'); + .tryGetMap('device'); return pushrules != null ? TryGetPushRule.tryFromJson(pushrules) : null; } @@ -2731,12 +2731,15 @@ class Client extends MatrixApi { /// Whether all push notifications are muted using the [.m.rule.master] /// rule of the push rules: https://matrix.org/docs/spec/client_server/r0.6.0#m-rule-master bool get allPushNotificationsMuted { - final Map? globalPushRules = - _accountData[EventTypes.PushRules]?.content['global']; + final Map? globalPushRules = + _accountData[EventTypes.PushRules] + ?.content + .tryGetMap('global'); if (globalPushRules == null) return false; - if (globalPushRules['override'] is List) { - for (final pushRule in globalPushRules['override']) { + final globalPushRulesOverride = globalPushRules.tryGetList('override'); + if (globalPushRulesOverride != null) { + for (final pushRule in globalPushRulesOverride) { if (pushRule['rule_id'] == '.m.rule.master') { return pushRule['enabled']; } @@ -2813,12 +2816,12 @@ class Client extends MatrixApi { } /// A list of mxids of users who are ignored. - List get ignoredUsers => (_accountData - .containsKey('m.ignored_user_list') && - _accountData['m.ignored_user_list']?.content['ignored_users'] is Map) - ? List.from( - _accountData['m.ignored_user_list']?.content['ignored_users'].keys) - : []; + List get ignoredUsers => + List.from(_accountData['m.ignored_user_list'] + ?.content + .tryGetMap('ignored_users') + ?.keys ?? + []); /// Ignore another user. This will clear the local cached messages to /// hide all previous messages from this user. diff --git a/lib/src/event.dart b/lib/src/event.dart index d2090154..c7e6f3e4 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -259,7 +259,7 @@ class Event extends MatrixEvent { String get messageType => type == EventTypes.Sticker ? MessageTypes.Sticker - : (content['msgtype'] is String ? content['msgtype'] : MessageTypes.Text); + : (content.tryGet('msgtype') ?? MessageTypes.Text); void setRedactionEvent(Event redactedBecause) { unsigned = { @@ -301,11 +301,10 @@ class Event extends MatrixEvent { } /// Returns the body of this event if it has a body. - String get text => content['body'] is String ? content['body'] : ''; + String get text => content.tryGet('body') ?? ''; /// Returns the formatted boy of this event if it has a formatted body. - String get formattedText => - content['formatted_body'] is String ? content['formatted_body'] : ''; + String get formattedText => content.tryGet('formatted_body') ?? ''; /// Use this to get the body. String get body { @@ -402,7 +401,7 @@ class Event extends MatrixEvent { // in the `sendEvent` method to transition -1 -> 0 -> 1 -> 2 return await room.sendEvent( content, - txid: txid ?? unsigned?['transaction_id'] ?? eventId, + txid: txid ?? unsigned?.tryGet('transaction_id') ?? eventId, ); } @@ -432,13 +431,19 @@ class Event extends MatrixEvent { content['can_request_session'] != true) { throw ('Session key not requestable'); } - await room.requestSessionKey(content['session_id'], content['sender_key']); + + final sessionId = content.tryGet('session_id'); + final senderKey = content.tryGet('sender_key'); + if (sessionId == null || senderKey == null) { + throw ('Unknown session_id or sender_key'); + } + await room.requestSessionKey(sessionId, senderKey); return; } /// Gets the info map of file events, or a blank map if none present Map get infoMap => - content['info'] is Map ? content['info'] : {}; + content.tryGetMap('info') ?? {}; /// Gets the thumbnail info map of file events, or a blank map if nonepresent Map get thumbnailInfoMap => infoMap['thumbnail_info'] is Map @@ -461,9 +466,10 @@ class Event extends MatrixEvent { /// Gets the mimetype of the attachment of a file event, or a blank string if not present String get attachmentMimetype => infoMap['mimetype'] is String ? infoMap['mimetype'].toLowerCase() - : (content['file'] is Map && content['file']['mimetype'] is String - ? content['file']['mimetype'] - : ''); + : (content + .tryGetMap('file') + ?.tryGet('mimetype') ?? + ''); /// Gets the mimetype of the thumbnail of a file event, or a blank string if not present String get thumbnailMimetype => thumbnailInfoMap['mimetype'] is String @@ -475,7 +481,9 @@ class Event extends MatrixEvent { /// Gets the underlying mxc url of an attachment of a file event, or null if not present Uri? get attachmentMxcUrl { - final url = isAttachmentEncrypted ? content['file']['url'] : content['url']; + final url = isAttachmentEncrypted + ? (content.tryGetMap('file')?['url']) + : content['url']; return url is String ? Uri.tryParse(url) : null; } @@ -759,21 +767,17 @@ class Event extends MatrixEvent { // if we need to strip the reply fallback. var htmlMessage = content['format'] != 'org.matrix.custom.html'; // If we have an edit, we want to operate on the new content + final newContent = content.tryGetMap('m.new_content'); if (hideEdit && relationshipType == RelationshipTypes.edit && - content.tryGet>('m.new_content') != null) { - if (plaintextBody && - content['m.new_content']['format'] == 'org.matrix.custom.html') { + newContent != null) { + if (plaintextBody && newContent['format'] == 'org.matrix.custom.html') { htmlMessage = true; body = HtmlToText.convert( - (content['m.new_content'] as Map) - .tryGet('formatted_body') ?? - formattedText); + newContent.tryGet('formatted_body') ?? formattedText); } else { htmlMessage = false; - body = (content['m.new_content'] as Map) - .tryGet('body') ?? - body; + body = newContent.tryGet('body') ?? body; } } // Hide reply fallback @@ -815,31 +819,27 @@ class Event extends MatrixEvent { /// Get the relationship type of an event. `null` if there is none String? get relationshipType { - if (content.tryGet>('m.relates_to') == null) { + final mRelatesTo = content.tryGetMap('m.relates_to'); + if (mRelatesTo == null) { return null; } - if (content['m.relates_to'].containsKey('rel_type')) { - if (content - .tryGet>('m.relates_to') - ?.tryGet('rel_type') == - RelationshipTypes.thread) { - return RelationshipTypes.thread; - } + final relType = mRelatesTo.tryGet('rel_type'); + if (relType == RelationshipTypes.thread) { + return RelationshipTypes.thread; } - if (content['m.relates_to'].containsKey('m.in_reply_to')) { + + if (mRelatesTo.containsKey('m.in_reply_to')) { return RelationshipTypes.reply; } - return content - .tryGet>('m.relates_to') - ?.tryGet('rel_type'); + return relType; } /// Get the event ID that this relationship will reference. `null` if there is none String? get relationshipEventId { - final relatesToMap = content.tryGetMap('m.relates_to'); + final relatesToMap = content.tryGetMap('m.relates_to'); return relatesToMap?.tryGet('event_id') ?? relatesToMap - ?.tryGetMap('m.in_reply_to') + ?.tryGetMap('m.in_reply_to') ?.tryGet('event_id'); } diff --git a/lib/src/room.dart b/lib/src/room.dart index 16977858..c29cad81 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -267,9 +267,10 @@ class Room { } if (membership == Membership.leave) { final invitation = getState(EventTypes.RoomMember, client.userID!); - if (invitation != null && invitation.unsigned?['prev_sender'] != null) { + if (invitation != null && + invitation.unsigned?.tryGet('prev_sender') != null) { final name = unsafeGetUserFromMemoryOrFallback( - invitation.unsigned?['prev_sender']) + invitation.unsigned!.tryGet('prev_sender')!) .calcDisplayname(i18n: i18n); return i18n.wasDirectChatDisplayName(name); } @@ -1315,8 +1316,9 @@ class Room { Future removeFromDirectChat() async { final directChats = client.directChats.copy(); for (final k in directChats.keys) { - if (directChats[k] is List && directChats[k].contains(id)) { - directChats[k].remove(id); + final directChat = directChats[k]; + if (directChat is List && directChat.contains(id)) { + directChat.remove(id); } } @@ -1747,7 +1749,7 @@ class Room { return getState(EventTypes.RoomCreate)?.senderId == userId ? 100 : 0; } return powerLevelMap - .tryGetMap('users') + .tryGetMap('users') ?.tryGet(userId) ?? powerLevelMap.tryGet('users_default') ?? 0; @@ -1802,7 +1804,7 @@ class Room { final powerLevelMap = getState(EventTypes.RoomPowerLevels)?.content; if (powerLevelMap == null) return 0; return powerLevelMap - .tryGetMap('events') + .tryGetMap('events') ?.tryGet(action) ?? powerLevelMap.tryGet('state_default') ?? 50; @@ -1833,7 +1835,8 @@ class Room { final currentPowerLevelsMap = getState(EventTypes.RoomPowerLevels)?.content; if (currentPowerLevelsMap != null) { final newPowerLevelMap = currentPowerLevelsMap; - final eventsMap = newPowerLevelMap['events'] ?? {}; + final eventsMap = newPowerLevelMap.tryGetMap('events') ?? + {}; eventsMap.addAll({ EventTypes.GroupCallPrefix: getDefaultPowerLevel(currentPowerLevelsMap), EventTypes.GroupCallMemberPrefix: @@ -1901,7 +1904,7 @@ class Room { final powerLevelsMap = getState(EventTypes.RoomPowerLevels)?.content; if (powerLevelsMap == null) return 0 <= ownPowerLevel; final pl = powerLevelsMap - .tryGetMap('events') + .tryGetMap('events') ?.tryGet(eventType) ?? powerLevelsMap.tryGet('events_default') ?? 0; @@ -1913,7 +1916,7 @@ class Room { final userLevel = getPowerLevelByUserId(userid); final notificationLevel = getState(EventTypes.RoomPowerLevels) ?.content - .tryGetMap('notifications') + .tryGetMap('notifications') ?.tryGet(notificationType) ?? 50; diff --git a/lib/src/timeline.dart b/lib/src/timeline.dart index 2da0d001..7ea015bf 100644 --- a/lib/src/timeline.dart +++ b/lib/src/timeline.dart @@ -347,16 +347,16 @@ class Timeline { if (event.type == EventTypes.Encrypted && event.messageType == MessageTypes.BadEncrypted && event.content['can_request_session'] == true) { - try { + final sessionId = event.content.tryGet('session_id'); + final senderKey = event.content.tryGet('sender_key'); + if (sessionId != null && senderKey != null) { room.client.encryption?.keyManager.maybeAutoRequest( room.id, - event.content['session_id'], - event.content['sender_key'], + sessionId, + senderKey, tryOnlineBackup: tryOnlineBackup, onlineKeyBackupOnly: onlineKeyBackupOnly, ); - } catch (_) { - // dispose } } } @@ -387,7 +387,7 @@ class Timeline { for (i = 0; i < events.length; i++) { final searchHaystack = {events[i].eventId}; - final txnid = events[i].unsigned?['transaction_id']; + final txnid = events[i].unsigned?.tryGet('transaction_id'); if (txnid != null) { searchHaystack.add(txnid); } @@ -401,8 +401,9 @@ class Timeline { void _removeEventFromSet(Set eventSet, Event event) { eventSet.removeWhere((e) => e.matchesEventOrTransactionId(event.eventId) || - (event.unsigned != null && - e.matchesEventOrTransactionId(event.unsigned?['transaction_id']))); + event.unsigned != null && + e.matchesEventOrTransactionId( + event.unsigned?.tryGet('transaction_id'))); } void addAggregatedEvent(Event event) { diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index 7efd2bdc..44456a26 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -275,7 +275,7 @@ abstract class SignableKey extends MatrixSignableKey { var haveValidSignature = false; var gotSignatureFromCache = false; final fullKeyIdBool = validSignatures - ?.tryGetMap(otherUserId) + ?.tryGetMap(otherUserId) ?.tryGet(fullKeyId); if (fullKeyIdBool == true) { haveValidSignature = true; @@ -426,14 +426,15 @@ class DeviceKeys extends SignableKey { late DateTime lastActive; String? get curve25519Key => keys['curve25519:$deviceId']; - String? get deviceDisplayName => unsigned?['device_display_name']; + String? get deviceDisplayName => + unsigned?.tryGet('device_display_name'); bool? _validSelfSignature; bool get selfSigned => _validSelfSignature ?? (_validSelfSignature = (deviceId != null && signatures - ?.tryGetMap(userId) + ?.tryGetMap(userId) ?.tryGet('ed25519:$deviceId') == null ? false diff --git a/lib/src/utils/event_localizations.dart b/lib/src/utils/event_localizations.dart index c39287d7..857418ba 100644 --- a/lib/src/utils/event_localizations.dart +++ b/lib/src/utils/event_localizations.dart @@ -204,11 +204,11 @@ abstract class EventLocalizations { event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n)), EventTypes.RoomName: (event, i18n, body) => i18n.changedTheChatNameTo( event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n), - event.content['name']), + event.content.tryGet('name') ?? ''), EventTypes.RoomTopic: (event, i18n, body) => i18n.changedTheChatDescriptionTo( event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n), - event.content['topic']), + event.content.tryGet('topic') ?? ''), EventTypes.RoomAvatar: (event, i18n, body) => i18n.changedTheChatAvatar( event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n)), EventTypes.GuestAccess: (event, i18n, body) { @@ -260,7 +260,7 @@ abstract class EventLocalizations { EventTypes.Reaction: (event, i18n, body) => i18n.sentReaction( event.senderFromMemoryOrFallback.calcDisplayname(i18n: i18n), event.content - .tryGetMap('m.relates_to') + .tryGetMap('m.relates_to') ?.tryGet('key') ?? body, ), diff --git a/lib/src/utils/image_pack_extension.dart b/lib/src/utils/image_pack_extension.dart index 00b97430..d2db19e3 100644 --- a/lib/src/utils/image_pack_extension.dart +++ b/lib/src/utils/image_pack_extension.dart @@ -60,12 +60,14 @@ extension ImagePackRoomExtension on Room { addImagePack(client.accountData['im.ponies.user_emotes'], slug: 'user'); // next we add all the external image packs final packRooms = client.accountData['im.ponies.emote_rooms']; - if (packRooms != null && packRooms.content['rooms'] is Map) { - for (final roomEntry in packRooms.content['rooms'].entries) { + final rooms = packRooms?.content.tryGetMap('rooms'); + if (packRooms != null && rooms != null) { + for (final roomEntry in rooms.entries) { final roomId = roomEntry.key; final room = client.getRoomById(roomId); - if (room != null && roomEntry.value is Map) { - for (final stateKeyEntry in roomEntry.value.entries) { + final roomEntryValue = roomEntry.value; + if (room != null && roomEntryValue is Map) { + for (final stateKeyEntry in roomEntryValue.entries) { final stateKey = stateKeyEntry.key; final fallbackSlug = '${room.getLocalizedDisplayname()}-${stateKey.isNotEmpty ? '$stateKey-' : ''}${room.id}'; diff --git a/lib/src/voip/voip.dart b/lib/src/voip/voip.dart index 3bd19c23..9331043e 100644 --- a/lib/src/voip/voip.dart +++ b/lib/src/voip/voip.dart @@ -704,18 +704,20 @@ class VoIP { final groupCallId = event.stateKey; - final callType = content['m.type']; + final callType = content.tryGet('m.type'); - if (callType != GroupCallType.Video && callType != GroupCallType.Voice) { + if (callType == null || + callType != GroupCallType.Video && callType != GroupCallType.Voice) { Logs().w('Received invalid group call type $callType for room $roomId.'); return null; } - final callIntent = content['m.intent']; + final callIntent = content.tryGet('m.intent'); - if (callIntent != GroupCallIntent.Prompt && - callIntent != GroupCallIntent.Room && - callIntent != GroupCallIntent.Ring) { + if (callIntent == null || + callIntent != GroupCallIntent.Prompt && + callIntent != GroupCallIntent.Room && + callIntent != GroupCallIntent.Ring) { Logs() .w('Received invalid group call intent $callType for room $roomId.'); return null; diff --git a/pubspec.yaml b/pubspec.yaml index ffd0484f..d31305f6 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -22,7 +22,7 @@ dependencies: image: ^4.0.15 js: ^0.6.3 markdown: ^4.0.0 - matrix_api_lite: ">=1.6.0 <1.7.0" # Pinned until compatible with 1.7.x + matrix_api_lite: ^1.7.0 mime: ^1.0.0 olm: ^2.0.2 random_string: ^2.3.1 @@ -38,3 +38,7 @@ dev_dependencies: lints: ^2.0.0 test: ^1.15.7 #flutter_test: {sdk: flutter} + +#dependency_overrides: +# matrix_api_lite: +# path: ../matrix_api_lite diff --git a/test/room_test.dart b/test/room_test.dart index 04ef1813..1496d0d4 100644 --- a/test/room_test.dart +++ b/test/room_test.dart @@ -1042,8 +1042,10 @@ void main() { test('pushRuleState', () async { expect(room.pushRuleState, PushRuleState.mentionsOnly); - matrix.accountData['m.push_rules']?.content['global']['override'].add( - matrix.accountData['m.push_rules']?.content['global']['room'][0]); + ((matrix.accountData['m.push_rules']?.content['global'] + as Map)['override'] as List) + .add(((matrix.accountData['m.push_rules']?.content['global'] + as Map)['room'] as List)[0]); expect(room.pushRuleState, PushRuleState.dontNotify); });