Coverage Report

Created: 2025-08-24 07:10

/src/fwupd/libfwupdplugin/fu-cab-firmware.c
Line
Count
Source (jump to first uncovered line)
1
/*
2
 * Copyright 2023 Richard Hughes <richard@hughsie.com>
3
 *
4
 * SPDX-License-Identifier: LGPL-2.1-or-later
5
 */
6
7
0
#define G_LOG_DOMAIN "FuCabFirmware"
8
9
#include "config.h"
10
11
#include <zlib.h>
12
13
#include "fu-byte-array.h"
14
#include "fu-cab-firmware-private.h"
15
#include "fu-cab-image.h"
16
#include "fu-cab-struct.h"
17
#include "fu-chunk-array.h"
18
#include "fu-common.h"
19
#include "fu-composite-input-stream.h"
20
#include "fu-input-stream.h"
21
#include "fu-mem-private.h"
22
#include "fu-partial-input-stream.h"
23
#include "fu-string.h"
24
25
typedef struct {
26
  gboolean compressed;
27
  gboolean only_basename;
28
} FuCabFirmwarePrivate;
29
30
G_DEFINE_TYPE_WITH_PRIVATE(FuCabFirmware, fu_cab_firmware, FU_TYPE_FIRMWARE)
31
21.4k
#define GET_PRIVATE(o) (fu_cab_firmware_get_instance_private(o))
32
33
2.29k
#define FU_CAB_FIRMWARE_MAX_FILES   1024
34
2.33k
#define FU_CAB_FIRMWARE_MAX_FOLDERS 64
35
36
2.20k
#define FU_CAB_FIRMWARE_DECOMPRESS_BUFSZ 0x4000 /* bytes */
37
38
/**
39
 * fu_cab_firmware_get_compressed:
40
 * @self: a #FuCabFirmware
41
 *
42
 * Gets if the cabinet archive should be compressed.
43
 *
44
 * Returns: boolean
45
 *
46
 * Since: 1.9.7
47
 **/
48
gboolean
49
fu_cab_firmware_get_compressed(FuCabFirmware *self)
50
0
{
51
0
  FuCabFirmwarePrivate *priv = GET_PRIVATE(self);
52
0
  g_return_val_if_fail(FU_IS_CAB_FIRMWARE(self), FALSE);
53
0
  return priv->compressed;
54
0
}
55
56
/**
57
 * fu_cab_firmware_set_compressed:
58
 * @self: a #FuCabFirmware
59
 * @compressed: boolean
60
 *
61
 * Sets if the cabinet archive should be compressed.
62
 *
63
 * Since: 1.9.7
64
 **/
65
void
66
fu_cab_firmware_set_compressed(FuCabFirmware *self, gboolean compressed)
67
0
{
68
0
  FuCabFirmwarePrivate *priv = GET_PRIVATE(self);
69
0
  g_return_if_fail(FU_IS_CAB_FIRMWARE(self));
70
0
  priv->compressed = compressed;
71
0
}
72
73
/**
74
 * fu_cab_firmware_get_only_basename:
75
 * @self: a #FuCabFirmware
76
 *
77
 * Gets if the cabinet archive filenames should have the path component removed.
78
 *
79
 * Returns: boolean
80
 *
81
 * Since: 1.9.7
82
 **/
83
gboolean
84
fu_cab_firmware_get_only_basename(FuCabFirmware *self)
85
0
{
86
0
  FuCabFirmwarePrivate *priv = GET_PRIVATE(self);
87
0
  g_return_val_if_fail(FU_IS_CAB_FIRMWARE(self), FALSE);
88
0
  return priv->only_basename;
89
0
}
90
91
/**
92
 * fu_cab_firmware_set_only_basename:
93
 * @self: a #FuCabFirmware
94
 * @only_basename: boolean
95
 *
96
 * Sets if the cabinet archive filenames should have the path component removed.
97
 *
98
 * Since: 1.9.7
99
 **/
100
void
101
fu_cab_firmware_set_only_basename(FuCabFirmware *self, gboolean only_basename)
102
0
{
103
0
  FuCabFirmwarePrivate *priv = GET_PRIVATE(self);
104
0
  g_return_if_fail(FU_IS_CAB_FIRMWARE(self));
105
0
  priv->only_basename = only_basename;
106
0
}
107
108
typedef struct {
109
  GInputStream *stream;
110
  FuFirmwareParseFlags parse_flags;
111
  gsize rsvd_folder;
112
  gsize rsvd_block;
113
  gsize size_total;
114
  FuCabCompression compression;
115
  GPtrArray *folder_data; /* of FuCompositeInputStream */
116
  z_stream zstrm;
117
  guint8 *decompress_buf;
118
  gsize decompress_bufsz;
119
  gsize ndatabsz;
120
} FuCabFirmwareParseHelper;
121
122
static void
123
fu_cab_firmware_parse_helper_free(FuCabFirmwareParseHelper *helper)
124
2.20k
{
125
2.20k
  inflateEnd(&helper->zstrm);
126
2.20k
  if (helper->stream != NULL)
127
2.20k
    g_object_unref(helper->stream);
128
2.20k
  if (helper->folder_data != NULL)
129
2.20k
    g_ptr_array_unref(helper->folder_data);
130
2.20k
  g_free(helper->decompress_buf);
131
2.20k
  g_free(helper);
132
2.20k
}
133
134
G_DEFINE_AUTOPTR_CLEANUP_FUNC(FuCabFirmwareParseHelper, fu_cab_firmware_parse_helper_free)
135
136
/* compute the MS cabinet checksum */
137
gboolean
138
fu_cab_firmware_compute_checksum(const guint8 *buf, gsize bufsz, guint32 *checksum, GError **error)
139
4.47k
{
140
4.47k
  guint32 tmp = *checksum;
141
2.51M
  for (gsize i = 0; i < bufsz; i += 4) {
142
2.50M
    gsize chunksz = bufsz - i;
143
2.50M
    if (G_LIKELY(chunksz >= 4)) {
144
      /* 3,2,1,0 */
145
2.50M
      tmp ^= ((guint32)buf[i + 3] << 24) | ((guint32)buf[i + 2] << 16) |
146
2.50M
             ((guint32)buf[i + 1] << 8) | (guint32)buf[i + 0];
147
2.50M
    } else if (chunksz == 3) {
148
      /* 0,1,2 -- yes, weird */
149
599
      tmp ^= ((guint32)buf[i + 0] << 16) | ((guint32)buf[i + 1] << 8) |
150
599
             (guint32)buf[i + 2];
151
1.40k
    } else if (chunksz == 2) {
152
      /* 0,1 -- yes, weird */
153
590
      tmp ^= ((guint32)buf[i + 0] << 8) | (guint32)buf[i + 1];
154
815
    } else {
155
      /* 0 */
156
815
      tmp ^= (guint32)buf[i + 0];
157
815
    }
158
2.50M
  }
159
4.47k
  *checksum = tmp;
160
4.47k
  return TRUE;
161
4.47k
}
162
163
static gboolean
164
fu_cab_firmware_compute_checksum_stream_cb(const guint8 *buf,
165
             gsize bufsz,
166
             gpointer user_data,
167
             GError **error)
