From 887e902e6c8f4fd9e23caf39a5041f2e9a539cd4 Mon Sep 17 00:00:00 2001 From: Osip Fatkullin Date: Fri, 25 Oct 2024 17:01:26 +0200 Subject: [PATCH] Add contains operator to ContentType objects --- .../JsonContentTypeMatcher.kt | 2 +- .../plugins/json/JsonContentTypeMatcher.kt | 2 +- ktor-http/api/ktor-http.api | 24 ++ ktor-http/api/ktor-http.klib.api | 48 ++++ .../common/src/io/ktor/http/ContentTypes.kt | 215 +++++++++++------- .../jvm/src/io/ktor/http/cio/Multipart.kt | 3 +- .../request/ApplicationRequestProperties.kt | 7 +- .../server/jetty/jakarta/JettyKtorHandler.kt | 2 +- .../io/ktor/server/jetty/JettyKtorHandler.kt | 2 +- 9 files changed, 219 insertions(+), 86 deletions(-) diff --git a/ktor-client/ktor-client-plugins/ktor-client-content-negotiation/common/src/io/ktor/client/plugins/contentnegotiation/JsonContentTypeMatcher.kt b/ktor-client/ktor-client-plugins/ktor-client-content-negotiation/common/src/io/ktor/client/plugins/contentnegotiation/JsonContentTypeMatcher.kt index 0e0ec736efd..0af54142db4 100644 --- a/ktor-client/ktor-client-plugins/ktor-client-content-negotiation/common/src/io/ktor/client/plugins/contentnegotiation/JsonContentTypeMatcher.kt +++ b/ktor-client/ktor-client-plugins/ktor-client-content-negotiation/common/src/io/ktor/client/plugins/contentnegotiation/JsonContentTypeMatcher.kt @@ -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) } } diff --git a/ktor-client/ktor-client-plugins/ktor-client-json/common/src/io/ktor/client/plugins/json/JsonContentTypeMatcher.kt b/ktor-client/ktor-client-plugins/ktor-client-json/common/src/io/ktor/client/plugins/json/JsonContentTypeMatcher.kt index 5d660ad8693..05259cebb35 100644 --- a/ktor-client/ktor-client-plugins/ktor-client-json/common/src/io/ktor/client/plugins/json/JsonContentTypeMatcher.kt +++ b/ktor-client/ktor-client-plugins/ktor-client-json/common/src/io/ktor/client/plugins/json/JsonContentTypeMatcher.kt @@ -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) } } diff --git a/ktor-http/api/ktor-http.api b/ktor-http/api/ktor-http.api index f369eca9d6a..e0745ba6178 100644 --- a/ktor-http/api/ktor-http.api +++ b/ktor-http/api/ktor-http.api @@ -154,6 +154,9 @@ public final class io/ktor/http/ContentType : io/ktor/http/HeaderValueWithParame public final class io/ktor/http/ContentType$Application { public static final field INSTANCE Lio/ktor/http/ContentType$Application; + public static final field TYPE Ljava/lang/String; + public final fun contains (Lio/ktor/http/ContentType;)Z + public final fun contains (Ljava/lang/CharSequence;)Z public final fun getAny ()Lio/ktor/http/ContentType; public final fun getAtom ()Lio/ktor/http/ContentType; public final fun getCbor ()Lio/ktor/http/ContentType; @@ -180,6 +183,9 @@ public final class io/ktor/http/ContentType$Application { public final class io/ktor/http/ContentType$Audio { public static final field INSTANCE Lio/ktor/http/ContentType$Audio; + public static final field TYPE Ljava/lang/String; + public final fun contains (Lio/ktor/http/ContentType;)Z + public final fun contains (Ljava/lang/CharSequence;)Z public final fun getAny ()Lio/ktor/http/ContentType; public final fun getMP4 ()Lio/ktor/http/ContentType; public final fun getMPEG ()Lio/ktor/http/ContentType; @@ -193,6 +199,9 @@ public final class io/ktor/http/ContentType$Companion { public final class io/ktor/http/ContentType$Font { public static final field INSTANCE Lio/ktor/http/ContentType$Font; + public static final field TYPE Ljava/lang/String; + public final fun contains (Lio/ktor/http/ContentType;)Z + public final fun contains (Ljava/lang/CharSequence;)Z public final fun getAny ()Lio/ktor/http/ContentType; public final fun getCollection ()Lio/ktor/http/ContentType; public final fun getOtf ()Lio/ktor/http/ContentType; @@ -204,6 +213,9 @@ public final class io/ktor/http/ContentType$Font { public final class io/ktor/http/ContentType$Image { public static final field INSTANCE Lio/ktor/http/ContentType$Image; + public static final field TYPE Ljava/lang/String; + public final fun contains (Lio/ktor/http/ContentType;)Z + public final fun contains (Ljava/lang/String;)Z public final fun getAny ()Lio/ktor/http/ContentType; public final fun getGIF ()Lio/ktor/http/ContentType; public final fun getJPEG ()Lio/ktor/http/ContentType; @@ -214,12 +226,18 @@ public final class io/ktor/http/ContentType$Image { public final class io/ktor/http/ContentType$Message { public static final field INSTANCE Lio/ktor/http/ContentType$Message; + public static final field TYPE Ljava/lang/String; + public final fun contains (Lio/ktor/http/ContentType;)Z + public final fun contains (Ljava/lang/String;)Z public final fun getAny ()Lio/ktor/http/ContentType; public final fun getHttp ()Lio/ktor/http/ContentType; } public final class io/ktor/http/ContentType$MultiPart { public static final field INSTANCE Lio/ktor/http/ContentType$MultiPart; + public static final field TYPE Ljava/lang/String; + public final fun contains (Lio/ktor/http/ContentType;)Z + public final fun contains (Ljava/lang/CharSequence;)Z public final fun getAlternative ()Lio/ktor/http/ContentType; public final fun getAny ()Lio/ktor/http/ContentType; public final fun getByteRanges ()Lio/ktor/http/ContentType; @@ -232,6 +250,9 @@ public final class io/ktor/http/ContentType$MultiPart { public final class io/ktor/http/ContentType$Text { public static final field INSTANCE Lio/ktor/http/ContentType$Text; + public static final field TYPE Ljava/lang/String; + public final fun contains (Lio/ktor/http/ContentType;)Z + public final fun contains (Ljava/lang/CharSequence;)Z public final fun getAny ()Lio/ktor/http/ContentType; public final fun getCSS ()Lio/ktor/http/ContentType; public final fun getCSV ()Lio/ktor/http/ContentType; @@ -245,6 +266,9 @@ public final class io/ktor/http/ContentType$Text { public final class io/ktor/http/ContentType$Video { public static final field INSTANCE Lio/ktor/http/ContentType$Video; + public static final field TYPE Ljava/lang/String; + public final fun contains (Lio/ktor/http/ContentType;)Z + public final fun contains (Ljava/lang/CharSequence;)Z public final fun getAny ()Lio/ktor/http/ContentType; public final fun getMP4 ()Lio/ktor/http/ContentType; public final fun getMPEG ()Lio/ktor/http/ContentType; diff --git a/ktor-http/api/ktor-http.klib.api b/ktor-http/api/ktor-http.klib.api index 7bd6b70711b..ef222663f48 100644 --- a/ktor-http/api/ktor-http.klib.api +++ b/ktor-http/api/ktor-http.klib.api @@ -416,6 +416,9 @@ final class io.ktor.http/ContentType : io.ktor.http/HeaderValueWithParameters { final fun withoutParameters(): io.ktor.http/ContentType // io.ktor.http/ContentType.withoutParameters|withoutParameters(){}[0] final object Application { // io.ktor.http/ContentType.Application|null[0] + final const val TYPE // io.ktor.http/ContentType.Application.TYPE|{}TYPE[0] + final fun (): kotlin/String // io.ktor.http/ContentType.Application.TYPE.|(){}[0] + final val Any // io.ktor.http/ContentType.Application.Any|{}Any[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Application.Any.|(){}[0] final val Atom // io.ktor.http/ContentType.Application.Atom|{}Atom[0] @@ -460,9 +463,15 @@ final class io.ktor.http/ContentType : io.ktor.http/HeaderValueWithParameters { final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Application.Xml_Dtd.|(){}[0] final val Zip // io.ktor.http/ContentType.Application.Zip|{}Zip[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Application.Zip.|(){}[0] + + final fun contains(io.ktor.http/ContentType): kotlin/Boolean // io.ktor.http/ContentType.Application.contains|contains(io.ktor.http.ContentType){}[0] + final fun contains(kotlin/CharSequence): kotlin/Boolean // io.ktor.http/ContentType.Application.contains|contains(kotlin.CharSequence){}[0] } final object Audio { // io.ktor.http/ContentType.Audio|null[0] + final const val TYPE // io.ktor.http/ContentType.Audio.TYPE|{}TYPE[0] + final fun (): kotlin/String // io.ktor.http/ContentType.Audio.TYPE.|(){}[0] + final val Any // io.ktor.http/ContentType.Audio.Any|{}Any[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Audio.Any.|(){}[0] final val MP4 // io.ktor.http/ContentType.Audio.MP4|{}MP4[0] @@ -471,6 +480,9 @@ final class io.ktor.http/ContentType : io.ktor.http/HeaderValueWithParameters { final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Audio.MPEG.|(){}[0] final val OGG // io.ktor.http/ContentType.Audio.OGG|{}OGG[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Audio.OGG.|(){}[0] + + final fun contains(io.ktor.http/ContentType): kotlin/Boolean // io.ktor.http/ContentType.Audio.contains|contains(io.ktor.http.ContentType){}[0] + final fun contains(kotlin/CharSequence): kotlin/Boolean // io.ktor.http/ContentType.Audio.contains|contains(kotlin.CharSequence){}[0] } final object Companion { // io.ktor.http/ContentType.Companion|null[0] @@ -481,6 +493,9 @@ final class io.ktor.http/ContentType : io.ktor.http/HeaderValueWithParameters { } final object Font { // io.ktor.http/ContentType.Font|null[0] + final const val TYPE // io.ktor.http/ContentType.Font.TYPE|{}TYPE[0] + final fun (): kotlin/String // io.ktor.http/ContentType.Font.TYPE.|(){}[0] + final val Any // io.ktor.http/ContentType.Font.Any|{}Any[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Font.Any.|(){}[0] final val Collection // io.ktor.http/ContentType.Font.Collection|{}Collection[0] @@ -495,9 +510,15 @@ final class io.ktor.http/ContentType : io.ktor.http/HeaderValueWithParameters { final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Font.Woff.|(){}[0] final val Woff2 // io.ktor.http/ContentType.Font.Woff2|{}Woff2[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Font.Woff2.|(){}[0] + + final fun contains(io.ktor.http/ContentType): kotlin/Boolean // io.ktor.http/ContentType.Font.contains|contains(io.ktor.http.ContentType){}[0] + final fun contains(kotlin/CharSequence): kotlin/Boolean // io.ktor.http/ContentType.Font.contains|contains(kotlin.CharSequence){}[0] } final object Image { // io.ktor.http/ContentType.Image|null[0] + final const val TYPE // io.ktor.http/ContentType.Image.TYPE|{}TYPE[0] + final fun (): kotlin/String // io.ktor.http/ContentType.Image.TYPE.|(){}[0] + final val Any // io.ktor.http/ContentType.Image.Any|{}Any[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Image.Any.|(){}[0] final val GIF // io.ktor.http/ContentType.Image.GIF|{}GIF[0] @@ -510,16 +531,28 @@ final class io.ktor.http/ContentType : io.ktor.http/HeaderValueWithParameters { final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Image.SVG.|(){}[0] final val XIcon // io.ktor.http/ContentType.Image.XIcon|{}XIcon[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Image.XIcon.|(){}[0] + + final fun contains(io.ktor.http/ContentType): kotlin/Boolean // io.ktor.http/ContentType.Image.contains|contains(io.ktor.http.ContentType){}[0] + final fun contains(kotlin/String): kotlin/Boolean // io.ktor.http/ContentType.Image.contains|contains(kotlin.String){}[0] } final object Message { // io.ktor.http/ContentType.Message|null[0] + final const val TYPE // io.ktor.http/ContentType.Message.TYPE|{}TYPE[0] + final fun (): kotlin/String // io.ktor.http/ContentType.Message.TYPE.|(){}[0] + final val Any // io.ktor.http/ContentType.Message.Any|{}Any[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Message.Any.|(){}[0] final val Http // io.ktor.http/ContentType.Message.Http|{}Http[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Message.Http.|(){}[0] + + final fun contains(io.ktor.http/ContentType): kotlin/Boolean // io.ktor.http/ContentType.Message.contains|contains(io.ktor.http.ContentType){}[0] + final fun contains(kotlin/String): kotlin/Boolean // io.ktor.http/ContentType.Message.contains|contains(kotlin.String){}[0] } final object MultiPart { // io.ktor.http/ContentType.MultiPart|null[0] + final const val TYPE // io.ktor.http/ContentType.MultiPart.TYPE|{}TYPE[0] + final fun (): kotlin/String // io.ktor.http/ContentType.MultiPart.TYPE.|(){}[0] + final val Alternative // io.ktor.http/ContentType.MultiPart.Alternative|{}Alternative[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.MultiPart.Alternative.|(){}[0] final val Any // io.ktor.http/ContentType.MultiPart.Any|{}Any[0] @@ -536,9 +569,15 @@ final class io.ktor.http/ContentType : io.ktor.http/HeaderValueWithParameters { final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.MultiPart.Related.|(){}[0] final val Signed // io.ktor.http/ContentType.MultiPart.Signed|{}Signed[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.MultiPart.Signed.|(){}[0] + + final fun contains(io.ktor.http/ContentType): kotlin/Boolean // io.ktor.http/ContentType.MultiPart.contains|contains(io.ktor.http.ContentType){}[0] + final fun contains(kotlin/CharSequence): kotlin/Boolean // io.ktor.http/ContentType.MultiPart.contains|contains(kotlin.CharSequence){}[0] } final object Text { // io.ktor.http/ContentType.Text|null[0] + final const val TYPE // io.ktor.http/ContentType.Text.TYPE|{}TYPE[0] + final fun (): kotlin/String // io.ktor.http/ContentType.Text.TYPE.|(){}[0] + final val Any // io.ktor.http/ContentType.Text.Any|{}Any[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Text.Any.|(){}[0] final val CSS // io.ktor.http/ContentType.Text.CSS|{}CSS[0] @@ -557,9 +596,15 @@ final class io.ktor.http/ContentType : io.ktor.http/HeaderValueWithParameters { final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Text.VCard.|(){}[0] final val Xml // io.ktor.http/ContentType.Text.Xml|{}Xml[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Text.Xml.|(){}[0] + + final fun contains(io.ktor.http/ContentType): kotlin/Boolean // io.ktor.http/ContentType.Text.contains|contains(io.ktor.http.ContentType){}[0] + final fun contains(kotlin/CharSequence): kotlin/Boolean // io.ktor.http/ContentType.Text.contains|contains(kotlin.CharSequence){}[0] } final object Video { // io.ktor.http/ContentType.Video|null[0] + final const val TYPE // io.ktor.http/ContentType.Video.TYPE|{}TYPE[0] + final fun (): kotlin/String // io.ktor.http/ContentType.Video.TYPE.|(){}[0] + final val Any // io.ktor.http/ContentType.Video.Any|{}Any[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Video.Any.|(){}[0] final val MP4 // io.ktor.http/ContentType.Video.MP4|{}MP4[0] @@ -570,6 +615,9 @@ final class io.ktor.http/ContentType : io.ktor.http/HeaderValueWithParameters { final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Video.OGG.|(){}[0] final val QuickTime // io.ktor.http/ContentType.Video.QuickTime|{}QuickTime[0] final fun (): io.ktor.http/ContentType // io.ktor.http/ContentType.Video.QuickTime.|(){}[0] + + final fun contains(io.ktor.http/ContentType): kotlin/Boolean // io.ktor.http/ContentType.Video.contains|contains(io.ktor.http.ContentType){}[0] + final fun contains(kotlin/CharSequence): kotlin/Boolean // io.ktor.http/ContentType.Video.contains|contains(kotlin.CharSequence){}[0] } } diff --git a/ktor-http/common/src/io/ktor/http/ContentTypes.kt b/ktor-http/common/src/io/ktor/http/ContentTypes.kt index c2444bc5c74..cf01c11dc88 100644 --- a/ktor-http/common/src/io/ktor/http/ContentTypes.kt +++ b/ktor-http/common/src/io/ktor/http/ContentTypes.kt @@ -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 @@ -154,43 +154,41 @@ 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) } /** @@ -198,10 +196,19 @@ public class ContentType private constructor( */ @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) } /** @@ -209,12 +216,21 @@ public class ContentType private constructor( */ @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) } /** @@ -222,8 +238,17 @@ public class ContentType private constructor( */ @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) } /** @@ -231,14 +256,23 @@ public class ContentType private constructor( */ @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) } /** @@ -246,15 +280,24 @@ public class ContentType private constructor( */ @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) } /** @@ -262,11 +305,20 @@ public class ContentType private constructor( */ @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) } /** @@ -274,13 +326,22 @@ public class ContentType private constructor( */ @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) } } diff --git a/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/Multipart.kt b/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/Multipart.kt index 3eeab2f5afa..d1c2337747b 100644 --- a/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/Multipart.kt +++ b/ktor-http/ktor-http-cio/jvm/src/io/ktor/http/cio/Multipart.kt @@ -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 @@ -153,7 +154,7 @@ public fun CoroutineScope.parseMultipart( contentLength: Long?, maxPartSize: Long = Long.MAX_VALUE, ): ReceiveChannel { - 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) diff --git a/ktor-server/ktor-server-core/common/src/io/ktor/server/request/ApplicationRequestProperties.kt b/ktor-server/ktor-server-core/common/src/io/ktor/server/request/ApplicationRequestProperties.kt index f3d20f9779b..5cc52d44d19 100644 --- a/ktor-server/ktor-server-core/common/src/io/ktor/server/request/ApplicationRequestProperties.kt +++ b/ktor-server/ktor-server-core/common/src/io/ktor/server/request/ApplicationRequestProperties.kt @@ -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.* @@ -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. diff --git a/ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyKtorHandler.kt b/ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyKtorHandler.kt index 9d1d522a7fe..e8bbc7efadf 100644 --- a/ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyKtorHandler.kt +++ b/ktor-server/ktor-server-jetty-jakarta/jvm/src/io/ktor/server/jetty/jakarta/JettyKtorHandler.kt @@ -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 } diff --git a/ktor-server/ktor-server-jetty/jvm/src/io/ktor/server/jetty/JettyKtorHandler.kt b/ktor-server/ktor-server-jetty/jvm/src/io/ktor/server/jetty/JettyKtorHandler.kt index 38a12f972a8..74c89ef642f 100644 --- a/ktor-server/ktor-server-jetty/jvm/src/io/ktor/server/jetty/JettyKtorHandler.kt +++ b/ktor-server/ktor-server-jetty/jvm/src/io/ktor/server/jetty/JettyKtorHandler.kt @@ -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 }