Skip to content

Commit

Permalink
Add contains operator to ContentType objects
Browse files Browse the repository at this point in the history
  • Loading branch information
osipxd committed Oct 29, 2024
1 parent 8c271cb commit ce84288
Show file tree
Hide file tree
Showing 7 changed files with 147 additions and 86 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,6 @@ public object JsonContentTypeMatcher : ContentTypeMatcher {
}

val value = contentType.withoutParameters().toString()
return value.startsWith("application/", ignoreCase = true) && value.endsWith("+json", ignoreCase = true)
return value in ContentType.Application && value.endsWith("+json", ignoreCase = true)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,6 @@ internal class JsonContentTypeMatcher : ContentTypeMatcher {
}

val value = contentType.withoutParameters().toString()
return value.startsWith("application/", ignoreCase = true) && value.endsWith("+json", ignoreCase = true)
return value in ContentType.Application && value.endsWith("+json", ignoreCase = true)
}
}
215 changes: 138 additions & 77 deletions ktor-http/common/src/io/ktor/http/ContentTypes.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
/*
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
* 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.http

Expand Down Expand Up @@ -154,133 +154,194 @@ public class ContentType private constructor(
*/
@Suppress("KDocMissingDocumentation", "unused")
public object Application {
public const val TYPE: String = "application"

/**
* Represents a pattern `application / *` to match any application content type.
*/
public val Any: ContentType = ContentType("application", "*")
public val Atom: ContentType = ContentType("application", "atom+xml")
public val Cbor: ContentType = ContentType("application", "cbor")
public val Json: ContentType = ContentType("application", "json")
public val HalJson: ContentType = ContentType("application", "hal+json")
public val JavaScript: ContentType = ContentType("application", "javascript")
public val OctetStream: ContentType = ContentType("application", "octet-stream")
public val Rss: ContentType = ContentType("application", "rss+xml")
public val Soap: ContentType = ContentType("application", "soap+xml")
public val Xml: ContentType = ContentType("application", "xml")
public val Xml_Dtd: ContentType = ContentType("application", "xml-dtd")
public val Zip: ContentType = ContentType("application", "zip")
public val GZip: ContentType = ContentType("application", "gzip")

public val FormUrlEncoded: ContentType =
ContentType("application", "x-www-form-urlencoded")

public val Pdf: ContentType = ContentType("application", "pdf")
public val Xlsx: ContentType = ContentType(
"application",
"vnd.openxmlformats-officedocument.spreadsheetml.sheet"
)
public val Docx: ContentType = ContentType(
"application",
"vnd.openxmlformats-officedocument.wordprocessingml.document"
)
public val Pptx: ContentType = ContentType(
"application",
"vnd.openxmlformats-officedocument.presentationml.presentation"
)
public val ProtoBuf: ContentType = ContentType("application", "protobuf")
public val Wasm: ContentType = ContentType("application", "wasm")
public val ProblemJson: ContentType = ContentType("application", "problem+json")
public val ProblemXml: ContentType = ContentType("application", "problem+xml")
public val Any: ContentType = ContentType(TYPE, "*")
public val Atom: ContentType = ContentType(TYPE, "atom+xml")
public val Cbor: ContentType = ContentType(TYPE, "cbor")
public val Json: ContentType = ContentType(TYPE, "json")
public val HalJson: ContentType = ContentType(TYPE, "hal+json")
public val JavaScript: ContentType = ContentType(TYPE, "javascript")
public val OctetStream: ContentType = ContentType(TYPE, "octet-stream")
public val Rss: ContentType = ContentType(TYPE, "rss+xml")
public val Soap: ContentType = ContentType(TYPE, "soap+xml")
public val Xml: ContentType = ContentType(TYPE, "xml")
public val Xml_Dtd: ContentType = ContentType(TYPE, "xml-dtd")
public val Zip: ContentType = ContentType(TYPE, "zip")
public val GZip: ContentType = ContentType(TYPE, "gzip")
public val FormUrlEncoded: ContentType = ContentType(TYPE, "x-www-form-urlencoded")
public val Pdf: ContentType = ContentType(TYPE, "pdf")
public val Xlsx: ContentType = ContentType(TYPE, "vnd.openxmlformats-officedocument.spreadsheetml.sheet")
public val Docx: ContentType = ContentType(TYPE, "vnd.openxmlformats-officedocument.wordprocessingml.document")
public val Pptx: ContentType =
ContentType(TYPE, "vnd.openxmlformats-officedocument.presentationml.presentation")
public val ProtoBuf: ContentType = ContentType(TYPE, "protobuf")
public val Wasm: ContentType = ContentType(TYPE, "wasm")
public val ProblemJson: ContentType = ContentType(TYPE, "problem+json")
public val ProblemXml: ContentType = ContentType(TYPE, "problem+xml")

/** Checks that the given [contentType] has type `application/`. */
public operator fun contains(contentType: CharSequence): Boolean =
contentType.startsWith("$TYPE/", ignoreCase = true)

/** Checks that the given [contentType] has type `application/`. */
public operator fun contains(contentType: ContentType): Boolean = contentType.match(Any)
}

/**
* Provides a list of standard subtypes of an `audio` content type.
*/
@Suppress("KDocMissingDocumentation", "unused")
public object Audio {
public val Any: ContentType = ContentType("audio", "*")
public val MP4: ContentType = ContentType("audio", "mp4")
public val MPEG: ContentType = ContentType("audio", "mpeg")
public val OGG: ContentType = ContentType("audio", "ogg")
public const val TYPE: String = "audio"

public val Any: ContentType = ContentType(TYPE, "*")
public val MP4: ContentType = ContentType(TYPE, "mp4")
public val MPEG: ContentType = ContentType(TYPE, "mpeg")
public val OGG: ContentType = ContentType(TYPE, "ogg")

/** Checks that the given [contentType] has type `audio/`. */
public operator fun contains(contentType: CharSequence): Boolean =
contentType.startsWith("$TYPE/", ignoreCase = true)

/** Checks that the given [contentType] has type `audio/`. */
public operator fun contains(contentType: ContentType): Boolean = contentType.match(Any)
}

/**
* Provides a list of standard subtypes of an `image` content type.
*/
@Suppress("KDocMissingDocumentation", "unused")
public object Image {
public val Any: ContentType = ContentType("image", "*")
public val GIF: ContentType = ContentType("image", "gif")
public val JPEG: ContentType = ContentType("image", "jpeg")
public val PNG: ContentType = ContentType("image", "png")
public val SVG: ContentType = ContentType("image", "svg+xml")
public val XIcon: ContentType = ContentType("image", "x-icon")
public const val TYPE: String = "image"

public val Any: ContentType = ContentType(TYPE, "*")
public val GIF: ContentType = ContentType(TYPE, "gif")
public val JPEG: ContentType = ContentType(TYPE, "jpeg")
public val PNG: ContentType = ContentType(TYPE, "png")
public val SVG: ContentType = ContentType(TYPE, "svg+xml")
public val XIcon: ContentType = ContentType(TYPE, "x-icon")

/** Checks that the given [contentType] has type `image/`. */
public operator fun contains(contentSubtype: String): Boolean =
contentSubtype.startsWith("$TYPE/", ignoreCase = true)

/** Checks that the given [contentType] has type `image/`. */
public operator fun contains(contentType: ContentType): Boolean = contentType.match(Any)
}

/**
* Provides a list of standard subtypes of a `message` content type.
*/
@Suppress("KDocMissingDocumentation", "unused")
public object Message {
public val Any: ContentType = ContentType("message", "*")
public val Http: ContentType = ContentType("message", "http")
public const val TYPE: String = "message"

public val Any: ContentType = ContentType(TYPE, "*")
public val Http: ContentType = ContentType(TYPE, "http")

/** Checks that the given [contentType] has type `message/`. */
public operator fun contains(contentSubtype: String): Boolean =
contentSubtype.startsWith("$TYPE/", ignoreCase = true)

/** Checks that the given [contentType] has type `message/`. */
public operator fun contains(contentType: ContentType): Boolean = contentType.match(Any)
}

