| 219 | if (h < 0) return false; |
| 220 | cp = (cp << 4) | (uint32_t)h; |
| 221 | } |
| 222 | *p += 6; |
| 223 | *out = cp; |
| 224 | return true; |
| 225 | } |
| 226 | |
| 227 | static bool json_string(const char **p, char **out) { |
| 228 | /* Always define *out. Every failure path below returns false without |
| 229 | * producing a string, and several callers reparse in place with |
| 230 | * `free(x); json_string(&p, &x)` (e.g. duplicate JSON keys, the "model" |
| 231 | * field). Without this, a non-string or malformed value leaves *out |
| 232 | * holding the just-freed pointer, which a later cleanup frees again -- |
| 233 | * a double-free. Nulling on entry closes that whole class at the root. */ |
| 234 | *out = NULL; |
| 235 | json_ws(p); |
| 236 | if (**p != '"') return false; |
| 237 | (*p)++; |
| 238 | buf b = {0}; |
| 239 | while (**p && **p != '"') { |
| 240 | unsigned char c = (unsigned char)*(*p)++; |
| 241 | if (c != '\\') { |
| 242 | buf_putc(&b, (char)c); |
| 243 | continue; |
| 244 | } |
| 245 | c = (unsigned char)*(*p)++; |
| 246 | switch (c) { |
| 247 | case '"': buf_putc(&b, '"'); break; |
| 248 | case '\\': buf_putc(&b, '\\'); break; |
| 249 | case '/': buf_putc(&b, '/'); break; |
| 250 | case 'b': buf_putc(&b, '\b'); break; |
| 251 | case 'f': buf_putc(&b, '\f'); break; |
| 252 | case 'n': buf_putc(&b, '\n'); break; |
| 253 | case 'r': buf_putc(&b, '\r'); break; |
| 254 | case 't': buf_putc(&b, '\t'); break; |
| 255 | case 'u': { |
| 256 | *p -= 2; |
| 257 | uint32_t cp = 0, lo = 0; |
| 258 | if (!json_u16(p, &cp)) goto fail; |
| 259 | if (cp >= 0xd800 && cp <= 0xdbff) { |
| 260 | const char *low_start = *p; |
| 261 | if (json_u16(p, &lo) && lo >= 0xdc00 && lo <= 0xdfff) { |
| 262 | cp = 0x10000u + ((cp - 0xd800u) << 10) + (lo - 0xdc00u); |
| 263 | } else { |
| 264 | *p = low_start; |
| 265 | cp = 0xfffd; |
| 266 | } |
| 267 | } |
| 268 | utf8_put(&b, cp); |
| 269 | break; |
| 270 | } |
| 271 | default: |
| 272 | goto fail; |