Repository: ktorio/ktor
File: ktor-http/common/src/io/ktor/http/URLParser.kt (Kotlin)

Code before:
    val contextIndex = parameters.indexOfFirst { it.startsWith("phone-context=") }
    val otherParameters = parameters.filterNot { it.startsWith("isdn-subaddress=")
        || it.startsWith("extension=")
        || it.startsWith("phone-context=") }

    if (isdnIndex != -1 && isdnIndex > 0) {
        throw IllegalArgumentException("Invalid tel URI: 'isdn-subaddress' must appear first if present")
    }

    if (extensionIndex != -1 && extensionIndex > 0) {
        throw IllegalArgumentException("Invalid tel URI: 'extension' must appear first if present")
    }

    if ((isdnIndex != -1 || extensionIndex != -1) && (contextIndex > 0 && contextIndex != 1)) {
        throw IllegalArgumentException(
            "Invalid tel URI: 'phone-context' must appear after 'isdn-subaddress' or 'extension'")
    }

    if (isdnIndex == -1 && extensionIndex == -1 && (contextIndex > 0 && contextIndex != 0)) {
        throw IllegalArgumentException(
            "Invalid tel URI: 'phone-context' must appear first if present")
    }

    if (otherParameters.sorted() != otherParameters) {
        throw IllegalArgumentException("Invalid tel URI: parameters must be in lexicographical order")
    }

}

private fun URLBuilder.parseQuery(urlString: String, startIndex: Int, endIndex: Int): Int {
    if (startIndex + 1 == endIndex) {
        trailingQuery = true
        return endIndex
    }

    val fragmentStart = urlString.indexOf('#', startIndex + 1).takeIf { it > 0 } ?: endIndex

    val rawParameters = parseQueryString(urlString.substring(startIndex + 1, fragmentStart), decode = false)
    rawParameters.forEach { key, values ->
        encodedParameters.appendAll(key, values)
    }

    return fragmentStart
}

private fun URLBuilder.parseFragment(urlString: String, startIndex: Int, endIndex: Int) {
    if (startIndex < endIndex && urlString[startIndex] == '#') {
        encodedFragment = urlString.substring(startIndex + 1, endIndex)
    }
}


Review comment (suggestion):
We could drop these requirements for parsing, but follow them on serialization (in `appendTel`).
To do so, we should store parameters to `URLBuilder` here and use them during serialization later.

Code after:

    host = urlString.substring(startIndex, colonIndex)

    port = if (colonIndex + 1 < endIndex) {
        urlString.substring(colonIndex + 1, endIndex).toInt()
    } else {
        DEFAULT_PORT
    }
}

/**
 * Finds scheme in the given [urlString]. If there is no scheme found the function returns -1. If the scheme contains
 * illegal characters an [IllegalArgumentException] will be thrown. If the scheme is present and it doesn't contain
 * illegal characters the function returns the length of the scheme.
 */
private fun findScheme(urlString: String, startIndex: Int, endIndex: Int): Int {
    var current = startIndex

    // Incorrect scheme position is used to identify the first position at which the character is not allowed in the
    // scheme or the part of the scheme. This number is reported in the exception message.
    var incorrectSchemePosition = -1
    val firstChar = urlString[current]
    if (firstChar !in 'a'..'z' && firstChar !in 'A'..'Z') {
        incorrectSchemePosition = current
    }

    while (current < endIndex) {
        val char = urlString[current]

        // Character ':' means the end of the scheme and at this point the length of the scheme should be returned or
        // the exception should be thrown in case the scheme contains illegal characters.
        if (char == ':') {
            if (incorrectSchemePosition != -1) {
                throw IllegalArgumentException("Illegal character in scheme at position $incorrectSchemePosition")
            }

            return current - startIndex
        }

        // If character '/' or '?' or '#' found this is not a scheme.
        if (char == '/' || char == '?' || char == '#') return -1

        // Update incorrect scheme position is current char is illegal.
        if (incorrectSchemePosition == -1 &&
            char !in 'a'..'z' &&
            char !in 'A'..'Z' &&
            char !in '0'..'9' &&
            char != '.' &&
            char != '+' &&
            char != '-'
        ) {