diff --git a/vector/src/main/java/im/vector/app/core/pushers/PushParser.kt b/vector/src/main/java/im/vector/app/core/pushers/PushParser.kt index b25b97be95..6de8935315 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/PushParser.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/PushParser.kt @@ -20,27 +20,26 @@ import im.vector.app.core.pushers.model.PushData import im.vector.app.core.pushers.model.PushDataFcm import im.vector.app.core.pushers.model.PushDataUnifiedPush import im.vector.app.core.pushers.model.toPushData -import org.json.JSONObject import org.matrix.android.sdk.api.extensions.tryOrNull import org.matrix.android.sdk.api.util.MatrixJsonParser import javax.inject.Inject +/** + * Parse the received data from Push. Json format are different depending on the source. + * + * Notifications received by FCM are formatted by the matrix gateway [1]. The data send to FCM is the content + * of the "notification" attribute of the json sent to the gateway [2][3]. + * On the other side, with UnifiedPush, the content of the message received is the content posted to the push + * gateway endpoint [3]. + * + * *Note*: If we want to get the same content with FCM and unifiedpush, we can do a new sygnal pusher [4]. + * + * [1] https://github.com/matrix-org/sygnal/blob/main/sygnal/gcmpushkin.py + * [2] https://github.com/matrix-org/sygnal/blob/main/sygnal/gcmpushkin.py#L366 + * [3] https://spec.matrix.org/latest/push-gateway-api/ + * [4] https://github.com/p1gp1g/sygnal/blob/unifiedpush/sygnal/upfcmpushkin.py (Not tested for a while) + */ class PushParser @Inject constructor() { - /** - * Parse the received data from Push. Json format are different depending on the source. - * - * Notifications received by FCM are formatted by the matrix gateway [1]. The data send to FCM is the content - * of the "notification" attribute of the json sent to the gateway [2][3]. - * On the other side, with UnifiedPush, the content of the message received is the content posted to the push - * gateway endpoint [3]. - * - * *Note*: If we want to get the same content with FCM and unifiedpush, we can do a new sygnal pusher [4]. - * - * [1] https://github.com/matrix-org/sygnal/blob/main/sygnal/gcmpushkin.py - * [2] https://github.com/matrix-org/sygnal/blob/main/sygnal/gcmpushkin.py#L366 - * [3] https://spec.matrix.org/latest/push-gateway-api/ - * [4] https://github.com/p1gp1g/sygnal/blob/unifiedpush/sygnal/upfcmpushkin.py (Not tested for a while) - */ fun parsePushDataUnifiedPush(message: ByteArray): PushData? { return MatrixJsonParser.getMoshi().let { tryOrNull { it.adapter(PushDataUnifiedPush::class.java).fromJson(String(message)) }?.toPushData() @@ -48,8 +47,16 @@ class PushParser @Inject constructor() { } fun parsePushDataFcm(message: Map<*, *>): PushData? { - return MatrixJsonParser.getMoshi().let { - tryOrNull { it.adapter(PushDataFcm::class.java).fromJson(JSONObject(message).toString()) }?.toPushData() + if ((message.containsKey("event_id") && message["event_id"] !is String) || + message.containsKey("room_id") && message["room_id"] !is String || + message.containsKey("unread") && message["unread"] !is Int) { + return null } + val pushDataFcm = PushDataFcm( + eventId = message["event_id"] as String?, + roomId = message["room_id"] as String?, + unread = message["unread"] as Int?, + ) + return pushDataFcm.toPushData() } } diff --git a/vector/src/main/java/im/vector/app/core/pushers/model/PushDataFcm.kt b/vector/src/main/java/im/vector/app/core/pushers/model/PushDataFcm.kt index 1b9c37ae0a..e78cdf5ff8 100644 --- a/vector/src/main/java/im/vector/app/core/pushers/model/PushDataFcm.kt +++ b/vector/src/main/java/im/vector/app/core/pushers/model/PushDataFcm.kt @@ -16,8 +16,6 @@ package im.vector.app.core.pushers.model -import com.squareup.moshi.Json -import com.squareup.moshi.JsonClass import org.matrix.android.sdk.api.MatrixPatterns /** @@ -32,11 +30,10 @@ import org.matrix.android.sdk.api.MatrixPatterns * </pre> * . */ -@JsonClass(generateAdapter = true) data class PushDataFcm( - @Json(name = "event_id") val eventId: String?, - @Json(name = "room_id") val roomId: String?, - @Json(name = "unread") var unread: Int?, + val eventId: String?, + val roomId: String?, + var unread: Int?, ) fun PushDataFcm.toPushData() = PushData( diff --git a/vector/src/test/java/im/vector/app/core/pushers/PushParserTest.kt b/vector/src/test/java/im/vector/app/core/pushers/PushParserTest.kt index 03577a4400..9dcaa992f8 100644 --- a/vector/src/test/java/im/vector/app/core/pushers/PushParserTest.kt +++ b/vector/src/test/java/im/vector/app/core/pushers/PushParserTest.kt @@ -35,73 +35,89 @@ class PushParserTest { ) @Test - fun `test edge cases`() { - doAllEdgeTests(true) - doAllEdgeTests(false) + fun `test edge cases Firebase`() { + val pushParser = PushParser() + // Empty Json + pushParser.parsePushDataFcm(emptyMap<String, Any>()) shouldBeEqualTo emptyData + // Bad Json + pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("room_id", 5)) shouldBe null + pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("event_id", 5)) shouldBe null + pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("unread", "str")) shouldBe null + // Extra data + pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("extra", 5)) shouldBeEqualTo validData } - private fun doAllEdgeTests(firebaseFormat: Boolean) { + @Test + fun `test edge cases UnifiedPush`() { val pushParser = PushParser() // Empty string - pushParser.parseData("", firebaseFormat) shouldBe null + pushParser.parsePushDataUnifiedPush("".toByteArray()) shouldBe null // Empty Json - pushParser.parseData("{}", firebaseFormat) shouldBeEqualTo emptyData + pushParser.parsePushDataUnifiedPush("{}".toByteArray()) shouldBeEqualTo emptyData // Bad Json - pushParser.parseData("ABC", firebaseFormat) shouldBe null + pushParser.parsePushDataUnifiedPush("ABC".toByteArray()) shouldBe null } @Test - fun `test unified push format`() { + fun `test UnifiedPush format`() { val pushParser = PushParser() - - pushParser.parseData(UNIFIED_PUSH_DATA, false) shouldBeEqualTo validData - pushParser.parseData(UNIFIED_PUSH_DATA, true) shouldBeEqualTo emptyData + pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.toByteArray()) shouldBeEqualTo validData } @Test - fun `test firebase push format`() { + fun `test Firebase format`() { val pushParser = PushParser() - - pushParser.parseData(FIREBASE_PUSH_DATA, true) shouldBeEqualTo validData - pushParser.parseData(FIREBASE_PUSH_DATA, false) shouldBeEqualTo emptyData + pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA) shouldBeEqualTo validData } @Test fun `test empty roomId`() { val pushParser = PushParser() - - pushParser.parseData(FIREBASE_PUSH_DATA.replace("!aRoomId:domain", ""), true) shouldBeEqualTo validData.copy(roomId = null) - pushParser.parseData(UNIFIED_PUSH_DATA.replace("!aRoomId:domain", ""), false) shouldBeEqualTo validData.copy(roomId = null) + val expected = validData.copy(roomId = null) + pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("room_id", "")) shouldBeEqualTo expected + pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.replace("!aRoomId:domain", "").toByteArray()) shouldBeEqualTo expected } @Test fun `test invalid roomId`() { val pushParser = PushParser() - - pushParser.parseData(FIREBASE_PUSH_DATA.replace("!aRoomId:domain", "aRoomId:domain"), true) shouldBeEqualTo validData.copy(roomId = null) - pushParser.parseData(UNIFIED_PUSH_DATA.replace("!aRoomId:domain", "aRoomId:domain"), false) shouldBeEqualTo validData.copy(roomId = null) + val expected = validData.copy(roomId = null) + pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("room_id", "aRoomId:domain")) shouldBeEqualTo expected + pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.mutate("!aRoomId:domain", "aRoomId:domain")) shouldBeEqualTo expected } @Test fun `test empty eventId`() { val pushParser = PushParser() - - pushParser.parseData(FIREBASE_PUSH_DATA.replace("\$anEventId", ""), true) shouldBeEqualTo validData.copy(eventId = null) - pushParser.parseData(UNIFIED_PUSH_DATA.replace("\$anEventId", ""), false) shouldBeEqualTo validData.copy(eventId = null) + val expected = validData.copy(eventId = null) + pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("event_id", "")) shouldBeEqualTo expected + pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.mutate("\$anEventId", "")) shouldBeEqualTo expected } @Test fun `test invalid eventId`() { val pushParser = PushParser() - - pushParser.parseData(FIREBASE_PUSH_DATA.replace("\$anEventId", "anEventId"), true) shouldBeEqualTo validData.copy(eventId = null) - pushParser.parseData(UNIFIED_PUSH_DATA.replace("\$anEventId", "anEventId"), false) shouldBeEqualTo validData.copy(eventId = null) + val expected = validData.copy(eventId = null) + pushParser.parsePushDataFcm(FIREBASE_PUSH_DATA.mutate("event_id", "anEventId")) shouldBeEqualTo expected + pushParser.parsePushDataUnifiedPush(UNIFIED_PUSH_DATA.mutate("\$anEventId", "anEventId")) shouldBeEqualTo expected } companion object { private const val UNIFIED_PUSH_DATA = "{\"notification\":{\"event_id\":\"\$anEventId\",\"room_id\":\"!aRoomId:domain\",\"counts\":{\"unread\":1},\"prio\":\"high\"}}" - private const val FIREBASE_PUSH_DATA = - "{\"event_id\":\"\$anEventId\",\"room_id\":\"!aRoomId:domain\",\"unread\":\"1\",\"prio\":\"high\"}" + private val FIREBASE_PUSH_DATA = mapOf( + "event_id" to "\$anEventId", + "room_id" to "!aRoomId:domain", + "unread" to 1, + "prio" to "high", + ) } } + +private fun <K, V> Map<K, V?>.mutate(key: K, value: V?): Map<K, V?> { + return toMutableMap().apply { put(key, value) } +} + +private fun String.mutate(oldValue: String, newValue: String): ByteArray { + return replace(oldValue, newValue).toByteArray() +}