Skip to content

Commit 2fde357

Browse files
committed
less buggy urlEncode implementation
1 parent 3669046 commit 2fde357

File tree

2 files changed

+54
-12
lines changed

2 files changed

+54
-12
lines changed
Lines changed: 14 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package com.jillesvangurp.geojson
22

33
import com.jillesvangurp.serializationext.DEFAULT_JSON
4-
import kotlinx.serialization.encodeToString
54

65

76
val FeatureCollection.geoJsonIOUrl: Any
@@ -13,17 +12,20 @@ val FeatureCollection.geoJsonIOUrl: Any
1312

1413
val Geometry.geoJsonIOUrl get() = this.asFeatureCollection.geoJsonIOUrl
1514

16-
fun String.urlEncode(): String {
17-
val allowedChars = ('A'..'Z') + ('a'..'z') + ('0'..'9') + listOf('-', '.', '_', '~')
18-
return buildString {
19-
this@urlEncode.forEach { char ->
20-
if (char in allowedChars) {
21-
append(char)
22-
} else {
23-
append(char.code.toByte().toInt().let {
24-
"%${it.shr(4).and(0xF).toString(16)}${it.and(0xF).toString(16)}"
25-
}.uppercase())
26-
}
15+
private const val HEX = "0123456789ABCDEF"
16+
/** Percent-encode according to RFC 3986 (UTF-8) */
17+
fun String.urlEncode(): String = buildString {
18+
for (byte in encodeToByteArray()) { // UTF-8 bytes
19+
val b = byte.toInt() and 0xFF // 0-255, no sign
20+
val c = b.toChar()
21+
if (c in "ABCDEFGHIJKLMNOPQRSTUVWXYZ" +
22+
"abcdefghijklmnopqrstuvwxyz0123456789-._~"
23+
) { // keep unreserved
24+
append(c)
25+
} else { // escape everything else
26+
append('%')
27+
append(HEX[b ushr 4])
28+
append(HEX[b and 0x0F])
2729
}
2830
}
2931
}
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package com.jillesvangurp.geojson
2+
3+
import io.kotest.matchers.shouldBe
4+
import kotlin.test.Test
5+
6+
class JsonHelpersTest {
7+
8+
@Test
9+
fun shouldCorrectlyUrlEncode() {
10+
val cases = mapOf(
11+
// unreserved chars must stay as-is
12+
"AZaz09-._~" to "AZaz09-._~",
13+
14+
// whitespace
15+
"Hello World" to "Hello%20World",
16+
17+
// German sharp-s (ß) – 2-byte UTF-8
18+
"Wattstraße" to "Wattstra%C3%9Fe",
19+
20+
// umlaut
21+
"München" to "M%C3%BCnchen",
22+
23+
// euro sign, ampersand, percent
24+
"€ & %" to "%E2%82%AC%20%26%20%25",
25+
26+
// plus and equals
27+
"a+b=c" to "a%2Bb%3Dc",
28+
29+
// slash must be escaped
30+
"path/part" to "path%2Fpart",
31+
32+
// tilde is unreserved
33+
"~tilde" to "~tilde"
34+
)
35+
36+
cases.forEach { (raw, expected) ->
37+
raw.urlEncode() shouldBe expected
38+
}
39+
}
40+
}

0 commit comments

Comments
 (0)