146 lines
5.8 KiB
C
146 lines
5.8 KiB
C
#include "json_flat.h"
|
|
#include "mem.h"
|
|
#include "debug_config.h"
|
|
#include <string.h>
|
|
|
|
#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;
|
|
case '/': ch = '/'; break;
|
|
case 'n': ch = '\n'; break;
|
|
case 'r': ch = '\r'; break;
|
|
case 't': ch = '\t'; break;
|
|
case 'b': ch = '\b'; break;
|
|
case 'f': ch = '\f'; break;
|
|
default: return -1;
|
|
}
|
|
}
|
|
if (out && n < out_sz - 1) out[n++] = ch;
|
|
p++;
|
|
}
|
|
CHECK(p, end); /* closing quote */
|
|
p++;
|
|
if (out && n < out_sz) out[n] = '\0';
|
|
else if (out && out_sz > 0) out[out_sz - 1] = '\0';
|
|
*pp = p;
|
|
return 0;
|
|
}
|
|
|
|
static int json_parse_pair(const char** pp, const char* end, char* scratch, size_t capacity,
|
|
json_flat_cb cb, void* arg) {
|
|
const char* p = *pp;
|
|
SKIP_WS(p, end); CHECK(p, end);
|
|
if (*p == '}') { *pp = p; return 0; }
|
|
|
|
char* key = scratch;
|
|
if (json_read_string(&p, end, key, capacity) != 0) return -1;
|
|
size_t key_size = strlen(key) + 1;
|
|
char* val = scratch + key_size;
|
|
SKIP_WS(p, end); CHECK(p, end);
|
|
if (*p != ':') return -1; p++;
|
|
SKIP_WS(p, end); CHECK(p, end);
|
|
if (json_read_string(&p, end, val, capacity - key_size) != 0) return -1;
|
|
|
|
if (cb) { int rc = cb(key, val, arg); if (rc != 0) { *pp = p; return rc < 0 ? -1 : 1; } }
|
|
|
|
SKIP_WS(p, end); CHECK(p, end);
|
|
if (*p == ',') p++;
|
|
|
|
*pp = p;
|
|
return 0;
|
|
}
|
|
|
|
int json_flat_parse(const char* json, json_flat_cb cb, void* arg) {
|
|
if (!json) return -1;
|
|
const char* p = json, *end = json + strlen(json);
|
|
SKIP_WS(p, end); CHECK(p, end);
|
|
if (*p != '{') return -1; p++;
|
|
|
|
/* Decoded key + value + two NULs fit within the encoded object size. */
|
|
size_t capacity = strlen(json) + 1;
|
|
if (capacity > UINT32_MAX) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "json_flat: input too large"); return -1; }
|
|
char local[384];
|
|
char* scratch = capacity <= sizeof(local) ? local : u_malloc(capacity);
|
|
if (!scratch) { DEBUG_ERROR(DEBUG_CATEGORY_GENERAL, "json_flat: allocation failed bytes=%zu", capacity); return -1; }
|
|
int result = 0;
|
|
for (;;) {
|
|
int rc = json_parse_pair(&p, end, scratch, capacity, cb, arg);
|
|
if (rc < 0) { result = -1; break; }
|
|
if (rc > 0) break;
|
|
SKIP_WS(p, end);
|
|
if (p >= end) { result = -1; break; }
|
|
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;
|
|
}
|
|
|
|
struct get_ctx { const char* key; char* out; size_t sz; int found; };
|
|
|
|
static int get_cb(const char* k, const char* v, void* arg) {
|
|
struct get_ctx* g = (struct get_ctx*)arg;
|
|
if (strcmp(k, g->key) == 0) { if (g->out) { size_t vl = strlen(v);
|
|
size_t cp = vl < g->sz - 1 ? vl : g->sz - 1; memcpy(g->out, v, cp); g->out[cp] = '\0'; }
|
|
g->found = 1; return 1; }
|
|
return 0;
|
|
}
|
|
|
|
int json_flat_get(const char* json, const char* key, char* value_out, size_t value_sz) {
|
|
if (!key || (value_out && !value_sz)) return -1;
|
|
struct get_ctx g = { key, value_out, value_sz, 0 };
|
|
if (json_flat_parse(json, get_cb, &g) != 0) return -1;
|
|
return g.found ? 0 : -1;
|
|
}
|