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

Commit c96d0ce6 authored by cketti's avatar cketti
Browse files

Allow disabling length checks in `EmailAddressParser`

parent 20d1e566
Loading
Loading
Loading
Loading
+26 −0
Original line number Diff line number Diff line
package app.k9mail.core.common.mail

// See RFC 5321, 4.5.3.1.3.
// The maximum length of 'Path' indirectly limits the length of 'Mailbox'.
internal const val MAXIMUM_EMAIL_ADDRESS_LENGTH = 254

// See RFC 5321, 4.5.3.1.1.
internal const val MAXIMUM_LOCAL_PART_LENGTH = 64

/**
 * Represents an email address.
 *
@@ -15,6 +22,14 @@ class EmailAddress internal constructor(

    init {
        warnings = buildSet {
            if (localPart.length > MAXIMUM_LOCAL_PART_LENGTH) {
                add(Warning.LocalPartExceedsLengthLimit)
            }

            if (address.length > MAXIMUM_EMAIL_ADDRESS_LENGTH) {
                add(Warning.EmailAddressExceedsLengthLimit)
            }

            if (localPart.isEmpty()) {
                add(Warning.EmptyLocalPart)
            }
@@ -65,6 +80,17 @@ class EmailAddress internal constructor(
    }

    enum class Warning {
        /**
         * The local part exceeds the length limit (see RFC 5321, 4.5.3.1.1.).
         */
        LocalPartExceedsLengthLimit,

        /**
         * The email address exceeds the length limit (see RFC 5321, 4.5.3.1.3.; The maximum length of 'Path'
         * indirectly limits the length of 'Mailbox').
         */
        EmailAddressExceedsLengthLimit,

        /**
         * The local part requires using a quoted string.
         *
+7 −12
Original line number Diff line number Diff line
@@ -13,13 +13,6 @@ import app.k9mail.core.common.mail.EmailAddressParserError.LocalPartRequiresQuot
import app.k9mail.core.common.mail.EmailAddressParserError.QuotedStringInLocalPart
import app.k9mail.core.common.mail.EmailAddressParserError.TotalLengthExceeded

// See RFC 5321, 4.5.3.1.3.
// The maximum length of 'Path' indirectly limits the length of 'Mailbox'.
internal const val MAXIMUM_EMAIL_ADDRESS_LENGTH = 254

// See RFC 5321, 4.5.3.1.1.
internal const val MAXIMUM_LOCAL_PART_LENGTH = 64

/**
 * Parse an email address.
 *
@@ -52,10 +45,16 @@ internal class EmailAddressParser(
            parserError(ExpectedEndOfInput)
        }

        if (emailAddress.address.length > MAXIMUM_EMAIL_ADDRESS_LENGTH) {
        if (
            config.isEmailAddressLengthCheckEnabled && Warning.EmailAddressExceedsLengthLimit in emailAddress.warnings
        ) {
            parserError(TotalLengthExceeded)
        }

        if (config.isLocalPartLengthCheckEnabled && Warning.LocalPartExceedsLengthLimit in emailAddress.warnings) {
            parserError(LocalPartLengthExceeded, position = input.lastIndexOf('@'))
        }

        if (
            !config.isLocalPartRequiringQuotedStringAllowed && Warning.QuotedStringInLocalPart in emailAddress.warnings
        ) {
@@ -96,10 +95,6 @@ internal class EmailAddressParser(
            }
        }

        if (localPart.length > MAXIMUM_LOCAL_PART_LENGTH) {
            parserError(LocalPartLengthExceeded)
        }

        return localPart
    }

+11 −0
Original line number Diff line number Diff line
@@ -3,6 +3,13 @@ package app.k9mail.core.common.mail
/**
 * Configuration to control the behavior when parsing an email address into [EmailAddress].
 *
 * @param isLocalPartLengthCheckEnabled When this is `true` the length of the local part is checked to make sure it
 * doesn't exceed the specified limit (see RFC 5321, 4.5.3.1.1.).
 *
 * @param isEmailAddressLengthCheckEnabled When this is `true` the length of the whole email address is checked to make
 * sure it doesn't exceed the specified limit (see RFC 5321, 4.5.3.1.3.; The maximum length of 'Path' indirectly limits
 * the length of 'Mailbox').
 *
 * @param isQuotedLocalPartAllowed When this is `true`, the parsing step allows email addresses with a local part
 * encoded as quoted string, e.g. `"foo bar"@domain.example`. Otherwise, the parser will throw an
 * [EmailAddressParserException] as soon as a quoted string is encountered.
@@ -24,12 +31,16 @@ package app.k9mail.core.common.mail
 * [isLocalPartRequiringQuotedStringAllowed] is `false`.
 */
