/src/json-c/json_pointer.c
Line | Count | Source (jump to first uncovered line) |
1 | | /* |
2 | | * Copyright (c) 2016 Alexandru Ardelean. |
3 | | * |
4 | | * This is free software; you can redistribute it and/or modify |
5 | | * it under the terms of the MIT license. See COPYING for details. |
6 | | * |
7 | | */ |
8 | | |
9 | | #include "config.h" |
10 | | |
11 | | #include "strerror_override.h" |
12 | | |
13 | | #include <stdarg.h> |
14 | | #include <stdio.h> |
15 | | #include <stdlib.h> |
16 | | #include <string.h> |
17 | | |
18 | | #include "json_object_private.h" |
19 | | #include "json_pointer.h" |
20 | | #include "json_pointer_private.h" |
21 | | #include "strdup_compat.h" |
22 | | #include "vasprintf_compat.h" |
23 | | |
24 | | /* Avoid ctype.h and locale overhead */ |
25 | 483 | #define is_plain_digit(c) ((c) >= '0' && (c) <= '9') |
26 | | |
27 | | /** |
28 | | * JavaScript Object Notation (JSON) Pointer |
29 | | * RFC 6901 - https://tools.ietf.org/html/rfc6901 |
30 | | */ |
31 | | |
32 | | static void string_replace_all_occurrences_with_char(char *s, const char *occur, char repl_char) |
33 | 380 | { |
34 | 380 | size_t slen = strlen(s); |
35 | 380 | size_t skip = strlen(occur) - 1; /* length of the occurrence, minus the char we're replacing */ |
36 | 380 | char *p = s; |
37 | 390 | while ((p = strstr(p, occur))) |
38 | 10 | { |
39 | 10 | *p = repl_char; |
40 | 10 | p++; |
41 | 10 | slen -= skip; |
42 | 10 | memmove(p, (p + skip), slen - (p - s) + 1); /* includes null char too */ |
43 | 10 | } |
44 | 380 | } |
45 | | |
46 | | static int is_valid_index(const char *path, size_t *idx) |
47 | 383 | { |
48 | 383 | size_t i, len = strlen(path); |
49 | | /* this code-path optimizes a bit, for when we reference the 0-9 index range |
50 | | * in a JSON array and because leading zeros not allowed |
51 | | */ |
52 | 383 | if (len == 1) |
53 | 103 | { |
54 | 103 | if (is_plain_digit(path[0])) |
55 | 34 | { |
56 | 34 | *idx = (path[0] - '0'); |
57 | 34 | return 1; |
58 | 34 | } |
59 | 69 | errno = EINVAL; |
60 | 69 | return 0; |
61 | 103 | } |
62 | | /* leading zeros not allowed per RFC */ |
63 | 280 | if (path[0] == '0') |
64 | 2 | { |
65 | 2 | errno = EINVAL; |
66 | 2 | return 0; |
67 | 2 | } |
68 | | /* RFC states base-10 decimals */ |
69 | 608 | for (i = 0; i < len; i++) |
70 | 380 | { |
71 | 380 | if (!is_plain_digit(path[i])) |
72 | 50 | { |
73 | 50 | errno = EINVAL; |
74 | 50 | return 0; |
75 | 50 | } |
76 | 380 | } |
77 | | |
78 | | // We know it's all digits, so the only error case here is overflow, |
79 | | // but ULLONG_MAX will be longer than any array length so that's ok. |
80 | 228 | *idx = strtoull(path, NULL, 10); |
81 | | |
82 | 228 | return 1; |
83 | 278 | } |
84 | | |
85 | | static int json_pointer_get_single_path(struct json_object *obj, char *path, |
86 | | struct json_object **value, size_t *idx) |
87 | 439 | { |
88 | 439 | if (json_object_is_type(obj, json_type_array)) |
89 | 249 | { |
90 | 249 | if (!is_valid_index(path, idx)) |
91 | 82 | return -1; |
92 | 167 | if (*idx >= json_object_array_length(obj)) |
93 | 56 | { |
94 | 56 | errno = ENOENT; |
95 | 56 | return -1; |
96 | 56 | } |
97 | | |
98 | 111 | obj = json_object_array_get_idx(obj, *idx); |
99 | 111 | if (obj) |
100 | 108 | { |
101 | 108 | if (value) |
102 | 108 | *value = obj; |
103 | 108 | return 0; |
104 | 108 | } |
105 | | /* Entry not found */ |
106 | 3 | errno = ENOENT; |
107 | 3 | return -1; |
108 | 111 | } |
109 | | |
110 | | /* RFC states that we first must eval all ~1 then all ~0 */ |
111 | 190 | string_replace_all_occurrences_with_char(path, "~1", '/'); |
112 | 190 | string_replace_all_occurrences_with_char(path, "~0", '~'); |
113 | | |
114 | 190 | if (!json_object_object_get_ex(obj, path, value)) |
115 | 100 | { |
116 | 100 | errno = ENOENT; |
117 | 100 | return -1; |
118 | 100 | } |
119 | | |
120 | 90 | return 0; |
121 | 190 | } |
122 | | |
123 | | static int json_object_array_put_idx_cb(struct json_object *parent, size_t idx, |
124 | | struct json_object *value, void *priv) |
125 | 95 | { |
126 | 95 | return json_object_array_put_idx(parent, idx, value); |
127 | 95 | } |
128 | | |
129 | | static int json_pointer_set_single_path(struct json_object *parent, const char *path, |
130 | | struct json_object *value, |
131 | | json_pointer_array_set_cb array_set_cb, void *priv) |
132 | 368 | { |
133 | 368 | if (json_object_is_type(parent, json_type_array)) |
134 | 171 | { |
135 | 171 | size_t idx; |
136 | | /* RFC (Chapter 4) states that '-' may be used to add new elements to an array */ |
137 | 171 | if (path[0] == '-' && path[1] == '\0') |
138 | 37 | return json_object_array_add(parent, value); |
139 | 134 | if (!is_valid_index(path, &idx)) |
140 | 39 | return -1; |
141 | 95 | return array_set_cb(parent, idx, value, priv); |
142 | 134 | } |
143 | | |
144 | | /* path replacements should have been done in json_pointer_get_single_path(), |
145 | | * and we should still be good here |
146 | | */ |
147 | 197 | if (json_object_is_type(parent, json_type_object)) |
148 | 51 | return json_object_object_add(parent, path, value); |
149 | | |
150 | | /* Getting here means that we tried to "dereference" a primitive JSON type |
151 | | * (like string, int, bool).i.e. add a sub-object to it |
152 | | */ |
153 | 146 | errno = ENOENT; |
154 | 146 | return -1; |
155 | 197 | } |
156 | | |
157 | | static int json_pointer_result_get_recursive(struct json_object *obj, char *path, |
158 | | struct json_pointer_get_result *res) |
159 | 499 | { |
160 | 499 | struct json_object *parent_obj = obj; |
161 | 499 | size_t idx = 0; |
162 | 499 | char *endp; |
163 | 499 | int rc; |
164 | | |
165 | | /* All paths (on each recursion level must have a leading '/' */ |
166 | 499 | if (path[0] != '/') |
167 | 60 | { |
168 | 60 | errno = EINVAL; |
169 | 60 | return -1; |
170 | 60 | } |
171 | 439 | path++; |
172 | | |
173 | 439 | endp = strchr(path, '/'); |
174 | 439 | if (endp) |
175 | 144 | *endp = '\0'; |
176 | | |
177 | | /* If we err-ed here, return here */ |
178 | 439 | if ((rc = json_pointer_get_single_path(obj, path, &obj, &idx))) |
179 | 241 | return rc; |
180 | | |
181 | 198 | if (endp) |
182 | 116 | { |
183 | | /* Put the slash back, so that the sanity check passes on next recursion level */ |
184 | 116 | *endp = '/'; |
185 | 116 | return json_pointer_result_get_recursive(obj, endp, res); |
186 | 116 | } |
187 | | |
188 | | /* We should be at the end of the recursion here */ |
189 | 82 | if (res) { |
190 | 82 | res->parent = parent_obj; |
191 | 82 | res->obj = obj; |
192 | 82 | if (json_object_is_type(res->parent, json_type_array)) |
193 | 59 | res->index_in_parent = idx; |
194 | 23 | else |
195 | 23 | res->key_in_parent = path; |
196 | 82 | } |
197 | | |
198 | 82 | return 0; |
199 | 198 | } |
200 | | |
201 | | static int json_pointer_object_get_recursive(struct json_object *obj, char *path, |
202 | | struct json_object **value) |
203 | 187 | { |
204 | 187 | struct json_pointer_get_result res; |
205 | 187 | int rc; |
206 | | |
207 | 187 | rc = json_pointer_result_get_recursive(obj, path, &res); |
208 | 187 | if (rc) |
209 | 141 | return rc; |
210 | | |
211 | 46 | if (value) |
212 | 46 | *value = res.obj; |
213 | | |
214 | 46 | return 0; |
215 | 187 | } |
216 | | |
217 | | int json_pointer_get_internal(struct json_object *obj, const char *path, |
218 | | struct json_pointer_get_result *res) |
219 | 2.06k | { |
220 | 2.06k | char *path_copy = NULL; |
221 | 2.06k | int rc; |
222 | | |
223 | 2.06k | if (!obj || !path) |
224 | 1.75k | { |
225 | 1.75k | errno = EINVAL; |
226 | 1.75k | return -1; |
227 | 1.75k | } |
228 | | |
229 | 310 | if (path[0] == '\0') |
230 | 114 | { |
231 | 114 | res->parent = NULL; |
232 | 114 | res->obj = obj; |
233 | 114 | res->key_in_parent = NULL; |
234 | 114 | res->index_in_parent = UINT32_MAX; |
235 | 114 | return 0; |
236 | 114 | } |
237 | | |
238 | | /* pass a working copy to the recursive call */ |
239 | 196 | if (!(path_copy = strdup(path))) |
240 | 0 | { |
241 | 0 | errno = ENOMEM; |
242 | 0 | return -1; |
243 | 0 | } |
244 | 196 | rc = json_pointer_result_get_recursive(obj, path_copy, res); |
245 | | /* re-map the path string to the const-path string */ |
246 | 196 | if (rc == 0 && json_object_is_type(res->parent, json_type_object) && res->key_in_parent) |
247 | 5 | res->key_in_parent = path + (res->key_in_parent - path_copy); |
248 | 196 | free(path_copy); |
249 | | |
250 | 196 | return rc; |
251 | 196 | } |
252 | | |
253 | | int json_pointer_get(struct json_object *obj, const char *path, struct json_object **res) |
254 | 2.06k | { |
255 | 2.06k | struct json_pointer_get_result jpres; |
256 | 2.06k | int rc; |
257 | | |
258 | 2.06k | rc = json_pointer_get_internal(obj, path, &jpres); |
259 | 2.06k | if (rc) |
260 | 1.91k | return rc; |
261 | | |
262 | 150 | if (res) |
263 | 150 | *res = jpres.obj; |
264 | | |
265 | 150 | return 0; |
266 | 2.06k | } |
267 | | |
268 | | int json_pointer_getf(struct json_object *obj, struct json_object **res, const char *path_fmt, ...) |
269 | 990 | { |
270 | 990 | char *path_copy = NULL; |
271 | 990 | int rc = 0; |
272 | 990 | va_list args; |
273 | | |
274 | 990 | if (!obj || !path_fmt) |
275 | 735 | { |
276 | 735 | errno = EINVAL; |
277 | 735 | return -1; |
278 | 735 | } |
279 | | |
280 | 255 | va_start(args, path_fmt); |
281 | 255 | rc = vasprintf(&path_copy, path_fmt, args); |
282 | 255 | va_end(args); |
283 | | |
284 | 255 | if (rc < 0) |
285 | 0 | return rc; |
286 | | |
287 | 255 | if (path_copy[0] == '\0') |
288 | 132 | { |
289 | 132 | if (res) |
290 | 132 | *res = obj; |
291 | 132 | goto out; |
292 | 132 | } |
293 | | |
294 | 123 | rc = json_pointer_object_get_recursive(obj, path_copy, res); |
295 | 255 | out: |
296 | 255 | free(path_copy); |
297 | | |
298 | 255 | return rc; |
299 | 123 | } |
300 | | |
301 | | int json_pointer_set_with_array_cb(struct json_object **obj, const char *path, |
302 | | struct json_object *value, |
303 | | json_pointer_array_set_cb array_set_cb, void *priv) |
304 | 2.06k | { |
305 | 2.06k | const char *endp; |
306 | 2.06k | char *path_copy = NULL; |
307 | 2.06k | struct json_object *set = NULL; |
308 | 2.06k | int rc; |
309 | | |
310 | 2.06k | if (!obj || !path) |
311 | 0 | { |
312 | 0 | errno = EINVAL; |
313 | 0 | return -1; |
314 | 0 | } |
315 | | |
316 | 2.06k | if (path[0] == '\0') |
317 | 1.62k | { |
318 | 1.62k | json_object_put(*obj); |
319 | 1.62k | *obj = value; |
320 | 1.62k | return 0; |
321 | 1.62k | } |
322 | | |
323 | 442 | if (path[0] != '/') |
324 | 207 | { |
325 | 207 | errno = EINVAL; |
326 | 207 | return -1; |
327 | 207 | } |
328 | | |
329 | | /* If there's only 1 level to set, stop here */ |
330 | 235 | if ((endp = strrchr(path, '/')) == path) |
331 | 204 | { |
332 | 204 | path++; |
333 | 204 | return json_pointer_set_single_path(*obj, path, value, array_set_cb, priv); |
334 | 204 | } |
335 | | |
336 | | /* pass a working copy to the recursive call */ |
337 | 31 | if (!(path_copy = strdup(path))) |
338 | 0 | { |
339 | 0 | errno = ENOMEM; |
340 | 0 | return -1; |
341 | 0 | } |
342 | 31 | path_copy[endp - path] = '\0'; |
343 | 31 | rc = json_pointer_object_get_recursive(*obj, path_copy, &set); |
344 | 31 | free(path_copy); |
345 | | |
346 | 31 | if (rc) |
347 | 18 | return rc; |
348 | | |
349 | 13 | endp++; |
350 | 13 | return json_pointer_set_single_path(set, endp, value, array_set_cb, priv); |
351 | 31 | } |
352 | | |
353 | | int json_pointer_set(struct json_object **obj, const char *path, struct json_object *value) |
354 | 2.06k | { |
355 | 2.06k | return json_pointer_set_with_array_cb(obj, path, value, json_object_array_put_idx_cb, NULL); |
356 | 2.06k | } |
357 | | |
358 | | int json_pointer_setf(struct json_object **obj, struct json_object *value, const char *path_fmt, |
359 | | ...) |
360 | 990 | { |
361 | 990 | char *endp; |
362 | 990 | char *path_copy = NULL; |
363 | 990 | struct json_object *set = NULL; |
364 | 990 | va_list args; |
365 | 990 | int rc = 0; |
366 | | |
367 | 990 | if (!obj || !path_fmt) |
368 | 0 | { |
369 | 0 | errno = EINVAL; |
370 | 0 | return -1; |
371 | 0 | } |
372 | | |
373 | | /* pass a working copy to the recursive call */ |
374 | 990 | va_start(args, path_fmt); |
375 | 990 | rc = vasprintf(&path_copy, path_fmt, args); |
376 | 990 | va_end(args); |
377 | | |
378 | 990 | if (rc < 0) |
379 | 0 | return rc; |
380 | | |
381 | 990 | if (path_copy[0] == '\0') |
382 | 704 | { |
383 | 704 | json_object_put(*obj); |
384 | 704 | *obj = value; |
385 | 704 | goto out; |
386 | 704 | } |
387 | | |
388 | 286 | if (path_copy[0] != '/') |
389 | 119 | { |
390 | 119 | errno = EINVAL; |
391 | 119 | rc = -1; |
392 | 119 | goto out; |
393 | 119 | } |
394 | | |
395 | | /* If there's only 1 level to set, stop here */ |
396 | 167 | if ((endp = strrchr(path_copy, '/')) == path_copy) |
397 | 134 | { |
398 | 134 | set = *obj; |
399 | 134 | goto set_single_path; |
400 | 134 | } |
401 | | |
402 | 33 | *endp = '\0'; |
403 | 33 | rc = json_pointer_object_get_recursive(*obj, path_copy, &set); |
404 | | |
405 | 33 | if (rc) |
406 | 16 | goto out; |
407 | | |
408 | 151 | set_single_path: |
409 | 151 | endp++; |
410 | 151 | rc = json_pointer_set_single_path(set, endp, value, |
411 | 151 | json_object_array_put_idx_cb, NULL); |
412 | 990 | out: |
413 | 990 | free(path_copy); |
414 | 990 | return rc; |
415 | 151 | } |