Coverage Report

Created: 2026-06-15 06:54

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/fwupd/libfwupd/fwupd-jcat-file.c
Line
Count
Source
1
/*
2
 * Copyright 2020 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-codec.h"
10
#include "fwupd-error.h"
11
#include "fwupd-jcat-file.h"
12
#include "fwupd-json-array.h"
13
#include "fwupd-json-parser.h"
14
15
struct _FwupdJcatFile {
16
  GObject parent_instance;
17
  GPtrArray *items;
18
  guint32 version_major;
19
  guint32 version_minor;
20
};
21
22
static void
23
fwupd_jcat_file_codec_iface_init(FwupdCodecInterface *iface);
24
25
0
G_DEFINE_TYPE_EXTENDED(FwupdJcatFile,
26
0
           fwupd_jcat_file,
27
0
           G_TYPE_OBJECT,
28
0
           0,
29
0
           G_IMPLEMENT_INTERFACE(FWUPD_TYPE_CODEC, fwupd_jcat_file_codec_iface_init));
30
0
31
0
static void
32
0
fwupd_jcat_file_add_string(FwupdCodec *codec, guint idt, GString *str)
33
0
{
34
0
  FwupdJcatFile *self = FWUPD_JCAT_FILE(codec);
35
36
0
  if (self->version_major > 0 || self->version_minor > 0) {
37
0
    g_autofree gchar *version = NULL;
38
0
    version = g_strdup_printf("%u.%u", self->version_major, self->version_minor);
39
0
    fwupd_codec_string_append(str, idt, "Version", version);
40
0
  }
41
0
  for (guint i = 0; i < self->items->len; i++) {
42
0
    FwupdJcatItem *item = g_ptr_array_index(self->items, i);
43
0
    fwupd_codec_add_string(FWUPD_CODEC(item), idt, str);
44
0
  }
45
0
}
46
47
static gboolean
48
fwupd_jcat_file_import_node(FwupdJcatFile *self, FwupdJsonNode *json_node, GError **error)
49
0
{
50
0
  gint64 version = 0;
51
0
  g_autoptr(FwupdJsonObject) json_obj = NULL;
52
0
  g_autoptr(FwupdJsonArray) json_array = NULL;
53
54
  /* get version */
55
0
  json_obj = fwupd_json_node_get_object(json_node, error);
56
0
  if (json_obj == NULL)
57
0
    return FALSE;
58
0
  if (!fwupd_json_object_get_integer(json_obj, "JcatVersionMajor", &version, error))
59
0
    return FALSE;
60
0
  if (version < 0 || version > G_MAXUINT32) {
61
0
    g_set_error_literal(error,
62
0
            FWUPD_ERROR,
63
0
            FWUPD_ERROR_INVALID_DATA,
64
0
            "invalid major version");
65
0
    return FALSE;
66
0
  }
67
0
  self->version_major = (guint32)version;
68
0
  if (!fwupd_json_object_get_integer(json_obj, "JcatVersionMinor", &version, error))
69
0
    return FALSE;
70
0
  if (version < 0 || version > G_MAXUINT32) {
71
0
    g_set_error_literal(error,
72
0
            FWUPD_ERROR,
73
0
            FWUPD_ERROR_INVALID_DATA,
74
0
            "invalid minor version");
75
0
    return FALSE;
76
0
  }
77
0
  self->version_minor = (guint32)version;
78
79
  /* get items */
80
0
  json_array = fwupd_json_object_get_array(json_obj, "Items", error);
81
0
  if (json_array == NULL)
82
0
    return FALSE;
83
0
  for (guint i = 0; i < fwupd_json_array_get_size(json_array); i++) {
84
0
    g_autoptr(FwupdJsonObject) json_item = NULL;
85
0
    g_autoptr(FwupdJcatItem) item = g_object_new(FWUPD_TYPE_JCAT_ITEM, NULL);
86
87
0
    json_item = fwupd_json_array_get_object(json_array, i, error);
88
0
    if (json_item == NULL)
89
0
      return FALSE;
90
0
    if (!fwupd_codec_from_json(FWUPD_CODEC(item), json_item, error))
91
0
      return FALSE;
92
0
    fwupd_jcat_file_add_item(self, item);
93
0
  }
94
95
  /* success */
96
0
  return TRUE;
