fix(json): decode Unicode escapes without losing subsequent profile fields
This commit is contained in:
1 parent
20c095f2f3
commit
1cc60a3079
3 files changed
+56
-1
No files matched your search
@@ -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
@@ -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 при ошибке; остановка не проверяет остаток ввода.
|
||||
* Синтаксис проверяется нестрого: допускаются пропущенные и завершающие запятые;
|
||||
|
||||
@@ -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");
|
||||
|
||||
Reference in new issue
Block a user