/src/fwupd/libfwupd/fwupd-json-parser.c
Line | Count | Source |
1 | | /* |
2 | | * Copyright 2025 Richard Hughes <richard@hughsie.com> |
3 | | * |
4 | | * SPDX-License-Identifier: LGPL-2.1-or-later |
5 | | */ |
6 | | |
7 | | #include "config.h" |
8 | | |
9 | | #include "fwupd-error.h" |
10 | | #include "fwupd-json-array-private.h" |
11 | | #include "fwupd-json-node-private.h" |
12 | | #include "fwupd-json-object-private.h" |
13 | | #include "fwupd-json-parser.h" |
14 | | |
15 | | /** |
16 | | * FwupdJsonParser: |
17 | | * |
18 | | * A streaming tokenizer JSON parser that is resistant to malicious input. |
19 | | * |
20 | | * One item of note is that most of the JSON string methods actually return a #GRefString -- which |
21 | | * can be used to avoid lots of tiny memory allocation when parsing JSON into other objects. |
22 | | * |
23 | | * See also: [struct@FwupdJsonArray] [struct@FwupdJsonObject] [struct@FwupdJsonNode] |
24 | | */ |
25 | | |
26 | | struct _FwupdJsonParser { |
27 | | GObject parent_instance; |
28 | | guint max_depth; |
29 | | guint max_items; |
30 | | guint max_quoted; |
31 | | }; |
32 | | |
33 | 10.9k | G_DEFINE_TYPE(FwupdJsonParser, fwupd_json_parser, G_TYPE_OBJECT) |
34 | 10.9k | |
35 | 10.9k | #define FWUPD_JSON_PARSER_NEWLINE_MAX 5 |
36 | | |
37 | 506k | #define FWUPD_JSON_PARSER_INDENT_MAX 8 /* per depth */ |
38 | | |
39 | | /** |
40 | | * fwupd_json_parser_set_max_depth: |
41 | | * @self: a #FwupdJsonParser |
42 | | * @max_depth: max nesting depth |
43 | | * |
44 | | * Sets the maximum nesting depth. |
45 | | * |
46 | | * The default maximum depth is %G_MAXUINT16, but users of #FwupdJsonParser should use this function |
47 | | * to set a better limit. |
48 | | * |
49 | | * Since: 2.1.1 |
50 | | **/ |
51 | | void |
52 | | fwupd_json_parser_set_max_depth(FwupdJsonParser *self, guint max_depth) |
53 | 2.18k | { |
54 | 2.18k | g_return_if_fail(FWUPD_IS_JSON_PARSER(self)); |
55 | 2.18k | self->max_depth = max_depth; |
56 | 2.18k | } |
57 | | |
58 | | /** |
59 | | * fwupd_json_parser_set_max_items: |
60 | | * @self: a #FwupdJsonParser |
61 | | * @max_items: max items |
62 | | * |
63 | | * Sets the maximum number of items in an array or object. |
64 | | * |
65 | | * The default maximum items is %G_MAXUINT16, but users of #FwupdJsonParser should use this function |
66 | | * to set a better limit. |
67 | | * |
68 | | * Since: 2.1.1 |
69 | | **/ |
70 | | void |
71 | | fwupd_json_parser_set_max_items(FwupdJsonParser *self, guint max_items) |
72 | 2.18k | { |
73 | 2.18k | g_return_if_fail(FWUPD_IS_JSON_PARSER(self)); |
74 | 2.18k | self->max_items = max_items; |
75 | 2.18k | } |
76 | | |
77 | | /** |
78 | | * fwupd_json_parser_set_max_quoted: |
79 | | * @self: a #FwupdJsonParser |
80 | | * @max_quoted: maximum size of a quoted string |
81 | | * |
82 | | * Sets the maximum size of a quoted string. |
83 | | * |
84 | | * The default maximum quoted string length is %G_MAXUINT16, but users of #FwupdJsonParser should |
85 | | * use this function to set a better limit. |
86 | | * |
87 | | * Since: 2.1.1 |
88 | | **/ |
89 | | void |
90 | | fwupd_json_parser_set_max_quoted(FwupdJsonParser *self, guint max_quoted) |
91 | 2.18k | { |
92 | 2.18k | g_return_if_fail(FWUPD_IS_JSON_PARSER(self)); |
93 | 2.18k | self->max_quoted = max_quoted; |
94 | 2.18k | } |
95 | | |
96 | | typedef enum { |
97 | | FWUPD_JSON_PARSER_TOKEN_INVALID = 0, |
98 | | FWUPD_JSON_PARSER_TOKEN_NULL = '0', |
99 | | FWUPD_JSON_PARSER_TOKEN_RAW = 'b', |
100 | | FWUPD_JSON_PARSER_TOKEN_STRING = '\"', |
101 | | FWUPD_JSON_PARSER_TOKEN_OBJECT_START = '{', |
102 | | FWUPD_JSON_PARSER_TOKEN_OBJECT_END = '}', |
103 | | FWUPD_JSON_PARSER_TOKEN_OBJECT_DELIM = ':', |
104 | | FWUPD_JSON_PARSER_TOKEN_ARRAY_START = '[', |
105 | | FWUPD_JSON_PARSER_TOKEN_ARRAY_END = ']', |
106 | | } FwupdJsonParserToken; |
107 | | |
108 | | typedef struct { |
109 | | FwupdJsonLoadFlags flags; |
110 | | GByteArray *buf; |
111 | | gsize buf_offset; /* into @buf */ |
112 | | GInputStream *stream; |
113 | | GString *acc; |
114 | | guint max_quoted; |
115 | | gboolean is_quoted; |
116 | | gboolean is_escape; |
117 | | guint linecnt; |
118 | | guint newlinecnt; |
119 | | guint whitespacecnt; |
120 | | guint depth; |
121 | | } FwupdJsonParserHelper; |
122 | | |
123 | | static FwupdJsonParserHelper * |
124 | | fwupd_json_parser_helper_new(FwupdJsonParser *self) |
125 | 2.18k | { |
126 | 2.18k | FwupdJsonParserHelper *helper = g_new0(FwupdJsonParserHelper, 1); |
127 | 2.18k | helper->max_quoted = self->max_quoted; |
128 | 2.18k | helper->linecnt = 1; |
129 | 2.18k | helper->buf = g_byte_array_new(); |
130 | 2.18k | helper->acc = g_string_sized_new(128); |
131 | 2.18k | helper->buf_offset = G_MAXSIZE; |
132 | 2.18k | g_byte_array_set_size(helper->buf, 32 * 1024); |
133 | 2.18k | return helper; |
134 | 2.18k | } |
135 | | |
136 | | static void |
137 | | fwupd_json_parser_helper_free(FwupdJsonParserHelper *helper) |
138 | 2.18k | { |
139 | 2.18k | if (helper->stream != NULL) |
140 | 2.18k | g_object_unref(helper->stream); |
141 | 2.18k | g_byte_array_unref(helper->buf); |
142 | 2.18k | g_string_free(helper->acc, TRUE); |
143 | 2.18k | g_free(helper); |
144 | 2.18k | } |
145 | | |
146 | | G_DEFINE_AUTOPTR_CLEANUP_FUNC(FwupdJsonParserHelper, fwupd_json_parser_helper_free) |
147 | | |
148 | | static void |
149 | | fwupd_json_parser_helper_dump_acc(FwupdJsonParserHelper *helper, |
150 | | FwupdJsonParserToken *token, |
151 | | GRefString **str) |
152 | 77.0k | { |
153 | 77.0k | if (helper->is_quoted) { |
154 | 16.8k | *token = FWUPD_JSON_PARSER_TOKEN_STRING; |
155 | 16.8k | if (str != NULL) |
156 | 16.8k | *str = g_ref_string_new_len(helper->acc->str, helper->acc->len); |
157 | 60.2k | } else { |
158 | 60.2k | if (helper->acc->len == 0) |
159 | 50.3k | return; |
160 | 9.89k | if (g_ascii_strncasecmp(helper->acc->str, "null", helper->acc->len) == 0) { |
161 | 1.71k | *token = FWUPD_JSON_PARSER_TOKEN_NULL; |
162 | 8.17k | } else { |
163 | 8.17k | *token = FWUPD_JSON_PARSER_TOKEN_RAW; |
164 | 8.17k | if (str != NULL) |
165 | 8.17k | *str = g_ref_string_new_len(helper->acc->str, helper->acc->len); |
166 | 8.17k | } |
167 | 9.89k | } |
168 | 26.7k | g_string_truncate(helper->acc, 0); |
169 | 26.7k | } |
170 | | |
171 | | static gboolean |
172 | | fwupd_json_parser_helper_slurp(FwupdJsonParserHelper *helper, GError **error) |
173 | 4.66k | { |
174 | 4.66k | gssize rc; |
175 | | |
176 | 4.66k | rc = g_input_stream_read(helper->stream, helper->buf->data, helper->buf->len, NULL, error); |
177 | 4.66k | if (rc < 0) { |
178 | 0 | fwupd_error_convert(error); |
179 | 0 | return FALSE; |
180 | 0 | } |
181 | 4.66k | if (rc == 0) { |
182 | 878 | g_set_error_literal(error, |
183 | 878 | FWUPD_ERROR, |
184 | 878 | FWUPD_ERROR_INVALID_DATA, |
185 | 878 | "incomplete data from stream"); |
186 | 878 | return FALSE; |
187 | 878 | } |
188 | 3.78k | g_byte_array_set_size(helper->buf, rc); |
189 | | |
190 | | /* success */ |
191 | 3.78k | helper->buf_offset = 0; |
192 | 3.78k | return TRUE; |
193 | 4.66k | } |
194 | | |
195 | | static gchar |
196 | | fwupd_json_parser_unescape_char(gchar data) |
197 | 927 | { |
198 | 927 | if (data == 'n') |
199 | 195 | return '\n'; |
200 | 732 | if (data == 't') |
201 | 194 | return '\t'; |
202 | 538 | if (data == '\\') |
203 | 251 | return '\\'; |
204 | 287 | if (data == '\"') |
205 | 264 | return '\"'; |
206 | 23 | return 0; |
207 | 287 | } |
208 | | |
209 | | static gboolean |
210 | | fwupd_json_parser_helper_get_next_token_chunk(FwupdJsonParserHelper *helper, |
211 | | FwupdJsonParserToken *token, |
212 | | GRefString **str, |
213 | | gboolean *buf_offset_enable, |
214 | | GError **error) |
215 | 56.8M | { |
216 | 56.8M | gchar data; |
217 | | |
218 | | /* need more data */ |
219 | 56.8M | if (G_UNLIKELY(helper->buf_offset >= helper->buf->len)) { |
220 | 4.66k | if (!fwupd_json_parser_helper_slurp(helper, error)) |
221 | 878 | return FALSE; |
222 | 4.66k | } |
223 | 56.8M | data = helper->buf->data[helper->buf_offset]; |
224 | | |
225 | | /* quotes */ |
226 | 56.8M | if (!helper->is_escape && data == '"') { |
227 | 33.8k | if (helper->is_quoted) { |
228 | 16.8k | fwupd_json_parser_helper_dump_acc(helper, token, str); |
229 | 16.8k | helper->is_quoted = FALSE; |
230 | 16.8k | return TRUE; |
231 | 16.8k | } |
232 | 16.9k | helper->is_quoted = TRUE; |
233 | 16.9k | helper->newlinecnt = 0; |
234 | 16.9k | helper->whitespacecnt = 0; |
235 | 16.9k | return TRUE; |
236 | 33.8k | } |
237 | 56.8M | if (helper->is_quoted) { |
238 | | /* escape char */ |
239 | 4.49k | if (!helper->is_escape && data == '\\') { |
240 | 934 | helper->is_escape = TRUE; |
241 | 934 | helper->newlinecnt = 0; |
242 | 934 | return TRUE; |
243 | 934 | } |
244 | 3.55k | if (G_UNLIKELY(helper->is_escape)) { |
245 | 927 | data = fwupd_json_parser_unescape_char(data); |
246 | 927 | if (G_UNLIKELY(data == 0)) { |
247 | 23 | g_set_error(error, |
248 | 23 | FWUPD_ERROR, |
249 | 23 | FWUPD_ERROR_INVALID_DATA, |
250 | 23 | "invalid escape char '%c'", |
251 | 23 | data); |
252 | 23 | return FALSE; |
253 | 23 | } |
254 | 904 | helper->is_escape = FALSE; |
255 | 904 | } |
256 | | |
257 | | /* save acc */ |
258 | 3.53k | helper->newlinecnt = 0; |
259 | 3.53k | helper->whitespacecnt = 0; |
260 | 3.53k | g_string_append_c(helper->acc, data); |
261 | 3.53k | if (G_UNLIKELY(helper->max_quoted > 0 && helper->acc->len > helper->max_quoted)) { |
262 | 43 | g_set_error(error, |
263 | 43 | FWUPD_ERROR, |
264 | 43 | FWUPD_ERROR_INVALID_DATA, |
265 | 43 | "token too long, limit was %u", |
266 | 43 | helper->max_quoted); |
267 | 43 | return FALSE; |
268 | 43 | } |
269 | 3.49k | return TRUE; |
270 | 3.53k | } |
271 | | |
272 | | /* newline, for error messages */ |
273 | 56.7M | if (data == '\n') { |
274 | 224 | helper->linecnt++; |
275 | 224 | if (helper->newlinecnt++ > FWUPD_JSON_PARSER_NEWLINE_MAX) { |
276 | 1 | g_set_error(error, |
277 | 1 | FWUPD_ERROR, |
278 | 1 | FWUPD_ERROR_INVALID_DATA, |
279 | 1 | "too many newlines, limit was %u", |
280 | 1 | (guint)FWUPD_JSON_PARSER_NEWLINE_MAX); |
281 | 1 | return FALSE; |
282 | 1 | } |
283 | 223 | fwupd_json_parser_helper_dump_acc(helper, token, str); |
284 | 223 | return TRUE; |
285 | 224 | } |
286 | 56.7M | helper->newlinecnt = 0; |
287 | | |
288 | | /* split */ |
289 | 56.7M | if (data == ',') { |
290 | 2.14k | fwupd_json_parser_helper_dump_acc(helper, token, str); |
291 | 2.14k | return TRUE; |
292 | 2.14k | } |
293 | | |
294 | | /* control token */ |
295 | 56.7M | if (data == FWUPD_JSON_PARSER_TOKEN_ARRAY_START || |
296 | 56.7M | data == FWUPD_JSON_PARSER_TOKEN_ARRAY_END || |
297 | 56.7M | data == FWUPD_JSON_PARSER_TOKEN_OBJECT_START || |
298 | 56.7M | data == FWUPD_JSON_PARSER_TOKEN_OBJECT_DELIM || |
299 | 56.7M | data == FWUPD_JSON_PARSER_TOKEN_OBJECT_END) { |
300 | 57.8k | fwupd_json_parser_helper_dump_acc(helper, token, str); |
301 | 57.8k | if (*token != FWUPD_JSON_PARSER_TOKEN_INVALID) { |
302 | 7.98k | *buf_offset_enable = FALSE; |
303 | 7.98k | return TRUE; |
304 | 7.98k | } |
305 | 49.8k | *token = (FwupdJsonParserToken)data; |
306 | 49.8k | helper->newlinecnt = 0; |
307 | 49.8k | helper->whitespacecnt = 0; |
308 | 49.8k | return TRUE; |
309 | 57.8k | } |
310 | | |
311 | | /* whitespace */ |
312 | 56.7M | if (g_ascii_isspace(data)) { |
313 | 506k | const guint8 *buf = helper->buf->data; |
314 | 506k | gsize bufsz = helper->buf->len; |
315 | 506k | gsize offset = helper->buf_offset + 1; |
316 | 506k | guint whitespace_max = FWUPD_JSON_PARSER_INDENT_MAX * (helper->depth + 1); |
317 | | |
318 | | /* the most likely next char is another space */ |
319 | 632k | for (; offset < bufsz; offset++) { |
320 | 632k | if (buf[offset] != ' ') |
321 | 506k | break; |
322 | 632k | } |
323 | 506k | helper->whitespacecnt += (offset - helper->buf_offset) - 1; |
324 | 506k | helper->buf_offset = offset - 1; |
325 | 506k | if (helper->whitespacecnt++ >= whitespace_max) { |
326 | 32 | g_set_error(error, |
327 | 32 | FWUPD_ERROR, |
328 | 32 | FWUPD_ERROR_INVALID_DATA, |
329 | 32 | "too much whitespace, limit was %u", |
330 | 32 | whitespace_max); |
331 | 32 | return FALSE; |
332 | 32 | } |
333 | 506k | return TRUE; |
334 | 506k | } |
335 | | |
336 | | /* strip control chars */ |
337 | 56.2M | if (g_ascii_iscntrl(data)) { |
338 | 16 | g_set_error(error, |
339 | 16 | FWUPD_ERROR, |
340 | 16 | FWUPD_ERROR_INVALID_DATA, |
341 | 16 | "ASCII control character detected 0x%x", |
342 | 16 | (guint)data); |
343 | 16 | return FALSE; |
344 | 16 | } |
345 | | |
346 | | /* save acc */ |
347 | 56.2M | g_string_append_c(helper->acc, data); |
348 | 56.2M | helper->whitespacecnt = 0; |
349 | 56.2M | return TRUE; |
350 | 56.2M | } |
351 | | |
352 | | static gboolean |
353 | | fwupd_json_parser_helper_get_next_token(FwupdJsonParserHelper *helper, |
354 | | FwupdJsonParserToken *token, |
355 | | GRefString **str, |
356 | | GError **error) |
357 | 77.6k | { |
358 | | /* process each byte until we get a token */ |
359 | 56.9M | while (*token == FWUPD_JSON_PARSER_TOKEN_INVALID) { |
360 | 56.8M | gboolean buf_offset_enable = TRUE; |
361 | 56.8M | if (!fwupd_json_parser_helper_get_next_token_chunk(helper, |
362 | 56.8M | token, |
363 | 56.8M | str, |
364 | 56.8M | &buf_offset_enable, |
365 | 56.8M | error)) |
366 | 993 | return FALSE; |
367 | 56.8M | if (buf_offset_enable) |
368 | 56.8M | helper->buf_offset++; |
369 | 56.8M | } |
370 | | |
371 | | /* success */ |
372 | 76.6k | return TRUE; |
373 | 77.6k | } |
374 | | |
375 | | static gboolean |
376 | | fwupd_json_parser_helper_check_depth(FwupdJsonParser *self, guint depth, GError **error) |
377 | 19.6k | { |
378 | 19.6k | if (self->max_depth > 0 && depth > self->max_depth) { |
379 | 23 | g_set_error(error, |
380 | 23 | FWUPD_ERROR, |
381 | 23 | FWUPD_ERROR_INVALID_DATA, |
382 | 23 | "structure too deep, limit was %u", |
383 | 23 | depth); |
384 | 23 | return FALSE; |
385 | 23 | } |
386 | 19.6k | return TRUE; |
387 | 19.6k | } |
388 | | |
389 | | static FwupdJsonObject * |
390 | | fwupd_json_parser_load_object(FwupdJsonParser *self, FwupdJsonParserHelper *helper, GError **error); |
391 | | |
392 | | static FwupdJsonArray * |
393 | | fwupd_json_parser_load_array(FwupdJsonParser *self, FwupdJsonParserHelper *helper, GError **error) |
394 | 10.2k | { |
395 | 10.2k | g_autoptr(FwupdJsonArray) json_arr = fwupd_json_array_new(); |
396 | | |
397 | 10.2k | if (G_UNLIKELY(!fwupd_json_parser_helper_check_depth(self, ++helper->depth, error))) |
398 | 19 | return NULL; |
399 | 30.6k | while (TRUE) { |
400 | 30.6k | g_autoptr(GRefString) str = NULL; |
401 | 30.6k | FwupdJsonParserToken token = FWUPD_JSON_PARSER_TOKEN_INVALID; |
402 | | |
403 | 30.6k | if (!fwupd_json_parser_helper_get_next_token(helper, &token, &str, error)) |
404 | 136 | return NULL; |
405 | 30.5k | if (token == FWUPD_JSON_PARSER_TOKEN_ARRAY_END) |
406 | 9.70k | break; |
407 | 20.8k | if (token == FWUPD_JSON_PARSER_TOKEN_OBJECT_START) { |
408 | 4.83k | g_autoptr(FwupdJsonObject) json_obj = |
409 | 4.83k | fwupd_json_parser_load_object(self, helper, error); |
410 | 4.83k | if (G_UNLIKELY(json_obj == NULL)) |
411 | 54 | return NULL; |
412 | 4.77k | fwupd_json_array_add_object(json_arr, json_obj); |
413 | 16.0k | } else if (token == FWUPD_JSON_PARSER_TOKEN_ARRAY_START) { |
414 | 7.62k | g_autoptr(FwupdJsonArray) json_array2 = |
415 | 7.62k | fwupd_json_parser_load_array(self, helper, error); |
416 | 7.62k | if (G_UNLIKELY(json_array2 == NULL)) |
417 | 342 | return NULL; |
418 | 7.28k | fwupd_json_array_add_array(json_arr, json_array2); |
419 | 8.40k | } else if (token == FWUPD_JSON_PARSER_TOKEN_STRING) { |
420 | 2.68k | fwupd_json_array_add_string_internal(json_arr, str); |
421 | 5.71k | } else if (token == FWUPD_JSON_PARSER_TOKEN_RAW) { |
422 | 5.69k | if (G_UNLIKELY(str == NULL)) { |
423 | 0 | g_set_error(error, |
424 | 0 | FWUPD_ERROR, |
425 | 0 | FWUPD_ERROR_INVALID_DATA, |
426 | 0 | "no raw data on line %u", |
427 | 0 | helper->linecnt); |
428 | 0 | return NULL; |
429 | 0 | } |
430 | 5.69k | fwupd_json_array_add_raw_internal(json_arr, str); |
431 | 5.69k | } else { |
432 | 17 | g_set_error_literal(error, |
433 | 17 | FWUPD_ERROR, |
434 | 17 | FWUPD_ERROR_INVALID_DATA, |
435 | 17 | "object delimiter not expected in array"); |
436 | 17 | return NULL; |
437 | 17 | } |
438 | 20.4k | if (G_UNLIKELY(self->max_items > 0 && |
439 | 20.4k | fwupd_json_array_get_size(json_arr) > self->max_items)) { |
440 | 9 | g_set_error(error, |
441 | 9 | FWUPD_ERROR, |
442 | 9 | FWUPD_ERROR_INVALID_DATA, |
443 | 9 | "too many items in array, limit was %u", |
444 | 9 | self->max_items); |
445 | 9 | return NULL; |
446 | 9 | } |
447 | 20.4k | } |
448 | 9.70k | helper->depth--; |
449 | | |
450 | | /* success */ |
451 | 9.70k | return g_steal_pointer(&json_arr); |
452 | 10.2k | } |
453 | | |
454 | | static FwupdJsonObject * |
455 | | fwupd_json_parser_load_object(FwupdJsonParser *self, FwupdJsonParserHelper *helper, GError **error) |
456 | 9.41k | { |
457 | 9.41k | g_autoptr(FwupdJsonObject) json_obj = fwupd_json_object_new(); |
458 | | |
459 | 9.41k | if (!fwupd_json_parser_helper_check_depth(self, ++helper->depth, error)) |
460 | 4 | return NULL; |
461 | 21.1k | while (TRUE) { |
462 | 21.1k | FwupdJsonParserToken token1 = FWUPD_JSON_PARSER_TOKEN_INVALID; |
463 | 21.1k | FwupdJsonParserToken token2 = FWUPD_JSON_PARSER_TOKEN_INVALID; |
464 | 21.1k | FwupdJsonParserToken token3 = FWUPD_JSON_PARSER_TOKEN_INVALID; |
465 | 21.1k | g_autoptr(GRefString) key = NULL; |
466 | 21.1k | g_autoptr(GRefString) val = NULL; |
467 | | |
468 | | /* "key" : value */ |
469 | 21.1k | if (!fwupd_json_parser_helper_get_next_token(helper, &token1, &key, error)) |
470 | 631 | return NULL; |
471 | 20.4k | if (token1 == FWUPD_JSON_PARSER_TOKEN_OBJECT_END) |
472 | 8.42k | break; |
473 | 12.0k | if (G_UNLIKELY(token1 != FWUPD_JSON_PARSER_TOKEN_STRING)) { |
474 | 243 | g_set_error(error, |
475 | 243 | FWUPD_ERROR, |
476 | 243 | FWUPD_ERROR_INVALID_DATA, |
477 | 243 | "object key '%s' must be quoted on line %u", |
478 | 243 | key, |
479 | 243 | helper->linecnt); |
480 | 243 | return NULL; |
481 | 243 | } |
482 | 11.8k | if (!fwupd_json_parser_helper_get_next_token(helper, &token2, NULL, error)) |
483 | 11 | return NULL; |
484 | 11.8k | if (token2 != FWUPD_JSON_PARSER_TOKEN_OBJECT_DELIM) { |
485 | 6 | g_set_error(error, |
486 | 6 | FWUPD_ERROR, |
487 | 6 | FWUPD_ERROR_INVALID_DATA, |
488 | 6 | "did not find object delimiter on line %u", |
489 | 6 | helper->linecnt); |
490 | 6 | return NULL; |
491 | 6 | } |
492 | 11.7k | if (!fwupd_json_parser_helper_get_next_token(helper, &token3, &val, error)) |
493 | 2 | return NULL; |
494 | | |
495 | 11.7k | if (token3 == FWUPD_JSON_PARSER_TOKEN_OBJECT_START) { |
496 | 3.29k | g_autoptr(FwupdJsonObject) json_obj2 = |
497 | 3.29k | fwupd_json_parser_load_object(self, helper, error); |
498 | 3.29k | if (G_UNLIKELY(json_obj2 == NULL)) |
499 | 78 | return NULL; |
500 | 3.22k | fwupd_json_object_add_object_internal(json_obj, key, json_obj2); |
501 | 8.49k | } else if (token3 == FWUPD_JSON_PARSER_TOKEN_ARRAY_START) { |
502 | 2.17k | g_autoptr(FwupdJsonArray) json_array2 = |
503 | 2.17k | fwupd_json_parser_load_array(self, helper, error); |
504 | 2.17k | if (G_UNLIKELY(json_array2 == NULL)) |
505 | 10 | return NULL; |
506 | 2.16k | fwupd_json_object_add_array_internal(json_obj, key, json_array2); |
507 | 6.31k | } else if (token3 == FWUPD_JSON_PARSER_TOKEN_STRING) { |
508 | 2.24k | fwupd_json_object_add_string_internal(json_obj, key, val, helper->flags); |
509 | 4.06k | } else if (token3 == FWUPD_JSON_PARSER_TOKEN_NULL) { |
510 | 1.69k | fwupd_json_object_add_null_internal(json_obj, key, helper->flags); |
511 | 2.36k | } else { |
512 | 2.36k | if (G_UNLIKELY(val == NULL)) { |
513 | 3 | g_set_error(error, |
514 | 3 | FWUPD_ERROR, |
515 | 3 | FWUPD_ERROR_INVALID_DATA, |
516 | 3 | "did not find raw value on line %u", |
517 | 3 | helper->linecnt); |
518 | 3 | return NULL; |
519 | 3 | } |
520 | 2.36k | fwupd_json_object_add_raw_internal(json_obj, key, val, helper->flags); |
521 | 2.36k | } |
522 | 11.7k | if (G_UNLIKELY(self->max_items > 0 && |
523 | 11.7k | fwupd_json_object_get_size(json_obj) > self->max_items)) { |
524 | 2 | g_set_error(error, |
525 | 2 | FWUPD_ERROR, |
526 | 2 | FWUPD_ERROR_INVALID_DATA, |
527 | 2 | "too many items in object, limit was %u", |
528 | 2 | self->max_items); |
529 | 2 | return NULL; |
530 | 2 | } |
531 | 11.7k | } |
532 | 8.42k | helper->depth--; |
533 | | |
534 | | /* success */ |
535 | 8.42k | return g_steal_pointer(&json_obj); |
536 | 9.40k | } |
537 | | |
538 | | static FwupdJsonNode * |
539 | | fwupd_json_parser_load_from_stream_internal(FwupdJsonParser *self, |
540 | | FwupdJsonParserHelper *helper, |
541 | | GError **error) |
542 | 2.18k | { |
543 | 2.18k | FwupdJsonParserToken token = FWUPD_JSON_PARSER_TOKEN_INVALID; |
544 | 2.18k | g_autoptr(GRefString) str = NULL; |
545 | | |
546 | 2.18k | #ifndef SUPPORTED_BUILD |
547 | | /* runtime warnings */ |
548 | 2.18k | if (self->max_depth == G_MAXUINT16) |
549 | 0 | g_warning("using the default max depth; use fwupd_json_parser_set_max_depth()"); |
550 | 2.18k | if (self->max_items == G_MAXUINT16) |
551 | 0 | g_warning("using the default max items; use fwupd_json_parser_set_max_items()"); |
552 | 2.18k | if (self->max_quoted == G_MAXUINT16) |
553 | 0 | g_warning("using the default max quoted; use fwupd_json_parser_set_max_quoted()"); |
554 | 2.18k | #endif |
555 | | |
556 | 2.18k | if (!fwupd_json_parser_helper_get_next_token(helper, &token, &str, error)) |
557 | 213 | return NULL; |
558 | 1.97k | if (token == FWUPD_JSON_PARSER_TOKEN_OBJECT_START) { |
559 | 1.27k | g_autoptr(FwupdJsonObject) json_obj = NULL; |
560 | 1.27k | json_obj = fwupd_json_parser_load_object(self, helper, error); |
561 | 1.27k | if (json_obj == NULL) |
562 | 858 | return NULL; |
563 | 420 | return fwupd_json_node_new_object(json_obj); |
564 | 1.27k | } |
565 | 692 | if (token == FWUPD_JSON_PARSER_TOKEN_ARRAY_START) { |
566 | 484 | g_autoptr(FwupdJsonArray) json_arr = NULL; |
567 | 484 | json_arr = fwupd_json_parser_load_array(self, helper, error); |
568 | 484 | if (json_arr == NULL) |
569 | 225 | return NULL; |
570 | 259 | return fwupd_json_node_new_array(json_arr); |
571 | 484 | } |
572 | 208 | if (token == FWUPD_JSON_PARSER_TOKEN_STRING) |
573 | 104 | return fwupd_json_node_new_string_internal(str); |
574 | 104 | if (token == FWUPD_JSON_PARSER_TOKEN_RAW) |
575 | 96 | return fwupd_json_node_new_raw_internal(str); |
576 | | |
577 | | /* failed */ |
578 | 8 | g_set_error_literal(error, |
579 | 8 | FWUPD_ERROR, |
580 | 8 | FWUPD_ERROR_INVALID_DATA, |
581 | 8 | "invalid JSON; token was not object, array, string or raw"); |
582 | 8 | return NULL; |
583 | 104 | } |
584 | | |
585 | | /** |
586 | | * fwupd_json_parser_load_from_bytes: (skip): |
587 | | * @self: a #FwupdJsonParser |
588 | | * @blob: a #GBytes |
589 | | * @flags: a #FwupdJsonLoadFlags |
590 | | * @error: (nullable): optional return location for an error |
591 | | * |
592 | | * Loads JSON from a string. |
593 | | * |
594 | | * Returns: (transfer full): a #FwupdJsonNode, or %NULL for error |
595 | | * |
596 | | * Since: 2.1.1 |
597 | | **/ |
598 | | FwupdJsonNode * |
599 | | fwupd_json_parser_load_from_bytes(FwupdJsonParser *self, |
600 | | GBytes *blob, |
601 | | FwupdJsonLoadFlags flags, |
602 | | GError **error) |
603 | 0 | { |
604 | 0 | g_autoptr(FwupdJsonParserHelper) helper = fwupd_json_parser_helper_new(self); |
605 | |
|
606 | 0 | g_return_val_if_fail(FWUPD_IS_JSON_PARSER(self), NULL); |
607 | 0 | g_return_val_if_fail(blob != NULL, NULL); |
608 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
609 | | |
610 | 0 | helper->flags = flags; |
611 | 0 | helper->stream = g_memory_input_stream_new_from_bytes(blob); |
612 | 0 | return fwupd_json_parser_load_from_stream_internal(self, helper, error); |
613 | 0 | } |
614 | | |
615 | | /** |
616 | | * fwupd_json_parser_load_from_data: (skip): |
617 | | * @self: a #FwupdJsonParser |
618 | | * @text: a string |
619 | | * @flags: a #FwupdJsonLoadFlags |
620 | | * @error: (nullable): optional return location for an error |
621 | | * |
622 | | * Loads JSON from a string. |
623 | | * |
624 | | * Returns: (transfer full): a #FwupdJsonNode, or %NULL for error |
625 | | * |
626 | | * Since: 2.1.1 |
627 | | **/ |
628 | | FwupdJsonNode * |
629 | | fwupd_json_parser_load_from_data(FwupdJsonParser *self, |
630 | | const gchar *text, |
631 | | FwupdJsonLoadFlags flags, |
632 | | GError **error) |
633 | 0 | { |
634 | 0 | g_autoptr(FwupdJsonParserHelper) helper = fwupd_json_parser_helper_new(self); |
635 | |
|
636 | 0 | g_return_val_if_fail(FWUPD_IS_JSON_PARSER(self), NULL); |
637 | 0 | g_return_val_if_fail(text != NULL, NULL); |
638 | 0 | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
639 | | |
640 | 0 | helper->flags = flags; |
641 | 0 | helper->stream = g_memory_input_stream_new_from_data(text, strlen(text), NULL); |
642 | 0 | return fwupd_json_parser_load_from_stream_internal(self, helper, error); |
643 | 0 | } |
644 | | |
645 | | /** |
646 | | * fwupd_json_parser_load_from_stream: (skip): |
647 | | * @self: a #FwupdJsonParser |
648 | | * @stream: a #GInputStream |
649 | | * @flags: a #FwupdJsonLoadFlags |
650 | | * @error: (nullable): optional return location for an error |
651 | | * |
652 | | * Loads JSON from a stream. |
653 | | * |
654 | | * Returns: (transfer full): a #FwupdJsonNode, or %NULL for error |
655 | | * |
656 | | * Since: 2.1.1 |
657 | | **/ |
658 | | FwupdJsonNode * |
659 | | fwupd_json_parser_load_from_stream(FwupdJsonParser *self, |
660 | | GInputStream *stream, |
661 | | FwupdJsonLoadFlags flags, |
662 | | GError **error) |
663 | 2.18k | { |
664 | 2.18k | g_autoptr(FwupdJsonParserHelper) helper = fwupd_json_parser_helper_new(self); |
665 | | |
666 | 2.18k | g_return_val_if_fail(FWUPD_IS_JSON_PARSER(self), NULL); |
667 | 2.18k | g_return_val_if_fail(G_IS_INPUT_STREAM(stream), NULL); |
668 | 2.18k | g_return_val_if_fail(error == NULL || *error == NULL, NULL); |
669 | | |
670 | | /* seek to start if possible */ |
671 | 2.18k | if (G_IS_SEEKABLE(stream) && g_seekable_can_seek(G_SEEKABLE(stream))) { |
672 | 2.18k | if (!g_seekable_seek(G_SEEKABLE(stream), 0x0, G_SEEK_SET, NULL, error)) { |
673 | 0 | fwupd_error_convert(error); |
674 | 0 | return NULL; |
675 | 0 | } |
676 | 2.18k | } |
677 | 2.18k | helper->stream = g_object_ref(stream); |
678 | 2.18k | helper->flags = flags; |
679 | 2.18k | return fwupd_json_parser_load_from_stream_internal(self, helper, error); |
680 | 2.18k | } |
681 | | |
682 | | static void |
683 | | fwupd_json_parser_class_init(FwupdJsonParserClass *klass) |
684 | 1 | { |
685 | 1 | } |
686 | | |
687 | | static void |
688 | | fwupd_json_parser_init(FwupdJsonParser *self) |
689 | 2.18k | { |
690 | 2.18k | self->max_depth = G_MAXUINT16; |
691 | 2.18k | self->max_items = G_MAXUINT16; |
692 | 2.18k | self->max_quoted = G_MAXUINT16; |
693 | 2.18k | } |
694 | | |
695 | | /** |
696 | | * fwupd_json_parser_new: |
697 | | * |
698 | | * Returns: (transfer full): a #FwupdJsonParser |
699 | | * |
700 | | * Since: 2.1.1 |
701 | | **/ |
702 | | FwupdJsonParser * |
703 | | fwupd_json_parser_new(void) |
704 | 2.18k | { |
705 | 2.18k | return g_object_new(FWUPD_TYPE_JSON_PARSER, NULL); |
706 | 2.18k | } |