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