From 7f6002caba3f0a6749820c2772161caf55b8d267 Mon Sep 17 00:00:00 2001 From: neonloop Date: Fri, 7 May 2021 20:00:12 +0000 Subject: Initial commit (uqm-0.8.0) --- src/libs/uio/zip/zip.c | 1680 ++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 1680 insertions(+) create mode 100644 src/libs/uio/zip/zip.c (limited to 'src/libs/uio/zip/zip.c') diff --git a/src/libs/uio/zip/zip.c b/src/libs/uio/zip/zip.c new file mode 100644 index 0000000..6da462b --- /dev/null +++ b/src/libs/uio/zip/zip.c @@ -0,0 +1,1680 @@ +/* + * Copyright (C) 2003 Serge van den Boom + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of version 2 of the GNU General Public License as + * published by the Free Software Foundation. + * Nota bene: later versions of the GNU General Public License do not apply + * to this program. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software + * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA + * + */ + +/* + * This file makes use of zlib (http://www.gzip.org/zlib/) + * + * References: + * The .zip format description from PKWare: + * http://www.pkware.com/products/enterprise/white_papers/appnote.html + * The .zip format description from InfoZip: + * ftp://ftp.info-zip.org/pub/infozip/doc/appnote-011203-iz.zip + */ + +#include +#include +#include +#include + +#include "zip.h" +#include "../physical.h" +#include "../uioport.h" +#include "../paths.h" +#include "../uioutils.h" +#ifdef uio_MEM_DEBUG +# include "../memdebug.h" +#endif + + +#define DIR_STRUCTURE_READ_BUFSIZE 0x10000 + +static int zip_badFile(zip_GPFileData *gPFileData, char *fileName); +static int zip_fillDirStructure(uio_GPDir *top, uio_Handle *handle); +#if zip_USE_HEADERS == zip_USE_LOCAL_HEADERS +static int zip_fillDirStructureLocal(uio_GPDir *top, uio_Handle *handle); +static int zip_fillDirStructureLocalProcessEntry(uio_GPDir *topGPDir, + uio_FileBlock *fileBlock, off_t *pos); +#endif +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +static off_t zip_findEndOfCentralDirectoryRecord(uio_Handle *handle, + uio_FileBlock *fileBlock); +static int zip_fillDirStructureCentral(uio_GPDir *top, uio_Handle *handle); +static int zip_fillDirStructureCentralProcessEntry(uio_GPDir *topGPDir, + uio_FileBlock *fileBlock, off_t *pos); +static int zip_updatePFileDataFromLocalFileHeader(zip_GPFileData *gPFileData, + uio_FileBlock *fileBlock, int pos); +int zip_updateFileDataFromLocalHeader(uio_Handle *handle, + zip_GPFileData *gPFileData); +#endif +static int zip_fillDirStructureProcessExtraFields( + uio_FileBlock *fileBlock, off_t extraFieldLength, + zip_GPFileData *gPFileData, const char *path, off_t pos, + uio_bool central); +static inline int zip_foundFile(uio_GPDir *gPDir, const char *path, + zip_GPFileData *gPFileData); +static inline int zip_foundDir(uio_GPDir *gPDir, const char *dirName, + zip_GPDirData *gPDirData); +static int zip_initZipStream(z_stream *zipStream); +static int zip_unInitZipStream(z_stream *zipStream); +static int zip_reInitZipStream(z_stream *zipStream); + +static voidpf zip_alloc(voidpf opaque, uInt items, uInt size); +static void zip_free(voidpf opaque, voidpf address); + +static inline zip_GPFileData * zip_GPFileData_new(void); +static inline void zip_GPFileData_delete(zip_GPFileData *gPFileData); +static inline zip_GPFileData *zip_GPFileData_alloc(void); +static inline void zip_GPFileData_free(zip_GPFileData *gPFileData); +static inline void zip_GPDirData_delete(zip_GPDirData *gPDirData); +static inline void zip_GPDirData_free(zip_GPDirData *gPDirData); + +static ssize_t zip_readStored(uio_Handle *handle, void *buf, size_t count); +static ssize_t zip_readDeflated(uio_Handle *handle, void *buf, size_t count); +static off_t zip_seekStored(uio_Handle *handle, off_t offset); +static off_t zip_seekDeflated(uio_Handle *handle, off_t offset); + +uio_FileSystemHandler zip_fileSystemHandler = { + /* .init = */ NULL, + /* .unInit = */ NULL, + /* .cleanup = */ NULL, + + /* .mount = */ zip_mount, + /* .umount = */ uio_GPRoot_umount, + + /* .access = */ zip_access, + /* .close = */ zip_close, + /* .fstat = */ zip_fstat, + /* .stat = */ zip_stat, + /* .mkdir = */ NULL, + /* .open = */ zip_open, + /* .read = */ zip_read, + /* .rename = */ NULL, + /* .rmdir = */ NULL, + /* .seek = */ zip_seek, + /* .write = */ NULL, + /* .unlink = */ NULL, + + /* .openEntries = */ uio_GPDir_openEntries, + /* .readEntries = */ uio_GPDir_readEntries, + /* .closeEntries = */ uio_GPDir_closeEntries, + + /* .getPDirEntryHandle = */ uio_GPDir_getPDirEntryHandle, + /* .deletePRootExtra = */ uio_GPRoot_delete, + /* .deletePDirHandleExtra = */ uio_GPDirHandle_delete, + /* .deletePFileHandleExtra = */ uio_GPFileHandle_delete, +}; + +uio_GPRoot_Operations zip_GPRootOperations = { + /* .fillGPDir = */ NULL, + /* .deleteGPRootExtra = */ NULL, + /* .deleteGPDirExtra = */ zip_GPDirData_delete, + /* .deleteGPFileExtra = */ zip_GPFileData_delete, +}; + + +#define NUM_COMPRESSION_METHODS 11 +/* + * [0] = stored uncompressed + * [1] = Shrunk + * [2] = Reduced with compression factor 1 + * [3] = Reduced with compression factor 2 + * [4] = Reduced with compression factor 3 + * [5] = Reduced with compression factor 4 + * [6] = Imploded + * [7] = Reserved for Tokenizing + * [8] = Deflated + * [9] = Deflate64 + * [10] = "PKWARE Data Compression Library Imploding" + */ +static const uio_bool + zip_compressionMethodSupported[NUM_COMPRESSION_METHODS] = { + true, false, false, false, false, false, false, false, + true, false, false }; + +typedef ssize_t (*zip_readFunctionType)(uio_Handle *handle, + void *buf, size_t count); +zip_readFunctionType zip_readMethods[NUM_COMPRESSION_METHODS] = { + zip_readStored, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + zip_readDeflated, NULL, NULL +}; +typedef off_t (*zip_seekFunctionType)(uio_Handle *handle, off_t offset); +zip_seekFunctionType zip_seekMethods[NUM_COMPRESSION_METHODS] = { + zip_seekStored, NULL, NULL, NULL, NULL, NULL, NULL, NULL, + zip_seekDeflated, NULL, NULL +}; + + +typedef enum { + zip_OSType_FAT, + zip_OSType_Amiga, + zip_OSType_OpenVMS, + zip_OSType_UNIX, + zip_OSType_VMCMS, + zip_OSType_AtariST, + zip_OSType_HPFS, + zip_OSType_HFS, + zip_OSType_ZSystem, + zip_OSType_CPM, + zip_OSType_TOPS20, + zip_OSType_NTFS, + zip_OSType_QDOS, + zip_OSType_Acorn, + zip_OSType_VFAT, + zip_OSType_MVS, + zip_OSType_BeOS, + zip_OSType_Tandem, + + zip_numOSTypes +} zip_OSType; + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +static mode_t zip_makeFileMode(zip_OSType creatorOS, uio_uint32 modeBytes); +#endif + +#define zip_INPUT_BUFFER_SIZE 0x10000 + // TODO: make this configurable a la sysctl? +#define zip_SEEK_BUFFER_SIZE zip_INPUT_BUFFER_SIZE + + +void +zip_close(uio_Handle *handle) { + zip_Handle *zip_handle; + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "zip_close - handle=%p\n", (void *) handle); +#endif + zip_handle = handle->native; + uio_GPFile_unref(zip_handle->file); + zip_unInitZipStream(&zip_handle->zipStream); + uio_closeFileBlock(zip_handle->fileBlock); + uio_free(zip_handle); +} + +static void +zip_fillStat(struct stat *statBuf, const zip_GPFileData *gPFileData) { + memset(statBuf, '\0', sizeof (struct stat)); + statBuf->st_size = gPFileData->uncompressedSize; + statBuf->st_uid = gPFileData->uid; + statBuf->st_gid = gPFileData->gid; + statBuf->st_mode = gPFileData->mode; + statBuf->st_atime = gPFileData->atime; + statBuf->st_mtime = gPFileData->mtime; + statBuf->st_ctime = gPFileData->ctime; +} + +int +zip_access(uio_PDirHandle *pDirHandle, const char *name, int mode) { + errno = ENOSYS; // Not implemented. + (void) pDirHandle; + (void) name; + (void) mode; + return -1; + +#if 0 + uio_GPDirEntry *entry; + + if (name[0] == '.' && name[1] == '\0') { + entry = (uio_GPDirEntry *) pDirHandle->extra; + } else { + entry = uio_GPDir_getGPDirEntry(pDirHandle->extra, name); + if (entry == NULL) { + errno = ENOENT; + return -1; + } + } + + if (mode & R_OK) + { + // Read permission is always granted. Nothing to check here. + } + + if (mode & W_OK) { + errno = EACCES; + return -1; + } + + if (mode & X_OK) { + if (uio_GPDirEntry_isDir(entry)) { + // Search permission on directories is always granted. + } else { + // WORK +#error + } + } + + if (mode & F_OK) { + // WORK +#error + } + + return 0; +#endif +} + +int +zip_fstat(uio_Handle *handle, struct stat *statBuf) { +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS + if (handle->native->file->extra->fileOffset == -1) { + // The local header wasn't read in yet. + if (zip_updateFileDataFromLocalHeader(handle->root->handle, + handle->native->file->extra) == -1) { + // errno is set + return -1; + } + } +#endif /* zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS */ + zip_fillStat(statBuf, handle->native->file->extra); + return 0; +} + +int +zip_stat(uio_PDirHandle *pDirHandle, const char *name, struct stat *statBuf) { + uio_GPDirEntry *entry; + + if (name[0] == '.' && name[1] == '\0') { + entry = (uio_GPDirEntry *) pDirHandle->extra; + } else { + entry = uio_GPDir_getGPDirEntry(pDirHandle->extra, name); + if (entry == NULL) { + errno = ENOENT; + return -1; + } + } + + if (uio_GPDirEntry_isDir(entry) && entry->extra == NULL) { + // No information about this directory was stored. + // We'll have to make something up. + memset(statBuf, '\0', sizeof (struct stat)); + statBuf->st_mode = S_IFDIR | S_IRUSR | S_IWUSR | S_IXUSR | + S_IRGRP | S_IWGRP | S_IXOTH | + S_IROTH | S_IWOTH | S_IXOTH; + statBuf->st_uid = 0; + statBuf->st_gid = 0; + return 0; + } + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +#ifndef zip_INCOMPLETE_STAT + if (((zip_GPFileData *) entry->extra)->fileOffset == -1) { + // The local header wasn't read in yet. + if (zip_updateFileDataFromLocalHeader(pDirHandle->pRoot->handle, + (zip_GPFileData *) entry->extra) == -1) { + // errno is set + return -1; + } + } +#endif /* !defined(zip_INCOMPLETE_STAT) */ +#endif /* zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS */ + + zip_fillStat(statBuf, (zip_GPFileData *) entry->extra); + return 0; +} + +/* + * Function name: zip_open + * Description: open a file in zip file + * Arguments: pDirHandle - handle to the dir where to open the file + * name - the name of the file to open + * flags - flags, as to stdio open() + * mode - mode, as to stdio open() + * Returns: handle, as from stdio open() + * If failed, errno is set and handle is -1. + */ +uio_Handle * +zip_open(uio_PDirHandle *pDirHandle, const char *name, int flags, + mode_t mode) { + zip_Handle *handle; + uio_GPFile *gPFile; + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "zip_open - pDirHandle=%p name=%s flags=%d mode=0%o\n", + (void *) pDirHandle, name, flags, mode); +#endif + + if ((flags & O_ACCMODE) != O_RDONLY) { + errno = EACCES; + return NULL; + } + + gPFile = (uio_GPFile *) uio_GPDir_getGPDirEntry(pDirHandle->extra, name); + if (gPFile == NULL) { + errno = ENOENT; + return NULL; + } + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS + if (gPFile->extra->fileOffset == -1) { + // The local header wasn't read in yet. + if (zip_updateFileDataFromLocalHeader(pDirHandle->pRoot->handle, + gPFile->extra) == -1) { + // errno is set + return NULL; + } + } +#endif + + handle = uio_malloc(sizeof (zip_Handle)); + uio_GPFile_ref(gPFile); + handle->file = gPFile; + handle->fileBlock = uio_openFileBlock2(pDirHandle->pRoot->handle, + gPFile->extra->fileOffset, gPFile->extra->compressedSize); + if (handle->fileBlock == NULL) { + // errno is set + return NULL; + } + + if (zip_initZipStream(&handle->zipStream) == -1) { + uio_GPFile_unref(gPFile); + uio_closeFileBlock(handle->fileBlock); + return NULL; + } + handle->compressedOffset = 0; + handle->uncompressedOffset = 0; + + (void) mode; + return uio_Handle_new(pDirHandle->pRoot, handle, flags); +} + +ssize_t +zip_read(uio_Handle *handle, void *buf, size_t count) { + ssize_t result; + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "zip_read - handle=%p buf=%p count=%d: ", (void *) handle, + (void *) buf, count); +#endif + result = zip_readMethods[handle->native->file->extra->compressionMethod] + (handle, buf, count); +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "%d\n", result); +#endif + return result; +} + +static ssize_t +zip_readStored(uio_Handle *handle, void *buf, size_t count) { + int numBytes; + zip_Handle *zipHandle; + + zipHandle = handle->native; + numBytes = uio_copyFileBlock(zipHandle->fileBlock, + zipHandle->uncompressedOffset, buf, count); + if (numBytes == -1) { + // errno is set + return -1; + } + zipHandle->uncompressedOffset += numBytes; + zipHandle->compressedOffset += numBytes; + return numBytes; +} + +static ssize_t +zip_readDeflated(uio_Handle *handle, void *buf, size_t count) { + zip_Handle *zipHandle; + int inflateResult; + + zipHandle = handle->native; + + if (count > ((zip_GPFileData *) (zipHandle->file->extra))-> + uncompressedSize - zipHandle->zipStream.total_out) + count = ((zip_GPFileData *) (zipHandle->file->extra))-> + uncompressedSize - zipHandle->zipStream.total_out; + + zipHandle->zipStream.next_out = (Bytef *) buf; + zipHandle->zipStream.avail_out = count; + while (zipHandle->zipStream.avail_out > 0) { + if (zipHandle->zipStream.avail_in == 0) { + ssize_t numBytes; + numBytes = uio_accessFileBlock(zipHandle->fileBlock, + zipHandle->compressedOffset, zip_INPUT_BUFFER_SIZE, + (char **) &zipHandle->zipStream.next_in); + if (numBytes == -1) { + // errno is set + return -1; + } +#if 0 + if (numBytes == 0) { + if (zipHandle->uncompressedOffset != + zipHandle->file->extra->uncompressedSize) { + // premature eof + errno = EIO; + return -1; + } + break; + } +#endif + zipHandle->zipStream.avail_in = numBytes; + zipHandle->compressedOffset += numBytes; + } + inflateResult = inflate(&zipHandle->zipStream, Z_SYNC_FLUSH); + zipHandle->uncompressedOffset = zipHandle->zipStream.total_out; + if (inflateResult == Z_STREAM_END) { + // Everything is decompressed + break; + } + if (inflateResult != Z_OK) { + switch (inflateResult) { + case Z_VERSION_ERROR: + fprintf(stderr, "Error: Incompatible version problem for " + " decompression.\n"); + break; + case Z_NEED_DICT: + fprintf(stderr, "Error: Decompressing requires " + "preset dictionary.\n"); + break; + case Z_DATA_ERROR: + fprintf(stderr, "Error: Compressed file is corrupted.\n"); + break; + case Z_STREAM_ERROR: + // This means zipHandle->zipStream is bad, which is + // most likely an error in the code using zlib. + fprintf(stderr, "Fatal: internal error using zlib.\n"); + abort(); + break; + case Z_MEM_ERROR: + fprintf(stderr, "Error: Not enough memory available " + "while decompressing.\n"); + break; + case Z_BUF_ERROR: + // No progress possible. Probably caused by premature + // end of input file. + fprintf(stderr, "Error: When decompressing: premature " + "end of input file.\n"); + errno = EIO; + return -1; +#if 0 + // If this happens, either the input buffer is empty + // or the output buffer is full. This should not happen. + fprintf(stderr, "Fatal: internal error using zlib: " + " no progress is possible in decompression.\n"); + abort(); + break; +#endif + default: + fprintf(stderr, "Fatal: unknown error from inflate().\n"); + abort(); + } + if (zipHandle->zipStream.msg != NULL) + fprintf(stderr, "ZLib reports: %s\n", + zipHandle->zipStream.msg); + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + } + return count - zipHandle->zipStream.avail_out; +} + +off_t +zip_seek(uio_Handle *handle, off_t offset, int whence) { + zip_Handle *zipHandle; + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "zip_seek - handle=%p offset=%d whence=%s\n", + (void *) handle, (int) offset, + whence == SEEK_SET ? "SEEK_SET" : + whence == SEEK_CUR ? "SEEK_CUR" : + whence == SEEK_END ? "SEEK_END" : "INVALID"); +#endif + zipHandle = handle->native; + + assert(whence == SEEK_SET || whence == SEEK_CUR || whence == SEEK_END); + switch(whence) { + case SEEK_SET: + break; + case SEEK_CUR: + offset += zipHandle->uncompressedOffset; + break; + case SEEK_END: + offset += zipHandle->file->extra->uncompressedSize; + break; + } + if (offset < 0) { + offset = 0; + } else if (offset > zipHandle->file->extra->uncompressedSize) { + offset = zipHandle->file->extra->uncompressedSize; + } + return zip_seekMethods[handle->native->file->extra->compressionMethod] + (handle, offset); +} + +static off_t +zip_seekStored(uio_Handle *handle, off_t offset) { + zip_Handle *zipHandle; + + zipHandle = handle->native; + if (offset > zipHandle->file->extra->uncompressedSize) + offset = zipHandle->file->extra->uncompressedSize; + + zipHandle->compressedOffset = offset; + zipHandle->uncompressedOffset = offset; + return offset; +} + +static off_t +zip_seekDeflated(uio_Handle *handle, off_t offset) { + zip_Handle *zipHandle; + + zipHandle = handle->native; + + if (offset < zipHandle->uncompressedOffset) { + // The new offset is earlier than the current offset. We need to + // seek from the beginning. + if (zip_reInitZipStream(&zipHandle->zipStream) == -1) { + // Need to abort. Handle would get in an inconsistent state. + // Should not fail anyhow. + fprintf(stderr, "Fatal: Could not reinitialise zip stream: " + "%s.\n", strerror(errno)); + abort(); + } + zipHandle->compressedOffset = 0; + zipHandle->uncompressedOffset = 0; + } + + if (offset == zipHandle->uncompressedOffset) + return offset; + + // Seek from the current position. + { + char *buffer; + ssize_t numRead; + size_t toRead; + + buffer = uio_malloc(zip_SEEK_BUFFER_SIZE); + toRead = offset - zipHandle->uncompressedOffset; + while (toRead > 0) { + numRead = zip_read(handle, buffer, + toRead < zip_SEEK_BUFFER_SIZE ? + toRead : zip_SEEK_BUFFER_SIZE); + if (numRead == -1) { + fprintf(stderr, "Warning: Could not read zipped file: %s\n", + strerror(errno)); + break; + // The current location is returned. + } + toRead -= numRead; + } + uio_free(buffer); + } + return zipHandle->uncompressedOffset; +} + +uio_PRoot * +zip_mount(uio_Handle *handle, int flags) { + uio_PRoot *result; + uio_PDirHandle *rootDirHandle; + + if ((flags & uio_MOUNT_RDONLY) != uio_MOUNT_RDONLY) { + errno = EACCES; + return NULL; + } + + uio_Handle_ref(handle); + result = uio_GPRoot_makePRoot( + uio_getFileSystemHandler(uio_FSTYPE_ZIP), flags, + &zip_GPRootOperations, NULL, uio_GPRoot_PERSISTENT, + handle, NULL, uio_GPDir_COMPLETE); + + rootDirHandle = uio_PRoot_getRootDirHandle(result); + if (zip_fillDirStructure(rootDirHandle->extra, handle) == -1) { + int savedErrno = errno; +#ifdef DEBUG + fprintf(stderr, "Error: failed to read the zip directory " + "structure - %s.\n", strerror(errno)); +#endif + uio_GPRoot_umount(result); + errno = savedErrno; + return NULL; + } + + return result; +} + +static int +zip_fillDirStructure(uio_GPDir *top, uio_Handle *handle) { +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS + return zip_fillDirStructureCentral(top, handle); +#endif +#if zip_USE_HEADERS == zip_USE_LOCAL_HEADERS + return zip_fillDirStructureLocal(top, handle); +#endif +} + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +static int +zip_fillDirStructureCentral(uio_GPDir *top, uio_Handle *handle) { + uio_FileBlock *fileBlock; + off_t pos; + char *buf; + ssize_t numBytes; + uio_uint16 numEntries; + // TODO: use numEntries to initialise the hash table + // to a smart size + off_t eocdr; + off_t startCentralDir; + + fileBlock = uio_openFileBlock(handle); + if (fileBlock == NULL) { + // errno is set + goto err; + } + + // first find the 'End of Central Directory Record' + eocdr = zip_findEndOfCentralDirectoryRecord(handle, fileBlock); + if (eocdr == -1) { + // errno is set + goto err; + } + + numBytes = uio_accessFileBlock(fileBlock, eocdr, 22, &buf); + if (numBytes == -1) { + // errno is set + goto err; + } + if (numBytes != 22) { + errno = EIO; + goto err; + } + + numEntries = makeUInt16(buf[10], buf[11]); + if (numEntries == 0xffff) { + fprintf(stderr, "Error: Zip64 .zip files are not supported.\n"); + errno = ENOSYS; + goto err; + } + + startCentralDir = makeUInt32(buf[16], buf[17], buf[18], buf[19]); + + // Enable read-ahead buffering, for speed. + uio_setFileBlockUsageHint(fileBlock, uio_FB_USAGE_FORWARD, + DIR_STRUCTURE_READ_BUFSIZE); + + pos = startCentralDir; + while (numEntries--) { + if (zip_fillDirStructureCentralProcessEntry(top, fileBlock, &pos) + == -1) { + // errno is set + goto err; + } + } + + uio_closeFileBlock(fileBlock); + return 0; + +err: + { + int savedErrno = errno; + + if (fileBlock != NULL) + uio_closeFileBlock(fileBlock); + errno = savedErrno; + return -1; + } +} + +static int +zip_fillDirStructureCentralProcessEntry(uio_GPDir *topGPDir, + uio_FileBlock *fileBlock, off_t *pos) { + char *buf; + zip_GPFileData *gPFileData; + ssize_t numBytes; + + uio_uint32 signature; + uio_uint16 lastModTime; + uio_uint16 lastModDate; + uio_uint32 crc; + uio_uint16 fileNameLength; + uio_uint16 extraFieldLength; + uio_uint16 fileCommentLength; + char *fileName; + zip_OSType creatorOS; + + off_t nextEntryOffset; + + numBytes = uio_accessFileBlock(fileBlock, *pos, 46, &buf); + if (numBytes != 46) + return zip_badFile(NULL, NULL); + + signature = makeUInt32(buf[0], buf[1], buf[2], buf[3]); + if (signature != 0x02014b50) { + fprintf(stderr, "Error: Premature end of central directory.\n"); + errno = EIO; + return -1; + } + + gPFileData = zip_GPFileData_new(); + creatorOS = (zip_OSType) buf[5]; + gPFileData->compressionFlags = makeUInt16(buf[8], buf[9]); + gPFileData->compressionMethod = makeUInt16(buf[10], buf[11]); + lastModTime = makeUInt16(buf[12], buf[13]); + lastModDate = makeUInt16(buf[14], buf[15]); + gPFileData->atime = (time_t) 0; + gPFileData->mtime = dosToUnixTime(lastModDate, lastModTime); + gPFileData->ctime = (time_t) 0; + crc = makeUInt32(buf[16], buf[17], buf[18], buf[19]); + gPFileData->compressedSize = + makeUInt32(buf[20], buf[21], buf[22], buf[23]); + gPFileData->uncompressedSize = + makeUInt32(buf[24], buf[25], buf[26], buf[27]); + fileNameLength = makeUInt16(buf[28], buf[29]); + extraFieldLength = makeUInt16(buf[30], buf[31]); + fileCommentLength = makeUInt16(buf[32], buf[33]); + gPFileData->uid = 0; + gPFileData->gid = 0; + gPFileData->mode = zip_makeFileMode(creatorOS, + makeUInt32(buf[38], buf[39], buf[40], buf[41])); + + gPFileData->headerOffset = + (off_t) makeSInt32(buf[42], buf[43], buf[44], buf[45]); + gPFileData->fileOffset = (off_t) -1; + + *pos += 46; + nextEntryOffset = *pos + fileNameLength + extraFieldLength + + fileCommentLength; + + numBytes = uio_accessFileBlock(fileBlock, *pos, fileNameLength, &buf); + if (numBytes != fileNameLength) + return zip_badFile(gPFileData, NULL); + fileName = uio_malloc(fileNameLength + 1); + memcpy(fileName, buf, fileNameLength); + fileName[fileNameLength] = '\0'; + *pos += fileNameLength; + + if (gPFileData->compressionMethod >= NUM_COMPRESSION_METHODS || + !zip_compressionMethodSupported[gPFileData->compressionMethod]) { + fprintf(stderr, "Warning: File '%s' is compressed with " + "unsupported method %d - skipped.\n", fileName, + gPFileData->compressionMethod); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + if (gPFileData->compressedSize == (off_t) 0xffffffff || + gPFileData->uncompressedSize == (off_t) 0xffffffff || + gPFileData->headerOffset < 0) { + fprintf(stderr, "Warning: Skipping Zip64 file '%s'\n", fileName); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + if (isBitSet(gPFileData->compressionFlags, 0)) { + fprintf(stderr, "Warning: Skipping encrypted file '%s'\n", fileName); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + switch (zip_fillDirStructureProcessExtraFields(fileBlock, + extraFieldLength, gPFileData, fileName, *pos, true)) { + case 0: // file is ok + break; + case 1: // file is not acceptable - skip file + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + case -1: + return zip_badFile(gPFileData, fileName); + } + + *pos += extraFieldLength; + + // If ctime or atime is 0, they will be filled in when the local + // file header is read. + + if (S_ISREG(gPFileData->mode)) { + if (zip_foundFile(topGPDir, fileName, gPFileData) == -1) { + if (errno == EISDIR) { + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + return zip_badFile(gPFileData, fileName); + } + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "Debug: Found file '%s'.\n", fileName); +#endif + } else if (S_ISDIR(gPFileData->mode)) { + if (fileName[fileNameLength - 1] == '/') + fileName[fileNameLength - 1] = '\0'; + if (zip_foundDir(topGPDir, fileName, gPFileData) == -1) { + if (errno == EISDIR) { + fprintf(stderr, "Warning: file '%s' already exists as a dir - " + "skipped.\n", fileName); + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + return zip_badFile(gPFileData, fileName); + } +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "Debug: Found dir '%s'.\n", fileName); +#endif + } else { + fprintf(stderr, "Warning: '%s' is not a regular file, nor a " + "directory - skipped.\n", fileName); + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + + uio_free(fileName); + return 0; +} + +static off_t +zip_findEndOfCentralDirectoryRecord(uio_Handle *handle, + uio_FileBlock *fileBlock) { + off_t fileSize; + off_t endPos, startPos; + char *buf, *bufPtr; + ssize_t bufLen; + + struct stat statBuf; + if (uio_fstat(handle, &statBuf) == -1) { + // errno is set + return -1; + } + fileSize = statBuf.st_size; + startPos = fileSize - 0xffff - 22; // max comment and record size + if (startPos < 0) + startPos = 0; + endPos = fileSize - 22; // last position to be checked + bufLen = uio_accessFileBlock(fileBlock, startPos, endPos - startPos + 4, + &buf); + if (bufLen == -1) { + int savedErrno = errno; + fprintf(stderr, "Error: Read error while searching for " + "'end-of-central-directory record'.\n"); + errno = savedErrno; + return -1; + } + if (bufLen != endPos - startPos + 4) { + fprintf(stderr, "Error: Read error while searching for " + "'end-of-central-directory record'.\n"); + errno = EIO; + return -1; + } + bufPtr = buf + (endPos - startPos); + while (1) { + if (bufPtr < buf) { + fprintf(stderr, "Error: Zip file corrupt; could not find " + "'end-of-central-directory record'.\n"); + errno = EIO; + return -1; + } + if (bufPtr[0] == 0x50 && bufPtr[1] == 0x4b && bufPtr[2] == 0x05 && + bufPtr[3] == 0x06) + break; + bufPtr--; + } + return startPos + (bufPtr - buf); +} + +static mode_t +zip_makeFileMode(zip_OSType creatorOS, uio_uint32 modeBytes) { + switch (creatorOS) { + case zip_OSType_FAT: + case zip_OSType_NTFS: + case zip_OSType_VFAT: { + // Only the least signigicant byte is relevant. + mode_t mode; + + if (modeBytes == 0) { + // File came from standard input + return S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | + S_IROTH | S_IWOTH; + } + if (modeBytes & 0x10) { + // Directory + mode = S_IFDIR | S_IRUSR | S_IXUSR | S_IRGRP | S_IXGRP | + S_IROTH | S_IXOTH; + } else { + // Regular file + mode = S_IFREG | S_IRUSR | S_IRGRP | S_IROTH; + } + if (modeBytes & 0x01) { + // readonly + return mode; + } else { + // Write allowed + return mode | S_IWUSR | S_IWGRP | S_IWOTH; + } + } + case zip_OSType_UNIX: + return (mode_t) (modeBytes >> 16); + default: + fprintf(stderr, "Warning: file created by unknown OS.\n"); + return S_IFREG | S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + } +} + +// If the data is read from the central directory, certain data +// will need to be updated from the local directory when it is needed. +// This function does that. +// returns 0 for success, -1 for error (errno is set) +// NB: Only the fields that may offer new information are checked. +// the fields that were already read from the central directory +// aren't verified. +static int +zip_updatePFileDataFromLocalFileHeader(zip_GPFileData *gPFileData, + uio_FileBlock *fileBlock, int pos) { + ssize_t numBytes; + char *buf; + uio_uint32 signature; + uio_uint16 fileNameLength; + uio_uint16 extraFieldLength; + + numBytes = uio_accessFileBlock(fileBlock, pos, 30, &buf); + if (numBytes != 30) { + errno = EIO; + return -1; + } + signature = makeUInt32(buf[0], buf[1], buf[2], buf[3]); + if (signature != 0x04034b50) { + errno = EIO; + return -1; + } + fileNameLength = makeUInt16(buf[26], buf[27]); + extraFieldLength = makeUInt16(buf[28], buf[29]); + pos += 30 + fileNameLength; + + switch (zip_fillDirStructureProcessExtraFields(fileBlock, + extraFieldLength, gPFileData, "", pos, false)) { + case 0: // file is ok + break; + case 1: + // File is not acceptable (but according to the central header + // it was) + fprintf(stderr, "Warning: according to the central directory " + "of a zip file, some file inside is acceptable, " + "but according to the local header it isn't.\n"); + errno = EIO; + return -1; + case -1: + errno = EIO; + return -1; + } + pos += extraFieldLength; + gPFileData->fileOffset = pos; + return 0; +} +#endif /* zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS */ + +#if zip_USE_HEADERS == zip_USE_LOCAL_HEADERS +static int +zip_fillDirStructureLocal(uio_GPDir *top, uio_Handle *handle) { + uio_FileBlock *fileBlock; + off_t pos; + char *buf; + ssize_t numBytes; + + pos = uio_lseek(handle, 0, SEEK_SET); + if (pos == -1) { + int savedErrno = errno; + errno = savedErrno; + return -1; + } + if (pos != 0) { + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + + // We read all the files from the beginning of the zip file to the end. + // (the directory record at the end of the file is ignored) + fileBlock = uio_openFileBlock(handle); + if (fileBlock == NULL) { + // errno is set + return -1; + } + + pos = 0; + while (1) { + uio_uint32 signature; + + numBytes = uio_accessFileBlock(fileBlock, pos, 4, &buf); + if (numBytes == -1) + goto err; + if (numBytes != 4) + break; + signature = makeUInt32(buf[0], buf[1], buf[2], buf[3]); + if (signature != 0x04034b50) { + // End of file data reached. + break; + } + pos += 4; + if (zip_fillDirStructureLocalProcessEntry(top, fileBlock, &pos) == -1) + goto err; + } + + uio_closeFileBlock(fileBlock); + return 0; + +err: + { + int savedErrno = errno; + + uio_closeFileBlock(fileBlock); + errno = savedErrno; + return -1; + } +} + +static int +zip_fillDirStructureLocalProcessEntry(uio_GPDir *topGPDir, + uio_FileBlock *fileBlock, off_t *pos) { + char *buf; + zip_GPFileData *gPFileData; + ssize_t numBytes; + + uio_uint16 lastModTime; + uio_uint16 lastModDate; + uio_uint32 crc; + uio_uint16 fileNameLength; + uio_uint16 extraFieldLength; + char *fileName; + + off_t nextEntryOffset; + + numBytes = uio_accessFileBlock(fileBlock, *pos, 26, &buf); + if (numBytes != 26) + return zip_badFile(NULL, NULL); + + gPFileData = zip_GPFileData_new(); + gPFileData->compressionFlags = makeUInt16(buf[2], buf[3]); + gPFileData->compressionMethod = makeUInt16(buf[4], buf[5]); + lastModTime = makeUInt16(buf[6], buf[7]); + lastModDate = makeUInt16(buf[8], buf[9]); + gPFileData->atime = (time_t) 0; + gPFileData->mtime = dosToUnixTime(lastModDate, lastModTime); + gPFileData->ctime = (time_t) 0; + gPFileData->uid = 0; + gPFileData->gid = 0; + gPFileData->mode = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; + if (!isBitSet(gPFileData->compressionFlags, 3)) { + // If bit 3 is not set, this info will be in the data descriptor + // behind the file data. + crc = makeUInt32(buf[10], buf[11], buf[12], buf[13]); + gPFileData->compressedSize = + makeUInt32(buf[14], buf[15], buf[16], buf[17]); + gPFileData->uncompressedSize = + makeUInt32(buf[18], buf[19], buf[20], buf[21]); + } + fileNameLength = makeUInt16(buf[22], buf[23]); + extraFieldLength = makeUInt16(buf[24], buf[25]); + *pos += 26; + nextEntryOffset = *pos + fileNameLength + extraFieldLength + + gPFileData->compressedSize; + if (isBitSet(gPFileData->compressionFlags, 3)) { + // There's a data descriptor present behind the file data. + nextEntryOffset += 16; + } + + if (gPFileData->compressionMethod >= NUM_COMPRESSION_METHODS || + !zip_compressionMethodSupported[gPFileData->compressionMethod]) { + fprintf(stderr, "Warning: File '%s' is compressed with " + "unsupported method %d - skipped.\n", fileName, + gPFileData->compressionMethod); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + if (gPFileData->compressedSize == (off_t) 0xffffffff || + gPFileData->uncompressedSize == (off_t) 0xffffffff) { + fprintf(stderr, "Warning: Skipping Zip64 file '%s'\n", fileName); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + if (isBitSet(gPFileData->compressionFlags, 0)) { + fprintf(stderr, "Warning: Skipping encrypted file '%s'\n", fileName); + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + return 0; + } + + numBytes = uio_accessFileBlock(fileBlock, *pos, fileNameLength, &buf); + if (numBytes != fileNameLength) + return zip_badFile(gPFileData, NULL); + *pos += fileNameLength; + if (buf[fileNameLength - 1] == '/') { + gPFileData->mode |= S_IFDIR; + fileNameLength--; + } else + gPFileData->mode |= S_IFREG; + fileName = uio_malloc(fileNameLength + 1); + memcpy(fileName, buf, fileNameLength); + fileName[fileNameLength] = '\0'; + + switch (zip_fillDirStructureProcessExtraFields(fileBlock, + extraFieldLength, gPFileData, fileName, *pos, false)) { + case 0: // file is ok + break; + case 1: // file is not acceptable - skip file + *pos = nextEntryOffset; + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + case -1: + return zip_badFile(gPFileData, fileName); + } + *pos += extraFieldLength; + + gPFileData->fileOffset = *pos; + + *pos += gPFileData->compressedSize; + if (isBitSet(gPFileData->compressionFlags, 3)) { + // Now comes a data descriptor. + // The PKWare version (which was never used) misses the signature. + // The InfoZip version is used below. + uio_uint32 signature; + + numBytes = uio_accessFileBlock(fileBlock, *pos, 16, &buf); + if (numBytes != 16) + return zip_badFile(gPFileData, fileName); + + signature = makeUInt32(buf[0], buf[1], buf[2], buf[3]); + if (signature != 0x08074b50) + return zip_badFile(gPFileData, fileName); + crc = makeUInt32(buf[4], buf[5], buf[6], buf[7]); + gPFileData->compressedSize = + makeUInt32(buf[8], buf[9], buf[10], buf[11]); + gPFileData->uncompressedSize = + makeUInt32(buf[12], buf[13], buf[14], buf[15]); + } + + if (gPFileData->ctime == (time_t) 0) + gPFileData->ctime = gPFileData->mtime; + + if (gPFileData->atime == (time_t) 0) + gPFileData->atime = gPFileData->mtime; + + if (S_ISREG(gPFileData->mode)) { + if (zip_foundFile(topGPDir, fileName, gPFileData) == -1) { + if (errno == EISDIR) { + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + return zip_badFile(gPFileData, fileName); + } + +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "Debug: Found file '%s'.\n", fileName); +#endif + } else if (S_ISDIR(gPFileData->mode)) { + if (fileName[fileNameLength - 1] == '/') + fileName[fileNameLength - 1] = '\0'; + if (zip_foundDir(topGPDir, fileName, gPFileData) == -1) { + if (errno == EISDIR) { + fprintf(stderr, "Warning: file '%s' already exists as a dir - " + "skipped.\n", fileName); + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + return zip_badFile(gPFileData, fileName); + } +#if defined(DEBUG) && DEBUG > 1 + fprintf(stderr, "Debug: Found dir '%s'.\n", fileName); +#endif + } else { + fprintf(stderr, "Warning: '%s' is not a regular file, nor a " + "directory - skipped.\n", fileName); + zip_GPFileData_delete(gPFileData); + uio_free(fileName); + return 0; + } + + uio_free(fileName); + return 0; +} +#endif /* zip_USE_HEADERS == zip_USE_LOCAL_HEADERS */ + +#if zip_USE_HEADERS == zip_USE_CENTRAL_HEADERS +int +zip_updateFileDataFromLocalHeader(uio_Handle *handle, + zip_GPFileData *gPFileData) { + uio_FileBlock *fileBlock; + + fileBlock = uio_openFileBlock(handle); + if (fileBlock == NULL) { + // errno is set + return -1; + } + if (zip_updatePFileDataFromLocalFileHeader(gPFileData, + fileBlock, gPFileData->headerOffset) == -1) { + int savedErrno = errno; + uio_closeFileBlock(fileBlock); + errno = savedErrno; + return -1; + } + if (gPFileData->ctime == (time_t) 0) + gPFileData->ctime = gPFileData->mtime; + if (gPFileData->atime == (time_t) 0) + gPFileData->atime = gPFileData->mtime; + uio_closeFileBlock(fileBlock); + return 0; +} +#endif + +// If the zip file is bad, -1 is returned (no errno set!) +// If the file in the zip file should be skipped, 1 is returned. +// If the file in the zip file is ok, 0 is returned. +static int +zip_fillDirStructureProcessExtraFields(uio_FileBlock *fileBlock, + off_t extraFieldLength, zip_GPFileData *gPFileData, + const char *fileName, off_t pos, uio_bool central) { + off_t posEnd; + uio_uint16 headerID; + ssize_t dataSize; + ssize_t numBytes; + char *buf; + + posEnd = pos + extraFieldLength; + while (pos < posEnd) { + numBytes = uio_accessFileBlock(fileBlock, pos, 4, &buf); + if (numBytes != 4) + return -1; + headerID = makeUInt16(buf[0], buf[1]); + dataSize = (ssize_t) makeUInt16(buf[2], buf[3]); + pos += 4; + numBytes = uio_accessFileBlock(fileBlock, pos, dataSize, &buf); + if (numBytes != dataSize) + return -1; + switch(headerID) { + case 0x000d: // 'Unix0' + // fallthrough + case 0x5855: // 'Unix1' + gPFileData->atime = (time_t) makeUInt32( + buf[0], buf[1], buf[2], buf[3]); + gPFileData->mtime = (time_t) makeUInt32( + buf[4], buf[5], buf[6], buf[7]); + if (central) + break; + if (dataSize > 8) { + gPFileData->uid = (uid_t) makeUInt16(buf[8], buf[9]); + gPFileData->gid = (uid_t) makeUInt16(buf[10], buf[11]); + } + // Unix0 has an extra (ignored) field at the end. + break; + case 0x5455: { // 'time' + uio_uint8 flags; + const char *bufPtr; + flags = buf[0]; + bufPtr = buf + 1; + if (isBitSet(flags, 0)) { + // modification time is present + gPFileData->mtime = (time_t) makeUInt32( + bufPtr[0], bufPtr[1], bufPtr[2], bufPtr[3]); + bufPtr += 4; + } + // The flags field, even in the central header, specifies what + // is present in the local header. + // The central header only contains a field for the mtime + // when it is present in the local header too, and + // never contains fields for other times. + if (central) + break; + if (isBitSet(flags, 1)) { + // modification time is present + gPFileData->atime = (time_t) makeUInt32( + bufPtr[0], bufPtr[1], bufPtr[2], bufPtr[3]); + bufPtr += 4; + } + // Creation time and possible other times are skipped. + break; + } + case 0x7855: // 'Unix2' + if (central) + break; + gPFileData->uid = (uid_t) makeUInt16(buf[0], buf[1]); + gPFileData->gid = (uid_t) makeUInt16(buf[2], buf[3]); + break; + case 0x756e: { // 'Unix3' + mode_t mode; + mode = (mode_t) makeUInt16(buf[4], buf[5]); + if (!S_ISREG(mode) && !S_ISDIR(mode)) { + fprintf(stderr, "Warning: Skipping '%s'; not a regular " + "file, nor a directory.\n", fileName); + return 1; + } + gPFileData->uid = (uid_t) makeUInt16(buf[10], buf[11]); + gPFileData->gid = (uid_t) makeUInt16(buf[12], buf[13]); + break; + } + default: +#ifdef DEBUG + fprintf(stderr, "Debug: Extra field 0x%04x unsupported, " + "used for file '%s' - ignored.\n", headerID, + fileName); +#endif + break; + } // switch + pos += dataSize; + } // while + if (pos != posEnd) + return -1; + return 0; +} + +static int +zip_badFile(zip_GPFileData *gPFileData, char *fileName) { + fprintf(stderr, "Error: Bad file format for .zip file.\n"); + if (gPFileData != NULL) + zip_GPFileData_delete(gPFileData); + if (fileName != NULL) + uio_free(fileName); + errno = EINVAL; // Is this the best choice? + return -1; +} + +static inline int +zip_foundFile(uio_GPDir *gPDir, const char *path, zip_GPFileData *gPFileData) { + uio_GPFile *file; + size_t pathLen; + const char *rest; + const char *pathEnd; + const char *start, *end; + char *buf; + + if (path[0] == '/') + path++; + pathLen = strlen(path); + if (path[pathLen - 1] == '/') { + fprintf(stderr, "Warning: '%s' is not a valid file name - skipped.\n", + path); + errno = EISDIR; + return -1; + } + pathEnd = path + pathLen; + + switch (uio_walkGPPath(gPDir, path, pathLen, &gPDir, &rest)) { + case 0: + // The entire path was matched. The last part was not supposed + // to be a dir. + fprintf(stderr, "Warning: '%s' already exists as a dir - " + "skipped.\n", path); + errno = EISDIR; + return -1; + case ENOTDIR: + fprintf(stderr, "Warning: A component to '%s' is not a " + "directory - file skipped.\n", path); + errno = ENOTDIR; + return -1; + case ENOENT: + break; + } + + buf = uio_malloc(pathLen + 1); + getFirstPathComponent(rest, pathEnd, &start, &end); + while (1) { + uio_GPDir *newGPDir; + + if (end == start || (end - start == 1 && start[0] == '.') || + (end - start == 2 && start[0] == '.' && start[1] == '.')) { + fprintf(stderr, "Warning: file '%s' has an invalid path - " + "skipped.\n", path); + uio_free(buf); + errno = EINVAL; + return -1; + } + if (end == pathEnd) { + // This is the last component; it should be the name of the dir. + rest = start; + break; + } + memcpy(buf, start, end - start); + buf[end - start] = '\0'; + newGPDir = uio_GPDir_prepareSubDir(gPDir, buf); + newGPDir->flags |= uio_GPDir_COMPLETE; + // It will be complete when we're done adding + // all files, and it won't be used before that. + uio_GPDir_commitSubDir(gPDir, buf, newGPDir); + + gPDir = newGPDir; + getNextPathComponent(pathEnd, &start, &end); + } + + uio_free(buf); + + file = uio_GPFile_new(gPDir->pRoot, (uio_GPFileExtra) gPFileData, + uio_gPFileFlagsFromPRootFlags(gPDir->pRoot->flags)); + uio_GPDir_addFile(gPDir, rest, file); + return 0; +} + +static inline int +zip_foundDir(uio_GPDir *gPDir, const char *path, zip_GPDirData *gPDirData) { + size_t pathLen; + const char *pathEnd; + const char *rest; + const char *start, *end; + char *buf; + + if (path[0] == '/') + path++; + pathLen = strlen(path); + pathEnd = path + pathLen; + + switch (uio_walkGPPath(gPDir, path, pathLen, &gPDir, &rest)) { + case 0: + // The dir already exists. Only need to add gPDirData + if (gPDir->extra != NULL) { + fprintf(stderr, "Warning: '%s' is present more than once " + "in the zip file. The last entry will be used.\n", + path); + zip_GPDirData_delete(gPDir->extra); + } + gPDir->extra = gPDirData; + return 0; + case ENOTDIR: + fprintf(stderr, "Warning: A component of '%s' is not a " + "directory - file skipped.\n", path); + errno = ENOTDIR; + return -1; + case ENOENT: + break; + } + + buf = uio_malloc(pathLen + 1); + getFirstPathComponent(rest, pathEnd, &start, &end); + while (start < pathEnd) { + uio_GPDir *newGPDir; + + if (end == start || (end - start == 1 && start[0] == '.') || + (end - start == 2 && start[0] == '.' && start[1] == '.')) { + fprintf(stderr, "Warning: directory '%s' has an invalid path - " + "skipped.\n", path); + uio_free(buf); + errno = EINVAL; + return -1; + } + memcpy(buf, start, end - start); + buf[end - start] = '\0'; + newGPDir = uio_GPDir_prepareSubDir(gPDir, buf); + newGPDir->flags |= uio_GPDir_COMPLETE; + // It will be complete when we're done adding + // all files, and it won't be used before that. + uio_GPDir_commitSubDir(gPDir, buf, newGPDir); + + gPDir = newGPDir; + getNextPathComponent(pathEnd, &start, &end); + } + gPDir->extra = gPDirData; + + uio_free(buf); + return 0; +} + +static int +zip_initZipStream(z_stream *zipStream) { + int retVal; + + zipStream->next_in = Z_NULL; + zipStream->avail_in = 0; + zipStream->zalloc = zip_alloc; + zipStream->zfree = zip_free; + zipStream->opaque = NULL; + retVal = inflateInit2(zipStream, -MAX_WBITS); + // Negative window size means that no zlib header is present. + // This feature is undocumented in zlib, but it's used + // in the minizip program from the zlib contrib dir. + // The absolute value is used as real Window size. + if (retVal != Z_OK) { + switch (retVal) { + case Z_MEM_ERROR: + fprintf(stderr, "Error: Not enough memory available for " + " decompression.\n"); + break; + case Z_VERSION_ERROR: + fprintf(stderr, "Error: Incompatible version problem for " + " decompression.\n"); + break; + default: + fprintf(stderr, "Fatal: unknown error from inflateInit().\n"); + abort(); + } + if (zipStream->msg != NULL) + fprintf(stderr, "ZLib reports: %s\n", zipStream->msg); + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + return 0; +} + +static int +zip_unInitZipStream(z_stream *zipStream) { + int retVal; + + retVal = inflateEnd(zipStream); + if (retVal != Z_OK) { + switch (retVal) { + case Z_STREAM_ERROR: + // This means zipStream is bad, which is most likely an + // error in the code using zlib. + fprintf(stderr, "Fatal: internal error using zlib.\n"); + abort(); + break; + default: + fprintf(stderr, "Fatal: unknown error from inflateEnd().\n"); + abort(); + } + if (zipStream->msg != NULL) + fprintf(stderr, "ZLib reports: %s\n", zipStream->msg); + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + return 0; +} + +static int +zip_reInitZipStream(z_stream *zipStream) { + int retVal; + + zipStream->next_in = Z_NULL; + zipStream->avail_in = 0; + retVal = inflateReset(zipStream); + if (retVal != Z_OK) { + switch (retVal) { + case Z_STREAM_ERROR: + // This means zipStream is bad, which is most likely an + // error in the code using zlib. + fprintf(stderr, "Fatal: internal error using zlib.\n"); + abort(); + break; + default: + fprintf(stderr, "Fatal: unknown error from inflateInit().\n"); + abort(); + } + if (zipStream->msg != NULL) + fprintf(stderr, "ZLib reports: %s\n", zipStream->msg); + errno = EIO; + // Using EIO to report an error in the backend. + return -1; + } + return 0; +} + + +// Used internally by zlib for allocating memory. +static voidpf +zip_alloc(voidpf opaque, uInt items, uInt size) { + (void) opaque; + return (voidpf) uio_calloc((size_t) items, (size_t) size); +} + +// Used internally by zlib for freeing memory. +static void +zip_free(voidpf opaque, voidpf address) { + (void) opaque; + uio_free((void *) address); +} + +static inline zip_GPFileData * +zip_GPFileData_new(void) { + return zip_GPFileData_alloc(); +} + +static inline void +zip_GPFileData_delete(zip_GPFileData *gPFileData) { + zip_GPFileData_free(gPFileData); +} + +static inline zip_GPFileData * +zip_GPFileData_alloc(void) { + zip_GPFileData *result = uio_malloc(sizeof (zip_GPFileData)); +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugAlloc(zip_GPFileData, (void *) result); +#endif + return result; +} + +static inline void +zip_GPFileData_free(zip_GPFileData *gPFileData) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(zip_GPFileData, (void *) gPFileData); +#endif + uio_free(gPFileData); +} + +static inline void +zip_GPDirData_delete(zip_GPDirData *gPDirData) { + zip_GPDirData_free(gPDirData); +} + +static inline void +zip_GPDirData_free(zip_GPDirData *gPDirData) { +#ifdef uio_MEM_DEBUG + uio_MemDebug_debugFree(zip_GPFileData, (void *) gPDirData); +#endif + uio_free(gPDirData); +} + + + -- cgit v1.2.3