Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
sudo-project
GitHub Repository: sudo-project/sudo
Path: blob/main/lib/util/json.c
1532 views
1
/*
2
* SPDX-License-Identifier: ISC
3
*
4
* Copyright (c) 2013-2020 Todd C. Miller <[email protected]>
5
*
6
* Permission to use, copy, modify, and distribute this software for any
7
* purpose with or without fee is hereby granted, provided that the above
8
* copyright notice and this permission notice appear in all copies.
9
*
10
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
11
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
13
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17
*/
18
19
#include <config.h>
20
21
#include <ctype.h>
22
#include <stdio.h>
23
#include <stdlib.h>
24
#ifdef HAVE_STDBOOL_H
25
# include <stdbool.h>
26
#else
27
# include <compat/stdbool.h>
28
#endif /* HAVE_STDBOOL_H */
29
#include <string.h>
30
31
#include <sudo_compat.h>
32
#include <sudo_debug.h>
33
#include <sudo_fatal.h>
34
#include <sudo_gettext.h>
35
#include <sudo_json.h>
36
#include <sudo_util.h>
37
38
/*
39
* Double the size of the json buffer.
40
* Returns true on success, false if out of memory.
41
*/
42
static bool
43
json_expand_buf(struct json_container *jsonc)
44
{
45
char *newbuf;
46
debug_decl(json_expand_buf, SUDO_DEBUG_UTIL);
47
48
if ((newbuf = reallocarray(jsonc->buf, 2, jsonc->bufsize)) == NULL) {
49
if (jsonc->memfatal) {
50
sudo_fatalx(U_("%s: %s"),
51
__func__, U_("unable to allocate memory"));
52
}
53
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
54
"%s: %s", __func__, "unable to allocate memory");
55
debug_return_bool(false);
56
}
57
jsonc->buf = newbuf;
58
jsonc->bufsize *= 2;
59
60
debug_return_bool(true);
61
}
62
63
/*
64
* Start a new line and indent unless formatting as minimal JSON.
65
* Append "indent" number of blank characters.
66
*/
67
static bool
68
json_new_line(struct json_container *jsonc)
69
{
70
unsigned int indent = jsonc->indent_level;
71
debug_decl(json_new_line, SUDO_DEBUG_UTIL);
72
73
/* No non-essential white space in minimal mode. */
74
if (jsonc->minimal)
75
debug_return_bool(true);
76
77
while (jsonc->buflen + 1 + indent >= jsonc->bufsize) {
78
if (!json_expand_buf(jsonc))
79
debug_return_bool(false);
80
}
81
jsonc->buf[jsonc->buflen++] = '\n';
82
while (indent--) {
83
jsonc->buf[jsonc->buflen++] = ' ';
84
}
85
jsonc->buf[jsonc->buflen] = '\0';
86
87
debug_return_bool(true);
88
}
89
90
/*
91
* Append a string to the JSON buffer, expanding as needed.
92
* Does not perform any quoting.
93
*/
94
static bool
95
json_append_buf(struct json_container *jsonc, const char *str)
96
{
97
size_t len;
98
debug_decl(json_append_buf, SUDO_DEBUG_UTIL);
99
100
len = strlen(str);
101
while (jsonc->buflen + len >= jsonc->bufsize) {
102
if (!json_expand_buf(jsonc))
103
debug_return_bool(false);
104
}
105
106
memcpy(jsonc->buf + jsonc->buflen, str, len);
107
jsonc->buflen += (unsigned int)len;
108
jsonc->buf[jsonc->buflen] = '\0';
109
110
debug_return_bool(true);
111
}
112
113
/*
114
* Append a quoted JSON string, escaping special chars and expanding as needed.
115
* Treats strings as 8-bit ASCII, escaping control characters.
116
*/
117
static bool
118
json_append_string(struct json_container *jsonc, const char *str)
119
{
120
const char hex[] = "0123456789abcdef";
121
char ch;
122
debug_decl(json_append_string, SUDO_DEBUG_UTIL);
123
124
if (!json_append_buf(jsonc, "\""))
125
debug_return_bool(false);
126
while ((ch = *str++) != '\0') {
127
char buf[sizeof("\\u0000")], *cp = buf;
128
129
switch (ch) {
130
case '"':
131
case '\\':
132
*cp++ = '\\';
133
break;
134
case '\b':
135
*cp++ = '\\';
136
ch = 'b';
137
break;
138
case '\f':
139
*cp++ = '\\';
140
ch = 'f';
141
break;
142
case '\n':
143
*cp++ = '\\';
144
ch = 'n';
145
break;
146
case '\r':
147
*cp++ = '\\';
148
ch = 'r';
149
break;
150
case '\t':
151
*cp++ = '\\';
152
ch = 't';
153
break;
154
default:
155
if (iscntrl((unsigned char)ch)) {
156
/* Escape control characters like \u0000 */
157
*cp++ = '\\';
158
*cp++ = 'u';
159
*cp++ = '0';
160
*cp++ = '0';
161
*cp++ = hex[ch >> 4];
162
ch = hex[ch & 0x0f];
163
}
164
break;
165
}
166
*cp++ = ch;
167
*cp = '\0';
168
if (!json_append_buf(jsonc, buf))
169
debug_return_bool(false);
170
}
171
if (!json_append_buf(jsonc, "\""))
172
debug_return_bool(false);
173
174
debug_return_bool(true);
175
}
176
177
bool
178
sudo_json_init_v2(struct json_container *jsonc, unsigned int indent,
179
bool minimal, bool memfatal, bool quiet)
180
{
181
debug_decl(sudo_json_init, SUDO_DEBUG_UTIL);
182
183
memset(jsonc, 0, sizeof(*jsonc));
184
jsonc->indent_level = indent;
185
jsonc->indent_increment = indent;
186
jsonc->minimal = minimal;
187
jsonc->memfatal = memfatal;
188
jsonc->quiet = quiet;
189
jsonc->buf = malloc(64 * 1024);
190
if (jsonc->buf == NULL) {
191
if (jsonc->memfatal) {
192
sudo_fatalx(U_("%s: %s"),
193
__func__, U_("unable to allocate memory"));
194
}
195
sudo_debug_printf(SUDO_DEBUG_ERROR|SUDO_DEBUG_ERRNO|SUDO_DEBUG_LINENO,
196
"%s: %s", __func__, "unable to allocate memory");
197
debug_return_bool(false);
198
}
199
*jsonc->buf = '\0';
200
jsonc->bufsize = 64 * 1024;
201
202
debug_return_bool(true);
203
}
204
205
bool
206
sudo_json_init_v1(struct json_container *jsonc, unsigned int indent,
207
bool minimal, bool memfatal)
208
{
209
return sudo_json_init_v2(jsonc, indent, minimal, memfatal, false);
210
}
211
212
void
213
sudo_json_free_v1(struct json_container *jsonc)
214
{
215
debug_decl(sudo_json_free, SUDO_DEBUG_UTIL);
216
217
free(jsonc->buf);
218
memset(jsonc, 0, sizeof(*jsonc));
219
220
debug_return;
221
}
222
223
bool
224
sudo_json_open_object_v1(struct json_container *jsonc, const char *name)
225
{
226
debug_decl(sudo_json_open_object, SUDO_DEBUG_UTIL);
227
228
/* Add comma if we are continuing an object/array. */
229
if (jsonc->need_comma) {
230
if (!json_append_buf(jsonc, ","))
231
debug_return_bool(false);
232
}
233
if (!json_new_line(jsonc))
234
debug_return_bool(false);
235
236
if (name != NULL) {
237
json_append_string(jsonc, name);
238
if (!json_append_buf(jsonc, jsonc->minimal ? ":{" : ": {"))
239
debug_return_bool(false);
240
} else {
241
if (!json_append_buf(jsonc, "{"))
242
debug_return_bool(false);
243
}
244
245
jsonc->indent_level += jsonc->indent_increment;
246
jsonc->need_comma = false;
247
248
debug_return_bool(true);
249
}
250
251
bool
252
sudo_json_close_object_v1(struct json_container *jsonc)
253
{
254
debug_decl(sudo_json_close_object, SUDO_DEBUG_UTIL);
255
256
if (!jsonc->minimal) {
257
jsonc->indent_level -= jsonc->indent_increment;
258
if (!json_new_line(jsonc))
259
debug_return_bool(false);
260
}
261
if (!json_append_buf(jsonc, "}"))
262
debug_return_bool(false);
263
jsonc->need_comma = true;
264
265
debug_return_bool(true);
266
}
267
268
bool
269
sudo_json_open_array_v1(struct json_container *jsonc, const char *name)
270
{
271
debug_decl(sudo_json_open_array, SUDO_DEBUG_UTIL);
272
273
/* Add comma if we are continuing an object/array. */
274
if (jsonc->need_comma) {
275
if (!json_append_buf(jsonc, ","))
276
debug_return_bool(false);
277
}
278
if (!json_new_line(jsonc))
279
debug_return_bool(false);
280
281
if (name != NULL) {
282
json_append_string(jsonc, name);
283
if (!json_append_buf(jsonc, jsonc->minimal ? ":[" : ": ["))
284
debug_return_bool(false);
285
} else {
286
if (!json_append_buf(jsonc, "["))
287
debug_return_bool(false);
288
}
289
290
jsonc->indent_level += jsonc->indent_increment;
291
jsonc->need_comma = false;
292
293
debug_return_bool(true);
294
}
295
296
bool
297
sudo_json_close_array_v1(struct json_container *jsonc)
298
{
299
debug_decl(sudo_json_close_array, SUDO_DEBUG_UTIL);
300
301
if (!jsonc->minimal) {
302
jsonc->indent_level -= jsonc->indent_increment;
303
if (!json_new_line(jsonc))
304
debug_return_bool(false);
305
}
306
if (!json_append_buf(jsonc, "]"))
307
debug_return_bool(false);
308
jsonc->need_comma = true;
309
310
debug_return_bool(true);
311
}
312
313
static bool
314
sudo_json_add_value_int(struct json_container *jsonc, const char *name,
315
struct json_value *value, bool as_object)
316
{
317
struct json_container saved_container = *jsonc;
318
char numbuf[STRLEN_MAX_SIGNED(long long) + 1];
319
debug_decl(sudo_json_add_value, SUDO_DEBUG_UTIL);
320
321
/* Add comma if we are continuing an object/array. */
322
if (jsonc->need_comma) {
323
if (!json_append_buf(jsonc, ","))
324
goto bad;
325
}
326
if (!json_new_line(jsonc))
327
goto bad;
328
jsonc->need_comma = true;
329
330
if (as_object) {
331
if (!json_append_buf(jsonc, jsonc->minimal ? "{" : "{ "))
332
goto bad;
333
}
334
335
/* name */
336
if (name != NULL) {
337
if (!json_append_string(jsonc, name))
338
goto bad;
339
if (!json_append_buf(jsonc, jsonc->minimal ? ":" : ": "))
340
goto bad;
341
}
342
343
/* value */
344
switch (value->type) {
345
case JSON_STRING:
346
if (!json_append_string(jsonc, value->u.string))
347
goto bad;
348
break;
349
case JSON_ID:
350
snprintf(numbuf, sizeof(numbuf), "%u", (unsigned int)value->u.id);
351
if (!json_append_buf(jsonc, numbuf))
352
goto bad;
353
break;
354
case JSON_NUMBER:
355
snprintf(numbuf, sizeof(numbuf), "%lld", value->u.number);
356
if (!json_append_buf(jsonc, numbuf))
357
goto bad;
358
break;
359
case JSON_NULL:
360
if (!json_append_buf(jsonc, "null"))
361
goto bad;
362
break;
363
case JSON_BOOL:
364
if (!json_append_buf(jsonc, value->u.boolean ? "true" : "false"))
365
goto bad;
366
break;
367
case JSON_ARRAY:
368
if (!jsonc->quiet)
369
sudo_warnx("internal error: add JSON_ARRAY as a value");
370
goto bad;
371
case JSON_OBJECT:
372
if (!jsonc->quiet)
373
sudo_warnx("internal error: add JSON_OBJECT as a value");
374
goto bad;
375
default:
376
if (!jsonc->quiet)
377
sudo_warnx("internal error: unknown JSON type %d", value->type);
378
goto bad;
379
}
380
381
if (as_object) {
382
if (!json_append_buf(jsonc, jsonc->minimal ? "}" : " }"))
383
goto bad;
384
}
385
386
debug_return_bool(true);
387
bad:
388
/* Restore container but handle reallocation of buf. */
389
saved_container.buf = jsonc->buf;
390
saved_container.bufsize = jsonc->bufsize;
391
*jsonc = saved_container;
392
jsonc->buf[jsonc->buflen] = '\0';
393
debug_return_bool(false);
394
}
395
396
bool
397
sudo_json_add_value_v1(struct json_container *jsonc, const char *name,
398
struct json_value *value)
399
{
400
return sudo_json_add_value_int(jsonc, name, value, false);
401
}
402
403
bool
404
sudo_json_add_value_as_object_v1(struct json_container *jsonc, const char *name,
405
struct json_value *value)
406
{
407
return sudo_json_add_value_int(jsonc, name, value, true);
408
}
409
410
char *
411
sudo_json_get_buf_v1(struct json_container *jsonc)
412
{
413
return jsonc->buf;
414
}
415
416
unsigned int
417
sudo_json_get_len_v1(struct json_container *jsonc)
418
{
419
return jsonc->buflen;
420
}
421
422