From 23871ffe2da7cec54dcb04a6d9c1a6ef2d6078b0 Mon Sep 17 00:00:00 2001 From: denbond7 Date: Tue, 22 Apr 2025 11:02:54 +0300 Subject: [PATCH 1/3] Fixed NPE in MessageExt.| #2998 --- .../api/services/gmail/model/MessageExt.kt | 56 +++++++++---------- 1 file changed, 28 insertions(+), 28 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/MessageExt.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/MessageExt.kt index 24d4cc5191..31b9a34d6a 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/MessageExt.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/MessageExt.kt @@ -19,15 +19,15 @@ import jakarta.mail.internet.InternetAddress /** * @author Denys Bondarenko */ -fun Message.hasPgp(): Boolean { - val baseContentType = payload?.headers?.firstOrNull { +fun Message?.hasPgp(): Boolean { + val baseContentType = this?.payload?.headers?.firstOrNull { it?.name == "Content-Type" }?.value?.asContentTypeOrNull() /** * based on https://datatracker.ietf.org/doc/html/rfc3156#section-5 */ - val isOpenPGPMimeSigned = payload?.parts?.size == 2 + val isOpenPGPMimeSigned = this?.payload?.parts?.size == 2 && "multipart/signed" == baseContentType?.baseType?.lowercase() && baseContentType.getParameter("protocol")?.lowercase() == "application/pgp-signature" && baseContentType.getParameter("micalg")?.lowercase()?.startsWith("pgp-") == true @@ -36,68 +36,68 @@ fun Message.hasPgp(): Boolean { * based on https://datatracker.ietf.org/doc/html/rfc3156#section-4 */ val isOpenPGPMimeEncrypted = !isOpenPGPMimeSigned - && payload?.parts?.size == 2 + && this?.payload?.parts?.size == 2 && "multipart/encrypted" == baseContentType?.baseType?.lowercase() && baseContentType.getParameter("protocol")?.lowercase() == "application/pgp-encrypted" - val hasEncryptedParts = payload?.parts?.any { it.hasPgp() } == true + val hasEncryptedParts = this?.payload?.parts?.any { it.hasPgp() } == true - return EmailUtil.hasEncryptedData(snippet) - || EmailUtil.hasSignedData(snippet) + return EmailUtil.hasEncryptedData(this?.snippet) + || EmailUtil.hasSignedData(this?.snippet) || isOpenPGPMimeSigned || isOpenPGPMimeEncrypted || hasEncryptedParts } -fun Message.getRecipients(vararg recipientType: String): List { - return payload?.headers?.firstOrNull { header -> +fun Message?.getRecipients(vararg recipientType: String): List { + return this?.payload?.headers?.firstOrNull { header -> header?.name in recipientType }?.value?.asInternetAddresses()?.toList() ?: emptyList() } -fun Message.getSubject(): String? { - return payload?.headers?.firstOrNull { header -> +fun Message?.getSubject(): String? { + return this?.payload?.headers?.firstOrNull { header -> header?.name == "Subject" }?.value } -fun Message.getInReplyTo(): String? { - return payload?.headers?.firstOrNull { header -> +fun Message?.getInReplyTo(): String? { + return this?.payload?.headers?.firstOrNull { header -> header?.name == JavaEmailConstants.HEADER_IN_REPLY_TO }?.value } -fun Message.getMessageId(): String? { - return payload?.headers?.firstOrNull { header -> +fun Message?.getMessageId(): String? { + return this?.payload?.headers?.firstOrNull { header -> header?.name == JavaEmailConstants.HEADER_MESSAGE_ID }?.value } -fun Message.isDraft(): Boolean { - return labelIds?.contains(GmailApiHelper.LABEL_DRAFT) == true +fun Message?.isDraft(): Boolean { + return this?.labelIds?.contains(GmailApiHelper.LABEL_DRAFT) == true } -fun Message.hasAttachments(): Boolean { - return payload?.hasAttachments() == true +fun Message?.hasAttachments(): Boolean { + return this?.payload?.hasAttachments() == true } -fun Message.filterHeadersWithName(name: String): List { - return payload?.headers?.filter { header -> header?.name == name } ?: emptyList() +fun Message?.filterHeadersWithName(name: String): List { + return this?.payload?.headers?.filter { header -> header?.name == name } ?: emptyList() } -fun Message.containsLabel(localFolder: LocalFolder?): Boolean? { - return labelIds?.contains(localFolder?.fullName) +fun Message?.containsLabel(localFolder: LocalFolder?): Boolean? { + return this?.labelIds?.contains(localFolder?.fullName) } -fun Message.isTrashed(): Boolean { - return labelIds?.contains(GmailApiHelper.LABEL_TRASH) == true +fun Message?.isTrashed(): Boolean { + return this?.labelIds?.contains(GmailApiHelper.LABEL_TRASH) == true } -fun Message.isSent(): Boolean { - return labelIds?.contains(GmailApiHelper.LABEL_SENT) == true +fun Message?.isSent(): Boolean { + return this?.labelIds?.contains(GmailApiHelper.LABEL_SENT) == true } -fun Message.canBeUsed(localFolder: LocalFolder?): Boolean { +fun Message?.canBeUsed(localFolder: LocalFolder?): Boolean { return if (localFolder?.getFolderType() == FoldersManager.FolderType.TRASH) { isTrashed() == true } else { From c3df2957a794f79ce8a65a7006a6c91545fbc8c8 Mon Sep 17 00:00:00 2001 From: denbond7 Date: Tue, 22 Apr 2025 13:26:51 +0300 Subject: [PATCH 2/3] wip --- .../email/api/email/gmail/GmailApiHelper.kt | 6 +- .../api/services/gmail/model/ThreadExt.kt | 61 ++++++++++--------- .../viewmodel/ThreadDetailsViewModel.kt | 4 +- 3 files changed, 39 insertions(+), 32 deletions(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt index fef94a43ce..89f2c9893d 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt @@ -420,8 +420,8 @@ class GmailApiHelper { t: Thread?, responseHeaders: HttpHeaders? ) { - t?.let { thread -> - listResult.add(thread.toThreadInfo(context, accountEntity, localFolder)) + thread.toThreadInfo(context, accountEntity, localFolder)?.let { threadInfo -> + listResult.add(threadInfo) } } @@ -453,7 +453,7 @@ class GmailApiHelper { format = format, metadataHeaders = metadataHeaders, fields = fields - )?.toThreadInfo(context, accountEntity, localFolder) + ).toThreadInfo(context, accountEntity, localFolder) } suspend fun getThread( diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/ThreadExt.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/ThreadExt.kt index ee053a18bd..fa10726c8b 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/ThreadExt.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/ThreadExt.kt @@ -20,11 +20,12 @@ import jakarta.mail.internet.InternetAddress /** * @author Denys Bondarenko */ -fun Thread.getUniqueRecipients(account: String, localFolder: LocalFolder?): List { +fun Thread?.getUniqueRecipients(account: String, localFolder: LocalFolder?): List { + val threadMessages = this?.messages return mutableListOf().apply { - val filteredMessages = messages?.filter { + val filteredMessages = threadMessages?.filter { it.canBeUsed(localFolder) - }?.takeIf { + }?.filterNotNull()?.takeIf { it.isNotEmpty() } ?: return@apply val fromHeaderName = "From" @@ -69,42 +70,43 @@ fun Thread.getUniqueRecipients(account: String, localFolder: LocalFolder?): List } } -fun Thread.getUniqueLabelsSet(localFolder: LocalFolder?): Set { - return messages?.filter { - it.canBeUsed(localFolder) +fun Thread?.getUniqueLabelsSet(localFolder: LocalFolder?): Set { + return this?.messages?.filter { + it?.canBeUsed(localFolder) == true }?.flatMap { - it.labelIds ?: emptyList() + it?.labelIds ?: emptyList() }?.toSortedSet() ?: emptySet() } -fun Thread.getDraftsCount(localFolder: LocalFolder?): Int { - return messages?.filter { - it.canBeUsed(localFolder) && it.labelIds.contains(LABEL_DRAFT) +fun Thread?.getDraftsCount(localFolder: LocalFolder?): Int { + return this?.messages?.filter { + it?.canBeUsed(localFolder) == true && it.labelIds?.contains(LABEL_DRAFT) == true }?.size ?: 0 } -fun Thread.hasUnreadMessages(localFolder: LocalFolder?): Boolean { - return messages?.filter { - it.canBeUsed(localFolder) +fun Thread?.hasUnreadMessages(localFolder: LocalFolder?): Boolean { + return this?.messages?.filter { + it?.canBeUsed(localFolder) == true }?.any { - it.labelIds?.contains(GmailApiHelper.LABEL_UNREAD) == true + it?.labelIds?.contains(GmailApiHelper.LABEL_UNREAD) == true } == true } -fun Thread.hasAttachments(localFolder: LocalFolder?): Boolean { - return messages?.filter { it.canBeUsed(localFolder) }?.any { it.hasAttachments() } == true +fun Thread?.hasAttachments(localFolder: LocalFolder?): Boolean { + return this?.messages?.filter { it?.canBeUsed(localFolder) == true } + ?.any { it.hasAttachments() } == true } -fun Thread.hasPgp(localFolder: LocalFolder?): Boolean { - return messages?.filter { it.canBeUsed(localFolder) }?.any { it.hasPgp() } == true +fun Thread?.hasPgp(localFolder: LocalFolder?): Boolean { + return this?.messages?.filter { it.canBeUsed(localFolder) }?.any { it.hasPgp() } == true } -fun Thread.extractSubject( +fun Thread?.extractSubject( context: Context, receiverEmail: String, localFolder: LocalFolder? ): String { - val filteredMessages = messages?.filter { it.canBeUsed(localFolder) } + val filteredMessages = this?.messages?.filter { it.canBeUsed(localFolder) } return filteredMessages?.getOrNull(0)?.takeIf { message -> (message.getRecipients("From").any { internetAddress -> @@ -120,22 +122,25 @@ fun Thread.extractSubject( ?: context.getString(R.string.no_subject) } -fun Thread.filteredMessages(localFolder: LocalFolder?): List { - return messages?.filter { it.canBeUsed(localFolder) } ?: emptyList() +fun Thread?.filteredMessages(localFolder: LocalFolder?): List { + return this?.messages?.filter { it.canBeUsed(localFolder) } ?: emptyList() } -fun Thread.toThreadInfo( +fun Thread?.toThreadInfo( context: Context, accountEntity: AccountEntity, localFolder: LocalFolder? = null -): GmailThreadInfo { +): GmailThreadInfo? { + val threadId = this?.id ?: return null val receiverEmail = accountEntity.email val lastMessage = messages?.lastOrNull { - !it.labelIds.contains(LABEL_DRAFT) && it.canBeUsed(localFolder) - } ?: messages?.first() + it?.labelIds?.contains(LABEL_DRAFT) == false && it.canBeUsed(localFolder) + } + ?: messages?.firstOrNull() + ?: return null val gmailThreadInfo = GmailThreadInfo( - id = id, - lastMessage = requireNotNull(lastMessage), + id = threadId, + lastMessage = lastMessage, messagesCount = messages?.filter { it.canBeUsed(localFolder) }?.size ?: 0, draftsCount = getDraftsCount(localFolder), recipients = getUniqueRecipients(receiverEmail, localFolder), diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ThreadDetailsViewModel.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ThreadDetailsViewModel.kt index e25e1002ab..bc98354ae6 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ThreadDetailsViewModel.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ThreadDetailsViewModel.kt @@ -388,7 +388,9 @@ class ThreadDetailsViewModel( format = GmailApiHelper.RESPONSE_FORMAT_FULL ) ?: error("Thread not found") - val threadInfo = thread.toThreadInfo(getApplication(), activeAccount, localFolder) + val threadInfo = requireNotNull( + thread.toThreadInfo(getApplication(), activeAccount, localFolder) + ) if (!threadInfo.labels.contains(localFolder.fullName) && !localFolder.isAll) { val context: Context = getApplication() From a4374672ca85a50cae86b2c9fdc2d79b27a67333 Mon Sep 17 00:00:00 2001 From: denbond7 Date: Tue, 22 Apr 2025 18:15:22 +0300 Subject: [PATCH 3/3] wip --- .../java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt index 89f2c9893d..cb3415e5bf 100644 --- a/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt +++ b/FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt @@ -420,7 +420,7 @@ class GmailApiHelper { t: Thread?, responseHeaders: HttpHeaders? ) { - thread.toThreadInfo(context, accountEntity, localFolder)?.let { threadInfo -> + t.toThreadInfo(context, accountEntity, localFolder)?.let { threadInfo -> listResult.add(threadInfo) } }