Coverage Report

Created: 2026-04-12 06:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/util-linux/lib/jsonwrt.c
Line
Count
Source
1
/*
2
 * JSON output formatting functions.
3
 *
4
 * No copyright is claimed.  This code is in the public domain; do with
5
 * it what you wish.
6
 *
7
 * Written by Karel Zak <kzak@redhat.com>
8
 */
9
#include <stdio.h>
10
#include <inttypes.h>
11
#include <ctype.h>
12
#include <cctype.h>
13
14
#include "c.h"
15
#include "jsonwrt.h"
16
17
/*
18
 * Requirements enumerated via testing (V8, Firefox, IE11):
19
 *
20
 * var charsToEscape = [];
21
 * for (var i = 0; i < 65535; i += 1) {
22
 *  try {
23
 *    JSON.parse('{"sample": "' + String.fromCodePoint(i) + '"}');
24
 *  } catch (e) {
25
 *    charsToEscape.push(i);
26
 *  }
27
 * }
28
 */
29
static void fputs_quoted_case_json(const char *data, FILE *out, int dir, size_t size)
30
0
{
31
0
  const char *p;
32
33
0
  fputc('"', out);
34
0
  for (p = data; p && *p && (!size || p < data + size); p++) {
35
36
0
    const unsigned int c = (unsigned int) *p;
37
38
    /* From http://www.json.org
39
     *
40
     * The double-quote and backslashes would break out a string or
41
     * init an escape sequence if not escaped.
42
     *
43
     * Note that single-quotes and forward slashes, while they're
44
     * in the JSON spec, don't break double-quoted strings.
45
     */
46
0
    if (c == '"' || c == '\\') {
47
0
      fputc('\\', out);
48
0
      fputc(c, out);
49
0
      continue;
50
0
    }
51
52
    /* All non-control characters OK; do the case swap as required. */
53
0
    if (c >= 0x20) {
54
      /*
55
       * Don't use locale sensitive ctype.h functions for regular
56
       * ASCII chars, because for example with Turkish locale
57
       * (aka LANG=tr_TR.UTF-8) toupper('I') returns 'I'.
58
       */
59
0
      if (c <= 127)
60
0
        fputc(dir ==  1 ? c_toupper(c) :
61
0
              dir == -1 ? c_tolower(c) : *p, out);
62
0
      else
63
0
        fputc(dir ==  1 ? toupper(c) :
64
0
              dir == -1 ? tolower(c) : *p, out);
65
0
      continue;
66
0
    }
67
68
    /* In addition, all chars under ' ' break Node's/V8/Chrome's, and
69
     * Firefox's JSON.parse function
70
     */
71
0
    switch (c) {
72
      /* Handle short-hand cases to reduce output size.  C
73
       * has most of the same stuff here, so if there's an
74
       * "Escape for C" function somewhere in the STL, we
75
       * should probably be using it.
76
       */
77
0
      case '\b':
78
0
        fputs("\\b", out);
79
0
        break;
80
0
      case '\t':
81
0
        fputs("\\t", out);
82
0
        break;
83
0
      case '\n':
84
0
        fputs("\\n", out);
85
0
        break;
86
0
      case '\f':
87
0
        fputs("\\f", out);
88
0
        break;
89
0
      case '\r':
90
0
        fputs("\\r", out);
91
0
        break;
92
0
      default:
93
        /* Other assorted control characters */
94
0
        fprintf(out, "\\u00%02x", c);
95
0
        break;
96
0
    }
97
0
  }
98
0
  fputc('"', out);
99
0
}
100
101
0
#define fputs_quoted_json(_d, _o)       fputs_quoted_case_json(_d, _o, 0, 0)
102
#define fputs_quoted_json_upper(_d, _o) fputs_quoted_case_json(_d, _o, 1, 0)
103
0
#define fputs_quoted_json_lower(_d, _o) fputs_quoted_case_json(_d, _o, -1, 0)
104
105
void ul_jsonwrt_init(struct ul_jsonwrt *fmt, FILE *out, int indent, enum ul_json_format json_format)
106
0
{
107
0
  fmt->out = out;
108
0
  fmt->indent = indent;
109
0
  fmt->json_format = json_format;
110
0
  fmt->after_close = 0;
111
0
}
112
113
int ul_jsonwrt_is_ready(struct ul_jsonwrt *fmt)
114
0
{
115
0
  return fmt->out == NULL ? 0 : 1;
116
0
}
117
118
void ul_jsonwrt_indent(struct ul_jsonwrt *fmt)
119
0
{
120
0
  int i;
121
122
0
  for (i = 0; i < fmt->indent; i++)
123
0
    fputs("   ", fmt->out);
124
0
}
125
126
static void print_name(struct ul_jsonwrt *fmt, const char *name)
127
0
{
128
0
  if (fmt->after_close) {
129
0
    if (fmt->json_format == UL_JSON_LINE)
130
0
      fputs(fmt->indent == 0 ? "\n" : ",", fmt->out);
131
0
    else if (fmt->json_format == UL_JSON_COMPACT)
132
0
      fputs(",", fmt->out);
133
0
    else
134
0
      fputs(name ? ",\n" : ",", fmt->out);
135
0
  }
136
137
0
  switch (fmt->json_format) {
138
0
  case UL_JSON_LINE:
139
0
  case UL_JSON_COMPACT:
140
0
    break;
141
0
  case UL_JSON_PRETTY:
142
0
  default:
143
0
    if (name || !fmt->after_close)
144
0
      ul_jsonwrt_indent(fmt);
145
0
    break;
146
0
  }
147
0
  if (name)
148
0
    fputs_quoted_json_lower(name, fmt->out);
149
0
}
150
151
void ul_jsonwrt_open(struct ul_jsonwrt *fmt, const char *name, int type)
152
0
{
153
0
  const char *s = "";
154
155
0
  print_name(fmt, name);
156
157
0
  switch (type) {
158
0
  case UL_JSON_OBJECT:
159
0
    if (fmt->json_format == UL_JSON_LINE)
160
0
      s = "{";
161
0
    else if (fmt->json_format == UL_JSON_COMPACT) {
162
      /* At nesting depth 2, put the object opens on a new line
163
       * for readability; otherwise keep fully compact (no newline). */
164
0
      if (fmt->indent == 2)
165
0
        s = name ? ":{" : "\n{";
166
0
      else
167
0
        s = "{";
168
0
    } else
169
0
      s = name ? ": {\n" : "{\n";
170
0
    fmt->indent++;
171
0
    break;
172
0
  case UL_JSON_ARRAY:
173
0
    if (fmt->json_format == UL_JSON_LINE)
174
0
      s = "[";
175
0
    else if (fmt->json_format == UL_JSON_COMPACT)
176
0
      s = name ? ":[" : "[";
177
0
    else
178
0
      s = name ? ": [\n" : "[\n";
179
0
    fmt->indent++;
180
0
    break;
181
0
  case UL_JSON_VALUE:
182
0
    if (fmt->json_format == UL_JSON_LINE)
183
0
      s = name ? ":" : "";
184
0
    else if (fmt->json_format == UL_JSON_COMPACT)
185
0
      s = name ? ":" : "";
186
0
    else
187
0
      s = name ? ": " : " ";
188
0
    break;
189
0
  }
190
0
  fputs(s, fmt->out);
191
0
  fmt->after_close = 0;
192
0
}
193
194
void ul_jsonwrt_empty(struct ul_jsonwrt *fmt, const char *name, int type)
195
0
{
196
0
  const char *s = "";
197
198
0
  print_name(fmt, name);
199
200
0
  switch (type) {
201
0
  case UL_JSON_OBJECT:
202
0
    if (fmt->json_format == UL_JSON_PRETTY)
203
0
      s = name ? ": {}" : "{}";
204
0
    else
205
0
      s = name ? ":{}" : "{}";
206
0
    break;
207
0
  case UL_JSON_ARRAY:
208
0
    if (fmt->json_format == UL_JSON_PRETTY)
209
0
      s = name ? ": []" : "[]";
210
0
    else
211
0
      s = name ? ":[]" : "[]";
212
0
    break;
213
0
  case UL_JSON_VALUE:
214
0
    if (fmt->json_format == UL_JSON_PRETTY)
215
0
      s = name ? ": null" : "null";
216
0
    else
217
0
      s = name ? ":null" : "null";
218
0
    break;
219
0
  }
220
0
  fputs(s, fmt->out);
221
0
  fmt->after_close = 1;
222
0
}
223
224
void ul_jsonwrt_close(struct ul_jsonwrt *fmt, int type)
225
0
{
226
0
  char endchr;
227
228
0
  assert(fmt->indent > 0);
229
230
0
  switch (type) {
231
0
  case UL_JSON_OBJECT:
232
0
    fmt->indent--;
233
0
    endchr = '}';
234
0
    break;
235
0
  case UL_JSON_ARRAY:
236
0
    fmt->indent--;
237
0
    endchr = ']';
238
0
    break;
239
0
  case UL_JSON_VALUE:
240
0
    fmt->after_close = 1;
241
0
    return;
242
0
  default:
243
0
    fmt->after_close = 1;
244
0
    return;
245
0
  }
246
247
  /* In COMPACT, when closing a direct child of root (indent==1),
248
   * insert a newline so the closing bracket starts on its own line. */
249
0
  if (fmt->json_format == UL_JSON_COMPACT && fmt->indent == 1)
250
0
    fputc('\n', fmt->out);
251
252
0
  switch (fmt->json_format) {
253
0
  case UL_JSON_LINE:
254
0
  case UL_JSON_COMPACT:
255
0
    break;
256
0
  case UL_JSON_PRETTY:
257
0
  default:
258
0
    fputs("\n", fmt->out);
259
0
    ul_jsonwrt_indent(fmt);
260
0
    break;
261
0
  }
262
263
0
  fputc(endchr, fmt->out);
264
265
0
  switch (fmt->json_format) {
266
0
  case UL_JSON_LINE:
267
0
    break;
268
0
  case UL_JSON_COMPACT:
269
0
  case UL_JSON_PRETTY:
270
0
  default:
271
0
    if (fmt->indent == 0)
272
0
      fputc('\n', fmt->out);
273
0
    break;
274
0
  }
275
0
  fmt->after_close = 1;
276
0
}
277
278
279
void ul_jsonwrt_flush(struct ul_jsonwrt *fmt)
280
0
{
281
0
  fflush(fmt->out);
282
0
}
283
284
void ul_jsonwrt_value_raw(struct ul_jsonwrt *fmt,
285
      const char *name, const char *data)
