IMAP: Add app version to ID command
This commit is contained in:
parent
faece141b7
commit
77ff16bcf7
14 changed files with 60 additions and 24 deletions
|
@ -10,6 +10,7 @@ import com.fsck.k9.mail.AuthType
|
|||
import com.fsck.k9.mail.power.PowerManager
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
import com.fsck.k9.mail.store.imap.IdleRefreshManager
|
||||
import com.fsck.k9.mail.store.imap.ImapClientId
|
||||
import com.fsck.k9.mail.store.imap.ImapStore
|
||||
import com.fsck.k9.mail.store.imap.ImapStoreConfig
|
||||
import com.fsck.k9.mail.transport.smtp.SmtpTransport
|
||||
|
@ -19,6 +20,7 @@ import kotlinx.coroutines.flow.Flow
|
|||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.map
|
||||
|
||||
@Suppress("LongParameterList")
|
||||
class ImapBackendFactory(
|
||||
private val accountManager: AccountManager,
|
||||
private val powerManager: PowerManager,
|
||||
|
@ -27,6 +29,7 @@ class ImapBackendFactory(
|
|||
private val trustedSocketFactory: TrustedSocketFactory,
|
||||
private val context: Context,
|
||||
private val clientIdAppName: String,
|
||||
private val clientIdAppVersion: String,
|
||||
) : BackendFactory {
|
||||
override fun createBackend(account: Account): Backend {
|
||||
val accountName = account.displayName
|
||||
|
@ -71,7 +74,7 @@ class ImapBackendFactory(
|
|||
|
||||
override fun isSubscribedFoldersOnly() = account.isSubscribedFoldersOnly
|
||||
|
||||
override fun clientIdAppName() = clientIdAppName
|
||||
override fun clientId() = ImapClientId(appName = clientIdAppName, appVersion = clientIdAppVersion)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -29,12 +29,14 @@ val backendsModule = module {
|
|||
trustedSocketFactory = get(),
|
||||
context = get(),
|
||||
clientIdAppName = get(named("ClientIdAppName")),
|
||||
clientIdAppVersion = get(named("ClientIdAppVersion")),
|
||||
)
|
||||
}
|
||||
single<SystemAlarmManager> { AndroidAlarmManager(context = get(), alarmManager = get()) }
|
||||
single<IdleRefreshManager> { BackendIdleRefreshManager(alarmManager = get()) }
|
||||
single { Pop3BackendFactory(get(), get()) }
|
||||
single(named("ClientIdAppName")) { BuildConfig.CLIENT_ID_APP_NAME }
|
||||
single(named("ClientIdAppVersion")) { BuildConfig.VERSION_NAME }
|
||||
single<OAuth2TokenProviderFactory> { RealOAuth2TokenProviderFactory(context = get()) }
|
||||
|
||||
developmentModuleAdditions()
|
||||
|
|
|
@ -30,6 +30,7 @@ val featureAccountServerValidationModule = module {
|
|||
trustedSocketFactory = get(),
|
||||
oAuth2TokenProviderFactory = get(),
|
||||
clientIdAppName = get(named("ClientIdAppName")),
|
||||
clientIdAppVersion = get(named("ClientIdAppVersion")),
|
||||
),
|
||||
pop3Validator = Pop3ServerSettingsValidator(
|
||||
trustedSocketFactory = get(),
|
||||
|
|
|
@ -46,6 +46,7 @@ class ServerValidationModuleKtTest : KoinTest {
|
|||
single<LocalKeyStore> { mock() }
|
||||
factory<AccountCommonExternalContract.AccountStateLoader> { mock() }
|
||||
single(named("ClientIdAppName")) { "App Name" }
|
||||
single(named("ClientIdAppVersion")) { "App Version" }
|
||||
}
|
||||
|
||||
@OptIn(KoinExperimentalAPI::class)
|
||||
|
|
|
@ -0,0 +1,6 @@
|
|||
package com.fsck.k9.mail.store.imap
|
||||
|
||||
data class ImapClientId(
|
||||
val appName: String,
|
||||
val appVersion: String,
|
||||
)
|
|
@ -16,6 +16,7 @@ class ImapServerSettingsValidator(
|
|||
private val trustedSocketFactory: TrustedSocketFactory,
|
||||
private val oAuth2TokenProviderFactory: OAuth2TokenProviderFactory?,
|
||||
private val clientIdAppName: String,
|
||||
private val clientIdAppVersion: String,
|
||||
) : ServerSettingsValidator {
|
||||
|
||||
@Suppress("TooGenericExceptionCaught")
|
||||
|
@ -26,7 +27,7 @@ class ImapServerSettingsValidator(
|
|||
val config = object : ImapStoreConfig {
|
||||
override val logLabel = "check"
|
||||
override fun isSubscribedFoldersOnly() = false
|
||||
override fun clientIdAppName() = clientIdAppName
|
||||
override fun clientId() = ImapClientId(appName = clientIdAppName, appVersion = clientIdAppVersion)
|
||||
}
|
||||
val oAuth2TokenProvider = createOAuth2TokenProviderOrNull(authStateStorage)
|
||||
val store = RealImapStore(serverSettings, config, trustedSocketFactory, oAuth2TokenProvider)
|
||||
|
|
|
@ -15,7 +15,7 @@ internal interface ImapSettings {
|
|||
val password: String?
|
||||
val clientCertificateAlias: String?
|
||||
val useCompression: Boolean
|
||||
val clientIdAppName: String?
|
||||
val clientId: ImapClientId?
|
||||
|
||||
var pathPrefix: String?
|
||||
var pathDelimiter: String?
|
||||
|
|
|
@ -3,5 +3,5 @@ package com.fsck.k9.mail.store.imap
|
|||
interface ImapStoreConfig {
|
||||
val logLabel: String
|
||||
fun isSubscribedFoldersOnly(): Boolean
|
||||
fun clientIdAppName(): String
|
||||
fun clientId(): ImapClientId
|
||||
}
|
||||
|
|
|
@ -563,10 +563,14 @@ internal class RealImapConnection(
|
|||
}
|
||||
|
||||
private fun sendClientIdIfSupported() {
|
||||
if (hasCapability(Capabilities.ID) && settings.clientIdAppName != null) {
|
||||
val encodedAppName = ImapUtility.encodeString(settings.clientIdAppName)
|
||||
val clientId = settings.clientId
|
||||
|
||||
if (hasCapability(Capabilities.ID) && clientId != null) {
|
||||
val encodedAppName = ImapUtility.encodeString(clientId.appName)
|
||||
val encodedAppVersion = ImapUtility.encodeString(clientId.appVersion)
|
||||
|
||||
try {
|
||||
executeSimpleCommand("""ID ("name" $encodedAppName)""")
|
||||
executeSimpleCommand("""ID ("name" $encodedAppName "version" $encodedAppVersion)""")
|
||||
} catch (e: NegativeImapResponseException) {
|
||||
Timber.d(e, "Ignoring negative response to ID command")
|
||||
}
|
||||
|
|
|
@ -302,7 +302,7 @@ internal open class RealImapStore(
|
|||
|
||||
override val useCompression: Boolean = serverSettings.isUseCompression
|
||||
|
||||
override val clientIdAppName: String? = config.clientIdAppName().takeIf { serverSettings.isSendClientId }
|
||||
override val clientId: ImapClientId? = config.clientId().takeIf { serverSettings.isSendClientId }
|
||||
|
||||
override var pathPrefix: String?
|
||||
get() = this@RealImapStore.pathPrefix
|
||||
|
|
|
@ -24,6 +24,7 @@ private const val AUTHORIZATION_STATE = "auth state"
|
|||
private const val AUTHORIZATION_TOKEN = "auth-token"
|
||||
private val CLIENT_CERTIFICATE_ALIAS: String? = null
|
||||
private const val CLIENT_ID = "clientId"
|
||||
private const val CLIENT_VERSION = "clientVersion"
|
||||
|
||||
class ImapServerSettingsValidatorTest {
|
||||
private val fakeTrustManager = FakeTrustManager()
|
||||
|
@ -32,6 +33,7 @@ class ImapServerSettingsValidatorTest {
|
|||
trustedSocketFactory = trustedSocketFactory,
|
||||
oAuth2TokenProviderFactory = null,
|
||||
clientIdAppName = CLIENT_ID,
|
||||
clientIdAppVersion = CLIENT_VERSION,
|
||||
)
|
||||
|
||||
@Test
|
||||
|
@ -45,7 +47,7 @@ class ImapServerSettingsValidatorTest {
|
|||
output("+")
|
||||
expect("AHVzZXIAcGFzc3dvcmQ=")
|
||||
output("2 OK [CAPABILITY IMAP4rev1 AUTH=PLAIN NAMESPACE ID] LOGIN completed")
|
||||
expect("3 ID (\"name\" \"$CLIENT_ID\")")
|
||||
expect("3 ID (\"name\" \"$CLIENT_ID\" \"version\" \"$CLIENT_VERSION\")")
|
||||
output("* ID NIL")
|
||||
output("3 OK ID completed")
|
||||
expect("4 NAMESPACE")
|
||||
|
@ -85,6 +87,7 @@ class ImapServerSettingsValidatorTest {
|
|||
FakeOAuth2TokenProvider()
|
||||
},
|
||||
clientIdAppName = CLIENT_ID,
|
||||
clientIdAppVersion = CLIENT_VERSION,
|
||||
)
|
||||
val server = startServer {
|
||||
output("* OK IMAP4rev1 server ready")
|
||||
|
@ -93,7 +96,7 @@ class ImapServerSettingsValidatorTest {
|
|||
output("1 OK CAPABILITY Completed")
|
||||
expect("2 AUTHENTICATE OAUTHBEARER bixhPXVzZXIsAWF1dGg9QmVhcmVyIGF1dGgtdG9rZW4BAQ==")
|
||||
output("2 OK [CAPABILITY IMAP4rev1 SASL-IR AUTH=PLAIN AUTH=OAUTHBEARER NAMESPACE ID] LOGIN completed")
|
||||
expect("3 ID (\"name\" \"$CLIENT_ID\")")
|
||||
expect("3 ID (\"name\" \"$CLIENT_ID\" \"version\" \"$CLIENT_VERSION\")")
|
||||
output("* ID NIL")
|
||||
output("3 OK ID completed")
|
||||
expect("4 NAMESPACE")
|
||||
|
|
|
@ -831,12 +831,15 @@ class RealImapConnectionTest {
|
|||
fun `open() with ID capability and clientIdAppName should send ID command`() {
|
||||
val server = MockImapServer().apply {
|
||||
simplePreAuthAndLoginDialog(postAuthCapabilities = "ID")
|
||||
expect("""3 ID ("name" "AppName")""")
|
||||
expect("""3 ID ("name" "AppName" "version" "AppVersion")""")
|
||||
output("""* ID ("name" "CustomImapServer" "vendor" "Company, Inc." "version" "0.1")""")
|
||||
output("3 OK ID completed")
|
||||
simplePostAuthenticationDialog(tag = 4)
|
||||
}
|
||||
val imapConnection = startServerAndCreateImapConnection(server, clientIdAppName = "AppName")
|
||||
val imapConnection = startServerAndCreateImapConnection(
|
||||
server,
|
||||
clientId = ImapClientId(appName = "AppName", appVersion = "AppVersion"),
|
||||
)
|
||||
|
||||
imapConnection.open()
|
||||
|
||||
|
@ -850,7 +853,10 @@ class RealImapConnectionTest {
|
|||
simplePreAuthAndLoginDialog()
|
||||
simplePostAuthenticationDialog(tag = 3)
|
||||
}
|
||||
val imapConnection = startServerAndCreateImapConnection(server, clientIdAppName = "AppName")
|
||||
val imapConnection = startServerAndCreateImapConnection(
|
||||
server,
|
||||
clientId = ImapClientId(appName = "AppName", appVersion = "AppVersion"),
|
||||
)
|
||||
|
||||
imapConnection.open()
|
||||
|
||||
|
@ -864,7 +870,7 @@ class RealImapConnectionTest {
|
|||
simplePreAuthAndLoginDialog(postAuthCapabilities = "ID")
|
||||
simplePostAuthenticationDialog(tag = 3)
|
||||
}
|
||||
val imapConnection = startServerAndCreateImapConnection(server, clientIdAppName = null)
|
||||
val imapConnection = startServerAndCreateImapConnection(server, clientId = null)
|
||||
|
||||
imapConnection.open()
|
||||
|
||||
|
@ -876,12 +882,15 @@ class RealImapConnectionTest {
|
|||
fun `open() with empty untagged ID response`() {
|
||||
val server = MockImapServer().apply {
|
||||
simplePreAuthAndLoginDialog(postAuthCapabilities = "ID")
|
||||
expect("""3 ID ("name" "AppName")""")
|
||||
expect("""3 ID ("name" "AppName" "version" "AppVersion")""")
|
||||
output("""* ID NIL""")
|
||||
output("3 OK ID completed")
|
||||
simplePostAuthenticationDialog(tag = 4)
|
||||
}
|
||||
val imapConnection = startServerAndCreateImapConnection(server, clientIdAppName = "AppName")
|
||||
val imapConnection = startServerAndCreateImapConnection(
|
||||
server,
|
||||
clientId = ImapClientId(appName = "AppName", appVersion = "AppVersion"),
|
||||
)
|
||||
|
||||
imapConnection.open()
|
||||
|
||||
|
@ -893,11 +902,14 @@ class RealImapConnectionTest {
|
|||
fun `open() with missing untagged ID response`() {
|
||||
val server = MockImapServer().apply {
|
||||
simplePreAuthAndLoginDialog(postAuthCapabilities = "ID")
|
||||
expect("""3 ID ("name" "AppName")""")
|
||||
expect("""3 ID ("name" "AppName" "version" "AppVersion")""")
|
||||
output("3 OK ID completed")
|
||||
simplePostAuthenticationDialog(tag = 4)
|
||||
}
|
||||
val imapConnection = startServerAndCreateImapConnection(server, clientIdAppName = "AppName")
|
||||
val imapConnection = startServerAndCreateImapConnection(
|
||||
server,
|
||||
clientId = ImapClientId(appName = "AppName", appVersion = "AppVersion"),
|
||||
)
|
||||
|
||||
imapConnection.open()
|
||||
|
||||
|
@ -909,11 +921,14 @@ class RealImapConnectionTest {
|
|||
fun `open() with BAD response to ID command should not throw`() {
|
||||
val server = MockImapServer().apply {
|
||||
simplePreAuthAndLoginDialog(postAuthCapabilities = "ID")
|
||||
expect("""3 ID ("name" "AppName")""")
|
||||
expect("""3 ID ("name" "AppName" "version" "AppVersion")""")
|
||||
output("3 BAD Server doesn't like the ID command")
|
||||
simplePostAuthenticationDialog(tag = 4)
|
||||
}
|
||||
val imapConnection = startServerAndCreateImapConnection(server, clientIdAppName = "AppName")
|
||||
val imapConnection = startServerAndCreateImapConnection(
|
||||
server,
|
||||
clientId = ImapClientId(appName = "AppName", appVersion = "AppVersion"),
|
||||
)
|
||||
|
||||
imapConnection.open()
|
||||
|
||||
|
@ -1113,7 +1128,7 @@ class RealImapConnectionTest {
|
|||
connectionSecurity: ConnectionSecurity = ConnectionSecurity.NONE,
|
||||
authType: AuthType = AuthType.PLAIN,
|
||||
useCompression: Boolean = false,
|
||||
clientIdAppName: String? = null,
|
||||
clientId: ImapClientId? = null,
|
||||
): ImapConnection {
|
||||
server.start()
|
||||
|
||||
|
@ -1125,7 +1140,7 @@ class RealImapConnectionTest {
|
|||
username = USERNAME,
|
||||
password = PASSWORD,
|
||||
useCompression = useCompression,
|
||||
clientIdAppName = clientIdAppName,
|
||||
clientId = clientId,
|
||||
)
|
||||
|
||||
return createImapConnection(settings, socketFactory, oAuth2TokenProvider)
|
||||
|
|
|
@ -413,7 +413,7 @@ class RealImapStoreTest {
|
|||
return object : ImapStoreConfig {
|
||||
override val logLabel: String = "irrelevant"
|
||||
override fun isSubscribedFoldersOnly(): Boolean = isSubscribedFoldersOnly
|
||||
override fun clientIdAppName(): String = "irrelevant"
|
||||
override fun clientId(): ImapClientId = ImapClientId(appName = "irrelevant", appVersion = "irrelevant")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@ internal class SimpleImapSettings(
|
|||
override val username: String,
|
||||
override val password: String? = null,
|
||||
override val useCompression: Boolean = false,
|
||||
override val clientIdAppName: String? = null,
|
||||
override val clientId: ImapClientId? = null,
|
||||
) : ImapSettings {
|
||||
override val clientCertificateAlias: String? = null
|
||||
|
||||
|
|
Loading…
Reference in a new issue