Loading app/autodiscovery/providersxml/src/main/java/com/fsck/k9/autodiscovery/providersxml/KoinModule.kt +1 −1 Original line number Diff line number Diff line Loading @@ -4,5 +4,5 @@ import org.koin.dsl.module val autodiscoveryProvidersXmlModule = module { factory { ProvidersXmlProvider(context = get()) } factory { ProvidersXmlDiscovery(backendManager = get(), xmlProvider = get()) } factory { ProvidersXmlDiscovery(xmlProvider = get()) } } app/autodiscovery/providersxml/src/main/java/com/fsck/k9/autodiscovery/providersxml/ProvidersXmlDiscovery.kt +61 −58 Original line number Diff line number Diff line package com.fsck.k9.autodiscovery.providersxml import android.content.res.XmlResourceParser import android.net.Uri import com.fsck.k9.autodiscovery.api.ConnectionSettingsDiscovery import com.fsck.k9.autodiscovery.api.DiscoveredServerSettings import com.fsck.k9.autodiscovery.api.DiscoveryResults import com.fsck.k9.autodiscovery.api.DiscoveryTarget import com.fsck.k9.backend.BackendManager import com.fsck.k9.helper.EmailHelper import com.fsck.k9.helper.UrlEncodingHelper import java.net.URI import java.net.URISyntaxException import com.fsck.k9.mail.AuthType import com.fsck.k9.mail.ConnectionSecurity import com.fsck.k9.preferences.Protocols import org.xmlpull.v1.XmlPullParser import timber.log.Timber class ProvidersXmlDiscovery( private val backendManager: BackendManager, private val xmlProvider: ProvidersXmlProvider ) : ConnectionSettingsDiscovery { override fun discover(email: String, target: DiscoveryTarget): DiscoveryResults? { val password = "" val user = EmailHelper.getLocalPartFromEmailAddress(email) ?: return null val domain = EmailHelper.getDomainFromEmailAddress(email) ?: return null val provider = findProviderForDomain(domain) ?: return null try { val userUrlEncoded = UrlEncodingHelper.encodeUtf8(user) val emailUrlEncoded = UrlEncodingHelper.encodeUtf8(email) val incomingUserUrlEncoded = provider.incomingUsernameTemplate .replace("\$email", emailUrlEncoded) .replace("\$user", userUrlEncoded) .replace("\$domain", domain) val incomingUri = with(URI(provider.incomingUriTemplate)) { URI(scheme, "$incomingUserUrlEncoded:$password", host, port, null, null, null).toString() } val incomingSettings = backendManager.decodeStoreUri(incomingUri).let { listOf( DiscoveredServerSettings( it.type, it.host, it.port, it.connectionSecurity, it.authenticationType, it.username ) ) } val outgoingUserUrlEncoded = provider.outgoingUsernameTemplate ?.replace("\$email", emailUrlEncoded) ?.replace("\$user", userUrlEncoded) ?.replace("\$domain", domain) val outgoingUserInfo = if (outgoingUserUrlEncoded != null) "$outgoingUserUrlEncoded:$password" else null val outgoingUri = with(URI(provider.outgoingUriTemplate)) { URI(scheme, outgoingUserInfo, host, port, null, null, null).toString() } val outgoingSettings = backendManager.decodeTransportUri(outgoingUri).let { listOf( DiscoveredServerSettings( it.type, it.host, it.port, it.connectionSecurity, it.authenticationType, it.username ) ) } return DiscoveryResults(incomingSettings, outgoingSettings) } catch (use: URISyntaxException) { return null } val incomingSettings = provider.toIncomingServerSettings(email) ?: return null val outgoingSettings = provider.toOutgoingServerSettings(email) ?: return null return DiscoveryResults(listOf(incomingSettings), listOf(outgoingSettings)) } private fun findProviderForDomain(domain: String): Provider? { Loading Loading @@ -124,17 +75,69 @@ class ProvidersXmlDiscovery( } } while (!(xmlEventType == XmlPullParser.END_TAG && xml.name == "provider")) return if (incomingUriTemplate != null && incomingUsernameTemplate != null && outgoingUriTemplate != null) { return if (incomingUriTemplate != null && incomingUsernameTemplate != null && outgoingUriTemplate != null && outgoingUsernameTemplate != null ) { Provider(incomingUriTemplate, incomingUsernameTemplate, outgoingUriTemplate, outgoingUsernameTemplate) } else { null } } private fun Provider.toIncomingServerSettings(email: String): DiscoveredServerSettings? { val user = EmailHelper.getLocalPartFromEmailAddress(email) ?: return null val domain = EmailHelper.getDomainFromEmailAddress(email) ?: return null val username = incomingUsernameTemplate.fillInUsernameTemplate(email, user, domain) val security = when { incomingUriTemplate.startsWith("imap+ssl") -> ConnectionSecurity.SSL_TLS_REQUIRED incomingUriTemplate.startsWith("imap+tls") -> ConnectionSecurity.STARTTLS_REQUIRED else -> error("Connection security required") } val uri = Uri.parse(incomingUriTemplate) val host = uri.host ?: error("Host name required") val port = if (uri.port == -1) { if (security == ConnectionSecurity.STARTTLS_REQUIRED) 143 else 993 } else { uri.port } return DiscoveredServerSettings(Protocols.IMAP, host, port, security, AuthType.PLAIN, username) } private fun Provider.toOutgoingServerSettings(email: String): DiscoveredServerSettings? { val user = EmailHelper.getLocalPartFromEmailAddress(email) ?: return null val domain = EmailHelper.getDomainFromEmailAddress(email) ?: return null val username = outgoingUsernameTemplate.fillInUsernameTemplate(email, user, domain) val security = when { outgoingUriTemplate.startsWith("smtp+ssl") -> ConnectionSecurity.SSL_TLS_REQUIRED outgoingUriTemplate.startsWith("smtp+tls") -> ConnectionSecurity.STARTTLS_REQUIRED else -> error("Connection security required") } val uri = Uri.parse(outgoingUriTemplate) val host = uri.host ?: error("Host name required") val port = if (uri.port == -1) { if (security == ConnectionSecurity.STARTTLS_REQUIRED) 587 else 465 } else { uri.port } return DiscoveredServerSettings(Protocols.SMTP, host, port, security, AuthType.PLAIN, username) } private fun String.fillInUsernameTemplate(email: String, user: String, domain: String): String { return this.replace("\$email", email).replace("\$user", user).replace("\$domain", domain) } internal data class Provider( val incomingUriTemplate: String, val incomingUsernameTemplate: String, val outgoingUriTemplate: String, val outgoingUsernameTemplate: String? val outgoingUsernameTemplate: String ) } app/autodiscovery/providersxml/src/main/res/xml/providers.xml +0 −10 Original line number Diff line number Diff line Loading @@ -713,14 +713,4 @@ <incoming uri="imap+ssl+://mail.ecloud.global" username="$email" /> <outgoing uri="smtp+tls+://mail.ecloud.global" username="$email" /> </provider> <!-- Developers' vanity providers --> <provider id="fsck.com" label="Jesse's personal mail" domain="fsck.com" > <incoming uri="imap+ssl+://fsck.com" username="$user" /> <outgoing uri="smtp+tls+://mail.bestpractical.com:2525" /> </provider> <provider id="bestpractical.com" label="Best Practical Solutions" domain="bestpractical.com" > <incoming uri="imap+ssl+://imap.bestpractical.com" username="$user" /> <outgoing uri="smtp+tls+://smtp.bestpractical.com:2525" /> </provider> </providers> app/autodiscovery/providersxml/src/test/java/com/fsck/k9/autodiscovery/providersxml/ProvidersXmlDiscoveryTest.kt +1 −13 Original line number Diff line number Diff line Loading @@ -3,26 +3,14 @@ package com.fsck.k9.autodiscovery.providersxml import androidx.test.core.app.ApplicationProvider import com.fsck.k9.RobolectricTest import com.fsck.k9.autodiscovery.api.DiscoveryTarget import com.fsck.k9.backend.BackendManager import com.fsck.k9.backend.imap.ImapStoreUriDecoder import com.fsck.k9.mail.AuthType import com.fsck.k9.mail.ConnectionSecurity import com.fsck.k9.mail.transport.smtp.SmtpTransportUriDecoder import com.google.common.truth.Truth.assertThat import com.nhaarman.mockitokotlin2.doAnswer import com.nhaarman.mockitokotlin2.mock import org.junit.Test import org.mockito.ArgumentMatchers.anyString class ProvidersXmlDiscoveryTest : RobolectricTest() { private val backendManager = mock<BackendManager> { on { decodeStoreUri(anyString()) } doAnswer { mock -> ImapStoreUriDecoder.decode(mock.getArgument(0)) } on { decodeTransportUri(anyString()) } doAnswer { mock -> SmtpTransportUriDecoder.decodeSmtpUri(mock.getArgument(0)) } } private val xmlProvider = ProvidersXmlProvider(ApplicationProvider.getApplicationContext()) private val providersXmlDiscovery = ProvidersXmlDiscovery(backendManager, xmlProvider) private val providersXmlDiscovery = ProvidersXmlDiscovery(xmlProvider) @Test fun discover_withGmailDomain_shouldReturnCorrectSettings() { Loading Loading
app/autodiscovery/providersxml/src/main/java/com/fsck/k9/autodiscovery/providersxml/KoinModule.kt +1 −1 Original line number Diff line number Diff line Loading @@ -4,5 +4,5 @@ import org.koin.dsl.module val autodiscoveryProvidersXmlModule = module { factory { ProvidersXmlProvider(context = get()) } factory { ProvidersXmlDiscovery(backendManager = get(), xmlProvider = get()) } factory { ProvidersXmlDiscovery(xmlProvider = get()) } }
app/autodiscovery/providersxml/src/main/java/com/fsck/k9/autodiscovery/providersxml/ProvidersXmlDiscovery.kt +61 −58 Original line number Diff line number Diff line package com.fsck.k9.autodiscovery.providersxml import android.content.res.XmlResourceParser import android.net.Uri import com.fsck.k9.autodiscovery.api.ConnectionSettingsDiscovery import com.fsck.k9.autodiscovery.api.DiscoveredServerSettings import com.fsck.k9.autodiscovery.api.DiscoveryResults import com.fsck.k9.autodiscovery.api.DiscoveryTarget import com.fsck.k9.backend.BackendManager import com.fsck.k9.helper.EmailHelper import com.fsck.k9.helper.UrlEncodingHelper import java.net.URI import java.net.URISyntaxException import com.fsck.k9.mail.AuthType import com.fsck.k9.mail.ConnectionSecurity import com.fsck.k9.preferences.Protocols import org.xmlpull.v1.XmlPullParser import timber.log.Timber class ProvidersXmlDiscovery( private val backendManager: BackendManager, private val xmlProvider: ProvidersXmlProvider ) : ConnectionSettingsDiscovery { override fun discover(email: String, target: DiscoveryTarget): DiscoveryResults? { val password = "" val user = EmailHelper.getLocalPartFromEmailAddress(email) ?: return null val domain = EmailHelper.getDomainFromEmailAddress(email) ?: return null val provider = findProviderForDomain(domain) ?: return null try { val userUrlEncoded = UrlEncodingHelper.encodeUtf8(user) val emailUrlEncoded = UrlEncodingHelper.encodeUtf8(email) val incomingUserUrlEncoded = provider.incomingUsernameTemplate .replace("\$email", emailUrlEncoded) .replace("\$user", userUrlEncoded) .replace("\$domain", domain) val incomingUri = with(URI(provider.incomingUriTemplate)) { URI(scheme, "$incomingUserUrlEncoded:$password", host, port, null, null, null).toString() } val incomingSettings = backendManager.decodeStoreUri(incomingUri).let { listOf( DiscoveredServerSettings( it.type, it.host, it.port, it.connectionSecurity, it.authenticationType, it.username ) ) } val outgoingUserUrlEncoded = provider.outgoingUsernameTemplate ?.replace("\$email", emailUrlEncoded) ?.replace("\$user", userUrlEncoded) ?.replace("\$domain", domain) val outgoingUserInfo = if (outgoingUserUrlEncoded != null) "$outgoingUserUrlEncoded:$password" else null val outgoingUri = with(URI(provider.outgoingUriTemplate)) { URI(scheme, outgoingUserInfo, host, port, null, null, null).toString() } val outgoingSettings = backendManager.decodeTransportUri(outgoingUri).let { listOf( DiscoveredServerSettings( it.type, it.host, it.port, it.connectionSecurity, it.authenticationType, it.username ) ) } return DiscoveryResults(incomingSettings, outgoingSettings) } catch (use: URISyntaxException) { return null } val incomingSettings = provider.toIncomingServerSettings(email) ?: return null val outgoingSettings = provider.toOutgoingServerSettings(email) ?: return null return DiscoveryResults(listOf(incomingSettings), listOf(outgoingSettings)) } private fun findProviderForDomain(domain: String): Provider? { Loading Loading @@ -124,17 +75,69 @@ class ProvidersXmlDiscovery( } } while (!(xmlEventType == XmlPullParser.END_TAG && xml.name == "provider")) return if (incomingUriTemplate != null && incomingUsernameTemplate != null && outgoingUriTemplate != null) { return if (incomingUriTemplate != null && incomingUsernameTemplate != null && outgoingUriTemplate != null && outgoingUsernameTemplate != null ) { Provider(incomingUriTemplate, incomingUsernameTemplate, outgoingUriTemplate, outgoingUsernameTemplate) } else { null } } private fun Provider.toIncomingServerSettings(email: String): DiscoveredServerSettings? { val user = EmailHelper.getLocalPartFromEmailAddress(email) ?: return null val domain = EmailHelper.getDomainFromEmailAddress(email) ?: return null val username = incomingUsernameTemplate.fillInUsernameTemplate(email, user, domain) val security = when { incomingUriTemplate.startsWith("imap+ssl") -> ConnectionSecurity.SSL_TLS_REQUIRED incomingUriTemplate.startsWith("imap+tls") -> ConnectionSecurity.STARTTLS_REQUIRED else -> error("Connection security required") } val uri = Uri.parse(incomingUriTemplate) val host = uri.host ?: error("Host name required") val port = if (uri.port == -1) { if (security == ConnectionSecurity.STARTTLS_REQUIRED) 143 else 993 } else { uri.port } return DiscoveredServerSettings(Protocols.IMAP, host, port, security, AuthType.PLAIN, username) } private fun Provider.toOutgoingServerSettings(email: String): DiscoveredServerSettings? { val user = EmailHelper.getLocalPartFromEmailAddress(email) ?: return null val domain = EmailHelper.getDomainFromEmailAddress(email) ?: return null val username = outgoingUsernameTemplate.fillInUsernameTemplate(email, user, domain) val security = when { outgoingUriTemplate.startsWith("smtp+ssl") -> ConnectionSecurity.SSL_TLS_REQUIRED outgoingUriTemplate.startsWith("smtp+tls") -> ConnectionSecurity.STARTTLS_REQUIRED else -> error("Connection security required") } val uri = Uri.parse(outgoingUriTemplate) val host = uri.host ?: error("Host name required") val port = if (uri.port == -1) { if (security == ConnectionSecurity.STARTTLS_REQUIRED) 587 else 465 } else { uri.port } return DiscoveredServerSettings(Protocols.SMTP, host, port, security, AuthType.PLAIN, username) } private fun String.fillInUsernameTemplate(email: String, user: String, domain: String): String { return this.replace("\$email", email).replace("\$user", user).replace("\$domain", domain) } internal data class Provider( val incomingUriTemplate: String, val incomingUsernameTemplate: String, val outgoingUriTemplate: String, val outgoingUsernameTemplate: String? val outgoingUsernameTemplate: String ) }
app/autodiscovery/providersxml/src/main/res/xml/providers.xml +0 −10 Original line number Diff line number Diff line Loading @@ -713,14 +713,4 @@ <incoming uri="imap+ssl+://mail.ecloud.global" username="$email" /> <outgoing uri="smtp+tls+://mail.ecloud.global" username="$email" /> </provider> <!-- Developers' vanity providers --> <provider id="fsck.com" label="Jesse's personal mail" domain="fsck.com" > <incoming uri="imap+ssl+://fsck.com" username="$user" /> <outgoing uri="smtp+tls+://mail.bestpractical.com:2525" /> </provider> <provider id="bestpractical.com" label="Best Practical Solutions" domain="bestpractical.com" > <incoming uri="imap+ssl+://imap.bestpractical.com" username="$user" /> <outgoing uri="smtp+tls+://smtp.bestpractical.com:2525" /> </provider> </providers>
app/autodiscovery/providersxml/src/test/java/com/fsck/k9/autodiscovery/providersxml/ProvidersXmlDiscoveryTest.kt +1 −13 Original line number Diff line number Diff line Loading @@ -3,26 +3,14 @@ package com.fsck.k9.autodiscovery.providersxml import androidx.test.core.app.ApplicationProvider import com.fsck.k9.RobolectricTest import com.fsck.k9.autodiscovery.api.DiscoveryTarget import com.fsck.k9.backend.BackendManager import com.fsck.k9.backend.imap.ImapStoreUriDecoder import com.fsck.k9.mail.AuthType import com.fsck.k9.mail.ConnectionSecurity import com.fsck.k9.mail.transport.smtp.SmtpTransportUriDecoder import com.google.common.truth.Truth.assertThat import com.nhaarman.mockitokotlin2.doAnswer import com.nhaarman.mockitokotlin2.mock import org.junit.Test import org.mockito.ArgumentMatchers.anyString class ProvidersXmlDiscoveryTest : RobolectricTest() { private val backendManager = mock<BackendManager> { on { decodeStoreUri(anyString()) } doAnswer { mock -> ImapStoreUriDecoder.decode(mock.getArgument(0)) } on { decodeTransportUri(anyString()) } doAnswer { mock -> SmtpTransportUriDecoder.decodeSmtpUri(mock.getArgument(0)) } } private val xmlProvider = ProvidersXmlProvider(ApplicationProvider.getApplicationContext()) private val providersXmlDiscovery = ProvidersXmlDiscovery(backendManager, xmlProvider) private val providersXmlDiscovery = ProvidersXmlDiscovery(xmlProvider) @Test fun discover_withGmailDomain_shouldReturnCorrectSettings() { Loading