From 3167126f61985aca170a92c5f1d209927c1a1967 Mon Sep 17 00:00:00 2001 From: Colin White Date: Wed, 28 Feb 2024 00:26:20 -0800 Subject: [PATCH] Optimize Uri.percentDecode. (#2149) * Optimize Uri.percentDecode. * Tweak test case. --- .../src/commonMain/kotlin/coil3/Uri.common.kt | 43 +++++++++++-------- .../src/commonTest/kotlin/coil3/UriTest.kt | 13 ++++++ 2 files changed, 37 insertions(+), 19 deletions(-) diff --git a/coil-core/src/commonMain/kotlin/coil3/Uri.common.kt b/coil-core/src/commonMain/kotlin/coil3/Uri.common.kt index 8d6b916162..32d1ac78c5 100644 --- a/coil-core/src/commonMain/kotlin/coil3/Uri.common.kt +++ b/coil-core/src/commonMain/kotlin/coil3/Uri.common.kt @@ -138,16 +138,19 @@ private fun parseUri(data: String): Uri { fragment = data.substring(fragmentStartIndex, data.length) } - val size = maxOf( - scheme.length, - authority.length, + val maxLength = maxOf( + 0, maxOf( - path.length, - query.length, - fragment.length, - ), + scheme.length, + authority.length, + maxOf( + path.length, + query.length, + fragment.length, + ), + ) - 2, ) - val bytes = ByteArray(size) + val bytes = ByteArray(maxLength) return Uri( data = data, scheme = scheme?.percentDecode(bytes), @@ -161,9 +164,19 @@ private fun parseUri(data: String): Uri { private fun String.percentDecode(bytes: ByteArray): String { var size = 0 var index = 0 - - while (index < length) { - if (get(index) == '%' && index + 2 < length) { + val length = length + val searchLength = maxOf(0, length - 2) + + while (true) { + if (index >= searchLength) { + if (index == size) { + // Fast path: the string doesn't have any encoded characters. + return this + } else if (index >= length) { + // Slow path: decode the byte array. + return bytes.decodeToString(endIndex = size) + } + } else if (get(index) == '%') { try { val hex = substring(index + 1, index + 3) bytes[size] = hex.toInt(16).toByte() @@ -177,14 +190,6 @@ private fun String.percentDecode(bytes: ByteArray): String { size++ index++ } - - if (size == length) { - // Fast path: the string doesn't have any encoded characters. - return this - } else { - // Slow path: decode the byte array. - return bytes.decodeToString(endIndex = size) - } } private val String?.length: Int diff --git a/coil-core/src/commonTest/kotlin/coil3/UriTest.kt b/coil-core/src/commonTest/kotlin/coil3/UriTest.kt index d3c88218d2..9f3594f2ac 100644 --- a/coil-core/src/commonTest/kotlin/coil3/UriTest.kt +++ b/coil-core/src/commonTest/kotlin/coil3/UriTest.kt @@ -75,6 +75,19 @@ class UriTest { assertEquals(string, uri.toString()) } + @Test + fun encodedSingle() { + val string = "https://example.com/something%20" + val uri = string.toUri() + assertEquals("https", uri.scheme) + assertEquals("example.com", uri.authority) + assertEquals("/something ", uri.path) + assertEquals(listOf("something "), uri.pathSegments) + assertNull(uri.query) + assertNull(uri.fragment) + assertEquals(string, uri.toString()) + } + @Test fun encodedMalformed() { val string = "https://example.com/%E4%B8%8A%E6%B5%B7%2B%E4%B8%AD%E5%9C%8B%"