/
zip.c
679 lines (544 loc) · 18.4 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
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 */
102
NULL, /* getLastModTime() method */ /* !!! FIXME: This can be determined in a zipfile. */
103
104
105
106
107
108
109
110
111
112
113
114
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",
115
"Ryan C. Gordon <icculus@clutteredmind.org>",
116
117
118
119
120
"http://www.icculus.org/physfs/",
};
121
122
static PHYSFS_sint64 ZIP_read(FileHandle *handle, void *buffer,
PHYSFS_uint32 objSize, PHYSFS_uint32 objCount)
123
{
124
unzFile fh = ((ZIPfileinfo *) (handle->opaque))->handle;
125
126
int bytes = (int) (objSize * objCount); /* !!! FIXME: overflow? */
PHYSFS_sint32 rc = unzReadCurrentFile(fh, buffer, bytes);
127
128
129
130
131
132
133
134
135
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);
136
137
138
139
140
} /* ZIP_read */
static int ZIP_eof(FileHandle *handle)
{
141
return(unzeof(((ZIPfileinfo *) (handle->opaque))->handle));
142
143
144
} /* ZIP_eof */
145
static PHYSFS_sint64 ZIP_tell(FileHandle *handle)
146
{
147
return(unztell(((ZIPfileinfo *) (handle->opaque))->handle));
148
149
150
} /* ZIP_tell */
151
static int ZIP_seek(FileHandle *handle, PHYSFS_uint64 offset)
152
{
153
/* !!! FIXME : this blows. */
154
unzFile fh = ((ZIPfileinfo *) (handle->opaque))->handle;
155
char *buf = NULL;
156
PHYSFS_uint32 bufsize = 4096 * 2;
157
158
BAIL_IF_MACRO(unztell(fh) == offset, NULL, 1);
159
BAIL_IF_MACRO(ZIP_fileLength(handle) <= (PHYSFS_sint64) offset, ERR_PAST_EOF, 0);
160
161
162
163
164
165
166
167
168
169
170
171
172
173
/* 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)
{
174
PHYSFS_uint32 chunk = (offset > bufsize) ? bufsize : (PHYSFS_uint32)offset;
175
PHYSFS_sint32 rc = unzReadCurrentFile(fh, buf, chunk);
176
177
178
179
180
181
182
183
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);
184
185
186
} /* ZIP_seek */
187
static PHYSFS_sint64 ZIP_fileLength(FileHandle *handle)
188
{
189
190
191
192
193
ZIPfileinfo *finfo = (ZIPfileinfo *) (handle->opaque);
unz_file_info info;
unzGetCurrentFileInfo(finfo->handle, &info, NULL, 0, NULL, 0, NULL, 0);
return(info.uncompressed_size);
194
195
196
} /* ZIP_fileLength */
197
198
static int ZIP_fileClose(FileHandle *handle)
{
199
200
201
202
203
ZIPfileinfo *finfo = (ZIPfileinfo *) (handle->opaque);
unzClose(finfo->handle);
free(finfo);
free(handle);
return(1);
204
205
206
207
208
} /* ZIP_fileClose */
static int ZIP_isArchive(const char *filename, int forWriting)
{
209
int retval = 0;
210
unzFile unz = unzOpen(filename);
211
212
213
214
215
216
217
218
219
220
unz_global_info global;
if (unz != NULL)
{
if (unzGetGlobalInfo(unz, &global) == UNZ_OK)
retval = 1;
unzClose(unz);
} /* if */
return(retval);
221
222
223
} /* ZIP_isArchive */
224
static void freeEntries(ZIPinfo *info, int count, const char *errmsg)
225
{
226
int i;
227
228
229
230
231
232
233
for (i = 0; i < count; i++)
{
free(info->entries[i].name);
if (info->entries[i].symlink != NULL)
free(info->entries[i].symlink);
} /* for */
234
235
free(info->entries);
236
237
238
239
if (errmsg != NULL)
__PHYSFS_setError(errmsg);
} /* freeEntries */
240
241
242
243
244
245
246
247
248
/*
* !!! 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)
249
{
250
char *retval = (char *) malloc(strlen(path) + 1);
251
BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL);
252
strcpy(retval, path);
253
return(retval);
254
} /* expand_symlink_path */
255
256
257
static char *ZIP_realpath(unzFile fh, unz_file_info *info, ZIPentry *entry)
258
{
259
260
261
262
263
264
265
266
267
268
269
270
271
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 */
272
273
274
275
276
static int version_does_symlinks(uLong version)
{
int retval = 0;
277
PHYSFS_uint8 hosttype = (PHYSFS_uint8) ((version >> 8) & 0xFF);
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
305
/*
* 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 */
306
static int loadZipEntries(ZIPinfo *info, unzFile unz)
307
{
308
309
310
311
312
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);
313
314
315
316
317
318
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++)
319
{
320
321
unz_file_info *d = &((info->entries[i]).info);
if (unzGetCurrentFileInfo(unz, d, NULL, 0, NULL, 0, NULL, 0) != UNZ_OK)
322
{
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
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))
{
346
info->entries[i].symlink = ZIP_realpath(unz, d, &info->entries[i]);
347
348
349
350
351
if (info->entries[i].symlink == NULL)
{
freeEntries(info, i + 1, NULL);
return(0);
} /* if */
352
} /* if */
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
399
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);
400
401
} /* if */
402
403
404
405
unzClose(unz);
strcpy(((ZIPinfo *) (retval->opaque))->archiveName, name);
retval->funcs = &__PHYSFS_DirFunctions_ZIP;
406
return(retval);
407
} /* ZIP_openArchive */
408
409
410
/* !!! This is seriously ugly. */
411
412
413
static LinkedStringList *ZIP_enumerateFiles(DirHandle *h,
const char *dirname,
int omitSymLinks)
414
{
415
ZIPinfo *zi = (ZIPinfo *) (h->opaque);
416
unsigned int i;
417
int dlen;
418
419
420
421
LinkedStringList *retval = NULL;
LinkedStringList *l = NULL;
LinkedStringList *prev = NULL;
char *d;
422
423
ZIPentry *entry;
char buf[MAXZIPENTRYSIZE];
424
425
426
427
dlen = strlen(dirname);
d = malloc(dlen + 1);
BAIL_IF_MACRO(d == NULL, ERR_OUT_OF_MEMORY, NULL);
428
strcpy(d, dirname);
429
430
431
432
433
if ((dlen > 0) && (d[dlen - 1] == '/')) /* no trailing slash. */
{
dlen--;
d[dlen] = '\0';
} /* if */
434
435
for (i = 0, entry = zi->entries; i < zi->global.number_entry; i++, entry++)
436
437
{
char *ptr;
438
439
440
char *add_file;
int this_dlen;
441
if ((omitSymLinks) && (entry->symlink != NULL))
442
443
continue;
444
445
446
447
448
449
this_dlen = strlen(entry->name);
if (this_dlen + 1 > MAXZIPENTRYSIZE)
continue; /* ugh. */
strcpy(buf, entry->name);
450
if ((this_dlen > 0) && (buf[this_dlen - 1] == '/')) /* no trailing slash. */
451
{
452
453
454
455
456
457
458
459
460
this_dlen--;
buf[this_dlen] = '\0';
} /* if */
if (this_dlen <= dlen) /* not in this dir. */
continue;
if (*d == '\0')
add_file = buf;
461
462
else
{
463
464
465
466
if (buf[dlen] != '/') /* can't be in same directory? */
continue;
buf[dlen] = '\0';
467
if (__PHYSFS_platformStricmp(d, buf) != 0) /* not same directory? */
468
469
470
continue;
add_file = buf + dlen + 1;
471
472
} /* else */
473
474
475
476
477
478
479
480
/* handle subdirectories... */
ptr = strchr(add_file, '/');
if (ptr != NULL)
{
LinkedStringList *j;
*ptr = '\0';
for (j = retval; j != NULL; j = j->next)
{
481
if (__PHYSFS_platformStricmp(j->str, ptr) == 0)
482
483
484
485
486
487
488
break;
} /* for */
if (j != NULL)
continue;
} /* if */
489
l = (LinkedStringList *) malloc(sizeof (LinkedStringList));
490
if (l == NULL)
491
492
break;
493
l->str = (char *) malloc(strlen(add_file) + 1);
494
495
496
497
498
499
if (l->str == NULL)
{
free(l);
break;
} /* if */
500
strcpy(l->str, add_file);
501
502
503
504
505
506
507
508
509
510
511
512
if (retval == NULL)
retval = l;
else
prev->next = l;
prev = l;
l->next = NULL;
} /* for */
free(d);
return(retval);
513
514
515
} /* ZIP_enumerateFiles */
516
517
518
519
520
521
522
/* !!! 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;
523
unsigned int i;
524
ZIPentry *entry;
525
526
527
dlen = strlen(name);
d = malloc(dlen + 1);
528
BAIL_IF_MACRO(d == NULL, ERR_OUT_OF_MEMORY, -1);
529
530
531
532
533
534
535
strcpy(d, name);
if ((dlen > 0) && (d[dlen - 1] == '/')) /* no trailing slash. */
{
dlen--;
d[dlen] = '\0';
} /* if */
536
for (i = 0, entry = zi->entries; i < zi->global.number_entry; i++, entry++)
537
{
538
539
540
541
542
int this_dlen = strlen(entry->name);
if (this_dlen + 1 > MAXZIPENTRYSIZE)
continue; /* ugh. */
strcpy(buf, entry->name);
543
544
545
546
547
548
549
550
551
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) )
{
552
int retval = i;
553
554
555
free(d);
if (follow) /* follow symlinks? */
{
556
557
if (entry->symlink != NULL)
retval = ZIP_exists_symcheck(h, entry->symlink, follow-1);
558
559
560
561
562
563
} /* if */
return(retval);
} /* if */
} /* for */
free(d);
564
return(-1);
565
566
567
} /* ZIP_exists_symcheck */
568
static int ZIP_exists(DirHandle *h, const char *name)
569
{
570
571
int retval = ZIP_exists_symcheck(h, name, SYMLINK_RECURSE_COUNT);
int is_sym;
572
573
574
if (retval == -1)
return(0);
575
576
577
578
/* 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);
579
580
return(1);
581
582
583
} /* ZIP_exists */
584
static int ZIP_isDirectory(DirHandle *h, const char *name)
585
{
586
587
588
int dlen;
int is_sym;
int retval = ZIP_exists_symcheck(h, name, SYMLINK_RECURSE_COUNT);
589
590
591
592
593
594
595
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);
596
597
598
599
dlen = strlen(name);
/* !!! yikes. Better way to check? */
retval = (((ZIPinfo *)(h->opaque))->entries[retval].name[dlen] == '/');
600
return(retval);
601
602
603
} /* ZIP_isDirectory */
604
static int ZIP_isSymLink(DirHandle *h, const char *name)
605
{
606
int retval = ZIP_exists_symcheck(h, name, 0);
607
608
if (retval == -1)
return(0);
609
610
retval = ( ((ZIPinfo *)(h->opaque))->entries[retval].symlink != NULL );
611
return(retval);
612
613
614
} /* ZIP_isSymLink */
615
static FileHandle *ZIP_openRead(DirHandle *h, const char *filename)
616
{
617
FileHandle *retval = NULL;
618
ZIPinfo *zi = ((ZIPinfo *) (h->opaque));
619
ZIPfileinfo *finfo = NULL;
620
int pos = ZIP_exists_symcheck(h, filename, SYMLINK_RECURSE_COUNT);
621
unzFile f;
622
623
BAIL_IF_MACRO(pos == -1, ERR_NO_SUCH_FILE, NULL);
624
625
f = unzOpen(zi->archiveName);
626
627
BAIL_IF_MACRO(f == NULL, ERR_IO_ERROR, NULL);
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
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) ||
644
( (finfo = (ZIPfileinfo *) malloc(sizeof (ZIPfileinfo))) == NULL ) )
645
646
647
648
649
{
unzClose(f);
BAIL_IF_MACRO(1, ERR_IO_ERROR, NULL);
} /* if */
650
651
652
653
654
655
656
657
658
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 */
659
finfo->handle = f;
660
retval->opaque = (void *) finfo;
661
retval->funcs = &__PHYSFS_FileFunctions_ZIP;
662
663
retval->dirHandle = h;
return(retval);
664
665
666
} /* ZIP_openRead */
667
static void ZIP_dirClose(DirHandle *h)
668
{
669
670
671
672
ZIPinfo *zi = (ZIPinfo *) (h->opaque);
freeEntries(zi, zi->global.number_entry, NULL);
free(zi->archiveName);
free(zi);
673
free(h);
674
675
} /* ZIP_dirClose */
676
677
#endif /* defined PHYSFS_SUPPORTS_ZIP */
678
/* end of zip.c ... */