From 27b4a620e547c7a131287dbf0cdf06439253f959 Mon Sep 17 00:00:00 2001 From: Christian Pauly Date: Fri, 22 May 2020 10:12:18 +0000 Subject: [PATCH] Add code formatting CI job --- .gitlab-ci.yml | 1 + CONTRIBUTING.md | 5 +- lib/src/client.dart | 42 +++--- lib/src/database/database.dart | 199 ++++++++++++++++++---------- lib/src/event.dart | 2 +- lib/src/presence.dart | 17 ++- lib/src/room.dart | 197 ++++++++++++++++----------- lib/src/sync/event_update.dart | 3 +- lib/src/timeline.dart | 21 ++- lib/src/utils/device_keys_list.dart | 22 ++- lib/src/utils/key_verification.dart | 146 +++++++++++++------- lib/src/utils/markdown.dart | 41 ++++-- lib/src/utils/room_key_request.dart | 5 +- lib/src/utils/session_key.dart | 5 +- test/markdown_test.dart | 27 ++-- test_driver/famedlysdk_test.dart | 6 +- 16 files changed, 486 insertions(+), 253 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 0be36fa..9c81333 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -54,6 +54,7 @@ code_analyze: image: cirrusci/flutter dependencies: [] script: + - flutter format lib/ test/ test_driver/ --set-exit-if-changed - flutter analyze build-api-doc: diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 23afbbc..a5c23f7 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -41,7 +41,10 @@ That means for example: "[users] add fetch users endpoint". - Directories need to be lowercase. ## Code style: -- We recommend using Android Studio for coding. We are using the Android Studio auto styling with ctrl+alt+shift+L. +Please use code formatting. You can use VSCode or Android Studio. On other editors you need to run: +``` +flutter format lib/**/*/*.dart +``` ## Code quality: - Don't repeat yourself! Use local variables, functions, classes. diff --git a/lib/src/client.dart b/lib/src/client.dart index 2841e69..d8a0b7f 100644 --- a/lib/src/client.dart +++ b/lib/src/client.dart @@ -84,7 +84,8 @@ class Client { /// debug: Print debug output? /// database: The database instance to use /// enableE2eeRecovery: Enable additional logic to try to recover from bad e2ee sessions - Client(this.clientName, {this.debug = false, this.database, this.enableE2eeRecovery = false}) { + Client(this.clientName, + {this.debug = false, this.database, this.enableE2eeRecovery = false}) { onLoginStateChanged.stream.listen((loginState) { print('LoginState: ${loginState.toString()}'); }); @@ -1045,8 +1046,7 @@ class Client { } if (sync['account_data'] is Map && sync['account_data']['events'] is List) { - await _handleGlobalEvents( - sync['account_data']['events'], 'account_data'); + await _handleGlobalEvents(sync['account_data']['events'], 'account_data'); } if (sync['device_lists'] is Map) { await _handleDeviceListsEvents(sync['device_lists']); @@ -1077,7 +1077,8 @@ class Client { } } - Future _handleDeviceListsEvents(Map deviceLists) async { + Future _handleDeviceListsEvents( + Map deviceLists) async { if (deviceLists['changed'] is List) { for (final userId in deviceLists['changed']) { if (_userDeviceKeys.containsKey(userId)) { @@ -1179,7 +1180,8 @@ class Client { } } - Future _handleRooms(Map rooms, Membership membership) async { + Future _handleRooms( + Map rooms, Membership membership) async { for (final entry in rooms.entries) { final id = entry.key; final room = entry.value; @@ -1252,8 +1254,7 @@ class Client { if (room['timeline'] is Map && room['timeline']['events'] is List && room['timeline']['events'].isNotEmpty) { - await _handleRoomEvents( - id, room['timeline']['events'], 'timeline'); + await _handleRoomEvents(id, room['timeline']['events'], 'timeline'); handledEvents = true; } @@ -1318,7 +1319,8 @@ class Client { } } - Future _handleRoomEvents(String chat_id, List events, String type) async { + Future _handleRoomEvents( + String chat_id, List events, String type) async { for (num i = 0; i < events.length; i++) { await _handleEvent(events[i], chat_id, type); } @@ -1341,14 +1343,17 @@ class Client { } } - Future _handleEvent(Map event, String roomID, String type) async { + Future _handleEvent( + Map event, String roomID, String type) async { if (event['type'] is String && event['content'] is Map) { // The client must ignore any new m.room.encryption event to prevent // man-in-the-middle attacks! final room = getRoomById(roomID); if (room == null || - (event['type'] == 'm.room.encryption' && room.encrypted && - event['content']['algorithm'] != room.getState('m.room.encryption')?.content['algorithm'])) { + (event['type'] == 'm.room.encryption' && + room.encrypted && + event['content']['algorithm'] != + room.getState('m.room.encryption')?.content['algorithm'])) { return; } @@ -1367,7 +1372,8 @@ class Client { } if (update.eventType == 'm.room.encrypted' && database != null) { // the event is still encrytped....let's try fetching the keys from the database! - await room.loadInboundGroupSessionKey(event['content']['session_id'], event['content']['sender_key']); + await room.loadInboundGroupSessionKey( + event['content']['session_id'], event['content']['sender_key']); update = update.decrypt(room); } if (type != 'ephemeral' && database != null) { @@ -1659,10 +1665,9 @@ class Client { if (entry.isValid) { _userDeviceKeys[userId].deviceKeys[deviceId] = entry; if (deviceId == deviceID && - entry.ed25519Key == - fingerprintKey) { - // Always trust the own device - entry.verified = true; + entry.ed25519Key == fingerprintKey) { + // Always trust the own device + entry.verified = true; } } if (database != null) { @@ -1670,8 +1675,9 @@ class Client { id, userId, deviceId, - json.encode( - _userDeviceKeys[userId].deviceKeys[deviceId].toJson()), + json.encode(_userDeviceKeys[userId] + .deviceKeys[deviceId] + .toJson()), _userDeviceKeys[userId].deviceKeys[deviceId].verified, _userDeviceKeys[userId].deviceKeys[deviceId].blocked, )); diff --git a/lib/src/database/database.dart b/lib/src/database/database.dart index e4af62a..9d69346 100644 --- a/lib/src/database/database.dart +++ b/lib/src/database/database.dart @@ -19,31 +19,31 @@ class Database extends _$Database { @override MigrationStrategy get migration => MigrationStrategy( - onCreate: (Migrator m) { - return m.createAll(); - }, - onUpgrade: (Migrator m, int from, int to) async { - // this appears to be only called once, so multiple consecutive upgrades have to be handled appropriately in here - if (from == 1) { - await m.createIndex(userDeviceKeysIndex); - await m.createIndex(userDeviceKeysKeyIndex); - await m.createIndex(olmSessionsIndex); - await m.createIndex(outboundGroupSessionsIndex); - await m.createIndex(inboundGroupSessionsIndex); - await m.createIndex(roomsIndex); - await m.createIndex(eventsIndex); - await m.createIndex(roomStatesIndex); - await m.createIndex(accountDataIndex); - await m.createIndex(roomAccountDataIndex); - await m.createIndex(presencesIndex); - from++; - } - if (from == 2) { - await m.deleteTable('outbound_group_sessions'); - await m.createTable(outboundGroupSessions); - } - }, - ); + onCreate: (Migrator m) { + return m.createAll(); + }, + onUpgrade: (Migrator m, int from, int to) async { + // this appears to be only called once, so multiple consecutive upgrades have to be handled appropriately in here + if (from == 1) { + await m.createIndex(userDeviceKeysIndex); + await m.createIndex(userDeviceKeysKeyIndex); + await m.createIndex(olmSessionsIndex); + await m.createIndex(outboundGroupSessionsIndex); + await m.createIndex(inboundGroupSessionsIndex); + await m.createIndex(roomsIndex); + await m.createIndex(eventsIndex); + await m.createIndex(roomStatesIndex); + await m.createIndex(accountDataIndex); + await m.createIndex(roomAccountDataIndex); + await m.createIndex(presencesIndex); + from++; + } + if (from == 2) { + await m.deleteTable('outbound_group_sessions'); + await m.createTable(outboundGroupSessions); + } + }, + ); Future getClient(String name) async { final res = await dbGetClient(name).get(); @@ -51,7 +51,8 @@ class Database extends _$Database { return res.first; } - Future> getUserDeviceKeys(int clientId) async { + Future> getUserDeviceKeys( + int clientId) async { final deviceKeys = await getAllUserDeviceKeys(clientId).get(); if (deviceKeys.isEmpty) { return {}; @@ -59,12 +60,14 @@ class Database extends _$Database { final deviceKeysKeys = await getAllUserDeviceKeysKeys(clientId).get(); final res = {}; for (final entry in deviceKeys) { - res[entry.userId] = sdk.DeviceKeysList.fromDb(entry, deviceKeysKeys.where((k) => k.userId == entry.userId).toList()); + res[entry.userId] = sdk.DeviceKeysList.fromDb(entry, + deviceKeysKeys.where((k) => k.userId == entry.userId).toList()); } return res; } - Future>> getOlmSessions(int clientId, String userId) async { + Future>> getOlmSessions( + int clientId, String userId) async { final raw = await getAllOlmSessions(clientId).get(); if (raw.isEmpty) { return {}; @@ -85,7 +88,8 @@ class Database extends _$Database { return res; } - Future getDbOutboundGroupSession(int clientId, String roomId) async { + Future getDbOutboundGroupSession( + int clientId, String roomId) async { final res = await dbGetOutboundGroupSession(clientId, roomId).get(); if (res.isEmpty) { return null; @@ -93,22 +97,28 @@ class Database extends _$Database { return res.first; } - Future> getDbInboundGroupSessions(int clientId, String roomId) async { + Future> getDbInboundGroupSessions( + int clientId, String roomId) async { return await dbGetInboundGroupSessionKeys(clientId, roomId).get(); } - Future getDbInboundGroupSession(int clientId, String roomId, String sessionId) async { - final res = await dbGetInboundGroupSessionKey(clientId, roomId, sessionId).get(); + Future getDbInboundGroupSession( + int clientId, String roomId, String sessionId) async { + final res = + await dbGetInboundGroupSessionKey(clientId, roomId, sessionId).get(); if (res.isEmpty) { return null; } return res.first; } - Future> getRoomList(sdk.Client client, {bool onlyLeft = false}) async { - final res = await (select(rooms)..where((t) => onlyLeft - ? t.membership.equals('leave') - : t.membership.equals('leave').not())).get(); + Future> getRoomList(sdk.Client client, + {bool onlyLeft = false}) async { + final res = await (select(rooms) + ..where((t) => onlyLeft + ? t.membership.equals('leave') + : t.membership.equals('leave').not())) + .get(); final resStates = await getAllRoomStates(client.id).get(); final resAccountData = await getAllRoomAccountData(client.id).get(); final roomList = []; @@ -145,11 +155,13 @@ class Database extends _$Database { /// Stores a RoomUpdate object in the database. Must be called inside of /// [transaction]. final Set _ensuredRooms = {}; - Future storeRoomUpdate(int clientId, sdk.RoomUpdate roomUpdate, [sdk.Room oldRoom]) async { + Future storeRoomUpdate(int clientId, sdk.RoomUpdate roomUpdate, + [sdk.Room oldRoom]) async { final setKey = '${clientId};${roomUpdate.id}'; if (roomUpdate.membership != sdk.Membership.leave) { if (!_ensuredRooms.contains(setKey)) { - await ensureRoomExists(clientId, roomUpdate.id, roomUpdate.membership.toString().split('.').last); + await ensureRoomExists(clientId, roomUpdate.id, + roomUpdate.membership.toString().split('.').last); _ensuredRooms.add(setKey); } } else { @@ -161,24 +173,37 @@ class Database extends _$Database { var doUpdate = oldRoom == null; if (!doUpdate) { doUpdate = roomUpdate.highlight_count != oldRoom.highlightCount || - roomUpdate.notification_count != oldRoom.notificationCount || - roomUpdate.membership.toString().split('.').last != oldRoom.membership.toString().split('.').last || - (roomUpdate.summary?.mJoinedMemberCount != null && - roomUpdate.summary.mJoinedMemberCount != oldRoom.mInvitedMemberCount) || - (roomUpdate.summary?.mInvitedMemberCount != null && - roomUpdate.summary.mJoinedMemberCount != oldRoom.mJoinedMemberCount) || - (roomUpdate.summary?.mHeroes != null && - roomUpdate.summary.mHeroes.join(',') != oldRoom.mHeroes.join(',')); + roomUpdate.notification_count != oldRoom.notificationCount || + roomUpdate.membership.toString().split('.').last != + oldRoom.membership.toString().split('.').last || + (roomUpdate.summary?.mJoinedMemberCount != null && + roomUpdate.summary.mJoinedMemberCount != + oldRoom.mInvitedMemberCount) || + (roomUpdate.summary?.mInvitedMemberCount != null && + roomUpdate.summary.mJoinedMemberCount != + oldRoom.mJoinedMemberCount) || + (roomUpdate.summary?.mHeroes != null && + roomUpdate.summary.mHeroes.join(',') != + oldRoom.mHeroes.join(',')); } if (doUpdate) { - await (update(rooms)..where((r) => r.roomId.equals(roomUpdate.id) & r.clientId.equals(clientId))).write(RoomsCompanion( + await (update(rooms) + ..where((r) => + r.roomId.equals(roomUpdate.id) & r.clientId.equals(clientId))) + .write(RoomsCompanion( highlightCount: Value(roomUpdate.highlight_count), notificationCount: Value(roomUpdate.notification_count), membership: Value(roomUpdate.membership.toString().split('.').last), - joinedMemberCount: roomUpdate.summary?.mJoinedMemberCount != null ? Value(roomUpdate.summary.mJoinedMemberCount) : Value.absent(), - invitedMemberCount: roomUpdate.summary?.mInvitedMemberCount != null ? Value(roomUpdate.summary.mInvitedMemberCount) : Value.absent(), - heroes: roomUpdate.summary?.mHeroes != null ? Value(roomUpdate.summary.mHeroes.join(',')) : Value.absent(), + joinedMemberCount: roomUpdate.summary?.mJoinedMemberCount != null + ? Value(roomUpdate.summary.mJoinedMemberCount) + : Value.absent(), + invitedMemberCount: roomUpdate.summary?.mInvitedMemberCount != null + ? Value(roomUpdate.summary.mInvitedMemberCount) + : Value.absent(), + heroes: roomUpdate.summary?.mHeroes != null + ? Value(roomUpdate.summary.mHeroes.join(',')) + : Value.absent(), )); } @@ -193,17 +218,24 @@ class Database extends _$Database { /// Stores an UserUpdate object in the database. Must be called inside of /// [transaction]. - Future storeUserEventUpdate(int clientId, sdk.UserUpdate userUpdate) async { + Future storeUserEventUpdate( + int clientId, sdk.UserUpdate userUpdate) async { if (userUpdate.type == 'account_data') { - await storeAccountData(clientId, userUpdate.eventType, json.encode(userUpdate.content['content'])); + await storeAccountData(clientId, userUpdate.eventType, + json.encode(userUpdate.content['content'])); } else if (userUpdate.type == 'presence') { - await storePresence(clientId, userUpdate.eventType, userUpdate.content['sender'], json.encode(userUpdate.content['content'])); + await storePresence( + clientId, + userUpdate.eventType, + userUpdate.content['sender'], + json.encode(userUpdate.content['content'])); } } /// Stores an EventUpdate object in the database. Must be called inside of /// [transaction]. - Future storeEventUpdate(int clientId, sdk.EventUpdate eventUpdate) async { + Future storeEventUpdate( + int clientId, sdk.EventUpdate eventUpdate) async { if (eventUpdate.type == 'ephemeral') return; final eventContent = eventUpdate.content; final type = eventUpdate.type; @@ -227,11 +259,13 @@ class Database extends _$Database { eventContent['unsigned'] is Map && eventContent['unsigned']['transaction_id'] is String) { // status changed and we have an old transaction id --> update event id and stuffs - await updateEventStatus(status, eventContent['event_id'], clientId, eventContent['unsigned']['transaction_id'], chatId); + await updateEventStatus(status, eventContent['event_id'], clientId, + eventContent['unsigned']['transaction_id'], chatId); } else { DbEvent oldEvent; if (type == 'history') { - final allOldEvents = await getEvent(clientId, eventContent['event_id'], chatId).get(); + final allOldEvents = + await getEvent(clientId, eventContent['event_id'], chatId).get(); if (allOldEvents.isNotEmpty) { oldEvent = allOldEvents.first; } @@ -241,7 +275,10 @@ class Database extends _$Database { eventContent['event_id'], chatId, oldEvent?.sortOrder ?? eventUpdate.sortOrder, - eventContent['origin_server_ts'] != null ? DateTime.fromMillisecondsSinceEpoch(eventContent['origin_server_ts']) : DateTime.now(), + eventContent['origin_server_ts'] != null + ? DateTime.fromMillisecondsSinceEpoch( + eventContent['origin_server_ts']) + : DateTime.now(), eventContent['sender'], eventContent['type'], json.encode(eventContent['unsigned'] ?? ''), @@ -256,7 +293,8 @@ class Database extends _$Database { if (status != -1 && eventUpdate.content.containsKey('unsigned') && eventUpdate.content['unsigned']['transaction_id'] is String) { - await removeEvent(clientId, eventUpdate.content['unsigned']['transaction_id'], chatId); + await removeEvent(clientId, + eventUpdate.content['unsigned']['transaction_id'], chatId); } } @@ -269,7 +307,10 @@ class Database extends _$Database { eventContent['event_id'] ?? now.millisecondsSinceEpoch.toString(), chatId, eventUpdate.sortOrder ?? 0.0, - eventContent['origin_server_ts'] != null ? DateTime.fromMillisecondsSinceEpoch(eventContent['origin_server_ts']) : now, + eventContent['origin_server_ts'] != null + ? DateTime.fromMillisecondsSinceEpoch( + eventContent['origin_server_ts']) + : now, eventContent['sender'], eventContent['type'], json.encode(eventContent['unsigned'] ?? ''), @@ -287,7 +328,8 @@ class Database extends _$Database { } } - Future getEventById(int clientId, String eventId, sdk.Room room) async { + Future getEventById( + int clientId, String eventId, sdk.Room room) async { final event = await getEvent(clientId, eventId, room.id).get(); if (event.isEmpty) { return null; @@ -296,7 +338,9 @@ class Database extends _$Database { } Future redactMessage(int clientId, sdk.EventUpdate eventUpdate) async { - final events = await getEvent(clientId, eventUpdate.content['redacts'], eventUpdate.roomID).get(); + final events = await getEvent( + clientId, eventUpdate.content['redacts'], eventUpdate.roomID) + .get(); var success = false; for (final dbEvent in events) { final event = sdk.Event.fromDb(dbEvent, null); @@ -325,29 +369,44 @@ class Database extends _$Database { Future forgetRoom(int clientId, String roomId) async { final setKey = '${clientId};${roomId}'; _ensuredRooms.remove(setKey); - await (delete(rooms)..where((r) => r.roomId.equals(roomId) & r.clientId.equals(clientId))).go(); - await (delete(events)..where((r) => r.roomId.equals(roomId) & r.clientId.equals(clientId))).go(); - await (delete(roomStates)..where((r) => r.roomId.equals(roomId) & r.clientId.equals(clientId))).go(); - await (delete(roomAccountData)..where((r) => r.roomId.equals(roomId) & r.clientId.equals(clientId))).go(); + await (delete(rooms) + ..where((r) => r.roomId.equals(roomId) & r.clientId.equals(clientId))) + .go(); + await (delete(events) + ..where((r) => r.roomId.equals(roomId) & r.clientId.equals(clientId))) + .go(); + await (delete(roomStates) + ..where((r) => r.roomId.equals(roomId) & r.clientId.equals(clientId))) + .go(); + await (delete(roomAccountData) + ..where((r) => r.roomId.equals(roomId) & r.clientId.equals(clientId))) + .go(); } Future clearCache(int clientId) async { await (delete(presences)..where((r) => r.clientId.equals(clientId))).go(); - await (delete(roomAccountData)..where((r) => r.clientId.equals(clientId))).go(); + await (delete(roomAccountData)..where((r) => r.clientId.equals(clientId))) + .go(); await (delete(accountData)..where((r) => r.clientId.equals(clientId))).go(); await (delete(roomStates)..where((r) => r.clientId.equals(clientId))).go(); await (delete(events)..where((r) => r.clientId.equals(clientId))).go(); await (delete(rooms)..where((r) => r.clientId.equals(clientId))).go(); - await (delete(outboundGroupSessions)..where((r) => r.clientId.equals(clientId))).go(); + await (delete(outboundGroupSessions) + ..where((r) => r.clientId.equals(clientId))) + .go(); await storePrevBatch(null, clientId); } Future clear(int clientId) async { await clearCache(clientId); - await (delete(inboundGroupSessions)..where((r) => r.clientId.equals(clientId))).go(); + await (delete(inboundGroupSessions) + ..where((r) => r.clientId.equals(clientId))) + .go(); await (delete(olmSessions)..where((r) => r.clientId.equals(clientId))).go(); - await (delete(userDeviceKeysKey)..where((r) => r.clientId.equals(clientId))).go(); - await (delete(userDeviceKeys)..where((r) => r.clientId.equals(clientId))).go(); + await (delete(userDeviceKeysKey)..where((r) => r.clientId.equals(clientId))) + .go(); + await (delete(userDeviceKeys)..where((r) => r.clientId.equals(clientId))) + .go(); await (delete(clients)..where((r) => r.clientId.equals(clientId))).go(); } diff --git a/lib/src/event.dart b/lib/src/event.dart index 6021fd1..e3699d7 100644 --- a/lib/src/event.dart +++ b/lib/src/event.dart @@ -418,7 +418,7 @@ class Event { } Future loadSession() { - return room.loadInboundGroupSessionKeyForEvent(this); + return room.loadInboundGroupSessionKeyForEvent(this); } /// Trys to decrypt this event. Returns a m.bad.encrypted event diff --git a/lib/src/presence.dart b/lib/src/presence.dart index 4d1f854..e351259 100644 --- a/lib/src/presence.dart +++ b/lib/src/presence.dart @@ -42,7 +42,15 @@ class Presence { final String statusMsg; final DateTime time; - Presence({this.sender, this.displayname, this.avatarUrl, this.currentlyActive, this.lastActiveAgo, this.presence, this.statusMsg, this.time}); + Presence( + {this.sender, + this.displayname, + this.avatarUrl, + this.currentlyActive, + this.lastActiveAgo, + this.presence, + this.statusMsg, + this.time}); Presence.fromJson(Map json) : sender = json['sender'], @@ -66,15 +74,16 @@ class Presence { return Presence( sender: dbEntry.sender, displayname: content['displayname'], - avatarUrl: content['avatar_url'] != null ? Uri.parse(content['avatar_url']) : null, + avatarUrl: content['avatar_url'] != null + ? Uri.parse(content['avatar_url']) + : null, currentlyActive: content['currently_active'], lastActiveAgo: content['last_active_ago'], time: DateTime.fromMillisecondsSinceEpoch( DateTime.now().millisecondsSinceEpoch - (content['last_active_ago'] ?? 0)), presence: PresenceType.values.firstWhere( - (e) => - e.toString() == "PresenceType.${content['presence']}", + (e) => e.toString() == "PresenceType.${content['presence']}", orElse: () => null), statusMsg: content['status_msg'], ); diff --git a/lib/src/room.dart b/lib/src/room.dart index 1ee4125..4d199ea 100644 --- a/lib/src/room.dart +++ b/lib/src/room.dart @@ -112,7 +112,8 @@ class Room { } Future updateSortOrder() async { - await client.database?.updateRoomSortOrder(_oldestSortOrder, _newestSortOrder, client.id, id); + await client.database?.updateRoomSortOrder( + _oldestSortOrder, _newestSortOrder, client.id, id); } /// Clears the existing outboundGroupSession, tries to create a new one and @@ -165,10 +166,12 @@ class Room { Future _storeOutboundGroupSession() async { if (_outboundGroupSession == null) return; await client.database?.storeOutboundGroupSession( - client.id, id, _outboundGroupSession.pickle(client.userID), - json.encode(_outboundGroupSessionDevices), _outboundGroupSessionCreationTime, - _outboundGroupSessionSentMessages - ); + client.id, + id, + _outboundGroupSession.pickle(client.userID), + json.encode(_outboundGroupSessionDevices), + _outboundGroupSessionCreationTime, + _outboundGroupSessionSentMessages); return; } @@ -190,21 +193,27 @@ class Room { } // next check if it needs to be rotated final encryptionContent = getState('m.room.encryption')?.content; - final maxMessages = encryptionContent != null && encryptionContent['rotation_period_msgs'] is int - ? encryptionContent['rotation_period_msgs'] - : 100; - final maxAge = encryptionContent != null && encryptionContent['rotation_period_ms'] is int - ? encryptionContent['rotation_period_ms'] - : 604800000; // default of one week + final maxMessages = encryptionContent != null && + encryptionContent['rotation_period_msgs'] is int + ? encryptionContent['rotation_period_msgs'] + : 100; + final maxAge = encryptionContent != null && + encryptionContent['rotation_period_ms'] is int + ? encryptionContent['rotation_period_ms'] + : 604800000; // default of one week if (_outboundGroupSessionSentMessages >= maxMessages || - _outboundGroupSessionCreationTime.add(Duration(milliseconds: maxAge)).isBefore(DateTime.now())) { + _outboundGroupSessionCreationTime + .add(Duration(milliseconds: maxAge)) + .isBefore(DateTime.now())) { wipe = true; } if (!wipe) { return false; } } - if (!wipe && _outboundGroupSessionDevices == null && _outboundGroupSession == null) { + if (!wipe && + _outboundGroupSessionDevices == null && + _outboundGroupSession == null) { return true; // let's just short-circuit out of here, no need to do DB stuff } _outboundGroupSessionDevices = null; @@ -250,8 +259,12 @@ class Room { indexes: {}, key: client.userID, ); - client.database?.storeInboundGroupSession(client.id, id, sessionId, - inboundGroupSession.pickle(client.userID), json.encode(content), + client.database?.storeInboundGroupSession( + client.id, + id, + sessionId, + inboundGroupSession.pickle(client.userID), + json.encode(content), json.encode({}), ); _tryAgainDecryptLastMessage(); @@ -398,8 +411,7 @@ class Room { states.forEach((final String key, final entry) { if (!entry.containsKey('')) return; final Event state = entry['']; - if (state.sortOrder != null && - state.sortOrder > lastSortOrder) { + if (state.sortOrder != null && state.sortOrder > lastSortOrder) { lastSortOrder = state.sortOrder; lastEvent = state; } @@ -436,7 +448,8 @@ class Room { this.roomAccountData = const {}, double newestSortOrder = 0.0, double oldestSortOrder = 0.0, - }) : _newestSortOrder = newestSortOrder, _oldestSortOrder = oldestSortOrder; + }) : _newestSortOrder = newestSortOrder, + _oldestSortOrder = oldestSortOrder; /// The default count of how much events should be requested when requesting the /// history of this room. @@ -524,7 +537,8 @@ class Room { name = name.replaceAll(RegExp(r'[^\w-]'), ''); return name.toLowerCase(); }; - final addEmotePack = (String packName, Map content, [String packNameOverride]) { + final addEmotePack = (String packName, Map content, + [String packNameOverride]) { if (!(content['short'] is Map)) { return; } @@ -572,7 +586,10 @@ class Room { final stateKey = stateKeyEntry.key; final event = room.getState('im.ponies.room_emotes', stateKey); if (event != null && stateKeyEntry.value is Map) { - addEmotePack(room.canonicalAlias.isEmpty ? room.id : canonicalAlias, event.content, stateKeyEntry.value['name']); + addEmotePack( + room.canonicalAlias.isEmpty ? room.id : canonicalAlias, + event.content, + stateKeyEntry.value['name']); } } } @@ -583,7 +600,11 @@ class Room { /// Sends a normal text message to this room. Returns the event ID generated /// by the server for this message. - Future sendTextEvent(String message, {String txid, Event inReplyTo, bool parseMarkdown = true, Map> emotePacks}) { + Future sendTextEvent(String message, + {String txid, + Event inReplyTo, + bool parseMarkdown = true, + Map> emotePacks}) { final event = { 'msgtype': 'm.text', 'body': message, @@ -836,7 +857,11 @@ class Room { final sortOrder = newSortOrder; // Display a *sending* event and store it. - var eventUpdate = EventUpdate(type: 'timeline', roomID: id, eventType: type, sortOrder: sortOrder, + var eventUpdate = EventUpdate( + type: 'timeline', + roomID: id, + eventType: type, + sortOrder: sortOrder, content: { 'type': type, 'event_id': messageID, @@ -994,7 +1019,8 @@ class Room { final dbActions = Function()>[]; if (client.database != null) { - dbActions.add(() => client.database.setRoomPrevBatch(prev_batch, client.id, id)); + dbActions.add( + () => client.database.setRoomPrevBatch(prev_batch, client.id, id)); } if (!(resp['chunk'] is List && @@ -1012,7 +1038,8 @@ class Room { ).decrypt(this); client.onEvent.add(eventUpdate); if (client.database != null) { - dbActions.add(() => client.database.storeEventUpdate(client.id, eventUpdate)); + dbActions.add( + () => client.database.storeEventUpdate(client.id, eventUpdate)); } } } @@ -1028,11 +1055,13 @@ class Room { ).decrypt(this); client.onEvent.add(eventUpdate); if (client.database != null) { - dbActions.add(() => client.database.storeEventUpdate(client.id, eventUpdate)); + dbActions.add( + () => client.database.storeEventUpdate(client.id, eventUpdate)); } } if (client.database != null) { - dbActions.add(() => client.database.setRoomPrevBatch(resp['end'], client.id, id)); + dbActions.add( + () => client.database.setRoomPrevBatch(resp['end'], client.id, id)); } await client.database?.transaction(() async { for (final f in dbActions) { @@ -1105,12 +1134,12 @@ class Room { /// Returns a Room from a json String which comes normally from the store. If the /// state are also given, the method will await them. static Future getRoomFromTableRow( - DbRoom row, // either Map or DbRoom - Client matrix, - { - dynamic states, // DbRoomState, as iterator and optionally as future - dynamic roomAccountData, // DbRoomAccountData, as iterator and optionally as future - }) async { + DbRoom row, // either Map or DbRoom + Client matrix, { + dynamic states, // DbRoomState, as iterator and optionally as future + dynamic + roomAccountData, // DbRoomAccountData, as iterator and optionally as future + }) async { final newRoom = Room( id: row.roomId, membership: Membership.values @@ -1136,7 +1165,8 @@ class Room { rawStates = states; } for (final rawState in rawStates) { - final newState = Event.fromDb(rawState, newRoom);; + final newState = Event.fromDb(rawState, newRoom); + ; newRoom.setState(newState); } } @@ -1179,7 +1209,8 @@ class Room { await events[i].loadSession(); events[i] = events[i].decrypted; if (events[i].type != EventTypes.Encrypted) { - await client.database.storeEventUpdate(client.id, + await client.database.storeEventUpdate( + client.id, EventUpdate( eventType: events[i].typeKey, content: events[i].toJson(), @@ -1301,7 +1332,8 @@ class Room { 'content': resp, 'state_key': mxID, }; - await client.database.storeEventUpdate(client.id, + await client.database.storeEventUpdate( + client.id, EventUpdate( content: content, roomID: id, @@ -1748,28 +1780,29 @@ class Room { var users = await requestParticipants(); for (final user in users) { if (client.userDeviceKeys.containsKey(user.id)) { - for (var deviceKeyEntry in client.userDeviceKeys[user.id].deviceKeys.values) { + for (var deviceKeyEntry + in client.userDeviceKeys[user.id].deviceKeys.values) { deviceKeys.add(deviceKeyEntry); } } } return deviceKeys; } - + bool _restoredOutboundGroupSession = false; Future restoreOutboundGroupSession() async { if (_restoredOutboundGroupSession || client.database == null) { return; } - final outboundSession = await client.database.getDbOutboundGroupSession(client.id, id); + final outboundSession = + await client.database.getDbOutboundGroupSession(client.id, id); if (outboundSession != null) { try { _outboundGroupSession = olm.OutboundGroupSession(); - _outboundGroupSession.unpickle( - client.userID, outboundSession.pickle); + _outboundGroupSession.unpickle(client.userID, outboundSession.pickle); _outboundGroupSessionDevices = - List.from(json.decode(outboundSession.deviceIds)); + List.from(json.decode(outboundSession.deviceIds)); _outboundGroupSessionCreationTime = outboundSession.creationTime; _outboundGroupSessionSentMessages = outboundSession.sentMessages; } catch (e) { @@ -1825,46 +1858,53 @@ class Room { Future requestSessionKey(String sessionId, String senderKey) async { final users = await requestParticipants(); await client.sendToDevice( - [], - 'm.room_key_request', - { - 'action': 'request_cancellation', - 'request_id': base64.encode(utf8.encode(sessionId)), - 'requesting_device_id': client.deviceID, - }, - encrypted: false, - toUsers: users); - await client.sendToDevice( - [], - 'm.room_key_request', - { - 'action': 'request', - 'body': { - 'algorithm': 'm.megolm.v1.aes-sha2', - 'room_id': id, - 'sender_key': senderKey, - 'session_id': sessionId, + [], + 'm.room_key_request', + { + 'action': 'request_cancellation', + 'request_id': base64.encode(utf8.encode(sessionId)), + 'requesting_device_id': client.deviceID, }, - 'request_id': base64.encode(utf8.encode(sessionId)), - 'requesting_device_id': client.deviceID, - }, - encrypted: false, - toUsers: users); + encrypted: false, + toUsers: users); + await client.sendToDevice( + [], + 'm.room_key_request', + { + 'action': 'request', + 'body': { + 'algorithm': 'm.megolm.v1.aes-sha2', + 'room_id': id, + 'sender_key': senderKey, + 'session_id': sessionId, + }, + 'request_id': base64.encode(utf8.encode(sessionId)), + 'requesting_device_id': client.deviceID, + }, + encrypted: false, + toUsers: users); } - Future loadInboundGroupSessionKey(String sessionId, [String senderKey]) async { - if (sessionId == null || inboundGroupSessions.containsKey(sessionId)) return; // nothing to do - final session = await client.database.getDbInboundGroupSession(client.id, id, sessionId); + Future loadInboundGroupSessionKey(String sessionId, + [String senderKey]) async { + if (sessionId == null || inboundGroupSessions.containsKey(sessionId)) { + return; + } // nothing to do + final session = await client.database + .getDbInboundGroupSession(client.id, id, sessionId); if (session == null) { // no session found, let's request it! - if (client.enableE2eeRecovery && !_requestedSessionIds.contains(sessionId) && senderKey != null) { + if (client.enableE2eeRecovery && + !_requestedSessionIds.contains(sessionId) && + senderKey != null) { unawaited(requestSessionKey(sessionId, senderKey)); _requestedSessionIds.add(sessionId); } return; } try { - _inboundGroupSessions[sessionId] = SessionKey.fromDb(session, client.userID); + _inboundGroupSessions[sessionId] = + SessionKey.fromDb(session, client.userID); } catch (e) { print('[LibOlm] Could not unpickle inboundGroupSession: ' + e.toString()); } @@ -1887,7 +1927,8 @@ class Room { /// Returns a m.bad.encrypted event if it fails and does nothing if the event /// was not encrypted. Event decryptGroupMessage(Event event) { - if (event.type != EventTypes.Encrypted || event.content['ciphertext'] == null) return event; + if (event.type != EventTypes.Encrypted || + event.content['ciphertext'] == null) return event; Map decryptedPayload; try { if (!client.encryptionEnabled) { @@ -1905,7 +1946,9 @@ class Room { .decrypt(event.content['ciphertext']); final messageIndexKey = event.eventId + event.time.millisecondsSinceEpoch.toString(); - if (inboundGroupSessions[sessionId].indexes.containsKey(messageIndexKey) && + if (inboundGroupSessions[sessionId] + .indexes + .containsKey(messageIndexKey) && inboundGroupSessions[sessionId].indexes[messageIndexKey] != decryptResult.message_index) { if ((_outboundGroupSession?.session_id() ?? '') == sessionId) { @@ -1919,11 +1962,17 @@ class Room { // the entry should always exist. In the case it doesn't, the following // line *could* throw an error. As that is a future, though, and we call // it un-awaited here, nothing happens, which is exactly the result we want - client.database?.updateInboundGroupSessionIndexes(json.encode(inboundGroupSessions[sessionId].indexes), client.id, id, sessionId); + client.database?.updateInboundGroupSessionIndexes( + json.encode(inboundGroupSessions[sessionId].indexes), + client.id, + id, + sessionId); decryptedPayload = json.decode(decryptResult.plaintext); } catch (exception) { // alright, if this was actually by our own outbound group session, we might as well clear it - if (client.enableE2eeRecovery && (_outboundGroupSession?.session_id() ?? '') == event.content['session_id']) { + if (client.enableE2eeRecovery && + (_outboundGroupSession?.session_id() ?? '') == + event.content['session_id']) { clearOutboundGroupSession(wipe: true); } if (exception.toString() == DecryptError.UNKNOWN_SESSION) { diff --git a/lib/src/sync/event_update.dart b/lib/src/sync/event_update.dart index c907594..51ff868 100644 --- a/lib/src/sync/event_update.dart +++ b/lib/src/sync/event_update.dart @@ -43,7 +43,8 @@ class EventUpdate { // the order where to stort this event final double sortOrder; - EventUpdate({this.eventType, this.roomID, this.type, this.content, this.sortOrder}); + EventUpdate( + {this.eventType, this.roomID, this.type, this.content, this.sortOrder}); EventUpdate decrypt(Room room) { if (eventType != 'm.room.encrypted') { diff --git a/lib/src/timeline.dart b/lib/src/timeline.dart index 9cb0762..9ff2a87 100644 --- a/lib/src/timeline.dart +++ b/lib/src/timeline.dart @@ -83,7 +83,9 @@ class Timeline { // as r.limitedTimeline can be "null" sometimes, we need to check for == true // as after receiving a limited timeline room update new events are expected // to be received via the onEvent stream, it is unneeded to call sortAndUpdate - roomSub ??= room.client.onRoomUpdate.stream.where((r) => r.id == room.id && r.limitedTimeline == true).listen((r) => events.clear()); + roomSub ??= room.client.onRoomUpdate.stream + .where((r) => r.id == room.id && r.limitedTimeline == true) + .listen((r) => events.clear()); sessionIdReceivedSub ??= room.onSessionKeyReceived.stream.listen(_sessionKeyReceived); } @@ -129,8 +131,8 @@ class Timeline { if (eventUpdate.eventType == 'm.room.redaction') { final eventId = _findEvent(event_id: eventUpdate.content['redacts']); if (eventId != null) { - events[eventId] - .setRedactionEvent(Event.fromJson(eventUpdate.content, room, eventUpdate.sortOrder)); + events[eventId].setRedactionEvent(Event.fromJson( + eventUpdate.content, room, eventUpdate.sortOrder)); } } else if (eventUpdate.content['status'] == -2) { var i = _findEvent(event_id: eventUpdate.content['event_id']); @@ -146,18 +148,23 @@ class Timeline { : null); if (i < events.length) { - events[i] = Event.fromJson(eventUpdate.content, room, eventUpdate.sortOrder); + events[i] = Event.fromJson( + eventUpdate.content, room, eventUpdate.sortOrder); } } else { Event newEvent; - var senderUser = room.getState('m.room.member', eventUpdate.content['sender'])?.asUser ?? await room.client.database - ?.getUser(room.client.id, eventUpdate.content['sender'], room); + var senderUser = room + .getState('m.room.member', eventUpdate.content['sender']) + ?.asUser ?? + await room.client.database?.getUser( + room.client.id, eventUpdate.content['sender'], room); if (senderUser != null) { eventUpdate.content['displayname'] = senderUser.displayName; eventUpdate.content['avatar_url'] = senderUser.avatarUrl.toString(); } - newEvent = Event.fromJson(eventUpdate.content, room, eventUpdate.sortOrder); + newEvent = + Event.fromJson(eventUpdate.content, room, eventUpdate.sortOrder); if (eventUpdate.type == 'history' && events.indexWhere( diff --git a/lib/src/utils/device_keys_list.dart b/lib/src/utils/device_keys_list.dart index 8cacbdc..68eb8e3 100644 --- a/lib/src/utils/device_keys_list.dart +++ b/lib/src/utils/device_keys_list.dart @@ -10,7 +10,8 @@ class DeviceKeysList { bool outdated = true; Map deviceKeys = {}; - DeviceKeysList.fromDb(DbUserDeviceKey dbEntry, List childEntries) { + DeviceKeysList.fromDb( + DbUserDeviceKey dbEntry, List childEntries) { userId = dbEntry.userId; outdated = dbEntry.outdated; deviceKeys = {}; @@ -67,11 +68,16 @@ class DeviceKeys { String get curve25519Key => keys['curve25519:$deviceId']; String get ed25519Key => keys['ed25519:$deviceId']; - bool get isValid => userId != null && deviceId != null && curve25519Key != null && ed25519Key != null; + bool get isValid => + userId != null && + deviceId != null && + curve25519Key != null && + ed25519Key != null; Future setVerified(bool newVerified, Client client) { verified = newVerified; - return client.database?.setVerifiedUserDeviceKey(newVerified, client.id, userId, deviceId); + return client.database + ?.setVerifiedUserDeviceKey(newVerified, client.id, userId, deviceId); } Future setBlocked(bool newBlocked, Client client) { @@ -82,7 +88,8 @@ class DeviceKeys { room.clearOutboundGroupSession(); } } - return client.database?.setBlockedUserDeviceKey(newBlocked, client.id, userId, deviceId); + return client.database + ?.setBlockedUserDeviceKey(newBlocked, client.id, userId, deviceId); } DeviceKeys({ @@ -101,7 +108,9 @@ class DeviceKeys { userId = dbEntry.userId; deviceId = dbEntry.deviceId; algorithms = content['algorithms'].cast(); - keys = content['keys'] != null ? Map.from(content['keys']) : null; + keys = content['keys'] != null + ? Map.from(content['keys']) + : null; signatures = content['signatures'] != null ? Map.from(content['signatures']) : null; @@ -147,7 +156,8 @@ class DeviceKeys { } KeyVerification startVerification(Client client) { - final request = KeyVerification(client: client, userId: userId, deviceId: deviceId); + final request = + KeyVerification(client: client, userId: userId, deviceId: deviceId); request.start(); client.addKeyVerificationRequest(request); return request; diff --git a/lib/src/utils/key_verification.dart b/lib/src/utils/key_verification.dart index 5735d50..b6fdf64 100644 --- a/lib/src/utils/key_verification.dart +++ b/lib/src/utils/key_verification.dart @@ -42,7 +42,14 @@ import '../room.dart'; | | */ -enum KeyVerificationState { askAccept, waitingAccept, askSas, waitingSas, done, error } +enum KeyVerificationState { + askAccept, + waitingAccept, + askSas, + waitingSas, + done, + error +} List _intersect(List a, List b) { final res = []; @@ -76,7 +83,8 @@ List _bytesToInt(Uint8List bytes, int totalBits) { final VERIFICATION_METHODS = [_KeyVerificationMethodSas.type]; -_KeyVerificationMethod _makeVerificationMethod(String type, KeyVerification request) { +_KeyVerificationMethod _makeVerificationMethod( + String type, KeyVerification request) { if (type == _KeyVerificationMethodSas.type) { return _KeyVerificationMethodSas(request: request); } @@ -89,7 +97,7 @@ class KeyVerification { final Room room; final String userId; void Function() onUpdate; - String get deviceId => _deviceId; + String get deviceId => _deviceId; String _deviceId; bool startedVerification = false; _KeyVerificationMethod method; @@ -104,7 +112,8 @@ class KeyVerification { String canceledCode; String canceledReason; - KeyVerification({this.client, this.room, this.userId, String deviceId, this.onUpdate}) { + KeyVerification( + {this.client, this.room, this.userId, String deviceId, this.onUpdate}) { lastActivity = DateTime.now(); _deviceId ??= deviceId; } @@ -115,9 +124,10 @@ class KeyVerification { } static String getTransactionId(Map payload) { - return payload['transaction_id'] ?? ( - payload['m.relates_to'] is Map ? payload['m.relates_to']['event_id'] : null - ); + return payload['transaction_id'] ?? + (payload['m.relates_to'] is Map + ? payload['m.relates_to']['event_id'] + : null); } Future start() async { @@ -132,7 +142,8 @@ class KeyVerification { setState(KeyVerificationState.waitingAccept); } - Future handlePayload(String type, Map payload, [String eventId]) async { + Future handlePayload(String type, Map payload, + [String eventId]) async { print('[Key Verification] Received type ${type}: ' + payload.toString()); try { switch (type) { @@ -141,13 +152,16 @@ class KeyVerification { transactionId ??= eventId ?? payload['transaction_id']; // verify the timestamp final now = DateTime.now(); - final verifyTime = DateTime.fromMillisecondsSinceEpoch(payload['timestamp']); - if (now.subtract(Duration(minutes: 10)).isAfter(verifyTime) || now.add(Duration(minutes: 5)).isBefore(verifyTime)) { + final verifyTime = + DateTime.fromMillisecondsSinceEpoch(payload['timestamp']); + if (now.subtract(Duration(minutes: 10)).isAfter(verifyTime) || + now.add(Duration(minutes: 5)).isBefore(verifyTime)) { await cancel('m.timeout'); return; } // verify it has a method we can use - possibleMethods = _intersect(VERIFICATION_METHODS, payload['methods']); + possibleMethods = + _intersect(VERIFICATION_METHODS, payload['methods']); if (possibleMethods.isEmpty) { // reject it outright await cancel('m.unknown_method'); @@ -156,7 +170,8 @@ class KeyVerification { setState(KeyVerificationState.askAccept); break; case 'm.key.verification.ready': - possibleMethods = _intersect(VERIFICATION_METHODS, payload['methods']); + possibleMethods = + _intersect(VERIFICATION_METHODS, payload['methods']); if (possibleMethods.isEmpty) { // reject it outright await cancel('m.unknown_method'); @@ -214,7 +229,8 @@ class KeyVerification { /// called when the user accepts an incoming verification Future acceptVerification() async { - if (!(await verifyLastStep(['m.key.verification.request', 'm.key.verification.start']))) { + if (!(await verifyLastStep( + ['m.key.verification.request', 'm.key.verification.start']))) { return; } setState(KeyVerificationState.waitingAccept); @@ -231,7 +247,8 @@ class KeyVerification { /// called when the user rejects an incoming verification Future rejectVerification() async { - if (!(await verifyLastStep(['m.key.verification.request', 'm.key.verification.start']))) { + if (!(await verifyLastStep( + ['m.key.verification.request', 'm.key.verification.start']))) { return; } await cancel('m.user'); @@ -251,7 +268,9 @@ class KeyVerification { List get sasNumbers { if (method is _KeyVerificationMethodSas) { - return _bytesToInt((method as _KeyVerificationMethodSas).makeSas(5), 13).map((n) => n + 1000).toList(); + return _bytesToInt((method as _KeyVerificationMethodSas).makeSas(5), 13) + .map((n) => n + 1000) + .toList(); } return []; } @@ -265,13 +284,15 @@ class KeyVerification { List get sasEmojis { if (method is _KeyVerificationMethodSas) { - final numbers = _bytesToInt((method as _KeyVerificationMethodSas).makeSas(6), 6); + final numbers = + _bytesToInt((method as _KeyVerificationMethodSas).makeSas(6), 6); return numbers.map((n) => KeyVerificationEmoji(n)).toList().sublist(0, 7); } return []; } - Future verifyKeys(Map keys, Future Function(String, DeviceKeys) verifier) async { + Future verifyKeys(Map keys, + Future Function(String, DeviceKeys) verifier) async { final verifiedDevices = []; if (!client.userDeviceKeys.containsKey(userId)) { @@ -282,8 +303,10 @@ class KeyVerification { final keyId = entry.key; final verifyDeviceId = keyId.substring('ed25519:'.length); final keyInfo = entry.value; - if (client.userDeviceKeys[userId].deviceKeys.containsKey(verifyDeviceId)) { - if (!(await verifier(keyInfo, client.userDeviceKeys[userId].deviceKeys[verifyDeviceId]))) { + if (client.userDeviceKeys[userId].deviceKeys + .containsKey(verifyDeviceId)) { + if (!(await verifier(keyInfo, + client.userDeviceKeys[userId].deviceKeys[verifyDeviceId]))) { await cancel('m.key_mismatch'); return; } @@ -295,12 +318,14 @@ class KeyVerification { } // okay, we reached this far, so all the devices are verified! for (final verifyDeviceId in verifiedDevices) { - await client.userDeviceKeys[userId].deviceKeys[verifyDeviceId].setVerified(true, client); + await client.userDeviceKeys[userId].deviceKeys[verifyDeviceId] + .setVerified(true, client); } } Future verifyActivity() async { - if (lastActivity != null && lastActivity.add(Duration(minutes: 10)).isAfter(DateTime.now())) { + if (lastActivity != null && + lastActivity.add(Duration(minutes: 10)).isAfter(DateTime.now())) { lastActivity = DateTime.now(); return true; } @@ -351,7 +376,8 @@ class KeyVerification { if (['m.key.verification.request'].contains(type)) { payload['msgtype'] = type; payload['to'] = userId; - payload['body'] = 'Attempting verification request. (${type}) Apparently your client doesn\'t support this'; + payload['body'] = + 'Attempting verification request. (${type}) Apparently your client doesn\'t support this'; type = 'm.room.message'; } final newTransactionId = await room.sendEvent(payload, type: type); @@ -360,7 +386,8 @@ class KeyVerification { client.addKeyVerificationRequest(this); } } else { - await client.sendToDevice([client.userDeviceKeys[userId].deviceKeys[deviceId]], type, payload); + await client.sendToDevice( + [client.userDeviceKeys[userId].deviceKeys[deviceId]], type, payload); } } @@ -385,6 +412,7 @@ abstract class _KeyVerificationMethod { bool validateStart(Map payload) { return false; } + Future sendStart(); void dispose() {} } @@ -395,7 +423,8 @@ const KNOWN_MESSAGE_AUTHENTIFICATION_CODES = ['hkdf-hmac-sha256']; const KNOWN_AUTHENTICATION_TYPES = ['emoji', 'decimal']; class _KeyVerificationMethodSas extends _KeyVerificationMethod { - _KeyVerificationMethodSas({KeyVerification request}) : super(request: request); + _KeyVerificationMethodSas({KeyVerification request}) + : super(request: request); static String type = 'm.sas.v1'; @@ -419,7 +448,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { try { switch (type) { case 'm.key.verification.start': - if (!(await request.verifyLastStep(['m.key.verification.request', 'm.key.verification.start']))) { + if (!(await request.verifyLastStep( + ['m.key.verification.request', 'm.key.verification.start']))) { return; // abort } if (!validateStart(payload)) { @@ -439,7 +469,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { await _sendKey(); break; case 'm.key.verification.key': - if (!(await request.verifyLastStep(['m.key.verification.accept', 'm.key.verification.start']))) { + if (!(await request.verifyLastStep( + ['m.key.verification.accept', 'm.key.verification.start']))) { return; } _handleKey(payload); @@ -506,7 +537,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { if (payload['method'] != type) { return false; } - final possibleKeyAgreementProtocols = _intersect(KNOWN_KEY_AGREEMENT_PROTOCOLS, payload['key_agreement_protocols']); + final possibleKeyAgreementProtocols = _intersect( + KNOWN_KEY_AGREEMENT_PROTOCOLS, payload['key_agreement_protocols']); if (possibleKeyAgreementProtocols.isEmpty) { return false; } @@ -516,12 +548,15 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { return false; } hash = possibleHashes.first; - final possibleMessageAuthenticationCodes = _intersect(KNOWN_MESSAGE_AUTHENTIFICATION_CODES, payload['message_authentication_codes']); + final possibleMessageAuthenticationCodes = _intersect( + KNOWN_MESSAGE_AUTHENTIFICATION_CODES, + payload['message_authentication_codes']); if (possibleMessageAuthenticationCodes.isEmpty) { return false; } messageAuthenticationCode = possibleMessageAuthenticationCodes.first; - final possibleAuthenticationTypes = _intersect(KNOWN_AUTHENTICATION_TYPES, payload['short_authentication_string']); + final possibleAuthenticationTypes = _intersect( + KNOWN_AUTHENTICATION_TYPES, payload['short_authentication_string']); if (possibleAuthenticationTypes.isEmpty) { return false; } @@ -544,7 +579,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { } bool _handleAccept(Map payload) { - if (!KNOWN_KEY_AGREEMENT_PROTOCOLS.contains(payload['key_agreement_protocol'])) { + if (!KNOWN_KEY_AGREEMENT_PROTOCOLS + .contains(payload['key_agreement_protocol'])) { return false; } keyAgreementProtocol = payload['key_agreement_protocol']; @@ -552,11 +588,13 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { return false; } hash = payload['hash']; - if (!KNOWN_MESSAGE_AUTHENTIFICATION_CODES.contains(payload['message_authentication_code'])) { + if (!KNOWN_MESSAGE_AUTHENTIFICATION_CODES + .contains(payload['message_authentication_code'])) { return false; } messageAuthenticationCode = payload['message_authentication_code']; - final possibleAuthenticationTypes = _intersect(KNOWN_AUTHENTICATION_TYPES, payload['short_authentication_string']); + final possibleAuthenticationTypes = _intersect( + KNOWN_AUTHENTICATION_TYPES, payload['short_authentication_string']); if (possibleAuthenticationTypes.isEmpty) { return false; } @@ -585,13 +623,23 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { Uint8List makeSas(int bytes) { var sasInfo = ''; if (keyAgreementProtocol == 'curve25519-hkdf-sha256') { - final ourInfo = '${client.userID}|${client.deviceID}|${sas.get_pubkey()}|'; - final theirInfo = '${request.userId}|${request.deviceId}|${theirPublicKey}|'; - sasInfo = 'MATRIX_KEY_VERIFICATION_SAS|' + (request.startedVerification ? ourInfo + theirInfo : theirInfo + ourInfo) + request.transactionId; + final ourInfo = + '${client.userID}|${client.deviceID}|${sas.get_pubkey()}|'; + final theirInfo = + '${request.userId}|${request.deviceId}|${theirPublicKey}|'; + sasInfo = 'MATRIX_KEY_VERIFICATION_SAS|' + + (request.startedVerification + ? ourInfo + theirInfo + : theirInfo + ourInfo) + + request.transactionId; } else if (keyAgreementProtocol == 'curve25519') { final ourInfo = client.userID + client.deviceID; final theirInfo = request.userId + request.deviceId; - sasInfo = 'MATRIX_KEY_VERIFICATION_SAS' + (request.startedVerification ? ourInfo + theirInfo : theirInfo + ourInfo) + request.transactionId; + sasInfo = 'MATRIX_KEY_VERIFICATION_SAS' + + (request.startedVerification + ? ourInfo + theirInfo + : theirInfo + ourInfo) + + request.transactionId; } else { throw 'Unknown key agreement protocol'; } @@ -600,9 +648,11 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { Future _sendMac() async { final baseInfo = 'MATRIX_KEY_VERIFICATION_MAC' + - client.userID + client.deviceID + - request.userId + request.deviceId + - request.transactionId; + client.userID + + client.deviceID + + request.userId + + request.deviceId + + request.transactionId; final mac = {}; final keyList = []; @@ -610,7 +660,8 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { // for now it is just our device key, once we have cross-signing // we would also add the cross signing key here final deviceKeyId = 'ed25519:${client.deviceID}'; - mac[deviceKeyId] = _calculateMac(client.fingerprintKey, baseInfo + deviceKeyId); + mac[deviceKeyId] = + _calculateMac(client.fingerprintKey, baseInfo + deviceKeyId); keyList.add(deviceKeyId); keyList.sort(); @@ -624,13 +675,16 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { Future _processMac() async { final payload = macPayload; final baseInfo = 'MATRIX_KEY_VERIFICATION_MAC' + - request.userId + request.deviceId + - client.userID + client.deviceID + - request.transactionId; + request.userId + + request.deviceId + + client.userID + + client.deviceID + + request.transactionId; final keyList = payload['mac'].keys.toList(); keyList.sort(); - if (payload['keys'] != _calculateMac(keyList.join(','), baseInfo + 'KEY_IDS')) { + if (payload['keys'] != + _calculateMac(keyList.join(','), baseInfo + 'KEY_IDS')) { await request.cancel('m.key_mismatch'); return; } @@ -646,7 +700,9 @@ class _KeyVerificationMethodSas extends _KeyVerificationMethod { } } await request.verifyKeys(mac, (String mac, DeviceKeys device) async { - return mac == _calculateMac(device.ed25519Key, baseInfo + 'ed25519:' + device.deviceId); + return mac == + _calculateMac( + device.ed25519Key, baseInfo + 'ed25519:' + device.deviceId); }); await request.send('m.key.verification.done', {}); if (request.state != KeyVerificationState.error) { diff --git a/lib/src/utils/markdown.dart b/lib/src/utils/markdown.dart index b9ae5df..b294473 100644 --- a/lib/src/utils/markdown.dart +++ b/lib/src/utils/markdown.dart @@ -13,11 +13,12 @@ class LinebreakSyntax extends InlineSyntax { class SpoilerSyntax extends TagSyntax { Map reasonMap = {}; - SpoilerSyntax() : super( - r'\|\|(?:([^\|]+)\|(?!\|))?', - requiresDelimiterRun: true, - end: r'\|\|', - ); + SpoilerSyntax() + : super( + r'\|\|(?:([^\|]+)\|(?!\|))?', + requiresDelimiterRun: true, + end: r'\|\|', + ); @override bool onMatch(InlineParser parser, Match match) { @@ -31,7 +32,8 @@ class SpoilerSyntax extends TagSyntax { @override bool onMatchEnd(InlineParser parser, Match match, TagState state) { final element = Element('span', state.children); - element.attributes['data-mx-spoiler'] = htmlEscape.convert(reasonMap[match.input] ?? ''); + element.attributes['data-mx-spoiler'] = + htmlEscape.convert(reasonMap[match.input] ?? ''); parser.addNode(element); return true; } @@ -88,14 +90,33 @@ class PillSyntax extends InlineSyntax { String markdown(String text, [Map> emotePacks]) { emotePacks ??= >{}; - var ret = markdownToHtml(text, + var ret = markdownToHtml( + text, extensionSet: ExtensionSet.commonMark, - inlineSyntaxes: [StrikethroughSyntax(), LinebreakSyntax(), SpoilerSyntax(), EmoteSyntax(emotePacks), PillSyntax()], + inlineSyntaxes: [ + StrikethroughSyntax(), + LinebreakSyntax(), + SpoilerSyntax(), + EmoteSyntax(emotePacks), + PillSyntax() + ], ); - + var stripPTags = '