97
0
}
98
99
static FwupdJsonObject *
100
fwupd_jcat_file_export_builder(FwupdJcatFile *self, FwupdCodecFlags flags)
101
0
{
102
0
  g_autoptr(FwupdJsonObject) json_obj = fwupd_json_object_new();
103
0
  g_autoptr(FwupdJsonArray) json_array = fwupd_json_array_new();
104
105
  /* add metadata */
106
0
  fwupd_json_object_add_integer(json_obj, "JcatVersionMajor", self->version_major);
107
0
  fwupd_json_object_add_integer(json_obj, "JcatVersionMinor", self->version_minor);
108
109
  /* add items */
110
0
  for (guint i = 0; i < self->items->len; i++) {
111
0
    FwupdJcatItem *item = g_ptr_array_index(self->items, i);
112
0
    g_autoptr(FwupdJsonObject) json_item = fwupd_json_object_new();
113
0
    fwupd_codec_to_json(FWUPD_CODEC(item), json_item, flags);
114
0
    fwupd_json_array_add_object(json_array, json_item);
115
0
  }
116
0
  fwupd_json_object_add_array(json_obj, "Items", json_array);
117
118
  /* success */
119
0
  return g_steal_pointer(&json_obj);
120
0
}
121
122
static FwupdJsonParser *
123
fwupd_jcat_file_json_parser_new(void)
124
0
{
125
0
  g_autoptr(FwupdJsonParser) json_parser = fwupd_json_parser_new();
126
0
  fwupd_json_parser_set_max_depth(json_parser, 5);
127
0
  fwupd_json_parser_set_max_items(json_parser, 1000);
128
0
  fwupd_json_parser_set_max_quoted(json_parser, 100 * 1024);
129
0
  return g_steal_pointer(&json_parser);
130
0
}
131
132
/**
133
 * fwupd_jcat_file_import_json:
134
 * @self: #FwupdJcatFile
135
 * @json: (not nullable): JSON data
136
 * @error: #GError, or %NULL
137
 *
138
 * Imports a FwupdJcat file from raw JSON.
139
 *
140
 * Returns: %TRUE for success
141
 *
142
 * Since: 2.1.3
143
 **/
144
gboolean
145
fwupd_jcat_file_import_json(FwupdJcatFile *self, const gchar *json, GError **error)
146
0
{
147
0
  g_autoptr(FwupdJsonNode) json_node = NULL;
148
0
  g_autoptr(FwupdJsonParser) json_parser = fwupd_jcat_file_json_parser_new();
149
150
0
  g_return_val_if_fail(FWUPD_IS_JCAT_FILE(self), FALSE);
151
0
  g_return_val_if_fail(json != NULL, FALSE);
152
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
153
154
0
  json_node =
155
0
      fwupd_json_parser_load_from_data(json_parser, json, FWUPD_JSON_LOAD_FLAG_NONE, error);
156
0
  if (json_node == NULL)
157
0
    return FALSE;
158
0
  return fwupd_jcat_file_import_node(self, json_node, error);
159
0
}
160
161
/**
162
 * fwupd_jcat_file_import_stream:
163
 * @self: #FwupdJcatFile
164
 * @istream: (not nullable): #GInputStream
165
 * @error: #GError, or %NULL
166
 *
167
 * Imports a compressed FwupdJcat file from a file.
168
 *
169
 * Returns: %TRUE for success
170
 *
171
 * Since: 2.1.3
172
 **/
173
gboolean
174
fwupd_jcat_file_import_stream(FwupdJcatFile *self, GInputStream *istream, GError **error)
175
0
{
176
0
  g_autoptr(FwupdJsonNode) json_node = NULL;
177
0
  g_autoptr(FwupdJsonParser) json_parser = fwupd_jcat_file_json_parser_new();
178
0
  g_autoptr(GConverter) conv = NULL;
179
0
  g_autoptr(GInputStream) istream_uncompressed = NULL;
180
181
0
  g_return_val_if_fail(FWUPD_IS_JCAT_FILE(self), FALSE);
182
0
  g_return_val_if_fail(G_IS_INPUT_STREAM(istream), FALSE);
183
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
184
185
0
  conv = G_CONVERTER(g_zlib_decompressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP));
186
0
  istream_uncompressed = g_converter_input_stream_new(istream, conv);
187
0
  g_filter_input_stream_set_close_base_stream(G_FILTER_INPUT_STREAM(istream_uncompressed),
188
0
                FALSE);
189
0
  json_node = fwupd_json_parser_load_from_stream(json_parser,
190
0
                   istream_uncompressed,
191
0
                   FWUPD_JSON_LOAD_FLAG_NONE,
192
0
                   error);
