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