diff options
author | nea <romangraef@gmail.com> | 2022-09-13 00:25:28 +0200 |
---|---|---|
committer | nea <romangraef@gmail.com> | 2022-09-13 00:25:28 +0200 |
commit | b7793a83b1f39ff94bfbaeef8ac4c387839a94de (patch) | |
tree | 2d89200e800de8230cf91aa4741d9958edd76d0a /src/main/kotlin/SMTPProtocol.kt | |
parent | 24b3430c42614bc2f9076a8a04d79720c05bb67b (diff) | |
download | javamailserver-master.tar.gz javamailserver-master.tar.bz2 javamailserver-master.zip |
Diffstat (limited to 'src/main/kotlin/SMTPProtocol.kt')
-rw-r--r-- | src/main/kotlin/SMTPProtocol.kt | 144 |
1 files changed, 7 insertions, 137 deletions
diff --git a/src/main/kotlin/SMTPProtocol.kt b/src/main/kotlin/SMTPProtocol.kt index 6f01bb2..e5da601 100644 --- a/src/main/kotlin/SMTPProtocol.kt +++ b/src/main/kotlin/SMTPProtocol.kt @@ -1,133 +1,8 @@ -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.launch -import java.io.InputStream -import java.io.OutputStream import java.net.InetAddress -import java.net.Socket import kotlin.contracts.ExperimentalContracts import kotlin.contracts.InvocationKind import kotlin.contracts.contract -class Invalidatable { - var isInvalid: Boolean = false - fun checkValid() { - if (isInvalid) - throw IllegalStateException("Accessed invalid object") - } - - fun invalidate() { - isInvalid = true - } -} - -abstract class Protocol { - interface IO { - fun isOpen(): Boolean - suspend fun pushBack(data: ByteArray) - suspend fun readBytes(into: ByteArray): Int - suspend fun send(bytes: ByteArray) - suspend fun close() - class FromSocket(val socket: Socket) : FromStreams(socket.getInputStream(), socket.getOutputStream()) { - override suspend fun close() { - super.close() - with(Dispatchers.IO) { - socket.close() - } - } - } - - open class FromStreams(val inputStream: InputStream, val outputStream: OutputStream) : IO { - private val i = Invalidatable() - override fun isOpen(): Boolean = - !i.isInvalid - - - val readBuffer = mutableListOf<ByteArray>() - override suspend fun pushBack(data: ByteArray) { - i.checkValid() - if (data.isEmpty()) return - readBuffer.add(0, data) - } - - override suspend fun send(bytes: ByteArray) { - i.checkValid() - with(Dispatchers.IO) { - outputStream.write(bytes) - outputStream.flush() - } - } - - override suspend fun close() { - i.checkValid() - i.invalidate() - with(Dispatchers.IO) { - inputStream.close() - outputStream.close() - } - } - - override suspend fun readBytes(into: ByteArray): Int { - i.checkValid() - val rb = readBuffer.removeFirstOrNull() - if (rb != null) { - val w = minOf(rb.size, into.size) - rb.copyInto(into, 0, 0, w) - return w - } - return with(Dispatchers.IO) { - inputStream.read(into) - } - } - } - } - - protected abstract suspend fun IO.execute() - - fun executeAsync(scope: CoroutineScope, io: Protocol.IO): Job { - return scope.launch { - io.execute() - } - } -} - -suspend fun Protocol.IO.send(string: String) = send(string.encodeToByteArray()) -suspend fun Protocol.IO.readLine(): String { - val y = mutableListOf<String>() - while (true) { - val buffer = ByteArray(4096) - val read = readBytes(buffer) - val i = buffer.findCRLF() - if (i in 0 until read) { - y.add(buffer.copyOfRange(0, i).decodeToString()) - pushBack(buffer.copyOfRange(i + 2, read)) - break - } else { - y.add(buffer.copyOfRange(0, read).decodeToString()) - } - } - return y.joinToString("") -} - -private fun ByteArray.findCRLF(): Int { - return this.asSequence().zipWithNext().withIndex().firstOrNull { (idx, v) -> - (v.first == '\r'.code.toByte()) and (v.second == '\n'.code.toByte()) - }?.index ?: -1 -} - -suspend fun Protocol.IO.pushBack(string: String) = pushBack(string.encodeToByteArray()) -suspend fun Protocol.IO.lookahead(string: String): Boolean = lookahead(string.encodeToByteArray()) -suspend fun Protocol.IO.lookahead(bytes: ByteArray): Boolean { - val buffer = ByteArray(bytes.size) - val read = readBytes(buffer) - if (read != bytes.size || !buffer.contentEquals(bytes)) { - pushBack(buffer.copyOf(read)) - return false - } - return true -} - @OptIn(ExperimentalContracts::class) class SMTPReceiveProtocol(val localHost: String, val inetAddress: InetAddress) : Protocol() { @@ -136,20 +11,20 @@ class SMTPReceiveProtocol(val localHost: String, val inetAddress: InetAddress) : var matched = false - suspend inline fun command(vararg name: String, block: IO.(String) -> Unit) { + suspend inline fun command(vararg name: String, block: suspend IO.(String) -> Unit) { contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) } for (n in name) commandOnce(n, block) } - suspend inline fun commandOnce(name: String, block: IO.(String) -> Unit) { + suspend inline fun commandOnce(name: String, block: suspend IO.(String) -> Unit) { contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) } if (matched) return - if (!line.startsWith(name)) return + if (!line.startsWith(name, ignoreCase = true)) return matched = true block(line.substring(name.length).trimStart()) } - suspend inline fun otherwise(block: IO.(String) -> Unit) { + suspend inline fun otherwise(block: suspend IO.(String) -> Unit) { contract { callsInPlace(block, InvocationKind.AT_MOST_ONCE) } if (matched) return matched = true @@ -157,7 +32,7 @@ class SMTPReceiveProtocol(val localHost: String, val inetAddress: InetAddress) : } } - suspend inline fun IO.commands(line: String, block: Commands.() -> Unit) { + suspend inline fun IO.commands(line: String, block: suspend Commands.() -> Unit) { contract { callsInPlace(block, InvocationKind.EXACTLY_ONCE) } Commands(line, this).block() } @@ -202,13 +77,8 @@ class SMTPReceiveProtocol(val localHost: String, val inetAddress: InetAddress) : } command("DATA") { send("354 Enter mail, end with \".\" on a line by itself\r\n") - var text = "" - while (true) { - val tmp = readLine() - if (tmp == ".") break - text += tmp + "\n" - } - messages.add(Mail(trans.sender!!, trans.recipients.toList(), text)) + var text = readUntil("\r\n.\r\n".encodeToByteArray()) + messages.add(Mail(trans.sender!!, trans.recipients.toList(), text.decodeToString())) trans.reset() send("250 Message accepted for delivery\r\n") } |