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

commonMain.com.copperleaf.ballast.navigation.internal.Uri.kt Maven / Gradle / Ivy

There is a newer version: 4.2.1
Show newest version
package com.copperleaf.ballast.navigation.internal

import io.ktor.http.ParametersBuilder
import io.ktor.http.Url
import io.ktor.http.decodeURLPart
import io.ktor.http.encodeURLPath
import io.ktor.http.encodeURLPathPart
import io.ktor.http.encodeURLQueryComponent
import io.ktor.http.parseQueryString
import io.ktor.utils.io.charsets.Charset
import io.ktor.utils.io.charsets.Charsets

internal class Uri private constructor(
    val protocol: String,
    val host: String,
    val port: Int,
    val decodedPathSegments: List,
    val decodedQueryParameters: Map>,
    val decodedFragment: String?,
) {
    internal val encodedPath: String = buildString {
        if (decodedPathSegments.isNotEmpty()) {
            decodedPathSegments.forEach {
                append('/')
                append(Uri.encodeUrlPathSegment(it))
            }
        }
    }
    internal val encodedQueryString: String = buildString {
        if (decodedQueryParameters.isNotEmpty()) {
            append('?')

            val (head, tail) = decodedQueryParameters
                .flatMap { (key, values) ->
                    values.map { value -> key to value }
                }
                .let { flattenedParams ->
                    flattenedParams.take(1).single() to flattenedParams.drop(1)
                }

            append(head.first)
            append('=')
            append(head.second)

            tail.forEach { (key, value) ->
                append('&')
                append(Uri.encodeUrlQueryComponent(key))
                append('=')
                append(Uri.encodeUrlQueryComponent(value))
            }
        }
    }
    internal val encodedFragment: String = buildString {
        if (!decodedFragment.isNullOrBlank()) {
            append("#")
            append(Uri.encodeUrlPath(decodedFragment))
        }
    }
    internal val encodedPathAndQuery: String = buildString {
        append(encodedPath)
        append(encodedQueryString)
    }

    private val urlString: String = buildString {
        append(protocol)
        append("://")
        append(host)

        val defaultPortForProtocol = protocolDefaultPorts[protocol]
        if (defaultPortForProtocol == null || defaultPortForProtocol != port) {
            append(':')
            append(port.toString())
        }

        append(encodedPathAndQuery)
        append(encodedFragment)
    }

    override fun toString(): String {
        return urlString
    }

    companion object {
        private val defaultProtocol = "http"
        private val defaultHost = "localhost"
        private val protocolDefaultPorts = mapOf(
            "http" to 80,
            "https" to 443,
            "ws" to 80,
            "wss" to 443,
            "socks" to 1080,
        )

        internal fun parse(uri: String): Uri {
            return Url(uri)
                .toUri()
        }

        internal fun Url.toUri(): Uri {
            val parsedUrl = this
            return Uri(
                protocol = parsedUrl.protocol.name,
                host = parsedUrl.host,
                port = parsedUrl.port,
                decodedPathSegments = parsedUrl.pathSegments.dropWhile { it.isBlank() }.dropLastWhile { it.isBlank() },
                decodedQueryParameters = parsedUrl.parameters.entries().associate { it.key to it.value },
                decodedFragment = parsedUrl.fragment.takeIf { it.isNotBlank() },
            )
        }

        internal fun build(
            encodedPath: String,
            encodedQueryString: String?,
        ): Uri {
            val decodedPathSegments = encodedPath
                .split('/')
                .dropWhile { it.isBlank() }.dropLastWhile { it.isBlank() }
                .map { decodeUrlPathSegment(it) }
            val decodedParameters = ParametersBuilder()
                .apply { appendAll(parseQueryString(encodedQueryString ?: "", decode = true)) }
                .entries()
                .associate { it.key to it.value }

            return Uri(
                protocol = defaultProtocol,
                host = defaultHost,
                port = protocolDefaultPorts[defaultProtocol]!!,
                decodedPathSegments = decodedPathSegments,
                decodedQueryParameters = decodedParameters,
                decodedFragment = null,
            )
        }

        internal fun decodeUrlPathSegment(part: String): String {
            return part.decodeURLPart()
        }

        internal fun encodeUrlPathSegment(part: String): String {
            return part.encodeURLPathPart()
        }

        internal fun encodeUrlPath(part: String): String {
            return part.encodeURLPath()
        }

        internal fun encodeUrlQueryComponent(
            queryComponent: String,
            spaceToPlus: Boolean = false,
            charset: Charset = Charsets.UTF_8
        ): String {
            return queryComponent.encodeURLQueryComponent(true, spaceToPlus, charset)
        }

        internal fun encodeUrlQueryString(
            queryComponent: String,
            spaceToPlus: Boolean = false,
            charset: Charset = Charsets.UTF_8
        ): String {
            return queryComponent.encodeURLQueryComponent(false, spaceToPlus, charset)
        }
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy