Coverage Report

Created: 2026-08-14 07:08

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/mpv/misc/json.c
Line
Count
Source
1
/*
2
 * This file is part of mpv.
3
 *
4
 * mpv is free software; you can redistribute it and/or
5
 * modify it under the terms of the GNU Lesser General Public
6
 * License as published by the Free Software Foundation; either
7
 * version 2.1 of the License, or (at your option) any later version.
8
 *
9
 * mpv is distributed in the hope that it will be useful,
10
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
11
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
12
 * GNU Lesser General Public License for more details.
13
 *
14
 * You should have received a copy of the GNU Lesser General Public
15
 * License along with mpv.  If not, see <http://www.gnu.org/licenses/>.
16
 */
17
18
/* JSON parser:
19
 *
20
 * Unlike standard JSON, \u escapes don't allow you to specify UTF-16 surrogate
21
 * pairs. There may be some differences how numbers are parsed (this parser
22
 * doesn't verify what's passed to strtod(), and also prefers parsing numbers
23
 * as integers with stroll() if possible).
24
 *
25
 * It has some non-standard extensions which shouldn't conflict with JSON:
26
 *  - a list or object item can have a trailing ","
27
 *  - object syntax accepts "=" in addition of ":"
28
 *  - object keys can be unquoted, if they start with a character in [A-Za-z_]
29
 *    and contain only characters in [A-Za-z0-9_]
30
 *  - byte escapes with "\xAB" are allowed (with AB being a 2 digit hex number)
31
 *
32
 * Also see: http://tools.ietf.org/html/rfc8259
33
 *
34
 * JSON writer:
35
 *
36
 * Doesn't insert whitespace. It's literally a waste of space.
37
 *
38
 * Can output invalid UTF-8, if input is invalid UTF-8. Consumers are supposed
39
 * to deal with somehow: either by using byte-strings for JSON, or by running
40
 * a "fixup" pass on the input data. The latter could for example change
41
 * invalid UTF-8 sequences to replacement characters.
42
 */
43
44
#include <stdlib.h>
45
#include <string.h>
46
#include <math.h>
47
#include <errno.h>
48
#include <inttypes.h>
49
#include <assert.h>
50
51
#include <mpv/client.h>
52
53
#include "common/common.h"
54
#include "misc/bstr.h"
55
#include "misc/ctype.h"
56
57
#include "json.h"
58
59
static bool eat_c(char **s, char c)
60
1.84M
{
61
1.84M
    if (**s == c) {
62
639k
        *s += 1;
63
639k
        return true;
64
639k
    }
65
1.20M
    return false;
66
1.84M
}
67
68
static void eat_ws(char **src)
69
1.83M
{
70
1.86M
    while (1) {
71
1.86M
        char c = **src;
72
1.86M
        if (c != ' ' && c != '\t' && c != '\n' && c != '\r')
73
1.83M
            return;
74
29.0k
        *src += 1;
75
29.0k
    }
76
1.83M
}
77
78
void json_skip_whitespace(char **src)
79
2.44k
{
80
2.44k
    eat_ws(src);
81
2.44k
}
82
83
static int read_id(void *ta_parent, struct mpv_node *dst, char **src)
84
69.1k
{
85
69.1k
    char *start = *src;
86
69.1k
    if (!mp_isalpha(**src) && **src != '_')
87
859
        return -1;
88
400k
    while (mp_isalnum(**src) || **src == '_')
89
332k
        *src += 1;
90
68.2k
    if (**src == ' ') {
91
301
        **src = '\0'; // we're allowed to mutate it => can avoid the strndup
92
301
        *src += 1;
93
67.9k
    } else {
94
67.9k
        start = talloc_strndup(ta_parent, start, *src - start);
95
67.9k
    }
96
68.2k
    dst->format = MPV_FORMAT_STRING;
97
68.2k
    dst->u.string = start;
98
68.2k
    return 0;
99
69.1k
}
100
101
static int read_str(void *ta_parent, struct mpv_node *dst, char **src)
102
2.61k
{
103
2.61k
    if (!eat_c(src, '"'))
104
59
        return -1; // not a string
105
2.55k
    char *str = *src;
106
2.55k
    char *cur = str;
107
2.55k
    bool has_escapes = false;
108
3.04M
    while (cur[0] && cur[0] != '"') {
109
3.04M
        if (cur[0] == '\\') {
110
30.9k
            has_escapes = true;
111
            // skip >\"< and >\\< (latter to handle >\\"< correctly)
112
30.9k
            if (cur[1] == '"' || cur[1] == '\\')
113
4.15k
                cur++;
114
30.9k
        }
115
3.04M
        cur++;
116
3.04M
    }
117
2.55k
    if (cur[0] != '"')
118
135
        return -1; // invalid termination
119
    // Mutate input string so we have a null-terminated string to the literal.
120
    // This is a stupid micro-optimization, so we can avoid allocation.
121
2.41k
    cur[0] = '\0';
122
2.41k
    *src = cur + 1;
123
2.41k
    if (has_escapes) {
124
1.54k
        bstr unescaped = {0};
125
1.54k
        bstr r = bstr0(str);
126
1.54k
        if (!mp_append_escaped_string(ta_parent, &unescaped, &r))
127
230
            return -1; // broken escapes
128
1.31k
        str = unescaped.start; // the function guarantees null-termination
129
1.31k
    }
130
2.18k
    dst->format = MPV_FORMAT_STRING;
131
2.18k
    dst->u.string = str;
132
2.18k
    return 0;
133
2.41k
}
134
135
static int read_sub(void *ta_parent, struct mpv_node *dst, char **src,
136
                    int max_depth)
137
6.27k
{
138
6.27k
    bool is_arr = eat_c(src, '[');
139
6.27k
    bool is_obj = !is_arr && eat_c(src, '{');
140
6.27k
    if (!is_arr && !is_obj)
141
0
        return -1; // not an array or object
142
6.27k
    char term = is_obj ? '}' : ']';
143
6.27k
    struct mpv_node_list *list = talloc_zero(ta_parent, struct mpv_node_list);
144
567k
    while (1) {
145
567k
        eat_ws(src);
146
567k
        if (eat_c(src, term))
147
2.11k
            break;
148
565k
        if (list->num > 0 && !eat_c(src, ','))
149
102
            return -1; // missing ','
150
565k
        eat_ws(src);
151
        // non-standard extension: allow a trailing ","
152
565k
        if (eat_c(src, term))
153
233
            break;
154
565k
        if (is_obj) {
155
69.1k
            struct mpv_node keynode;
156
            // non-standard extension: allow unquoted strings as keys
157
69.1k
            if (read_id(list, &keynode, src) < 0 &&
158
859
                read_str(list, &keynode, src) < 0)
159
132
                return -1; // key is not a string
160
69.0k
            eat_ws(src);
161
            // non-standard extension: allow "=" instead of ":"
162
69.0k
            if (!eat_c(src, ':') && !eat_c(src, '='))
163
214
                return -1; // ':' missing
164
68.7k
            eat_ws(src);
165
68.7k
            MP_TARRAY_GROW(list, list->keys, list->num);
166
68.7k
            list->keys[list->num] = keynode.u.string;
167
68.7k
        }
168
564k
        MP_TARRAY_GROW(list, list->values, list->num);
169
564k
        if (json_parse(ta_parent, &list->values[list->num], src, max_depth) < 0)
170
3.48k
            return -1;
171
561k
        list->num++;
172
561k
    }
173
2.34k
    dst->format = is_obj ? MPV_FORMAT_NODE_MAP : MPV_FORMAT_NODE_ARRAY;
174
2.34k
    dst->u.list = list;
175
2.34k
    return 0;
176
6.27k
}
177
178
/* Parse the string in *src as JSON, and write the result into *dst.
179
 * max_depth limits the recursion and JSON tree depth.
180
 * Warning: this overwrites the input string (what *src points to)!
181
 * Returns:
182
 *   0: success, *dst is valid, *src points to the end (the caller must check
183
 *      whether *src really terminates)
184
 *  -1: failure, *dst is invalid, there may be dead allocs under ta_parent
185
 *      (ta_free_children(ta_parent) is the only way to free them)
186
 * The input string can be mutated in both cases. *dst might contain string
187
 * elements, which point into the (mutated) input string.
188
 */
189
int json_parse(void *ta_parent, struct mpv_node *dst, char **src, int max_depth)
190
566k
{
191
566k
    max_depth -= 1;
192
566k
    if (max_depth < 0)
193
16
        return -1;
194
195
566k
    eat_ws(src);
196
197
566k
    char c = **src;
198
566k
    if (!c)
199
244
        return -1; // early EOF
200
566k
    if (c == 'n' && strncmp(*src, "null", 4) == 0) {
201
296
        *src += 4;
202
296
        dst->format = MPV_FORMAT_NONE;
203
296
        return 0;
204
566k
    } else if (c == 't' && strncmp(*src, "true", 4) == 0) {
205
288
        *src += 4;
206
288
        dst->format = MPV_FORMAT_FLAG;
207
288
        dst->u.flag = 1;
208
288
        return 0;
209
565k
    } else if (c == 'f' && strncmp(*src, "false", 5) == 0) {
210
194
        *src += 5;
211
194
        dst->format = MPV_FORMAT_FLAG;
212
194
        dst->u.flag = 0;
213
194
        return 0;
214
565k
    } else if (c == '"') {
215
1.75k
        return read_str(ta_parent, dst, src);
216
563k
    } else if (c == '[' || c == '{') {
217
6.27k
        return read_sub(ta_parent, dst, src, max_depth);
218
557k
    } else if (c == '-' || (c >= '0' && c <= '9')) {
219
        // The number could be either a float or an int. JSON doesn't make a
220
        // difference, but the client API does.
221
557k
        char *nsrci = *src, *nsrcf = *src;
222
557k
        errno = 0;
223
557k
        long long int numi = strtoll(*src, &nsrci, 0);
224
557k
        if (errno)
225
194
            nsrci = *src;
226
557k
        errno = 0;
227
557k
        double numf = strtod(*src, &nsrcf);
228
557k
        if (errno)
229
1
            nsrcf = *src;
230
557k
        if (nsrci >= nsrcf) {
231
381k
            *src = nsrci;
232
381k
            dst->format = MPV_FORMAT_INT64; // long long is usually 64 bits
233
381k
            dst->u.int64 = numi;
234
381k
            return 0;
235
381k
        }
236
175k
        if (nsrcf > *src && isfinite(numf)) {
237
175k
            *src = nsrcf;
238
175k
            dst->format = MPV_FORMAT_DOUBLE;
239
175k
            dst->u.double_ = numf;
240
175k
            return 0;
241
175k
        }
242
1
        return -1;
243
175k
    }
244
173
    return -1; // character doesn't start a valid token
245
566k
}
246
247
248
72.2M
#define APPEND(b, s) bstr_xappend(NULL, (b), bstr0(s))
249
250
static const char special_escape[] = {
251
    ['\b'] = 'b',
252
    ['\f'] = 'f',
253
    ['\n'] = 'n',
254
    ['\r'] = 'r',
255
    ['\t'] = 't',
256
};
257
258
static void write_json_str(bstr *b, unsigned char *str)
259
16.6M
{
260
16.6M
    mp_assert(str);
261
262
16.6M
    APPEND(b, "\"");
263
157M
    while (1) {
264
157M
        unsigned char *cur = str;
265
746M
        while (cur[0] >= 32 && cur[0] != '"' && cur[0] != '\\')
266
588M
            cur++;
267
157M
        if (!cur[0])
268
16.6M
            break;
269
141M
        bstr_xappend(NULL, b, (bstr){str, cur - str});
270
141M
        if (cur[0] == '\"') {
271
1.89M
            bstr_xappend(NULL, b, (bstr){"\\\"", 2});
272
139M
        } else if (cur[0] == '\\') {
273
1.14M
            bstr_xappend(NULL, b, (bstr){"\\\\", 2});
274
138M
        } else if (cur[0] < sizeof(special_escape) && special_escape[cur[0]]) {
275
51.2M
            bstr_xappend_asprintf(NULL, b, "\\%c", special_escape[cur[0]]);
276
86.8M
        } else {
277
86.8M
            bstr_xappend_asprintf(NULL, b, "\\u%04x", (unsigned char)cur[0]);
278
86.8M
        }
279
141M
        str = cur + 1;
280
141M
    }
281
16.6M
    APPEND(b, str);
282
16.6M
    APPEND(b, "\"");
283
16.6M
}
284
285
static void add_indent(bstr *b, int indent)
286
14.2M
{
287
14.2M
    if (indent < 0)
288
7.78M
        return;
289
6.41M
    bstr_xappend(NULL, b, bstr0("\n"));
290
17.8M
    for (int n = 0; n < indent; n++)
291
11.4M
        bstr_xappend(NULL, b, bstr0(" "));
292
6.41M
}
293
294
int json_append(bstr *b, const struct mpv_node *src, int indent)
295
12.1M
{
296
12.1M
    switch (src->format) {
297
632
    case MPV_FORMAT_NONE:
298
632
        APPEND(b, "null");
299
632
        return 0;
300
140k
    case MPV_FORMAT_FLAG:
301
140k
        APPEND(b, src->u.flag ? "true" : "false");
302
140k
        return 0;
303
317k
    case MPV_FORMAT_INT64:
304
317k
        bstr_xappend_asprintf(NULL, b, "%"PRId64, src->u.int64);
305
317k
        return 0;
306
361k
    case MPV_FORMAT_DOUBLE: {
307
361k
        const char *px = (isfinite(src->u.double_) || indent == 0) ? "" : "\"";
308
361k
        bstr_xappend_asprintf(NULL, b, "%s%f%s", px, src->u.double_, px);
309
361k
        return 0;
310
0
    }
311
8.55M
    case MPV_FORMAT_STRING:
312
8.55M
        if (indent == 0)
313
417
            APPEND(b, src->u.string);
314
8.55M
        else
315
8.55M
            write_json_str(b, src->u.string);
316
8.55M
        return 0;
317
114k
    case MPV_FORMAT_NODE_ARRAY:
318
2.78M
    case MPV_FORMAT_NODE_MAP: {
319
2.78M
        struct mpv_node_list *list = src->u.list;
320
2.78M
        bool is_obj = src->format == MPV_FORMAT_NODE_MAP;
321
2.78M
        APPEND(b, is_obj ? "{" : "[");
322
2.78M
        int next_indent = indent >= 0 ? indent + 1 : -1;
323
14.2M
        for (int n = 0; n < list->num; n++) {
324
11.4M
            if (n)
325
8.67M
                APPEND(b, ",");
326
11.4M
            add_indent(b, next_indent);
327
11.4M
            if (is_obj) {
328
8.06M
                write_json_str(b, list->keys[n]);
329
8.06M
                APPEND(b, ":");
330
8.06M
            }
331
11.4M
            json_append(b, &list->values[n], next_indent);
332
11.4M
        }
333
2.78M
        add_indent(b, indent);
334
2.78M
        APPEND(b, is_obj ? "}" : "]");
335
2.78M
        return 0;
336
114k
    }
337
12.1M
    }
338
0
    return -1; // unknown format
339
12.1M
}
340
341
static int json_append_str(char **dst, struct mpv_node *src, int indent)
342
743k
{
343
743k
    bstr buffer = bstr0(*dst);
344
743k
    int r = json_append(&buffer, src, indent);
345
743k
    *dst = buffer.start;
346
743k
    return r;
347
743k
}
348
349
/* Write the contents of *src as JSON, and append the JSON string to *dst.
350
 * This will use strlen() to determine the start offset, and ta_get_size()
351
 * and ta_realloc() to extend the memory allocation of *dst.
352
 * Returns: 0 on success, <0 on failure.
353
 */
354
int json_write(char **dst, struct mpv_node *src)
355
736k
{
356
736k
    return json_append_str(dst, src, -1);
357
736k
}
358
359
// Same as json_write(), but add whitespace to make it readable.
360
int json_write_pretty(char **dst, struct mpv_node *src)
361
6.96k
{
362
6.96k
    return json_append_str(dst, src, 0);
363
6.96k
}