/**
* Provides a list of standard subtypes of a `multipart` content type.
*/
@Suppress("KDocMissingDocumentation", "unused")
public object MultiPart {
public val Any: ContentType = ContentType("multipart", "*")
public val Mixed: ContentType = ContentType("multipart", "mixed")
public val Alternative: ContentType = ContentType("multipart", "alternative")
public val Related: ContentType = ContentType("multipart", "related")
public val FormData: ContentType = ContentType("multipart", "form-data")
public val Signed: ContentType = ContentType("multipart", "signed")
public val Encrypted: ContentType = ContentType("multipart", "encrypted")
public val ByteRanges: ContentType = ContentType("multipart", "byteranges")
public const val TYPE: String = "multipart"

public val Any: ContentType = ContentType(TYPE, "*")
public val Mixed: ContentType = ContentType(TYPE, "mixed")
public val Alternative: ContentType = ContentType(TYPE, "alternative")
public val Related: ContentType = ContentType(TYPE, "related")
public val FormData: ContentType = ContentType(TYPE, "form-data")
public val Signed: ContentType = ContentType(TYPE, "signed")
public val Encrypted: ContentType = ContentType(TYPE, "encrypted")
public val ByteRanges: ContentType = ContentType(TYPE, "byteranges")

/** Checks that the given [contentType] has type `multipart/`. */
public operator fun contains(contentType: CharSequence): Boolean =
contentType.startsWith("$TYPE/", ignoreCase = true)

/** Checks that the given [contentType] has type `multipart/`. */
public operator fun contains(contentType: ContentType): Boolean = contentType.match(Any)
}

/**
* Provides a list of standard subtypes of a `text` content type.
*/
@Suppress("KDocMissingDocumentation", "unused")
public object Text {
public val Any: ContentType = ContentType("text", "*")
public val Plain: ContentType = ContentType("text", "plain")
public val CSS: ContentType = ContentType("text", "css")
public val CSV: ContentType = ContentType("text", "csv")
public val Html: ContentType = ContentType("text", "html")
public val JavaScript: ContentType = ContentType("text", "javascript")
public val VCard: ContentType = ContentType("text", "vcard")
public val Xml: ContentType = ContentType("text", "xml")
public val EventStream: ContentType = ContentType("text", "event-stream")
public const val TYPE: String = "text"

public val Any: ContentType = ContentType(TYPE, "*")
public val Plain: ContentType = ContentType(TYPE, "plain")
public val CSS: ContentType = ContentType(TYPE, "css")
public val CSV: ContentType = ContentType(TYPE, "csv")
public val Html: ContentType = ContentType(TYPE, "html")
public val JavaScript: ContentType = ContentType(TYPE, "javascript")
public val VCard: ContentType = ContentType(TYPE, "vcard")
public val Xml: ContentType = ContentType(TYPE, "xml")
public val EventStream: ContentType = ContentType(TYPE, "event-stream")

/** Checks that the given [contentType] has type `text/`. */
public operator fun contains(contentType: CharSequence): Boolean =
contentType.startsWith("$TYPE/", ignoreCase = true)

/** Checks that the given [contentType] has type `text/`. */
public operator fun contains(contentType: ContentType): Boolean = contentType.match(Any)
}

/**
* Provides a list of standard subtypes of a `video` content type.
*/
@Suppress("KDocMissingDocumentation", "unused")
public object Video {
public val Any: ContentType = ContentType("video", "*")
public val MPEG: ContentType = ContentType("video", "mpeg")
public val MP4: ContentType = ContentType("video", "mp4")
public val OGG: ContentType = ContentType("video", "ogg")
public val QuickTime: ContentType = ContentType("video", "quicktime")
public const val TYPE: String = "video"

public val Any: ContentType = ContentType(TYPE, "*")
public val MPEG: ContentType = ContentType(TYPE, "mpeg")
public val MP4: ContentType = ContentType(TYPE, "mp4")
public val OGG: ContentType = ContentType(TYPE, "ogg")
public val QuickTime: ContentType = ContentType(TYPE, "quicktime")

/** Checks that the given [contentType] has type `video/`. */
public operator fun contains(contentType: CharSequence): Boolean =
contentType.startsWith("$TYPE/", ignoreCase = true)

/** Checks that the given [contentType] has type `video/`. */
public operator fun contains(contentType: ContentType): Boolean = contentType.match(Any)
}

