Index: src/OFZIPArchive.h ================================================================== --- src/OFZIPArchive.h +++ src/OFZIPArchive.h @@ -30,11 +30,11 @@ * @brief A class for accessing and manipulating ZIP files. */ OF_SUBCLASSING_RESTRICTED @interface OFZIPArchive: OFObject { - OFStream *_stream; + OF_KINDOF(OFStream *) _stream; #ifdef OF_ZIP_ARCHIVE_M @public #endif int64_t _offset; @protected Index: src/OFZIPArchive.m ================================================================== --- src/OFZIPArchive.m +++ src/OFZIPArchive.m @@ -99,20 +99,23 @@ OF_DIRECT_MEMBERS @interface OFZIPArchiveFileWriteStream: OFStream { OFZIPArchive *_archive; - OFStream *_stream; + OF_KINDOF(OFStream *) _stream; uint32_t _CRC32; + OFStreamOffset _CRC32Offset, _size64Offset; @public unsigned long long _bytesWritten; OFMutableZIPArchiveEntry *_entry; } - (instancetype)of_initWithArchive: (OFZIPArchive *)archive stream: (OFStream *)stream - entry: (OFMutableZIPArchiveEntry *)entry; + entry: (OFMutableZIPArchiveEntry *)entry + CRC32Offset: (OFStreamOffset)CRC32Offset + size64Offset: (OFStreamOffset)size64Offset; @end uint32_t OFZIPArchiveReadField32(const uint8_t **data, uint16_t *size) { @@ -210,11 +213,11 @@ [self of_readEntries]; } if (_mode == modeAppend) { _offset = _centralDirectoryOffset; - seekOrThrowInvalidFormat((OFSeekableStream *)_stream, + seekOrThrowInvalidFormat(_stream, (OFStreamOffset)_offset, OFSeekSet); } } @catch (id e) { /* * If we are in write or append mode, we do not want -[close] @@ -272,12 +275,11 @@ uint16_t commentLength; OFStreamOffset offset = -22; bool valid = false; do { - seekOrThrowInvalidFormat((OFSeekableStream *)_stream, - offset, OFSeekEnd); + seekOrThrowInvalidFormat(_stream, offset, OFSeekEnd); if ([_stream readLittleEndianInt32] == 0x06054B50) { valid = true; break; } @@ -305,12 +307,11 @@ _centralDirectorySize == 0xFFFFFFFF || _centralDirectoryOffset == 0xFFFFFFFF) { int64_t offset64; uint64_t size; - seekOrThrowInvalidFormat((OFSeekableStream *)_stream, - offset - 20, OFSeekEnd); + seekOrThrowInvalidFormat(_stream, offset - 20, OFSeekEnd); if ([_stream readLittleEndianInt32] != 0x07064B50) { objc_autoreleasePoolPop(pool); return; } @@ -323,11 +324,11 @@ offset64 = [_stream readLittleEndianInt64]; if (offset64 < 0 || (OFStreamOffset)offset64 != offset64) @throw [OFOutOfRangeException exception]; - seekOrThrowInvalidFormat((OFSeekableStream *)_stream, + seekOrThrowInvalidFormat(_stream, (OFStreamOffset)offset64, OFSeekSet); if ([_stream readLittleEndianInt32] != 0x06064B50) @throw [OFInvalidFormatException exception]; @@ -363,11 +364,11 @@ if (_centralDirectoryOffset < 0 || (OFStreamOffset)_centralDirectoryOffset != _centralDirectoryOffset) @throw [OFOutOfRangeException exception]; - seekOrThrowInvalidFormat((OFSeekableStream *)_stream, + seekOrThrowInvalidFormat(_stream, (OFStreamOffset)_centralDirectoryOffset, OFSeekSet); for (size_t i = 0; i < _centralDirectoryEntries; i++) { OFZIPArchiveEntry *entry = [[[OFZIPArchiveEntry alloc] of_initWithStream: _stream] autorelease]; @@ -434,12 +435,11 @@ offset64 = entry.of_localFileHeaderOffset; if (offset64 < 0 || (OFStreamOffset)offset64 != offset64) @throw [OFOutOfRangeException exception]; - seekOrThrowInvalidFormat((OFSeekableStream *)_stream, - (OFStreamOffset)offset64, OFSeekSet); + seekOrThrowInvalidFormat(_stream, (OFStreamOffset)offset64, OFSeekSet); localFileHeader = [[[OFZIPArchiveLocalFileHeader alloc] initWithStream: _stream] autorelease]; if (![localFileHeader matchesEntry: entry]) @throw [OFInvalidFormatException exception]; @@ -463,15 +463,16 @@ return _lastReturnedStream; } - (OFStream *)streamForWritingEntry: (OFZIPArchiveEntry *)entry_ { - /* TODO: Avoid data descriptor when _stream is an OFSeekableStream */ int64_t offsetAdd = 0; void *pool; OFMutableZIPArchiveEntry *entry; OFString *fileName; + bool seekable; + OFStreamOffset CRC32Offset = 0, size64Offset = 0; OFData *extraField; uint16_t fileNameLength, extraFieldLength; if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; @@ -505,25 +506,29 @@ extraFieldLength = extraField.count; if (UINT16_MAX - extraFieldLength < 20) @throw [OFOutOfRangeException exception]; + seekable = [_stream isKindOfClass: [OFSeekableStream class]]; + entry.versionMadeBy = (entry.versionMadeBy & 0xFF00) | 45; entry.minVersionNeeded = (entry.minVersionNeeded & 0xFF00) | 45; entry.compressedSize = 0; entry.uncompressedSize = 0; entry.CRC32 = 0; - entry.generalPurposeBitFlag |= (1u << 3) | (1u << 11); + entry.generalPurposeBitFlag |= (seekable ? 0 : (1u << 3)) | (1u << 11); entry.of_localFileHeaderOffset = _offset; [_stream writeLittleEndianInt32: 0x04034B50]; [_stream writeLittleEndianInt16: entry.minVersionNeeded]; [_stream writeLittleEndianInt16: entry.generalPurposeBitFlag]; [_stream writeLittleEndianInt16: entry.compressionMethod]; [_stream writeLittleEndianInt16: entry.of_lastModifiedFileTime]; [_stream writeLittleEndianInt16: entry.of_lastModifiedFileDate]; - /* We use the data descriptor */ + /* Written later or data descriptor used instead */ + if (seekable) + CRC32Offset = [_stream seekToOffset: 0 whence: OFSeekCurrent]; [_stream writeLittleEndianInt32: 0]; /* We use ZIP64 */ [_stream writeLittleEndianInt32: 0xFFFFFFFF]; [_stream writeLittleEndianInt32: 0xFFFFFFFF]; [_stream writeLittleEndianInt16: fileNameLength]; @@ -533,11 +538,13 @@ [_stream writeString: fileName]; offsetAdd += fileNameLength; [_stream writeLittleEndianInt16: OFZIPArchiveEntryExtraFieldTagZIP64]; [_stream writeLittleEndianInt16: 16]; - /* We use the data descriptor */ + /* Written later or data descriptor used instead */ + if (seekable) + size64Offset = [_stream seekToOffset: 0 whence: OFSeekCurrent]; [_stream writeLittleEndianInt64: 0]; [_stream writeLittleEndianInt64: 0]; offsetAdd += (2 * 2) + (2 * 8); if (extraField != nil) @@ -550,11 +557,13 @@ _offset += offsetAdd; _lastReturnedStream = [[OFZIPArchiveFileWriteStream alloc] of_initWithArchive: self stream: _stream - entry: entry]; + entry: entry + CRC32Offset: CRC32Offset + size64Offset: size64Offset]; objc_autoreleasePoolPop(pool); return [_lastReturnedStream autorelease]; } @@ -863,17 +872,21 @@ @implementation OFZIPArchiveFileWriteStream - (instancetype)of_initWithArchive: (OFZIPArchive *)archive stream: (OFStream *)stream entry: (OFMutableZIPArchiveEntry *)entry + CRC32Offset: (OFStreamOffset)CRC32Offset + size64Offset: (OFStreamOffset)size64Offset { self = [super init]; _archive = [archive retain]; _stream = [stream retain]; _entry = [entry retain]; _CRC32 = ~0; + _CRC32Offset = CRC32Offset; + _size64Offset = size64Offset; return self; } - (void)dealloc @@ -921,30 +934,48 @@ return length; } - (void)close { + bool seekable; + if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; if (_bytesWritten > UINT64_MAX) @throw [OFOutOfRangeException exception]; - [_stream writeLittleEndianInt32: 0x08074B50]; - [_stream writeLittleEndianInt32: _CRC32]; - [_stream writeLittleEndianInt64: (uint64_t)_bytesWritten]; - [_stream writeLittleEndianInt64: (uint64_t)_bytesWritten]; + seekable = [_stream isKindOfClass: [OFSeekableStream class]]; + + if (seekable) { + OFStreamOffset offset = [_stream seekToOffset: 0 + whence: OFSeekCurrent]; + + [_stream seekToOffset: _CRC32Offset whence: OFSeekSet]; + [_stream writeLittleEndianInt32: ~_CRC32]; + [_stream seekToOffset: _size64Offset whence: OFSeekSet]; + [_stream writeLittleEndianInt64: (uint64_t)_bytesWritten]; + [_stream writeLittleEndianInt64: (uint64_t)_bytesWritten]; + + [_stream seekToOffset: offset whence: OFSeekSet]; + } else { + [_stream writeLittleEndianInt32: 0x08074B50]; + [_stream writeLittleEndianInt32: ~_CRC32]; + [_stream writeLittleEndianInt64: (uint64_t)_bytesWritten]; + [_stream writeLittleEndianInt64: (uint64_t)_bytesWritten]; + } [_stream release]; _stream = nil; _entry.CRC32 = ~_CRC32; _entry.compressedSize = _bytesWritten; _entry.uncompressedSize = _bytesWritten; [_entry makeImmutable]; - _bytesWritten += (2 * 4 + 2 * 8); + if (!seekable) + _bytesWritten += (2 * 4 + 2 * 8); [_archive->_entries addObject: _entry]; [_archive->_pathToEntryMap setObject: _entry forKey: _entry.fileName]; if (ULLONG_MAX - _archive->_offset < _bytesWritten)