193
0
  if (json_node == NULL)
194
0
    return FALSE;
195
0
  return fwupd_jcat_file_import_node(self, json_node, error);
196
0
}
197
198
/**
199
 * fwupd_jcat_file_import_bytes:
200
 * @self: #FwupdJcatFile
201
 * @blob: (not nullable): a #GBytes
202
 * @error: #GError, or %NULL
203
 *
204
 * Imports a compressed FwupdJcat file from a blob of data.
205
 *
206
 * Returns: %TRUE for success
207
 *
208
 * Since: 2.1.3
209
 **/
210
gboolean
211
fwupd_jcat_file_import_bytes(FwupdJcatFile *self, GBytes *blob, GError **error)
212
0
{
213
0
  g_autoptr(GInputStream) istream = NULL;
214
215
0
  g_return_val_if_fail(FWUPD_IS_JCAT_FILE(self), FALSE);
216
0
  g_return_val_if_fail(blob != NULL, FALSE);
217
0
  g_return_val_if_fail(error == NULL || *error == NULL, FALSE);
218
219
0
  istream = g_memory_input_stream_new_from_bytes(blob);
220
0
  return fwupd_jcat_file_import_stream(self, istream, error);
221
0
}
222
223
/**
224
 * fwupd_jcat_file_export_json:
225
 * @self: #FwupdJcatFile
226
 * @flags: a #FwupdCodecFlags, typically %FWUPD_CODEC_FLAG_NONE
227
 * @error: #GError, or %NULL
228
 *
229
 * Exports a FwupdJcat file to raw JSON.
230
 *
231
 * Returns: (transfer full): JSON output, or %NULL for error
232
 *
233
 * Since: 2.1.3
234
 **/
235
gchar *
236
fwupd_jcat_file_export_json(FwupdJcatFile *self, FwupdCodecFlags flags, GError **error)
237
0
{
238
0
  g_autoptr(FwupdJsonObject) json_obj = NULL;
239
0
  g_autoptr(GString) str = NULL;
240
241
0
  g_return_val_if_fail(FWUPD_IS_JCAT_FILE(self), NULL);
242
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
243
244
0
  json_obj = fwupd_jcat_file_export_builder(self, flags);
245
0
  str = fwupd_json_object_to_string(json_obj, FWUPD_JSON_EXPORT_FLAG_INDENT);
246
0
  return g_string_free(g_steal_pointer(&str), FALSE);
247
0
}
248
249
/**
250
 * fwupd_jcat_file_export_bytes:
251
 * @self: #FwupdJcatFile
252
 * @error: #GError, or %NULL
253
 *
254
 * Exports a FwupdJcat file to a compressed blob.
255
 *
256
 * Returns: (transfer full): a #GBytes
257
 *
258
 * Since: 2.1.3
259
 **/
260
GBytes *
261
fwupd_jcat_file_export_bytes(FwupdJcatFile *self, GError **error)
262
0
{
263
0
  g_autoptr(FwupdJsonObject) json_obj = NULL;
264
0
  g_autoptr(GConverter) converter = NULL;
265
0
  g_autoptr(GBytes) blob = NULL;
266
267
0
  g_return_val_if_fail(FWUPD_IS_JCAT_FILE(self), NULL);
268
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
269
270
  /* export all */
271
0
  json_obj = fwupd_jcat_file_export_builder(self, FWUPD_CODEC_FLAG_NONE);
272
0
  blob = fwupd_json_object_to_bytes(json_obj, FWUPD_JSON_EXPORT_FLAG_INDENT);
273
274
  /* compress blob */
275
0
  converter = G_CONVERTER(g_zlib_compressor_new(G_ZLIB_COMPRESSOR_FORMAT_GZIP, -1));
276
#if GLIB_CHECK_VERSION(2, 82, 0)
277
  return g_converter_convert_bytes(converter, blob, error);
278
#else
279
0
  {
280
0
    guint8 tmp[0x8000]; /* nocheck:zero-init */
281
0
    g_autoptr(GInputStream) istream1 = NULL;
282
0
    g_autoptr(GInputStream) istream2 = NULL;
283
0
    g_autoptr(GByteArray) buf = g_byte_array_new();
284
0
    g_autoptr(GError) error_local = NULL;
285
286
0
    istream1 = g_memory_input_stream_new_from_bytes(blob);
287
0
    istream2 = g_converter_input_stream_new(istream1, converter);
288
0
    while (TRUE) {
289
0
      gssize sz;
290
0
      sz = g_input_stream_read(istream2, tmp, sizeof(tmp), NULL, &error_local);
291
0
      if (sz == 0)
292
0
        break;
293
0
      if (sz < 0) {
294
0
        g_set_error_literal(error,
295
0
                FWUPD_ERROR,
296
0
                FWUPD_ERROR_INVALID_FILE,
297
0
                error_local->message);
298
0
        return NULL;
299
0
      }
300
0
      g_byte_array_append(buf, tmp, sz);
301
0
    }
302
0
    return g_byte_array_free_to_bytes(g_steal_pointer(&buf)); /* nocheck:blocked */
303
0
  }
304
0
#endif
305
0
}
306
307
/**
308
 * fwupd_jcat_file_get_items:
309
 * @self: #FwupdJcatFile
310
 *
311
 * Returns all the items in the file.
312
 *
313
 * Returns: (transfer container) (element-type FwupdJcatItem): all the items in the file
314
 *
315
 * Since: 2.1.3
316
 **/