data class EmailAddressParserConfig(
    val isLocalPartLengthCheckEnabled: Boolean,
    val isEmailAddressLengthCheckEnabled: Boolean,
    val isQuotedLocalPartAllowed: Boolean,
    val isLocalPartRequiringQuotedStringAllowed: Boolean,
    val isEmptyLocalPartAllowed: Boolean = false,
) {
    companion object {
        val DEFAULT = EmailAddressParserConfig(
            isLocalPartLengthCheckEnabled = true,
            isEmailAddressLengthCheckEnabled = true,
            isQuotedLocalPartAllowed = true,
            isLocalPartRequiringQuotedStringAllowed = true,
            isEmptyLocalPartAllowed = false,
+48 −4
Original line number Diff line number Diff line
@@ -15,6 +15,7 @@ import app.k9mail.core.common.mail.EmailAddressParserError.UnexpectedCharacter
import assertk.all
import assertk.assertFailure
import assertk.assertThat
import assertk.assertions.contains
import assertk.assertions.hasMessage
import assertk.assertions.isEqualTo
import assertk.assertions.isInstanceOf
@@ -224,9 +225,12 @@ class EmailAddressParserTest {
    }

    @Test
    fun `local part exceeds maximum size`() {
    fun `local part exceeds maximum size with length check enabled`() {
        assertFailure {
            parseEmailAddress("1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx12345@domain.example")
            parseEmailAddress(
                address = "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx12345@domain.example",
                isLocalPartLengthCheckEnabled = true,
            )
        }.isInstanceOf<EmailAddressParserException>().all {
            prop(EmailAddressParserException::error).isEqualTo(LocalPartLengthExceeded)
            prop(EmailAddressParserException::position).isEqualTo(65)
@@ -235,13 +239,27 @@ class EmailAddressParserTest {
    }

    @Test
    fun `email exceeds maximum size`() {
    fun `local part exceeds maximum size with length check disabled`() {
        val input = "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx12345@domain.example"

        val emailAddress = parseEmailAddress(address = input, isLocalPartLengthCheckEnabled = false)

        assertThat(emailAddress.localPart)
            .isEqualTo("1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx12345")
        assertThat(emailAddress.domain).isEqualTo(EmailDomain("domain.example"))
        assertThat(emailAddress.address).isEqualTo(input)
        assertThat(emailAddress.warnings).contains(EmailAddress.Warning.LocalPartExceedsLengthLimit)
    }

    @Test
    fun `email exceeds maximum size with length check enabled`() {
        assertFailure {
            parseEmailAddress(
                "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx1234@" +
                address = "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx1234@" +
                    "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx123." +
                    "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx123." +
                    "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx12",
                isEmailAddressLengthCheckEnabled = true,
            )
        }.isInstanceOf<EmailAddressParserException>().all {
            prop(EmailAddressParserException::error).isEqualTo(TotalLengthExceeded)
@@ -250,6 +268,28 @@ class EmailAddressParserTest {
        }
    }

    @Test
    fun `email exceeds maximum size with length check disabled`() {
        val input = "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx1234@" +
            "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx123." +
            "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx123." +
            "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx12"

        val emailAddress = parseEmailAddress(address = input, isEmailAddressLengthCheckEnabled = false)

        assertThat(emailAddress.localPart)
            .isEqualTo("1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx1234")
        assertThat(emailAddress.domain).isEqualTo(
            EmailDomain(
                "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx123." +
                    "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx123." +
                    "1xxxxxxxxx2xxxxxxxxx3xxxxxxxxx4xxxxxxxxx5xxxxxxxxx6xxxxxxxxx12",
            ),
        )
        assertThat(emailAddress.address).isEqualTo(input)
        assertThat(emailAddress.warnings).contains(EmailAddress.Warning.EmailAddressExceedsLengthLimit)
    }

    @Test
    fun `input contains additional character`() {
        assertFailure {
@@ -263,11 +303,15 @@ class EmailAddressParserTest {

    private fun parseEmailAddress(
        address: String,
        isLocalPartLengthCheckEnabled: Boolean = false,
        isEmailAddressLengthCheckEnabled: Boolean = false,
        isEmptyLocalPartAllowed: Boolean = false,
        isLocalPartRequiringQuotedStringAllowed: Boolean = isEmptyLocalPartAllowed,
        isQuotedLocalPartAllowed: Boolean = isLocalPartRequiringQuotedStringAllowed,
    ): EmailAddress {
        val config = EmailAddressParserConfig(
            isLocalPartLengthCheckEnabled,
            isEmailAddressLengthCheckEnabled,
            isQuotedLocalPartAllowed,
            isLocalPartRequiringQuotedStringAllowed,
            isEmptyLocalPartAllowed,