Index: src/OFZIPArchive.h ================================================================== --- src/OFZIPArchive.h +++ src/OFZIPArchive.h @@ -28,13 +28,13 @@ */ @interface OFZIPArchive: OFObject { OFFile *_file; OFString *_path; - uint16_t _diskNumber, _centralDirectoryDisk; - uint16_t _centralDirectoryEntriesInDisk, _centralDirectoryEntries; - uint32_t _centralDirectorySize, _centralDirectoryOffset; + uint32_t _diskNumber, _centralDirectoryDisk; + uint64_t _centralDirectoryEntriesInDisk, _centralDirectoryEntries; + uint64_t _centralDirectorySize, _centralDirectoryOffset; OFString *_archiveComment; OFMutableArray *_entries; OFMutableDictionary *_pathToEntryMap; } Index: src/OFZIPArchive.m ================================================================== --- src/OFZIPArchive.m +++ src/OFZIPArchive.m @@ -14,12 +14,10 @@ * file. */ #include "config.h" -#include - #import "OFZIPArchive.h" #import "OFZIPArchiveEntry.h" #import "OFZIPArchiveEntry+Private.h" #import "OFDataArray.h" #import "OFArray.h" @@ -30,10 +28,11 @@ #import "OFChecksumFailedException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFNotImplementedException.h" #import "OFOpenFileFailedException.h" +#import "OFOutOfRangeException.h" #import "OFReadFailedException.h" #import "OFUnsupportedVersionException.h" #import "autorelease.h" #import "macros.h" @@ -43,11 +42,10 @@ /* * FIXME: Current limitations: * - Split archives are not supported. * - Write support is missing. * - The ZIP has to be a file on the local file system. - * - No support for ZIP64. * - Encrypted files cannot be read. */ @interface OFZIPArchive (OF_PRIVATE_CATEGORY) - (void)OF_readZIPInfo; @@ -57,11 +55,12 @@ @interface OFZIPArchive_LocalFileHeader: OFObject { @public uint16_t _minVersion, _generalPurposeBitFlag, _compressionMethod; uint16_t _lastModifiedFileTime, _lastModifiedFileDate; - uint32_t _CRC32, _compressedSize, _uncompressedSize; + uint32_t _CRC32; + uint64_t _compressedSize, _uncompressedSize; OFString *_fileName; OFDataArray *_extraField; } - initWithFile: (OFFile*)file; @@ -81,10 +80,81 @@ - initWithArchiveFile: (OFString*)path offset: (off_t)offset localFileHeader: (OFZIPArchive_LocalFileHeader*)localFileHeader; @end + +void +of_zip_archive_find_extra_field(OFDataArray *extraField, uint16_t tag, + uint8_t **data, uint16_t *size) +{ + uint8_t *bytes; + size_t i, count; + + bytes = [extraField items]; + count = [extraField count]; + + for (i = 0; i < count;) { + uint16_t currentTag, currentSize; + + if (i + 3 >= count) + @throw [OFInvalidFormatException exception]; + + currentTag = (bytes[i + 1] << 8) | bytes[i]; + currentSize = (bytes[i + 3] << 8) | bytes[i + 2]; + + if (i + 3 + currentSize >= count) + @throw [OFInvalidFormatException exception]; + + if (currentTag == tag) { + *data = bytes + i + 4; + *size = currentSize; + return; + } + + i += 4 + currentSize; + } + + *data = NULL; + *size = 0; +} + +uint32_t +of_zip_archive_read_field32(uint8_t **data, uint16_t *size) +{ + uint32_t field = 0; + uint_fast8_t i; + + if (*size < 4) + @throw [OFInvalidFormatException exception]; + + for (i = 0; i < 4; i++) + field |= (uint32_t)(*data)[i] << (i * 8); + + *data += 4; + *size -= 4; + + return field; +} + +uint64_t +of_zip_archive_read_field64(uint8_t **data, uint16_t *size) +{ + uint64_t field = 0; + uint_fast8_t i; + + if (*size < 8) + @throw [OFInvalidFormatException exception]; + + for (i = 0; i < 8; i++) + field |= (uint64_t)(*data)[i] << (i * 8); + + *data += 8; + *size -= 8; + + return field; +} static uint32_t crc32(uint32_t crc, uint8_t *bytes, size_t length) { size_t i; @@ -151,24 +221,22 @@ - (void)OF_readZIPInfo { void *pool = objc_autoreleasePoolPush(); uint16_t commentLength; - size_t offset = 0; + off_t offset = -22; bool valid = false; - [_file seekToOffset: -22 - whence: SEEK_END]; + do { + [_file seekToOffset: offset + whence: SEEK_END]; - while (offset++ < 65536) { if ([_file readLittleEndianInt32] == 0x06054B50) { valid = true; break; - } else - [_file seekToOffset: -5 - whence: SEEK_CUR]; - } + } + } while (--offset >= -65557); if (!valid) @throw [OFInvalidFormatException exception]; _diskNumber = [_file readLittleEndianInt16], @@ -180,18 +248,73 @@ commentLength = [_file readLittleEndianInt16]; _archiveComment = [[_file readStringWithLength: commentLength encoding: OF_STRING_ENCODING_CODEPAGE_437] copy]; + + if (_diskNumber == 0xFFFF || + _centralDirectoryDisk == 0xFFFF || + _centralDirectoryEntriesInDisk == 0xFFFF || + _centralDirectoryEntries == 0xFFFF || + _centralDirectorySize == 0xFFFFFFFF || + _centralDirectoryOffset == 0xFFFFFFFF) { + uint64_t offset64, size; + + [_file seekToOffset: offset - 20 + whence: SEEK_END]; + + if ([_file readLittleEndianInt32] != 0x07064B50) { + objc_autoreleasePoolPop(pool); + return; + } + + /* + * FIXME: Handle number of the disk containing ZIP64 end of + * central directory record. + */ + [_file readLittleEndianInt32]; + offset64 = [_file readLittleEndianInt64]; + + if ((off_t)offset64 != offset64) + @throw [OFOutOfRangeException exception]; + + [_file seekToOffset: (off_t)offset64 + whence: SEEK_SET]; + + if ([_file readLittleEndianInt32] != 0x06064B50) + @throw [OFInvalidFormatException exception]; + + size = [_file readLittleEndianInt64]; + if (size < 44) + @throw [OFInvalidFormatException exception]; + + /* version made by */ + [_file readLittleEndianInt16]; + /* version needed to extract */ + [_file readLittleEndianInt16]; + + _diskNumber = [_file readLittleEndianInt32]; + _centralDirectoryDisk = [_file readLittleEndianInt32]; + _centralDirectoryEntriesInDisk = [_file readLittleEndianInt64]; + _centralDirectoryEntries = [_file readLittleEndianInt64]; + _centralDirectorySize = [_file readLittleEndianInt64]; + _centralDirectoryOffset = [_file readLittleEndianInt64]; + + if ((off_t)_centralDirectoryOffset != _centralDirectoryOffset) + @throw [OFOutOfRangeException exception]; + } objc_autoreleasePoolPop(pool); } - (void)OF_readEntries { void *pool = objc_autoreleasePoolPush(); size_t i; + + if ((off_t)_centralDirectoryOffset != _centralDirectoryOffset) + @throw [OFOutOfRangeException exception]; [_file seekToOffset: _centralDirectoryOffset whence: SEEK_SET]; _entries = [[OFMutableArray alloc] init]; @@ -230,26 +353,31 @@ { OFStream *ret; void *pool = objc_autoreleasePoolPush(); OFZIPArchiveEntry *entry = [_pathToEntryMap objectForKey: path]; OFZIPArchive_LocalFileHeader *localFileHeader; + uint64_t offset; if (entry == nil) { errno = ENOENT; @throw [OFOpenFileFailedException exceptionWithPath: path mode: @"rb"]; } - [_file seekToOffset: [entry OF_localFileHeaderOffset] + offset = [entry OF_localFileHeaderOffset]; + if ((off_t)offset != offset) + @throw [OFOutOfRangeException exception]; + + [_file seekToOffset: offset whence: SEEK_SET]; localFileHeader = [[[OFZIPArchive_LocalFileHeader alloc] initWithFile: _file] autorelease]; if (![localFileHeader matchesEntry: entry]) @throw [OFInvalidFormatException exception]; - if ((localFileHeader->_minVersion & 0xFF) > 20) { + if ((localFileHeader->_minVersion & 0xFF) > 45) { OFString *version = [OFString stringWithFormat: @"%u.%u", (localFileHeader->_minVersion & 0xFF) / 10, (localFileHeader->_minVersion & 0xFF) % 10]; @throw [OFUnsupportedVersionException @@ -274,10 +402,12 @@ self = [super init]; @try { uint16_t fileNameLength, extraFieldLength; of_string_encoding_t encoding; + uint8_t *ZIP64; + uint16_t ZIP64Size; if ([file readLittleEndianInt32] != 0x04034B50) @throw [OFInvalidFormatException exception]; _minVersion = [file readLittleEndianInt16]; @@ -296,10 +426,25 @@ _fileName = [[file readStringWithLength: fileNameLength encoding: encoding] copy]; _extraField = [[file readDataArrayWithCount: extraFieldLength] retain]; + + of_zip_archive_find_extra_field(_extraField, 0x0001, + &ZIP64, &ZIP64Size); + + if (ZIP64 != NULL) { + if (_uncompressedSize == 0xFFFFFFFF) + _uncompressedSize = of_zip_archive_read_field64( + &ZIP64, &ZIP64Size); + if (_compressedSize == 0xFFFFFFFF) + _compressedSize = of_zip_archive_read_field64( + &ZIP64, &ZIP64Size); + + if (ZIP64Size > 0) + @throw [OFInvalidFormatException exception]; + } } @catch (id e) { [self release]; @throw e; } @@ -410,10 +555,15 @@ CRC32 = [_file readLittleEndianInt32]; if (~_CRC32 != CRC32) @throw [OFChecksumFailedException exception]; + /* + * FIXME: Check (un)compressed length! + * (Note: Both are 64 bit if the entry uses ZIP64!) + */ + return 0; } ret = [_stream readIntoBuffer: buffer length: length]; Index: src/OFZIPArchiveEntry+Private.h ================================================================== --- src/OFZIPArchiveEntry+Private.h +++ src/OFZIPArchiveEntry+Private.h @@ -28,7 +28,7 @@ - (uint16_t)OF_lastModifiedFileDate; - (OFDataArray*)OF_extraFieldNoCopy; - (uint16_t)OF_startDiskNumber; - (uint16_t)OF_internalAttributes; - (uint32_t)OF_externalAttributes; -- (uint32_t)OF_localFileHeaderOffset; +- (uint64_t)OF_localFileHeaderOffset; @end Index: src/OFZIPArchiveEntry.h ================================================================== --- src/OFZIPArchiveEntry.h +++ src/OFZIPArchiveEntry.h @@ -30,21 +30,24 @@ @interface OFZIPArchiveEntry: OFObject { uint16_t _madeWithVersion, _minVersion, _generalPurposeBitFlag; uint16_t _compressionMethod, _lastModifiedFileTime; uint16_t _lastModifiedFileDate; - uint32_t _CRC32, _compressedSize, _uncompressedSize; + uint32_t _CRC32; + uint64_t _compressedSize, _uncompressedSize; OFString *_fileName; OFDataArray *_extraField; OFString *_fileComment; - uint16_t _startDiskNumber, _internalAttributes; - uint32_t _externalAttributes, _localFileHeaderOffset; + uint32_t _startDiskNumber; + uint16_t _internalAttributes; + uint32_t _externalAttributes; + uint64_t _localFileHeaderOffset; } #ifdef OF_HAVE_PROPERTIES @property (readonly, copy) OFString *fileName, *fileComment; -@property (readonly) off_t compressedSize, uncompressedSize; +@property (readonly) uint64_t compressedSize, uncompressedSize; @property (readonly, retain) OFDate *modificationDate; @property (readonly) uint32_t CRC32; @property (readonly, copy) OFDataArray *extraField; #endif @@ -65,18 +68,18 @@ /*! * @brief Returns the compressed size of the entry's file. * * @return The compressed size of the entry's file */ -- (off_t)compressedSize; +- (uint64_t)compressedSize; /*! * @brief Returns the uncompressed size of the entry's file. * * @return The uncompressed size of the entry's file */ -- (off_t)uncompressedSize; +- (uint64_t)uncompressedSize; /*! * @brief Returns the last modification date of the entry's file. * * @return The last modification date of the entry's file Index: src/OFZIPArchiveEntry.m ================================================================== --- src/OFZIPArchiveEntry.m +++ src/OFZIPArchiveEntry.m @@ -27,19 +27,26 @@ #import "macros.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" +extern void of_zip_archive_find_extra_field(OFDataArray*, uint16_t, uint8_t**, + uint16_t*); +extern uint32_t of_zip_archive_read_field32(uint8_t**, uint16_t*); +extern uint64_t of_zip_archive_read_field64(uint8_t**, uint16_t*); + @implementation OFZIPArchiveEntry - (instancetype)OF_initWithFile: (OFFile*)file { self = [super init]; @try { void *pool = objc_autoreleasePoolPush(); uint16_t fileNameLength, extraFieldLength, fileCommentLength; of_string_encoding_t encoding; + uint8_t *ZIP64 = NULL; + uint16_t ZIP64Size; if ([file readLittleEndianInt32] != 0x02014B50) @throw [OFInvalidFormatException exception]; _madeWithVersion = [file readLittleEndianInt16]; @@ -67,10 +74,32 @@ encoding: encoding] copy]; _extraField = [[file readDataArrayWithCount: extraFieldLength] retain]; _fileComment = [[file readStringWithLength: fileCommentLength encoding: encoding] copy]; + + of_zip_archive_find_extra_field(_extraField, 0x0001, + &ZIP64, &ZIP64Size); + + if (ZIP64 != NULL) { + if (_uncompressedSize == 0xFFFFFFFF) + _uncompressedSize = of_zip_archive_read_field64( + &ZIP64, &ZIP64Size); + if (_compressedSize == 0xFFFFFFFF) + _compressedSize = of_zip_archive_read_field64( + &ZIP64, &ZIP64Size); + if (_localFileHeaderOffset == 0xFFFFFFFF) + _localFileHeaderOffset = + of_zip_archive_read_field64(&ZIP64, + &ZIP64Size); + if (_startDiskNumber == 0xFFFF) + _startDiskNumber = of_zip_archive_read_field32( + &ZIP64, &ZIP64Size); + + if (ZIP64Size > 0) + @throw [OFInvalidFormatException exception]; + } objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; @@ -96,16 +125,16 @@ - (OFString*)fileComment { OF_GETTER(_fileComment, true) } -- (off_t)compressedSize +- (uint64_t)compressedSize { return _compressedSize; } -- (off_t)uncompressedSize +- (uint64_t)uncompressedSize { return _uncompressedSize; } - (OFDate*)modificationDate @@ -216,11 +245,11 @@ - (uint32_t)OF_externalAttributes { return _externalAttributes; } -- (uint32_t)OF_localFileHeaderOffset +- (uint64_t)OF_localFileHeaderOffset { return _localFileHeaderOffset; } - (of_comparison_result_t)compare: (id)object