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 39646 : static const char *skip_ws(const char *p) {
10 39646 : while (*p && isspace((unsigned char)*p)) p++;
11 39646 : 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 5393 : static const char *skip_value(const char *p) {
19 5393 : p = skip_ws(p);
20 5393 : if (!*p) return NULL;
21 :
22 5393 : if (*p == '"') {
23 : /* String: advance past closing quote, handling escapes */
24 2655 : p++;
25 42604 : while (*p && *p != '"') {
26 39949 : if (*p == '\\') { p++; if (!*p) return NULL; }
27 39949 : p++;
28 : }
29 2655 : return *p == '"' ? p + 1 : NULL;
30 : }
31 2738 : if (*p == '{') {
32 : /* Object: match braces */
33 2018 : int depth = 1; p++;
34 18954 : while (*p && depth > 0) {
35 16936 : if (*p == '{') depth++;
36 16936 : else if (*p == '}') depth--;
37 14918 : else if (*p == '"') {
38 8468 : p++;
39 90276 : while (*p && *p != '"') {
40 81808 : if (*p == '\\') { p++; if (!*p) return NULL; }
41 81808 : p++;
42 : }
43 8468 : if (!*p) return NULL;
44 : }
45 16936 : p++;
46 : }
47 2018 : return depth == 0 ? p : NULL;
48 : }
49 720 : if (*p == '[') {
50 : /* Array: match brackets */
51 675 : int depth = 1; p++;
52 39071 : while (*p && depth > 0) {
53 38396 : if (*p == '[') depth++;
54 38396 : else if (*p == ']') depth--;
55 37721 : else if (*p == '"') {
56 15550 : p++;
57 174060 : while (*p && *p != '"') {
58 158510 : if (*p == '\\') { p++; if (!*p) return NULL; }
59 158510 : p++;
60 : }
61 15550 : if (!*p) return NULL;
62 : }
63 38396 : p++;
64 : }
65 675 : return depth == 0 ? p : NULL;
66 : }
67 : /* Number, true, false, null: advance past alphanumeric + signs */
68 148 : while (*p && (isalnum((unsigned char)*p) || *p == '.' ||
69 45 : *p == '+' || *p == '-'))
70 103 : p++;
71 45 : 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 3547 : static const char *find_key(const char *p, const char *key) {
80 3547 : size_t klen = strlen(key);
81 3547 : p = skip_ws(p);
82 :
83 6922 : while (*p && *p != '}') {
84 : /* Expect a quoted key */
85 6889 : if (*p != '"') return NULL;
86 6889 : p++;
87 6889 : const char *ks = p;
88 38204 : while (*p && *p != '"') {
89 31315 : if (*p == '\\') { p++; if (!*p) return NULL; }
90 31315 : p++;
91 : }
92 6889 : if (!*p) return NULL;
93 6889 : size_t found_len = (size_t)(p - ks);
94 6889 : int match = (found_len == klen && memcmp(ks, key, klen) == 0);
95 6889 : p++; /* past closing quote */
96 :
97 6889 : p = skip_ws(p);
98 6889 : if (*p != ':') return NULL;
99 6889 : p++;
100 6889 : p = skip_ws(p);
101 :
102 6889 : if (match) return p;
103 :
104 : /* Skip the value */
105 3375 : p = skip_value(p);
106 3375 : if (!p) return NULL;
107 3375 : p = skip_ws(p);
108 3375 : if (*p == ',') p++;
109 3375 : p = skip_ws(p);
110 : }
111 33 : 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 3835 : static char *unescape(const char *src, size_t len) {
119 3835 : char *buf = malloc(len + 1);
120 3835 : if (!buf) return NULL;
121 3835 : size_t out = 0;
122 234369 : for (size_t i = 0; i < len; i++) {
123 230534 : if (src[i] == '\\' && i + 1 < len) {
124 0 : i++;
125 0 : switch (src[i]) {
126 0 : case '"': buf[out++] = '"'; break;
127 0 : case '\\': buf[out++] = '\\'; break;
128 0 : case '/': buf[out++] = '/'; break;
129 0 : case 'n': buf[out++] = '\n'; break;
130 0 : case 'r': buf[out++] = '\r'; break;
131 0 : case 't': buf[out++] = '\t'; break;
132 0 : case 'b': buf[out++] = '\b'; break;
133 0 : case 'f': buf[out++] = '\f'; break;
134 0 : case 'u':
135 : /* \uXXXX — emit '?' for non-ASCII, decode ASCII range */
136 0 : if (i + 4 < len) {
137 0 : char hex[5] = {src[i+1], src[i+2], src[i+3], src[i+4], 0};
138 0 : unsigned long cp = strtoul(hex, NULL, 16);
139 0 : if (cp < 0x80)
140 0 : buf[out++] = (char)cp;
141 : else
142 0 : buf[out++] = '?'; /* non-ASCII BMP placeholder */
143 0 : i += 4;
144 : }
145 0 : break;
146 0 : default:
147 0 : buf[out++] = src[i];
148 0 : break;
149 : }
150 : } else {
151 230534 : buf[out++] = src[i];
152 : }
153 : }
154 3835 : buf[out] = '\0';
155 3835 : return buf;
156 : }
157 :
158 : /* ── Public API ─────────────────────────────────────────────────────── */
159 :
160 2881 : char *json_get_string(const char *json, const char *key) {
161 2881 : if (!json || !key) return NULL;
162 :
163 2881 : const char *p = skip_ws(json);
164 2881 : if (*p != '{') return NULL;
165 2881 : p++;
166 :
167 2881 : const char *val = find_key(p, key);
168 2881 : if (!val || *val != '"') return NULL;
169 :
170 2863 : val++; /* past opening quote */
171 2863 : const char *end = val;
172 227906 : while (*end && *end != '"') {
173 225043 : if (*end == '\\') { end++; if (!*end) return NULL; }
174 225043 : end++;
175 : }
176 2863 : if (!*end) return NULL;
177 :
178 2863 : return unescape(val, (size_t)(end - val));
179 : }
180 :
181 0 : int json_get_int(const char *json, const char *key, int *out) {
182 0 : if (!json || !key || !out) return -1;
183 :
184 0 : const char *p = skip_ws(json);
185 0 : if (*p != '{') return -1;
186 0 : p++;
187 :
188 0 : const char *val = find_key(p, key);
189 0 : if (!val) return -1;
190 :
191 : /* Accept number or quoted number */
192 0 : if (*val == '"') {
193 0 : val++;
194 : char *end;
195 0 : long v = strtol(val, &end, 10);
196 0 : if (end == val) return -1;
197 0 : *out = (int)v;
198 0 : return 0;
199 : }
200 0 : if (*val == '-' || isdigit((unsigned char)*val)) {
201 : char *end;
202 0 : long v = strtol(val, &end, 10);
203 0 : if (end == val) return -1;
204 0 : *out = (int)v;
205 0 : return 0;
206 : }
207 0 : return -1;
208 : }
209 :
210 601 : int json_get_string_array(const char *json, const char *key,
211 : char ***out, int *count_out) {
212 601 : if (!json || !key || !out || !count_out) return -1;
213 601 : *out = NULL;
214 601 : *count_out = 0;
215 :
216 601 : const char *p = skip_ws(json);
217 601 : if (*p != '{') return -1;
218 601 : p++;
219 :
220 601 : const char *val = find_key(p, key);
221 601 : if (!val || *val != '[') return -1;
222 :
223 601 : val++; /* past '[' */
224 601 : val = skip_ws(val);
225 :
226 601 : int cap = 8;
227 601 : char **arr = malloc((size_t)cap * sizeof(char *));
228 601 : if (!arr) return -1;
229 601 : int count = 0;
230 :
231 1573 : while (*val && *val != ']') {
232 972 : if (*val != '"') { val = skip_ws(val); if (*val == ']') break; goto fail; }
233 972 : val++; /* past opening quote */
234 972 : const char *end = val;
235 6463 : while (*end && *end != '"') {
236 5491 : if (*end == '\\') { end++; if (!*end) goto fail; }
237 5491 : end++;
238 : }
239 972 : if (!*end) goto fail;
240 :
241 972 : char *s = unescape(val, (size_t)(end - val));
242 972 : if (!s) goto fail;
243 :
244 972 : if (count >= cap) {
245 0 : cap *= 2;
246 0 : char **tmp = realloc(arr, (size_t)cap * sizeof(char *));
247 0 : if (!tmp) { free(s); goto fail; }
248 0 : arr = tmp;
249 : }
250 972 : arr[count++] = s;
251 :
252 972 : val = end + 1; /* past closing quote */
253 972 : val = skip_ws(val);
254 972 : if (*val == ',') val++;
255 972 : val = skip_ws(val);
256 : }
257 :
258 601 : *out = arr;
259 601 : *count_out = count;
260 601 : 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 65 : int json_foreach_object(const char *json, const char *key,
269 : JsonObjectCb cb, void *ctx) {
270 65 : if (!json || !key || !cb) return -1;
271 :
272 65 : const char *p = skip_ws(json);
273 65 : if (*p != '{') return -1;
274 65 : p++;
275 :
276 65 : const char *val = find_key(p, key);
277 65 : if (!val || *val != '[') return -1;
278 :
279 50 : val++; /* past '[' */
280 50 : val = skip_ws(val);
281 50 : int index = 0;
282 :
283 2068 : while (*val && *val != ']') {
284 2018 : if (*val != '{') { val = skip_ws(val); if (*val == ']') break; return -1; }
285 :
286 : /* Find the extent of this object */
287 2018 : const char *obj_start = val;
288 2018 : const char *obj_end = skip_value(val);
289 2018 : if (!obj_end) return -1;
290 :
291 : /* Create a NUL-terminated copy for the callback */
292 2018 : size_t obj_len = (size_t)(obj_end - obj_start);
293 2018 : char *obj_copy = malloc(obj_len + 1);
294 2018 : if (!obj_copy) return -1;
295 2018 : memcpy(obj_copy, obj_start, obj_len);
296 2018 : obj_copy[obj_len] = '\0';
297 :
298 2018 : cb(obj_copy, index, ctx);
299 2018 : free(obj_copy);
300 2018 : index++;
301 :
302 2018 : val = skip_ws(obj_end);
303 2018 : if (*val == ',') val++;
304 2018 : val = skip_ws(val);
305 : }
306 :
307 50 : return index;
308 : }
|