317
GPtrArray *
318
fwupd_jcat_file_get_items(FwupdJcatFile *self)
319
0
{
320
0
  g_return_val_if_fail(FWUPD_IS_JCAT_FILE(self), NULL);
321
0
  return g_ptr_array_ref(self->items);
322
0
}
323
324
/**
325
 * fwupd_jcat_file_get_item_by_id:
326
 * @self: #FwupdJcatFile
327
 * @id: (not nullable): An ID, typically a filename basename
328
 * @error: #GError, or %NULL
329
 *
330
 * Finds the item with the specified ID, falling back to the ID alias if set.
331
 *
332
 * Returns: (transfer full): a #FwupdJcatItem, or %NULL if the filename was not found
333
 *
334
 * Since: 2.1.3
335
 **/
336
FwupdJcatItem *
337
fwupd_jcat_file_get_item_by_id(FwupdJcatFile *self, const gchar *id, GError **error)
338
0
{
339
0
  g_autoptr(FwupdJcatItem) item_found = NULL;
340
341
0
  g_return_val_if_fail(FWUPD_IS_JCAT_FILE(self), NULL);
342
0
  g_return_val_if_fail(id != NULL, NULL);
343
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
344
345
  /* exact ID match */
346
0
  for (guint i = 0; i < self->items->len; i++) {
347
0
    FwupdJcatItem *item = g_ptr_array_index(self->items, i);
348
0
    if (g_strcmp0(fwupd_jcat_item_get_id(item), id) == 0) {
349
0
      if (item_found != NULL) {
350
0
        g_set_error(error,
351
0
              FWUPD_ERROR,
352
0
              FWUPD_ERROR_NOT_SUPPORTED,
353
0
              "multiple matches for %s",
354
0
              id);
355
0
        return NULL;
356
0
      }
357
0
      item_found = g_object_ref(item);
358
0
    }
359
0
  }
360
0
  if (item_found != NULL)
361
0
    return g_steal_pointer(&item_found);
362
363
  /* try aliases this time */
364
0
  for (guint i = 0; i < self->items->len; i++) {
365
0
    FwupdJcatItem *item = g_ptr_array_index(self->items, i);
366
0
    g_autoptr(GPtrArray) alias_ids = fwupd_jcat_item_get_alias_ids(item);
367
0
    for (guint j = 0; j < alias_ids->len; j++) {
368
0
      const gchar *id_tmp = g_ptr_array_index(alias_ids, j);
369
0
      if (g_strcmp0(id_tmp, id) == 0) {
370
0
        if (item_found != NULL) {
371
0
          g_set_error(error,
372
0
                FWUPD_ERROR,
373
0
                FWUPD_ERROR_NOT_SUPPORTED,
374
0
                "multiple aliases for %s",
375
0
                id);
376
0
          return NULL;
377
0
        }
378
0
        item_found = g_object_ref(item);
379
0
      }
380
0
    }
381
0
  }
382
0
  if (item_found != NULL)
383
0
    return g_steal_pointer(&item_found);
384
385
  /* failed */
386
0
  g_set_error(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "failed to find %s", id);
387
0
  return NULL;
388
0
}
389
390
/**
391
 * fwupd_jcat_file_get_item_default:
392
 * @self: #FwupdJcatFile
393
 * @error: #GError, or %NULL
394
 *
395
 * Finds the default item. If more than one #FwupdJcatItem exists this function will
396
 * return with an error.
397
 *
398
 * Returns: (transfer full): a #FwupdJcatItem, or %NULL if no default exists
399
 *
400
 * Since: 2.1.3
401
 **/
