Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ local.properties
/.idea/misc.xml
/release
/docker-mailserver/maildata_volume/

# Keystore files
*.jks

# keystore info
/FlowCrypt/keystore.properties
/FlowCrypt/keystore.properties

# some runtime files
.attach*

Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

package com.flowcrypt.email.util.gson

import com.flowcrypt.email.api.retrofit.response.model.node.BaseMsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.GenericMsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.DecryptErrorMsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.MsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.PublicKeyMsgBlock
Expand All @@ -31,7 +31,7 @@ class MsgBlockAdapter : JsonDeserializer<MsgBlock> {

MsgBlock.Type.DECRYPT_ERROR -> return context.deserialize<MsgBlock>(json, DecryptErrorMsgBlock::class.java)

else -> return context.deserialize<MsgBlock>(json, BaseMsgBlock::class.java)
else -> return context.deserialize<MsgBlock>(json, GenericMsgBlock::class.java)
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ package com.flowcrypt.email.api.email.model

import android.os.Parcel
import android.os.Parcelable
import com.flowcrypt.email.api.retrofit.response.model.node.BaseMsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.GenericMsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.MsgBlock
import com.flowcrypt.email.database.entity.MessageEntity
import com.flowcrypt.email.model.MessageEncryptionType
Expand Down Expand Up @@ -90,7 +90,7 @@ data class IncomingMessageInfo constructor(val msgEntity: MessageEntity,
source.readParcelable<LocalFolder>(LocalFolder::class.java.classLoader),
source.readString(),
source.readString(),
mutableListOf<MsgBlock>().apply { source.readTypedList(this, BaseMsgBlock.CREATOR) },
mutableListOf<MsgBlock>().apply { source.readTypedList(this, GenericMsgBlock.CREATOR) },
source.readString(),
source.readParcelable(MessageEncryptionType::class.java.classLoader)!!
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

package com.flowcrypt.email.api.retrofit.node.gson

import com.flowcrypt.email.api.retrofit.response.model.node.BaseMsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.GenericMsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.DecryptErrorMsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.DecryptedAttMsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.MsgBlock
Expand All @@ -28,14 +28,13 @@ class MsgBlockDeserializer : JsonDeserializer<MsgBlock> {
override fun deserialize(json: JsonElement, typeOfT: Type, context: JsonDeserializationContext): MsgBlock? {
val jsonObject = json.asJsonObject

val msgBlock: MsgBlock? = when (context.deserialize<MsgBlock.Type>(jsonObject.get(BaseMsgBlock.TAG_TYPE), MsgBlock.Type::class.java)) {
val msgBlock: MsgBlock? = when (
context.deserialize<MsgBlock.Type>(jsonObject.get(MsgBlock.TAG_TYPE), MsgBlock.Type::class.java)
) {
MsgBlock.Type.PUBLIC_KEY -> context.deserialize(json, PublicKeyMsgBlock::class.java)

MsgBlock.Type.DECRYPT_ERROR -> context.deserialize(json, DecryptErrorMsgBlock::class.java)

MsgBlock.Type.DECRYPTED_ATT -> context.deserialize(json, DecryptedAttMsgBlock::class.java)

else -> context.deserialize(json, BaseMsgBlock::class.java)
else -> context.deserialize(json, GenericMsgBlock::class.java)
}

if (msgBlock?.type == MsgBlock.Type.PUBLIC_KEY) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: DenBond7
* Contributors:
* DenBond7
* Ivan Pizhenko
*/

package com.flowcrypt.email.api.retrofit.response.model.node
Expand All @@ -10,16 +12,18 @@ import android.os.Parcelable
import com.google.gson.annotations.Expose

/**
* It's a base [MsgBlock]
* Generic message block represents any message block without a dedicated support.
*
* @author Denis Bondarenko
* Date: 3/26/19
* Time: 9:46 AM
* E-mail: DenBond7@gmail.com
*
* @author Ivan Pizhenko
*/
data class BaseMsgBlock(@Expose override val type: MsgBlock.Type = MsgBlock.Type.UNKNOWN,
@Expose override val content: String?,
@Expose override val complete: Boolean) : MsgBlock {
data class GenericMsgBlock(@Expose override val type: MsgBlock.Type = MsgBlock.Type.UNKNOWN,
@Expose override val content: String?,
@Expose override val complete: Boolean) : MsgBlock {

constructor(source: Parcel, type: MsgBlock.Type) : this(
type,
Expand All @@ -35,34 +39,14 @@ data class BaseMsgBlock(@Expose override val type: MsgBlock.Type = MsgBlock.Type
}

companion object {
const val TAG_TYPE = "type"

val handledMsgBlockTypes = listOf(
MsgBlock.Type.PUBLIC_KEY,
MsgBlock.Type.DECRYPT_ERROR,
MsgBlock.Type.DECRYPTED_ATT
)

@JvmField
val CREATOR: Parcelable.Creator<MsgBlock> = object : Parcelable.Creator<MsgBlock> {
override fun createFromParcel(source: Parcel): MsgBlock {
val partType = source.readParcelable<MsgBlock.Type>(MsgBlock.Type::class.java.classLoader)!!
return genMsgBlockFromType(source, partType)
return MsgBlockFactory.fromParcel(partType, source)
}

override fun newArray(size: Int): Array<MsgBlock?> = arrayOfNulls(size)
}

fun genMsgBlockFromType(source: Parcel, type: MsgBlock.Type): MsgBlock {
return when (type) {
MsgBlock.Type.PUBLIC_KEY -> PublicKeyMsgBlock(source)

MsgBlock.Type.DECRYPT_ERROR -> DecryptErrorMsgBlock(source)

MsgBlock.Type.DECRYPTED_ATT -> DecryptedAttMsgBlock(source)

else -> BaseMsgBlock(source, type)
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,13 @@ interface MsgBlock : Parcelable {
DECRYPTED_HTML,

@SerializedName("decryptErr")
DECRYPT_ERROR;
DECRYPT_ERROR,

@SerializedName("certificate")
CERTIFICATE,

@SerializedName("signature")
SIGNATURE;

override fun describeContents(): Int {
return 0
Expand All @@ -81,4 +87,8 @@ interface MsgBlock : Parcelable {
}
}
}

companion object {
const val TAG_TYPE = "type"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: Ivan Pizhenko
*/

package com.flowcrypt.email.api.retrofit.response.model.node

import android.os.Parcel
import java.lang.IllegalArgumentException

object MsgBlockFactory {
@JvmStatic
val supportedMsgBlockTypes = listOf(
MsgBlock.Type.PUBLIC_KEY,
MsgBlock.Type.DECRYPT_ERROR,
MsgBlock.Type.DECRYPTED_ATT
)

@JvmStatic
fun fromParcel(type: MsgBlock.Type, source: Parcel): MsgBlock {
return when (type) {
MsgBlock.Type.PUBLIC_KEY -> PublicKeyMsgBlock(source)
MsgBlock.Type.DECRYPT_ERROR -> DecryptErrorMsgBlock(source)
MsgBlock.Type.DECRYPTED_ATT -> DecryptedAttMsgBlock(source)
else -> GenericMsgBlock(source, type)
}
}

@JvmStatic
fun fromContent(type: MsgBlock.Type, content: String, missingEnd: Boolean = false): MsgBlock {
val complete = !missingEnd
return when (type) {
MsgBlock.Type.PUBLIC_KEY -> PublicKeyMsgBlock(content, complete, null)
MsgBlock.Type.DECRYPT_ERROR -> DecryptErrorMsgBlock(content, complete, null)
else -> GenericMsgBlock(type, content, complete)
}
}

@JvmStatic
fun fromAttachment(type: MsgBlock.Type, content: String, attMeta: AttMeta): MsgBlock {
return when (type) {
MsgBlock.Type.DECRYPTED_ATT -> DecryptedAttMsgBlock(content, true, attMeta, null)
else ->
throw IllegalArgumentException("Can't create block of type ${type.name} from attachment")
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import android.os.Parcelable
import android.util.Base64
import com.flowcrypt.email.api.retrofit.node.gson.NodeGson
import com.flowcrypt.email.api.retrofit.response.base.ApiError
import com.flowcrypt.email.api.retrofit.response.model.node.BaseMsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.GenericMsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.DecryptedAttMsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.MsgBlock
import com.flowcrypt.email.model.MessageEncryptionType
Expand Down Expand Up @@ -123,7 +123,7 @@ data class ParseDecryptedMsgResult constructor(
source.readString()!!,
source.readString(),
source.readParcelable<ApiError>(ApiError::class.java.classLoader),
mutableListOf<MsgBlock>().apply { source.readTypedList(this, BaseMsgBlock.CREATOR) }
mutableListOf<MsgBlock>().apply { source.readTypedList(this, GenericMsgBlock.CREATOR) }
)

override fun describeContents() = 0
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,128 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: ivan
*/

package com.flowcrypt.email.core.msg

import com.flowcrypt.email.api.retrofit.response.model.node.MsgBlock
import com.flowcrypt.email.api.retrofit.response.model.node.MsgBlockFactory
import com.flowcrypt.email.security.pgp.PgpArmor

@Suppress("unused")
object MsgBlockParser {

private const val ARMOR_HEADER_MAX_LENGTH = 50

@JvmStatic
fun detectBlocks(text: String): List<MsgBlock> {
val normalized = normalize(text)
val blocks = mutableListOf<MsgBlock>()
var startAt = 0
while (true) {
val continueAt = detectBlockNext(normalized, startAt, blocks)
if (startAt >= continueAt) return blocks
startAt = continueAt
}
}

@JvmStatic
private fun detectBlockNext(text: String, startAt: Int, blocks: MutableList<MsgBlock>): Int {
val initialBlockCount = blocks.size
var continueAt = -1
val beginIndex = text.indexOf(
PgpArmor.ARMOR_HEADER_DICT[MsgBlock.Type.UNKNOWN]!!.begin, startAt)
if (beginIndex != -1) { // found
val potentialBeginHeader = text.substring(beginIndex, beginIndex + ARMOR_HEADER_MAX_LENGTH)
for (blockHeaderKvp in PgpArmor.ARMOR_HEADER_DICT) {
val blockHeaderDef = blockHeaderKvp.value
if (!blockHeaderDef.replace || potentialBeginHeader.indexOf(blockHeaderDef.begin) != 0) {
continue
}

if (beginIndex > startAt) {
// only replace blocks if they begin on their own line
// contains deliberate block: `-----BEGIN PGP PUBLIC KEY BLOCK-----\n...`
// contains deliberate block: `Hello\n-----BEGIN PGP PUBLIC KEY BLOCK-----\n...`
// just plaintext (accidental block): `Hello -----BEGIN PGP PUBLIC KEY BLOCK-----\n...`
// block treated as plaintext, not on dedicated line - considered accidental
// this will actually cause potential deliberate blocks
// that follow accidental block to be ignored
// but if the message already contains accidental
// (not on dedicated line) blocks, it's probably a good thing to ignore the rest
var textBeforeBlock = text.substring(startAt, beginIndex)
if (!textBeforeBlock.endsWith('\n')) continue
textBeforeBlock = textBeforeBlock.trim()
if (textBeforeBlock.isNotEmpty()) {
blocks.add(MsgBlockFactory.fromContent(MsgBlock.Type.PLAIN_TEXT, textBeforeBlock))
}
}

val endHeaderIndex: Int
val endHeaderLength: Int
if (blockHeaderDef.endRegexp != null) {
val found = blockHeaderDef.endRegexp.find(text.substring(beginIndex))
if (found != null) {
endHeaderIndex = beginIndex + found.range.first
endHeaderLength = found.range.last - found.range.first
} else {
endHeaderIndex = -1
endHeaderLength = 0
}
} else { // string
val end = blockHeaderDef.end!!
endHeaderIndex = text.indexOf(end, beginIndex + blockHeaderDef.begin.length)
endHeaderLength = if (endHeaderIndex == -1) 0 else end.length
}

if (endHeaderIndex != -1) {
// identified end of the same block
continueAt = endHeaderIndex + endHeaderLength
blocks.add(
MsgBlockFactory.fromContent(
blockHeaderKvp.key,
text.substring(beginIndex, continueAt).trim()
)
)
} else {
// corresponding end not found
blocks.add(
MsgBlockFactory.fromContent(blockHeaderKvp.key, text.substring(beginIndex), true)
)
}
break
}
}

if (text.isNotEmpty() && blocks.size == initialBlockCount) {
// didn't find any blocks, but input is non-empty
val remainingText = text.substring(startAt).trim()
if (remainingText.isNotEmpty()) {
blocks.add(MsgBlockFactory.fromContent(MsgBlock.Type.PLAIN_TEXT, remainingText))
}
}

return continueAt
}

@JvmStatic
fun normalize(str: String): String {
return normalizeSpaces(normalizeDashes(str))
}

@JvmStatic
private fun normalizeSpaces(str: String): String {
return str.replace(char160, ' ')
}

@JvmStatic
private fun normalizeDashes(str: String): String {
return str.replace(dashesRegex, "-----")
}

@JvmStatic
private val char160 = 160.toChar()

@JvmStatic
private val dashesRegex = Regex("^—–|—–$")
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact human@flowcrypt.com
* Contributors: Ivan Pizhenko
*/

package com.flowcrypt.email.security.pgp

data class CryptoArmorHeaderDefinition (
val begin: String,
val middle: String? = null,
val end: String?,
val endRegexp: Regex? = null,
val replace: Boolean
)
Loading