Index: src/OFInflate64Stream.h ================================================================== --- src/OFInflate64Stream.h +++ src/OFInflate64Stream.h @@ -68,10 +68,18 @@ } huffman; } _context; bool _inLastBlock, _atEndOfStream; } +/** + * @brief The underlying stream of the inflate stream. + * + * Setting this can be useful if the the data to be inflated is coming from + * multiple streams, such as split across multiple files. + */ +@property (retain, nonatomic) OFStream *underlyingStream; + /** * @brief Creates a new OFInflate64Stream with the specified underlying stream. * * @param stream The underlying stream to which compressed data is written or * from which compressed data is read Index: src/OFInflateStream.h ================================================================== --- src/OFInflateStream.h +++ src/OFInflateStream.h @@ -68,10 +68,18 @@ } huffman; } _context; bool _inLastBlock, _atEndOfStream; } +/** + * @brief The underlying stream of the inflate stream. + * + * Setting this can be useful if the the data to be inflated is coming from + * multiple streams, such as split across multiple files. + */ +@property (retain, nonatomic) OFStream *underlyingStream; + /** * @brief Creates a new OFInflateStream with the specified underlying stream. * * @param stream The underlying stream to which compressed data is written or * from which compressed data is read Index: src/OFInflateStream.m ================================================================== --- src/OFInflateStream.m +++ src/OFInflateStream.m @@ -98,10 +98,12 @@ 16, 17, 18, 0, 8, 7, 9, 6, 10, 5, 11, 4, 12, 3, 13, 2, 14, 1, 15 }; static OFHuffmanTree fixedLitLenTree, fixedDistTree; @implementation OFInflateStream +@synthesize underlyingStream = _stream; + static OF_INLINE bool tryReadBits(OFInflateStream *stream, uint16_t *bits, uint8_t count) { uint16_t ret = stream->_savedBits; Index: src/OFZIPArchive.h ================================================================== --- src/OFZIPArchive.h +++ src/OFZIPArchive.h @@ -37,16 +37,16 @@ * @brief A callback that is called when an @ref OFZIPArchive wants to read a * different archive part. * * @param archive The archive that wants to read another part * @param partNumber The number of the part the archive wants to read - * @param totalNumber The total number of parts of the archive + * @param lastPartNumber The number of the last archive part * @return The stream to read the needed part, or `nil` if no such part exists */ - (nullable OFSeekableStream *)archive: (OFZIPArchive *)archive wantsPartNumbered: (unsigned int)partNumber - totalNumberOfParts: (unsigned int)totalNumber; + lastPartNumber: (unsigned int)lastPartNumber; @end /** * @class OFZIPArchive OFZIPArchive.h ObjFW/OFZIPArchive.h * @@ -53,19 +53,20 @@ * @brief A class for accessing and manipulating ZIP files. */ OF_SUBCLASSING_RESTRICTED @interface OFZIPArchive: OFObject { - OFObject *_Nullable _delegate; - OF_KINDOF(OFStream *) _stream; #ifdef OF_ZIP_ARCHIVE_M @public #endif + OFObject *_Nullable _delegate; + OF_KINDOF(OFStream *) _stream; int64_t _offset; -@protected uint_least8_t _mode; - uint32_t _diskNumber, _numDisks, _centralDirectoryDisk; + uint32_t _diskNumber, _lastDiskNumber; +@protected + uint32_t _centralDirectoryDisk; uint64_t _centralDirectoryEntriesInDisk, _centralDirectoryEntries; uint64_t _centralDirectorySize; int64_t _centralDirectoryOffset; OFString *_Nullable _archiveComment; #ifdef OF_ZIP_ARCHIVE_M Index: src/OFZIPArchive.m ================================================================== --- src/OFZIPArchive.m +++ src/OFZIPArchive.m @@ -83,11 +83,12 @@ OF_DIRECT_MEMBERS @interface OFZIPArchiveFileReadStream: OFStream { OFZIPArchive *_archive; - OFStream *_stream, *_decompressedStream; + OFZIPArchiveEntryCompressionMethod _compressionMethod; + OF_KINDOF(OFStream *) _decompressedStream; OFZIPArchiveEntry *_entry; unsigned long long _toRead; uint32_t _CRC32; bool _atEndOfStream; } @@ -159,17 +160,18 @@ { if (diskNumber != NULL && *diskNumber != archive->_diskNumber) { OFStream *oldStream; OFSeekableStream *stream; - if (archive->_mode != modeRead) + if (archive->_mode != modeRead || + *diskNumber > archive->_lastDiskNumber) @throw [OFInvalidFormatException exception]; oldStream = archive->_stream; stream = [archive->_delegate archive: archive wantsPartNumbered: *diskNumber - totalNumberOfParts: archive->_numDisks]; + lastPartNumber: archive->_lastDiskNumber]; if (stream == nil) @throw [OFInvalidFormatException exception]; archive->_diskNumber = *diskNumber; @@ -306,11 +308,11 @@ } while (--offset >= -65557); if (!valid) @throw [OFInvalidFormatException exception]; - _diskNumber = _numDisks = [_stream readLittleEndianInt16]; + _diskNumber = _lastDiskNumber = [_stream readLittleEndianInt16]; _centralDirectoryDisk = [_stream readLittleEndianInt16]; _centralDirectoryEntriesInDisk = [_stream readLittleEndianInt16]; _centralDirectoryEntries = [_stream readLittleEndianInt16]; _centralDirectorySize = [_stream readLittleEndianInt32]; _centralDirectoryOffset = [_stream readLittleEndianInt32]; @@ -318,11 +320,11 @@ commentLength = [_stream readLittleEndianInt16]; _archiveComment = [[_stream readStringWithLength: commentLength encoding: OFStringEncodingCodepage437] copy]; - if (_numDisks == 0xFFFF || + if (_lastDiskNumber == 0xFFFF || _centralDirectoryDisk == 0xFFFF || _centralDirectoryEntriesInDisk == 0xFFFF || _centralDirectoryEntries == 0xFFFF || _centralDirectorySize == 0xFFFFFFFF || _centralDirectoryOffset == 0xFFFFFFFF) { @@ -341,11 +343,14 @@ * FIXME: Handle number of the disk containing ZIP64 end of * central directory record. */ diskNumber = [_stream readLittleEndianInt32]; offset64 = [_stream readLittleEndianInt64]; - _numDisks = [_stream readLittleEndianInt32]; + _lastDiskNumber = [_stream readLittleEndianInt32]; + if (_lastDiskNumber == 0) + @throw [OFInvalidFormatException exception]; + _lastDiskNumber--; if (offset64 < 0 || (OFStreamOffset)offset64 != offset64) @throw [OFOutOfRangeException exception]; seekOrThrowInvalidFormat(self, &diskNumber, @@ -770,23 +775,23 @@ { self = [super init]; @try { _archive = [archive retain]; - _stream = [stream retain]; + _compressionMethod = entry.compressionMethod; - switch (entry.compressionMethod) { + switch (_compressionMethod) { case OFZIPArchiveEntryCompressionMethodNone: - _decompressedStream = [stream retain]; + _decompressedStream = [_archive->_stream retain]; break; case OFZIPArchiveEntryCompressionMethodDeflate: _decompressedStream = [[OFInflateStream alloc] - initWithStream: stream]; + initWithStream: _archive->_stream]; break; case OFZIPArchiveEntryCompressionMethodDeflate64: _decompressedStream = [[OFInflate64Stream alloc] - initWithStream: stream]; + initWithStream: _archive->_stream]; break; default: @throw [OFNotImplementedException exceptionWithSelector: _cmd object: nil]; @@ -803,11 +808,11 @@ return self; } - (void)dealloc { - if (_stream != nil || _decompressedStream != nil) + if (_decompressedStream != nil) [self close]; [_entry release]; if (_archive->_lastReturnedStream == self) @@ -818,28 +823,64 @@ [super dealloc]; } - (bool)lowlevelIsAtEndOfStream { - if (_stream == nil) + if (_decompressedStream == nil) @throw [OFNotOpenException exceptionWithObject: self]; return _atEndOfStream; } - (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length { size_t ret; - if (_stream == nil) + if (_decompressedStream == nil) @throw [OFNotOpenException exceptionWithObject: self]; if (_atEndOfStream) return 0; - if (_stream.atEndOfStream && !_decompressedStream.hasDataInReadBuffer) - @throw [OFTruncatedDataException exception]; + if (_archive->_stream.atEndOfStream && + !_decompressedStream.hasDataInReadBuffer) { + OFStream *oldStream, *oldDecompressedStream; + OFSeekableStream *stream; + + if (_archive->_diskNumber >= _archive->_lastDiskNumber) + @throw [OFTruncatedDataException exception]; + + oldStream = _archive->_stream; + stream = [_archive->_delegate + archive: _archive + wantsPartNumbered: _archive->_diskNumber + 1 + lastPartNumber: _archive->_lastDiskNumber]; + + if (stream == nil) + @throw [OFInvalidFormatException exception]; + + _archive->_diskNumber++; + _archive->_stream = [stream retain]; + [oldStream release]; + + switch (_compressionMethod) { + case OFZIPArchiveEntryCompressionMethodNone: + oldDecompressedStream = _decompressedStream; + _decompressedStream = [_archive->_stream retain]; + [oldDecompressedStream release]; + break; + case OFZIPArchiveEntryCompressionMethodDeflate: + case OFZIPArchiveEntryCompressionMethodDeflate64: + [_decompressedStream + setUnderlyingStream: _archive->_stream]; + break; + default: + @throw [OFNotImplementedException + exceptionWithSelector: _cmd + object: nil]; + } + } #if SIZE_MAX >= UINT64_MAX if (length > UINT64_MAX) @throw [OFOutOfRangeException exception]; #endif @@ -882,16 +923,13 @@ .fileDescriptorForReading; } - (void)close { - if (_stream == nil || _decompressedStream == nil) + if (_decompressedStream == nil) @throw [OFNotOpenException exceptionWithObject: self]; - [_stream release]; - _stream = nil; - [_decompressedStream release]; _decompressedStream = nil; [super close]; } Index: utils/ofarc/ZIPArchive.m ================================================================== --- utils/ofarc/ZIPArchive.m +++ utils/ofarc/ZIPArchive.m @@ -122,11 +122,11 @@ [super dealloc]; } - (OFSeekableStream *)archive: (OFZIPArchive *)archive wantsPartNumbered: (unsigned int)partNumber - totalNumberOfParts: (unsigned int)totalNumber + lastPartNumber: (unsigned int)lastPartNumber { OFString *path; if ([_path.pathExtension caseInsensitiveCompare: @"zip"] != OFOrderedSame) @@ -133,11 +133,11 @@ return nil; if (partNumber > 98) return nil; - if (partNumber == totalNumber) + if (partNumber == lastPartNumber) path = _path; else path = [_path.stringByDeletingPathExtension stringByAppendingFormat: @".z%02u", partNumber + 1];