Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 07b272a1 authored by Vincent Breitmoser's avatar Vincent Breitmoser
Browse files

Add Autocrypt-Draft-State header to saved drafts

parent 47451c89
Loading
Loading
Loading
Loading
+56 −0
Original line number Diff line number Diff line
package com.fsck.k9.autocrypt

import com.fsck.k9.message.CryptoStatus


data class AutocryptDraftStateHeader(val isEncrypt: Boolean,
                                              val isSignOnly: Boolean,
                                              val isReply: Boolean,
                                              val isByChoice: Boolean,
                                              val isPgpInline: Boolean,
                                              val parameters: Map<String, String> = mapOf()) {

    fun toHeaderValue(): String {
        val builder = StringBuilder()

        builder.append(AutocryptDraftStateHeader.PARAM_ENCRYPT)
        builder.append(if (isEncrypt) "=yes; " else "=no; ")

        if (isReply) {
            builder.append(AutocryptDraftStateHeader.PARAM_IS_REPLY).append("=yes; ")
        }
        if (isSignOnly) {
            builder.append(AutocryptDraftStateHeader.PARAM_SIGN_ONLY).append("=yes; ")
        }
        if (isByChoice) {
            builder.append(AutocryptDraftStateHeader.PARAM_BY_CHOICE).append("=yes; ")
        }
        if (isPgpInline) {
            builder.append(AutocryptDraftStateHeader.PARAM_PGP_INLINE).append("=yes; ")
        }

        return builder.toString()
    }

    companion object {
        const val AUTOCRYPT_DRAFT_STATE_HEADER = "Autocrypt-Draft-State"

        const val PARAM_ENCRYPT = "encrypt"

        const val PARAM_IS_REPLY = "_is-reply"
        const val PARAM_BY_CHOICE = "_by-choice"
        const val PARAM_PGP_INLINE = "_pgp-inline"
        const val PARAM_SIGN_ONLY = "_sign-only"

        const val VALUE_YES = "yes";

        fun fromCryptoStatus(cryptoStatus: CryptoStatus): AutocryptDraftStateHeader {
            if (cryptoStatus.isSignOnly()) {
                return AutocryptDraftStateHeader(false, true, cryptoStatus.isReplyToEncrypted(),
                        cryptoStatus.isUserChoice(), cryptoStatus.isPgpInlineModeEnabled(), mapOf())
            }
            return AutocryptDraftStateHeader(cryptoStatus.isEncryptionEnabled(), false, cryptoStatus.isReplyToEncrypted(),
                    cryptoStatus.isUserChoice(), cryptoStatus.isPgpInlineModeEnabled(), mapOf())
        }
    }
}
+46 −0
Original line number Diff line number Diff line
package com.fsck.k9.autocrypt


import com.fsck.k9.mail.internet.MimeUtility


class AutocryptDraftStateHeaderParser internal constructor() {

    fun parseAutocryptDraftStateHeader(headerValue: String): AutocryptDraftStateHeader? {
        val parameters = MimeUtility.getAllHeaderParameters(headerValue)

        val isEncryptStr = parameters.remove(AutocryptDraftStateHeader.PARAM_ENCRYPT) ?: return null
        val isEncrypt = isEncryptStr == AutocryptDraftStateHeader.VALUE_YES

        val isSignOnlyStr = parameters.remove(AutocryptDraftStateHeader.PARAM_SIGN_ONLY)
        val isSignOnly = isSignOnlyStr == AutocryptDraftStateHeader.VALUE_YES

        val isReplyStr = parameters.remove(AutocryptDraftStateHeader.PARAM_IS_REPLY)
        val isReply = isReplyStr == AutocryptDraftStateHeader.VALUE_YES

        val isByChoiceStr = parameters.remove(AutocryptDraftStateHeader.PARAM_BY_CHOICE)
        val isByChoice = isByChoiceStr == AutocryptDraftStateHeader.VALUE_YES

        val isPgpInlineStr = parameters.remove(AutocryptDraftStateHeader.PARAM_PGP_INLINE)
        val isPgpInline = isPgpInlineStr == AutocryptDraftStateHeader.VALUE_YES

        if (hasCriticalParameters(parameters)) {
            return null
        }

        return AutocryptDraftStateHeader(isEncrypt, isSignOnly, isReply, isByChoice, isPgpInline, parameters)
    }

    private fun hasCriticalParameters(parameters: Map<String, String>): Boolean {
        for (parameterName in parameters.keys) {
            if (!parameterName.startsWith("_")) {
                return true
            }
        }
        return false
    }

