Bump to libzip 1.9.2
[platform/upstream/libzip.git] / src / zipcmp.c
1 /*
2   zipcmp.c -- compare zip files
3   Copyright (C) 2003-2022 Dieter Baron and Thomas Klausner
4
5   This file is part of libzip, a library to manipulate ZIP archives.
6   The authors can be contacted at <libzip@nih.at>
7
8   Redistribution and use in source and binary forms, with or without
9   modification, are permitted provided that the following conditions
10   are met:
11   1. Redistributions of source code must retain the above copyright
12      notice, this list of conditions and the following disclaimer.
13   2. Redistributions in binary form must reproduce the above copyright
14      notice, this list of conditions and the following disclaimer in
15      the documentation and/or other materials provided with the
16      distribution.
17   3. The names of the authors may not be used to endorse or promote
18      products derived from this software without specific prior
19      written permission.
20
21   THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND ANY EXPRESS
22   OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
23   WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
24   ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY
25   DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
26   DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE
27   GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
28   INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER
29   IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
30   OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN
31   IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33
34
35 #include "config.h"
36
37 #include <errno.h>
38 #include <stdio.h>
39 #include <stdlib.h>
40 #include <string.h>
41 #include <sys/stat.h>
42 #ifdef HAVE_STRINGS_H
43 #include <strings.h>
44 #endif
45 #ifdef HAVE_FTS_H
46 #include <fts.h>
47 #endif
48 #include <zlib.h>
49
50 #ifndef HAVE_GETOPT
51 #include "getopt.h"
52 #endif
53
54 #include "zip.h"
55
56 #include "compat.h"
57
58 #include "diff_output.h"
59
60 struct archive {
61     const char *name;
62     zip_t *za;
63     zip_uint64_t nentry;
64     struct entry *entry;
65     const char *comment;
66     size_t comment_length;
67 };
68
69 struct ef {
70     const char *name;
71     zip_uint16_t flags;
72     zip_uint16_t id;
73     zip_uint16_t size;
74     const zip_uint8_t *data;
75 };
76
77 struct entry {
78     char *name;
79     zip_uint64_t size;
80     zip_uint32_t crc;
81     zip_uint32_t comp_method;
82     struct ef *extra_fields;
83     zip_uint16_t n_extra_fields;
84     const char *comment;
85     zip_uint32_t comment_length;
86 };
87
88
89 typedef struct {
90     uint32_t value;
91     const char * const name;
92 } enum_map_t;
93
94 const enum_map_t comp_methods[] = {
95     { 0, "Stored (no compression)" },
96     { 1, "Shrunk" },
97     { 2, "Reduced with compression factor 1" },
98     { 3, "Reduced with compression factor 2" },
99     { 4, "Reduced with compression factor 3" },
100     { 5, "Reduced with compression factor 4" },
101     { 6, "Imploded" },
102     { 7, "Reserved for Tokenizing compression algorithm" },
103     { 8, "Deflated" },
104     { 9, "Enhanced Deflating using Deflate64(tm)" },
105     { 10, "PKWARE Data Compression Library Imploding (old IBM TERSE)" },
106     { 11, "11 (Reserved by PKWARE)" },
107     { 12, "BZIP2" },
108     { 13, "13 (Reserved by PKWARE)" },
109     { 14, "LZMA (EFS)" },
110     { 15, "15 (Reserved by PKWARE)" },
111     { 16, "16 (Reserved by PKWARE)" },
112     { 17, "17 (Reserved by PKWARE)" },
113     { 18, "IBM TERSE (new)" },
114     { 19, "IBM LZ77 z Architecture (PFS)" },
115     { 20, "Zstandard compressed data (obsolete)" },
116     { 93, "Zstandard compressed data" },
117     { 95, "XZ compressed data" },
118     { 97, "WavPack compressed data" },
119     { 98, "PPMd version I, Rev 1" },
120     { 99, "WinZIP AES Encryption" },
121     { UINT32_MAX, NULL }
122 };
123
124 const enum_map_t extra_fields[] = {
125     /* PKWARE defined */
126     { 0x0001, "Zip64 extended information" },
127     { 0x0007, "AV Info" },
128     { 0x0008, "Reserved for extended language encoding data (PFS)" },
129     { 0x0009, "OS/2" },
130     { 0x000a, "NTFS" },
131     { 0x000c, "OpenVMS" },
132     { 0x000d, "UNIX" },
133     { 0x000e, "Reserved for file stream and fork descriptors" },
134     { 0x000f, "Patch Descriptor" },
135     { 0x0014, "PKCS#7 Store for X.509 Certificates" },
136     { 0x0015, "X.509 Certificate ID and Signature for individual file" },
137     { 0x0016, "X.509 Certificate ID for Central Directory" },
138     { 0x0017, "Strong Encryption Header" },
139     { 0x0018, "Record Management Controls" },
140     { 0x0019, "PKCS#7 Encryption Recipient Certificate List" },
141     { 0x0065, "IBM S/390 (Z390), AS/400 (I400) attributes - uncompressed" },
142     { 0x0066, "Reserved for IBM S/390 (Z390), AS/400 (I400) attributes - compressed" },
143     { 0x4690, "POSZIP 4690 (reserved)" },
144
145     /* Third-Party defined; see InfoZIP unzip sources proginfo/extrafld.txt */
146     { 0x07c8, "Info-ZIP Macintosh (old)" },
147     { 0x2605, "ZipIt Macintosh (first version)" },
148     { 0x2705, "ZipIt Macintosh 1.3.5+ (w/o full filename)" },
149     { 0x2805, "ZipIt Macintosh 1.3.5+" },
150     { 0x334d, "Info-ZIP Macintosh (new)" },
151     { 0x4154, "Tandem NSK" },
152     { 0x4341, "Acorn/SparkFS" },
153     { 0x4453, "Windows NT security descriptor" },
154     { 0x4704, "VM/CMS" },
155     { 0x470f, "MVS" },
156     { 0x4854, "Theos, old unofficial port" },
157     { 0x4b46, "FWKCS MD5" },
158     { 0x4c41, "OS/2 access control list (text ACL)" },
159     { 0x4d49, "Info-ZIP OpenVMS (obsolete)" },
160     { 0x4d63, "Macintosh SmartZIP" },
161     { 0x4f4c, "Xceed original location extra field" },
162     { 0x5356, "AOS/VS (ACL)" },
163     { 0x5455, "extended timestamp" },
164     { 0x554e, "Xceed unicode extra field" },
165     { 0x5855, "Info-ZIP UNIX (original)" },
166     { 0x6375, "Info-ZIP UTF-8 comment field" },
167     { 0x6542, "BeOS (BeBox, PowerMac, etc.)" },
168     { 0x6854, "Theos" },
169     { 0x7075, "Info-ZIP UTF-8 name field" },
170     { 0x7441, "AtheOS (AtheOS/Syllable attributes)" },
171     { 0x756e, "ASi UNIX" },
172     { 0x7855, "Info-ZIP UNIX" },
173     { 0x7875, "Info-ZIP UNIX 3rd generation" },
174     { 0x9901, "WinZIP AES encryption" },
175     { 0xa220, "Microsoft Open Packaging Growth Hint" },
176     { 0xcafe, "executable Java JAR file" },
177     { 0xfb4a, "SMS/QDOS" }, /* per InfoZIP extrafld.txt */
178     { 0xfd4a, "SMS/QDOS" }, /* per appnote.txt */
179     { UINT32_MAX, NULL }
180 };
181
182
183 const char *progname;
184
185 #define PROGRAM "zipcmp"
186
187 #define USAGE "usage: %s [-hipqtVv] archive1 archive2\n"
188
189 char help_head[] = PROGRAM " (" PACKAGE ") by Dieter Baron and Thomas Klausner\n\n";
190
191 char help[] = "\n\
192   -h       display this help message\n\
193   -C       check archive consistencies\n\
194   -i       compare names ignoring case distinctions\n\
195   -p       compare as many details as possible\n\
196   -q       be quiet\n\
197   -s       print a summary\n\
198   -t       test zip files (compare file contents to checksum)\n\
199   -V       display version number\n\
200   -v       be verbose (print differences, default)\n\
201 \n\
202 Report bugs to <libzip@nih.at>.\n";
203
204 char version_string[] = PROGRAM " (" PACKAGE " " VERSION ")\n\
205 Copyright (C) 2003-2022 Dieter Baron and Thomas Klausner\n\
206 " PACKAGE " comes with ABSOLUTELY NO WARRANTY, to the extent permitted by law.\n";
207
208 #define OPTIONS "hVCipqstv"
209
210
211 #define BOTH_ARE_ZIPS(a) (a[0].za && a[1].za)
212
213 static int comment_compare(const char *c1, size_t l1, const char *c2, size_t l2);
214 static int compare_list(char *const name[2], const void *list[2], const zip_uint64_t list_length[2], int element_size, int (*cmp)(const void *a, const void *b), int (*ignore)(const void *list, int last, const void *other), int (*check)(char *const name[2], const void *a, const void *b), void (*print)(char side, const void *element), void (*start_file)(const void *element));
215 static int compare_zip(char *const zn[]);
216 static int ef_compare(char *const name[2], const struct entry *e1, const struct entry *e2);
217 static int ef_order(const void *a, const void *b);
218 static void ef_print(char side, const void *p);
219 static int ef_read(zip_t *za, zip_uint64_t idx, struct entry *e);
220 static int entry_cmp(const void *p1, const void *p2);
221 static int entry_ignore(const void *p1, int last, const void *o);
222 static int entry_paranoia_checks(char *const name[2], const void *p1, const void *p2);
223 static void entry_print(char side, const void *p);
224 static void entry_start_file(const void *p);
225 static const char *map_enum(const enum_map_t *map, uint32_t value);
226
227 static int is_directory(const char *name);
228 #ifdef HAVE_FTS_H
229 static int list_directory(const char *name, struct archive *a);
230 #endif
231 static int list_zip(const char *name, struct archive *a);
232 static int test_file(zip_t *za, zip_uint64_t idx, const char *zipname, const char *filename, zip_uint64_t size, zip_uint32_t crc);
233
234 int ignore_case, test_files, paranoid, verbose, have_directory, check_consistency, summary;
235 int plus_count = 0, minus_count = 0;
236
237 diff_output_t output;
238
239
240 int
241 main(int argc, char *const argv[]) {
242     int c;
243
244     progname = argv[0];
245
246     ignore_case = 0;
247     test_files = 0;
248     check_consistency = 0;
249     paranoid = 0;
250     have_directory = 0;
251     verbose = 1;
252     summary = 0;
253
254     while ((c = getopt(argc, argv, OPTIONS)) != -1) {
255         switch (c) {
256             case 'C':
257                 check_consistency = 1;
258                 break;
259             case 'i':
260                 ignore_case = 1;
261                 break;
262             case 'p':
263                 paranoid = 1;
264                 break;
265             case 'q':
266                 verbose = 0;
267                 break;
268             case 's':
269                 summary = 1;
270                 break;
271             case 't':
272                 test_files = 1;
273                 break;
274             case 'v':
275                 verbose = 1;
276                 break;
277                 
278             case 'h':
279                 fputs(help_head, stdout);
280                 printf(USAGE, progname);
281                 fputs(help, stdout);
282                 exit(0);
283             case 'V':
284                 fputs(version_string, stdout);
285                 exit(0);
286                 
287             default:
288                 fprintf(stderr, USAGE, progname);
289                 exit(2);
290         }
291     }
292
293     if (argc != optind + 2) {
294         fprintf(stderr, USAGE, progname);
295         exit(2);
296     }
297
298     exit((compare_zip(argv + optind) == 0) ? 0 : 1);
299 }
300
301
302 static int
303 compare_zip(char *const zn[]) {
304     struct archive a[2];
305     struct entry *e[2];
306     zip_uint64_t n[2];
307     int i;
308     int res;
309
310     for (i = 0; i < 2; i++) {
311         a[i].name = zn[i];
312         a[i].entry = NULL;
313         a[i].nentry = 0;
314         a[i].za = NULL;
315         a[i].comment = NULL;
316         a[i].comment_length = 0;
317
318         if (is_directory(zn[i])) {
319 #ifndef HAVE_FTS_H
320             fprintf(stderr, "%s: reading directories not supported\n", progname);
321             exit(2);
322 #else
323             if (list_directory(zn[i], a + i) < 0)
324                 exit(2);
325             have_directory = 1;
326             paranoid = 0; /* paranoid checks make no sense for directories, since they compare zip metadata */
327 #endif
328         }
329         else {
330             if (list_zip(zn[i], a + i) < 0)
331                 exit(2);
332         }
333         if (a[i].nentry > 0)
334             qsort(a[i].entry, a[i].nentry, sizeof(a[i].entry[0]), entry_cmp);
335     }
336
337     diff_output_init(&output, verbose, zn);
338
339     e[0] = a[0].entry;
340     e[1] = a[1].entry;
341     n[0] = a[0].nentry;
342     n[1] = a[1].nentry;
343     res = compare_list(zn, (const void **)e, n, sizeof(e[i][0]), entry_cmp, have_directory ? entry_ignore : NULL, paranoid ? entry_paranoia_checks : NULL, entry_print, entry_start_file);
344
345     if (paranoid) {
346         if (comment_compare(a[0].comment, a[0].comment_length, a[1].comment, a[1].comment_length) != 0) {
347             if (a[0].comment_length > 0) {
348                 diff_output_data(&output, '-', (const zip_uint8_t *)a[0].comment, a[0].comment_length, "archive comment");
349                 minus_count++;
350             }
351             if (a[1].comment_length > 0) {
352                 diff_output_data(&output, '+', (const zip_uint8_t *)a[1].comment, a[1].comment_length, "archive comment");
353                 plus_count++;
354             }
355             res = 1;
356         }
357     }
358
359     for (i = 0; i < 2; i++) {
360         zip_uint64_t j;
361
362         if (a[i].za) {
363             zip_close(a[i].za);
364         }
365         for (j = 0; j < a[i].nentry; j++) {
366             free(a[i].entry[j].name);
367         }
368         free(a[i].entry);
369     }
370
371     if (summary) {
372         printf("%d files removed, %d files added\n", minus_count, plus_count);
373     }
374
375     switch (res) {
376     case 0:
377         exit(0);
378
379     case 1:
380         exit(1);
381
382     default:
383         exit(2);
384     }
385 }
386
387 #ifdef HAVE_FTS_H
388 static zip_int64_t
389 compute_crc(const char *fname) {
390     FILE *f;
391     uLong crc = crc32(0L, Z_NULL, 0);
392     size_t n;
393     Bytef buffer[8192];
394
395
396     if ((f = fopen(fname, "rb")) == NULL) {
397         fprintf(stderr, "%s: can't open %s: %s\n", progname, fname, strerror(errno));
398         return -1;
399     }
400
401     while ((n = fread(buffer, 1, sizeof(buffer), f)) > 0) {
402         crc = crc32(crc, buffer, (unsigned int)n);
403     }
404
405     if (ferror(f)) {
406         fprintf(stderr, "%s: read error on %s: %s\n", progname, fname, strerror(errno));
407         fclose(f);
408         return -1;
409     }
410
411     fclose(f);
412
413     return (zip_int64_t)crc;
414 }
415 #endif
416
417
418 static int
419 is_directory(const char *name) {
420     struct stat st;
421
422     if (stat(name, &st) < 0)
423         return 0;
424
425     return S_ISDIR(st.st_mode);
426 }
427
428
429 #ifdef HAVE_FTS_H
430 static int
431 list_directory(const char *name, struct archive *a) {
432     FTS *fts;
433     FTSENT *ent;
434     zip_uint64_t nalloc;
435     size_t prefix_length;
436
437     char *const names[2] = {(char *)name, NULL};
438
439
440     if ((fts = fts_open(names, FTS_NOCHDIR | FTS_LOGICAL, NULL)) == NULL) {
441         fprintf(stderr, "%s: can't open directory '%s': %s\n", progname, name, strerror(errno));
442         return -1;
443     }
444     prefix_length = strlen(name) + 1;
445
446     nalloc = 0;
447
448     while ((ent = fts_read(fts))) {
449         zip_int64_t crc;
450
451         switch (ent->fts_info) {
452             case FTS_DOT:
453             case FTS_DP:
454             case FTS_DEFAULT:
455             case FTS_SL:
456             case FTS_NSOK:
457                 break;
458
459             case FTS_DC:
460             case FTS_DNR:
461             case FTS_ERR:
462             case FTS_NS:
463             case FTS_SLNONE:
464                 /* TODO: error */
465                 fts_close(fts);
466                 return -1;
467
468             case FTS_D:
469             case FTS_F:
470                 if (a->nentry >= nalloc) {
471                     nalloc += 16;
472                     if (nalloc > SIZE_MAX / sizeof(a->entry[0])) {
473                         fprintf(stderr, "%s: malloc failure\n", progname);
474                         exit(1);
475                     }
476                     a->entry = realloc(a->entry, sizeof(a->entry[0]) * nalloc);
477                     if (a->entry == NULL) {
478                         fprintf(stderr, "%s: malloc failure\n", progname);
479                         exit(1);
480                     }
481                 }
482                 
483                 if (ent->fts_info == FTS_D) {
484                     char *dir_name;
485                     
486                     if (ent->fts_path[prefix_length - 1] == '\0') {
487                         break;
488                     }
489                     
490                     dir_name = malloc(strlen(ent->fts_path + prefix_length) + 2);
491                     if (dir_name == NULL) {
492                         fprintf(stderr, "%s: malloc failure\n", progname);
493                         exit(1);
494                     }
495                     sprintf(dir_name, "%s/", ent->fts_path + prefix_length);
496                     a->entry[a->nentry].name = dir_name;
497                     a->entry[a->nentry].size = 0;
498                     a->entry[a->nentry].crc = 0;
499                 }
500                 else {
501                     a->entry[a->nentry].name = strdup(ent->fts_path + prefix_length);
502                     a->entry[a->nentry].size = (zip_uint64_t)ent->fts_statp->st_size;
503                     if ((crc = compute_crc(ent->fts_accpath)) < 0) {
504                         fts_close(fts);
505                         return -1;
506                     }
507                 
508                     a->entry[a->nentry].crc = (zip_uint32_t)crc;
509                 }
510                 a->nentry++;
511                 break;
512         }
513     }
514
515     if (fts_close(fts)) {
516         fprintf(stderr, "%s: error closing directory '%s': %s\n", progname, a->name, strerror(errno));
517         return -1;
518     }
519
520     return 0;
521 }
522 #endif
523
524
525 static int
526 list_zip(const char *name, struct archive *a) {
527     zip_t *za;
528     int err;
529     struct zip_stat st;
530     unsigned int i;
531
532     if ((za = zip_open(name, check_consistency ? ZIP_CHECKCONS : 0, &err)) == NULL) {
533         zip_error_t error;
534         zip_error_init_with_code(&error, err);
535         fprintf(stderr, "%s: cannot open zip archive '%s': %s\n", progname, name, zip_error_strerror(&error));
536         zip_error_fini(&error);
537         return -1;
538     }
539
540     a->za = za;
541     a->nentry = (zip_uint64_t)zip_get_num_entries(za, 0);
542
543     if (a->nentry == 0)
544         a->entry = NULL;
545     else {
546         if ((a->nentry > SIZE_MAX / sizeof(a->entry[0])) || (a->entry = (struct entry *)malloc(sizeof(a->entry[0]) * a->nentry)) == NULL) {
547             fprintf(stderr, "%s: malloc failure\n", progname);
548             exit(1);
549         }
550
551         for (i = 0; i < a->nentry; i++) {
552             zip_stat_index(za, i, 0, &st);
553             a->entry[i].name = strdup(st.name);
554             a->entry[i].size = st.size;
555             a->entry[i].crc = st.crc;
556             if (test_files)
557                 test_file(za, i, name, st.name, st.size, st.crc);
558             if (paranoid) {
559                 a->entry[i].comp_method = st.comp_method;
560                 ef_read(za, i, a->entry + i);
561                 a->entry[i].comment = zip_file_get_comment(za, i, &a->entry[i].comment_length, 0);
562             }
563             else {
564                 a->entry[i].comp_method = 0;
565                 a->entry[i].n_extra_fields = 0;
566             }
567         }
568
569         if (paranoid) {
570             int length;
571             a->comment = zip_get_archive_comment(za, &length, 0);
572             a->comment_length = (size_t)length;
573         }
574         else {
575             a->comment = NULL;
576             a->comment_length = 0;
577         }
578     }
579
580     return 0;
581 }
582
583
584 static int
585 comment_compare(const char *c1, size_t l1, const char *c2, size_t l2) {
586     if (l1 != l2)
587         return 1;
588
589     if (l1 == 0)
590         return 0;
591
592     if (c1 == NULL || c2 == NULL)
593         return c1 == c2;
594
595     return memcmp(c1, c2, (size_t)l2);
596 }
597
598
599 static int compare_list(char *const name[2], const void *list[2], const zip_uint64_t list_length[2], int element_size, int (*cmp)(const void *a, const void *b), int (*ignore)(const void *list, int last, const void *other), int (*check)(char *const name[2], const void *a, const void *b), void (*print)(char side, const void *element), void (*start_file)(const void *element)) {
600     unsigned int i[2];
601     int j;
602     int diff;
603
604 #define INC(k) (i[k]++, list[k] = ((const char *)list[k]) + element_size)
605 #define PRINT(k)                                          \
606     do {                                                  \
607         if (ignore && ignore(list[k], i[k] >= list_length[k] - 1, i[1-k] < list_length[1-k] ? list[1-k] : NULL)) {   \
608             break;                                        \
609         }                                                 \
610         print((k) ? '+' : '-', list[k]);                  \
611         (k) ? plus_count++ : minus_count++;               \
612         diff = 1;                                         \
613     } while (0)
614
615     i[0] = i[1] = 0;
616     diff = 0;
617     while (i[0] < list_length[0] && i[1] < list_length[1]) {
618         int c = cmp(list[0], list[1]);
619
620         if (c == 0) {
621             if (check) {
622                 if (start_file) {
623                     start_file(list[0]);
624                 }
625                 diff |= check(name, list[0], list[1]);
626                 if (start_file) {
627                     diff_output_end_file(&output);
628                 }
629             }
630             INC(0);
631             INC(1);
632         }
633         else if (c < 0) {
634             PRINT(0);
635             INC(0);
636         }
637         else {
638             PRINT(1);
639             INC(1);
640         }
641     }
642
643     for (j = 0; j < 2; j++) {
644         while (i[j] < list_length[j]) {
645             PRINT(j);
646             INC(j);
647         }
648     }
649
650     return diff;
651 }
652
653
654 static int
655 ef_read(zip_t *za, zip_uint64_t idx, struct entry *e) {
656     zip_int16_t n_local, n_central;
657     zip_uint16_t i;
658
659     if ((n_local = zip_file_extra_fields_count(za, idx, ZIP_FL_LOCAL)) < 0 || (n_central = zip_file_extra_fields_count(za, idx, ZIP_FL_CENTRAL)) < 0) {
660         return -1;
661     }
662
663     e->n_extra_fields = (zip_uint16_t)(n_local + n_central);
664
665     if ((e->extra_fields = (struct ef *)malloc(sizeof(e->extra_fields[0]) * e->n_extra_fields)) == NULL)
666         return -1;
667
668     for (i = 0; i < n_local; i++) {
669         e->extra_fields[i].name = e->name;
670         e->extra_fields[i].data = zip_file_extra_field_get(za, idx, i, &e->extra_fields[i].id, &e->extra_fields[i].size, ZIP_FL_LOCAL);
671         if (e->extra_fields[i].data == NULL)
672             return -1;
673         e->extra_fields[i].flags = ZIP_FL_LOCAL;
674     }
675     for (; i < e->n_extra_fields; i++) {
676         e->extra_fields[i].name = e->name;
677         e->extra_fields[i].data = zip_file_extra_field_get(za, idx, (zip_uint16_t)(i - n_local), &e->extra_fields[i].id, &e->extra_fields[i].size, ZIP_FL_CENTRAL);
678         if (e->extra_fields[i].data == NULL)
679             return -1;
680         e->extra_fields[i].flags = ZIP_FL_CENTRAL;
681     }
682
683     qsort(e->extra_fields, e->n_extra_fields, sizeof(e->extra_fields[0]), ef_order);
684
685     return 0;
686 }
687
688
689 static int
690 ef_compare(char *const name[2], const struct entry *e1, const struct entry *e2) {
691     struct ef *ef[2];
692     zip_uint64_t n[2];
693
694     ef[0] = e1->extra_fields;
695     ef[1] = e2->extra_fields;
696     n[0] = e1->n_extra_fields;
697     n[1] = e2->n_extra_fields;
698
699     return compare_list(name, (const void **)ef, n, sizeof(struct ef), ef_order, NULL, NULL, ef_print, NULL);
700 }
701
702
703 static int
704 ef_order(const void *ap, const void *bp) {
705     const struct ef *a, *b;
706
707     a = (struct ef *)ap;
708     b = (struct ef *)bp;
709
710     if (a->flags != b->flags)
711         return a->flags - b->flags;
712     if (a->id != b->id)
713         return a->id - b->id;
714     if (a->size != b->size)
715         return a->size - b->size;
716     return memcmp(a->data, b->data, a->size);
717 }
718
719
720 static void
721 ef_print(char side, const void *p) {
722     const struct ef *ef = (struct ef *)p;
723
724     diff_output_data(&output, side, ef->data, ef->size, "  %s extra field %s", ef->flags == ZIP_FL_LOCAL ? "local" : "central", map_enum(extra_fields, ef->id));
725 }
726
727
728 static int
729 entry_cmp(const void *p1, const void *p2) {
730     const struct entry *e1, *e2;
731     int c;
732
733     e1 = (struct entry *)p1;
734     e2 = (struct entry *)p2;
735
736     if ((c = (ignore_case ? strcasecmp : strcmp)(e1->name, e2->name)) != 0)
737         return c;
738     if (e1->size != e2->size) {
739         if (e1->size > e2->size)
740             return 1;
741         else
742             return -1;
743     }
744     if (e1->crc != e2->crc)
745         return (int)e1->crc - (int)e2->crc;
746
747     return 0;
748 }
749
750
751 static int
752 entry_ignore(const void *p, int last, const void *o) {
753     const struct entry *e = (const struct entry *)p;
754     const struct entry *other = (const struct entry *)o;
755     
756     size_t length = strlen(e[0].name);
757     
758     if (length == 0 || e[0].name[length - 1] != '/') {
759         /* not a directory */
760         return 0;
761     }
762     
763     if (other != NULL && strlen(other->name) > length && strncmp(other->name, e[0].name, length) == 0) {
764         /* not empty in other archive */
765         return 1;
766     }
767     
768     if (last || (strlen(e[1].name) < length || strncmp(e[0].name, e[1].name, length) != 0)) {
769         /* empty in this archive */
770         return 0;
771     }
772     
773     /* not empty in this archive */
774     return 1;
775 }
776
777
778 static int
779 entry_paranoia_checks(char *const name[2], const void *p1, const void *p2) {
780     const struct entry *e1, *e2;
781     int ret;
782
783     e1 = (struct entry *)p1;
784     e2 = (struct entry *)p2;
785
786     ret = 0;
787
788     if (e1->comp_method != e2->comp_method) {
789         diff_output(&output, '-', "  compression method %s", map_enum(comp_methods, e1->comp_method));
790         diff_output(&output, '+', "  compression method %s", map_enum(comp_methods, e2->comp_method));
791         ret = 1;
792     }
793
794     if (ef_compare(name, e1, e2) != 0) {
795         ret = 1;
796     }
797
798     if (comment_compare(e1->comment, e1->comment_length, e2->comment, e2->comment_length) != 0) {
799         diff_output_data(&output, '-', (const zip_uint8_t *)e1->comment, e1->comment_length, "  comment");
800         diff_output_data(&output, '+', (const zip_uint8_t *)e2->comment, e2->comment_length, "  comment");
801         ret = 1;
802     }
803
804     return ret;
805 }
806
807
808 static void entry_print(char side, const void *p) {
809     const struct entry *e = (struct entry *)p;
810
811     diff_output_file(&output, side, e->name, e->size, e->crc);
812 }
813
814
815 static void entry_start_file(const void *p) {
816     const struct entry *e = (struct entry *)p;
817     
818     diff_output_start_file(&output, e->name, e->size, e->crc);
819 }
820
821
822 static int
823 test_file(zip_t *za, zip_uint64_t idx, const char *zipname, const char *filename, zip_uint64_t size, zip_uint32_t crc) {
824     zip_file_t *zf;
825     char buf[8192];
826     zip_uint64_t nsize;
827     zip_int64_t n;
828     zip_uint32_t ncrc;
829
830     if ((zf = zip_fopen_index(za, idx, 0)) == NULL) {
831         fprintf(stderr, "%s: %s: cannot open file %s (index %" PRIu64 "): %s\n", progname, zipname, filename, idx, zip_strerror(za));
832         return -1;
833     }
834
835     ncrc = (zip_uint32_t)crc32(0, NULL, 0);
836     nsize = 0;
837
838     while ((n = zip_fread(zf, buf, sizeof(buf))) > 0) {
839         nsize += (zip_uint64_t)n;
840         ncrc = (zip_uint32_t)crc32(ncrc, (const Bytef *)buf, (unsigned int)n);
841     }
842
843     if (n < 0) {
844         fprintf(stderr, "%s: %s: error reading file %s (index %" PRIu64 "): %s\n", progname, zipname, filename, idx, zip_file_strerror(zf));
845         zip_fclose(zf);
846         return -1;
847     }
848
849     zip_fclose(zf);
850
851     if (nsize != size) {
852         fprintf(stderr, "%s: %s: file %s (index %" PRIu64 "): unexpected length %" PRId64 " (should be %" PRId64 ")\n", progname, zipname, filename, idx, nsize, size);
853         return -2;
854     }
855     if (ncrc != crc) {
856         fprintf(stderr, "%s: %s: file %s (index %" PRIu64 "): unexpected length %x (should be %x)\n", progname, zipname, filename, idx, ncrc, crc);
857         return -2;
858     }
859
860     return 0;
861 }
862
863
864 static const char *map_enum(const enum_map_t *map, uint32_t value) {
865     static char unknown[16];
866     size_t i = 0;
867     
868     while (map[i].value < UINT32_MAX) {
869         if (map[i].value == value) {
870             return map[i].name;
871         }
872         i++;
873     }
874     
875     snprintf(unknown, sizeof(unknown), "unknown (%u)", value);
876     unknown[sizeof(unknown) - 1] = '\0';
877     
878     return unknown;
879 }