286
0
{
287
0
  ul_jsonwrt_value_open(fmt, name);
288
0
  if (data && *data)
289
0
    fputs(data, fmt->out);
290
0
  else
291
0
    fputs("null", fmt->out);
292
0
  ul_jsonwrt_value_close(fmt);
293
0
}
294
295
void ul_jsonwrt_value_s(struct ul_jsonwrt *fmt,
296
      const char *name, const char *data)
297
0
{
298
0
  ul_jsonwrt_value_open(fmt, name);
299
0
  if (data && *data)
300
0
    fputs_quoted_json(data, fmt->out);
301
0
  else
302
0
    fputs("null", fmt->out);
303
0
  ul_jsonwrt_value_close(fmt);
304
0
}
305
306
void ul_jsonwrt_value_s_sized(struct ul_jsonwrt *fmt,
307
            const char *name, const char *data, size_t size)
308
0
{
309
0
  ul_jsonwrt_value_open(fmt, name);
310
0
  if (data && *data)
311
0
    fputs_quoted_case_json(data, fmt->out, 0, size);
312
0
  else
313
0
    fputs("null", fmt->out);
314
0
  ul_jsonwrt_value_close(fmt);
315
0
}
316
317
void ul_jsonwrt_value_u64(struct ul_jsonwrt *fmt,
318
      const char *name, uint64_t data)
319
0
{
320
0
  ul_jsonwrt_value_open(fmt, name);
321
0
  fprintf(fmt->out, "%"PRIu64, data);
322
0
  ul_jsonwrt_value_close(fmt);
323
0
}
324
325
void ul_jsonwrt_value_double(struct ul_jsonwrt *fmt,
326
      const char *name, long double data)
327
0
{
328
0
  ul_jsonwrt_value_open(fmt, name);
329
0
  fprintf(fmt->out, "%Lg", data);
330
0
  ul_jsonwrt_value_close(fmt);
331
0
}
332
333
void ul_jsonwrt_value_boolean(struct ul_jsonwrt *fmt,
334
      const char *name, int data)
335
0
{
336
0
  ul_jsonwrt_value_open(fmt, name);
337
0
  fputs(data ? "true" : "false", fmt->out);
338
0
  ul_jsonwrt_value_close(fmt);
339
0
}