or run

npx @tessl/cli init
Log in

Version

Tile

Overview

Evals

Files

docs

authentication.mdcontent-processing.mdcontent-types.mdcookies.mddate-utilities.mdheader-parsing.mdheaders.mdhttp-message-extensions.mdhttp-methods-status.mdindex.mdmultipart.mdparameters.mdurl-encoding.mdurl-handling.md
tile.json

header-parsing.mddocs/

Header Value Parsing

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.

Capabilities

Header Value Representation

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) // true

Basic Header Parsing

Parse 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\""

Quality-Based Sorting

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"

Content Type Negotiation

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"

Parameter Utilities

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) // 4

Advanced Header Parsing Examples

import 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"

Types

/**
 * 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
)