Line data Source code
1 : #include "json_util.h"
2 : #include <stdlib.h>
3 : #include <string.h>
4 : #include <ctype.h>
5 :
6 : /* ── Internal helpers ───────────────────────────────────────────────── */
7 :
8 : /** Skip whitespace, return pointer to first non-ws char. */
9 41343 : static const char *skip_ws(const char *p) {
10 41421 : while (*p && isspace((unsigned char)*p)) p++;
11 41343 : return p;
12 : }
13 :
14 : /**
15 : * Skip a JSON value starting at *p (string, number, object, array,
16 : * true/false/null). Returns pointer past the value, or NULL on error.
17 : */
18 5579 : static const char *skip_value(const char *p) {
19 5579 : p = skip_ws(p);
20 5579 : if (!*p) return NULL;
21 :
22 5579 : if (*p == '"') {
23 : /* String: advance past closing quote, handling escapes */
24 2728 : p++;
25 43264 : while (*p && *p != '"') {
26 40536 : if (*p == '\\') { p++; if (!*p) return NULL; }
27 40536 : p++;
28 : }
29 2728 : return *p == '"' ? p + 1 : NULL;
30 : }
31 2851 : if (*p == '{') {
32 : /* Object: match braces */
33 2073 : int depth = 1; p++;
34 19438 : while (*p && depth > 0) {
35 17365 : if (*p == '{') depth++;
36 17364 : else if (*p == '}') depth--;
37 15290 : else if (*p == '"') {
38 8673 : p++;
39 91524 : while (*p && *p != '"') {
40 82851 : if (*p == '\\') { p++; if (!*p) return NULL; }
41 82851 : p++;
42 : }
43 8673 : if (!*p) return NULL;
44 : }
45 17365 : p++;
46 : }
47 2073 : return depth == 0 ? p : NULL;
48 : }
49 778 : if (*p == '[') {
50 : /* Array: match brackets */
51 713 : int depth = 1; p++;
52 39550 : while (*p && depth > 0) {
53 38837 : if (*p == '[') depth++;
54 38834 : else if (*p == ']') depth--;
55 38118 : else if (*p == '"') {
56 15720 : p++;
57 175325 : while (*p && *p != '"') {
58 159605 : if (*p == '\\') { p++; if (!*p) return NULL; }
59 159605 : p++;
60 : }
61 15720 : if (!*p) return NULL;
62 : }
63 38837 : p++;
64 : }
65 713 : return depth == 0 ? p : NULL;
66 : }
67 : /* Number, true, false, null: advance past alphanumeric + signs */
68 188 : while (*p && (isalnum((unsigned char)*p) || *p == '.' ||
69 65 : *p == '+' || *p == '-'))
70 123 : p++;
71 65 : return p;
72 : }
73 :
74 : /**
75 : * Find a key at the current object nesting level.
76 : * p must point inside a '{' ... '}' block (past the opening '{').
77 : * Returns pointer to the value (after the ':' and whitespace), or NULL.
78 : */
79 3740 : static const char *find_key(const char *p, const char *key) {
80 3740 : size_t klen = strlen(key);
81 3740 : p = skip_ws(p);
82 :
83 7247 : while (*p && *p != '}') {
84 : /* Expect a quoted key */
85 7195 : if (*p != '"') return NULL;
86 7195 : p++;
87 7195 : const char *ks = p;
88 40206 : while (*p && *p != '"') {
89 33011 : if (*p == '\\') { p++; if (!*p) return NULL; }
90 33011 : p++;
91 : }
92 7195 : if (!*p) return NULL;
93 7195 : size_t found_len = (size_t)(p - ks);
94 7195 : int match = (found_len == klen && memcmp(ks, key, klen) == 0);
95 7195 : p++; /* past closing quote */
96 :
97 7195 : p = skip_ws(p);
98 7195 : if (*p != ':') return NULL;
99 7195 : p++;
100 7195 : p = skip_ws(p);
101 :
102 7195 : if (match) return p;
103 :
104 : /* Skip the value */
105 3507 : p = skip_value(p);
106 3507 : if (!p) return NULL;
107 3507 : p = skip_ws(p);
108 3507 : if (*p == ',') p++;
109 3507 : p = skip_ws(p);
110 : }
111 52 : return NULL;
112 : }
113 :
114 : /**
115 : * Unescape a JSON string from src[0..len-1] into a heap-allocated buffer.
116 : * Handles: \\, \", \/, \n, \r, \t, \b, \f, \uXXXX (BMP only, as ASCII ?).
117 : */
118 4008 : static char *unescape(const char *src, size_t len) {
119 4008 : char *buf = malloc(len + 1);
120 4008 : if (!buf) return NULL;
121 4008 : size_t out = 0;
122 237740 : for (size_t i = 0; i < len; i++) {
123 233732 : if (src[i] == '\\' && i + 1 < len) {
124 14 : i++;
125 14 : switch (src[i]) {
126 2 : case '"': buf[out++] = '"'; break;
127 1 : case '\\': buf[out++] = '\\'; break;
128 1 : case '/': buf[out++] = '/'; break;
129 2 : case 'n': buf[out++] = '\n'; break;
130 1 : case 'r': buf[out++] = '\r'; break;
131 2 : case 't': buf[out++] = '\t'; break;
132 1 : case 'b': buf[out++] = '\b'; break;
133 1 : case 'f': buf[out++] = '\f'; break;
134 2 : case 'u':
135 : /* \uXXXX — emit '?' for non-ASCII, decode ASCII range */
136 2 : if (i + 4 < len) {
137 2 : char hex[5] = {src[i+1], src[i+2], src[i+3], src[i+4], 0};
138 2 : unsigned long cp = strtoul(hex, NULL, 16);
139 2 : if (cp < 0x80)
140 1 : buf[out++] = (char)cp;
141 : else
142 1 : buf[out++] = '?'; /* non-ASCII BMP placeholder */
143 2 : i += 4;
144 : }
145 2 : break;
146 1 : default:
147 1 : buf[out++] = src[i];
148 1 : break;
149 : }
150 : } else {
151 233718 : buf[out++] = src[i];
152 : }
153 : }
154 4008 : buf[out] = '\0';
155 4008 : return buf;
156 : }
157 :
158 : /* ── Public API ─────────────────────────────────────────────────────── */
159 :
160 3024 : char *json_get_string(const char *json, const char *key) {
161 3024 : if (!json || !key) return NULL;
162 :
163 3022 : const char *p = skip_ws(json);
164 3022 : if (*p != '{') return NULL;
165 3022 : p++;
166 :
167 3022 : const char *val = find_key(p, key);
168 3022 : if (!val || *val != '"') return NULL;
169 :
170 2988 : val++; /* past opening quote */
171 2988 : const char *end = val;
172 230955 : while (*end && *end != '"') {
173 227967 : if (*end == '\\') { end++; if (!*end) return NULL; }
174 227967 : end++;
175 : }
176 2988 : if (!*end) return NULL;
177 :
178 2988 : return unescape(val, (size_t)(end - val));
179 : }
180 :
181 6 : int json_get_int(const char *json, const char *key, int *out) {
182 6 : if (!json || !key || !out) return -1;
183 :
184 5 : const char *p = skip_ws(json);
185 5 : if (*p != '{') return -1;
186 5 : p++;
187 :
188 5 : const char *val = find_key(p, key);
189 5 : if (!val) return -1;
190 :
191 : /* Accept number or quoted number */
192 4 : if (*val == '"') {
193 1 : val++;
194 : char *end;
195 1 : long v = strtol(val, &end, 10);
196 1 : if (end == val) return -1;
197 1 : *out = (int)v;
198 1 : return 0;
199 : }
200 3 : if (*val == '-' || isdigit((unsigned char)*val)) {
201 : char *end;
202 2 : long v = strtol(val, &end, 10);
203 2 : if (end == val) return -1;
204 2 : *out = (int)v;
205 2 : return 0;
206 : }
207 1 : return -1;
208 : }
209 :
210 618 : int json_get_string_array(const char *json, const char *key,
211 : char ***out, int *count_out) {
212 618 : if (!json || !key || !out || !count_out) return -1;
213 618 : *out = NULL;
214 618 : *count_out = 0;
215 :
216 618 : const char *p = skip_ws(json);
217 618 : if (*p != '{') return -1;
218 618 : p++;
219 :
220 618 : const char *val = find_key(p, key);
221 618 : if (!val || *val != '[') return -1;
222 :
223 617 : val++; /* past '[' */
224 617 : val = skip_ws(val);
225 :
226 617 : int cap = 8;
227 617 : char **arr = malloc((size_t)cap * sizeof(char *));
228 617 : if (!arr) return -1;
229 617 : int count = 0;
230 :
231 1637 : while (*val && *val != ']') {
232 1020 : if (*val != '"') { val = skip_ws(val); if (*val == ']') break; goto fail; }
233 1020 : val++; /* past opening quote */
234 1020 : const char *end = val;
235 6793 : while (*end && *end != '"') {
236 5773 : if (*end == '\\') { end++; if (!*end) goto fail; }
237 5773 : end++;
238 : }
239 1020 : if (!*end) goto fail;
240 :
241 1020 : char *s = unescape(val, (size_t)(end - val));
242 1020 : if (!s) goto fail;
243 :
244 1020 : if (count >= cap) {
245 2 : cap *= 2;
246 2 : char **tmp = realloc(arr, (size_t)cap * sizeof(char *));
247 2 : if (!tmp) { free(s); goto fail; }
248 2 : arr = tmp;
249 : }
250 1020 : arr[count++] = s;
251 :
252 1020 : val = end + 1; /* past closing quote */
253 1020 : val = skip_ws(val);
254 1020 : if (*val == ',') val++;
255 1020 : val = skip_ws(val);
256 : }
257 :
258 617 : *out = arr;
259 617 : *count_out = count;
260 617 : return 0;
261 :
262 0 : fail:
263 0 : for (int i = 0; i < count; i++) free(arr[i]);
264 0 : free(arr);
265 0 : return -1;
266 : }
267 :
268 95 : int json_foreach_object(const char *json, const char *key,
269 : JsonObjectCb cb, void *ctx) {
270 95 : if (!json || !key || !cb) return -1;
271 :
272 95 : const char *p = skip_ws(json);
273 95 : if (*p != '{') return -1;
274 95 : p++;
275 :
276 95 : const char *val = find_key(p, key);
277 95 : if (!val || *val != '[') return -1;
278 :
279 79 : val++; /* past '[' */
280 79 : val = skip_ws(val);
281 79 : int index = 0;
282 :
283 2151 : while (*val && *val != ']') {
284 2072 : if (*val != '{') { val = skip_ws(val); if (*val == ']') break; return -1; }
285 :
286 : /* Find the extent of this object */
287 2072 : const char *obj_start = val;
288 2072 : const char *obj_end = skip_value(val);
289 2072 : if (!obj_end) return -1;
290 :
291 : /* Create a NUL-terminated copy for the callback */
292 2072 : size_t obj_len = (size_t)(obj_end - obj_start);
293 2072 : char *obj_copy = malloc(obj_len + 1);
294 2072 : if (!obj_copy) return -1;
295 2072 : memcpy(obj_copy, obj_start, obj_len);
296 2072 : obj_copy[obj_len] = '\0';
297 :
298 2072 : cb(obj_copy, index, ctx);
299 2072 : free(obj_copy);
300 2072 : index++;
301 :
302 2072 : val = skip_ws(obj_end);
303 2072 : if (*val == ',') val++;
304 2072 : val = skip_ws(val);
305 : }
306 :
307 79 : return index;
308 : }
|