Add AutoconfigParser
interface
This commit is contained in:
parent
0ab058656e
commit
29ce3dbfaf
6 changed files with 322 additions and 312 deletions
|
@ -64,6 +64,6 @@ private fun createAutoconfigDiscovery(
|
|||
urlProvider: AutoconfigUrlProvider,
|
||||
): AutoconfigDiscovery {
|
||||
val fetcher = OkHttpAutoconfigFetcher(okHttpClient)
|
||||
val parser = SuspendableAutoconfigParser(AutoconfigParser())
|
||||
val parser = SuspendableAutoconfigParser(RealAutoconfigParser())
|
||||
return AutoconfigDiscovery(urlProvider, fetcher, parser)
|
||||
}
|
||||
|
|
|
@ -1,320 +1,14 @@
|
|||
package app.k9mail.autodiscovery.autoconfig
|
||||
|
||||
import app.k9mail.autodiscovery.api.AuthenticationType
|
||||
import app.k9mail.autodiscovery.api.AuthenticationType.OAuth2
|
||||
import app.k9mail.autodiscovery.api.AuthenticationType.PasswordCleartext
|
||||
import app.k9mail.autodiscovery.api.AuthenticationType.PasswordEncrypted
|
||||
import app.k9mail.autodiscovery.api.AutoDiscoveryResult
|
||||
import app.k9mail.autodiscovery.api.ConnectionSecurity
|
||||
import app.k9mail.autodiscovery.api.ConnectionSecurity.StartTLS
|
||||
import app.k9mail.autodiscovery.api.ConnectionSecurity.TLS
|
||||
import app.k9mail.autodiscovery.api.ImapServerSettings
|
||||
import app.k9mail.autodiscovery.api.IncomingServerSettings
|
||||
import app.k9mail.autodiscovery.api.OutgoingServerSettings
|
||||
import app.k9mail.autodiscovery.api.SmtpServerSettings
|
||||
import app.k9mail.core.common.mail.EmailAddress
|
||||
import app.k9mail.core.common.net.HostNameUtils
|
||||
import app.k9mail.core.common.net.Hostname
|
||||
import app.k9mail.core.common.net.Port
|
||||
import app.k9mail.core.common.net.toHostname
|
||||
import app.k9mail.core.common.net.toPort
|
||||
import com.fsck.k9.helper.EmailHelper
|
||||
import com.fsck.k9.logging.Timber
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
|
||||
private typealias ServerSettingsFactory<T> = (
|
||||
hostname: Hostname,
|
||||
port: Port,
|
||||
connectionSecurity: ConnectionSecurity,
|
||||
authenticationType: AuthenticationType,
|
||||
username: String,
|
||||
) -> T
|
||||
|
||||
/**
|
||||
* Parser for Thunderbird's Autoconfig file format.
|
||||
*
|
||||
* See [https://github.com/thundernest/autoconfig](https://github.com/thundernest/autoconfig)
|
||||
*/
|
||||
internal class AutoconfigParser {
|
||||
fun parseSettings(stream: InputStream, email: EmailAddress): AutoDiscoveryResult? {
|
||||
return try {
|
||||
ClientConfigParser(stream, email.address).parse()
|
||||
} catch (e: XmlPullParserException) {
|
||||
throw AutoconfigParserException("Error parsing Autoconfig XML", e)
|
||||
}
|
||||
}
|
||||
internal interface AutoconfigParser {
|
||||
fun parseSettings(inputStream: InputStream, email: EmailAddress): AutoDiscoveryResult?
|
||||
}
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
private class ClientConfigParser(
|
||||
private val inputStream: InputStream,
|
||||
private val email: String,
|
||||
) {
|
||||
private val localPart = requireNotNull(EmailHelper.getLocalPartFromEmailAddress(email)) { "Invalid email address" }
|
||||
private val domain = requireNotNull(EmailHelper.getDomainFromEmailAddress(email)) { "Invalid email address" }
|
||||
|
||||
private val pullParser: XmlPullParser = XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||
setInput(InputStreamReader(inputStream))
|
||||
}
|
||||
|
||||
fun parse(): AutoDiscoveryResult {
|
||||
var autoDiscoveryResult: AutoDiscoveryResult? = null
|
||||
do {
|
||||
val eventType = pullParser.next()
|
||||
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
when (pullParser.name) {
|
||||
"clientConfig" -> {
|
||||
autoDiscoveryResult = parseClientConfig()
|
||||
}
|
||||
else -> skipElement()
|
||||
}
|
||||
}
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT)
|
||||
|
||||
if (autoDiscoveryResult == null) {
|
||||
parserError("Missing 'clientConfig' element")
|
||||
}
|
||||
|
||||
return autoDiscoveryResult
|
||||
}
|
||||
|
||||
private fun parseClientConfig(): AutoDiscoveryResult {
|
||||
var autoDiscoveryResult: AutoDiscoveryResult? = null
|
||||
|
||||
readElement { eventType ->
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
when (pullParser.name) {
|
||||
"emailProvider" -> {
|
||||
autoDiscoveryResult = parseEmailProvider()
|
||||
}
|
||||
else -> skipElement()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return autoDiscoveryResult ?: parserError("Missing 'emailProvider' element")
|
||||
}
|
||||
|
||||
private fun parseEmailProvider(): AutoDiscoveryResult {
|
||||
var domainFound = false
|
||||
var incomingServerSettings: IncomingServerSettings? = null
|
||||
var outgoingServerSettings: OutgoingServerSettings? = null
|
||||
|
||||
// The 'id' attribute is required (but not really used) by Thunderbird desktop.
|
||||
val emailProviderId = pullParser.getAttributeValue(null, "id")
|
||||
if (emailProviderId == null) {
|
||||
parserError("Missing 'emailProvider.id' attribute")
|
||||
} else if (!emailProviderId.isValidHostname()) {
|
||||
parserError("Invalid 'emailProvider.id' attribute")
|
||||
}
|
||||
|
||||
readElement { eventType ->
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
when (pullParser.name) {
|
||||
"domain" -> {
|
||||
val domain = readText()
|
||||
if (domain.isValidHostname()) {
|
||||
domainFound = true
|
||||
}
|
||||
}
|
||||
"incomingServer" -> {
|
||||
val serverSettings = parseServer("imap", ::createImapServerSettings)
|
||||
if (incomingServerSettings == null) {
|
||||
incomingServerSettings = serverSettings
|
||||
}
|
||||
}
|
||||
"outgoingServer" -> {
|
||||
val serverSettings = parseServer("smtp", ::createSmtpServerSettings)
|
||||
if (outgoingServerSettings == null) {
|
||||
outgoingServerSettings = serverSettings
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
skipElement()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Thunderbird desktop requires at least one valid 'domain' element.
|
||||
if (!domainFound) {
|
||||
parserError("Valid 'domain' element required")
|
||||
}
|
||||
|
||||
return AutoDiscoveryResult(
|
||||
incomingServerSettings = incomingServerSettings ?: parserError("Missing 'incomingServer' element"),
|
||||
outgoingServerSettings = outgoingServerSettings ?: parserError("Missing 'outgoingServer' element"),
|
||||
)
|
||||
}
|
||||
|
||||
private fun <T> parseServer(
|
||||
protocolType: String,
|
||||
createServerSettings: ServerSettingsFactory<T>,
|
||||
): T? {
|
||||
val type = pullParser.getAttributeValue(null, "type")
|
||||
if (type != protocolType) {
|
||||
Timber.d("Unsupported '%s[type]' value: '%s'", pullParser.name, type)
|
||||
skipElement()
|
||||
return null
|
||||
}
|
||||
|
||||
var hostname: String? = null
|
||||
var port: Int? = null
|
||||
var userName: String? = null
|
||||
var authenticationType: AuthenticationType? = null
|
||||
var connectionSecurity: ConnectionSecurity? = null
|
||||
|
||||
readElement { eventType ->
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
when (pullParser.name) {
|
||||
"hostname" -> hostname = readHostname()
|
||||
"port" -> port = readPort()
|
||||
"username" -> userName = readUsername()
|
||||
"authentication" -> authenticationType = readAuthentication(authenticationType)
|
||||
"socketType" -> connectionSecurity = readSocketType()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val finalHostname = hostname ?: parserError("Missing 'hostname' element")
|
||||
val finalPort = port ?: parserError("Missing 'port' element")
|
||||
val finalUserName = userName ?: parserError("Missing 'username' element")
|
||||
val finalAuthenticationType = authenticationType ?: parserError("No usable 'authentication' element found")
|
||||
val finalConnectionSecurity = connectionSecurity ?: parserError("Missing 'socketType' element")
|
||||
|
||||
return createServerSettings(
|
||||
finalHostname.toHostname(),
|
||||
finalPort.toPort(),
|
||||
finalConnectionSecurity,
|
||||
finalAuthenticationType,
|
||||
finalUserName,
|
||||
)
|
||||
}
|
||||
|
||||
private fun readHostname(): String {
|
||||
val hostNameText = readText()
|
||||
val hostName = hostNameText.replaceVariables()
|
||||
return hostName.takeIf { it.isValidHostname() }
|
||||
?: parserError("Invalid 'hostname' value: '$hostNameText'")
|
||||
}
|
||||
|
||||
private fun readPort(): Int {
|
||||
val portText = readText()
|
||||
return portText.toIntOrNull()?.takeIf { it.isValidPort() }
|
||||
?: parserError("Invalid 'port' value: '$portText'")
|
||||
}
|
||||
|
||||
private fun readUsername(): String = readText().replaceVariables()
|
||||
|
||||
private fun readAuthentication(authenticationType: AuthenticationType?): AuthenticationType? {
|
||||
return authenticationType ?: readText().toAuthenticationType()
|
||||
}
|
||||
|
||||
private fun readSocketType() = readText().toConnectionSecurity()
|
||||
|
||||
private fun String.toAuthenticationType(): AuthenticationType? {
|
||||
return when (this) {
|
||||
"OAuth2" -> OAuth2
|
||||
"password-cleartext" -> PasswordCleartext
|
||||
"password-encrypted" -> PasswordEncrypted
|
||||
else -> {
|
||||
Timber.d("Ignoring unknown 'authentication' value '$this'")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.toConnectionSecurity(): ConnectionSecurity {
|
||||
return when (this) {
|
||||
"SSL" -> TLS
|
||||
"STARTTLS" -> StartTLS
|
||||
else -> parserError("Unknown 'socketType' value: '$this'")
|
||||
}
|
||||
}
|
||||
|
||||
private fun readElement(block: (Int) -> Unit) {
|
||||
require(pullParser.eventType == XmlPullParser.START_TAG)
|
||||
|
||||
val tagName = pullParser.name
|
||||
val depth = pullParser.depth
|
||||
while (true) {
|
||||
when (val eventType = pullParser.next()) {
|
||||
XmlPullParser.END_DOCUMENT -> {
|
||||
parserError("End of document reached while reading element '$tagName'")
|
||||
}
|
||||
XmlPullParser.END_TAG -> {
|
||||
if (pullParser.name == tagName && pullParser.depth == depth) return
|
||||
}
|
||||
else -> {
|
||||
block(eventType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readText(): String {
|
||||
var text = ""
|
||||
readElement { eventType ->
|
||||
when (eventType) {
|
||||
XmlPullParser.TEXT -> {
|
||||
text = pullParser.text
|
||||
}
|
||||
else -> {
|
||||
parserError("Expected text, but got ${XmlPullParser.TYPES[eventType]}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
private fun skipElement() {
|
||||
Timber.d("Skipping element '%s'", pullParser.name)
|
||||
readElement { /* Do nothing */ }
|
||||
}
|
||||
|
||||
private fun parserError(message: String): Nothing {
|
||||
throw AutoconfigParserException(message)
|
||||
}
|
||||
|
||||
private fun String.isValidHostname(): Boolean {
|
||||
val cleanUpHostName = HostNameUtils.cleanUpHostName(this)
|
||||
return HostNameUtils.isLegalHostNameOrIP(cleanUpHostName) != null
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun Int.isValidPort() = this in 0..65535
|
||||
|
||||
private fun String.replaceVariables(): String {
|
||||
return replace("%EMAILDOMAIN%", domain)
|
||||
.replace("%EMAILLOCALPART%", localPart)
|
||||
.replace("%EMAILADDRESS%", email)
|
||||
}
|
||||
|
||||
private fun createImapServerSettings(
|
||||
hostname: Hostname,
|
||||
port: Port,
|
||||
connectionSecurity: ConnectionSecurity,
|
||||
authenticationType: AuthenticationType,
|
||||
username: String,
|
||||
): ImapServerSettings {
|
||||
return ImapServerSettings(hostname, port, connectionSecurity, authenticationType, username)
|
||||
}
|
||||
|
||||
private fun createSmtpServerSettings(
|
||||
hostname: Hostname,
|
||||
port: Port,
|
||||
connectionSecurity: ConnectionSecurity,
|
||||
authenticationType: AuthenticationType,
|
||||
username: String,
|
||||
): SmtpServerSettings {
|
||||
return SmtpServerSettings(hostname, port, connectionSecurity, authenticationType, username)
|
||||
}
|
||||
}
|
||||
|
||||
class AutoconfigParserException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
package app.k9mail.autodiscovery.autoconfig
|
||||
|
||||
class AutoconfigParserException(message: String, cause: Throwable? = null) : RuntimeException(message, cause)
|
|
@ -100,6 +100,6 @@ fun createMxLookupAutoconfigDiscovery(okHttpClient: OkHttpClient): MxLookupAutoc
|
|||
subDomainExtractor = RealSubDomainExtractor(baseDomainExtractor),
|
||||
urlProvider = IspDbAutoconfigUrlProvider(),
|
||||
fetcher = OkHttpAutoconfigFetcher(okHttpClient),
|
||||
parser = SuspendableAutoconfigParser(AutoconfigParser()),
|
||||
parser = SuspendableAutoconfigParser(RealAutoconfigParser()),
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,313 @@
|
|||
package app.k9mail.autodiscovery.autoconfig
|
||||
|
||||
import app.k9mail.autodiscovery.api.AuthenticationType
|
||||
import app.k9mail.autodiscovery.api.AuthenticationType.OAuth2
|
||||
import app.k9mail.autodiscovery.api.AuthenticationType.PasswordCleartext
|
||||
import app.k9mail.autodiscovery.api.AuthenticationType.PasswordEncrypted
|
||||
import app.k9mail.autodiscovery.api.AutoDiscoveryResult
|
||||
import app.k9mail.autodiscovery.api.ConnectionSecurity
|
||||
import app.k9mail.autodiscovery.api.ConnectionSecurity.StartTLS
|
||||
import app.k9mail.autodiscovery.api.ConnectionSecurity.TLS
|
||||
import app.k9mail.autodiscovery.api.ImapServerSettings
|
||||
import app.k9mail.autodiscovery.api.IncomingServerSettings
|
||||
import app.k9mail.autodiscovery.api.OutgoingServerSettings
|
||||
import app.k9mail.autodiscovery.api.SmtpServerSettings
|
||||
import app.k9mail.core.common.mail.EmailAddress
|
||||
import app.k9mail.core.common.net.HostNameUtils
|
||||
import app.k9mail.core.common.net.Hostname
|
||||
import app.k9mail.core.common.net.Port
|
||||
import app.k9mail.core.common.net.toHostname
|
||||
import app.k9mail.core.common.net.toPort
|
||||
import com.fsck.k9.helper.EmailHelper
|
||||
import com.fsck.k9.logging.Timber
|
||||
import java.io.InputStream
|
||||
import java.io.InputStreamReader
|
||||
import org.xmlpull.v1.XmlPullParser
|
||||
import org.xmlpull.v1.XmlPullParserException
|
||||
import org.xmlpull.v1.XmlPullParserFactory
|
||||
|
||||
private typealias ServerSettingsFactory<T> = (
|
||||
hostname: Hostname,
|
||||
port: Port,
|
||||
connectionSecurity: ConnectionSecurity,
|
||||
authenticationType: AuthenticationType,
|
||||
username: String,
|
||||
) -> T
|
||||
|
||||
internal class RealAutoconfigParser : AutoconfigParser {
|
||||
override fun parseSettings(inputStream: InputStream, email: EmailAddress): AutoDiscoveryResult? {
|
||||
return try {
|
||||
ClientConfigParser(inputStream, email.address).parse()
|
||||
} catch (e: XmlPullParserException) {
|
||||
throw AutoconfigParserException("Error parsing Autoconfig XML", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Suppress("TooManyFunctions")
|
||||
private class ClientConfigParser(
|
||||
private val inputStream: InputStream,
|
||||
private val email: String,
|
||||
) {
|
||||
private val localPart = requireNotNull(EmailHelper.getLocalPartFromEmailAddress(email)) { "Invalid email address" }
|
||||
private val domain = requireNotNull(EmailHelper.getDomainFromEmailAddress(email)) { "Invalid email address" }
|
||||
|
||||
private val pullParser: XmlPullParser = XmlPullParserFactory.newInstance().newPullParser().apply {
|
||||
setInput(InputStreamReader(inputStream))
|
||||
}
|
||||
|
||||
fun parse(): AutoDiscoveryResult {
|
||||
var autoDiscoveryResult: AutoDiscoveryResult? = null
|
||||
do {
|
||||
val eventType = pullParser.next()
|
||||
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
when (pullParser.name) {
|
||||
"clientConfig" -> {
|
||||
autoDiscoveryResult = parseClientConfig()
|
||||
}
|
||||
else -> skipElement()
|
||||
}
|
||||
}
|
||||
} while (eventType != XmlPullParser.END_DOCUMENT)
|
||||
|
||||
if (autoDiscoveryResult == null) {
|
||||
parserError("Missing 'clientConfig' element")
|
||||
}
|
||||
|
||||
return autoDiscoveryResult
|
||||
}
|
||||
|
||||
private fun parseClientConfig(): AutoDiscoveryResult {
|
||||
var autoDiscoveryResult: AutoDiscoveryResult? = null
|
||||
|
||||
readElement { eventType ->
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
when (pullParser.name) {
|
||||
"emailProvider" -> {
|
||||
autoDiscoveryResult = parseEmailProvider()
|
||||
}
|
||||
else -> skipElement()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return autoDiscoveryResult ?: parserError("Missing 'emailProvider' element")
|
||||
}
|
||||
|
||||
private fun parseEmailProvider(): AutoDiscoveryResult {
|
||||
var domainFound = false
|
||||
var incomingServerSettings: IncomingServerSettings? = null
|
||||
var outgoingServerSettings: OutgoingServerSettings? = null
|
||||
|
||||
// The 'id' attribute is required (but not really used) by Thunderbird desktop.
|
||||
val emailProviderId = pullParser.getAttributeValue(null, "id")
|
||||
if (emailProviderId == null) {
|
||||
parserError("Missing 'emailProvider.id' attribute")
|
||||
} else if (!emailProviderId.isValidHostname()) {
|
||||
parserError("Invalid 'emailProvider.id' attribute")
|
||||
}
|
||||
|
||||
readElement { eventType ->
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
when (pullParser.name) {
|
||||
"domain" -> {
|
||||
val domain = readText()
|
||||
if (domain.isValidHostname()) {
|
||||
domainFound = true
|
||||
}
|
||||
}
|
||||
"incomingServer" -> {
|
||||
val serverSettings = parseServer("imap", ::createImapServerSettings)
|
||||
if (incomingServerSettings == null) {
|
||||
incomingServerSettings = serverSettings
|
||||
}
|
||||
}
|
||||
"outgoingServer" -> {
|
||||
val serverSettings = parseServer("smtp", ::createSmtpServerSettings)
|
||||
if (outgoingServerSettings == null) {
|
||||
outgoingServerSettings = serverSettings
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
skipElement()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Thunderbird desktop requires at least one valid 'domain' element.
|
||||
if (!domainFound) {
|
||||
parserError("Valid 'domain' element required")
|
||||
}
|
||||
|
||||
return AutoDiscoveryResult(
|
||||
incomingServerSettings = incomingServerSettings ?: parserError("Missing 'incomingServer' element"),
|
||||
outgoingServerSettings = outgoingServerSettings ?: parserError("Missing 'outgoingServer' element"),
|
||||
)
|
||||
}
|
||||
|
||||
private fun <T> parseServer(
|
||||
protocolType: String,
|
||||
createServerSettings: ServerSettingsFactory<T>,
|
||||
): T? {
|
||||
val type = pullParser.getAttributeValue(null, "type")
|
||||
if (type != protocolType) {
|
||||
Timber.d("Unsupported '%s[type]' value: '%s'", pullParser.name, type)
|
||||
skipElement()
|
||||
return null
|
||||
}
|
||||
|
||||
var hostname: String? = null
|
||||
var port: Int? = null
|
||||
var userName: String? = null
|
||||
var authenticationType: AuthenticationType? = null
|
||||
var connectionSecurity: ConnectionSecurity? = null
|
||||
|
||||
readElement { eventType ->
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
when (pullParser.name) {
|
||||
"hostname" -> hostname = readHostname()
|
||||
"port" -> port = readPort()
|
||||
"username" -> userName = readUsername()
|
||||
"authentication" -> authenticationType = readAuthentication(authenticationType)
|
||||
"socketType" -> connectionSecurity = readSocketType()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val finalHostname = hostname ?: parserError("Missing 'hostname' element")
|
||||
val finalPort = port ?: parserError("Missing 'port' element")
|
||||
val finalUserName = userName ?: parserError("Missing 'username' element")
|
||||
val finalAuthenticationType = authenticationType ?: parserError("No usable 'authentication' element found")
|
||||
val finalConnectionSecurity = connectionSecurity ?: parserError("Missing 'socketType' element")
|
||||
|
||||
return createServerSettings(
|
||||
finalHostname.toHostname(),
|
||||
finalPort.toPort(),
|
||||
finalConnectionSecurity,
|
||||
finalAuthenticationType,
|
||||
finalUserName,
|
||||
)
|
||||
}
|
||||
|
||||
private fun readHostname(): String {
|
||||
val hostNameText = readText()
|
||||
val hostName = hostNameText.replaceVariables()
|
||||
return hostName.takeIf { it.isValidHostname() }
|
||||
?: parserError("Invalid 'hostname' value: '$hostNameText'")
|
||||
}
|
||||
|
||||
private fun readPort(): Int {
|
||||
val portText = readText()
|
||||
return portText.toIntOrNull()?.takeIf { it.isValidPort() }
|
||||
?: parserError("Invalid 'port' value: '$portText'")
|
||||
}
|
||||
|
||||
private fun readUsername(): String = readText().replaceVariables()
|
||||
|
||||
private fun readAuthentication(authenticationType: AuthenticationType?): AuthenticationType? {
|
||||
return authenticationType ?: readText().toAuthenticationType()
|
||||
}
|
||||
|
||||
private fun readSocketType() = readText().toConnectionSecurity()
|
||||
|
||||
private fun String.toAuthenticationType(): AuthenticationType? {
|
||||
return when (this) {
|
||||
"OAuth2" -> OAuth2
|
||||
"password-cleartext" -> PasswordCleartext
|
||||
"password-encrypted" -> PasswordEncrypted
|
||||
else -> {
|
||||
Timber.d("Ignoring unknown 'authentication' value '$this'")
|
||||
null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun String.toConnectionSecurity(): ConnectionSecurity {
|
||||
return when (this) {
|
||||
"SSL" -> TLS
|
||||
"STARTTLS" -> StartTLS
|
||||
else -> parserError("Unknown 'socketType' value: '$this'")
|
||||
}
|
||||
}
|
||||
|
||||
private fun readElement(block: (Int) -> Unit) {
|
||||
require(pullParser.eventType == XmlPullParser.START_TAG)
|
||||
|
||||
val tagName = pullParser.name
|
||||
val depth = pullParser.depth
|
||||
while (true) {
|
||||
when (val eventType = pullParser.next()) {
|
||||
XmlPullParser.END_DOCUMENT -> {
|
||||
parserError("End of document reached while reading element '$tagName'")
|
||||
}
|
||||
XmlPullParser.END_TAG -> {
|
||||
if (pullParser.name == tagName && pullParser.depth == depth) return
|
||||
}
|
||||
else -> {
|
||||
block(eventType)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun readText(): String {
|
||||
var text = ""
|
||||
readElement { eventType ->
|
||||
when (eventType) {
|
||||
XmlPullParser.TEXT -> {
|
||||
text = pullParser.text
|
||||
}
|
||||
else -> {
|
||||
parserError("Expected text, but got ${XmlPullParser.TYPES[eventType]}")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
private fun skipElement() {
|
||||
Timber.d("Skipping element '%s'", pullParser.name)
|
||||
readElement { /* Do nothing */ }
|
||||
}
|
||||
|
||||
private fun parserError(message: String): Nothing {
|
||||
throw AutoconfigParserException(message)
|
||||
}
|
||||
|
||||
private fun String.isValidHostname(): Boolean {
|
||||
val cleanUpHostName = HostNameUtils.cleanUpHostName(this)
|
||||
return HostNameUtils.isLegalHostNameOrIP(cleanUpHostName) != null
|
||||
}
|
||||
|
||||
@Suppress("MagicNumber")
|
||||
private fun Int.isValidPort() = this in 0..65535
|
||||
|
||||
private fun String.replaceVariables(): String {
|
||||
return replace("%EMAILDOMAIN%", domain)
|
||||
.replace("%EMAILLOCALPART%", localPart)
|
||||
.replace("%EMAILADDRESS%", email)
|
||||
}
|
||||
|
||||
private fun createImapServerSettings(
|
||||
hostname: Hostname,
|
||||
port: Port,
|
||||
connectionSecurity: ConnectionSecurity,
|
||||
authenticationType: AuthenticationType,
|
||||
username: String,
|
||||
): ImapServerSettings {
|
||||
return ImapServerSettings(hostname, port, connectionSecurity, authenticationType, username)
|
||||
}
|
||||
|
||||
private fun createSmtpServerSettings(
|
||||
hostname: Hostname,
|
||||
port: Port,
|
||||
connectionSecurity: ConnectionSecurity,
|
||||
authenticationType: AuthenticationType,
|
||||
username: String,
|
||||
): SmtpServerSettings {
|
||||
return SmtpServerSettings(hostname, port, connectionSecurity, authenticationType, username)
|
||||
}
|
||||
}
|
|
@ -27,8 +27,8 @@ import org.junit.Test
|
|||
|
||||
private const val PRINT_MODIFIED_XML = false
|
||||
|
||||
class AutoconfigParserTest {
|
||||
private val parser = AutoconfigParser()
|
||||
class RealAutoconfigParserTest {
|
||||
private val parser = RealAutoconfigParser()
|
||||
|
||||
@Language("XML")
|
||||
private val minimalConfig =
|
Loading…
Reference in a new issue