Matt Russell
Matt Russell

Reputation: 325

Can you limit the size of data that can be deserialized in Ktor?

In Ktor, is there a way to limit size of data that can be attempted to be deserialized from JSON? Context is defending against denial-of-service attacks where a malicious client might try and send a huge payload to cause out-of-memory issues

I've used a similar capability in Play before (https://www.playframework.com/documentation/2.8.x/ScalaBodyParsers#Max-content-length). You can set the maximum globally, and also specifically override on individual routes.

Upvotes: 0

Views: 714

Answers (1)

Aleksei Tirman
Aleksei Tirman

Reputation: 7109

Unfortunately, there is no built-in functionality in Ktor to limit the size of a body for deserialization. Still, you can write an interceptor for the ApplicationReceivePipeline pipeline to replace the ByteReadChannel (body bytes are read from this object) with another channel. The latter's implementation will count a total number of bytes read and throw an exception if that count exceeds some limit. Here is an example:

import io.ktor.application.*
import io.ktor.features.*
import io.ktor.request.*
import io.ktor.routing.*
import io.ktor.serialization.*
import io.ktor.server.engine.*
import io.ktor.server.netty.*
import io.ktor.util.pipeline.*
import io.ktor.utils.io.*
import kotlinx.coroutines.GlobalScope

fun main() {
    embeddedServer(Netty, port = 2222) {
        val beforeSerialization = PipelinePhase("")
        receivePipeline.insertPhaseBefore(ApplicationReceivePipeline.Transform, beforeSerialization)

        receivePipeline.intercept(beforeSerialization) { receive ->
            if (subject.value is ByteReadChannel) {
                val readChannel = subject.value as ByteReadChannel
                val limit = 1024
                var totalBytes = 0
                val channel = GlobalScope.writer {
                    val byteArray = ByteArray(4088)
                    val bytesRead = readChannel.readAvailable(byteArray)
                    totalBytes += bytesRead

                    if (totalBytes > limit) {
                        throw IllegalStateException("Limit ($limit) for receiving exceeded. Read $totalBytes.")
                    }

                    channel.writeFully(byteArray, 0, bytesRead)
                }.channel

                proceedWith(ApplicationReceiveRequest(receive.typeInfo, channel, reusableValue = true))
            }
        }

        install(ContentNegotiation) {
            json()
        }
        routing {
            post("/") {
                val r = call.receive<MyData>()
                println(r)
            }
        }
    }.start(wait = true)
}

@kotlinx.serialization.Serializable
data class MyData(val x: String)

Upvotes: 1

Related Questions