fix(json): decode Unicode escapes without losing subsequent profile fields

This commit is contained in:
evgeny committed 2026-10-27 06:21:05 +01:00
1 parent 20c095f2f3
commit 1cc60a3079
3 files changed
+56 -1

No files matched your search

+41
View File
@@ -6,14 +6,54 @@
#define SKIP_WS(p, end) while ((p) < (end) && (*(p) == ' ' || *(p) == '\t' || *(p) == '\n' || *(p) == '\r')) (p)++
#define CHECK(p, end) if ((p) >= (end)) return -1
static int json_unicode_unit(const char** text, const char* end, uint32_t* value) {
if (end - *text < 4) return -1;
uint32_t result = 0;
for (int i = 0; i < 4; i++) {
unsigned char c = (unsigned char)(*text)[i]; int digit;
if (c >= '0' && c <= '9') digit = c - '0';
else if (c >= 'a' && c <= 'f') digit = c - 'a' + 10;
else if (c >= 'A' && c <= 'F') digit = c - 'A' + 10;
else return -1;
result = (result << 4) | (uint32_t)digit;
}
*text += 4; *value = result; return 0;
}
static int json_read_string(const char** pp, const char* end, char* out, size_t out_sz) {
const char* p = *pp;
CHECK(p, end);
if (*p != '"') return -1; p++;
size_t n = 0;
while (p < end && *p != '"') {
if ((unsigned char)*p < 32) return -1;
char ch = *p;
if (*p == '\\') { p++; CHECK(p, end);
if (*p == 'u') {
uint32_t code; p++; if (json_unicode_unit(&p, end, &code) < 0 || !code) return -1;
if (code >= 0xd800 && code <= 0xdbff) {
if (end - p < 2 || p[0] != '\\' || p[1] != 'u') return -1;
p += 2; uint32_t low;
if (json_unicode_unit(&p, end, &low) < 0 || low < 0xdc00 || low > 0xdfff) return -1;
code = 0x10000 + ((code - 0xd800) << 10) + low - 0xdc00;
} else if (code >= 0xdc00 && code <= 0xdfff) return -1;
uint8_t utf8[4]; size_t bytes;
if (code < 0x80) { utf8[0] = (uint8_t)code; bytes = 1; }
else if (code < 0x800) {
utf8[0] = (uint8_t)(0xc0 | (code >> 6)); utf8[1] = (uint8_t)(0x80 | (code & 63)); bytes = 2;
} else if (code < 0x10000) {
utf8[0] = (uint8_t)(0xe0 | (code >> 12)); utf8[1] = (uint8_t)(0x80 | ((code >> 6) & 63));
utf8[2] = (uint8_t)(0x80 | (code & 63)); bytes = 3;
} else {
utf8[0] = (uint8_t)(0xf0 | (code >> 18)); utf8[1] = (uint8_t)(0x80 | ((code >> 12) & 63));
utf8[2] = (uint8_t)(0x80 | ((code >> 6) & 63)); utf8[3] = (uint8_t)(0x80 | (code & 63)); bytes = 4;
}
if (out) {
if (n + bytes >= out_sz) return -1;
memcpy(out + n, utf8, bytes); n += bytes;
}
continue;
}
switch (*p) {
case '"': ch = '"'; break;
case '\\': ch = '\\'; break;
@@ -83,6 +123,7 @@ int json_flat_parse(const char* json, json_flat_cb cb, void* arg) {
if (*p == '}') break;
}
if (scratch != local) u_free(scratch);
if (result < 0) DEBUG_WARN(DEBUG_CATEGORY_GENERAL, "json_flat: invalid object/string bytes=%zu", capacity - 1);
return result;
}
+2 -1
View File
@@ -2,7 +2,8 @@
* @file json_flat.h
* @brief Парсер плоского JSON без вложенности: {"key":"value",...}
*
* Только строковые значения и обычные escapes; \uXXXX не поддерживается.
* Только строковые значения,обычные escapes и \uXXXX (surrogate pairs→UTF-8).
* Нулевой Unicode-символ,непарные суррогаты,неверный hex и сырые ASCII-control отклоняются.
* Callback получает полные key/value, действительные только до его возврата.
* parse возвращает 0 при успехе/остановке callback, -1 при ошибке; остановка не проверяет остаток ввода.
* Синтаксис проверяется нестрого: допускаются пропущенные и завершающие запятые;
+13
View File
@@ -4,10 +4,22 @@
#include "../src/chat/member_sync.h"
#include "../lib/debug_config.h"
#include "../lib/mem.h"
#include "../lib/json_flat.h"
#include <assert.h>
#include <string.h>
static int fail_read;
static void unicode(void) {
const char* json = "{\"name\":\"\\u0410\\uD83D\\uDE00\",\"protect\":\"yes\",\"control\":\"\\u0001\"}";
char value[64]; assert(!json_flat_get(json, "name", value, sizeof(value)) && !strcmp(value, "А😀"));
assert(!json_flat_get(json, "protect", value, sizeof(value)) && !strcmp(value, "yes"));
assert(!json_flat_get(json, "control", value, sizeof(value)) && value[0] == 1 && !value[1]);
const char* invalid[] = {"{\"a\":\"\\u0000\"}", "{\"a\":\"\\uD800\"}", "{\"a\":\"\\uDC00\"}",
"{\"a\":\"\\uD800\\u0001\"}", "{\"a\":\"\\uXX12\"}", "{\"a\":\"\\u123\"}", "{\"a\":\"raw\nline\"}"};
for (unsigned i = 0; i < sizeof(invalid) / sizeof(invalid[0]); i++) assert(json_flat_parse(invalid[i], NULL, NULL) < 0);
puts("PASS profile Unicode reader:UTF-8,surrogate pair,control,following policy field;invalid Unicode/NUL/raw control refused");
}
extern int __real_sqlite3_finalize(sqlite3_stmt*);
int __wrap_sqlite3_finalize(sqlite3_stmt* st) {
const char* prefix = "SELECT userinfo,update_sig,update_ts";
@@ -18,6 +30,7 @@ int __wrap_sqlite3_finalize(sqlite3_stmt* st) {
int main(void) {
debug_config_init(); debug_set_level(DEBUG_LEVEL_ERROR); size_t baseline = u_get_allocated_count();
unicode();
utun_instance_set_tun_init_enabled(0); struct UASYNC* ua = uasync_create(); assert(ua);
struct UTUN_INSTANCE* inst = utun_instance_create_from_str(ua,
"[global]\nmy_node_name=profile test\n[ntp]\nenabled=no\n[chatserver]\ngroup_autoconnect=0\nstorage_autoload=0\n");