Include all supported server settings in AutoconfigParserResult

This commit is contained in:
cketti 2024-02-08 22:36:52 +01:00
parent 193ebee382
commit 330ac1ee31
4 changed files with 154 additions and 59 deletions

View file

@ -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<IncomingServerSettings>,
val outgoingServerSettings: List<OutgoingServerSettings>,
) : AutoconfigParserResult {
init {
require(incomingServerSettings.isNotEmpty())
require(outgoingServerSettings.isNotEmpty())
}
}
/**
* Server settings couldn't be extracted.

View file

@ -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(),
)

View file

@ -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<IncomingServerSettings>()
val outgoingServerSettings = mutableListOf<OutgoingServerSettings>()
// 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,
)
}

View file

@ -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 {
</clientConfig>
""".trimIndent()
@Language("XML")
private val additionalIncomingServer =
"""
<incomingServer type="imap">
<hostname>imap.domain.example</hostname>
<port>143</port>
<socketType>STARTTLS</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</incomingServer>
""".trimIndent()
@Language("XML")
private val additionalOutgoingServer =
"""
<outgoingServer type="smtp">
<hostname>smtp.domain.example</hostname>
<port>465</port>
<socketType>SSL</socketType>
<authentication>password-cleartext</authentication>
<username>%EMAILADDRESS%</username>
</outgoingServer>
""".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<Settings>()
.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<Settings>()
.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<Settings>()
.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<Settings>()
.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<ParserError>()
.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<ParserError>()
.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<ParserError>()
.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<ParserError>()
.prop(ParserError::error).hasMessage("Missing 'outgoingServer' element")
.prop(ParserError::error).hasMessage("No supported 'outgoingServer' element found")
}
@Test