168
2.31k
{
169
2.31k
  guint32 *checksum = (guint32 *)user_data;
170
2.31k
  return fu_cab_firmware_compute_checksum(buf, bufsz, checksum, error);
171
2.31k
}
172
173
static voidpf
174
fu_cab_firmware_zalloc(voidpf opaque, uInt items, uInt size)
175
2.73k
{
176
2.73k
  return g_malloc0_n(items, size);
177
2.73k
}
178
179
static void
180
fu_cab_firmware_zfree(voidpf opaque, voidpf address)
181
2.73k
{
182
2.73k
  g_free(address);
183
2.73k
}
184
185
typedef z_stream z_stream_deflater;
186
187
static void
188
fu_cab_firmware_zstream_deflater_free(z_stream_deflater *zstrm)
189
0
{
190
0
  deflateEnd(zstrm);
191
0
}
192
193
G_DEFINE_AUTOPTR_CLEANUP_FUNC(z_stream_deflater, fu_cab_firmware_zstream_deflater_free)
194
195
static gboolean
196
fu_cab_firmware_parse_data(FuCabFirmware *self,
197
         FuCabFirmwareParseHelper *helper,
198
         gsize *offset,
199
         GInputStream *folder_data,
200
         GError **error)
201
702k
{
202
702k
  gsize blob_comp;
203
702k
  gsize blob_uncomp;
204
702k
  gsize hdr_sz;
205
702k
  gsize size_max = fu_firmware_get_size_max(FU_FIRMWARE(self));
206
702k
  g_autoptr(FuStructCabData) st = NULL;
207
702k
  g_autoptr(GInputStream) partial_stream = NULL;
208
209
  /* parse header */
210
702k
  st = fu_struct_cab_data_parse_stream(helper->stream, *offset, error);
211
702k
  if (st == NULL)
212
142
    return FALSE;
213
214
  /* sanity check */
215
701k
  blob_comp = fu_struct_cab_data_get_comp(st);
216
701k
  blob_uncomp = fu_struct_cab_data_get_uncomp(st);
217
701k
  if (helper->compression == FU_CAB_COMPRESSION_NONE && blob_comp != blob_uncomp) {
218
103
    g_set_error_literal(error,
219
103
            FWUPD_ERROR,
220
103
            FWUPD_ERROR_NOT_SUPPORTED,
221
103
            "mismatched compressed data");
222
103
    return FALSE;
223
103
  }
224
701k
  helper->size_total += blob_uncomp;
225
701k
  if (size_max > 0 && helper->size_total > size_max) {
226
0
    g_autofree gchar *sz_val = g_format_size(helper->size_total);
227
0
    g_autofree gchar *sz_max = g_format_size(size_max);
228
0
    g_set_error(error,
229
0
          FWUPD_ERROR,
230
0
          FWUPD_ERROR_INVALID_DATA,
231
0
          "uncompressed data too large (%s, limit %s)",
232
0
          sz_val,
233
0
          sz_max);
234
0
    return FALSE;
235
0
  }
236
237
701k
  hdr_sz = st->len + helper->rsvd_block;
238
239
  /* verify checksum */
240
701k
  partial_stream =
241
701k
      fu_partial_input_stream_new(helper->stream, *offset + hdr_sz, blob_comp, error);
242
701k
  if (partial_stream == NULL) {
243
51
    g_prefix_error_literal(error, "failed to cut cabinet checksum: ");
244
51
    return FALSE;
245
51
  }
246
701k
  if ((helper->parse_flags & FU_FIRMWARE_PARSE_FLAG_IGNORE_CHECKSUM) == 0) {
247
239k
    guint32 checksum = fu_struct_cab_data_get_checksum(st);
248
239k
    if (checksum != 0) {
249
2.15k
      guint32 checksum_actual = 0;
250
2.15k
      g_autoptr(GByteArray) hdr = g_byte_array_new();
251
252
2.15k
      if (!fu_input_stream_chunkify(partial_stream,
253
2.15k
                  fu_cab_firmware_compute_checksum_stream_cb,
254
2.15k
                  &checksum_actual,
255
2.15k
                  error))
256
0
        return FALSE;
257
2.15k
      fu_byte_array_append_uint16(hdr, blob_comp, G_LITTLE_ENDIAN);
258
2.15k
      fu_byte_array_append_uint16(hdr, blob_uncomp, G_LITTLE_ENDIAN);
259
2.15k
      if (!fu_cab_firmware_compute_checksum(hdr->data,
260
2.15k
                    hdr->len,
261
2.15k
                    &checksum_actual,
262
2.15k
                    error))
263
0
        return FALSE;
264
2.15k
      if (checksum_actual != checksum) {
265
708
        g_set_error(error,
266
708
              FWUPD_ERROR,
267
708
              FWUPD_ERROR_NOT_SUPPORTED,
268
708
              "invalid checksum at 0x%x, expected 0x%x, got 0x%x",
269
708
              (guint)*offset,
270
708
              checksum,
271
708
              checksum_actual);
272
708
        return FALSE;
273
708
      }
274
2.15k
    }
275
239k
  }
276
277
  /* decompress Zlib data after removing *another *header... */
278
701k
  if (helper->compression == FU_CAB_COMPRESSION_MSZIP) {
279
3.28k
    int zret;
280
3.28k
    g_autofree gchar *kind = NULL;
281
3.28k
    g_autoptr(GByteArray) buf = g_byte_array_new();
282
3.28k
    g_autoptr(GBytes) bytes_comp = NULL;
283
3.28k
    g_autoptr(GBytes) bytes_uncomp = NULL;
284
285
    /* check compressed header */
286
3.28k
    bytes_comp = fu_input_stream_read_bytes(helper->stream,
287
3.28k
              *offset + hdr_sz,
288
3.28k
              blob_comp,
289
3.28k
              NULL,
290
3.28k
              error);
291
3.28k
    if (bytes_comp == NULL)
292
8
      return FALSE;
293
3.27k
    kind = fu_memstrsafe(g_bytes_get_data(bytes_comp, NULL),
294
3.27k
             g_bytes_get_size(bytes_comp),
295
3.27k
             0x0,
296
3.27k
             2,
297
3.27k
             error);
298
3.27k
    if (kind == NULL)
299
91
      return FALSE;
300
3.18k
    if (g_strcmp0(kind, "CK") != 0) {
301
34
      g_set_error(error,
302
34
            FWUPD_ERROR,
303
34
            FWUPD_ERROR_NOT_SUPPORTED,
304
34
            "compressed header invalid: %s",
305
34
            kind);
306
34
      return FALSE;
307
34
    }
308
3.15k
    if (helper->decompress_buf == NULL)
309
537
      helper->decompress_buf = g_malloc0(helper->decompress_bufsz);
310
3.15k
    helper->zstrm.avail_in = g_bytes_get_size(bytes_comp) - 2;
311
3.15k
    helper->zstrm.next_in = (z_const Bytef *)g_bytes_get_data(bytes_comp, NULL) + 2;
312
8.03k
    while (1) {
313
8.03k
      helper->zstrm.avail_out = helper->decompress_bufsz;
314
8.03k
      helper->zstrm.next_out = helper->decompress_buf;
315
8.03k
      zret = inflate(&helper->zstrm, Z_BLOCK);
316
8.03k
      if (zret == Z_STREAM_END)
317
3.14k
        break;
318
4.89k
      g_byte_array_append(buf,
319
4.89k
              helper->decompress_buf,
320
4.89k
              helper->decompress_bufsz - helper->zstrm.avail_out);
321
4.89k
      if (zret != Z_OK) {
322
8
        g_set_error(error,
323
8
              FWUPD_ERROR,
324
8
              FWUPD_ERROR_NOT_SUPPORTED,
325
8
              "inflate error @0x%x: %s",
326
8
              (guint)*offset,
327
8
              zError(zret));
328
8
        return FALSE;
329
8
      }
330
4.89k
    }
331
3.14k
    zret = inflateReset(&helper->zstrm);
332
3.14k
    if (zret != Z_OK) {
333
0
      g_set_error(error,
334
0
            FWUPD_ERROR,
335
0
            FWUPD_ERROR_NOT_SUPPORTED,
336
0
            "failed to reset inflate: %s",
337
0
            zError(zret));
338
0
      return FALSE;
339
0
    }
340
3.14k
    zret = inflateSetDictionary(&helper->zstrm, buf->data, buf->len);
341
3.14k
    if (zret != Z_OK) {
342
0
      g_set_error(error,
343
0
            FWUPD_ERROR,
344
0
            FWUPD_ERROR_NOT_SUPPORTED,
345
0
            "failed to set inflate dictionary: %s",
346
0
            zError(zret));
347
0
      return FALSE;
348
0
    }
349
3.14k
    bytes_uncomp =
350
3.14k
        g_byte_array_free_to_bytes(g_steal_pointer(&buf)); /* nocheck:blocked */
351
3.14k
    fu_composite_input_stream_add_bytes(FU_COMPOSITE_INPUT_STREAM(folder_data),
352
3.14k
                bytes_uncomp);
353
697k
  } else {
354
697k
    fu_composite_input_stream_add_partial_stream(
355
697k
        FU_COMPOSITE_INPUT_STREAM(folder_data),
356
697k
        FU_PARTIAL_INPUT_STREAM(partial_stream));
357
697k
  }
358
359
  /* success */
360
700k
  *offset += blob_comp + hdr_sz;
361
700k
  return TRUE;
362
701k
}
363
364
static gboolean
365
fu_cab_firmware_parse_folder(FuCabFirmware *self,
366
           FuCabFirmwareParseHelper *helper,
367
           guint idx,
368
           gsize offset,
369
           GInputStream *folder_data,
370
           GError **error)
