@@ -148,26 +148,12 @@ *size -= 8; return field; } -static void -seekOrThrowInvalidFormat(OFSeekableStream *stream, - OFStreamOffset offset, OFSeekWhence whence) -{ - @try { - [stream seekToOffset: offset whence: whence]; - } @catch (OFSeekFailedException *e) { - if (e.errNo == EINVAL) - @throw [OFInvalidFormatException exception]; - - @throw e; - } -} - @implementation OFZIPArchive -@synthesize archiveComment = _archiveComment; +@synthesize delegate = _delegate, archiveComment = _archiveComment; + (instancetype)archiveWithStream: (OFStream *)stream mode: (OFString *)mode { return [[[self alloc] initWithStream: stream mode: mode] autorelease]; } @@ -213,11 +199,11 @@ [self of_readEntries]; } if (_mode == modeAppend) { _offset = _centralDirectoryOffset; - seekOrThrowInvalidFormat(_stream, + seekOrThrowInvalidFormat(self, NULL, (OFStreamOffset)_offset, OFSeekSet); } } @catch (id e) { /* * If we are in write or append mode, we do not want -[close] @@ -266,20 +252,49 @@ [_entries release]; [_pathToEntryMap release]; [super dealloc]; } + +static void +seekOrThrowInvalidFormat(OFZIPArchive *archive, const uint32_t *diskNumber, + OFStreamOffset offset, OFSeekWhence whence) +{ + if (diskNumber != NULL && *diskNumber != archive->_diskNumber) { + OFStream *oldStream = archive->_stream; + OFSeekableStream *stream = + [archive->_delegate archive: archive + wantsPartNumbered: *diskNumber + totalNumberOfParts: archive->_numDisks]; + + if (stream == nil) + @throw [OFInvalidFormatException exception]; + + archive->_diskNumber = *diskNumber; + archive->_stream = [stream retain]; + [oldStream release]; + } + + @try { + [archive->_stream seekToOffset: offset whence: whence]; + } @catch (OFSeekFailedException *e) { + if (e.errNo == EINVAL) + @throw [OFInvalidFormatException exception]; + + @throw e; + } +} - (void)of_readZIPInfo { void *pool = objc_autoreleasePoolPush(); uint16_t commentLength; OFStreamOffset offset = -22; bool valid = false; do { - seekOrThrowInvalidFormat(_stream, offset, OFSeekEnd); + seekOrThrowInvalidFormat(self, NULL, offset, OFSeekEnd); if ([_stream readLittleEndianInt32] == 0x06054B50) { valid = true; break; } @@ -286,11 +301,11 @@ } while (--offset >= -65557); if (!valid) @throw [OFInvalidFormatException exception]; - _diskNumber = [_stream readLittleEndianInt16]; + _diskNumber = _numDisks = [_stream readLittleEndianInt16]; _centralDirectoryDisk = [_stream readLittleEndianInt16]; _centralDirectoryEntriesInDisk = [_stream readLittleEndianInt16]; _centralDirectoryEntries = [_stream readLittleEndianInt16]; _centralDirectorySize = [_stream readLittleEndianInt32]; _centralDirectoryOffset = [_stream readLittleEndianInt32]; @@ -298,20 +313,21 @@ commentLength = [_stream readLittleEndianInt16]; _archiveComment = [[_stream readStringWithLength: commentLength encoding: OFStringEncodingCodepage437] copy]; - if (_diskNumber == 0xFFFF || + if (_numDisks == 0xFFFF || _centralDirectoryDisk == 0xFFFF || _centralDirectoryEntriesInDisk == 0xFFFF || _centralDirectoryEntries == 0xFFFF || _centralDirectorySize == 0xFFFFFFFF || _centralDirectoryOffset == 0xFFFFFFFF) { + uint32_t diskNumber; int64_t offset64; uint64_t size; - seekOrThrowInvalidFormat(_stream, offset - 20, OFSeekEnd); + seekOrThrowInvalidFormat(self, NULL, offset - 20, OFSeekEnd); if ([_stream readLittleEndianInt32] != 0x07064B50) { objc_autoreleasePoolPop(pool); return; } @@ -318,17 +334,18 @@ /* * FIXME: Handle number of the disk containing ZIP64 end of * central directory record. */ - [_stream readLittleEndianInt32]; + diskNumber = [_stream readLittleEndianInt32]; offset64 = [_stream readLittleEndianInt64]; + _numDisks = [_stream readLittleEndianInt32]; if (offset64 < 0 || (OFStreamOffset)offset64 != offset64) @throw [OFOutOfRangeException exception]; - seekOrThrowInvalidFormat(_stream, + seekOrThrowInvalidFormat(self, &diskNumber, (OFStreamOffset)offset64, OFSeekSet); if ([_stream readLittleEndianInt32] != 0x06064B50) @throw [OFInvalidFormatException exception]; @@ -339,11 +356,13 @@ /* version made by */ [_stream readLittleEndianInt16]; /* version needed to extract */ [_stream readLittleEndianInt16]; - _diskNumber = [_stream readLittleEndianInt32]; + if ([_stream readLittleEndianInt32] != _diskNumber) + @throw [OFInvalidFormatException exception]; + _centralDirectoryDisk = [_stream readLittleEndianInt32]; _centralDirectoryEntriesInDisk = [_stream readLittleEndianInt64]; _centralDirectoryEntries = [_stream readLittleEndianInt64]; _centralDirectorySize = [_stream readLittleEndianInt64]; @@ -364,11 +383,11 @@ if (_centralDirectoryOffset < 0 || (OFStreamOffset)_centralDirectoryOffset != _centralDirectoryOffset) @throw [OFOutOfRangeException exception]; - seekOrThrowInvalidFormat(_stream, + seekOrThrowInvalidFormat(self, &_centralDirectoryDisk, (OFStreamOffset)_centralDirectoryOffset, OFSeekSet); for (size_t i = 0; i < _centralDirectoryEntries; i++) { OFZIPArchiveEntry *entry = [[[OFZIPArchiveEntry alloc] of_initWithStream: _stream] autorelease]; @@ -411,10 +430,11 @@ - (OFStream *)streamForReadingFile: (OFString *)path { void *pool = objc_autoreleasePoolPush(); OFZIPArchiveEntry *entry; OFZIPArchiveLocalFileHeader *localFileHeader; + uint32_t startDiskNumber; int64_t offset64; if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; @@ -431,15 +451,17 @@ } @catch (OFNotOpenException *e) { /* Might have already been closed by the user - that's fine. */ } _lastReturnedStream = nil; + startDiskNumber = entry.of_startDiskNumber; offset64 = entry.of_localFileHeaderOffset; if (offset64 < 0 || (OFStreamOffset)offset64 != offset64) @throw [OFOutOfRangeException exception]; - seekOrThrowInvalidFormat(_stream, (OFStreamOffset)offset64, OFSeekSet); + seekOrThrowInvalidFormat(self, &startDiskNumber, + (OFStreamOffset)offset64, OFSeekSet); localFileHeader = [[[OFZIPArchiveLocalFileHeader alloc] initWithStream: _stream] autorelease]; if (![localFileHeader matchesEntry: entry]) @throw [OFInvalidFormatException exception];