Comprehensive HTTP header value parsing utilities for handling complex header formats with parameters, quality values, content type negotiation, and multi-value headers according to HTTP specifications.
Core data classes for representing parsed header values with parameters and quality scoring.
/**
* Represents a single header value with optional parameters
* @param value the main header value
* @param params list of parameters associated with this value
*/
data class HeaderValue(
val value: String,
val params: List<HeaderValueParam> = emptyList()
) {
/**
* Quality value from 'q' parameter, defaults to 1.0 if missing or invalid
* Used for content negotiation and priority ordering
*/
val quality: Double
}
/**
* Represents a header parameter
* @param name parameter name (case-insensitive)
* @param value parameter value
* @param escapeValue whether the value should be escaped when serialized
*/
data class HeaderValueParam(
val name: String,
val value: String,
val escapeValue: Boolean = false
)Usage Examples:
import io.ktor.http.*
// Create header values manually
val jsonHeader = HeaderValue(
value = "application/json",
params = listOf(
HeaderValueParam("charset", "utf-8"),
HeaderValueParam("boundary", "abc123", escapeValue = true)
)
)
println(jsonHeader.quality) // 1.0 (default)
val xmlHeader = HeaderValue(
value = "application/xml",
params = listOf(HeaderValueParam("q", "0.8"))
)
println(xmlHeader.quality) // 0.8
// Parameter comparison (case-insensitive)
val param1 = HeaderValueParam("Content-Type", "application/json")
val param2 = HeaderValueParam("content-type", "APPLICATION/JSON")
println(param1 == param2) // trueParse header values supporting multiple values separated by commas and parameters.
/**
* Parse header value string into list of HeaderValue objects
* @param text header value string to parse
* @return list of parsed header values
*/
fun parseHeaderValue(text: String?): List<HeaderValue>
/**
* Parse header value with control over value parsing
* @param text header value string to parse
* @param parametersOnly if true, parse only parameters without main value
* @return list of parsed header values
*/
fun parseHeaderValue(text: String?, parametersOnly: Boolean): List<HeaderValue>Usage Examples:
import io.ktor.http.*
// Parse simple header
val simpleHeader = parseHeaderValue("application/json")
println(simpleHeader[0].value) // "application/json"
println(simpleHeader[0].params.size) // 0
// Parse header with parameters
val withParams = parseHeaderValue("text/html; charset=utf-8; boundary=abc123")
val headerValue = withParams[0]
println(headerValue.value) // "text/html"
println(headerValue.params.size) // 2
println(headerValue.params[0].name) // "charset"
println(headerValue.params[0].value) // "utf-8"
// Parse multiple values
val multiValue = parseHeaderValue("gzip, deflate, br")
println(multiValue.size) // 3
println(multiValue[0].value) // "gzip"
println(multiValue[1].value) // "deflate"
println(multiValue[2].value) // "br"
// Parse complex Accept header
val acceptHeader = parseHeaderValue("text/html; q=0.9, application/json; q=0.8, */*; q=0.1")
println(acceptHeader.size) // 3
println(acceptHeader[0].value) // "text/html"
println(acceptHeader[0].quality) // 0.9
println(acceptHeader[1].value) // "application/json"
println(acceptHeader[1].quality) // 0.8
// Parse quoted parameter values
val quotedParams = parseHeaderValue("attachment; filename=\"my file.txt\"; size=1024")
val attachment = quotedParams[0]
println(attachment.params.find { it.name == "filename" }?.value) // "my file.txt"
println(attachment.params.find { it.name == "size" }?.value) // "1024"
// Handle escaped quotes
val escapedQuotes = parseHeaderValue("text; value=\"He said \\\"Hello\\\"\"")
val textValue = escapedQuotes[0]
println(textValue.params[0].value) // "He said \"Hello\""Parse and sort header values by quality for content negotiation.
/**
* Parse header value and sort by quality (q parameter) in descending order
* Higher quality values appear first in the result
* @param header header value string to parse
* @return list of HeaderValue objects sorted by quality
*/
fun parseAndSortHeader(header: String?): List<HeaderValue>Usage Examples:
import io.ktor.http.*
// Parse and sort Accept header
val acceptHeader = "text/html; q=0.5, application/json, text/plain; q=0.8, */*; q=0.1"
val sortedValues = parseAndSortHeader(acceptHeader)
println("Sorted by preference:")
sortedValues.forEach { headerValue ->
println("${headerValue.value} (q=${headerValue.quality})")
}
// Output:
// application/json (q=1.0)
// text/plain (q=0.8)
// text/html (q=0.5)
// */* (q=0.1)
// Parse Accept-Language with quality
val languageHeader = "en-US; q=0.9, en; q=0.8, fr; q=0.7, *; q=0.1"
val sortedLanguages = parseAndSortHeader(languageHeader)
println("Preferred languages:")
sortedLanguages.forEach { lang ->
println("${lang.value} (preference: ${lang.quality})")
}
// Parse Accept-Encoding
val encodingHeader = "gzip; q=1.0, deflate; q=0.8, identity; q=0.5"
val sortedEncodings = parseAndSortHeader(encodingHeader)
val preferredEncoding = sortedEncodings.firstOrNull()?.value
println("Preferred encoding: $preferredEncoding") // "gzip"Special parsing for Content-Type headers with sophisticated sorting by quality, specificity, and parameter count.
/**
* Parse Content-Type header values and sort by preference
* Sorting criteria (in order):
* 1. Quality value (q parameter) - higher first
* 2. Specificity - fewer wildcards first
* 3. Parameter count - more parameters first
* @param header Content-Type header value
* @return list of HeaderValue objects sorted by negotiation preference
*/
fun parseAndSortContentTypeHeader(header: String?): List<HeaderValue>Usage Examples:
import io.ktor.http.*
// Parse complex Accept header for content negotiation
val acceptHeader = "text/html; q=0.9, application/*; q=0.8, text/*; q=0.7, */*; q=0.1, application/json"
val negotiated = parseAndSortContentTypeHeader(acceptHeader)
println("Content type negotiation order:")
negotiated.forEach { ct ->
println("${ct.value} (q=${ct.quality})")
}
// Output prioritizes:
// 1. application/json (q=1.0, most specific)
// 2. text/html (q=0.9, specific)
// 3. application/* (q=0.8, wildcard subtype)
// 4. text/* (q=0.7, wildcard subtype)
// 5. */* (q=0.1, wildcard type)
// Content negotiation with parameters
val complexAccept = "application/json; charset=utf-8; q=0.9, application/xml; q=0.8, text/plain"
val sorted = parseAndSortContentTypeHeader(complexAccept)
// Find best match for available content types
val availableTypes = listOf("application/json", "text/plain", "application/xml")
val bestMatch = sorted.firstOrNull { headerValue ->
availableTypes.any { available ->
ContentType.parse(headerValue.value).match(ContentType.parse(available))
}
}
println("Best match: ${bestMatch?.value}") // "text/plain" (q=1.0)
// Handle wildcard matching
fun findBestContentType(acceptHeader: String, availableTypes: List<String>): String? {
val parsed = parseAndSortContentTypeHeader(acceptHeader)
for (accepted in parsed) {
val acceptedType = ContentType.parse(accepted.value)
for (available in availableTypes) {
val availableType = ContentType.parse(available)
if (availableType.match(acceptedType)) {
return available
}
}
}
return null
}
val available = listOf("application/json", "text/html", "image/png")
val clientAccepts = "text/*; q=0.8, application/json; q=0.9, */*; q=0.1"
val chosen = findBestContentType(clientAccepts, available)
println("Selected: $chosen") // "application/json"Helper functions for working with header parameters.
/**
* Convert pairs to HeaderValueParam list
* @return list of HeaderValueParam objects
*/
fun Iterable<Pair<String, String>>.toHeaderParamsList(): List<HeaderValueParam>Usage Examples:
import io.ktor.http.*
// Create parameters from pairs
val paramPairs = listOf(
"charset" to "utf-8",
"boundary" to "----WebKitFormBoundary",
"q" to "0.8"
)
val params = paramPairs.toHeaderParamsList()
// Use in HeaderValue
val contentType = HeaderValue("multipart/form-data", params)
println(contentType.quality) // 0.8
println(contentType.params.find { it.name == "boundary" }?.value) // "----WebKitFormBoundary"
// Combine with manual parameter creation
val mixedParams = listOf(
HeaderValueParam("type", "image"),
HeaderValueParam("quality", "high", escapeValue = true)
) + listOf("format" to "png", "compression" to "lossless").toHeaderParamsList()
val imageHeader = HeaderValue("image/png", mixedParams)
println(imageHeader.params.size) // 4import io.ktor.http.*
// Complete content negotiation system
class ContentNegotiator {
fun negotiate(acceptHeader: String?, availableTypes: List<String>): String? {
if (acceptHeader == null) return availableTypes.firstOrNull()
val parsed = parseAndSortContentTypeHeader(acceptHeader)
for (accepted in parsed) {
val acceptedType = try {
ContentType.parse(accepted.value)
} catch (e: Exception) {
continue
}
for (available in availableTypes) {
val availableType = try {
ContentType.parse(available)
} catch (e: Exception) {
continue
}
if (matches(acceptedType, availableType)) {
return available
}
}
}
return null
}
private fun matches(accepted: ContentType, available: ContentType): Boolean {
return when {
accepted.contentType == "*" -> true
accepted.contentType != available.contentType -> false
accepted.contentSubtype == "*" -> true
else -> accepted.contentSubtype == available.contentSubtype
}
}
}
// Language preference parsing
fun parseLanguagePreferences(acceptLanguage: String?): List<String> {
if (acceptLanguage == null) return emptyList()
return parseAndSortHeader(acceptLanguage)
.map { it.value.lowercase() }
.filter { it != "*" }
}
// Cache control directive parsing
fun parseCacheControl(cacheControl: String?): Map<String, String?> {
if (cacheControl == null) return emptyMap()
return parseHeaderValue(cacheControl, parametersOnly = true)
.flatMap { it.params }
.associate { param ->
param.name.lowercase() to param.value.takeIf { it.isNotEmpty() }
}
}
// Usage examples
val negotiator = ContentNegotiator()
val result = negotiator.negotiate(
"application/json; q=0.9, text/html; q=0.8, */*; q=0.1",
listOf("text/html", "application/xml", "text/plain")
)
println("Negotiated: $result") // "text/html"
val languages = parseLanguagePreferences("en-US; q=0.9, en; q=0.8, fr; q=0.5")
println("Languages: $languages") // ["en-us", "en", "fr"]
val cacheDirectives = parseCacheControl("public, max-age=3600, no-transform")
println("Max age: ${cacheDirectives["max-age"]}") // "3600"
println("Is public: ${cacheDirectives.containsKey("public")}") // true
// Complex multipart boundary extraction
fun extractBoundary(contentType: String): String? {
val parsed = parseHeaderValue(contentType).firstOrNull() ?: return null
return if (parsed.value.startsWith("multipart/")) {
parsed.params.find { it.name.equals("boundary", ignoreCase = true) }?.value
} else null
}
val multipartHeader = "multipart/form-data; boundary=----WebKitFormBoundary7MA4YWxkTrZu0gW"
val boundary = extractBoundary(multipartHeader)
println("Boundary: $boundary") // "----WebKitFormBoundary7MA4YWxkTrZu0gW"/**
* Header value with parameters and automatic quality calculation
*/
data class HeaderValue(
val value: String,
val params: List<HeaderValueParam> = emptyList()
) {
val quality: Double // Automatically calculated from 'q' parameter
}
/**
* Header parameter with case-insensitive name comparison
*/
data class HeaderValueParam(
val name: String,
val value: String,
val escapeValue: Boolean = false
)