371
6.60k
{
372
6.60k
  FuCabFirmwarePrivate *priv = GET_PRIVATE(self);
373
6.60k
  g_autoptr(GByteArray) st = NULL;
374
375
  /* parse header */
376
6.60k
  st = fu_struct_cab_folder_parse_stream(helper->stream, offset, error);
377
6.60k
  if (st == NULL)
378
134
    return FALSE;
379
380
  /* sanity check */
381
6.46k
  if (fu_struct_cab_folder_get_ndatab(st) == 0) {
382
13
    g_set_error_literal(error,
383
13
            FWUPD_ERROR,
384
13
            FWUPD_ERROR_NOT_SUPPORTED,
385
13
            "no CFDATA blocks");
386
13
    return FALSE;
387
13
  }
388
6.45k
  helper->compression = fu_struct_cab_folder_get_compression(st);
389
6.45k
  if (helper->compression != FU_CAB_COMPRESSION_NONE)
390
3.38k
    priv->compressed = TRUE;
391
6.45k
  if (helper->compression != FU_CAB_COMPRESSION_NONE &&
392
6.45k
      helper->compression != FU_CAB_COMPRESSION_MSZIP) {
393
69
    g_set_error(error,
394
69
          FWUPD_ERROR,
395
69
          FWUPD_ERROR_NOT_SUPPORTED,
396
69
          "compression %s not supported",
397
69
          fu_cab_compression_to_string(helper->compression));
398
69
    return FALSE;
399
69
  }
400
401
  /* parse CDATA, either using the stream offset or the per-spec FuStructCabFolder.ndatab */
402
6.38k
  if (helper->ndatabsz > 0) {
403
0
    for (gsize off = fu_struct_cab_folder_get_offset(st); off < helper->ndatabsz;) {
404
0
      if (!fu_cab_firmware_parse_data(self, helper, &off, folder_data, error))
405
0
        return FALSE;
406
0
    }
407
6.38k
  } else {
408
6.38k
    gsize off = fu_struct_cab_folder_get_offset(st);
409
707k
    for (guint16 i = 0; i < fu_struct_cab_folder_get_ndatab(st); i++) {
410
702k
      if (!fu_cab_firmware_parse_data(self, helper, &off, folder_data, error))
411
1.14k
        return FALSE;
412
702k
    }
413
6.38k
  }
414
415
  /* success */
416
5.24k
  return TRUE;
417
6.38k
}
418
419
static gboolean
420
fu_cab_firmware_parse_file(FuCabFirmware *self,
421
         FuCabFirmwareParseHelper *helper,
422
         gsize *offset,
423
         GError **error)
