All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.io.ktor.http.HttpHeaderValueParser.kt Maven / Gradle / Ivy

package io.ktor.http

import io.ktor.util.*

/**
 * Represents a single value parameter
 * @property name of parameter
 * @property value of parameter
 */
data class HeaderValueParam(val name: String, val value: String) {
    override fun equals(other: Any?): Boolean {
        return other is HeaderValueParam
            && other.name.equals(name, ignoreCase = true)
            && other.value.equals(value, ignoreCase = true)
    }

    override fun hashCode(): Int {
        var result = name.toLowerCase().hashCode()
        result += 31 * result + value.toLowerCase().hashCode()
        return result
    }
}

/**
 * Represents a header value. Similar to [HeaderValueWithParameters]
 * @property value
 * @property params for this value (could be empty)
 */
data class HeaderValue(val value: String, val params: List = listOf()) {
    /**
     * Value's quality according to `q` parameter or `1.0` if missing
     */
    val quality: Double = params.firstOrNull { it.name == "q" }?.value?.toDoubleOrNull() ?: 1.0
}

/**
 * Parse header value and sort multiple values according to qualities
 */
fun parseAndSortHeader(header: String?): List = parseHeaderValue(header).sortedByDescending { it.quality }

/**
 * Parse `Content-Type` header values and sort them by quality and asterisks quantity
 */
fun parseAndSortContentTypeHeader(header: String?): List = parseHeaderValue(header).sortedWith(
    compareByDescending { it.quality }.thenBy {
        val contentType = ContentType.parse(it.value)
        var asterisks = 0
        if (contentType.contentType == "*")
            asterisks += 2
        if (contentType.contentSubtype == "*")
            asterisks++
        asterisks
    }.thenByDescending { it.params.size })

/**
 * Parse header value respecting multi-values
 */
fun parseHeaderValue(text: String?): List {
    return parseHeaderValue(text, false)
}

/**
 * Parse header value respecting multi-values
 * @param parametersOnly if no header value itself, only parameters
 */
@KtorExperimentalAPI
fun parseHeaderValue(text: String?, parametersOnly: Boolean): List {
    if (text == null)
        return emptyList()

    var pos = 0
    val items = lazy(LazyThreadSafetyMode.NONE) { arrayListOf() }
    while (pos <= text.lastIndex) {
        pos = parseHeaderValueItem(text, pos, items, parametersOnly)
    }
    return items.valueOrEmpty()
}

/**
 * Construct a list of [HeaderValueParam] from an iterable of pairs
 */
@KtorExperimentalAPI
fun Iterable>.toHeaderParamsList(): List =
    map { HeaderValueParam(it.first, it.second) }

private fun  Lazy>.valueOrEmpty(): List = if (isInitialized()) value else emptyList()
private fun String.subtrim(start: Int, end: Int): String {
    return substring(start, end).trim()
}

private fun parseHeaderValueItem(
    text: String,
    start: Int,
    items: Lazy>,
    parametersOnly: Boolean
): Int {
    var pos = start
    val parameters = lazy(LazyThreadSafetyMode.NONE) { arrayListOf() }
    var valueEnd: Int? = if (parametersOnly) pos else null
    while (pos <= text.lastIndex) {
        when (text[pos]) {
            ',' -> {
                items.value.add(HeaderValue(text.subtrim(start, valueEnd ?: pos), parameters.valueOrEmpty()))
                return pos + 1
            }
            ';' -> {
                if (valueEnd == null) valueEnd = pos
                pos = parseHeaderValueParameter(text, pos + 1, parameters)
            }
            else -> {
                pos = if (parametersOnly) {
                    parseHeaderValueParameter(text, pos, parameters)
                } else {
                    pos + 1
                }
            }
        }
    }
    items.value.add(HeaderValue(text.subtrim(start, valueEnd ?: pos), parameters.valueOrEmpty()))
    return pos
}

private fun parseHeaderValueParameter(text: String, start: Int, parameters: Lazy>): Int {
    fun addParam(text: String, start: Int, end: Int, value: String) {
        val name = text.subtrim(start, end)
        if (name.isEmpty())
            return
        parameters.value.add(HeaderValueParam(name, value))
    }

    var pos = start
    while (pos <= text.lastIndex) {
        when (text[pos]) {
            '=' -> {
                val (paramEnd, paramValue) = parseHeaderValueParameterValue(text, pos + 1)
                addParam(text, start, pos, paramValue)
                return paramEnd
            }
            ';', ',' -> {
                addParam(text, start, pos, "")
                return pos
            }
            else -> pos++
        }
    }

    addParam(text, start, pos, "")
    return pos
}


private fun parseHeaderValueParameterValue(value: String, start: Int): Pair {
    var pos = start
    while (pos <= value.lastIndex) {
        when (value[pos]) {
            '"' -> return parseHeaderValueParameterValueQuoted(value, pos + 1)
            ';', ',' -> return pos to value.subtrim(start, pos)
            else -> pos++
        }
    }
    return pos to value.subtrim(start, pos)
}

private fun parseHeaderValueParameterValueQuoted(value: String, start: Int): Pair {
    var pos = start
    val sb = StringBuilder()
    while (pos <= value.lastIndex) {
        val c = value[pos]
        when (c) {
            '"' -> return pos + 1 to sb.toString()
            '\\' -> {
                if (pos < value.lastIndex - 2) {
                    sb.append(value[pos + 1])
                    pos += 2
                } // quoted value
                else {
                    sb.append(c)
                    pos++ // broken value, escape at the end
                }
            }
            else -> {
                sb.append(c)
                pos++
            }
        }
    }
    return pos to sb.toString()
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy