/
zip.c
678 lines (543 loc) · 18.3 KB
1
2
3
4
5
6
7
8
/*
* ZIP support routines for PhysicsFS.
*
* Please see the file LICENSE in the source's root directory.
*
* This file written by Ryan C. Gordon.
*/
9
10
11
12
13
14
#if HAVE_CONFIG_H
# include <config.h>
#endif
#if (defined PHYSFS_SUPPORTS_ZIP)
15
16
17
18
19
/*
* !!! FIXME: overall design bugs.
*
* Maybe add a seekToStartOfCurrentFile() in unzip.c if complete seek
* semantics are impossible.
20
21
22
*
* Could be more i/o efficient if we combined unzip.c and this file.
* (and thus lose all the unzGoToNextFile() dummy loops.
23
*/
24
25
26
#include <stdio.h>
#include <stdlib.h>
27
#include <string.h>
28
#include <assert.h>
29
#include "physfs.h"
30
31
#include "unzip.h"
32
33
34
35
#define __PHYSICSFS_INTERNAL__
#include "physfs_internal.h"
36
#define MAXZIPENTRYSIZE 256
37
38
39
typedef struct
{
40
41
42
43
44
45
46
char *name;
unz_file_info info;
char *symlink;
} ZIPentry;
typedef struct
{
47
char *archiveName;
48
49
unz_global_info global;
ZIPentry *entries;
50
51
52
53
} ZIPinfo;
typedef struct
{
54
unzFile handle;
55
56
57
} ZIPfileinfo;
58
59
60
61
/* Number of symlinks to follow before we assume it's a recursive link... */
#define SYMLINK_RECURSE_COUNT 20
62
63
static PHYSFS_sint64 ZIP_read(FileHandle *handle, void *buffer,
PHYSFS_uint32 objSize, PHYSFS_uint32 objCount);
64
static int ZIP_eof(FileHandle *handle);
65
66
67
static PHYSFS_sint64 ZIP_tell(FileHandle *handle);
static int ZIP_seek(FileHandle *handle, PHYSFS_uint64 offset);
static PHYSFS_sint64 ZIP_fileLength(FileHandle *handle);
68
69
static int ZIP_fileClose(FileHandle *handle);
static int ZIP_isArchive(const char *filename, int forWriting);
70
static char *ZIP_realpath(unzFile fh, unz_file_info *info, ZIPentry *entry);
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
static DirHandle *ZIP_openArchive(const char *name, int forWriting);
static LinkedStringList *ZIP_enumerateFiles(DirHandle *h,
const char *dirname,
int omitSymLinks);
static int ZIP_exists(DirHandle *h, const char *name);
static int ZIP_isDirectory(DirHandle *h, const char *name);
static int ZIP_isSymLink(DirHandle *h, const char *name);
static FileHandle *ZIP_openRead(DirHandle *h, const char *filename);
static void ZIP_dirClose(DirHandle *h);
static const FileFunctions __PHYSFS_FileFunctions_ZIP =
{
ZIP_read, /* read() method */
NULL, /* write() method */
ZIP_eof, /* eof() method */
ZIP_tell, /* tell() method */
ZIP_seek, /* seek() method */
ZIP_fileLength, /* fileLength() method */
ZIP_fileClose /* fileClose() method */
};
const DirFunctions __PHYSFS_DirFunctions_ZIP =
{
ZIP_isArchive, /* isArchive() method */
ZIP_openArchive, /* openArchive() method */
ZIP_enumerateFiles, /* enumerateFiles() method */
ZIP_exists, /* exists() method */
ZIP_isDirectory, /* isDirectory() method */
ZIP_isSymLink, /* isSymLink() method */
ZIP_openRead, /* openRead() method */
NULL, /* openWrite() method */
NULL, /* openAppend() method */
NULL, /* remove() method */
NULL, /* mkdir() method */
ZIP_dirClose /* dirClose() method */
};
const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_ZIP =
{
"ZIP",
"PkZip/WinZip/Info-Zip compatible",
114
"Ryan C. Gordon <icculus@clutteredmind.org>",
115
116
117
118
119
"http://www.icculus.org/physfs/",
};
120
121
static PHYSFS_sint64 ZIP_read(FileHandle *handle, void *buffer,
PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
122
{
123
unzFile fh = ((ZIPfileinfo *) (handle->opaque))->handle;
124
125
int bytes = (int) (objSize * objCount); /* !!! FIXME: overflow? */
PHYSFS_sint32 rc = unzReadCurrentFile(fh, buffer, bytes);
126
127
128
129
130
131
132
133
134
if (rc < bytes)
__PHYSFS_setError(ERR_PAST_EOF);
else if (rc == UNZ_ERRNO)
__PHYSFS_setError(ERR_IO_ERROR);
else if (rc < 0)
__PHYSFS_setError(ERR_COMPRESSION);
return(rc / objSize);
135
136
137
138
139
} /* ZIP_read */
static int ZIP_eof(FileHandle *handle)
{
140
return(unzeof(((ZIPfileinfo *) (handle->opaque))->handle));
141
142
143
} /* ZIP_eof */
144
static PHYSFS_sint64 ZIP_tell(FileHandle *handle)
145
{
146
return(unztell(((ZIPfileinfo *) (handle->opaque))->handle));
147
148
149
} /* ZIP_tell */
150
static int ZIP_seek(FileHandle *handle, PHYSFS_uint64 offset)
151
{
152
/* !!! FIXME : this blows. */
153
unzFile fh = ((ZIPfileinfo *) (handle->opaque))->handle;
154
char *buf = NULL;
155
PHYSFS_uint32 bufsize = 4096 * 2;
156
157
BAIL_IF_MACRO(unztell(fh) == offset, NULL, 1);
158
BAIL_IF_MACRO(ZIP_fileLength(handle) <= (PHYSFS_sint64) offset, ERR_PAST_EOF, 0);
159
160
161
162
163
164
165
166
167
168
169
170
171
172
/* reset to the start of the zipfile. */
unzCloseCurrentFile(fh);
BAIL_IF_MACRO(unzOpenCurrentFile(fh) != UNZ_OK, ERR_IO_ERROR, 0);
while ((buf == NULL) && (bufsize >= 512))
{
bufsize >>= 1; /* divides by two. */
buf = (char *) malloc(bufsize);
} /* while */
BAIL_IF_MACRO(buf == NULL, ERR_OUT_OF_MEMORY, 0);
while (offset > 0)
{
173
PHYSFS_uint32 chunk = (offset > bufsize) ? bufsize : (PHYSFS_uint32)offset;
174
PHYSFS_sint32 rc = unzReadCurrentFile(fh, buf, chunk);
175
176
177
178
179
180
181
182
BAIL_IF_MACRO(rc == 0, ERR_IO_ERROR, 0); /* shouldn't happen. */
BAIL_IF_MACRO(rc == UNZ_ERRNO, ERR_IO_ERROR, 0);
BAIL_IF_MACRO(rc < 0, ERR_COMPRESSION, 0);
offset -= rc;
} /* while */
free(buf);
return(offset == 0);
183
184
185
} /* ZIP_seek */
186
static PHYSFS_sint64 ZIP_fileLength(FileHandle *handle)
187
{
188
189
190
191
192
ZIPfileinfo *finfo = (ZIPfileinfo *) (handle->opaque);
unz_file_info info;
unzGetCurrentFileInfo(finfo->handle, &info, NULL, 0, NULL, 0, NULL, 0);
return(info.uncompressed_size);
193
194
195
} /* ZIP_fileLength */
196
197
static int ZIP_fileClose(FileHandle *handle)
{
198
199
200
201
202
ZIPfileinfo *finfo = (ZIPfileinfo *) (handle->opaque);
unzClose(finfo->handle);
free(finfo);
free(handle);
return(1);
203
204
205
206
207
} /* ZIP_fileClose */
static int ZIP_isArchive(const char *filename, int forWriting)
{
208
int retval = 0;
209
unzFile unz = unzOpen(filename);
210
211
212
213
214
215
216
217
218
219
unz_global_info global;
if (unz != NULL)
{
if (unzGetGlobalInfo(unz, &global) == UNZ_OK)
retval = 1;
unzClose(unz);
} /* if */
return(retval);
220
221
222
} /* ZIP_isArchive */
223
static void freeEntries(ZIPinfo *info, int count, const char *errmsg)
224
{
225
int i;
226
227
228
229
230
231
232
for (i = 0; i < count; i++)
{
free(info->entries[i].name);
if (info->entries[i].symlink != NULL)
free(info->entries[i].symlink);
} /* for */
233
234
free(info->entries);
235
236
237
238
if (errmsg != NULL)
__PHYSFS_setError(errmsg);
} /* freeEntries */
239
240
241
242
243
244
245
246
247
/*
* !!! FIXME: Really implement this.
* !!! FIXME: symlinks in zipfiles can be relative paths, including
* !!! FIXME: "." and ".." entries. These need to be parsed out.
* !!! FIXME: For now, though, we're just copying the relative path. Oh well.
*/
static char *expand_symlink_path(const char *path, ZIPentry *entry)
248
{
249
char *retval = (char *) malloc(strlen(path) + 1);
250
BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL);
251
strcpy(retval, path);
252
return(retval);
253
} /* expand_symlink_path */
254
255
256
static char *ZIP_realpath(unzFile fh, unz_file_info *info, ZIPentry *entry)
257
{
258
259
260
261
262
263
264
265
266
267
268
269
270
char path[MAXZIPENTRYSIZE];
int size = info->uncompressed_size;
int rc;
BAIL_IF_MACRO(size >= sizeof (path), ERR_IO_ERROR, NULL);
BAIL_IF_MACRO(unzOpenCurrentFile(fh) != UNZ_OK, ERR_IO_ERROR, NULL);
rc = unzReadCurrentFile(fh, path, size);
unzCloseCurrentFile(fh);
BAIL_IF_MACRO(rc != size, ERR_IO_ERROR, NULL);
path[size] = '\0'; /* null terminate it. */
return(expand_symlink_path(path, entry)); /* retval is malloc()'d. */
} /* ZIP_realpath */
271
272
273
274
275
static int version_does_symlinks(uLong version)
{
int retval = 0;
276
PHYSFS_uint8 hosttype = (PHYSFS_uint8) ((version >> 8) & 0xFF);
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
/*
* These are the platforms that can build an archive with symlinks,
* according to the Info-ZIP project.
*/
switch (hosttype)
{
case 3: /* Unix */
case 16: /* BeOS */
case 5: /* Atari */
retval = 1;
break;
} /* switch */
return(retval);
} /* version_does_symlinks */
static int entry_is_symlink(unz_file_info *info)
{
return (
(version_does_symlinks(info->version)) &&
(info->uncompressed_size > 0) &&
(info->external_fa & 0x0120000) /* symlink flag. */
);
} /* entry_is_symlink */
305
static int loadZipEntries(ZIPinfo *info, unzFile unz)
306
{
307
308
309
310
311
int i, max;
BAIL_IF_MACRO(unzGetGlobalInfo(unz, &(info->global)) != UNZ_OK,
ERR_IO_ERROR, 0);
BAIL_IF_MACRO(unzGoToFirstFile(unz) != UNZ_OK, ERR_IO_ERROR, 0);
312
313
314
315
316
317
max = info->global.number_entry;
info->entries = (ZIPentry *) malloc(sizeof (ZIPentry) * max);
BAIL_IF_MACRO(info->entries == NULL, ERR_OUT_OF_MEMORY, 0);
for (i = 0; i < max; i++)
318
{
319
320
unz_file_info *d = &((info->entries[i]).info);
if (unzGetCurrentFileInfo(unz, d, NULL, 0, NULL, 0, NULL, 0) != UNZ_OK)
321
{
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
freeEntries(info, i, ERR_IO_ERROR);
return(0);
} /* if */
(info->entries[i]).name = (char *) malloc(d->size_filename + 1);
if ((info->entries[i]).name == NULL)
{
freeEntries(info, i, ERR_OUT_OF_MEMORY);
return(0);
} /* if */
info->entries[i].symlink = NULL;
if (unzGetCurrentFileInfo(unz, NULL, (info->entries[i]).name,
d->size_filename + 1, NULL, 0,
NULL, 0) != UNZ_OK)
{
freeEntries(info, i + 1, ERR_IO_ERROR);
return(0);
} /* if */
if (entry_is_symlink(d))
{
345
info->entries[i].symlink = ZIP_realpath(unz, d, &info->entries[i]);
346
347
348
349
350
if (info->entries[i].symlink == NULL)
{
freeEntries(info, i + 1, NULL);
return(0);
} /* if */
351
} /* if */
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
if ((unzGoToNextFile(unz) != UNZ_OK) && (i + 1 < max))
{
freeEntries(info, i + 1, ERR_IO_ERROR);
return(0);
} /* if */
} /* for */
return(1);
} /* loadZipEntries */
static DirHandle *ZIP_openArchive(const char *name, int forWriting)
{
unzFile unz = NULL;
DirHandle *retval = NULL;
BAIL_IF_MACRO(forWriting, ERR_ARC_IS_READ_ONLY, NULL);
retval = malloc(sizeof (DirHandle));
BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL);
unz = unzOpen(name);
if (unz == NULL)
{
free(retval);
BAIL_IF_MACRO(1, ERR_UNSUPPORTED_ARCHIVE, NULL);
} /* if */
retval->opaque = malloc(sizeof (ZIPinfo));
if (retval->opaque == NULL)
{
free(retval);
unzClose(unz);
BAIL_IF_MACRO(1, ERR_OUT_OF_MEMORY, NULL);
} /* if */
((ZIPinfo *) (retval->opaque))->archiveName = malloc(strlen(name) + 1);
if ( (((ZIPinfo *) (retval->opaque))->archiveName == NULL) ||
(!loadZipEntries( (ZIPinfo *) (retval->opaque), unz)) )
{
if (((ZIPinfo *) (retval->opaque))->archiveName != NULL)
free(((ZIPinfo *) (retval->opaque))->archiveName);
free(retval->opaque);
free(retval);
unzClose(unz);
BAIL_IF_MACRO(1, ERR_OUT_OF_MEMORY, NULL);
399
400
} /* if */
401
402
403
404
unzClose(unz);
strcpy(((ZIPinfo *) (retval->opaque))->archiveName, name);
retval->funcs = &__PHYSFS_DirFunctions_ZIP;
405
return(retval);
406
} /* ZIP_openArchive */
407
408
409
/* !!! This is seriously ugly. */
410
411
412
static LinkedStringList *ZIP_enumerateFiles(DirHandle *h,
const char *dirname,
int omitSymLinks)
413
{
414
ZIPinfo *zi = (ZIPinfo *) (h->opaque);
415
unsigned int i;
416
int dlen;
417
418
419
420
LinkedStringList *retval = NULL;
LinkedStringList *l = NULL;
LinkedStringList *prev = NULL;
char *d;
421
422
ZIPentry *entry;
char buf[MAXZIPENTRYSIZE];
423
424
425
426
dlen = strlen(dirname);
d = malloc(dlen + 1);
BAIL_IF_MACRO(d == NULL, ERR_OUT_OF_MEMORY, NULL);
427
strcpy(d, dirname);
428
429
430
431
432
if ((dlen > 0) && (d[dlen - 1] == '/')) /* no trailing slash. */
{
dlen--;
d[dlen] = '\0';
} /* if */
433
434
for (i = 0, entry = zi->entries; i < zi->global.number_entry; i++, entry++)
435
436
{
char *ptr;
437
438
439
char *add_file;
int this_dlen;
440
if ((omitSymLinks) && (entry->symlink != NULL))
441
442
continue;
443
444
445
446
447
448
this_dlen = strlen(entry->name);
if (this_dlen + 1 > MAXZIPENTRYSIZE)
continue; /* ugh. */
strcpy(buf, entry->name);
449
if ((this_dlen > 0) && (buf[this_dlen - 1] == '/')) /* no trailing slash. */
450
{
451
452
453
454
455
456
457
458
459
this_dlen--;
buf[this_dlen] = '\0';
} /* if */
if (this_dlen <= dlen) /* not in this dir. */
continue;
if (*d == '\0')
add_file = buf;
460
461
else
{
462
463
464
465
if (buf[dlen] != '/') /* can't be in same directory? */
continue;
buf[dlen] = '\0';
466
if (__PHYSFS_platformStricmp(d, buf) != 0) /* not same directory? */
467
468
469
continue;
add_file = buf + dlen + 1;
470
471
} /* else */
472
473
474
475
476
477
478
479
/* handle subdirectories... */
ptr = strchr(add_file, '/');
if (ptr != NULL)
{
LinkedStringList *j;
*ptr = '\0';
for (j = retval; j != NULL; j = j->next)
{
480
if (__PHYSFS_platformStricmp(j->str, ptr) == 0)
481
482
483
484
485
486
487
break;
} /* for */
if (j != NULL)
continue;
} /* if */
488
l = (LinkedStringList *) malloc(sizeof (LinkedStringList));
489
if (l == NULL)
490
491
break;
492
l->str = (char *) malloc(strlen(add_file) + 1);
493
494
495
496
497
498
if (l->str == NULL)
{
free(l);
break;
} /* if */
499
strcpy(l->str, add_file);
500
501
502
503
504
505
506
507
508
509
510
511
if (retval == NULL)
retval = l;
else
prev->next = l;
prev = l;
l->next = NULL;
} /* for */
free(d);
return(retval);
512
513
514
} /* ZIP_enumerateFiles */
515
516
517
518
519
520
521
/* !!! This is seriously ugly. */
static int ZIP_exists_symcheck(DirHandle *h, const char *name, int follow)
{
char buf[MAXZIPENTRYSIZE];
ZIPinfo *zi = (ZIPinfo *) (h->opaque);
int dlen;
char *d;
522
unsigned int i;
523
ZIPentry *entry;
524
525
526
dlen = strlen(name);
d = malloc(dlen + 1);
527
BAIL_IF_MACRO(d == NULL, ERR_OUT_OF_MEMORY, -1);
528
529
530
531
532
533
534
strcpy(d, name);
if ((dlen > 0) && (d[dlen - 1] == '/')) /* no trailing slash. */
{
dlen--;
d[dlen] = '\0';
} /* if */
535
for (i = 0, entry = zi->entries; i < zi->global.number_entry; i++, entry++)
536
{
537
538
539
540
541
int this_dlen = strlen(entry->name);
if (this_dlen + 1 > MAXZIPENTRYSIZE)
continue; /* ugh. */
strcpy(buf, entry->name);
542
543
544
545
546
547
548
549
550
if ((this_dlen > 0) && (buf[this_dlen - 1] == '/')) /* no trailing slash. */
{
this_dlen--;
buf[this_dlen] = '\0';
} /* if */
if ( ((buf[dlen] == '/') || (buf[dlen] == '\0')) &&
(strncmp(d, buf, dlen) == 0) )
{
551
int retval = i;
552
553
554
free(d);
if (follow) /* follow symlinks? */
{
555
556
if (entry->symlink != NULL)
retval = ZIP_exists_symcheck(h, entry->symlink, follow-1);
557
558
559
560
561
562
} /* if */
return(retval);
} /* if */
} /* for */
free(d);
563
return(-1);
564
565
566
} /* ZIP_exists_symcheck */
567
static int ZIP_exists(DirHandle *h, const char *name)
568
{
569
570
int retval = ZIP_exists_symcheck(h, name, SYMLINK_RECURSE_COUNT);
int is_sym;
571
572
573
if (retval == -1)
return(0);
574
575
576
577
/* if it's a symlink, then we ran into a possible symlink loop. */
is_sym = ( ((ZIPinfo *)(h->opaque))->entries[retval].symlink != NULL );
BAIL_IF_MACRO(is_sym, ERR_TOO_MANY_SYMLINKS, 0);
578
579
return(1);
580
581
582
} /* ZIP_exists */
583
static int ZIP_isDirectory(DirHandle *h, const char *name)
584
{
585
586
587
int dlen;
int is_sym;
int retval = ZIP_exists_symcheck(h, name, SYMLINK_RECURSE_COUNT);
588
589
590
591
592
593
594
if (retval == -1)
return(0);
/* if it's a symlink, then we ran into a possible symlink loop. */
is_sym = ( ((ZIPinfo *)(h->opaque))->entries[retval].symlink != NULL );
BAIL_IF_MACRO(is_sym, ERR_TOO_MANY_SYMLINKS, 0);
595
596
597
598
dlen = strlen(name);
/* !!! yikes. Better way to check? */
retval = (((ZIPinfo *)(h->opaque))->entries[retval].name[dlen] == '/');
599
return(retval);
600
601
602
} /* ZIP_isDirectory */
603
static int ZIP_isSymLink(DirHandle *h, const char *name)
604
{
605
int retval = ZIP_exists_symcheck(h, name, 0);
606
607
if (retval == -1)
return(0);
608
609
retval = ( ((ZIPinfo *)(h->opaque))->entries[retval].symlink != NULL );
610
return(retval);
611
612
613
} /* ZIP_isSymLink */
614
static FileHandle *ZIP_openRead(DirHandle *h, const char *filename)
615
{
616
FileHandle *retval = NULL;
617
ZIPinfo *zi = ((ZIPinfo *) (h->opaque));
618
ZIPfileinfo *finfo = NULL;
619
int pos = ZIP_exists_symcheck(h, filename, SYMLINK_RECURSE_COUNT);
620
unzFile f;
621
622
BAIL_IF_MACRO(pos == -1, ERR_NO_SUCH_FILE, NULL);
623
624
f = unzOpen(zi->archiveName);
625
626
BAIL_IF_MACRO(f == NULL, ERR_IO_ERROR, NULL);
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
if (unzGoToFirstFile(f) != UNZ_OK)
{
unzClose(f);
BAIL_IF_MACRO(1, ERR_IO_ERROR, NULL);
} /* if */
for (; pos > 0; pos--)
{
if (unzGoToNextFile(f) != UNZ_OK)
{
unzClose(f);
BAIL_IF_MACRO(1, ERR_IO_ERROR, NULL);
} /* if */
} /* for */
if ( (unzOpenCurrentFile(f) != UNZ_OK) ||
643
( (finfo = (ZIPfileinfo *) malloc(sizeof (ZIPfileinfo))) == NULL ) )
644
645
646
647
648
{
unzClose(f);
BAIL_IF_MACRO(1, ERR_IO_ERROR, NULL);
} /* if */
649
650
651
652
653
654
655
656
657
if ( (!(retval = (FileHandle *) malloc(sizeof (FileHandle)))) ||
(!(retval->opaque = (ZIPfileinfo *) malloc(sizeof (ZIPfileinfo)))) )
{
if (retval)
free(retval);
unzClose(f);
BAIL_IF_MACRO(1, ERR_OUT_OF_MEMORY, NULL);
} /* if */
658
finfo->handle = f;
659
retval->opaque = (void *) finfo;
660
retval->funcs = &__PHYSFS_FileFunctions_ZIP;
661
662
retval->dirHandle = h;
return(retval);
663
664
665
} /* ZIP_openRead */
666
static void ZIP_dirClose(DirHandle *h)
667
{
668
669
670
671
ZIPinfo *zi = (ZIPinfo *) (h->opaque);
freeEntries(zi, zi->global.number_entry, NULL);
free(zi->archiveName);
free(zi);
672
free(h);
673
674
} /* ZIP_dirClose */
675
676
#endif /* defined PHYSFS_SUPPORTS_ZIP */
677
/* end of zip.c ... */