From 330ac1ee31a6d5976161486297f078f5e9d2da23 Mon Sep 17 00:00:00 2001 From: cketti Date: Thu, 8 Feb 2024 22:36:52 +0100 Subject: [PATCH] Include all supported server settings in `AutoconfigParserResult` --- .../autoconfig/AutoconfigParserResult.kt | 11 +- .../autoconfig/RealAutoconfigFetcher.kt | 4 +- .../autoconfig/RealAutoconfigParser.kt | 26 ++- .../autoconfig/RealAutoconfigParserTest.kt | 172 +++++++++++++----- 4 files changed, 154 insertions(+), 59 deletions(-) diff --git a/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/AutoconfigParserResult.kt b/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/AutoconfigParserResult.kt index d82caefec..4abe033ae 100644 --- a/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/AutoconfigParserResult.kt +++ b/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/AutoconfigParserResult.kt @@ -11,9 +11,14 @@ internal sealed interface AutoconfigParserResult { * Server settings extracted from the Autoconfig XML. */ data class Settings( - val incomingServerSettings: IncomingServerSettings, - val outgoingServerSettings: OutgoingServerSettings, - ) : AutoconfigParserResult + val incomingServerSettings: List, + val outgoingServerSettings: List, + ) : AutoconfigParserResult { + init { + require(incomingServerSettings.isNotEmpty()) + require(outgoingServerSettings.isNotEmpty()) + } + } /** * Server settings couldn't be extracted. diff --git a/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigFetcher.kt b/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigFetcher.kt index e43ece464..2b03c2aab 100644 --- a/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigFetcher.kt +++ b/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigFetcher.kt @@ -38,8 +38,8 @@ internal class RealAutoconfigFetcher( return when (val parserResult = parser.parseSettings(inputStream, email)) { is Settings -> { AutoDiscoveryResult.Settings( - incomingServerSettings = parserResult.incomingServerSettings, - outgoingServerSettings = parserResult.outgoingServerSettings, + incomingServerSettings = parserResult.incomingServerSettings.first(), + outgoingServerSettings = parserResult.outgoingServerSettings.first(), isTrusted = fetchResult.isTrusted, source = autoconfigUrl.toString(), ) diff --git a/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigParser.kt b/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigParser.kt index e0424e61e..f155ea8d7 100644 --- a/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigParser.kt +++ b/feature/autodiscovery/autoconfig/src/main/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigParser.kt @@ -97,8 +97,8 @@ private class ClientConfigParser( private fun parseEmailProvider(): AutoconfigParserResult { var domainFound = false - var incomingServerSettings: IncomingServerSettings? = null - var outgoingServerSettings: OutgoingServerSettings? = null + val incomingServerSettings = mutableListOf() + val outgoingServerSettings = mutableListOf() // The 'id' attribute is required (but not really used) by Thunderbird desktop. val emailProviderId = pullParser.getAttributeValue(null, "id") @@ -118,15 +118,13 @@ private class ClientConfigParser( } } "incomingServer" -> { - val serverSettings = parseServer("imap", ::createImapServerSettings) - if (incomingServerSettings == null) { - incomingServerSettings = serverSettings + parseServer("imap", ::createImapServerSettings)?.let { serverSettings -> + incomingServerSettings.add(serverSettings) } } "outgoingServer" -> { - val serverSettings = parseServer("smtp", ::createSmtpServerSettings) - if (outgoingServerSettings == null) { - outgoingServerSettings = serverSettings + parseServer("smtp", ::createSmtpServerSettings)?.let { serverSettings -> + outgoingServerSettings.add(serverSettings) } } else -> { @@ -141,9 +139,17 @@ private class ClientConfigParser( parserError("Valid 'domain' element required") } + if (incomingServerSettings.isEmpty()) { + parserError("No supported 'incomingServer' element found") + } + + if (outgoingServerSettings.isEmpty()) { + parserError("No supported 'outgoingServer' element found") + } + return AutoconfigParserResult.Settings( - incomingServerSettings = incomingServerSettings ?: parserError("Missing 'incomingServer' element"), - outgoingServerSettings = outgoingServerSettings ?: parserError("Missing 'outgoingServer' element"), + incomingServerSettings = incomingServerSettings, + outgoingServerSettings = outgoingServerSettings, ) } diff --git a/feature/autodiscovery/autoconfig/src/test/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigParserTest.kt b/feature/autodiscovery/autoconfig/src/test/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigParserTest.kt index f2663ca26..62c6a98d3 100644 --- a/feature/autodiscovery/autoconfig/src/test/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigParserTest.kt +++ b/feature/autodiscovery/autoconfig/src/test/kotlin/app/k9mail/autodiscovery/autoconfig/RealAutoconfigParserTest.kt @@ -12,6 +12,7 @@ import app.k9mail.core.common.mail.toUserEmailAddress import app.k9mail.core.common.net.toHostname import app.k9mail.core.common.net.toPort import assertk.assertThat +import assertk.assertions.containsExactly import assertk.assertions.hasMessage import assertk.assertions.isEqualTo import assertk.assertions.isInstanceOf @@ -55,6 +56,30 @@ class RealAutoconfigParserTest { """.trimIndent() + @Language("XML") + private val additionalIncomingServer = + """ + + imap.domain.example + 143 + STARTTLS + password-cleartext + %EMAILADDRESS% + + """.trimIndent() + + @Language("XML") + private val additionalOutgoingServer = + """ + + smtp.domain.example + 465 + SSL + password-cleartext + %EMAILADDRESS% + + """.trimIndent() + private val irrelevantEmailAddress = "irrelevant@domain.example".toUserEmailAddress() @Test @@ -65,19 +90,23 @@ class RealAutoconfigParserTest { assertThat(result).isNotNull().isEqualTo( Settings( - ImapServerSettings( - hostname = "imap.domain.example".toHostname(), - port = 993.toPort(), - connectionSecurity = TLS, - authenticationTypes = listOf(PasswordCleartext), - username = "user@domain.example", + incomingServerSettings = listOf( + ImapServerSettings( + hostname = "imap.domain.example".toHostname(), + port = 993.toPort(), + connectionSecurity = TLS, + authenticationTypes = listOf(PasswordCleartext), + username = "user@domain.example", + ), ), - SmtpServerSettings( - hostname = "smtp.domain.example".toHostname(), - port = 587.toPort(), - connectionSecurity = StartTLS, - authenticationTypes = listOf(PasswordCleartext), - username = "user@domain.example", + outgoingServerSettings = listOf( + SmtpServerSettings( + hostname = "smtp.domain.example".toHostname(), + port = 587.toPort(), + connectionSecurity = StartTLS, + authenticationTypes = listOf(PasswordCleartext), + username = "user@domain.example", + ), ), ), ) @@ -91,19 +120,70 @@ class RealAutoconfigParserTest { assertThat(result).isNotNull().isEqualTo( Settings( - ImapServerSettings( - hostname = "imap.gmail.com".toHostname(), - port = 993.toPort(), - connectionSecurity = TLS, - authenticationTypes = listOf(OAuth2, PasswordCleartext), - username = "test@gmail.com", + incomingServerSettings = listOf( + ImapServerSettings( + hostname = "imap.gmail.com".toHostname(), + port = 993.toPort(), + connectionSecurity = TLS, + authenticationTypes = listOf(OAuth2, PasswordCleartext), + username = "test@gmail.com", + ), ), - SmtpServerSettings( - hostname = "smtp.gmail.com".toHostname(), - port = 465.toPort(), - connectionSecurity = TLS, - authenticationTypes = listOf(OAuth2, PasswordCleartext), - username = "test@gmail.com", + outgoingServerSettings = listOf( + SmtpServerSettings( + hostname = "smtp.gmail.com".toHostname(), + port = 465.toPort(), + connectionSecurity = TLS, + authenticationTypes = listOf(OAuth2, PasswordCleartext), + username = "test@gmail.com", + ), + ), + ), + ) + } + + @Test + fun `multiple incomingServer and outgoingServer elements`() { + val inputStream = minimalConfig.withModifications { + element("incomingServer").insertBefore(additionalIncomingServer) + element("outgoingServer").insertBefore(additionalOutgoingServer) + } + + val result = parser.parseSettings(inputStream, email = "user@domain.example".toUserEmailAddress()) + + assertThat(result).isNotNull().isEqualTo( + Settings( + incomingServerSettings = listOf( + ImapServerSettings( + hostname = "imap.domain.example".toHostname(), + port = 143.toPort(), + connectionSecurity = StartTLS, + authenticationTypes = listOf(PasswordCleartext), + username = "user@domain.example", + ), + ImapServerSettings( + hostname = "imap.domain.example".toHostname(), + port = 993.toPort(), + connectionSecurity = TLS, + authenticationTypes = listOf(PasswordCleartext), + username = "user@domain.example", + ), + ), + outgoingServerSettings = listOf( + SmtpServerSettings( + hostname = "smtp.domain.example".toHostname(), + port = 465.toPort(), + connectionSecurity = TLS, + authenticationTypes = listOf(PasswordCleartext), + username = "user@domain.example", + ), + SmtpServerSettings( + hostname = "smtp.domain.example".toHostname(), + port = 587.toPort(), + connectionSecurity = StartTLS, + authenticationTypes = listOf(PasswordCleartext), + username = "user@domain.example", + ), ), ), ) @@ -122,19 +202,23 @@ class RealAutoconfigParserTest { assertThat(result).isNotNull().isEqualTo( Settings( - ImapServerSettings( - hostname = "user.domain.example".toHostname(), - port = 993.toPort(), - connectionSecurity = TLS, - authenticationTypes = listOf(PasswordCleartext), - username = "user@domain.example", + incomingServerSettings = listOf( + ImapServerSettings( + hostname = "user.domain.example".toHostname(), + port = 993.toPort(), + connectionSecurity = TLS, + authenticationTypes = listOf(PasswordCleartext), + username = "user@domain.example", + ), ), - SmtpServerSettings( - hostname = "user.outgoing.domain.example".toHostname(), - port = 587.toPort(), - connectionSecurity = StartTLS, - authenticationTypes = listOf(PasswordCleartext), - username = "domain.example", + outgoingServerSettings = listOf( + SmtpServerSettings( + hostname = "user.outgoing.domain.example".toHostname(), + port = 587.toPort(), + connectionSecurity = StartTLS, + authenticationTypes = listOf(PasswordCleartext), + username = "domain.example", + ), ), ), ) @@ -153,7 +237,7 @@ class RealAutoconfigParserTest { val result = parser.parseSettings(inputStream, email = "user@domain.example".toUserEmailAddress()) assertThat(result).isInstanceOf() - .prop(Settings::incomingServerSettings).isEqualTo( + .prop(Settings::incomingServerSettings).containsExactly( ImapServerSettings( hostname = "imap.domain.example".toHostname(), port = 993.toPort(), @@ -173,7 +257,7 @@ class RealAutoconfigParserTest { val result = parser.parseSettings(inputStream, email = "user@domain.example".toUserEmailAddress()) assertThat(result).isInstanceOf() - .prop(Settings::incomingServerSettings).isEqualTo( + .prop(Settings::incomingServerSettings).containsExactly( ImapServerSettings( hostname = "imap.domain.example".toHostname(), port = 993.toPort(), @@ -193,7 +277,7 @@ class RealAutoconfigParserTest { val result = parser.parseSettings(inputStream, email = "user@domain.example".toUserEmailAddress()) assertThat(result).isInstanceOf() - .prop(Settings::outgoingServerSettings).isEqualTo( + .prop(Settings::outgoingServerSettings).containsExactly( SmtpServerSettings( hostname = "smtp.domain.example".toHostname(), port = 587.toPort(), @@ -213,7 +297,7 @@ class RealAutoconfigParserTest { val result = parser.parseSettings(inputStream, email = "user@domain.example".toUserEmailAddress()) assertThat(result).isInstanceOf() - .prop(Settings::incomingServerSettings).isEqualTo( + .prop(Settings::incomingServerSettings).containsExactly( ImapServerSettings( hostname = "imap.domain.example".toHostname(), port = 993.toPort(), @@ -281,7 +365,7 @@ class RealAutoconfigParserTest { val result = parser.parseSettings(inputStream, irrelevantEmailAddress) assertThat(result).isInstanceOf() - .prop(ParserError::error).hasMessage("Missing 'incomingServer' element") + .prop(ParserError::error).hasMessage("No supported 'incomingServer' element found") } @Test @@ -293,7 +377,7 @@ class RealAutoconfigParserTest { val result = parser.parseSettings(inputStream, irrelevantEmailAddress) assertThat(result).isInstanceOf() - .prop(ParserError::error).hasMessage("Missing 'outgoingServer' element") + .prop(ParserError::error).hasMessage("No supported 'outgoingServer' element found") } @Test @@ -440,7 +524,7 @@ class RealAutoconfigParserTest { val result = parser.parseSettings(inputStream, irrelevantEmailAddress) assertThat(result).isInstanceOf() - .prop(ParserError::error).hasMessage("Missing 'incomingServer' element") + .prop(ParserError::error).hasMessage("No supported 'incomingServer' element found") } @Test @@ -455,7 +539,7 @@ class RealAutoconfigParserTest { val result = parser.parseSettings(inputStream, irrelevantEmailAddress) assertThat(result).isInstanceOf() - .prop(ParserError::error).hasMessage("Missing 'outgoingServer' element") + .prop(ParserError::error).hasMessage("No supported 'outgoingServer' element found") } @Test