'.allMatches(ret).length <= 1; if (stripPTags) { - final otherBlockTags = ['table', 'pre', 'ol', 'ul', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'blockquote']; + final otherBlockTags = [ + 'table', + 'pre', + 'ol', + 'ul', + 'h1', + 'h2', + 'h3', + 'h4', + 'h5', + 'h6', + 'blockquote' + ]; for (final tag in otherBlockTags) { // we check for the close tag as the opening one might have attributes if (ret.contains('')) { diff --git a/lib/src/utils/room_key_request.dart b/lib/src/utils/room_key_request.dart index d839daa..8f3d1bd 100644 --- a/lib/src/utils/room_key_request.dart +++ b/lib/src/utils/room_key_request.dart @@ -25,8 +25,9 @@ class RoomKeyRequest extends ToDeviceEvent { await requestingDevice?.setVerified(true, client); var message = session.content; message['forwarding_curve25519_key_chain'] = forwardedKeys; - - message['session_key'] = session.inboundGroupSession.export_session(session.inboundGroupSession.first_known_index()); + + message['session_key'] = session.inboundGroupSession + .export_session(session.inboundGroupSession.first_known_index()); await client.sendToDevice( [requestingDevice], 'm.forwarded_room_key', diff --git a/lib/src/utils/session_key.dart b/lib/src/utils/session_key.dart index b31707c..2155b21 100644 --- a/lib/src/utils/session_key.dart +++ b/lib/src/utils/session_key.dart @@ -20,9 +20,8 @@ class SessionKey { SessionKey.fromDb(DbInboundGroupSession dbEntry, String key) : key = key { final parsedContent = Event.getMapFromPayload(dbEntry.content); final parsedIndexes = Event.getMapFromPayload(dbEntry.indexes); - content = parsedContent != null - ? Map.from(parsedContent) - : null; + content = + parsedContent != null ? Map.from(parsedContent) : null; indexes = parsedIndexes != null ? Map.from(parsedIndexes) : {}; diff --git a/test/markdown_test.dart b/test/markdown_test.dart index 58db060..8ceafcd 100644 --- a/test/markdown_test.dart +++ b/test/markdown_test.dart @@ -15,12 +15,15 @@ void main() { }, }; test('simple markdown', () { - expect(markdown('hey *there* how are **you** doing?'), 'hey there how are you doing?'); + expect(markdown('hey *there* how are **you** doing?'), + 'hey there how are you doing?'); expect(markdown('wha ~~strike~~ works!'), 'wha strike works!'); }); test('spoilers', () { - expect(markdown('Snape killed ||Dumbledoor||'), 'Snape killed Dumbledoor'); - expect(markdown('Snape killed ||Story|Dumbledoor||'), 'Snape killed Dumbledoor'); + expect(markdown('Snape killed ||Dumbledoor||'), + 'Snape killed Dumbledoor'); + expect(markdown('Snape killed ||Story|Dumbledoor||'), + 'Snape killed Dumbledoor'); }); test('multiple paragraphs', () { expect(markdown('Heya!\n\nBeep'), '

