summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAntoine A <>2024-02-12 16:18:36 +0100
committerAntoine A <>2024-02-12 16:22:54 +0100
commitb8a29f97b6b4c571f53b81b2aa2acb8d46415a17 (patch)
treec7571a7aaebbf9335b1da5d9ba6991daf95a89ea
parent9ad7d47e0bcff24c71429be2ac3e7a2a68c99247 (diff)
downloadlibeufin-b8a29f97b6b4c571f53b81b2aa2acb8d46415a17.tar.gz
libeufin-b8a29f97b6b4c571f53b81b2aa2acb8d46415a17.tar.bz2
libeufin-b8a29f97b6b4c571f53b81b2aa2acb8d46415a17.zip
Fix BodyLimitAndDecompression plugin
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/Error.kt5
-rw-r--r--bank/src/main/kotlin/tech/libeufin/bank/Main.kt55
-rw-r--r--bank/src/test/kotlin/SecurityTest.kt26
-rw-r--r--bank/src/test/kotlin/WireGatewayApiTest.kt2
-rw-r--r--common/src/main/kotlin/Client.kt19
5 files changed, 66 insertions, 41 deletions
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Error.kt b/bank/src/main/kotlin/tech/libeufin/bank/Error.kt
index eacf6a08..7fd4c3b5 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Error.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Error.kt
@@ -120,6 +120,11 @@ fun badRequest(
detail: String? = null
): LibeufinException = libeufinError(HttpStatusCode.BadRequest, hint, error, detail)
+fun unsupportedMediaType(
+ hint: String,
+ error: TalerErrorCode = TalerErrorCode.END,
+): LibeufinException = libeufinError(HttpStatusCode.UnsupportedMediaType, hint, error)
+
/* ----- Currency checks ----- */
diff --git a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
index f91d2cdd..f0d8ac89 100644
--- a/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
+++ b/bank/src/main/kotlin/tech/libeufin/bank/Main.kt
@@ -70,32 +70,41 @@ val bodyPlugin = createApplicationPlugin("BodyLimitAndDecompression") {
transformBody { body ->
val bytes = ByteArray(MAX_BODY_LENGTH.toInt() + 1)
var read = 0;
- if (call.request.headers[HttpHeaders.ContentEncoding] == "deflate") {
- // Decompress and check decompressed length
- val inflater = Inflater()
- while (!body.isClosedForRead) {
- body.read { buf ->
- inflater.setInput(buf)
- try {
- read += inflater.inflate(bytes, read, bytes.size - read)
- } catch (e: DataFormatException) {
- logger.error("Deflated request failed to inflate: ${e.message}")
- throw badRequest(
- "Could not inflate request",
- TalerErrorCode.GENERIC_COMPRESSION_INVALID
- )
+ when (val encoding = call.request.headers[HttpHeaders.ContentEncoding]) {
+ "deflate" -> {
+ // Decompress and check decompressed length
+ val inflater = Inflater()
+ while (!body.isClosedForRead) {
+ body.read { buf ->
+ inflater.setInput(buf)
+ try {
+ read += inflater.inflate(bytes, read, bytes.size - read)
+ } catch (e: DataFormatException) {
+ logger.error("Deflated request failed to inflate: ${e.message}")
+ throw badRequest(
+ "Could not inflate request",
+ TalerErrorCode.GENERIC_COMPRESSION_INVALID
+ )
+ }
}
+ if (read > MAX_BODY_LENGTH)
+ throw badRequest("Decompressed body is suspiciously big > $MAX_BODY_LENGTH B")
}
- if (read > MAX_BODY_LENGTH)
- throw badRequest("Decompressed body is suspiciously big")
- }
- } else {
- // Check body length
- while (!body.isClosedForRead) {
- read += body.readAvailable(bytes, read, bytes.size - read)
- if (read > MAX_BODY_LENGTH)
- throw badRequest("Body is suspiciously big")
}
+ null -> {
+ // Check body length
+ while (true) {
+ val new = body.readAvailable(bytes, read, bytes.size - read)
+ if (new == -1) break; // Channel is closed
+ read += new
+ if (read > MAX_BODY_LENGTH)
+ throw badRequest("Body is suspiciously big > $MAX_BODY_LENGTH B")
+ }
+ }
+ else -> throw unsupportedMediaType(
+ "Content encoding '$encoding' not supported, expected plain or deflate",
+ TalerErrorCode.GENERIC_COMPRESSION_INVALID
+ )
}
ByteReadChannel(bytes, 0, read)
}
diff --git a/bank/src/test/kotlin/SecurityTest.kt b/bank/src/test/kotlin/SecurityTest.kt
index b3c5f5ac..b5559983 100644
--- a/bank/src/test/kotlin/SecurityTest.kt
+++ b/bank/src/test/kotlin/SecurityTest.kt
@@ -20,14 +20,30 @@
import io.ktor.client.plugins.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
+import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.server.engine.*
import io.ktor.server.testing.*
import kotlin.test.*
import kotlinx.coroutines.*
+import kotlinx.serialization.json.*
import org.junit.Test
import tech.libeufin.bank.*
import tech.libeufin.common.*
+import tech.libeufin.common.*
+import java.io.ByteArrayOutputStream
+import java.util.zip.DeflaterOutputStream
+
+inline fun <reified B> HttpRequestBuilder.jsonDeflate(b: B) {
+ val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), b);
+ contentType(ContentType.Application.Json)
+ headers.set(HttpHeaders.ContentEncoding, "deflate")
+ val bos = ByteArrayOutputStream()
+ val ios = DeflaterOutputStream(bos)
+ ios.write(json.toByteArray())
+ ios.finish()
+ setBody(bos.toByteArray())
+}
class SecurityTest {
@Test
@@ -49,10 +65,16 @@ class SecurityTest {
// Check body too big even after compression
client.postA("/accounts/merchant/transactions") {
- json(valid_req, deflate = true) {
+ jsonDeflate(obj(valid_req) {
"payto_uri" to "$exchangePayto?message=payout${"A".repeat(4100)}"
- }
+ })
}.assertBadRequest()
+
+ // Check uknown encoding
+ client.postA("/accounts/merchant/transactions") {
+ headers.set(HttpHeaders.ContentEncoding, "unknown")
+ json(valid_req)
+ }.assertStatus(HttpStatusCode.UnsupportedMediaType, TalerErrorCode.GENERIC_COMPRESSION_INVALID)
}
}
diff --git a/bank/src/test/kotlin/WireGatewayApiTest.kt b/bank/src/test/kotlin/WireGatewayApiTest.kt
index ec20ec15..ba1f3360 100644
--- a/bank/src/test/kotlin/WireGatewayApiTest.kt
+++ b/bank/src/test/kotlin/WireGatewayApiTest.kt
@@ -220,7 +220,7 @@ class WireGatewayApiTest {
// Giving debt allowance and checking the OK case.
setMaxDebt("merchant", "KUDOS:1000")
client.postA("/accounts/exchange/taler-wire-gateway/admin/add-incoming") {
- json(valid_req, deflate = true)
+ json(valid_req)
}.assertOk()
// Trigger conflict due to reused reserve_pub
diff --git a/common/src/main/kotlin/Client.kt b/common/src/main/kotlin/Client.kt
index 785e3f32..608a11b4 100644
--- a/common/src/main/kotlin/Client.kt
+++ b/common/src/main/kotlin/Client.kt
@@ -24,7 +24,6 @@ import kotlinx.serialization.json.*
import io.ktor.client.request.*
import io.ktor.client.statement.*
import java.io.ByteArrayOutputStream
-import java.util.zip.DeflaterOutputStream
import kotlin.test.assertEquals
/* ----- Json DSL ----- */
@@ -46,27 +45,17 @@ class JsonBuilder(from: JsonObject) {
/* ----- Json body helper ----- */
-inline fun <reified B> HttpRequestBuilder.json(b: B, deflate: Boolean = false) {
+inline fun <reified B> HttpRequestBuilder.json(b: B) {
val json = Json.encodeToString(kotlinx.serialization.serializer<B>(), b);
contentType(ContentType.Application.Json)
- if (deflate) {
- headers.set("Content-Encoding", "deflate")
- val bos = ByteArrayOutputStream()
- val ios = DeflaterOutputStream(bos)
- ios.write(json.toByteArray())
- ios.finish()
- setBody(bos.toByteArray())
- } else {
- setBody(json)
- }
+ setBody(json)
}
inline fun HttpRequestBuilder.json(
- from: JsonObject = JsonObject(emptyMap()),
- deflate: Boolean = false,
+ from: JsonObject = JsonObject(emptyMap()),
builderAction: JsonBuilder.() -> Unit
) {
- json(obj(from, builderAction), deflate)
+ json(obj(from, builderAction))
}
inline suspend fun <reified B> HttpResponse.json(): B =