Loading core/common/src/main/kotlin/app/k9mail/core/common/mail/EmailAddress.kt +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. * Loading @@ -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) } Loading Loading @@ -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. * Loading core/common/src/main/kotlin/app/k9mail/core/common/mail/EmailAddressParser.kt +7 −12 Original line number Diff line number Diff line Loading @@ -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. * Loading Loading @@ -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 ) { Loading Loading @@ -96,10 +95,6 @@ internal class EmailAddressParser( } } if (localPart.length > MAXIMUM_LOCAL_PART_LENGTH) { parserError(LocalPartLengthExceeded) } return localPart } Loading core/common/src/main/kotlin/app/k9mail/core/common/mail/EmailAddressParserConfig.kt +11 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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, Loading core/common/src/test/kotlin/app/k9mail/core/common/mail/EmailAddressParserTest.kt +48 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) Loading @@ -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 { Loading @@ -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, Loading Loading
core/common/src/main/kotlin/app/k9mail/core/common/mail/EmailAddress.kt +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. * Loading @@ -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) } Loading Loading @@ -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. * Loading
core/common/src/main/kotlin/app/k9mail/core/common/mail/EmailAddressParser.kt +7 −12 Original line number Diff line number Diff line Loading @@ -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. * Loading Loading @@ -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 ) { Loading Loading @@ -96,10 +95,6 @@ internal class EmailAddressParser( } } if (localPart.length > MAXIMUM_LOCAL_PART_LENGTH) { parserError(LocalPartLengthExceeded) } return localPart } Loading
core/common/src/main/kotlin/app/k9mail/core/common/mail/EmailAddressParserConfig.kt +11 −0 Original line number Diff line number Diff line Loading @@ -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. Loading @@ -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, Loading
core/common/src/test/kotlin/app/k9mail/core/common/mail/EmailAddressParserTest.kt +48 −4 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) Loading @@ -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) Loading @@ -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 { Loading @@ -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, Loading