99 lines
3.2 KiB
Kotlin
99 lines
3.2 KiB
Kotlin
package de.waypointaudio.license
|
|
|
|
import org.json.JSONArray
|
|
import org.json.JSONObject
|
|
|
|
/**
|
|
* v2.5.20 — Kanonische JSON-Serialisierung, kompatibel zum Python-Tool
|
|
* (`canonical_json_bytes`):
|
|
* - Keys lexikografisch sortiert.
|
|
* - Trennzeichen ohne Whitespace: "," / ":".
|
|
* - `ensure_ascii=False` ⇒ Unicode-Strings werden nicht escapet, ausser den
|
|
* minimal nötigen JSON-Escapes (`\"`, `\\`, Steuerzeichen < 0x20).
|
|
* - `null` für JSON-Null; `JSONObject.NULL` und Kotlin-`null` gleich behandelt.
|
|
*
|
|
* Diese kanonische Form ist die Eingabe für die ECDSA-P256-SHA256-Signatur.
|
|
*/
|
|
internal object CanonicalJson {
|
|
|
|
fun encode(value: Any?): String {
|
|
val sb = StringBuilder()
|
|
appendValue(sb, value)
|
|
return sb.toString()
|
|
}
|
|
|
|
private fun appendValue(sb: StringBuilder, value: Any?) {
|
|
when {
|
|
value == null || value === JSONObject.NULL -> sb.append("null")
|
|
value is Boolean -> sb.append(if (value) "true" else "false")
|
|
value is Number -> appendNumber(sb, value)
|
|
value is String -> appendString(sb, value)
|
|
value is JSONObject -> appendObject(sb, value)
|
|
value is JSONArray -> appendArray(sb, value)
|
|
else -> appendString(sb, value.toString())
|
|
}
|
|
}
|
|
|
|
private fun appendObject(sb: StringBuilder, obj: JSONObject) {
|
|
val keys = obj.keys().asSequence().toList().sorted()
|
|
sb.append('{')
|
|
for ((index, key) in keys.withIndex()) {
|
|
if (index > 0) sb.append(',')
|
|
appendString(sb, key)
|
|
sb.append(':')
|
|
appendValue(sb, obj.opt(key))
|
|
}
|
|
sb.append('}')
|
|
}
|
|
|
|
private fun appendArray(sb: StringBuilder, arr: JSONArray) {
|
|
sb.append('[')
|
|
for (i in 0 until arr.length()) {
|
|
if (i > 0) sb.append(',')
|
|
appendValue(sb, arr.opt(i))
|
|
}
|
|
sb.append(']')
|
|
}
|
|
|
|
private fun appendNumber(sb: StringBuilder, n: Number) {
|
|
when (n) {
|
|
is Int, is Long, is Short, is Byte -> sb.append(n.toString())
|
|
is Double -> {
|
|
if (n.isNaN() || n.isInfinite()) {
|
|
sb.append("null")
|
|
} else if (n == n.toLong().toDouble()) {
|
|
sb.append(n.toLong().toString())
|
|
} else {
|
|
sb.append(n.toString())
|
|
}
|
|
}
|
|
is Float -> appendNumber(sb, n.toDouble())
|
|
else -> sb.append(n.toString())
|
|
}
|
|
}
|
|
|
|
private fun appendString(sb: StringBuilder, s: String) {
|
|
sb.append('"')
|
|
for (c in s) {
|
|
when (c.code) {
|
|
0x22 -> sb.append("\\\"")
|
|
0x5C -> sb.append("\\\\")
|
|
0x08 -> sb.append("\\b")
|
|
0x0C -> sb.append("\\f")
|
|
0x0A -> sb.append("\\n")
|
|
0x0D -> sb.append("\\r")
|
|
0x09 -> sb.append("\\t")
|
|
else -> {
|
|
val code = c.code
|
|
if (code < 0x20) {
|
|
sb.append("\\u%04x".format(code))
|
|
} else {
|
|
sb.append(c)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
sb.append('"')
|
|
}
|
|
}
|