-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathfixmszip.c
282 lines (252 loc) · 9.07 KB
/
fixmszip.c
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
/* fixmszip.c. Simple program to fix large zip files created on
* Windows so that mac/unix zip utilities play nicely with them.
* This involves changing the "Total Number of disks" field in the Zip64
* End Of Central Directory Locator structure from "0" to "1"
* Use is entirely at user's own risk
* Copyright Keith Young 2021
* For copying information, see the file COPYING distributed with this file
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/mman.h>
#define EOCDR_BASE_SIZE 22 /* End of Central Directory record size */
#define Z64_EOCDL_SIZE 20 /* Zip64 EOCD Locator */
#define Z64_EOCDL_SIG 0x07064b50 /* Zip64 EOCDL signature */
#define SIG_LEN 4 /* Zip64 signature length */
#define ERRMAX 1024 /* Maximum error message size */
static char *progname = "fixmszip";
static int little_endian; /* This system's endianness. 1 == litle */
/* Print usage an exit */
void usage()
{
fprintf(stderr,"Usage: %s [-vn] zipfile [...]\n",progname);
exit(1);
}
/* Return 2 little endian bytes as an unsigned short */
unsigned short get2bytes(const unsigned char* ptr)
{
if (little_endian) {
return((*(ptr+1) <<8) + *ptr);
}
return((*ptr <<8) + *(ptr+1));
}
/* Return 4 little endian bytes as an unsigned int */
unsigned int get4bytes(const unsigned char *ptr)
{
int i;
unsigned int val=0;
if (little_endian) {
for (i = 0; i < 4; i++) {
val += *ptr++ <<(8 * i);
}
} else {
for (i = 0; i < 4; i++) {
val += *ptr++ <<(8* (3-i));
}
}
return val;
}
/* Find and patch a problematic Zip64 EOCDL
We do this as follows.
* mmap enough of the last part of the file to encompass the Zip64 EOCDL
and the EOCDR including the maximum sized comment, and rouded up to the
previous page boundary
* Starting from 18 bytes from the end of the file (where the last byte of
the EOCDR signature would be if there were no comment), look backwards
through the file for the signature. If found, check that the comment
length in the last two bytes of the EOCDR (assuming the signature does
mark the start of th EOCDR), added to the offset of the assumed end of the
EOCDR is equal to the file size. If not, the signature is a fluke, so keep
looking. If the value looks "correct" check that:
- This disk is the start disk (from fields in the EOCDR). We ignore this
file if not.
- That the central directory offset is 0xffffffff, signifying a Zip64 file.
If not, the file doesn't need patching
- That the Zip64 EOCDL signature is where it should be
- That the Zip64 EOCDL "Total number of disks" field is set to 0
...and assuming the "dryrun" option is not set, change number of disks to 1
return values: -1: error
0: file doesn't need updating
1: file updated (or would have been if not dryrun)
*/
int fixup(char *filename, char **err, int dryrun)
{
int fd,i,res=0,pagesize;
unsigned char *fptr,*ptr,*minptr;
off_t fsize,offsize = 0, pageoff;
struct stat sbuf;
unsigned cd_offset,z64sig,numdisks;
unsigned short comment_len,this_disk,start_disk;
const unsigned char sig[] = {0x50,0x4b,0x05,0x06};
static char errbuf[ERRMAX];
*errbuf = '\0';
*err = errbuf;
if (lstat(filename,&sbuf)) {
snprintf(errbuf,ERRMAX,"Failed to stat %s: %s",
filename,strerror(errno));
return(-1);
}
/* If file is smaller than End of Central Directory Record, it can't be
a zip file (TODO: check for smallest viable zip file */
if ((fsize = sbuf.st_size) < EOCDR_BASE_SIZE) {
snprintf(errbuf,ERRMAX,"%s is not a zip file\n",filename);
return(-1);
}
/* We don't need to mmap all of the file, only enough of the last part
to encompass the End of Central Directory Record (EOCDR) including
any comment and the Zip64 End of Central Directory Locator (EOCDL),
plus enough preceding bytes to enable mapping on a page boundary. */
if (fsize > 65577) {
offsize = fsize - 65577;
pagesize = getpagesize();
pageoff = offsize % pagesize;
offsize -= pageoff;
fsize = 65577;
} else {
offsize = pageoff = 0;
}
if ((fd = open(filename,O_RDWR)) < 0) {
snprintf(errbuf,ERRMAX,"Failed to open %s: %s",
filename,strerror(errno));
return(-1);
}
if ((fptr = (unsigned char *) mmap(NULL,fsize,PROT_READ|PROT_WRITE,
MAP_SHARED,fd,offsize)) == MAP_FAILED) {
(void) close(fd);
snprintf(errbuf,ERRMAX,"Failed to mmap %s: %s",
filename,strerror(errno));
return(-1);
}
/* Starting at what would be the last byte of the EOCDR signature were
there no comment, work backwards through the mmaped part of the file
looking for the EOCDR signature */
for (i=3,ptr=fptr+fsize+pageoff-EOCDR_BASE_SIZE+3,
minptr=fptr+pageoff+Z64_EOCDL_SIZE;ptr >= minptr ;--ptr) {
if (*ptr == sig[i]) {
if (i == 0) {
/* If we think we've found the signature, check what would
then be the comment length field and confirm that offset
of the EOCDR, plus length of the EOCDR, plus comment length
are equal to the file size. If not, signature must be a
fluke: continute searching. */
comment_len = get2bytes(ptr+20);
if ((ptr - fptr) - pageoff + EOCDR_BASE_SIZE + comment_len
!= fsize) {
i = SIG_LEN;
continue;
}
/* Here it looks like we've found the EOCDR. If this disk
is not the start disk, we don't do anything */
this_disk = get2bytes(ptr+4);
start_disk = get2bytes(ptr+6);
if (this_disk != start_disk) {
sprintf(errbuf,"Not start disk");
res = -1;
break;
}
/* If the central directory offset is not 0xffffffff, there
should be no need to patch the Zip64 EOCDL */
cd_offset = get4bytes(ptr+16);
if (cd_offset != 0xffffffff) {
sprintf(errbuf,"Offset <4GB");
break;
}
/* Check the Zip64 EOCDL signature is where it should be */
z64sig = get4bytes(ptr-Z64_EOCDL_SIZE);
if (z64sig != Z64_EOCDL_SIG) {
i = SIG_LEN;
continue;
}
/* Check number of disks in Zip64 EOCDL. If 0 and not
a dry run, change it to 1 */
numdisks = get4bytes(ptr-4);
if (numdisks == 0) {
if (!dryrun) {
*(ptr-4) = 1;
}
res = 1;
} else {
sprintf(errbuf,"Number of disks already 1");
}
break;
} else {
--i;
}
} else {
if (i != 3) {
i = 3;
}
}
}
(void) munmap(fptr,fsize);
if (ptr == minptr) {
sprintf(errbuf,"No Zip64 EOCDL found");
}
return(res);
}
int main (int argc, char **argv)
{
unsigned problems = 0;
int c,i,err,res,verbose = 0,nopatch=0;
char *errmsg;
c = 1;
little_endian = *(char *)&c;
while ((c = getopt(argc,argv,"vn")) != -1) {
switch (c) {
case 'v': /* Verbose output */
verbose++;
break;
case 'n': /* dry run */
nopatch++;
break;
default:
usage();
}
}
if (optind == argc) {
usage();
}
for (i = optind; i < argc; i++) {
if (verbose) {
printf("Fixing %s:...",argv[i]);
}
if (access(argv[i],W_OK)) {
err=errno;
if (verbose) {
printf("Failed!\n");
fflush(stdout);
}
fprintf(stderr,"Failed to fix %s: %s\n",argv[i],strerror(err));
fflush(stderr);
problems++;
continue;
}
if ((res = fixup(argv[i],&errmsg,nopatch)) < 0) {
problems++;
}
if (verbose) {
if (res == 1) {
printf("Success!%s\n",nopatch?" (dryrun: no change made)":"");
} else if (res == 0) {
printf("Unnecessary: %s\n",errmsg);
} else {
printf("Failed\n");
if (*errmsg) {
fprintf(stderr,"%s\n",errmsg);
};
}
fflush(stdout);
fflush(stderr);
}
}
if (problems) {
fprintf(stderr,"Errors were encountered during fixup\n");
exit(1);
}
exit(0);
}