424
14.7k
{
425
14.7k
  FuCabFirmwarePrivate *priv = GET_PRIVATE(self);
426
14.7k
  GInputStream *folder_data;
427
14.7k
  guint16 date;
428
14.7k
  guint16 index;
429
14.7k
  guint16 time;
430
14.7k
  g_autoptr(FuCabImage) img = fu_cab_image_new();
431
14.7k
  g_autoptr(GByteArray) st = NULL;
432
14.7k
  g_autoptr(GDateTime) created = NULL;
433
14.7k
  g_autoptr(GInputStream) stream = NULL;
434
14.7k
  g_autoptr(GString) filename = g_string_new(NULL);
435
14.7k
  g_autoptr(GTimeZone) tz_utc = g_time_zone_new_utc();
436
437
  /* parse header */
438
14.7k
  st = fu_struct_cab_file_parse_stream(helper->stream, *offset, error);
439
14.7k
  if (st == NULL)
440
405
    return FALSE;
441
14.3k
  fu_firmware_set_offset(FU_FIRMWARE(img), fu_struct_cab_file_get_uoffset(st));
442
14.3k
  fu_firmware_set_size(FU_FIRMWARE(img), fu_struct_cab_file_get_usize(st));
443
444
  /* sanity check */
445
14.3k
  index = fu_struct_cab_file_get_index(st);
446
14.3k
  if (index >= helper->folder_data->len) {
447
39
    g_set_error(error,
448
39
          FWUPD_ERROR,
449
39
          FWUPD_ERROR_NOT_SUPPORTED,
450
39
          "failed to get folder data for 0x%x",
451
39
          index);
452
39
    return FALSE;
453
39
  }
454
14.2k
  folder_data = g_ptr_array_index(helper->folder_data, index);
455
456
  /* parse filename */
457
14.2k
  *offset += FU_STRUCT_CAB_FILE_SIZE;
458
212k
  for (guint i = 0; i < 255; i++) {
459
212k
    guint8 value = 0;
460
212k
    if (!fu_input_stream_read_u8(helper->stream, *offset + i, &value, error))
461
104
      return FALSE;
462
212k
    if (value == 0)
463
13.6k
      break;
464
198k
    if (!g_ascii_isprint((gchar)value)) {
465
40
      g_set_error(error,
466
40
            FWUPD_ERROR,
467
40
            FWUPD_ERROR_NOT_SUPPORTED,
468
40
            "non-ASCII filenames are not supported: 0x%02x",
469
40
            value);
470
40
      return FALSE;
471
40
    }
472
    /* convert to UNIX path */
473
198k
    if (value == '\\')
474
6.74k
      value = '/';
475
198k
    g_string_append_c(filename, (gchar)value);
476
198k
  }
477
478
  /* add image */
479
14.1k
  if (priv->only_basename) {
480
0
    g_autofree gchar *id = g_path_get_basename(filename->str);
481
0
    fu_firmware_set_id(FU_FIRMWARE(img), id);
482
14.1k
  } else {
483
14.1k
    fu_firmware_set_id(FU_FIRMWARE(img), filename->str);
484
14.1k
  }
485
14.1k
  stream = fu_partial_input_stream_new(folder_data,
486
14.1k
               fu_struct_cab_file_get_uoffset(st),
487
14.1k
               fu_struct_cab_file_get_usize(st),
488
14.1k
               error);
489
14.1k
  if (stream == NULL) {
490
66
    g_prefix_error_literal(error, "failed to cut cabinet image: ");
491
66
    return FALSE;
492
66
  }
493
14.0k
  if (!fu_firmware_parse_stream(FU_FIRMWARE(img), stream, 0x0, helper->parse_flags, error))
494
4
    return FALSE;
495
14.0k
  if (!fu_firmware_add_image_full(FU_FIRMWARE(self), FU_FIRMWARE(img), error))
496
0
    return FALSE;
497
498
  /* set created date time */
499
14.0k
  date = fu_struct_cab_file_get_date(st);
500
14.0k
  time = fu_struct_cab_file_get_time(st);
501
14.0k
  created = g_date_time_new(tz_utc,
502
14.0k
          1980 + ((date & 0xFE00) >> 9),
503
14.0k
          (date & 0x01E0) >> 5,
504
14.0k
          date & 0x001F,
505
14.0k
          (time & 0xF800) >> 11,
506
14.0k
          (time & 0x07E0) >> 5,
507
14.0k
          (time & 0x001F) * 2);
508
14.0k
  fu_cab_image_set_created(img, created);
509
510
  /* offset to next entry */
511
14.0k
  *offset += filename->len + 1;
512
14.0k
  return TRUE;
513
14.0k
}
514
515
static gboolean
516
fu_cab_firmware_validate(FuFirmware *firmware, GInputStream *stream, gsize offset, GError **error)
517
3.58M
{
518
3.58M
  return fu_struct_cab_header_validate_stream(stream, offset, error);
519
3.58M
}
520
521
static FuCabFirmwareParseHelper *
522
fu_cab_firmware_parse_helper_new(GInputStream *stream, FuFirmwareParseFlags flags, GError **error)
523
2.20k
{
524
2.20k
  int zret;
525
2.20k
  g_autoptr(FuCabFirmwareParseHelper) helper = g_new0(FuCabFirmwareParseHelper, 1);
526
527
  /* zlib */
528
2.20k
  helper->zstrm.zalloc = fu_cab_firmware_zalloc;
529
2.20k
  helper->zstrm.zfree = fu_cab_firmware_zfree;
530
2.20k
  zret = inflateInit2(&helper->zstrm, -MAX_WBITS);
531
2.20k
  if (zret != Z_OK) {
532
0
    g_set_error(error,
533
0
          FWUPD_ERROR,
534
0
          FWUPD_ERROR_NOT_SUPPORTED,
535
0
          "failed to initialize inflate: %s",
536
0
          zError(zret));
537
0
    return NULL;
538
0
  }
539
540
2.20k
  helper->stream = g_object_ref(stream);
541
2.20k
  helper->parse_flags = flags;
542
2.20k
  helper->folder_data = g_ptr_array_new_with_free_func((GDestroyNotify)g_object_unref);
543
2.20k
  helper->decompress_bufsz = FU_CAB_FIRMWARE_DECOMPRESS_BUFSZ;
544
2.20k
  return g_steal_pointer(&helper);
545
2.20k
}
546
547
static gboolean
548
fu_cab_firmware_parse(FuFirmware *firmware,
549
          GInputStream *stream,
550
          FuFirmwareParseFlags flags,
551
          GError **error)
