Skip to content

Commit

Permalink
KTOR-4849 Routing: Wrong content-type should result in 415 (#3161)
Browse files Browse the repository at this point in the history
  • Loading branch information
rsinukov committed Sep 26, 2022
1 parent d300441 commit 6369491
Show file tree
Hide file tree
Showing 3 changed files with 72 additions and 4 deletions.
Expand Up @@ -7,7 +7,6 @@ package io.ktor.server.routing
import io.ktor.http.*
import io.ktor.server.plugins.*
import io.ktor.server.request.*
import io.ktor.util.*

/**
* A result of a route evaluation against a call.
Expand Down Expand Up @@ -552,8 +551,8 @@ public data class HttpMethodRouteSelector(

/**
* Evaluates a route against a header in the request.
* @param name is a name of the header
* @param value is a value of the header
* @param name is the name of the header
* @param value is the value of the header
*/
public data class HttpHeaderRouteSelector(
val name: String,
Expand All @@ -572,6 +571,32 @@ public data class HttpHeaderRouteSelector(
override fun toString(): String = "(header:$name = $value)"
}

/**
* Evaluates a route against a `Content-Type` in the [HttpHeaders.ContentType] request header.
* @param contentType is an instance of [ContentType]
*/
internal data class ContentTypeHeaderRouteSelector(
val contentType: ContentType
) : RouteSelector() {

private val failedEvaluation = RouteSelectorEvaluation.Failure(
RouteSelectorEvaluation.qualityFailedParameter,
HttpStatusCode.UnsupportedMediaType
)

override fun evaluate(context: RoutingResolveContext, segmentIndex: Int): RouteSelectorEvaluation {
val headers = context.call.request.header(HttpHeaders.ContentType)
val parsedHeaders = parseAndSortContentTypeHeader(headers)

val header = parsedHeaders.firstOrNull { ContentType.parse(it.value).match(contentType) }
?: return failedEvaluation

return RouteSelectorEvaluation.Success(header.quality)
}

override fun toString(): String = "(contentType = $contentType)"
}

/**
* Evaluates a route against a `Content-Type` in the [HttpHeaders.Accept] request header.
* @param contentType is an instance of [ContentType]
Expand Down
Expand Up @@ -96,7 +96,8 @@ public fun Route.accept(contentType: ContentType, build: Route.() -> Unit): Rout
*/
@KtorDsl
public fun Route.contentType(contentType: ContentType, build: Route.() -> Unit): Route {
return header(HttpHeaders.ContentType, "${contentType.contentType}/${contentType.contentSubtype}", build)
val selector = ContentTypeHeaderRouteSelector(contentType)
return createChild(selector).apply(build)
}

/**
Expand Down
Expand Up @@ -510,6 +510,48 @@ class RoutingProcessingTest {
}
}

@Test
fun testContentTypeHeaderProcessing() = testApplication {
routing {
route("/") {
contentType(ContentType.Text.Plain) {
handle {
call.respond("OK")
}
}
contentType(ContentType.Application.Any) {
handle {
call.respondText("{\"status\": \"OK\"}", ContentType.Application.Json)
}
}
}
}

client.get("/") {
header(HttpHeaders.ContentType, "text/plain")
}.let {
assertEquals("OK", it.bodyAsText())
}

client.get("/") {
header(HttpHeaders.ContentType, "application/json")
}.let {
assertEquals("{\"status\": \"OK\"}", it.bodyAsText())
}

client.get("/") {
header(HttpHeaders.ContentType, "application/pdf")
}.let {
assertEquals("{\"status\": \"OK\"}", it.bodyAsText())
}

client.get("/") {
header(HttpHeaders.ContentType, "text/html")
}.let {
assertEquals(HttpStatusCode.UnsupportedMediaType, it.status)
}
}

@Test
fun testTransparentSelectorWithHandler() = withTestApplication {
application.routing {
Expand Down

0 comments on commit 6369491

Please sign in to comment.