Coverage Report

Created: 2025-04-22 06:08

/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
}