compat: stop mangling refusal messages with HTML escapes
The server's 426 body reached the user as "needs \u003e= 0.2.0, \u003c 1.0.0": Go escapes <, > and & by default for JSON destined for a page, which this is not. Disabled at the encoder. The client now parses the error field rather than pattern-matching it, so it survives whatever a future encoder decides to escape. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
9d6572bc33
commit
33a6acb0bf
@@ -4,6 +4,8 @@
|
||||
package app.echo_lot.protocol
|
||||
|
||||
import kotlinx.serialization.json.Json
|
||||
import kotlinx.serialization.json.jsonObject
|
||||
import kotlinx.serialization.json.jsonPrimitive
|
||||
import java.net.URL
|
||||
import javax.net.ssl.HttpsURLConnection
|
||||
|
||||
@@ -59,13 +61,16 @@ class ControlClient(
|
||||
if (conn.responseCode == 426) throw VersionRefused(extractError(body) ?: body.take(200))
|
||||
}
|
||||
|
||||
/** Pulls the "error" string out of a JSON body without pulling in a parser for one field. */
|
||||
private fun extractError(body: String): String? =
|
||||
Regex(""""error"\s*:\s*"((?:[^"\\]|\\.)*)"""").find(body)
|
||||
?.groupValues?.get(1)
|
||||
?.replace("\\\"", "\"")
|
||||
?.replace("\\n", "\n")
|
||||
?.replace("\\\\", "\\")
|
||||
/**
|
||||
* Pulls the "error" string out of a JSON body.
|
||||
*
|
||||
* Parsed rather than pattern-matched: an encoder may legitimately escape characters in the
|
||||
* message (Go escapes ">" by default), and a regex hands the user "needs \u003e= 0.2.0".
|
||||
* The parser knows how to undo every escape; a regex would have to be taught each one.
|
||||
*/
|
||||
private fun extractError(body: String): String? = runCatching {
|
||||
json.parseToJsonElement(body).jsonObject["error"]?.jsonPrimitive?.content
|
||||
}.getOrNull()
|
||||
|
||||
/**
|
||||
* Reads the response body, and turns a 426 into [VersionRefused] first.
|
||||
|
||||
@@ -430,7 +430,12 @@ func bearer(r *http.Request) string {
|
||||
func writeJSON(w http.ResponseWriter, code int, v any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(code)
|
||||
_ = json.NewEncoder(w).Encode(v)
|
||||
enc := json.NewEncoder(w)
|
||||
// Go escapes <, > and & by default, for JSON embedded in HTML. This is an API, and the
|
||||
// escaping is actively harmful here: a refusal message reading "needs >= 0.2.0" is what
|
||||
// the user ends up seeing. Nothing we emit is ever interpolated into a page.
|
||||
enc.SetEscapeHTML(false)
|
||||
_ = enc.Encode(v)
|
||||
}
|
||||
|
||||
// enroll redeems a single-use enrollment token for a device credential (§2.1).
|
||||
|
||||
Reference in New Issue
Block a user