    companion object {
        val instance = AutocryptDraftStateHeaderParser()
    }
}
+3 −0
Original line number Diff line number Diff line
@@ -7,6 +7,9 @@ interface CryptoStatus {
    fun isSigningEnabled(): Boolean
    fun isEncryptionEnabled(): Boolean
    fun isPgpInlineModeEnabled(): Boolean
    fun isSignOnly(): Boolean
    fun isUserChoice(): Boolean
    fun isReplyToEncrypted(): Boolean
    fun hasRecipients(): Boolean
    fun isEncryptSubject(): Boolean
    fun getRecipientAddresses(): Array<String>
+25 −3
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@ import android.support.annotation.VisibleForTesting;
import com.fsck.k9.CoreResourceProvider;
import com.fsck.k9.DI;
import com.fsck.k9.K9;
import com.fsck.k9.autocrypt.AutocryptDraftStateHeader;
import com.fsck.k9.autocrypt.AutocryptOpenPgpApiInteractor;
import com.fsck.k9.autocrypt.AutocryptOperations;
import com.fsck.k9.mail.Address;
@@ -110,6 +111,13 @@ public class PgpMessageBuilder extends MessageBuilder {
            return;
        }

        addAutocryptHeaderIfAvailable(openPgpKeyId);
        addDraftStateHeader();

        startOrContinueBuildMessage(null);
    }

    private void addAutocryptHeaderIfAvailable(long openPgpKeyId) {
        Address address = currentProcessedMimeMessage.getFrom()[0];
        byte[] keyData = autocryptOpenPgpApiInteractor.getKeyMaterialForKeyId(
                openPgpApi, openPgpKeyId, address.getAddress());
@@ -117,8 +125,13 @@ public class PgpMessageBuilder extends MessageBuilder {
            autocryptOperations.addAutocryptHeaderToMessage(currentProcessedMimeMessage, keyData,
                    address.getAddress(), cryptoStatus.isSenderPreferEncryptMutual());
        }
    }

        startOrContinueBuildMessage(null);
    private void addDraftStateHeader() {
        AutocryptDraftStateHeader autocryptDraftStateHeader =
                AutocryptDraftStateHeader.Companion.fromCryptoStatus(cryptoStatus);
        currentProcessedMimeMessage.setHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER,
                autocryptDraftStateHeader.toHeaderValue());
    }

    @Override
@@ -155,8 +168,9 @@ public class PgpMessageBuilder extends MessageBuilder {

                boolean payloadSupportsMimeHeaders = !isPgpInlineMode;
                if (payloadSupportsMimeHeaders) {
                    moveDraftStateIntoEncryptedPayload();
                    if (cryptoStatus.isEncryptSubject()) {
                        encryptMessageSubject();
                        moveSubjectIntoEncryptedPayload();
                    }
                    maybeAddGossipHeadersToBodyPart();
                }
@@ -195,7 +209,7 @@ public class PgpMessageBuilder extends MessageBuilder {
        return bodyPart;
    }

    private void encryptMessageSubject() {
    private void moveSubjectIntoEncryptedPayload() {
        String[] subjects = currentProcessedMimeMessage.getHeader(MimeHeader.SUBJECT);
        if (subjects.length > 0) {
            messageContentBodyPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
@@ -205,6 +219,14 @@ public class PgpMessageBuilder extends MessageBuilder {
        }
    }

    private void moveDraftStateIntoEncryptedPayload() {
        String[] autocryptDraftState = currentProcessedMimeMessage.getHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER);
        if (autocryptDraftState.length == 1) {
            messageContentBodyPart.setHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER, autocryptDraftState[0]);
            currentProcessedMimeMessage.removeHeader(AutocryptDraftStateHeader.AUTOCRYPT_DRAFT_STATE_HEADER);
        }
    }

    private void maybeAddGossipHeadersToBodyPart() {
        if (!cryptoStatus.isEncryptionEnabled()) {
            return;
+54 −0
Original line number Diff line number Diff line
package com.fsck.k9.autocrypt


import com.fsck.k9.RobolectricTest
import com.google.common.truth.Truth.assertThat
import org.junit.Test


class AutocryptDraftStateHeaderParserTest : RobolectricTest() {
    internal var autocryptHeaderParser = AutocryptDraftStateHeaderParser()

    @Test
    fun testEncryptReplyByChoice() {
        val draftStateHeader = AutocryptDraftStateHeader(true, false, true, true, false)

        val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader(draftStateHeader.toHeaderValue())

        assertThat(parsedHeader).isEqualTo(draftStateHeader)
    }

     @Test
     fun testSignOnly() {
         val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader("encrypt=no; _by-choice=yes; _sign-only=yes")

         with(parsedHeader!!) {
             assertThat(isEncrypt).isFalse()
             assertThat(isByChoice).isTrue()
             assertThat(isSignOnly).isTrue()
             assertThat(isPgpInline).isFalse()
             assertThat(isReply).isFalse()
         }
     }

    @Test
    fun badCritical() {
        val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader("encrypt=no; badcritical=value")

        assertThat(parsedHeader).isNull()
    }

    @Test
    fun missingEncrypt() {
        val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader("encrpt-with-typo=no; _non_critical=value")

        assertThat(parsedHeader).isNull()
    }

    @Test
    fun unknownNonCritical() {
        val parsedHeader = autocryptHeaderParser.parseAutocryptDraftStateHeader("encrypt=no; _non-critical=value")

        assertThat(parsedHeader).isNotNull()
    }
}
Loading