feat(ai): add full JSON \uXXXX decoding with UTF-8 and surrogate pair support #126
Reference in New Issue
Block a user
No description provided.
Delete Branch "fix/ai-json-encoding"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implements proper JSON Unicode escape (
\uXXXX) decoding in the AI client, including UTF-8 encoding and UTF-16 surrogate pair handling. Also tightens the/ai startcommand to require an explicit provider name.Changes
src/ai/ai_client.c_hex4_to_int()— converts 4-hex-digit sequences to 16-bit code points_write_utf8()— encodes a Unicode code point as UTF-8 bytes_json_unescape_substring()to handle:\uXXXXsequences → UTF-8 encoded characters\uHHHH\uLLLL) → characters outside the BMP (U+10000–U+10FFFF)\uXXXX)g_realloc()to shrink output buffer to exact sizesrc/command/cmd_funcs.c/ai startMotivation
Previously,
\uXXXXJSON escapes were passed through verbatim, resulting in garbled output for non-ASCII characters (e.g., emojis, CJK, mathematical symbols). This change ensures proper Unicode round-tripping through the AI pipeline.The provider default removal in
/ai startprevents silent use of an unexpected provider when the user's default may have changed or been misconfigured.Probably worth addressing in this PR
1. UAF fix may be doing more than fixing the UAF
src/command/cmd_funcs.c:10932-10941. The original UAF looks real to me —
auto_gchar gchar* default_providerwas scoped to an innerifblock, so cleanup ran at block exit whileprovider_namestill held a pointer into it. The PR's resolution is to drop the fallback toPREF_AI_PROVIDERand the"openai"default, which fixes the lifetime issue but also removes a feature. That may have been intentional, but I can't tell from the commit body — it reads as a bugfix while behaving as a breaking change.There's already an unused
auto_gchar gchar* owned_provider_namein scope; assigning into it would extend the lifetime across the function and seemingly fix the UAF without touching the fallback path:Maybe author considered this and rejected it for a reason I can't see — worth a quick clarification either way.
2. Help / examples may be inconsistent with new behaviour
src/command/cmd_defs.c:2811 still shows
"/ai start [<provider>] [<model>]"with the optional brackets, and src/command/cmd_defs.c:2841 lists"/ai start"as a valid example. With the new mandatory-arg behaviour, that example seems like it would now produce the"Please specify provider name."error. If the removal in #1 stays, these probably want updating; if #1 reverts to keeping the fallback, they're fine as-is.Probably minor
3. Surrogate-pair
elsebranch may be effectively dead codesrc/ai/ai_client.c:325-339. The outer guard reads
in[2] != '\\' || in[3] != 'u'— when false, it would mean the hex digits of\uXXXXstart with\u, which I think can only happen on malformed input. If I'm reading this right, even when the branch is entered,_hex4_to_int(in + 2)returns −1 (sincein[2] == '\\'isn't a hex digit), so the surrogate-pair logic after that point shouldn't actually run. The valid-JSON surrogate-pair path looks like it's handled inside the first branch's nestedelse(the surrogate-range check oncp).If that reading is correct, the outer else can probably be dropped and the case simplified. Not urgent — no observable misbehaviour, just confusing on read.