/**
* Provides a list of standard subtypes of a `font` content type.
*/
@Suppress("KDocMissingDocumentation", "unused")
public object Font {
public val Any: ContentType = ContentType("font", "*")
public val Collection: ContentType = ContentType("font", "collection")
public val Otf: ContentType = ContentType("font", "otf")
public val Sfnt: ContentType = ContentType("font", "sfnt")
public val Ttf: ContentType = ContentType("font", "ttf")
public val Woff: ContentType = ContentType("font", "woff")
public val Woff2: ContentType = ContentType("font", "woff2")
public const val TYPE: String = "font"

public val Any: ContentType = ContentType(TYPE, "*")
public val Collection: ContentType = ContentType(TYPE, "collection")
public val Otf: ContentType = ContentType(TYPE, "otf")
public val Sfnt: ContentType = ContentType(TYPE, "sfnt")
public val Ttf: ContentType = ContentType(TYPE, "ttf")
public val Woff: ContentType = ContentType(TYPE, "woff")
public val Woff2: ContentType = ContentType(TYPE, "woff2")

/** Checks that the given [contentType] has type `font/`. */
public operator fun contains(contentType: CharSequence): Boolean =
contentType.startsWith("$TYPE/", ignoreCase = true)

/** Checks that the given [contentType] has type `font/`. */
public operator fun contains(contentType: ContentType): Boolean = contentType.match(Any)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

package io.ktor.http.cio

import io.ktor.http.*
import io.ktor.http.cio.internals.*
import io.ktor.utils.io.*
import io.ktor.utils.io.ByteString
Expand Down Expand Up @@ -153,7 +154,7 @@ public fun CoroutineScope.parseMultipart(
contentLength: Long?,
maxPartSize: Long = Long.MAX_VALUE,
): ReceiveChannel<MultipartEvent> {
if (!contentType.startsWith("multipart/", ignoreCase = true)) {
if (contentType !in ContentType.MultiPart) {
throw IOException("Failed to parse multipart: Content-Type should be multipart/* but it is $contentType")
}
val boundaryByteBuffer = parseBoundaryInternal(contentType)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
/*
* Copyright 2014-2021 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/
* Copyright 2014-2024 JetBrains s.r.o and contributors. Use of this source code is governed by the Apache 2.0 license.
*/

@file:Suppress("unused")

package io.ktor.server.request

import io.ktor.http.*
import io.ktor.server.application.*
import io.ktor.server.plugins.*
import io.ktor.utils.io.charsets.*

Expand Down Expand Up @@ -111,7 +110,7 @@ public fun ApplicationRequest.isChunked(): Boolean =
/**
* Checks whether a request body is multipart-encoded.
*/
public fun ApplicationRequest.isMultipart(): Boolean = contentType().match(ContentType.MultiPart.Any)
public fun ApplicationRequest.isMultipart(): Boolean = contentType() in ContentType.MultiPart

/**
* Gets a request's `User-Agent` header value.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ internal class JettyKtorHandler(
) {
try {
val contentType = request.contentType
if (contentType != null && contentType.startsWith("multipart/", ignoreCase = true)) {
if (contentType != null && contentType in ContentType.MultiPart) {
baseRequest.setAttribute(Request.__MULTIPART_CONFIG_ELEMENT, multipartConfig)
// TODO someone reported auto-cleanup issues so we have to check it
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ internal class JettyKtorHandler(
) {
try {
val contentType = request.contentType
if (contentType != null && contentType.startsWith("multipart/", ignoreCase = true)) {
if (contentType != null && contentType in ContentType.MultiPart) {
baseRequest.setAttribute(Request.MULTIPART_CONFIG_ELEMENT, multipartConfig)
// TODO someone reported auto-cleanup issues so we have to check it
}
Expand Down

0 comments on commit ce84288

Please sign in to comment.