552
2.38k
{
553
2.38k
  FuCabFirmware *self = FU_CAB_FIRMWARE(firmware);
554
2.38k
  gsize off_cffile = 0;
555
2.38k
  gsize offset = 0;
556
2.38k
  gsize streamsz = 0;
557
2.38k
  g_autoptr(GByteArray) st = NULL;
558
2.38k
  g_autoptr(FuCabFirmwareParseHelper) helper = NULL;
559
560
  /* get size */
561
2.38k
  if (!fu_input_stream_size(stream, &streamsz, error))
562
0
    return FALSE;
563
564
  /* parse header */
565
2.38k
  st = fu_struct_cab_header_parse_stream(stream, offset, error);
566
2.38k
  if (st == NULL)
567
0
    return FALSE;
568
569
  /* sanity checks */
570
2.38k
  if (fu_struct_cab_header_get_size(st) < streamsz) {
571
39
    g_set_error(error,
572
39
          FWUPD_ERROR,
573
39
          FWUPD_ERROR_NOT_SUPPORTED,
574
39
          "buffer size 0x%x is less than stream size 0x%x",
575
39
          (guint)streamsz,
576
39
          fu_struct_cab_header_get_size(st));
577
39
    return FALSE;
578
39
  }
579
2.34k
  if (fu_struct_cab_header_get_idx_cabinet(st) != 0) {
580
32
    g_set_error_literal(error,
581
32
            FWUPD_ERROR,
582
32
            FWUPD_ERROR_NOT_SUPPORTED,
583
32
            "chained archive not supported");
584
32
    return FALSE;
585
32
  }
586
2.31k
  if (fu_struct_cab_header_get_nr_folders(st) == 0 ||
587
2.31k
      fu_struct_cab_header_get_nr_files(st) == 0) {
588
6
    g_set_error_literal(error,
589
6
            FWUPD_ERROR,
590
6
            FWUPD_ERROR_NOT_SUPPORTED,
591
6
            "archive is empty");
592
6
    return FALSE;
593
6
  }
594
2.30k
  if (fu_struct_cab_header_get_nr_folders(st) > FU_CAB_FIRMWARE_MAX_FOLDERS) {
595
25
    g_set_error(error,
596
25
          FWUPD_ERROR,
597
25
          FWUPD_ERROR_NOT_SUPPORTED,
598
25
          "too many CFFOLDERS, parsed %u and limit was %u",
599
25
          fu_struct_cab_header_get_nr_folders(st),
600
25
          (guint)FU_CAB_FIRMWARE_MAX_FOLDERS);
601
25
    return FALSE;
602
25
  }
603
2.28k
  if (fu_struct_cab_header_get_nr_files(st) > FU_CAB_FIRMWARE_MAX_FILES) {
604
16
    g_set_error(error,
605
16
          FWUPD_ERROR,
606
16
          FWUPD_ERROR_NOT_SUPPORTED,
607
16
          "too many CFFILES, parsed %u and limit was %u",
608
16
          fu_struct_cab_header_get_nr_files(st),
609
16
          (guint)FU_CAB_FIRMWARE_MAX_FILES);
610
16
    return FALSE;
611
16
  }
612
2.26k
  off_cffile = fu_struct_cab_header_get_off_cffile(st);
613
2.26k
  if (off_cffile > streamsz) {
614
63
    g_set_error_literal(error,
615
63
            FWUPD_ERROR,
616
63
            FWUPD_ERROR_NOT_SUPPORTED,
617
63
            "archive is corrupt");
618
63
    return FALSE;
619
63
  }
620
621
  /* create helper */
622
2.20k
  helper = fu_cab_firmware_parse_helper_new(stream, flags, error);
623
2.20k
  if (helper == NULL)
624
0
    return FALSE;
625
626
  /* if the only folder is >= 2GB then FuStructCabFolder.ndatab will overflow */
627
2.20k
  if (streamsz >= 0x8000 * 0xFFFF && fu_struct_cab_header_get_nr_folders(st) == 1)
628
0
    helper->ndatabsz = streamsz;
629
630
  /* reserved sizes */
631
2.20k
  offset += st->len;
632
2.20k
  if (fu_struct_cab_header_get_flags(st) & 0x0004) {
633
88
    g_autoptr(GByteArray) st2 = NULL;
634
88
    st2 = fu_struct_cab_header_reserve_parse_stream(stream, offset, error);
635
88
    if (st2 == NULL)
636
19
      return FALSE;
637
69
    offset += st2->len;
638
69
    offset += fu_struct_cab_header_reserve_get_rsvd_hdr(st2);
639
69
    helper->rsvd_block = fu_struct_cab_header_reserve_get_rsvd_block(st2);
640
69
    helper->rsvd_folder = fu_struct_cab_header_reserve_get_rsvd_folder(st2);
641
69
  }
642
643
  /* parse CFFOLDER */
644
7.41k
  for (guint i = 0; i < fu_struct_cab_header_get_nr_folders(st); i++) {
645
6.60k
    g_autoptr(GInputStream) folder_data = fu_composite_input_stream_new();
646
6.60k
    if (!fu_cab_firmware_parse_folder(self, helper, i, offset, folder_data, error))
647
1.36k
      return FALSE;
648
5.24k
    if (!fu_input_stream_size(folder_data, &streamsz, error))
649
0
      return FALSE;
650
5.24k
    if (streamsz == 0) {
651
6
      g_set_error_literal(error,
652
6
              FWUPD_ERROR,
653
6
              FWUPD_ERROR_NOT_SUPPORTED,
654
6
              "no folder data");
655
6
      return FALSE;
656
6
    }
657
5.23k
    g_ptr_array_add(helper->folder_data, g_steal_pointer(&folder_data));
658
5.23k
    offset += FU_STRUCT_CAB_FOLDER_SIZE + helper->rsvd_folder;
659
5.23k
  }
660
661
  /* parse CFFILEs */
662
14.8k
  for (guint i = 0; i < fu_struct_cab_header_get_nr_files(st); i++) {
663
14.7k
    if (!fu_cab_firmware_parse_file(self, helper, &off_cffile, error))
664
658
      return FALSE;
665
14.7k
  }
666
667
  /* success */
668
157
  return TRUE;
669
815
}
670
671
static GByteArray *
672
fu_cab_firmware_write(FuFirmware *firmware, GError **error)
673
157
{
674
157
  FuCabFirmware *self = FU_CAB_FIRMWARE(firmware);
675
157
  FuCabFirmwarePrivate *priv = GET_PRIVATE(self);
676
157
  gsize archive_size;
677
157
  gsize offset;
678
157
  guint32 index_into = 0;
679
157
  g_autoptr(GByteArray) st_hdr = fu_struct_cab_header_new();
680
157
  g_autoptr(GByteArray) st_folder = fu_struct_cab_folder_new();
681
157
  g_autoptr(GPtrArray) imgs = fu_firmware_get_images(firmware);
682
157
  g_autoptr(GByteArray) cfdata_linear = g_byte_array_new();
683
157
  g_autoptr(GBytes) cfdata_linear_blob = NULL;
684
157
  g_autoptr(FuChunkArray) chunks = NULL;
685
157
  g_autoptr(GPtrArray) chunks_zlib =
686
157
      g_ptr_array_new_with_free_func((GDestroyNotify)g_byte_array_unref);
687
688
  /* create linear CFDATA block */
689
157
  for (guint i = 0; i < imgs->len; i++) {
690
157
    FuFirmware *img = g_ptr_array_index(imgs, i);
691
157
    const gchar *filename_win32 = fu_cab_image_get_win32_filename(FU_CAB_IMAGE(img));
692
157
    g_autoptr(GBytes) img_blob = NULL;
693
694
157
    if (filename_win32 == NULL) {
695
13
      g_set_error_literal(error,
696
13
              FWUPD_ERROR,
697
13
              FWUPD_ERROR_NOT_SUPPORTED,
698
13
              "no image filename");
699
13
      return NULL;
700
13
    }
701
144
    img_blob = fu_firmware_get_bytes(img, error);
702
144
    if (img_blob == NULL)
703
144
      return NULL;
704
0
    fu_byte_array_append_bytes(cfdata_linear, img_blob);
705
0
  }
706
707
  /* chunkify and compress with a fixed size */
708
0
  if (cfdata_linear->len == 0) {
709
0
    g_set_error_literal(error,
710
0
            FWUPD_ERROR,
711
0
            FWUPD_ERROR_NOT_SUPPORTED,
712
0
            "no data to compress");
713
0
    return NULL;
714
0
  }
715
0
  cfdata_linear_blob =
716
0
      g_byte_array_free_to_bytes(g_steal_pointer(&cfdata_linear)); /* nocheck:blocked */
717
0
  chunks = fu_chunk_array_new_from_bytes(cfdata_linear_blob,
718
0
                 FU_CHUNK_ADDR_OFFSET_NONE,
719
0
                 FU_CHUNK_PAGESZ_NONE,
720
0
                 0x8000);
721
0
  for (guint i = 0; i < fu_chunk_array_length(chunks); i++) {
722
0
    g_autoptr(FuChunk) chk = NULL;
723
0
    g_autoptr(GByteArray) chunk_zlib = g_byte_array_new();
724
0
    g_autoptr(GByteArray) buf = g_byte_array_new();
725
726
0
    chk = fu_chunk_array_index(chunks, i, error);
727
0
    if (chk == NULL)
728
0
      return NULL;
729
0
    fu_byte_array_set_size(chunk_zlib, fu_chunk_get_data_sz(chk) * 2, 0x0);
730
0
    if (priv->compressed) {
731
0
      int zret;
732
0
      z_stream zstrm = {
733
0
          .zalloc = fu_cab_firmware_zalloc,
734
0
          .zfree = fu_cab_firmware_zfree,
735
0
          .opaque = Z_NULL,
736
0
          .next_in = (guint8 *)fu_chunk_get_data(chk),
737
0
          .avail_in = fu_chunk_get_data_sz(chk),
738
0
          .next_out = chunk_zlib->data,
739
0
          .avail_out = chunk_zlib->len,
740
0
      };
741
0
      g_autoptr(z_stream_deflater) zstrm_deflater = &zstrm;
742
0
      zret = deflateInit2(zstrm_deflater,
743
0
              Z_DEFAULT_COMPRESSION,
744
0
              Z_DEFLATED,
745
0
              -15,
746
0
              8,
747
0
              Z_DEFAULT_STRATEGY);
748
0
      if (zret != Z_OK) {
749
0
        g_set_error(error,
750
0
              FWUPD_ERROR,
751
0
              FWUPD_ERROR_NOT_SUPPORTED,
752
0
              "failed to initialize deflate: %s",
753
0
              zError(zret));
754
0
        return NULL;
755
0
      }
756
0
      zret = deflate(zstrm_deflater, Z_FINISH);
757
0
      if (zret != Z_OK && zret != Z_STREAM_END) {
758
0
        g_set_error(error,
759
0
              FWUPD_ERROR,
760
0
              FWUPD_ERROR_NOT_SUPPORTED,
761
0
              "zlib deflate failed: %s",
762
0
              zError(zret));
763
0
        return NULL;
764
0
      }
765
0
      fu_byte_array_append_uint8(buf, (guint8)'C');
766
0
      fu_byte_array_append_uint8(buf, (guint8)'K');
767
0
      g_byte_array_append(buf, chunk_zlib->data, zstrm.total_out);
768
0
    } else {
769
0
      g_byte_array_append(buf, fu_chunk_get_data(chk), fu_chunk_get_data_sz(chk));
770
0
    }
771
0
    g_ptr_array_add(chunks_zlib, g_steal_pointer(&buf));
772
0
  }
773
774
  /* create header */
775
0
  archive_size = FU_STRUCT_CAB_HEADER_SIZE;
776
0
  archive_size += FU_STRUCT_CAB_FOLDER_SIZE;
777
0
  for (guint i = 0; i < imgs->len; i++) {
778
0
    FuFirmware *img = g_ptr_array_index(imgs, i);
779
0
    const gchar *filename_win32 = fu_cab_image_get_win32_filename(FU_CAB_IMAGE(img));
780
0
    archive_size += FU_STRUCT_CAB_FILE_SIZE + strlen(filename_win32) + 1;
781
0
  }
782
0
  for (guint i = 0; i < chunks_zlib->len; i++) {
783
0
    GByteArray *chunk = g_ptr_array_index(chunks_zlib, i);
784
0
    archive_size += FU_STRUCT_CAB_DATA_SIZE + chunk->len;
785
0
  }
786
0
  offset = FU_STRUCT_CAB_HEADER_SIZE;
787
0
  offset += FU_STRUCT_CAB_FOLDER_SIZE;
788
0
  fu_struct_cab_header_set_size(st_hdr, archive_size);
789
0
  fu_struct_cab_header_set_off_cffile(st_hdr, offset);
790
0
  fu_struct_cab_header_set_nr_files(st_hdr, imgs->len);
791
792
  /* create folder */
793
0
  for (guint i = 0; i < imgs->len; i++) {
794
0
    FuFirmware *img = g_ptr_array_index(imgs, i);
795
0
    const gchar *filename_win32 = fu_cab_image_get_win32_filename(FU_CAB_IMAGE(img));
796
0
    offset += FU_STRUCT_CAB_FILE_SIZE;
797
0
    offset += strlen(filename_win32) + 1;
798
0
  }
799
0
  fu_struct_cab_folder_set_offset(st_folder, offset);
800
0
  fu_struct_cab_folder_set_ndatab(st_folder, fu_chunk_array_length(chunks));
801
0
  fu_struct_cab_folder_set_compression(st_folder,
802
0
               priv->compressed ? FU_CAB_COMPRESSION_MSZIP
803
0
                    : FU_CAB_COMPRESSION_NONE);
804
0
  g_byte_array_append(st_hdr, st_folder->data, st_folder->len);
805
806
  /* create each CFFILE */
807
0
  for (guint i = 0; i < imgs->len; i++) {
808
0
    FuFirmware *img = g_ptr_array_index(imgs, i);
809
0
    FuCabFileAttribute fattr = FU_CAB_FILE_ATTRIBUTE_NONE;
810
0
    GDateTime *created = fu_cab_image_get_created(FU_CAB_IMAGE(img));
811
0
    const gchar *filename_win32 = fu_cab_image_get_win32_filename(FU_CAB_IMAGE(img));
812
0
    g_autoptr(GByteArray) st_file = fu_struct_cab_file_new();
813
0
    g_autoptr(GBytes) img_blob = fu_firmware_get_bytes(img, NULL);
814
815
0
    if (!g_str_is_ascii(filename_win32))
816
0
      fattr |= FU_CAB_FILE_ATTRIBUTE_NAME_UTF8;
817
0
    fu_struct_cab_file_set_fattr(st_file, fattr);
818
0
    fu_struct_cab_file_set_usize(st_file, g_bytes_get_size(img_blob));
819
0
    fu_struct_cab_file_set_uoffset(st_file, index_into);
820
0
    if (created != NULL) {
821
0
      fu_struct_cab_file_set_date(st_file,
822
0
                ((g_date_time_get_year(created) - 1980) << 9) +
823
0
              (g_date_time_get_month(created) << 5) +
824
0
              g_date_time_get_day_of_month(created));
825
0
      fu_struct_cab_file_set_time(st_file,
826
0
                (g_date_time_get_hour(created) << 11) +
827
0
              (g_date_time_get_minute(created) << 5) +
828
0
              (g_date_time_get_second(created) / 2));
829
0
    }
830
0
    g_byte_array_append(st_hdr, st_file->data, st_file->len);
831
832
0
    g_byte_array_append(st_hdr, (const guint8 *)filename_win32, strlen(filename_win32));
833
0
    fu_byte_array_append_uint8(st_hdr, 0x0);
834
0
    index_into += g_bytes_get_size(img_blob);
835
0
  }
836
837
  /* create each CFDATA */
838
0
  for (guint i = 0; i < fu_chunk_array_length(chunks); i++) {
839
0
    guint32 checksum = 0;
840
0
    GByteArray *chunk_zlib = g_ptr_array_index(chunks_zlib, i);
841
0
    g_autoptr(FuChunk) chk = NULL;
842
0
    g_autoptr(GByteArray) hdr = g_byte_array_new();
843
0
    g_autoptr(GByteArray) st_data = fu_struct_cab_data_new();
844
845
    /* prepare chunk */
846
0
    chk = fu_chunk_array_index(chunks, i, error);
847
0
    if (chk == NULL)
848
0
      return NULL;
849
850
    /* first do the 'checksum' on the data, then the partial header -- slightly crazy */
851
0
    if (!fu_cab_firmware_compute_checksum(chunk_zlib->data,
852
0
                  chunk_zlib->len,
853
0
                  &checksum,
854
0
                  error))
855
0
      return NULL;
856
0
    fu_byte_array_append_uint16(hdr, chunk_zlib->len, G_LITTLE_ENDIAN);
857
0
    fu_byte_array_append_uint16(hdr, fu_chunk_get_data_sz(chk), G_LITTLE_ENDIAN);
858
0
    if (!fu_cab_firmware_compute_checksum(hdr->data, hdr->len, &checksum, error))
859
0
      return NULL;
860
861
0
    fu_struct_cab_data_set_checksum(st_data, checksum);
862
0
    fu_struct_cab_data_set_comp(st_data, chunk_zlib->len);
863
0
    fu_struct_cab_data_set_uncomp(st_data, fu_chunk_get_data_sz(chk));
864
0
    g_byte_array_append(st_hdr, st_data->data, st_data->len);
865
0
    g_byte_array_append(st_hdr, chunk_zlib->data, chunk_zlib->len);
866
0
  }
867
868
  /* success */
869
0
  return g_steal_pointer(&st_hdr);
870
0
}
871
872
static gboolean
873
fu_cab_firmware_build(FuFirmware *firmware, XbNode *n, GError **error)
874
0
{
875
0
  FuCabFirmware *self = FU_CAB_FIRMWARE(firmware);
876
0
  FuCabFirmwarePrivate *priv = GET_PRIVATE(self);
877
0
  const gchar *tmp;
878
879
  /* simple properties */
880
0
  tmp = xb_node_query_text(n, "compressed", NULL);
881
0
  if (tmp != NULL) {
882
0
    if (!fu_strtobool(tmp, &priv->compressed, error))
883
0
      return FALSE;
884
0
  }
885
0
  tmp = xb_node_query_text(n, "only_basename", NULL);
886
0
  if (tmp != NULL) {
887
0
    if (!fu_strtobool(tmp, &priv->only_basename, error))
888
0
      return FALSE;
889
0
  }
890
891
  /* success */
892
0
  return TRUE;
893
0
}
894
895
static void
896
fu_cab_firmware_export(FuFirmware *firmware, FuFirmwareExportFlags flags, XbBuilderNode *bn)
897
0
{
898
0
  FuCabFirmware *self = FU_CAB_FIRMWARE(firmware);
899
0
  FuCabFirmwarePrivate *priv = GET_PRIVATE(self);
900
0
  fu_xmlb_builder_insert_kb(bn, "compressed", priv->compressed);
901
0
  fu_xmlb_builder_insert_kb(bn, "only_basename", priv->only_basename);
902
0
}
903
904
static void
905
fu_cab_firmware_class_init(FuCabFirmwareClass *klass)
906
1
{
907
1
  FuFirmwareClass *firmware_class = FU_FIRMWARE_CLASS(klass);
908
1
  firmware_class->validate = fu_cab_firmware_validate;
909
1
  firmware_class->parse = fu_cab_firmware_parse;
910
1
  firmware_class->write = fu_cab_firmware_write;
911
1
  firmware_class->build = fu_cab_firmware_build;
912
1
  firmware_class->export = fu_cab_firmware_export;
913
1
}
914
915
static void
916
fu_cab_firmware_init(FuCabFirmware *self)
917
2.91k
{
918
2.91k
  g_type_ensure(FU_TYPE_CAB_IMAGE);
919
2.91k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_STORED_SIZE);
920
2.91k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_HAS_CHECKSUM);
921
2.91k
  fu_firmware_add_flag(FU_FIRMWARE(self), FU_FIRMWARE_FLAG_DEDUPE_ID);
922
2.91k
  fu_firmware_set_images_max(FU_FIRMWARE(self), G_MAXUINT16);
923
2.91k
}
924
925
/**
926
 * fu_cab_firmware_new:
927
 *
928
 * Returns: (transfer full): a #FuCabFirmware
929
 *
930
 * Since: 1.9.7
931
 **/
932
FuCabFirmware *
933
fu_cab_firmware_new(void)
934
0
{
935
0
  return g_object_new(FU_TYPE_CAB_FIRMWARE, NULL);
936
0
}