Repo created
This commit is contained in:
parent
75dc487a7a
commit
39c29d175b
6317 changed files with 388324 additions and 2 deletions
18
mail/testing/build.gradle.kts
Normal file
18
mail/testing/build.gradle.kts
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
plugins {
|
||||
id(ThunderbirdPlugins.Library.jvm)
|
||||
alias(libs.plugins.android.lint)
|
||||
}
|
||||
|
||||
val testCoverageEnabled: Boolean by extra
|
||||
if (testCoverageEnabled) {
|
||||
apply(plugin = "jacoco")
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api(projects.mail.common)
|
||||
api(projects.core.common)
|
||||
|
||||
api(libs.okio)
|
||||
api(libs.junit)
|
||||
api(libs.assertk)
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
package com.fsck.k9.mail.testing
|
||||
|
||||
fun String.crlf() = replace("\n", "\r\n")
|
||||
|
||||
fun String.removeLineBreaks() = replace(Regex("""\r|\n"""), "")
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
package com.fsck.k9.mail.testing;
|
||||
|
||||
import com.fsck.k9.mail.filter.Base64;
|
||||
|
||||
|
||||
public class XOAuth2ChallengeParserTestData {
|
||||
public static final String STATUS_400_RESPONSE = Base64.encode(
|
||||
"{\"status\":\"400\",\"schemes\":\"bearer mac\",\"scope\":\"https://mail.google.com/\"}");
|
||||
public static final String STATUS_401_RESPONSE = Base64.encode(
|
||||
"{\"status\":\"401\",\"schemes\":\"bearer mac\",\"scope\":\"https://mail.google.com/\"}");
|
||||
public static final String MISSING_STATUS_RESPONSE = Base64.encode(
|
||||
"{\"schemes\":\"bearer mac\",\"scope\":\"https://mail.google.com/\"}");
|
||||
public static final String INVALID_RESPONSE = Base64.encode(
|
||||
"{\"status\":\"401\",\"schemes\":\"bearer mac\",\"scope\":\"https://mail.google.com/\"");
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
@file:Suppress("TooManyFunctions")
|
||||
|
||||
package com.fsck.k9.mail.testing.assertk
|
||||
|
||||
import assertk.Assert
|
||||
import assertk.assertions.prop
|
||||
import com.fsck.k9.mail.Body
|
||||
import com.fsck.k9.mail.BodyPart
|
||||
import com.fsck.k9.mail.Part
|
||||
import com.fsck.k9.mail.internet.MimeMultipart
|
||||
import com.fsck.k9.mail.internet.MimeParameterDecoder
|
||||
import com.fsck.k9.mail.internet.MimeUtility
|
||||
import com.fsck.k9.mail.internet.MimeValue
|
||||
import com.fsck.k9.mail.internet.RawDataBody
|
||||
import com.fsck.k9.mail.internet.TextBody
|
||||
|
||||
fun Assert<Part>.body() = prop(Part::getBody)
|
||||
|
||||
@JvmName("textBodyEncoding")
|
||||
fun Assert<TextBody>.contentTransferEncoding() = prop(TextBody::getEncoding)
|
||||
|
||||
@JvmName("rawDataBodyEncoding")
|
||||
fun Assert<RawDataBody>.contentTransferEncoding() = prop(RawDataBody::getEncoding)
|
||||
|
||||
fun Assert<Body>.asBytes() = transform { it.inputStream.readBytes() }
|
||||
|
||||
fun Assert<Body>.asText() = transform {
|
||||
String(MimeUtility.decodeBody(it).readBytes())
|
||||
}
|
||||
|
||||
fun Assert<MimeMultipart>.bodyParts() = transform { it.bodyParts }
|
||||
|
||||
fun Assert<MimeMultipart>.bodyPart(index: Int): Assert<BodyPart> = transform { it.getBodyPart(index) }
|
||||
|
||||
fun Assert<Part>.mimeType() = transform { it.mimeType }
|
||||
|
||||
fun Assert<Part>.contentType() = transform { MimeParameterDecoder.decode(it.contentType) }
|
||||
|
||||
fun Assert<MimeValue>.value() = transform { it.value }
|
||||
|
||||
fun Assert<MimeValue>.parameter(name: String): Assert<String?> = transform { it.parameters[name] }
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
package com.fsck.k9.mail.testing.message
|
||||
|
||||
import com.fsck.k9.mail.Message
|
||||
import com.fsck.k9.mail.Multipart
|
||||
import com.fsck.k9.mail.Part
|
||||
import com.fsck.k9.mail.internet.MimeBodyPart
|
||||
import com.fsck.k9.mail.internet.MimeMessage
|
||||
import com.fsck.k9.mail.internet.MimeMessageHelper
|
||||
import com.fsck.k9.mail.internet.MimeMultipart
|
||||
import com.fsck.k9.mail.internet.TextBody
|
||||
import com.fsck.k9.mailstore.BinaryMemoryBody
|
||||
|
||||
fun buildMessage(block: PartBuilder.() -> Unit): Message {
|
||||
return MimeMessage().also { message ->
|
||||
PartBuilder(message).block()
|
||||
}
|
||||
}
|
||||
|
||||
@DslMarker
|
||||
annotation class MessageBuilderMarker
|
||||
|
||||
@MessageBuilderMarker
|
||||
class PartBuilder(private val part: Part) {
|
||||
private var gotBodyBlock = false
|
||||
|
||||
fun header(name: String, value: String) {
|
||||
part.addHeader(name, value)
|
||||
}
|
||||
|
||||
fun textBody(text: String = "Hello World") {
|
||||
require(!gotBodyBlock) { "Only one body block allowed" }
|
||||
gotBodyBlock = true
|
||||
|
||||
val body = TextBody(text)
|
||||
MimeMessageHelper.setBody(part, body)
|
||||
}
|
||||
|
||||
fun dataBody(size: Int = 20 * 1024, encoding: String = "7bit") {
|
||||
require(!gotBodyBlock) { "Only one body block allowed" }
|
||||
gotBodyBlock = true
|
||||
|
||||
val body = BinaryMemoryBody(ByteArray(size) { 'A'.code.toByte() }, encoding)
|
||||
MimeMessageHelper.setBody(part, body)
|
||||
}
|
||||
|
||||
fun multipart(subType: String = "mixed", block: MultipartBuilder.() -> Unit) {
|
||||
require(!gotBodyBlock) { "Only one body block allowed" }
|
||||
gotBodyBlock = true
|
||||
|
||||
val multipart = MimeMultipart.newInstance()
|
||||
multipart.setSubType(subType)
|
||||
MultipartBuilder(multipart).block()
|
||||
MimeMessageHelper.setBody(part, multipart)
|
||||
}
|
||||
}
|
||||
|
||||
@MessageBuilderMarker
|
||||
class MultipartBuilder(private val multipart: Multipart) {
|
||||
fun bodyPart(mimeType: String? = null, block: PartBuilder.() -> Unit) {
|
||||
MimeBodyPart().let { bodyPart ->
|
||||
if (mimeType != null) {
|
||||
bodyPart.addHeader("Content-Type", mimeType)
|
||||
}
|
||||
multipart.addBodyPart(bodyPart)
|
||||
PartBuilder(bodyPart).block()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,77 @@
|
|||
package com.fsck.k9.mail.testing.message;
|
||||
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
|
||||
import com.fsck.k9.mail.Address;
|
||||
import net.thunderbird.core.common.exception.MessagingException;
|
||||
import com.fsck.k9.mail.internet.MimeMessage;
|
||||
import okio.BufferedSink;
|
||||
import okio.Okio;
|
||||
|
||||
|
||||
class TestMessage extends MimeMessage {
|
||||
private final long messageSize;
|
||||
private final Address[] from;
|
||||
private final Address[] to;
|
||||
private final boolean hasAttachments;
|
||||
|
||||
|
||||
TestMessage(TestMessageBuilder builder) {
|
||||
from = toAddressArray(builder.from);
|
||||
to = toAddressArray(builder.to);
|
||||
hasAttachments = builder.hasAttachments;
|
||||
messageSize = builder.messageSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address[] getFrom() {
|
||||
return from;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Address[] getRecipients(RecipientType type) {
|
||||
switch (type) {
|
||||
case TO:
|
||||
return to;
|
||||
case CC:
|
||||
case BCC:
|
||||
case X_ORIGINAL_TO:
|
||||
case DELIVERED_TO:
|
||||
case X_ENVELOPE_TO:
|
||||
return new Address[0];
|
||||
}
|
||||
|
||||
throw new AssertionError("Missing switch case: " + type);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasAttachments() {
|
||||
return hasAttachments;
|
||||
}
|
||||
|
||||
@Override
|
||||
public long calculateSize() {
|
||||
return messageSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void writeTo(OutputStream out) throws IOException, MessagingException {
|
||||
BufferedSink bufferedSink = Okio.buffer(Okio.sink(out));
|
||||
bufferedSink.writeUtf8("[message data]");
|
||||
bufferedSink.emit();
|
||||
}
|
||||
|
||||
private static Address[] toAddressArray(String[] emails) {
|
||||
return emails == null ? new Address[0] : stringArrayToAddressArray(emails);
|
||||
}
|
||||
|
||||
private static Address[] stringArrayToAddressArray(String[] emails) {
|
||||
Address addresses[] = new Address[emails.length];
|
||||
for (int i = 0; i < emails.length; i++) {
|
||||
addresses[i] = new Address(emails[i]);
|
||||
}
|
||||
return addresses;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.fsck.k9.mail.testing.message;
|
||||
|
||||
|
||||
import com.fsck.k9.mail.Message;
|
||||
|
||||
|
||||
public class TestMessageBuilder {
|
||||
String[] from;
|
||||
String[] to;
|
||||
boolean hasAttachments;
|
||||
long messageSize;
|
||||
|
||||
|
||||
public TestMessageBuilder from(String... email) {
|
||||
from = email;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestMessageBuilder to(String... email) {
|
||||
to = email;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestMessageBuilder setHasAttachments(boolean hasAttachments) {
|
||||
this.hasAttachments = hasAttachments;
|
||||
return this;
|
||||
}
|
||||
|
||||
public TestMessageBuilder messageSize(long messageSize) {
|
||||
this.messageSize = messageSize;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Message build() {
|
||||
return new TestMessage(this);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.fsck.k9.mail.testing.message;
|
||||
|
||||
|
||||
import com.fsck.k9.mail.Body;
|
||||
import com.fsck.k9.mail.BodyPart;
|
||||
import net.thunderbird.core.common.exception.MessagingException;
|
||||
import com.fsck.k9.mail.internet.MimeBodyPart;
|
||||
import com.fsck.k9.mail.internet.MimeHeader;
|
||||
import com.fsck.k9.mail.internet.MimeMessage;
|
||||
import com.fsck.k9.mail.internet.MimeMessageHelper;
|
||||
import com.fsck.k9.mail.internet.MimeMultipart;
|
||||
import com.fsck.k9.mail.internet.TextBody;
|
||||
|
||||
|
||||
public class TestMessageConstructionUtils {
|
||||
public static MimeMessage messageFromBody(String subject, BodyPart bodyPart) throws MessagingException {
|
||||
MimeMessage message = messageFromBody(bodyPart);
|
||||
message.setSubject(subject);
|
||||
return message;
|
||||
}
|
||||
|
||||
public static MimeMessage messageFromBody(BodyPart bodyPart) throws MessagingException {
|
||||
MimeMessage message = new MimeMessage();
|
||||
MimeMessageHelper.setBody(message, bodyPart.getBody());
|
||||
if (bodyPart.getContentType() != null) {
|
||||
message.setHeader("Content-Type", bodyPart.getContentType());
|
||||
}
|
||||
message.setUid("msguid");
|
||||
return message;
|
||||
}
|
||||
|
||||
public static MimeBodyPart multipart(String type, BodyPart... subParts) throws MessagingException {
|
||||
return multipart(type, null, subParts);
|
||||
}
|
||||
|
||||
public static MimeBodyPart multipart(String type, String typeParameters, BodyPart... subParts) throws MessagingException {
|
||||
MimeMultipart multiPart = MimeMultipart.newInstance();
|
||||
multiPart.setSubType(type);
|
||||
for (BodyPart subPart : subParts) {
|
||||
multiPart.addBodyPart(subPart);
|
||||
}
|
||||
MimeBodyPart mimeBodyPart = new MimeBodyPart(multiPart);
|
||||
if (typeParameters != null) {
|
||||
mimeBodyPart.setHeader(MimeHeader.HEADER_CONTENT_TYPE,
|
||||
mimeBodyPart.getContentType() + "; " + typeParameters);
|
||||
}
|
||||
return mimeBodyPart;
|
||||
}
|
||||
|
||||
public static BodyPart bodypart(String type) throws MessagingException {
|
||||
return new MimeBodyPart(null, type);
|
||||
}
|
||||
|
||||
public static MimeBodyPart bodypart(String type, String text) throws MessagingException {
|
||||
TextBody textBody = new TextBody(text);
|
||||
return new MimeBodyPart(textBody, type);
|
||||
}
|
||||
|
||||
public static BodyPart bodypart(String type, Body body) throws MessagingException {
|
||||
return new MimeBodyPart(body, type);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
package com.fsck.k9.mail.testing.security
|
||||
|
||||
import com.fsck.k9.mail.CertificateChainException
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
@Suppress("CustomX509TrustManager")
|
||||
class FakeTrustManager : X509TrustManager {
|
||||
var shouldThrowException = false
|
||||
|
||||
override fun checkClientTrusted(chain: Array<out X509Certificate>?, authType: String?) = Unit
|
||||
|
||||
override fun checkServerTrusted(chain: Array<out X509Certificate>?, authType: String?) {
|
||||
if (shouldThrowException) {
|
||||
throw CertificateChainException("Test", chain, Exception("cause"))
|
||||
}
|
||||
}
|
||||
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate> = emptyArray()
|
||||
}
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
package com.fsck.k9.mail.testing.security
|
||||
|
||||
import com.fsck.k9.mail.ClientCertificateError
|
||||
import com.fsck.k9.mail.ClientCertificateException
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
import java.net.Socket
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
class SimpleTrustedSocketFactory(private val trustManager: X509TrustManager) : TrustedSocketFactory {
|
||||
private var clientCertificateError: ClientCertificateError? = null
|
||||
|
||||
override fun createSocket(socket: Socket?, host: String, port: Int, clientCertificateAlias: String?): Socket {
|
||||
requireNotNull(socket)
|
||||
|
||||
@Suppress("ThrowingExceptionsWithoutMessageOrCause")
|
||||
when (val error = clientCertificateError) {
|
||||
ClientCertificateError.RetrievalFailure -> throw ClientCertificateException(error, RuntimeException())
|
||||
ClientCertificateError.CertificateExpired -> throw ClientCertificateException(error, RuntimeException())
|
||||
null -> Unit
|
||||
}
|
||||
|
||||
val trustManagers = arrayOf<TrustManager>(trustManager)
|
||||
|
||||
val sslContext = SSLContext.getInstance("TLS").apply {
|
||||
init(null, trustManagers, null)
|
||||
}
|
||||
|
||||
return sslContext.socketFactory.createSocket(
|
||||
socket,
|
||||
socket.inetAddress.hostAddress,
|
||||
socket.port,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
fun injectClientCertificateError(error: ClientCertificateError) {
|
||||
clientCertificateError = error
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,27 @@
|
|||
package com.fsck.k9.mail.testing.security
|
||||
|
||||
import java.security.KeyStore
|
||||
import java.security.cert.X509Certificate
|
||||
|
||||
object TestKeyStoreProvider {
|
||||
|
||||
private const val KEYSTORE_PASSWORD = "password"
|
||||
private const val KEYSTORE_RESOURCE = "/keystore.jks"
|
||||
private const val SERVER_CERTIFICATE_ALIAS = "mockimapserver"
|
||||
|
||||
val keyStore: KeyStore by lazy { loadKeyStore() }
|
||||
val password: CharArray by lazy { KEYSTORE_PASSWORD.toCharArray() }
|
||||
val serverCertificate: X509Certificate by lazy {
|
||||
keyStore.getCertificate(SERVER_CERTIFICATE_ALIAS) as X509Certificate
|
||||
}
|
||||
|
||||
private fun loadKeyStore(): KeyStore {
|
||||
val keyStore = KeyStore.getInstance("JKS")
|
||||
val keyStoreInputStream = TestKeyStoreProvider::class.java.getResourceAsStream(KEYSTORE_RESOURCE)
|
||||
keyStoreInputStream.use { inputStream ->
|
||||
keyStore.load(inputStream, KEYSTORE_PASSWORD.toCharArray())
|
||||
}
|
||||
|
||||
return keyStore
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package com.fsck.k9.mail.testing.security
|
||||
|
||||
import com.fsck.k9.mail.ssl.TrustedSocketFactory
|
||||
import java.io.IOException
|
||||
import java.net.Socket
|
||||
import java.security.KeyManagementException
|
||||
import java.security.NoSuchAlgorithmException
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.SSLContext
|
||||
import javax.net.ssl.TrustManager
|
||||
import net.thunderbird.core.common.exception.MessagingException
|
||||
|
||||
/**
|
||||
* A test trusted socket factory that creates sockets that trust only a predefined server certificate
|
||||
*/
|
||||
object TestTrustedSocketFactory : TrustedSocketFactory {
|
||||
|
||||
private val serverCertificate: X509Certificate by lazy {
|
||||
TestKeyStoreProvider.serverCertificate
|
||||
}
|
||||
|
||||
@Throws(
|
||||
NoSuchAlgorithmException::class,
|
||||
KeyManagementException::class,
|
||||
MessagingException::class,
|
||||
IOException::class,
|
||||
)
|
||||
override fun createSocket(
|
||||
socket: Socket?,
|
||||
host: String,
|
||||
port: Int,
|
||||
clientCertificateAlias: String?,
|
||||
): Socket {
|
||||
val trustManagers: Array<TrustManager> = arrayOf(VeryTrustingTrustManager(serverCertificate))
|
||||
|
||||
val sslContext = SSLContext.getInstance("TLS").apply {
|
||||
init(null, trustManagers, null)
|
||||
}
|
||||
|
||||
val sslSocketFactory = sslContext.socketFactory
|
||||
|
||||
return if (socket == null) {
|
||||
sslSocketFactory.createSocket(host, port)
|
||||
} else {
|
||||
sslSocketFactory.createSocket(
|
||||
socket,
|
||||
socket.inetAddress.hostAddress,
|
||||
socket.port,
|
||||
true,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
package com.fsck.k9.mail.testing.security
|
||||
|
||||
import java.security.cert.CertificateException
|
||||
import java.security.cert.X509Certificate
|
||||
import javax.net.ssl.X509TrustManager
|
||||
|
||||
/**
|
||||
* A very trusting trust manager that accepts all certificates. It is used in tests to accept all certificates.
|
||||
*
|
||||
* WARNING: This trust manager is very insecure and should never be used in production code!
|
||||
*
|
||||
* @param serverCertificate The server certificate to return as the accepted issuer.
|
||||
*/
|
||||
@Suppress("CustomX509TrustManager")
|
||||
internal class VeryTrustingTrustManager(private val serverCertificate: X509Certificate?) : X509TrustManager {
|
||||
|
||||
/**
|
||||
* Always trust the client certificate.
|
||||
*/
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkClientTrusted(chain: Array<X509Certificate?>?, authType: String?) = Unit
|
||||
|
||||
/**
|
||||
* Always trust the server certificate.
|
||||
*/
|
||||
@Throws(CertificateException::class)
|
||||
override fun checkServerTrusted(chain: Array<X509Certificate?>?, authType: String?) = Unit
|
||||
|
||||
override fun getAcceptedIssuers(): Array<X509Certificate?> {
|
||||
return arrayOf<X509Certificate?>(serverCertificate)
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue