Skip to content

Commit

Permalink
Merge pull request #8140 from thunderbird/qr_code_parser
Browse files Browse the repository at this point in the history
Add QR code payload reader
  • Loading branch information
cketti authored Sep 17, 2024
2 parents 41c06ce + b57719e commit 4d91bea
Show file tree
Hide file tree
Showing 13 changed files with 1,697 additions and 0 deletions.
14 changes: 14 additions & 0 deletions feature/migration/qrcode/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
plugins {
id(ThunderbirdPlugins.Library.android)
}

android {
namespace = "app.k9mail.feature.migration.qrcode"
resourcePrefix = "migration_qrcode_"
}

dependencies {
implementation(projects.core.common)
implementation(libs.moshi)
implementation(libs.timber)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
package app.k9mail.feature.migration.qrcode

import app.k9mail.core.common.mail.EmailAddress
import app.k9mail.core.common.net.Hostname
import app.k9mail.core.common.net.Port

internal data class AccountData(
val sequenceNumber: Int,
val sequenceEnd: Int,
val accounts: List<Account>,
) {
data class Account(
val accountName: String,
val incomingServer: IncomingServer,
val outgoingServerGroups: List<OutgoingServerGroup>,
)

data class IncomingServer(
val protocol: IncomingServerProtocol,
val hostname: Hostname,
val port: Port,
val connectionSecurity: ConnectionSecurity,
val authenticationType: AuthenticationType,
val username: String,
val password: String?,
)

data class OutgoingServer(
val protocol: OutgoingServerProtocol,
val hostname: Hostname,
val port: Port,
val connectionSecurity: ConnectionSecurity,
val authenticationType: AuthenticationType,
val username: String,
val password: String?,
)

data class OutgoingServerGroup(
val outgoingServer: OutgoingServer,
val identities: List<Identity>,
)

data class Identity(
val emailAddress: EmailAddress,
val displayName: String,
)

@Suppress("MagicNumber")
enum class IncomingServerProtocol(val intValue: Int) {
Imap(0),
Pop3(1),
;

companion object {
fun fromInt(value: Int): IncomingServerProtocol {
return requireNotNull(entries.find { it.intValue == value }) { "Unsupported value: $value" }
}
}
}

@Suppress("MagicNumber")
enum class OutgoingServerProtocol(val intValue: Int) {
Smtp(0),
;

companion object {
fun fromInt(value: Int): OutgoingServerProtocol {
return requireNotNull(entries.find { it.intValue == value }) { "Unsupported value: $value" }
}
}
}

@Suppress("MagicNumber")
enum class ConnectionSecurity(val intValue: Int) {
Plain(0),
TryStartTls(1),
AlwaysStartTls(2),
Tls(3),
;

companion object {
fun fromInt(value: Int): ConnectionSecurity {
return requireNotNull(entries.find { it.intValue == value }) { "Unsupported value: $value" }
}
}
}

@Suppress("MagicNumber")
enum class AuthenticationType(val intValue: Int) {
None(0),
PasswordCleartext(1),
PasswordEncrypted(2),
Gssapi(3),
Ntlm(4),
TlsCertificate(5),
OAuth2(6),
;

companion object {
fun fromInt(value: Int): AuthenticationType {
return requireNotNull(entries.find { it.intValue == value }) { "Unsupported value: $value" }
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package app.k9mail.feature.migration.qrcode

internal data class QrCodeData(
val version: Int,
val misc: Misc,
val accounts: List<Account>,
) {
data class Misc(
val sequenceNumber: Int,
val sequenceEnd: Int,
)

data class Account(
val incomingServer: IncomingServer,
val outgoingServers: List<OutgoingServer>,
)

data class IncomingServer(
val protocol: Int,
val hostname: String,
val port: Int,
val connectionSecurity: Int,
val authenticationType: Int,
val username: String,
val accountName: String?,
val password: String?,
)

data class OutgoingServer(
val protocol: Int,
val hostname: String,
val port: Int,
val connectionSecurity: Int,
val authenticationType: Int,
val username: String,
val password: String?,
val identities: List<Identity>,
)

data class Identity(
val emailAddress: String,
val displayName: String,
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
package app.k9mail.feature.migration.qrcode

import com.squareup.moshi.JsonAdapter
import com.squareup.moshi.JsonReader
import com.squareup.moshi.JsonWriter
import timber.log.Timber

internal class QrCodePayloadAdapter : JsonAdapter<QrCodeData>() {
override fun fromJson(jsonReader: JsonReader): QrCodeData? {
jsonReader.beginArray()

val version = jsonReader.nextInt()
if (version != 1) {
// We don't even attempt to read something that is newer than version 1.
Timber.d("Unsupported version: %s", version)
return null
}

val misc = readMiscellaneousData(jsonReader)

val accounts = buildList {
do {
add(readAccount(jsonReader))
} while (jsonReader.hasNext())
}

jsonReader.endArray()

return QrCodeData(version, misc, accounts)
}

private fun readMiscellaneousData(jsonReader: JsonReader): QrCodeData.Misc {
jsonReader.beginArray()

val sequenceNumber = jsonReader.nextInt()
val sequenceEnd = jsonReader.nextInt()

skipAdditionalArrayEntries(jsonReader)
jsonReader.endArray()

return QrCodeData.Misc(
sequenceNumber,
sequenceEnd,
)
}

private fun readAccount(jsonReader: JsonReader): QrCodeData.Account {
val incomingServer = readIncomingServer(jsonReader)
val outgoingServers = readOutgoingServers(jsonReader)

return QrCodeData.Account(incomingServer, outgoingServers)
}

private fun readIncomingServer(jsonReader: JsonReader): QrCodeData.IncomingServer {
jsonReader.beginArray()

val protocol = jsonReader.nextInt()
val hostname = jsonReader.nextString()
val port = jsonReader.nextInt()
val connectionSecurity = jsonReader.nextInt()
val authenticationType = jsonReader.nextInt()
val username = jsonReader.nextString()
val accountName = if (jsonReader.hasNext()) jsonReader.nextString() else null
val password = if (jsonReader.hasNext()) jsonReader.nextString() else null

skipAdditionalArrayEntries(jsonReader)
jsonReader.endArray()

return QrCodeData.IncomingServer(
protocol,
hostname,
port,
connectionSecurity,
authenticationType,
username,
accountName,
password,
)
}

private fun readOutgoingServers(jsonReader: JsonReader): List<QrCodeData.OutgoingServer> {
jsonReader.beginArray()

val outgoingServers = buildList {
do {
add(readOutgoingServer(jsonReader))
} while (jsonReader.hasNext())
}

jsonReader.endArray()

return outgoingServers
}

private fun readOutgoingServer(jsonReader: JsonReader): QrCodeData.OutgoingServer {
jsonReader.beginArray()

jsonReader.beginArray()

val protocol = jsonReader.nextInt()
val hostname = jsonReader.nextString()
val port = jsonReader.nextInt()
val connectionSecurity = jsonReader.nextInt()
val authenticationType = jsonReader.nextInt()
val username = jsonReader.nextString()
val password = if (jsonReader.hasNext()) jsonReader.nextString() else null

skipAdditionalArrayEntries(jsonReader)
jsonReader.endArray()

val identities = buildList {
do {
add(readIdentity(jsonReader))
} while (jsonReader.hasNext())
}

jsonReader.endArray()

return QrCodeData.OutgoingServer(
protocol,
hostname,
port,
connectionSecurity,
authenticationType,
username,
password,
identities,
)
}

private fun readIdentity(jsonReader: JsonReader): QrCodeData.Identity {
jsonReader.beginArray()

val emailAddress = jsonReader.nextString()
val displayName = jsonReader.nextString()

skipAdditionalArrayEntries(jsonReader)
jsonReader.endArray()

return QrCodeData.Identity(emailAddress, displayName)
}

private fun skipAdditionalArrayEntries(jsonReader: JsonReader) {
// For forward compatibility allow additional array elements.
while (jsonReader.hasNext()) {
jsonReader.readJsonValue()
}
}

override fun toJson(jsonWriter: JsonWriter, value: QrCodeData?) {
throw UnsupportedOperationException("not implemented")
}
}
Loading

0 comments on commit 4d91bea

Please sign in to comment.