Heya!

\n

Beep

'); @@ -32,16 +35,22 @@ void main() { expect(markdown('foxies\ncute'), 'foxies
\ncute'); }); test('emotes', () { - expect(markdown(':fox:', emotePacks), ':fox:'); - expect(markdown(':user~fox:', emotePacks), ':fox:'); - expect(markdown(':raccoon:', emotePacks), ':raccoon:'); + expect(markdown(':fox:', emotePacks), + ':fox:'); + expect(markdown(':user~fox:', emotePacks), + ':fox:'); + expect(markdown(':raccoon:', emotePacks), + ':raccoon:'); expect(markdown(':invalid:', emotePacks), ':invalid:'); expect(markdown(':room~invalid:', emotePacks), ':room~invalid:'); }); test('pills', () { - expect(markdown('Hey @sorunome:sorunome.de!'), 'Hey @sorunome:sorunome.de!'); - expect(markdown('#fox:sorunome.de: you all are awesome'), '#fox:sorunome.de: you all are awesome'); - expect(markdown('!blah:example.org'), '!blah:example.org'); + expect(markdown('Hey @sorunome:sorunome.de!'), + 'Hey @sorunome:sorunome.de!'); + expect(markdown('#fox:sorunome.de: you all are awesome'), + '#fox:sorunome.de: you all are awesome'); + expect(markdown('!blah:example.org'), + '!blah:example.org'); }); }); } diff --git a/test_driver/famedlysdk_test.dart b/test_driver/famedlysdk_test.dart index fc145cd..5e9daed 100644 --- a/test_driver/famedlysdk_test.dart +++ b/test_driver/famedlysdk_test.dart @@ -128,7 +128,8 @@ void test() async { await Future.delayed(Duration(seconds: 5)); assert(room.outboundGroupSession != null); var currentSessionIdA = room.outboundGroupSession.session_id(); - assert(room.inboundGroupSessions.containsKey(room.outboundGroupSession.session_id())); + assert(room.inboundGroupSessions + .containsKey(room.outboundGroupSession.session_id())); assert(testClientA.olmSessions[testClientB.identityKey].length == 1); assert(testClientB.olmSessions[testClientA.identityKey].length == 1); assert(testClientA.olmSessions[testClientB.identityKey].first.session_id() == @@ -236,7 +237,8 @@ void test() async { assert(restoredRoom.outboundGroupSession.session_id() == room.outboundGroupSession.session_id()); assert(restoredRoom.inboundGroupSessions.length == 4); - assert(restoredRoom.inboundGroupSessions.length == room.inboundGroupSessions.length); + assert(restoredRoom.inboundGroupSessions.length == + room.inboundGroupSessions.length); for (var i = 0; i < restoredRoom.inboundGroupSessions.length; i++) { assert(restoredRoom.inboundGroupSessions.keys.toList()[i] == room.inboundGroupSessions.keys.toList()[i]);