402
FwupdJcatItem *
403
fwupd_jcat_file_get_item_default(FwupdJcatFile *self, GError **error)
404
0
{
405
0
  g_return_val_if_fail(FWUPD_IS_JCAT_FILE(self), NULL);
406
0
  g_return_val_if_fail(error == NULL || *error == NULL, NULL);
407
408
  /* sanity check */
409
0
  if (self->items->len == 0) {
410
0
    g_set_error_literal(error, FWUPD_ERROR, FWUPD_ERROR_NOT_FOUND, "no items found");
411
0
    return NULL;
412
0
  }
413
0
  if (self->items->len > 1) {
414
0
    g_set_error_literal(error,
415
0
            FWUPD_ERROR,
416
0
            FWUPD_ERROR_NOT_SUPPORTED,
417
0
            "multiple items found, no default possible");
418
0
    return NULL;
419
0
  }
420
421
  /* only one possible */
422
0
  return g_object_ref(g_ptr_array_index(self->items, 0));
423
0
}
424
425
/**
426
 * fwupd_jcat_file_add_item:
427
 * @self: #FwupdJcatFile
428
 * @item: (not nullable): #FwupdJcatItem
429
 *
430
 * Adds an item to a file.
431
 *
432
 * Since: 2.1.3
433
 **/
434
void
435
fwupd_jcat_file_add_item(FwupdJcatFile *self, FwupdJcatItem *item)
436
0
{
437
0
  g_return_if_fail(FWUPD_IS_JCAT_FILE(self));
438
0
  g_return_if_fail(FWUPD_IS_JCAT_ITEM(item));
439
0
  g_ptr_array_add(self->items, g_object_ref(item));
440
0
}
441
442
/**
443
 * fwupd_jcat_file_get_version_major:
444
 * @self: #FwupdJcatFile
445
 *
446
 * Returns the major version number of the FwupdJcat specification
447
 *
448
 * Returns: integer
449
 *
450
 * Since: 2.1.3
451
 **/
452
guint32
453
fwupd_jcat_file_get_version_major(FwupdJcatFile *self)
454
0
{
455
0
  g_return_val_if_fail(FWUPD_IS_JCAT_FILE(self), 0);
456
0
  return self->version_major;
457
0
}
458
459
/**
460
 * fwupd_jcat_file_get_version_minor:
461
 * @self: #FwupdJcatFile
462
 *
463
 * Returns the minor version number of the FwupdJcat specification
464
 *
465
 * Returns: integer
466
 *
467
 * Since: 2.1.3
468
 **/
469
guint32
470
fwupd_jcat_file_get_version_minor(FwupdJcatFile *self)
471
0
{
472
0
  g_return_val_if_fail(FWUPD_IS_JCAT_FILE(self), 0);
473
0
  return self->version_minor;
474
0
}
475
476
static void
477
fwupd_jcat_file_codec_iface_init(FwupdCodecInterface *iface)
478
0
{
479
0
  iface->add_string = fwupd_jcat_file_add_string;
480
0
}
481
482
static void
483
fwupd_jcat_file_finalize(GObject *obj)
484
0
{
485
0
  FwupdJcatFile *self = FWUPD_JCAT_FILE(obj);
486
487
0
  g_ptr_array_unref(self->items);
488
0
  G_OBJECT_CLASS(fwupd_jcat_file_parent_class)->finalize(obj);
489
0
}
490
491
static void
492
fwupd_jcat_file_class_init(FwupdJcatFileClass *klass)
493
0
{
494
0
  GObjectClass *object_class = G_OBJECT_CLASS(klass);
495
0
  object_class->finalize = fwupd_jcat_file_finalize;
496
0
}
497
498
static void
499
fwupd_jcat_file_init(FwupdJcatFile *self)
500
0
{
501
0
  self->items = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
502
0
  self->version_major = 0;
503
0
  self->version_minor = 1;
504
0
}
505
506
/**
507
 * fwupd_jcat_file_new:
508
 *
509
 * Creates a new file.
510
 *
511
 * Returns: a #FwupdJcatFile
512
 *
513
 * Since: 2.1.3
514
 **/
515
FwupdJcatFile *
516
fwupd_jcat_file_new(void)
517
0
{
518
0
  return g_object_new(FWUPD_TYPE_JCAT_FILE, NULL);
519
0
}