Use EmailAddress class in :feature:autodiscovery:autoconfig module

This commit is contained in:
cketti 2023-05-19 18:44:34 +02:00
parent 57e3db5443
commit 3bb0d2e529
9 changed files with 59 additions and 49 deletions

View file

@ -6,3 +6,5 @@ value class EmailAddress(val address: String) {
require(address.isNotBlank()) { "Email address must not be blank" }
}
}
fun String.toEmailAddress() = EmailAddress(this)

View file

@ -1,7 +1,7 @@
package app.k9mail.autodiscovery.autoconfig
import app.k9mail.autodiscovery.api.ConnectionSettingsDiscovery
import app.k9mail.autodiscovery.api.DiscoveryResults
import app.k9mail.core.common.mail.EmailAddress
import app.k9mail.core.common.net.toDomain
import com.fsck.k9.helper.EmailHelper
@ -9,10 +9,10 @@ class AutoconfigDiscovery(
private val urlProvider: AutoconfigUrlProvider,
private val fetcher: AutoconfigFetcher,
private val parser: AutoconfigParser,
) : ConnectionSettingsDiscovery {
) {
override fun discover(email: String): DiscoveryResults? {
val domain = requireNotNull(EmailHelper.getDomainFromEmailAddress(email)?.toDomain()) {
fun discover(email: EmailAddress): DiscoveryResults? {
val domain = requireNotNull(EmailHelper.getDomainFromEmailAddress(email.address)?.toDomain()) {
"Couldn't extract domain from email address: $email"
}

View file

@ -2,6 +2,7 @@ package app.k9mail.autodiscovery.autoconfig
import app.k9mail.autodiscovery.api.DiscoveredServerSettings
import app.k9mail.autodiscovery.api.DiscoveryResults
import app.k9mail.core.common.mail.EmailAddress
import app.k9mail.core.common.net.HostNameUtils
import com.fsck.k9.helper.EmailHelper
import com.fsck.k9.logging.Timber
@ -19,9 +20,9 @@ import org.xmlpull.v1.XmlPullParserFactory
* See [https://github.com/thundernest/autoconfig](https://github.com/thundernest/autoconfig)
*/
class AutoconfigParser {
fun parseSettings(stream: InputStream, email: String): DiscoveryResults {
fun parseSettings(stream: InputStream, email: EmailAddress): DiscoveryResults {
return try {
ClientConfigParser(stream, email).parse()
ClientConfigParser(stream, email.address).parse()
} catch (e: XmlPullParserException) {
throw AutoconfigParserException("Error parsing Autoconfig XML", e)
}

View file

@ -1,8 +1,9 @@
package app.k9mail.autodiscovery.autoconfig
import app.k9mail.core.common.mail.EmailAddress
import app.k9mail.core.common.net.Domain
import okhttp3.HttpUrl
interface AutoconfigUrlProvider {
fun getAutoconfigUrls(domain: Domain, email: String? = null): List<HttpUrl>
fun getAutoconfigUrls(domain: Domain, email: EmailAddress? = null): List<HttpUrl>
}

View file

@ -1,11 +1,12 @@
package app.k9mail.autodiscovery.autoconfig
import app.k9mail.core.common.mail.EmailAddress
import app.k9mail.core.common.net.Domain
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrl
class IspDbAutoconfigUrlProvider : AutoconfigUrlProvider {
override fun getAutoconfigUrls(domain: Domain, email: String?): List<HttpUrl> {
override fun getAutoconfigUrls(domain: Domain, email: EmailAddress?): List<HttpUrl> {
return listOf(createIspDbUrl(domain))
}

View file

@ -1,7 +1,7 @@
package app.k9mail.autodiscovery.autoconfig
import app.k9mail.autodiscovery.api.ConnectionSettingsDiscovery
import app.k9mail.autodiscovery.api.DiscoveryResults
import app.k9mail.core.common.mail.EmailAddress
import app.k9mail.core.common.net.Domain
import app.k9mail.core.common.net.toDomain
import com.fsck.k9.helper.EmailHelper
@ -13,12 +13,12 @@ class MxLookupAutoconfigDiscovery(
private val urlProvider: AutoconfigUrlProvider,
private val fetcher: AutoconfigFetcher,
private val parser: AutoconfigParser,
) : ConnectionSettingsDiscovery {
) {
@Suppress("ReturnCount")
override fun discover(email: String): DiscoveryResults? {
val domain = requireNotNull(EmailHelper.getDomainFromEmailAddress(email)?.toDomain()) {
"Couldn't extract domain from email address: $email"
fun discover(email: EmailAddress): DiscoveryResults? {
val domain = requireNotNull(EmailHelper.getDomainFromEmailAddress(email.address)?.toDomain()) {
"Couldn't extract domain from email address: ${email.address}"
}
val mxHostName = mxLookup(domain) ?: return null

View file

@ -1,10 +1,11 @@
package app.k9mail.autodiscovery.autoconfig
import app.k9mail.core.common.mail.EmailAddress
import app.k9mail.core.common.net.Domain
import okhttp3.HttpUrl
class ProviderAutoconfigUrlProvider(private val config: AutoconfigUrlConfig) : AutoconfigUrlProvider {
override fun getAutoconfigUrls(domain: Domain, email: String?): List<HttpUrl> {
override fun getAutoconfigUrls(domain: Domain, email: EmailAddress?): List<HttpUrl> {
return buildList {
add(createProviderUrl(domain, email, useHttps = true))
add(createDomainUrl(domain, email, useHttps = true))
@ -16,7 +17,7 @@ class ProviderAutoconfigUrlProvider(private val config: AutoconfigUrlConfig) : A
}
}
private fun createProviderUrl(domain: Domain, email: String?, useHttps: Boolean): HttpUrl {
private fun createProviderUrl(domain: Domain, email: EmailAddress?, useHttps: Boolean): HttpUrl {
// https://autoconfig.{domain}/mail/config-v1.1.xml?emailaddress={email}
// http://autoconfig.{domain}/mail/config-v1.1.xml?emailaddress={email}
return HttpUrl.Builder()
@ -25,13 +26,13 @@ class ProviderAutoconfigUrlProvider(private val config: AutoconfigUrlConfig) : A
.addEncodedPathSegments("mail/config-v1.1.xml")
.apply {
if (email != null && config.includeEmailAddress) {
addQueryParameter("emailaddress", email)
addQueryParameter("emailaddress", email.address)
}
}
.build()
}
private fun createDomainUrl(domain: Domain, email: String?, useHttps: Boolean): HttpUrl {
private fun createDomainUrl(domain: Domain, email: EmailAddress?, useHttps: Boolean): HttpUrl {
// https://{domain}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={email}
// http://{domain}/.well-known/autoconfig/mail/config-v1.1.xml?emailaddress={email}
return HttpUrl.Builder()
@ -40,7 +41,7 @@ class ProviderAutoconfigUrlProvider(private val config: AutoconfigUrlConfig) : A
.addEncodedPathSegments(".well-known/autoconfig/mail/config-v1.1.xml")
.apply {
if (email != null && config.includeEmailAddress) {
addQueryParameter("emailaddress", email)
addQueryParameter("emailaddress", email.address)
}
}
.build()

View file

@ -2,6 +2,7 @@ package app.k9mail.autodiscovery.autoconfig
import app.k9mail.autodiscovery.api.DiscoveredServerSettings
import app.k9mail.autodiscovery.api.DiscoveryResults
import app.k9mail.core.common.mail.toEmailAddress
import assertk.all
import assertk.assertFailure
import assertk.assertThat
@ -50,11 +51,13 @@ class AutoconfigParserTest {
</clientConfig>
""".trimIndent()
private val irrelevantEmailAddress = "irrelevant@domain.example".toEmailAddress()
@Test
fun `minimal data`() {
val inputStream = minimalConfig.byteInputStream()
val result = parser.parseSettings(inputStream, email = "user@domain.example")
val result = parser.parseSettings(inputStream, email = "user@domain.example".toEmailAddress())
assertThat(result).isNotNull().all {
prop(DiscoveryResults::incoming).containsExactly(
@ -84,7 +87,7 @@ class AutoconfigParserTest {
fun `real-world data`() {
val inputStream = javaClass.getResourceAsStream("/2022-11-19-googlemail.com.xml")!!
val result = parser.parseSettings(inputStream, email = "test@gmail.com")
val result = parser.parseSettings(inputStream, email = "test@gmail.com".toEmailAddress())
assertThat(result).isNotNull().all {
prop(DiscoveryResults::incoming).containsExactly(
@ -118,7 +121,7 @@ class AutoconfigParserTest {
element("outgoingServer > username").text("%EMAILDOMAIN%")
}
val result = parser.parseSettings(inputStream, email = "user@domain.example")
val result = parser.parseSettings(inputStream, email = "user@domain.example".toEmailAddress())
assertThat(result).isNotNull().all {
prop(DiscoveryResults::incoming).containsExactly(
@ -154,7 +157,7 @@ class AutoconfigParserTest {
element("incomingServer > username").prepend("<!-- comment -->")
}
val result = parser.parseSettings(inputStream, email = "user@domain.example")
val result = parser.parseSettings(inputStream, email = "user@domain.example".toEmailAddress())
assertThat(result.incoming).containsExactly(
DiscoveredServerSettings(
@ -174,7 +177,7 @@ class AutoconfigParserTest {
element("incomingServer").insertBefore("""<incomingServer type="smtp"/>""")
}
val result = parser.parseSettings(inputStream, email = "user@domain.example")
val result = parser.parseSettings(inputStream, email = "user@domain.example".toEmailAddress())
assertThat(result.incoming).containsExactly(
DiscoveredServerSettings(
@ -194,7 +197,7 @@ class AutoconfigParserTest {
element("outgoingServer").insertBefore("""<outgoingServer type="imap"/>""")
}
val result = parser.parseSettings(inputStream, email = "user@domain.example")
val result = parser.parseSettings(inputStream, email = "user@domain.example".toEmailAddress())
assertThat(result.outgoing).containsExactly(
DiscoveredServerSettings(
@ -214,7 +217,7 @@ class AutoconfigParserTest {
element("incomingServer > authentication").insertBefore("<authentication></authentication>")
}
val result = parser.parseSettings(inputStream, email = "user@domain.example")
val result = parser.parseSettings(inputStream, email = "user@domain.example".toEmailAddress())
assertThat(result.incoming).containsExactly(
DiscoveredServerSettings(
@ -235,7 +238,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Missing 'emailProvider.id' attribute")
}
@ -247,7 +250,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Invalid 'emailProvider.id' attribute")
}
@ -259,7 +262,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Valid 'domain' element required")
}
@ -271,7 +274,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Valid 'domain' element required")
}
@ -283,7 +286,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Missing 'incomingServer' element")
}
@ -295,7 +298,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Missing 'outgoingServer' element")
}
@ -307,7 +310,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Missing 'hostname' element")
}
@ -319,7 +322,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Invalid 'hostname' value: 'in valid'")
}
@ -331,7 +334,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Missing 'port' element")
}
@ -343,7 +346,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Missing 'socketType' element")
}
@ -355,7 +358,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("No usable 'authentication' element found")
}
@ -367,7 +370,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Missing 'username' element")
}
@ -379,7 +382,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Invalid 'port' value: 'invalid'")
}
@ -391,7 +394,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Invalid 'port' value: '100000'")
}
@ -403,7 +406,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Unknown 'socketType' value: 'TLS'")
}
@ -415,7 +418,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Expected text, but got START_TAG")
}
@ -427,7 +430,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Missing 'emailProvider' element")
}
@ -442,7 +445,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Missing 'incomingServer' element")
}
@ -457,7 +460,7 @@ class AutoconfigParserTest {
}
assertFailure {
parser.parseSettings(inputStream, email = "user@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Missing 'outgoingServer' element")
}
@ -467,7 +470,7 @@ class AutoconfigParserTest {
val inputStream = "invalid".byteInputStream()
assertFailure {
parser.parseSettings(inputStream, email = "irrelevant@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Error parsing Autoconfig XML")
}
@ -482,7 +485,7 @@ class AutoconfigParserTest {
""".trimIndent().byteInputStream()
assertFailure {
parser.parseSettings(inputStream, email = "irrelevant@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Missing 'clientConfig' element")
}
@ -514,7 +517,7 @@ class AutoconfigParserTest {
""".trimIndent().byteInputStream()
assertFailure {
parser.parseSettings(inputStream, email = "irrelevant@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("Error parsing Autoconfig XML")
}
@ -530,7 +533,7 @@ class AutoconfigParserTest {
""".trimIndent().byteInputStream()
assertFailure {
parser.parseSettings(inputStream, email = "irrelevant@domain.example")
parser.parseSettings(inputStream, irrelevantEmailAddress)
}.isInstanceOf<AutoconfigParserException>()
.hasMessage("End of document reached while reading element 'emailProvider'")
}

View file

@ -1,5 +1,6 @@
package app.k9mail.autodiscovery.autoconfig
import app.k9mail.core.common.mail.toEmailAddress
import app.k9mail.core.common.net.toDomain
import assertk.assertThat
import assertk.assertions.containsExactly
@ -7,7 +8,7 @@ import org.junit.Test
class ProviderAutoconfigUrlProviderTest {
private val domain = "domain.example".toDomain()
private val email = "test@domain.example"
private val email = "test@domain.example".toEmailAddress()
@Test
fun `getAutoconfigUrls with http allowed and email address included`() {