1 /*
2 * LibGCab
3 * Copyright (c) 2012, Marc-André Lureau <marcandre.lureau@gmail.com>
4 * Copyright (c) 2017, Richard Hughes <richard@hughsie.com>
5 *
6 * This library is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU Lesser General Public
8 * License as published by the Free Software Foundation; either
9 * version 2.1 of the License, or (at your option) any later version.
10 *
11 * This library is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 * Lesser General Public License for more details.
15 *
16 * You should have received a copy of the GNU Lesser General Public
17 * License along with this library; if not, write to the Free Software
18 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
19 * MA 02110-1301 USA
20 */
21
22 #include "config.h"
23
24 #include "gcab-priv.h"
25
26 static voidpf
zalloc(voidpf opaque,uInt items,uInt size)27 zalloc (voidpf opaque, uInt items, uInt size)
28 {
29 return g_malloc (items *size);
30 }
31 static void
zfree(voidpf opaque,voidpf address)32 zfree (voidpf opaque, voidpf address)
33 {
34 g_free (address);
35 }
36
37 static gboolean
cdata_set(cdata_t * cd,int type,guint8 * data,size_t size)38 cdata_set (cdata_t *cd, int type, guint8 *data, size_t size)
39 {
40 if (type > GCAB_COMPRESSION_MSZIP) {
41 g_critical ("unsupported compression method %d", type);
42 return FALSE;
43 }
44
45 cd->nubytes = size;
46
47 if (type == 0) {
48 memcpy (cd->in, data, size);
49 cd->ncbytes = size;
50 }
51
52 if (type == GCAB_COMPRESSION_MSZIP) {
53 z_stream stream = { 0, };
54
55 stream.zalloc = zalloc;
56 stream.zfree = zfree;
57 if (deflateInit2 (&stream, Z_DEFAULT_COMPRESSION, Z_DEFLATED, -15, 8, Z_DEFAULT_STRATEGY) != Z_OK)
58 return FALSE;
59 stream.next_in = data;
60 stream.avail_in = size;
61 stream.next_out = cd->in + 2;
62 stream.avail_out = sizeof (cd->in) - 2;
63 /* insert the signature */
64 cd->in[0] = 'C';
65 cd->in[1] = 'K';
66 deflate (&stream, Z_FINISH);
67 deflateEnd (&stream);
68 cd->ncbytes = stream.total_out + 2;
69 }
70
71 return TRUE;
72 }
73
74 static char *
_data_input_stream_read_until(GDataInputStream * stream,const gchar * stop_chars,gsize stop_len,GCancellable * cancellable,GError ** error)75 _data_input_stream_read_until (GDataInputStream *stream,
76 const gchar *stop_chars,
77 gsize stop_len,
78 GCancellable *cancellable,
79 GError **error)
80 {
81 GBufferedInputStream *bstream;
82 gchar *result;
83
84 bstream = G_BUFFERED_INPUT_STREAM (stream);
85
86 result = g_data_input_stream_read_upto (stream, stop_chars, stop_len,
87 NULL, cancellable, error);
88
89 /* If we're not at end of stream then we have a stop_char to consume. */
90 if (result != NULL && g_buffered_input_stream_get_available (bstream) > 0)
91 {
92 gsize res;
93 gchar b;
94
95 res = g_input_stream_read (G_INPUT_STREAM (stream), &b, 1, NULL, NULL);
96 g_assert (res == 1);
97 }
98
99 return result;
100 }
101
102 #define R1(val) G_STMT_START{ \
103 val = g_data_input_stream_read_byte (in, cancellable, error); \
104 if (error && *error) \
105 goto end; \
106 }G_STMT_END
107 #define R2(val) G_STMT_START{ \
108 val = g_data_input_stream_read_uint16 (in, cancellable, error); \
109 if (error && *error) \
110 goto end; \
111 }G_STMT_END
112 #define R4(val) G_STMT_START{ \
113 val = g_data_input_stream_read_uint32 (in, cancellable, error); \
114 if (error && *error) \
115 goto end; \
116 }G_STMT_END
117 #define RS(val) G_STMT_START{ \
118 val = _data_input_stream_read_until (in, "\0", 1, \
119 cancellable, error); \
120 if (error && *error) \
121 goto end; \
122 if (!val) { \
123 g_set_error(error, GCAB_ERROR, GCAB_ERROR_FORMAT, \
124 "Invalid contents"); \
125 goto end; \
126 } \
127 }G_STMT_END
128 #define RN(buff, size) G_STMT_START{ \
129 if (size) { \
130 gint _val = g_input_stream_read (G_INPUT_STREAM (in), buff, size, cancellable, error); \
131 if (error && *error) \
132 goto end; \
133 if (_val >= 0 && _val < size) { \
134 g_set_error(error, GCAB_ERROR, GCAB_ERROR_FORMAT, \
135 "Expected %d bytes, got %d", size, _val); \
136 goto end; \
137 } \
138 if (_val == -1) { \
139 g_set_error(error, GCAB_ERROR, GCAB_ERROR_FORMAT, \
140 "Invalid contents"); \
141 goto end; \
142 } \
143 } \
144 }G_STMT_END
145
146 static void
hexdump(guchar * p,gsize s)147 hexdump (guchar *p, gsize s)
148 {
149 gsize i;
150
151 for (i = 0; i < s; i++) {
152 if (i != 0) {
153 if (i % 16 == 0)
154 g_printerr ("\n");
155 else if (i % 8 == 0)
156 g_printerr (" ");
157 else
158 g_printerr (" ");
159 }
160
161 if (i % 16 == 0)
162 g_printerr ("%.8x ", (guint)i);
163
164 g_printerr ("%.2x", (guint)p[i]);
165 }
166
167 g_printerr ("\n");
168 }
169
170 #define P1(p, field) \
171 g_debug ("%15s: %.2x", #field, (guint)p->field)
172 #define P2(p, field) \
173 g_debug ("%15s: %.4x", #field, (guint)p->field)
174 #define P4(p, field) \
175 g_debug ("%15s: %.8x", #field, (guint)p->field)
176 #define PS(p, field) \
177 g_debug ("%15s: %s", #field, p->field)
178 #define PN(p, field, size) \
179 g_debug ("%15s:", #field), hexdump (p->field, size)
180 #define PND(p, field, size) \
181 g_debug ("%15s:", #field), hexdump (field, size)
182
183 #define W1(val) \
184 g_data_output_stream_put_byte (out, val, cancellable, error)
185 #define W2(val) \
186 g_data_output_stream_put_uint16 (out, val, cancellable, error)
187 #define W4(val) \
188 g_data_output_stream_put_uint32 (out, val, cancellable, error)
189 #define WS(val) \
190 g_data_output_stream_put_string (out, val, cancellable, error)
191
192 G_GNUC_INTERNAL gboolean
cheader_write(cheader_t * ch,GDataOutputStream * out,GCancellable * cancellable,GError ** error)193 cheader_write (cheader_t *ch, GDataOutputStream *out,
194 GCancellable *cancellable, GError **error)
195 {
196 GOutputStream *stream = g_filter_output_stream_get_base_stream (G_FILTER_OUTPUT_STREAM (out));
197
198 if (!W1 ('M') || !W1 ('S') || !W1 ('C') || !W1 ('F') ||
199 !W4 (ch->res1) ||
200 !W4 (ch->size) ||
201 !W4 (ch->res2) ||
202 !W4 (ch->offsetfiles) ||
203 !W4 (ch->res3) ||
204 !W1 (ch->versionMIN = 3) ||
205 !W1 (ch->versionMAJ = 1) ||
206 !W2 (ch->nfolders) ||
207 !W2 (ch->nfiles) ||
208 !W2 (ch->flags) ||
209 !W2 (ch->setID) ||
210 !W2 (ch->cabID))
211 return FALSE;
212
213 if (ch->flags & CABINET_HEADER_RESERVE) {
214 if (!W2 (ch->res_header) ||
215 !W1 (ch->res_folder) ||
216 !W1 (ch->res_data))
217 return FALSE;
218 if (g_output_stream_write (stream, ch->reserved, ch->res_header,
219 cancellable, error) == -1)
220 return FALSE;
221 }
222
223 return TRUE;
224 }
225
226 G_GNUC_INTERNAL gboolean
cheader_read(cheader_t * ch,GDataInputStream * in,GCancellable * cancellable,GError ** error)227 cheader_read (cheader_t *ch, GDataInputStream *in,
228 GCancellable *cancellable, GError **error)
229 {
230 gboolean success = FALSE;
231 guint8 sig[4];
232
233 R1 (sig[0]);
234 R1 (sig[1]);
235 R1 (sig[2]);
236 R1 (sig[3]);
237 if (memcmp (sig, "MSCF", 4)) {
238 g_set_error (error, GCAB_ERROR, GCAB_ERROR_FORMAT,
239 "The input is not of cabinet format");
240 goto end;
241 }
242
243 memset (ch, 0, sizeof (cheader_t));
244 R4 (ch->res1);
245 R4 (ch->size);
246 R4 (ch->res2);
247 R4 (ch->offsetfiles);
248 R4 (ch->res3);
249 R1 (ch->versionMIN);
250 R1 (ch->versionMAJ);
251 R2 (ch->nfolders);
252 R2 (ch->nfiles);
253 R2 (ch->flags);
254 R2 (ch->setID);
255 R2 (ch->cabID);
256
257 if (ch->flags & CABINET_HEADER_RESERVE) {
258 R2 (ch->res_header);
259 R1 (ch->res_folder);
260 R1 (ch->res_data);
261 ch->reserved = g_malloc (ch->res_header);
262 RN (ch->reserved, ch->res_header);
263 }
264
265 if (ch->flags & CABINET_HEADER_PREV) {
266 RS (ch->cab_prev);
267 RS (ch->disk_prev);
268 }
269
270 if (ch->flags & CABINET_HEADER_NEXT) {
271 RS (ch->cab_next);
272 RS (ch->disk_next);
273 }
274
275 if (g_getenv ("GCAB_DEBUG")) {
276 g_debug ("CFHEADER");
277 P4 (ch, res1);
278 P4 (ch, size);
279 P4 (ch, res2);
280 P4 (ch, offsetfiles);
281 P4 (ch, res3);
282 P1 (ch, versionMIN);
283 P1 (ch, versionMAJ);
284 P2 (ch, nfolders);
285 P2 (ch, nfiles);
286 P2 (ch, flags);
287 P2 (ch, setID);
288 P2 (ch, cabID);
289 if (ch->flags & CABINET_HEADER_RESERVE) {
290 P2 (ch, res_header);
291 P1 (ch, res_folder);
292 P1 (ch, res_data);
293 if (ch->res_header)
294 PN (ch, reserved, ch->res_header);
295 }
296 if (ch->flags & CABINET_HEADER_PREV) {
297 PS (ch, cab_prev);
298 PS (ch, disk_prev);
299 }
300 if (ch->flags & CABINET_HEADER_NEXT) {
301 PS (ch, cab_next);
302 PS (ch, disk_next);
303 }
304
305 }
306
307 success = TRUE;
308
309 end:
310 return success;
311 }
312
313 void
cheader_free(cheader_t * ch)314 cheader_free (cheader_t *ch)
315 {
316 if (ch == NULL)
317 return;
318 g_free (ch->reserved);
319 g_free (ch->cab_prev);
320 g_free (ch->disk_prev);
321 g_free (ch->cab_next);
322 g_free (ch->disk_next);
323 g_free (ch);
324 }
325
326 G_GNUC_INTERNAL gboolean
cfolder_write(cfolder_t * cf,GDataOutputStream * out,GCancellable * cancellable,GError ** error)327 cfolder_write (cfolder_t *cf, GDataOutputStream *out,
328 GCancellable *cancellable, GError **error)
329 {
330 if ((!W4 (cf->offsetdata)) ||
331 (!W2 (cf->ndatab)) ||
332 (!W2 (cf->typecomp)))
333 return FALSE;
334
335 return TRUE;
336 }
337
338 G_GNUC_INTERNAL gboolean
cfolder_read(cfolder_t * cf,guint8 res_size,GDataInputStream * in,GCancellable * cancellable,GError ** error)339 cfolder_read (cfolder_t *cf, guint8 res_size, GDataInputStream *in,
340 GCancellable *cancellable, GError **error)
341 {
342 gboolean success = FALSE;
343
344 R4 (cf->offsetdata);
345 R2 (cf->ndatab);
346 R2 (cf->typecomp);
347 cf->reserved = g_malloc (res_size);
348 RN (cf->reserved, res_size);
349
350 if (g_getenv ("GCAB_DEBUG")) {
351 g_debug ("CFOLDER");
352 P4 (cf, offsetdata);
353 P2 (cf, ndatab);
354 P2 (cf, typecomp);
355 if (res_size)
356 PN (cf, reserved, res_size);
357 }
358
359 success = TRUE;
360
361 end:
362 return success;
363 }
364
365 void
cfolder_free(cfolder_t * cf)366 cfolder_free (cfolder_t *cf)
367 {
368 if (cf == NULL)
369 return;
370 g_free (cf->reserved);
371 g_free (cf);
372 }
373
374 G_GNUC_INTERNAL gboolean
cfile_write(cfile_t * cf,GDataOutputStream * out,GCancellable * cancellable,GError ** error)375 cfile_write (cfile_t *cf, GDataOutputStream *out,
376 GCancellable *cancellable, GError **error)
377 {
378 if ((!W4 (cf->usize)) ||
379 (!W4 (cf->uoffset)) ||
380 (!W2 (cf->index)) ||
381 (!W2 (cf->date)) ||
382 (!W2 (cf->time)) ||
383 (!W2 (cf->fattr)) ||
384 (!WS (cf->name) || !W1 (0)))
385 return FALSE;
386
387 return TRUE;
388 }
389
390
391 G_GNUC_INTERNAL gboolean
cfile_read(cfile_t * cf,GDataInputStream * in,GCancellable * cancellable,GError ** error)392 cfile_read (cfile_t *cf, GDataInputStream *in,
393 GCancellable *cancellable, GError **error)
394 {
395 gboolean success = FALSE;
396
397 R4 (cf->usize);
398 R4 (cf->uoffset);
399 R2 (cf->index);
400 R2 (cf->date);
401 R2 (cf->time);
402 R2 (cf->fattr);
403 RS (cf->name);
404
405 if (g_getenv ("GCAB_DEBUG")) {
406 g_debug ("CFILE");
407 P4 (cf, usize);
408 P4 (cf, uoffset);
409 P2 (cf, index);
410 P2 (cf, date);
411 P2 (cf, time);
412 P2 (cf, fattr);
413 PS (cf, name);
414 }
415
416 success = TRUE;
417
418 end:
419 return success;
420 }
421
422 void
cfile_free(cfile_t * cf)423 cfile_free (cfile_t *cf)
424 {
425 if (cf == NULL)
426 return;
427 g_free (cf->name);
428 g_free (cf);
429 }
430
431 static guint32
compute_checksum(guint8 * in,guint16 ncbytes,guint32 seed)432 compute_checksum (guint8 *in, guint16 ncbytes, guint32 seed)
433 {
434 int no_ulongs;
435 guint32 csum=0;
436 guint32 temp;
437
438 no_ulongs = ncbytes / 4;
439 csum = seed;
440
441 while (no_ulongs-- > 0) {
442 temp = ((guint32) (*in++));
443 temp |= (((guint32) (*in++)) << 8);
444 temp |= (((guint32) (*in++)) << 16);
445 temp |= (((guint32) (*in++)) << 24);
446
447 csum ^= temp;
448 }
449
450 temp = 0;
451 switch (ncbytes % 4) {
452 case 3: temp |= (((guint32) (*in++)) << 16);
453 /* fall-thru */
454 case 2: temp |= (((guint32) (*in++)) << 8);
455 /* fall-thru */
456 case 1: temp |= ((guint32) (*in++));
457 /* fall-thru */
458 default: break;
459 }
460
461 csum ^= temp;
462
463 return csum;
464 }
465
466 G_GNUC_INTERNAL gboolean
cdata_write(cdata_t * cd,GDataOutputStream * out,int type,guint8 * data,size_t size,gsize * bytes_written,GCancellable * cancellable,GError ** error)467 cdata_write (cdata_t *cd, GDataOutputStream *out, int type,
468 guint8 *data, size_t size, gsize *bytes_written,
469 GCancellable *cancellable, GError **error)
470 {
471 if (!cdata_set(cd, type, data, size))
472 return FALSE;
473
474 guint32 datacsum = compute_checksum(cd->in, cd->ncbytes, 0);
475 guint8 sizecsum[4];
476 guint16 nbytes_le;
477
478 nbytes_le = GUINT16_TO_LE (cd->ncbytes);
479 memcpy (&sizecsum[0], &nbytes_le, 2);
480 nbytes_le = GUINT16_TO_LE (cd->nubytes);
481 memcpy (&sizecsum[2], &nbytes_le, 2);
482 cd->checksum = compute_checksum (sizecsum, sizeof(sizecsum), datacsum);
483 GOutputStream *stream = g_filter_output_stream_get_base_stream (G_FILTER_OUTPUT_STREAM (out));
484
485 *bytes_written = 0;
486
487 if ((!W4 (cd->checksum)) ||
488 (!W2 (cd->ncbytes)) ||
489 (!W2 (cd->nubytes)) ||
490 (g_output_stream_write (stream, cd->in, cd->ncbytes, cancellable, error) == -1))
491 return FALSE;
492
493 *bytes_written = 4 + 2 + 2 + cd->ncbytes;
494
495 return TRUE;
496 }
497
498 G_GNUC_INTERNAL void
cdata_free(cdata_t * cd)499 cdata_free (cdata_t *cd)
500 {
501 z_stream *z = &cd->z;
502
503 if (cd->decomp.comptype == GCAB_COMPRESSION_LZX) {
504 LZXfdi_clear (&cd->decomp);
505 }
506
507 if (cd->decomp.comptype == GCAB_COMPRESSION_MSZIP) {
508 if (z->opaque) {
509 inflateEnd (z);
510 z->opaque = NULL;
511 }
512 }
513 g_free (cd->reserved);
514 g_free (cd);
515 }
516
517 static gint
_enforce_checksum(void)518 _enforce_checksum (void)
519 {
520 static gint enforce = -1;
521 if (enforce == -1)
522 enforce = g_getenv ("GCAB_SKIP_CHECKSUM") == NULL ? 1 : 0;
523 return enforce;
524 }
525
526 G_GNUC_INTERNAL gboolean
cdata_read(cdata_t * cd,guint8 res_data,gint comptype,GDataInputStream * in,GCancellable * cancellable,GError ** error)527 cdata_read (cdata_t *cd, guint8 res_data, gint comptype,
528 GDataInputStream *in, GCancellable *cancellable, GError **error)
529
530 {
531 gboolean success = FALSE;
532 int ret, zret = Z_OK;
533 gint compression = comptype & GCAB_COMPRESSION_MASK;
534 gsize buf_sz;
535 guint8 *buf = NULL;
536 guint32 datacsum;
537 guint32 checksum_tmp;
538 guint8 sizecsum[4];
539 guint16 nbytes_le;
540
541 /* decompress directly into ->out for no decompression */
542 switch (compression) {
543 case GCAB_COMPRESSION_NONE:
544 buf = cd->out;
545 buf_sz = sizeof(cd->out);
546 break;
547 case GCAB_COMPRESSION_MSZIP:
548 case GCAB_COMPRESSION_LZX:
549 buf = cd->in;
550 buf_sz = sizeof(cd->in);
551 break;
552 default:
553 g_set_error (error, GCAB_ERROR, GCAB_ERROR_NOT_SUPPORTED,
554 "unsupported compression method %d", compression);
555 break;
556 }
557 if (buf == NULL)
558 return FALSE;
559
560 R4 (cd->checksum);
561 R2 (cd->ncbytes);
562 if (cd->ncbytes > buf_sz) {
563 g_set_error (error, GCAB_ERROR, GCAB_ERROR_INVALID_DATA,
564 "tried to decompress %" G_GUINT16_FORMAT " bytes "
565 "into buffer of size %" G_GSIZE_FORMAT,
566 cd->ncbytes, buf_sz);
567 return FALSE;
568 }
569 R2 (cd->nubytes);
570 if (cd->nubytes > CAB_BLOCKMAX) {
571 g_set_error (error, GCAB_ERROR, GCAB_ERROR_INVALID_DATA,
572 "CDATA block of %" G_GUINT16_FORMAT " bytes "
573 "was bigger than maximum size %i",
574 cd->nubytes, CAB_BLOCKMAX);
575 return FALSE;
576 }
577 RN (cd->reserved, res_data);
578 RN (buf, cd->ncbytes);
579
580 datacsum = compute_checksum(buf, cd->ncbytes, 0);
581 nbytes_le = GUINT16_TO_LE (cd->ncbytes);
582 memcpy (&sizecsum[0], &nbytes_le, 2);
583 nbytes_le = GUINT16_TO_LE (cd->nubytes);
584 memcpy (&sizecsum[2], &nbytes_le, 2);
585 checksum_tmp = compute_checksum (sizecsum, sizeof(sizecsum), datacsum);
586 if (cd->checksum != checksum_tmp) {
587 if (_enforce_checksum ()) {
588 g_set_error_literal (error, GCAB_ERROR, GCAB_ERROR_INVALID_DATA,
589 "incorrect checksum detected");
590 return FALSE;
591 }
592 if (g_getenv ("GCAB_DEBUG"))
593 g_debug ("CDATA checksum 0x%08x", (guint) checksum_tmp);
594 }
595
596 if (g_getenv ("GCAB_DEBUG")) {
597 g_debug ("CDATA");
598 P4 (cd, checksum);
599 P2 (cd, ncbytes);
600 P2 (cd, nubytes);
601 if (res_data)
602 PN (cd, reserved, res_data);
603 PND (cd, buf, 64);
604 }
605
606 if (compression == GCAB_COMPRESSION_LZX) {
607 if (cd->fdi.alloc == NULL) {
608 cd->fdi.alloc = g_malloc;
609 cd->fdi.free = g_free;
610 cd->decomp.fdi = &cd->fdi;
611 cd->decomp.inbuf = cd->in;
612 cd->decomp.outbuf = cd->out;
613 cd->decomp.comptype = compression;
614
615 ret = LZXfdi_init((comptype >> 8) & 0x1f, &cd->decomp);
616 if (ret < 0)
617 goto end;
618 }
619
620 ret = LZXfdi_decomp (cd->ncbytes, cd->nubytes, &cd->decomp);
621 if (ret < 0)
622 goto end;
623 }
624
625 if (compression == GCAB_COMPRESSION_MSZIP) {
626 if (cd->in[0] != 'C' || cd->in[1] != 'K')
627 goto end;
628
629 cd->decomp.comptype = compression;
630 z_stream *z = &cd->z;
631
632 z->avail_in = cd->ncbytes - 2;
633 z->next_in = cd->in + 2;
634 z->avail_out = cd->nubytes;
635 z->next_out = cd->out;
636 z->total_out = 0;
637
638 if (!z->opaque) {
639 z->zalloc = zalloc;
640 z->zfree = zfree;
641 z->opaque = cd;
642
643 zret = inflateInit2 (z, -MAX_WBITS);
644 if (zret != Z_OK)
645 goto end;
646 }
647
648 while (1) {
649 zret = inflate (z, Z_BLOCK);
650 if (zret == Z_STREAM_END)
651 break;
652 if (zret != Z_OK)
653 goto end;
654 }
655
656 g_warn_if_fail (z->avail_in == 0);
657 g_warn_if_fail (z->avail_out == 0);
658 if (z->avail_in != 0 || z->avail_out != 0)
659 goto end;
660
661 zret = inflateReset (z);
662 if (zret != Z_OK)
663 goto end;
664
665 zret = inflateSetDictionary (z, cd->out, cd->nubytes);
666 if (zret != Z_OK)
667 goto end;
668 }
669
670 success = TRUE;
671
672 end:
673 if (zret != Z_OK)
674 g_set_error (error, GCAB_ERROR, GCAB_ERROR_FAILED,
675 "zlib failed: %s", zError (zret));
676
677 if (error != NULL && *error == NULL && !success)
678 g_set_error (error, GCAB_ERROR, GCAB_ERROR_FAILED,
679 "Invalid cabinet chunk");
680
681 return success;
682 }
683