zip.c 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594
  1. /*
  2. * ZIP support routines for PhysicsFS.
  3. *
  4. * Please see the file LICENSE in the source's root directory.
  5. *
  6. * This file written by Ryan C. Gordon.
  7. */
  8. /*
  9. * !!! FIXME: overall design bugs.
  10. *
  11. * It'd be nice if we could remove the thread issues: I/O to individual
  12. * files inside an archive are safe, but the searches over the central
  13. * directory and the ZIP_openRead() call are race conditions. Basically,
  14. * we need to hack something like openDir() into unzip.c, so that directory
  15. * reads are separated by handles, and maybe add a openFileByName() call,
  16. * or make unzOpenCurrentFile() take a handle, too.
  17. *
  18. * Make unz_file_info.version into two fields of unsigned char. That's what
  19. * they are in the zipfile; heavens knows why unzip.c casts it...this causes
  20. * a byte ordering headache for me in entry_is_symlink().
  21. *
  22. * Maybe add a seekToStartOfCurrentFile() in unzip.c if complete seek
  23. * semantics are impossible.
  24. */
  25. #include <stdio.h>
  26. #include <stdlib.h>
  27. #include <string.h>
  28. #include <unistd.h>
  29. #include <errno.h>
  30. #include "physfs.h"
  31. #include "unzip.h"
  32. #define __PHYSICSFS_INTERNAL__
  33. #include "physfs_internal.h"
  34. #if (!defined PHYSFS_SUPPORTS_ZIP)
  35. #error PHYSFS_SUPPORTS_ZIP must be defined.
  36. #endif
  37. #define MAXZIPENTRYSIZE 256
  38. typedef struct
  39. {
  40. unzFile handle;
  41. uLong totalEntries;
  42. char *archiveName;
  43. } ZIPinfo;
  44. typedef struct
  45. {
  46. unzFile handle;
  47. } ZIPfileinfo;
  48. extern const DirFunctions __PHYSFS_DirFunctions_ZIP;
  49. static const FileFunctions __PHYSFS_FileFunctions_ZIP;
  50. static int ZIP_read(FileHandle *handle, void *buffer,
  51. unsigned int objSize, unsigned int objCount)
  52. {
  53. unzFile fh = ((ZIPfileinfo *) (handle->opaque))->handle;
  54. int bytes = objSize * objCount;
  55. int rc = unzReadCurrentFile(fh, buffer, bytes);
  56. if (rc < bytes)
  57. __PHYSFS_setError(ERR_PAST_EOF);
  58. else if (rc == UNZ_ERRNO)
  59. __PHYSFS_setError(ERR_IO_ERROR);
  60. else if (rc < 0)
  61. __PHYSFS_setError(ERR_COMPRESSION);
  62. return(rc / objSize);
  63. } /* ZIP_read */
  64. static int ZIP_eof(FileHandle *handle)
  65. {
  66. return(unzeof(((ZIPfileinfo *) (handle->opaque))->handle));
  67. } /* ZIP_eof */
  68. static int ZIP_tell(FileHandle *handle)
  69. {
  70. return(unztell(((ZIPfileinfo *) (handle->opaque))->handle));
  71. } /* ZIP_tell */
  72. static int ZIP_fileLength(FileHandle *handle);
  73. static int ZIP_seek(FileHandle *handle, int offset)
  74. {
  75. /* this blows. */
  76. unzFile fh = ((ZIPfileinfo *) (handle->opaque))->handle;
  77. char *buf = NULL;
  78. int bufsize = 4096 * 2;
  79. BAIL_IF_MACRO(unztell(fh) == offset, NULL, 1);
  80. BAIL_IF_MACRO(ZIP_fileLength(handle) <= offset, ERR_PAST_EOF, 0);
  81. /* reset to the start of the zipfile. */
  82. unzCloseCurrentFile(fh);
  83. BAIL_IF_MACRO(unzOpenCurrentFile(fh) != UNZ_OK, ERR_IO_ERROR, 0);
  84. while ((buf == NULL) && (bufsize >= 512))
  85. {
  86. bufsize >>= 1; /* divides by two. */
  87. buf = (char *) malloc(bufsize);
  88. } /* while */
  89. BAIL_IF_MACRO(buf == NULL, ERR_OUT_OF_MEMORY, 0);
  90. while (offset > 0)
  91. {
  92. int chunk = (offset > bufsize) ? bufsize : offset;
  93. int rc = unzReadCurrentFile(fh, buf, chunk);
  94. BAIL_IF_MACRO(rc == 0, ERR_IO_ERROR, 0); /* shouldn't happen. */
  95. BAIL_IF_MACRO(rc == UNZ_ERRNO, ERR_IO_ERROR, 0);
  96. BAIL_IF_MACRO(rc < 0, ERR_COMPRESSION, 0);
  97. offset -= rc;
  98. } /* while */
  99. free(buf);
  100. return(offset == 0);
  101. } /* ZIP_seek */
  102. static int ZIP_fileLength(FileHandle *handle)
  103. {
  104. ZIPfileinfo *finfo = (ZIPfileinfo *) (handle->opaque);
  105. unz_file_info info;
  106. unzGetCurrentFileInfo(finfo->handle, &info, NULL, 0, NULL, 0, NULL, 0);
  107. return(info.uncompressed_size);
  108. } /* ZIP_fileLength */
  109. static int ZIP_fileClose(FileHandle *handle)
  110. {
  111. ZIPfileinfo *finfo = (ZIPfileinfo *) (handle->opaque);
  112. unzClose(finfo->handle);
  113. free(finfo);
  114. free(handle);
  115. return(1);
  116. } /* ZIP_fileClose */
  117. static int ZIP_isArchive(const char *filename, int forWriting)
  118. {
  119. int retval = 0;
  120. unzFile unz = unzOpen(filename);
  121. unz_global_info global;
  122. if (unz != NULL)
  123. {
  124. if (unzGetGlobalInfo(unz, &global) == UNZ_OK)
  125. retval = 1;
  126. unzClose(unz);
  127. } /* if */
  128. return(retval);
  129. } /* ZIP_isArchive */
  130. static DirHandle *ZIP_openArchive(const char *name, int forWriting)
  131. {
  132. unzFile unz = NULL;
  133. DirHandle *retval = NULL;
  134. unz_global_info global;
  135. BAIL_IF_MACRO(forWriting, ERR_ARC_IS_READ_ONLY, NULL);
  136. errno = 0;
  137. BAIL_IF_MACRO(access(name, R_OK) != 0, strerror(errno), NULL);
  138. retval = malloc(sizeof (DirHandle));
  139. BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL);
  140. unz = unzOpen(name);
  141. if ((unz == NULL) || (unzGetGlobalInfo(unz, &global) != UNZ_OK))
  142. {
  143. if (unz)
  144. unzClose(unz);
  145. free(retval);
  146. BAIL_IF_MACRO(1, ERR_UNSUPPORTED_ARCHIVE, NULL);
  147. } /* if */
  148. retval->opaque = malloc(sizeof (ZIPinfo));
  149. if (retval->opaque == NULL)
  150. {
  151. free(retval);
  152. unzClose(unz);
  153. BAIL_IF_MACRO(1, ERR_OUT_OF_MEMORY, NULL);
  154. } /* if */
  155. ((ZIPinfo *) (retval->opaque))->archiveName = malloc(strlen(name) + 1);
  156. if (((ZIPinfo *) (retval->opaque))->archiveName == NULL)
  157. {
  158. free(retval->opaque);
  159. free(retval);
  160. unzClose(unz);
  161. BAIL_IF_MACRO(1, ERR_OUT_OF_MEMORY, NULL);
  162. } /* if */
  163. ((ZIPinfo *) (retval->opaque))->handle = unz;
  164. ((ZIPinfo *) (retval->opaque))->totalEntries = global.number_entry;
  165. strcpy(((ZIPinfo *) (retval->opaque))->archiveName, name);
  166. retval->funcs = &__PHYSFS_DirFunctions_ZIP;
  167. return(retval);
  168. } /* ZIP_openArchive */
  169. /* "uLong" is defined by zlib and/or unzip.h ... */
  170. typedef union
  171. {
  172. unsigned char uchar4[4];
  173. uLong ul;
  174. } uchar4_uLong;
  175. static int version_does_symlinks(uLong version)
  176. {
  177. int retval = 0;
  178. unsigned char hosttype;
  179. uchar4_uLong converter;
  180. converter.ul = version;
  181. hosttype = converter.uchar4[1]; /* !!! BYTE ORDERING ALERT! */
  182. /*
  183. * These are the platforms that can build an archive with symlinks,
  184. * according to the Info-ZIP project.
  185. */
  186. switch (hosttype)
  187. {
  188. case 3: /* Unix */
  189. case 16: /* BeOS */
  190. case 5: /* Atari */
  191. retval = 1;
  192. break;
  193. } /* switch */
  194. return(retval);
  195. } /* version_does_symlinks */
  196. static int entry_is_symlink(unz_file_info *info)
  197. {
  198. return (
  199. (version_does_symlinks(info->version)) &&
  200. (info->uncompressed_size > 0) &&
  201. (info->external_fa & 0x0120000) /* symlink flag. */
  202. );
  203. } /* entry_is_symlink */
  204. static char *ZIP_realpath(unzFile fh, unz_file_info *info)
  205. {
  206. char *retval = NULL;
  207. int size;
  208. if (entry_is_symlink(info))
  209. {
  210. BAIL_IF_MACRO(unzOpenCurrentFile(fh) != UNZ_OK, ERR_IO_ERROR, NULL);
  211. size = info->uncompressed_size;
  212. retval = (char *) malloc(size + 1);
  213. BAIL_IF_MACRO(retval == NULL, ERR_OUT_OF_MEMORY, NULL);
  214. if (unzReadCurrentFile(fh, retval, size) != size)
  215. {
  216. free(retval);
  217. __PHYSFS_setError(ERR_IO_ERROR);
  218. retval = NULL;
  219. } /* if */
  220. retval[size] = '\0';
  221. unzCloseCurrentFile(fh);
  222. } /* if */
  223. return(retval);
  224. } /* ZIP_realpath */
  225. /* !!! This is seriously ugly. */
  226. static LinkedStringList *ZIP_enumerateFiles(DirHandle *h,
  227. const char *dirname,
  228. int omitSymLinks)
  229. {
  230. ZIPinfo *zi = (ZIPinfo *) (h->opaque);
  231. unzFile fh = zi->handle;
  232. int i;
  233. int dlen;
  234. LinkedStringList *retval = NULL;
  235. LinkedStringList *l = NULL;
  236. LinkedStringList *prev = NULL;
  237. char buf[MAXZIPENTRYSIZE];
  238. char *d;
  239. unz_file_info info;
  240. /* jump to first file entry... */
  241. BAIL_IF_MACRO(unzGoToFirstFile(fh) != UNZ_OK, ERR_IO_ERROR, NULL);
  242. dlen = strlen(dirname);
  243. d = malloc(dlen + 1);
  244. BAIL_IF_MACRO(d == NULL, ERR_OUT_OF_MEMORY, NULL);
  245. strcpy(d, dirname);
  246. if ((dlen > 0) && (d[dlen - 1] == '/')) /* no trailing slash. */
  247. {
  248. dlen--;
  249. d[dlen] = '\0';
  250. } /* if */
  251. for (i = 0; i < zi->totalEntries; i++, unzGoToNextFile(fh))
  252. {
  253. char *ptr;
  254. char *add_file;
  255. int this_dlen;
  256. unzGetCurrentFileInfo(fh, &info, buf, sizeof (buf), NULL, 0, NULL, 0);
  257. if ((omitSymLinks) && (entry_is_symlink(&info)))
  258. continue;
  259. buf[sizeof (buf) - 1] = '\0'; /* safety. */
  260. this_dlen = strlen(buf);
  261. if ((this_dlen > 0) && (buf[this_dlen - 1] == '/')) /* no trailing slash. */
  262. {
  263. this_dlen--;
  264. buf[this_dlen] = '\0';
  265. } /* if */
  266. if (this_dlen <= dlen) /* not in this dir. */
  267. continue;
  268. if (*d == '\0')
  269. add_file = buf;
  270. else
  271. {
  272. if (buf[dlen] != '/') /* can't be in same directory? */
  273. continue;
  274. buf[dlen] = '\0';
  275. if (__PHYSFS_platformStricmp(d, buf) != 0) /* not same directory? */
  276. continue;
  277. add_file = buf + dlen + 1;
  278. } /* else */
  279. /* handle subdirectories... */
  280. ptr = strchr(add_file, '/');
  281. if (ptr != NULL)
  282. {
  283. LinkedStringList *j;
  284. *ptr = '\0';
  285. for (j = retval; j != NULL; j = j->next)
  286. {
  287. if (__PHYSFS_platformStricmp(j->str, ptr) == 0)
  288. break;
  289. } /* for */
  290. if (j != NULL)
  291. continue;
  292. } /* if */
  293. l = (LinkedStringList *) malloc(sizeof (LinkedStringList));
  294. if (l == NULL)
  295. break;
  296. l->str = (char *) malloc(strlen(add_file) + 1);
  297. if (l->str == NULL)
  298. {
  299. free(l);
  300. break;
  301. } /* if */
  302. strcpy(l->str, add_file);
  303. if (retval == NULL)
  304. retval = l;
  305. else
  306. prev->next = l;
  307. prev = l;
  308. l->next = NULL;
  309. } /* for */
  310. free(d);
  311. return(retval);
  312. } /* ZIP_enumerateFiles */
  313. /* !!! This is seriously ugly. */
  314. static int ZIP_exists_symcheck(DirHandle *h, const char *name, int follow)
  315. {
  316. char buf[MAXZIPENTRYSIZE];
  317. ZIPinfo *zi = (ZIPinfo *) (h->opaque);
  318. unzFile fh = zi->handle;
  319. int dlen;
  320. char *d;
  321. int i;
  322. unz_file_info info;
  323. BAIL_IF_MACRO(unzGoToFirstFile(fh) != UNZ_OK, ERR_IO_ERROR, 0);
  324. dlen = strlen(name);
  325. d = malloc(dlen + 1);
  326. BAIL_IF_MACRO(d == NULL, ERR_OUT_OF_MEMORY, 0);
  327. strcpy(d, name);
  328. if ((dlen > 0) && (d[dlen - 1] == '/')) /* no trailing slash. */
  329. {
  330. dlen--;
  331. d[dlen] = '\0';
  332. } /* if */
  333. for (i = 0; i < zi->totalEntries; i++, unzGoToNextFile(fh))
  334. {
  335. int this_dlen;
  336. unzGetCurrentFileInfo(fh, &info, buf, sizeof (buf), NULL, 0, NULL, 0);
  337. buf[sizeof (buf) - 1] = '\0'; /* safety. */
  338. this_dlen = strlen(buf);
  339. if ((this_dlen > 0) && (buf[this_dlen - 1] == '/')) /* no trailing slash. */
  340. {
  341. this_dlen--;
  342. buf[this_dlen] = '\0';
  343. } /* if */
  344. if ( ((buf[dlen] == '/') || (buf[dlen] == '\0')) &&
  345. (strncmp(d, buf, dlen) == 0) )
  346. {
  347. int retval = 1;
  348. free(d);
  349. if (follow) /* follow symlinks? */
  350. {
  351. char *real = ZIP_realpath(fh, &info);
  352. if (real != NULL)
  353. {
  354. retval = ZIP_exists_symcheck(h, real, follow - 1);
  355. free(real);
  356. } /* if */
  357. } /* if */
  358. return(retval);
  359. } /* if */
  360. } /* for */
  361. free(d);
  362. return(0);
  363. } /* ZIP_exists_symcheck */
  364. static int ZIP_exists_nofollow(DirHandle *h, const char *name)
  365. {
  366. return(ZIP_exists_symcheck(h, name, 0));
  367. } /* ZIP_exists_nofollow */
  368. static int ZIP_exists(DirHandle *h, const char *name)
  369. {
  370. /* follow at most 20 links to prevent recursion... */
  371. int retval = ZIP_exists_symcheck(h, name, 20);
  372. unz_file_info info;
  373. unzFile fh = ((ZIPinfo *) (h->opaque))->handle;
  374. if (retval)
  375. {
  376. /* current zip entry will be the file in question. */
  377. unzGetCurrentFileInfo(fh, &info, NULL, 0, NULL, 0, NULL, 0);
  378. /* if it's a symlink, then we ran into a possible symlink loop. */
  379. BAIL_IF_MACRO(entry_is_symlink(&info), ERR_TOO_MANY_SYMLINKS, 0);
  380. } /* if */
  381. return(retval);
  382. } /* ZIP_exists */
  383. static int ZIP_isDirectory(DirHandle *h, const char *name)
  384. {
  385. char buf[MAXZIPENTRYSIZE];
  386. unzFile fh = ((ZIPinfo *) (h->opaque))->handle;
  387. int retval = ZIP_exists(h, name);
  388. int dlen = strlen(name);
  389. if (retval)
  390. {
  391. /* current zip entry will be the file in question. */
  392. unzGetCurrentFileInfo(fh, NULL, buf, sizeof (buf), NULL, 0, NULL, 0);
  393. retval = (buf[dlen] == '/'); /* !!! yikes. */
  394. } /* if */
  395. return(retval);
  396. } /* ZIP_isDirectory */
  397. static int ZIP_isSymLink(DirHandle *h, const char *name)
  398. {
  399. unzFile fh = ((ZIPinfo *) (h->opaque))->handle;
  400. int retval = ZIP_exists_nofollow(h, name);
  401. unz_file_info info;
  402. if (retval)
  403. {
  404. /* current zip entry will be the file in question. */
  405. unzGetCurrentFileInfo(fh, &info, NULL, 0, NULL, 0, NULL, 0);
  406. retval = entry_is_symlink(&info);
  407. } /* if */
  408. return(retval);
  409. } /* ZIP_isSymLink */
  410. static FileHandle *ZIP_openRead(DirHandle *h, const char *filename)
  411. {
  412. FileHandle *retval = NULL;
  413. ZIPfileinfo *finfo = NULL;
  414. char *name = ((ZIPinfo *) (h->opaque))->archiveName;
  415. unzFile f;
  416. BAIL_IF_MACRO(!ZIP_exists(h, filename), ERR_NO_SUCH_FILE, NULL);
  417. f = unzOpen(name);
  418. BAIL_IF_MACRO(f == NULL, ERR_IO_ERROR, NULL);
  419. if ( (unzLocateFile(f, filename, 2) != UNZ_OK) ||
  420. (unzOpenCurrentFile(f) != UNZ_OK) ||
  421. ( (finfo = (ZIPfileinfo *) malloc(sizeof (ZIPfileinfo))) == NULL ) )
  422. {
  423. unzClose(f);
  424. BAIL_IF_MACRO(1, ERR_IO_ERROR, NULL);
  425. } /* if */
  426. if ( (!(retval = (FileHandle *) malloc(sizeof (FileHandle)))) ||
  427. (!(retval->opaque = (ZIPfileinfo *) malloc(sizeof (ZIPfileinfo)))) )
  428. {
  429. if (retval)
  430. free(retval);
  431. unzClose(f);
  432. BAIL_IF_MACRO(1, ERR_OUT_OF_MEMORY, NULL);
  433. } /* if */
  434. finfo->handle = f;
  435. retval->opaque = (void *) finfo;
  436. retval->funcs = &__PHYSFS_FileFunctions_ZIP;
  437. retval->dirHandle = h;
  438. return(retval);
  439. } /* ZIP_openRead */
  440. static void ZIP_dirClose(DirHandle *h)
  441. {
  442. unzClose(((ZIPinfo *) (h->opaque))->handle);
  443. free(h->opaque);
  444. free(h);
  445. } /* ZIP_dirClose */
  446. static const FileFunctions __PHYSFS_FileFunctions_ZIP =
  447. {
  448. ZIP_read, /* read() method */
  449. NULL, /* write() method */
  450. ZIP_eof, /* eof() method */
  451. ZIP_tell, /* tell() method */
  452. ZIP_seek, /* seek() method */
  453. ZIP_fileLength, /* fileLength() method */
  454. ZIP_fileClose /* fileClose() method */
  455. };
  456. const DirFunctions __PHYSFS_DirFunctions_ZIP =
  457. {
  458. ZIP_isArchive, /* isArchive() method */
  459. ZIP_openArchive, /* openArchive() method */
  460. ZIP_enumerateFiles, /* enumerateFiles() method */
  461. ZIP_exists, /* exists() method */
  462. ZIP_isDirectory, /* isDirectory() method */
  463. ZIP_isSymLink, /* isSymLink() method */
  464. ZIP_openRead, /* openRead() method */
  465. NULL, /* openWrite() method */
  466. NULL, /* openAppend() method */
  467. NULL, /* remove() method */
  468. NULL, /* mkdir() method */
  469. ZIP_dirClose /* dirClose() method */
  470. };
  471. const PHYSFS_ArchiveInfo __PHYSFS_ArchiveInfo_ZIP =
  472. {
  473. "ZIP",
  474. "PkZip/WinZip/Info-Zip compatible",
  475. "Ryan C. Gordon (icculus@linuxgames.com)",
  476. "http://www.icculus.org/~icculus/",
  477. };
  478. /* end of zip.c ... */