-
Notifications
You must be signed in to change notification settings - Fork 1.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
KTOR-7470 receiveMultipart throw UnsupportedMediaTypeException #4339
base: main
Are you sure you want to change the base?
Changes from 3 commits
e8d2d61
58d1714
5735df8
3eba61e
51d9c34
28c4a2e
f64059a
2831967
fc2a59e
0ccca30
4e022e4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change | ||||
---|---|---|---|---|---|---|
|
@@ -8,6 +8,7 @@ import io.ktor.http.* | |||||
import io.ktor.http.cio.* | ||||||
import io.ktor.http.content.* | ||||||
import io.ktor.server.application.* | ||||||
import io.ktor.server.plugins.UnsupportedMediaTypeException | ||||||
import io.ktor.server.request.* | ||||||
import io.ktor.util.pipeline.* | ||||||
import io.ktor.utils.io.* | ||||||
|
@@ -17,6 +18,7 @@ import io.ktor.utils.io.streams.* | |||||
import kotlinx.coroutines.* | ||||||
import kotlinx.io.* | ||||||
import java.io.* | ||||||
import java.io.IOException | ||||||
|
||||||
internal actual suspend fun PipelineContext<Any, PipelineCall>.defaultPlatformTransformations( | ||||||
query: Any | ||||||
|
@@ -33,16 +35,21 @@ internal actual suspend fun PipelineContext<Any, PipelineCall>.defaultPlatformTr | |||||
@OptIn(InternalAPI::class) | ||||||
internal actual fun PipelineContext<*, PipelineCall>.multiPartData(rc: ByteReadChannel): MultiPartData { | ||||||
val contentType = call.request.header(HttpHeaders.ContentType) | ||||||
?: throw IllegalStateException("Content-Type header is required for multipart processing") | ||||||
?: throw UnsupportedMediaTypeException(null) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There is also one more place, where exception is thrown - CIO:
2)
Can we please fix it too? |
||||||
|
||||||
val contentLength = call.request.header(HttpHeaders.ContentLength)?.toLong() | ||||||
return CIOMultipartDataBase( | ||||||
coroutineContext + Dispatchers.Unconfined, | ||||||
rc, | ||||||
contentType, | ||||||
contentLength, | ||||||
call.formFieldLimit | ||||||
) | ||||||
|
||||||
try { | ||||||
return CIOMultipartDataBase( | ||||||
coroutineContext + Dispatchers.Unconfined, | ||||||
rc, | ||||||
contentType, | ||||||
contentLength, | ||||||
call.formFieldLimit | ||||||
) | ||||||
} catch (_: IOException) { | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why do we need try-catch here? It's a constructor method There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. We do not have an access to UnsupportedMediaTypeException in CIO module I had to catch an exception on the server and rethrow it here, it is the latest server code before CIO module This should answer this question There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Thanks. Can we add a check that the exception message contains "Content-Type " because another IOException also thrown There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've added this check, but maybe there could be a way to add Not sure if it is a common practice to define catch logic based on exception's message There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately, adding |
||||||
throw UnsupportedMediaTypeException(ContentType.parse(contentType)) | ||||||
} | ||||||
} | ||||||
|
||||||
internal actual fun Source.readTextWithCustomCharset(charset: Charset): String = | ||||||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,55 @@ | ||
/* | ||
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license. | ||
*/ | ||
|
||
package io.ktor.tests.server.engine | ||
|
||
import io.ktor.http.* | ||
import io.ktor.server.application.* | ||
import io.ktor.server.engine.multiPartData | ||
import io.ktor.server.plugins.UnsupportedMediaTypeException | ||
import io.ktor.server.request.* | ||
import io.ktor.util.pipeline.* | ||
import io.ktor.utils.io.* | ||
import io.mockk.* | ||
import kotlinx.coroutines.* | ||
import kotlinx.coroutines.test.TestScope | ||
import kotlinx.coroutines.test.runTest | ||
import kotlin.test.* | ||
|
||
class MultiPartDataTest { | ||
private val mockContext = mockk<PipelineContext<*, PipelineCall>>(relaxed = true) | ||
private val mockRequest = mockk<PipelineRequest>(relaxed = true) | ||
private val testScope = TestScope() | ||
|
||
@Test | ||
fun givenRequest_whenNoContentTypeHeaderPresent_thenUnsupportedMediaTypeException() { | ||
// Setup | ||
every { mockContext.call.request } returns mockRequest | ||
every { mockRequest.header(HttpHeaders.ContentType) } returns null | ||
|
||
// Act & Assert | ||
assertFailsWith<UnsupportedMediaTypeException> { | ||
runBlocking { mockContext.multiPartData(ByteReadChannel("sample data")) } | ||
} | ||
} | ||
|
||
@Test | ||
fun givenWrongContentType_whenProcessMultiPart_thenUnsupportedMediaTypeException() { | ||
// Given | ||
val rc = ByteReadChannel("sample data") | ||
val contentType = "test/plain; boundary=test" | ||
val contentLength = "123" | ||
every { mockContext.call.request } returns mockRequest | ||
every { mockContext.call.attributes.getOrNull<Long>(any()) } returns 0L | ||
every { mockRequest.header(HttpHeaders.ContentType) } returns contentType | ||
every { mockRequest.header(HttpHeaders.ContentLength) } returns contentLength | ||
|
||
// When & Then | ||
testScope.runTest { | ||
assertFailsWith<UnsupportedMediaTypeException> { | ||
mockContext.multiPartData(rc) | ||
} | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Content-Type header may be required not only for multipart requests, we can remove this part