Skip to content

Commit 8827510

Browse files
authored
fix NPE for Gmail API (#3020)
* Fixed NPE in MessageExt.| #2998 * wip * wip
1 parent 87d66c9 commit 8827510

4 files changed

Lines changed: 67 additions & 60 deletions

File tree

FlowCrypt/src/main/java/com/flowcrypt/email/api/email/gmail/GmailApiHelper.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -420,8 +420,8 @@ class GmailApiHelper {
420420
t: Thread?,
421421
responseHeaders: HttpHeaders?
422422
) {
423-
t?.let { thread ->
424-
listResult.add(thread.toThreadInfo(context, accountEntity, localFolder))
423+
t.toThreadInfo(context, accountEntity, localFolder)?.let { threadInfo ->
424+
listResult.add(threadInfo)
425425
}
426426
}
427427

@@ -453,7 +453,7 @@ class GmailApiHelper {
453453
format = format,
454454
metadataHeaders = metadataHeaders,
455455
fields = fields
456-
)?.toThreadInfo(context, accountEntity, localFolder)
456+
).toThreadInfo(context, accountEntity, localFolder)
457457
}
458458

459459
suspend fun getThread(

FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/MessageExt.kt

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -19,15 +19,15 @@ import jakarta.mail.internet.InternetAddress
1919
/**
2020
* @author Denys Bondarenko
2121
*/
22-
fun Message.hasPgp(): Boolean {
23-
val baseContentType = payload?.headers?.firstOrNull {
22+
fun Message?.hasPgp(): Boolean {
23+
val baseContentType = this?.payload?.headers?.firstOrNull {
2424
it?.name == "Content-Type"
2525
}?.value?.asContentTypeOrNull()
2626

2727
/**
2828
* based on https://datatracker.ietf.org/doc/html/rfc3156#section-5
2929
*/
30-
val isOpenPGPMimeSigned = payload?.parts?.size == 2
30+
val isOpenPGPMimeSigned = this?.payload?.parts?.size == 2
3131
&& "multipart/signed" == baseContentType?.baseType?.lowercase()
3232
&& baseContentType.getParameter("protocol")?.lowercase() == "application/pgp-signature"
3333
&& baseContentType.getParameter("micalg")?.lowercase()?.startsWith("pgp-") == true
@@ -36,68 +36,68 @@ fun Message.hasPgp(): Boolean {
3636
* based on https://datatracker.ietf.org/doc/html/rfc3156#section-4
3737
*/
3838
val isOpenPGPMimeEncrypted = !isOpenPGPMimeSigned
39-
&& payload?.parts?.size == 2
39+
&& this?.payload?.parts?.size == 2
4040
&& "multipart/encrypted" == baseContentType?.baseType?.lowercase()
4141
&& baseContentType.getParameter("protocol")?.lowercase() == "application/pgp-encrypted"
4242

43-
val hasEncryptedParts = payload?.parts?.any { it.hasPgp() } == true
43+
val hasEncryptedParts = this?.payload?.parts?.any { it.hasPgp() } == true
4444

45-
return EmailUtil.hasEncryptedData(snippet)
46-
|| EmailUtil.hasSignedData(snippet)
45+
return EmailUtil.hasEncryptedData(this?.snippet)
46+
|| EmailUtil.hasSignedData(this?.snippet)
4747
|| isOpenPGPMimeSigned
4848
|| isOpenPGPMimeEncrypted
4949
|| hasEncryptedParts
5050
}
5151

52-
fun Message.getRecipients(vararg recipientType: String): List<InternetAddress> {
53-
return payload?.headers?.firstOrNull { header ->
52+
fun Message?.getRecipients(vararg recipientType: String): List<InternetAddress> {
53+
return this?.payload?.headers?.firstOrNull { header ->
5454
header?.name in recipientType
5555
}?.value?.asInternetAddresses()?.toList() ?: emptyList()
5656
}
5757

58-
fun Message.getSubject(): String? {
59-
return payload?.headers?.firstOrNull { header ->
58+
fun Message?.getSubject(): String? {
59+
return this?.payload?.headers?.firstOrNull { header ->
6060
header?.name == "Subject"
6161
}?.value
6262
}
6363

64-
fun Message.getInReplyTo(): String? {
65-
return payload?.headers?.firstOrNull { header ->
64+
fun Message?.getInReplyTo(): String? {
65+
return this?.payload?.headers?.firstOrNull { header ->
6666
header?.name == JavaEmailConstants.HEADER_IN_REPLY_TO
6767
}?.value
6868
}
6969

70-
fun Message.getMessageId(): String? {
71-
return payload?.headers?.firstOrNull { header ->
70+
fun Message?.getMessageId(): String? {
71+
return this?.payload?.headers?.firstOrNull { header ->
7272
header?.name == JavaEmailConstants.HEADER_MESSAGE_ID
7373
}?.value
7474
}
7575

76-
fun Message.isDraft(): Boolean {
77-
return labelIds?.contains(GmailApiHelper.LABEL_DRAFT) == true
76+
fun Message?.isDraft(): Boolean {
77+
return this?.labelIds?.contains(GmailApiHelper.LABEL_DRAFT) == true
7878
}
7979

80-
fun Message.hasAttachments(): Boolean {
81-
return payload?.hasAttachments() == true
80+
fun Message?.hasAttachments(): Boolean {
81+
return this?.payload?.hasAttachments() == true
8282
}
8383

84-
fun Message.filterHeadersWithName(name: String): List<MessagePartHeader> {
85-
return payload?.headers?.filter { header -> header?.name == name } ?: emptyList()
84+
fun Message?.filterHeadersWithName(name: String): List<MessagePartHeader> {
85+
return this?.payload?.headers?.filter { header -> header?.name == name } ?: emptyList()
8686
}
8787

88-
fun Message.containsLabel(localFolder: LocalFolder?): Boolean? {
89-
return labelIds?.contains(localFolder?.fullName)
88+
fun Message?.containsLabel(localFolder: LocalFolder?): Boolean? {
89+
return this?.labelIds?.contains(localFolder?.fullName)
9090
}
9191

92-
fun Message.isTrashed(): Boolean {
93-
return labelIds?.contains(GmailApiHelper.LABEL_TRASH) == true
92+
fun Message?.isTrashed(): Boolean {
93+
return this?.labelIds?.contains(GmailApiHelper.LABEL_TRASH) == true
9494
}
9595

96-
fun Message.isSent(): Boolean {
97-
return labelIds?.contains(GmailApiHelper.LABEL_SENT) == true
96+
fun Message?.isSent(): Boolean {
97+
return this?.labelIds?.contains(GmailApiHelper.LABEL_SENT) == true
9898
}
9999

100-
fun Message.canBeUsed(localFolder: LocalFolder?): Boolean {
100+
fun Message?.canBeUsed(localFolder: LocalFolder?): Boolean {
101101
return if (localFolder?.getFolderType() == FoldersManager.FolderType.TRASH) {
102102
isTrashed() == true
103103
} else {

FlowCrypt/src/main/java/com/flowcrypt/email/extensions/com/google/api/services/gmail/model/ThreadExt.kt

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,12 @@ import jakarta.mail.internet.InternetAddress
2020
/**
2121
* @author Denys Bondarenko
2222
*/
23-
fun Thread.getUniqueRecipients(account: String, localFolder: LocalFolder?): List<InternetAddress> {
23+
fun Thread?.getUniqueRecipients(account: String, localFolder: LocalFolder?): List<InternetAddress> {
24+
val threadMessages = this?.messages
2425
return mutableListOf<InternetAddress>().apply {
25-
val filteredMessages = messages?.filter {
26+
val filteredMessages = threadMessages?.filter {
2627
it.canBeUsed(localFolder)
27-
}?.takeIf {
28+
}?.filterNotNull()?.takeIf {
2829
it.isNotEmpty()
2930
} ?: return@apply
3031
val fromHeaderName = "From"
@@ -69,42 +70,43 @@ fun Thread.getUniqueRecipients(account: String, localFolder: LocalFolder?): List
6970
}
7071
}
7172

72-
fun Thread.getUniqueLabelsSet(localFolder: LocalFolder?): Set<String> {
73-
return messages?.filter {
74-
it.canBeUsed(localFolder)
73+
fun Thread?.getUniqueLabelsSet(localFolder: LocalFolder?): Set<String> {
74+
return this?.messages?.filter {
75+
it?.canBeUsed(localFolder) == true
7576
}?.flatMap {
76-
it.labelIds ?: emptyList()
77+
it?.labelIds ?: emptyList()
7778
}?.toSortedSet() ?: emptySet()
7879
}
7980

80-
fun Thread.getDraftsCount(localFolder: LocalFolder?): Int {
81-
return messages?.filter {
82-
it.canBeUsed(localFolder) && it.labelIds.contains(LABEL_DRAFT)
81+
fun Thread?.getDraftsCount(localFolder: LocalFolder?): Int {
82+
return this?.messages?.filter {
83+
it?.canBeUsed(localFolder) == true && it.labelIds?.contains(LABEL_DRAFT) == true
8384
}?.size ?: 0
8485
}
8586

86-
fun Thread.hasUnreadMessages(localFolder: LocalFolder?): Boolean {
87-
return messages?.filter {
88-
it.canBeUsed(localFolder)
87+
fun Thread?.hasUnreadMessages(localFolder: LocalFolder?): Boolean {
88+
return this?.messages?.filter {
89+
it?.canBeUsed(localFolder) == true
8990
}?.any {
90-
it.labelIds?.contains(GmailApiHelper.LABEL_UNREAD) == true
91+
it?.labelIds?.contains(GmailApiHelper.LABEL_UNREAD) == true
9192
} == true
9293
}
9394

94-
fun Thread.hasAttachments(localFolder: LocalFolder?): Boolean {
95-
return messages?.filter { it.canBeUsed(localFolder) }?.any { it.hasAttachments() } == true
95+
fun Thread?.hasAttachments(localFolder: LocalFolder?): Boolean {
96+
return this?.messages?.filter { it?.canBeUsed(localFolder) == true }
97+
?.any { it.hasAttachments() } == true
9698
}
9799

98-
fun Thread.hasPgp(localFolder: LocalFolder?): Boolean {
99-
return messages?.filter { it.canBeUsed(localFolder) }?.any { it.hasPgp() } == true
100+
fun Thread?.hasPgp(localFolder: LocalFolder?): Boolean {
101+
return this?.messages?.filter { it.canBeUsed(localFolder) }?.any { it.hasPgp() } == true
100102
}
101103

102-
fun Thread.extractSubject(
104+
fun Thread?.extractSubject(
103105
context: Context,
104106
receiverEmail: String,
105107
localFolder: LocalFolder?
106108
): String {
107-
val filteredMessages = messages?.filter { it.canBeUsed(localFolder) }
109+
val filteredMessages = this?.messages?.filter { it.canBeUsed(localFolder) }
108110

109111
return filteredMessages?.getOrNull(0)?.takeIf { message ->
110112
(message.getRecipients("From").any { internetAddress ->
@@ -120,22 +122,25 @@ fun Thread.extractSubject(
120122
?: context.getString(R.string.no_subject)
121123
}
122124

123-
fun Thread.filteredMessages(localFolder: LocalFolder?): List<Message> {
124-
return messages?.filter { it.canBeUsed(localFolder) } ?: emptyList()
125+
fun Thread?.filteredMessages(localFolder: LocalFolder?): List<Message> {
126+
return this?.messages?.filter { it.canBeUsed(localFolder) } ?: emptyList()
125127
}
126128

127-
fun Thread.toThreadInfo(
129+
fun Thread?.toThreadInfo(
128130
context: Context,
129131
accountEntity: AccountEntity,
130132
localFolder: LocalFolder? = null
131-
): GmailThreadInfo {
133+
): GmailThreadInfo? {
134+
val threadId = this?.id ?: return null
132135
val receiverEmail = accountEntity.email
133136
val lastMessage = messages?.lastOrNull {
134-
!it.labelIds.contains(LABEL_DRAFT) && it.canBeUsed(localFolder)
135-
} ?: messages?.first()
137+
it?.labelIds?.contains(LABEL_DRAFT) == false && it.canBeUsed(localFolder)
138+
}
139+
?: messages?.firstOrNull()
140+
?: return null
136141
val gmailThreadInfo = GmailThreadInfo(
137-
id = id,
138-
lastMessage = requireNotNull(lastMessage),
142+
id = threadId,
143+
lastMessage = lastMessage,
139144
messagesCount = messages?.filter { it.canBeUsed(localFolder) }?.size ?: 0,
140145
draftsCount = getDraftsCount(localFolder),
141146
recipients = getUniqueRecipients(receiverEmail, localFolder),

FlowCrypt/src/main/java/com/flowcrypt/email/jetpack/viewmodel/ThreadDetailsViewModel.kt

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -388,7 +388,9 @@ class ThreadDetailsViewModel(
388388
format = GmailApiHelper.RESPONSE_FORMAT_FULL
389389
) ?: error("Thread not found")
390390

391-
val threadInfo = thread.toThreadInfo(getApplication(), activeAccount, localFolder)
391+
val threadInfo = requireNotNull(
392+
thread.toThreadInfo(getApplication(), activeAccount, localFolder)
393+
)
392394

393395
if (!threadInfo.labels.contains(localFolder.fullName) && !localFolder.isAll) {
394396
val context: Context = getApplication()

0 commit comments

Comments
 (0)