Index: src/OFMutableZIPArchiveEntry.h ================================================================== --- src/OFMutableZIPArchiveEntry.h +++ src/OFMutableZIPArchiveEntry.h @@ -38,10 +38,12 @@ @property OF_NULLABLE_PROPERTY (readwrite, nonatomic, copy) OFString *fileComment; /*! * The extra field of the entry. + * + * The item size *must* be 1! */ @property OF_NULLABLE_PROPERTY (readwrite, nonatomic, copy) OFData *extraField; /*! * The version which made the entry. Index: src/OFMutableZIPArchiveEntry.m ================================================================== --- src/OFMutableZIPArchiveEntry.m +++ src/OFMutableZIPArchiveEntry.m @@ -15,18 +15,23 @@ */ #include "config.h" #import "OFMutableZIPArchiveEntry.h" +#import "OFZIPArchiveEntry+Private.h" #import "OFString.h" #import "OFData.h" #import "OFDate.h" + +#import "OFInvalidArgumentException.h" +#import "OFOutOfRangeException.h" @implementation OFMutableZIPArchiveEntry @dynamic fileName, fileComment, extraField, versionMadeBy, minVersionNeeded; @dynamic modificationDate, compressionMethod, compressedSize, uncompressedSize; @dynamic CRC32, versionSpecificAttributes, generalPurposeBitFlag; +@dynamic of_localFileHeaderOffset; - copy { OFMutableZIPArchiveEntry *copy = [self mutableCopy]; @@ -35,27 +40,54 @@ return copy; } - (void)setFileName: (OFString *)fileName { - OFString *old = _fileName; + void *pool = objc_autoreleasePoolPush(); + OFString *old; + + if ([fileName UTF8StringLength] > UINT16_MAX) + @throw [OFOutOfRangeException exception]; + + old = _fileName; _fileName = [fileName copy]; [old release]; + + objc_autoreleasePoolPop(pool); } - (void)setFileComment: (OFString *)fileComment { - OFString *old = _fileComment; + void *pool = objc_autoreleasePoolPush(); + OFString *old; + + if ([fileComment UTF8StringLength] > UINT16_MAX) + @throw [OFOutOfRangeException exception]; + + old = _fileComment; _fileComment = [fileComment copy]; [old release]; + + objc_autoreleasePoolPop(pool); } - (void)setExtraField: (OFData *)extraField { - OFData *old = _extraField; + void *pool = objc_autoreleasePoolPush(); + OFData *old; + + if ([extraField itemSize] != 1) + @throw [OFInvalidArgumentException exception]; + + if ([extraField count] > UINT16_MAX) + @throw [OFOutOfRangeException exception]; + + old = _extraField; _extraField = [extraField copy]; [old release]; + + objc_autoreleasePoolPop(pool); } - (void)setVersionMadeBy: (uint16_t)versionMadeBy { _versionMadeBy = versionMadeBy; @@ -106,11 +138,19 @@ - (void)setGeneralPurposeBitFlag: (uint16_t)generalPurposeBitFlag { _generalPurposeBitFlag = generalPurposeBitFlag; } + +- (void)of_setLocalFileHeaderOffset: (int64_t)localFileHeaderOffset +{ + if (localFileHeaderOffset < 0) + @throw [OFInvalidArgumentException exception]; + + _localFileHeaderOffset = localFileHeaderOffset; +} - (void)makeImmutable { object_setClass(self, [OFZIPArchiveEntry class]); } @end Index: src/OFTarArchive.h ================================================================== --- src/OFTarArchive.h +++ src/OFTarArchive.h @@ -96,11 +96,11 @@ /*! * @brief Returns the next entry from the tar archive or `nil` if all entries * have been read. * - * This is only available in read mode. + * @note This is only available in read mode. * * @warning Calling @ref nextEntry will invalidate all streams returned by * @ref streamForReadingCurrentEntry or * @ref streamForWritingEntry:! Reading from or writing to an * invalidated stream will throw an @ref OFReadFailedException or @@ -111,17 +111,21 @@ */ - (OFTarArchiveEntry *)nextEntry; /*! * @brief Returns a stream for reading the current entry. + * + * @note This is only available in read mode. * * @return A stream for reading the current entry */ - (OFStream *)streamForReadingCurrentEntry; /*! * @brief Returns a stream for writing the specified entry. + * + * @note This is only available in write and append mode. * * @warning Calling @ref nextEntry will invalidate all streams returned by * @ref streamForReadingCurrentEntry or * @ref streamForWritingEntry:! Reading from or writing to an * invalidated stream will throw an @ref OFReadFailedException or Index: src/OFTarArchive.m ================================================================== --- src/OFTarArchive.m +++ src/OFTarArchive.m @@ -40,12 +40,12 @@ OFStream *_stream; uint64_t _toRead; bool _atEndOfStream; } -- initWithEntry: (OFTarArchiveEntry *)entry - stream: (OFStream *)stream; +- initWithStream: (OFStream *)stream + entry: (OFTarArchiveEntry *)entry; - (void)of_skip; @end @interface OFTarArchive_FileWriteStream: OFStream { @@ -52,12 +52,12 @@ OFTarArchiveEntry *_entry; OFStream *_stream; uint64_t _toWrite; } -- initWithEntry: (OFTarArchiveEntry *)entry - stream: (OFStream *)stream; +- initWithStream: (OFStream *)stream + entry: (OFTarArchiveEntry *)entry; @end static void stringToBuffer(unsigned char *buffer, OFString *string, size_t length) { @@ -210,12 +210,12 @@ entry = [[[OFTarArchiveEntry alloc] of_initWithHeader: buffer.c] autorelease]; _lastReturnedStream = [[OFTarArchive_FileReadStream alloc] - initWithEntry: entry - stream: _stream]; + initWithStream: _stream + entry: entry]; return entry; } - (OFStream *)streamForReadingCurrentEntry @@ -283,12 +283,12 @@ [_stream writeBuffer: buffer length: sizeof(buffer)]; _lastReturnedStream = [[OFTarArchive_FileWriteStream alloc] - initWithEntry: entry - stream: _stream]; + initWithStream: _stream + entry: entry]; objc_autoreleasePoolPop(pool); return [[_lastReturnedStream retain] autorelease]; } @@ -314,12 +314,12 @@ _stream = nil; } @end @implementation OFTarArchive_FileReadStream -- initWithEntry: (OFTarArchiveEntry *)entry - stream: (OFStream *)stream +- initWithStream: (OFStream *)stream + entry: (OFTarArchiveEntry *)entry { self = [super init]; @try { _entry = [entry copy]; @@ -417,12 +417,12 @@ exactLength: 512 - ((size_t)size % 512)]; } @end @implementation OFTarArchive_FileWriteStream -- initWithEntry: (OFTarArchiveEntry *)entry - stream: (OFStream *)stream +- initWithStream: (OFStream *)stream + entry: (OFTarArchiveEntry *)entry { self = [super init]; @try { _entry = [entry copy]; Index: src/OFZIPArchive.h ================================================================== --- src/OFZIPArchive.h +++ src/OFZIPArchive.h @@ -31,10 +31,11 @@ * @brief A class for accessing and manipulating ZIP files. */ @interface OFZIPArchive: OFObject { OF_KINDOF(OFStream *) _stream; + int64_t _offset; enum { OF_ZIP_ARCHIVE_MODE_READ, OF_ZIP_ARCHIVE_MODE_WRITE, OF_ZIP_ARCHIVE_MODE_APPEND } _mode; @@ -50,11 +51,11 @@ } /*! * The archive comment. */ -@property (readonly, nonatomic) OFString *archiveComment; +@property OF_NULLABLE_PROPERTY (nonatomic, copy) OFString *archiveComment; /*! * @brief Creates a new OFZIPArchive object with the specified stream. * * @param stream A stream from which the ZIP archive will be read. @@ -125,18 +126,48 @@ - (OFArray OF_GENERIC(OFZIPArchiveEntry *) *)entries; /*! * @brief Returns a stream for reading the specified file from the archive. * - * This method is only available in read and append mode. + * @note This method is only available in read mode. * * @warning Calling @ref streamForReadingFile: will invalidate all streams - * previously returned by @ref streamForReadingFile:! Reading from an - * invalidated stream will throw an @ref OFReadFailedException! + * previously returned by @ref streamForReadingFile: or + * @ref streamForWritingEntry:! Reading from or writing to an + * invalidated stream will throw an @ref OFReadFailedException or + * @ref OFWriteFailedException! * * @param path The path to the file inside the archive * @return A stream for reading the specified file form the archive */ - (OFStream *)streamForReadingFile: (OFString *)path; + +/*! + * @brief Returns a stream for writing the specified entry to the archive. + * + * @note This method is only available in write and append mode. + * + * @warning Calling @ref streamForWritingEntry: will invalidate all streams + * previously returned by @ref streamForReadingFile: or + * @ref streamForWritingEntry:! Reading from or writing to an + * invalidated stream will throw an @ref OFReadFailedException or + * @ref OFWriteFailedException! + * + * @param entry The entry to write to the archive.@n + * The following parts of the specified entry will be ignored: + * * The lower 8 bits of the version made by. + * * The lower 8 bits of the minimum version needed. + * * The compressed size. + * * The uncompressed size. + * * The CRC32. + * * Bit 3 and 11 of the general purpose bit flag. + * @return A stream for writing the specified entry to the archive + */ +- (OFStream *)streamForWritingEntry: (OFZIPArchiveEntry *)entry; + +/*! + * @brief Closes the OFZIPArchive. + */ +- (void)close; @end OF_ASSUME_NONNULL_END Index: src/OFZIPArchive.m ================================================================== --- src/OFZIPArchive.m +++ src/OFZIPArchive.m @@ -47,15 +47,18 @@ /* * FIXME: Current limitations: * - Split archives are not supported. * - Write support is missing. * - Encrypted files cannot be read. + * - No support for writing ZIP64 files. */ @interface OFZIPArchive () - (void)of_readZIPInfo; - (void)of_readEntries; +- (void)of_closeLastReturnedStream; +- (void)of_writeCentralDirectory; @end @interface OFZIPArchive_LocalFileHeader: OFObject { @public @@ -69,23 +72,36 @@ - initWithStream: (OFStream *)stream; - (bool)matchesEntry: (OFZIPArchiveEntry *)entry; @end -@interface OFZIPArchive_FileStream: OFStream +@interface OFZIPArchive_FileReadStream: OFStream { OFStream *_stream, *_decompressedStream; OFZIPArchive_LocalFileHeader *_localFileHeader; bool _hasDataDescriptor; uint64_t _size; uint32_t _CRC32; bool _atEndOfStream; } -- initWithStream: (OFStream *)path +- initWithStream: (OFStream *)stream localFileHeader: (OFZIPArchive_LocalFileHeader *)localFileHeader; @end + +@interface OFZIPArchive_FileWriteStream: OFStream +{ + OFStream *_stream; + uint32_t _CRC32; +@public + uint64_t _bytesWritten; + OFMutableZIPArchiveEntry *_entry; +} + +- initWithStream: (OFStream *)stream + entry: (OFMutableZIPArchiveEntry *)entry; +@end uint32_t of_zip_archive_read_field32(const uint8_t **data, uint16_t *size) { uint32_t field = 0; @@ -162,26 +178,37 @@ mode: (OFString *)mode { self = [super init]; @try { + if ([mode isEqual: @"r"]) + _mode = OF_ZIP_ARCHIVE_MODE_READ; + else if ([mode isEqual: @"w"]) + _mode = OF_ZIP_ARCHIVE_MODE_WRITE; + else if ([mode isEqual: @"a"]) + _mode = OF_ZIP_ARCHIVE_MODE_APPEND; + else + @throw [OFInvalidArgumentException exception]; + _stream = [stream retain]; + _entries = [[OFMutableArray alloc] init]; + _pathToEntryMap = [[OFMutableDictionary alloc] init]; - if ([mode isEqual: @"r"]) { + if (_mode == OF_ZIP_ARCHIVE_MODE_READ || + _mode == OF_ZIP_ARCHIVE_MODE_APPEND) { if (![stream isKindOfClass: [OFSeekableStream class]]) @throw [OFInvalidArgumentException exception]; - _mode = OF_ZIP_ARCHIVE_MODE_READ; - [self of_readZIPInfo]; [self of_readEntries]; - } else if ([mode isEqual: @"w"] || [mode isEqual: @"a"]) - @throw [OFNotImplementedException - exceptionWithSelector: _cmd - object: self]; - else - @throw [OFInvalidArgumentException exception]; + } + + if (_mode == OF_ZIP_ARCHIVE_MODE_APPEND) { + _offset = _centralDirectoryOffset; + seekOrThrowInvalidFormat(_stream, + (of_offset_t)_offset, SEEK_SET); + } } @catch (id e) { [self release]; @throw e; } @@ -190,12 +217,19 @@ #ifdef OF_HAVE_FILES - initWithPath: (OFString *)path mode: (OFString *)mode { - OFFile *file = [[OFFile alloc] initWithPath: path + OFFile *file; + + if ([mode isEqual: @"a"]) + file = [[OFFile alloc] initWithPath: path + mode: @"r+"]; + else + file = [[OFFile alloc] initWithPath: path mode: mode]; + @try { self = [self initWithStream: file mode: mode]; } @finally { [file release]; @@ -205,10 +239,12 @@ } #endif - (void)dealloc { + [self close]; + [_stream release]; [_archiveComment release]; [_entries release]; [_pathToEntryMap release]; [_lastReturnedStream release]; @@ -314,13 +350,10 @@ @throw [OFOutOfRangeException exception]; seekOrThrowInvalidFormat(_stream, (of_offset_t)_centralDirectoryOffset, SEEK_SET); - _entries = [[OFMutableArray alloc] init]; - _pathToEntryMap = [[OFMutableDictionary alloc] init]; - for (size_t i = 0; i < _centralDirectoryEntries; i++) { OFZIPArchiveEntry *entry = [[[OFZIPArchiveEntry alloc] of_initWithStream: _stream] autorelease]; if ([_pathToEntryMap objectForKey: [entry fileName]] != nil) @@ -329,40 +362,78 @@ [_entries addObject: entry]; [_pathToEntryMap setObject: entry forKey: [entry fileName]]; } - [_entries makeImmutable]; - [_pathToEntryMap makeImmutable]; - objc_autoreleasePoolPop(pool); } - (OFArray *)entries { return [[_entries copy] autorelease]; } + +- (OFString *)archiveComment +{ + return _archiveComment; +} + +- (void)setArchiveComment: (OFString *)comment +{ + void *pool = objc_autoreleasePoolPush(); + OFString *old; + + if ([comment UTF8StringLength] > UINT16_MAX) + @throw [OFOutOfRangeException exception]; + + old = _archiveComment; + _archiveComment = [comment copy]; + [old release]; + + objc_autoreleasePoolPop(pool); +} + +- (void)of_closeLastReturnedStream +{ + [_lastReturnedStream close]; + + if ((_mode == OF_ZIP_ARCHIVE_MODE_WRITE || + _mode == OF_ZIP_ARCHIVE_MODE_APPEND) && + [_lastReturnedStream isKindOfClass: + [OFZIPArchive_FileWriteStream class]]) { + OFZIPArchive_FileWriteStream *stream = + (OFZIPArchive_FileWriteStream *)_lastReturnedStream; + + _offset += stream->_bytesWritten; + + if (stream->_entry != nil) { + [_entries addObject: stream->_entry]; + [_pathToEntryMap setObject: stream->_entry + forKey: [stream->_entry fileName]]; + } + } + + [_lastReturnedStream release]; + _lastReturnedStream = nil; +} - (OFStream *)streamForReadingFile: (OFString *)path { void *pool = objc_autoreleasePoolPush(); - OFZIPArchiveEntry *entry = [_pathToEntryMap objectForKey: path]; + OFZIPArchiveEntry *entry; OFZIPArchive_LocalFileHeader *localFileHeader; int64_t offset64; - if (_mode != OF_ZIP_ARCHIVE_MODE_READ && - _mode != OF_ZIP_ARCHIVE_MODE_APPEND) + if (_mode != OF_ZIP_ARCHIVE_MODE_READ) @throw [OFInvalidArgumentException exception]; if ((entry = [_pathToEntryMap objectForKey: path]) == nil) @throw [OFOpenItemFailedException exceptionWithPath: path mode: @"r" errNo: ENOENT]; - [_lastReturnedStream close]; - [_lastReturnedStream release]; - _lastReturnedStream = nil; + [self of_closeLastReturnedStream]; offset64 = [entry of_localFileHeaderOffset]; if (offset64 < 0 || (of_offset_t)offset64 != offset64) @throw [OFOutOfRangeException exception]; @@ -380,18 +451,144 @@ @throw [OFUnsupportedVersionException exceptionWithVersion: version]; } - _lastReturnedStream = [[OFZIPArchive_FileStream alloc] + _lastReturnedStream = [[OFZIPArchive_FileReadStream alloc] initWithStream: _stream localFileHeader: localFileHeader]; objc_autoreleasePoolPop(pool); return [[_lastReturnedStream retain] autorelease]; } + +- (OFStream *)streamForWritingEntry: (OFZIPArchiveEntry *)entry_ +{ + /* TODO: Avoid data descriptor when _stream is an OFSeekableStream */ + void *pool; + OFMutableZIPArchiveEntry *entry; + OFString *fileName; + OFData *extraField; + uint16_t fileNameLength, extraFieldLength; + + if (_mode != OF_ZIP_ARCHIVE_MODE_WRITE && + _mode != OF_ZIP_ARCHIVE_MODE_APPEND) + @throw [OFInvalidArgumentException exception]; + + pool = objc_autoreleasePoolPush(); + entry = [[entry_ mutableCopy] autorelease]; + + if ([_pathToEntryMap objectForKey: [entry fileName]] != nil) + @throw [OFOpenItemFailedException + exceptionWithPath: [entry fileName] + mode: @"w" + errNo: EEXIST]; + + if ([entry compressionMethod] != + OF_ZIP_ARCHIVE_ENTRY_COMPRESSION_METHOD_NONE) + @throw [OFNotImplementedException exceptionWithSelector: _cmd + object: self]; + + [self of_closeLastReturnedStream]; + + if (_offset > UINT32_MAX) + @throw [OFOutOfRangeException exception]; + + [entry setVersionMadeBy: ([entry versionMadeBy] & 0xFF00) | 45]; + [entry setMinVersionNeeded: ([entry minVersionNeeded] & 0xFF00) | 45]; + [entry setCompressedSize: 0]; + [entry setUncompressedSize: 0]; + [entry setCRC32: 0]; + [entry setGeneralPurposeBitFlag: + [entry generalPurposeBitFlag] | (1 << 3) | (1 << 11)]; + [entry of_setLocalFileHeaderOffset: _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 */ + [_stream writeLittleEndianInt32: 0]; + [_stream writeLittleEndianInt32: 0]; + [_stream writeLittleEndianInt32: 0]; + _offset += 4 + (5 * 2) + (3 * 4); + + fileName = [entry fileName]; + fileNameLength = [fileName UTF8StringLength]; + extraField = [entry extraField]; + extraFieldLength = [extraField count]; + + [_stream writeLittleEndianInt16: fileNameLength]; + [_stream writeLittleEndianInt16: extraFieldLength]; + _offset += 2 * 2; + + [_stream writeString: fileName]; + if (extraField != nil) + [_stream writeData: extraField]; + _offset += fileNameLength + extraFieldLength; + + _lastReturnedStream = [[OFZIPArchive_FileWriteStream alloc] + initWithStream: _stream + entry: entry]; + + objc_autoreleasePoolPop(pool); + + return [[_lastReturnedStream retain] autorelease]; +} + +- (void)of_writeCentralDirectory +{ + void *pool = objc_autoreleasePoolPush(); + + if (_offset > UINT32_MAX) + @throw [OFOutOfRangeException exception]; + + _centralDirectoryEntries = 0; + _centralDirectoryEntriesInDisk = 0; + _centralDirectorySize = 0; + _centralDirectoryOffset = _offset; + + for (OFZIPArchiveEntry *entry in _entries) { + _centralDirectorySize += [entry of_writeToStream: _stream]; + _centralDirectoryEntries++; + _centralDirectoryEntriesInDisk++; + } + + if (_centralDirectorySize > UINT32_MAX) + @throw [OFOutOfRangeException exception]; + + [_stream writeLittleEndianInt32: 0x06054B50]; + [_stream writeLittleEndianInt16: _diskNumber]; + [_stream writeLittleEndianInt16: _centralDirectoryDisk]; + [_stream writeLittleEndianInt16: _centralDirectoryEntriesInDisk]; + [_stream writeLittleEndianInt16: _centralDirectoryEntries]; + [_stream writeLittleEndianInt32: (uint32_t)_centralDirectorySize]; + [_stream writeLittleEndianInt32: (uint32_t)_centralDirectoryOffset]; + [_stream writeLittleEndianInt16: [_archiveComment UTF8StringLength]]; + if (_archiveComment != nil) + [_stream writeString: _archiveComment]; + + objc_autoreleasePoolPop(pool); +} + +- (void)close +{ + if (_stream == nil) + return; + + [self of_closeLastReturnedStream]; + + if (_mode == OF_ZIP_ARCHIVE_MODE_WRITE || + _mode == OF_ZIP_ARCHIVE_MODE_APPEND) + [self of_writeCentralDirectory]; + + [_stream release]; + _stream = nil; +} @end @implementation OFZIPArchive_LocalFileHeader - initWithStream: (OFStream *)stream { @@ -473,11 +670,11 @@ return true; } @end -@implementation OFZIPArchive_FileStream +@implementation OFZIPArchive_FileReadStream - initWithStream: (OFStream *)stream localFileHeader: (OFZIPArchive_LocalFileHeader *)localFileHeader { self = [super init]; @@ -591,5 +788,69 @@ _stream = nil; [super close]; } @end + +@implementation OFZIPArchive_FileWriteStream +- initWithStream: (OFStream *)stream + entry: (OFMutableZIPArchiveEntry *)entry +{ + self = [super init]; + + _stream = [stream retain]; + _entry = [entry retain]; + _CRC32 = ~0; + + return self; +} + +- (void)dealloc +{ + [self close]; + + [_stream release]; + [_entry release]; + + [super dealloc]; +} + +- (void)lowlevelWriteBuffer: (const void *)buffer + length: (size_t)length +{ + if (length > INT64_MAX || INT64_MAX - _bytesWritten < length) + @throw [OFOutOfRangeException exception]; + + [_stream writeBuffer: buffer + length: length]; + + _bytesWritten += length; + _CRC32 = of_crc32(_CRC32, buffer, length); +} + +- (void)close +{ + uint32_t bytesWritten; + + if (_stream == nil) + return; + + if (_bytesWritten > UINT32_MAX) + @throw [OFOutOfRangeException exception]; + + bytesWritten = (uint32_t)_bytesWritten; + + [_stream writeLittleEndianInt32: 0x08074B50]; + [_stream writeLittleEndianInt32: _CRC32]; + [_stream writeLittleEndianInt32: bytesWritten]; + [_stream writeLittleEndianInt32: bytesWritten]; + _bytesWritten += (4 * 4); + + [_stream release]; + _stream = nil; + + [_entry setCRC32: ~_CRC32]; + [_entry setCompressedSize: bytesWritten]; + [_entry setUncompressedSize: bytesWritten]; + [_entry makeImmutable]; +} +@end Index: src/OFZIPArchiveEntry+Private.h ================================================================== --- src/OFZIPArchiveEntry+Private.h +++ src/OFZIPArchiveEntry+Private.h @@ -22,8 +22,14 @@ @property (readonly, nonatomic) uint16_t of_lastModifiedFileTime, of_lastModifiedFileDate; @property (readonly, nonatomic) int64_t of_localFileHeaderOffset; - (instancetype)of_initWithStream: (OFStream *)stream OF_METHOD_FAMILY(init); +- (uint64_t)of_writeToStream: (OFStream *)stream; +@end + +@interface OFMutableZIPArchiveEntry () +@property (readwrite, nonatomic, setter=of_setLocalFileHeaderOffset:) + int64_t of_localFileHeaderOffset; @end OF_ASSUME_NONNULL_END Index: src/OFZIPArchiveEntry.h ================================================================== --- src/OFZIPArchiveEntry.h +++ src/OFZIPArchiveEntry.h @@ -113,10 +113,12 @@ */ @property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFString *fileComment; /*! * The extra field of the entry. + * + * The item size *must* be 1! */ @property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFData *extraField; /*! * The version which made the entry. Index: src/OFZIPArchiveEntry.m ================================================================== --- src/OFZIPArchiveEntry.m +++ src/OFZIPArchiveEntry.m @@ -25,10 +25,11 @@ #import "OFDate.h" #import "OFStream.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" +#import "OFOutOfRangeException.h" extern uint32_t of_zip_archive_read_field32(const uint8_t **, uint16_t *); extern uint64_t of_zip_archive_read_field64(const uint8_t **, uint16_t *); OFString * @@ -155,11 +156,18 @@ - initWithFileName: (OFString *)fileName { self = [super init]; @try { - _fileName = [_fileName copy]; + void *pool = objc_autoreleasePoolPush(); + + if ([fileName UTF8StringLength] > UINT16_MAX) + @throw [OFOutOfRangeException exception]; + + _fileName = [fileName copy]; + + objc_autoreleasePoolPop(pool); } @catch (id e) { [self release]; @throw e; } @@ -227,11 +235,11 @@ &ZIP64Size); if (_startDiskNumber == 0xFFFF) _startDiskNumber = of_zip_archive_read_field32( &ZIP64, &ZIP64Size); - if (ZIP64Size > 0) + if (ZIP64Size > 0 || _localFileHeaderOffset < 0) @throw [OFInvalidFormatException exception]; } objc_autoreleasePoolPop(pool); } @catch (id e) { @@ -279,10 +287,12 @@ copy->_localFileHeaderOffset = _localFileHeaderOffset; } @catch (id e) { [copy release]; @throw e; } + + return copy; } - (OFString *)fileName { return _fileName; @@ -400,6 +410,52 @@ objc_autoreleasePoolPop(pool); return [ret autorelease]; } + +- (uint64_t)of_writeToStream: (OFStream *)stream +{ + void *pool; + uint64_t size = 0; + + if (_compressedSize > UINT32_MAX || + _uncompressedSize > UINT32_MAX || + _localFileHeaderOffset > UINT32_MAX) + @throw [OFOutOfRangeException exception]; + + pool = objc_autoreleasePoolPush(); + + [stream writeLittleEndianInt32: 0x02014B50]; + [stream writeLittleEndianInt16: _versionMadeBy]; + [stream writeLittleEndianInt16: _minVersionNeeded]; + [stream writeLittleEndianInt16: _generalPurposeBitFlag]; + [stream writeLittleEndianInt16: _compressionMethod]; + [stream writeLittleEndianInt16: _lastModifiedFileTime]; + [stream writeLittleEndianInt16: _lastModifiedFileDate]; + [stream writeLittleEndianInt32: _CRC32]; + [stream writeLittleEndianInt32: (uint32_t)_compressedSize]; + [stream writeLittleEndianInt32: (uint32_t)_uncompressedSize]; + [stream writeLittleEndianInt16: (uint16_t)[_fileName UTF8StringLength]]; + [stream writeLittleEndianInt16: (uint16_t)[_extraField count]]; + [stream writeLittleEndianInt16: + (uint16_t)[_fileComment UTF8StringLength]]; + [stream writeLittleEndianInt16: _startDiskNumber]; + [stream writeLittleEndianInt16: _internalAttributes]; + [stream writeLittleEndianInt32: _versionSpecificAttributes]; + [stream writeLittleEndianInt32: (uint32_t)_localFileHeaderOffset]; + size += (4 + (6 * 2) + (3 * 4) + (5 * 2) + (2 * 4)); + + [stream writeString: _fileName]; + if (_extraField != nil) + [stream writeData: _extraField]; + if (_fileComment != nil) + [stream writeString: _fileComment]; + size += (uint64_t)[_fileName UTF8StringLength] + + (uint64_t)[_extraField count] + + (uint64_t)[_fileComment UTF8StringLength]; + + objc_autoreleasePoolPop(pool); + + return size; +} @end Index: src/crc32.h ================================================================== --- src/crc32.h +++ src/crc32.h @@ -24,10 +24,10 @@ #import "macros.h" #ifdef __cplusplus extern "C" { #endif -extern uint32_t of_crc32(uint32_t crc, unsigned char *_Nonnull bytes, +extern uint32_t of_crc32(uint32_t crc, const unsigned char *_Nonnull bytes, size_t length); #ifdef __cplusplus } #endif Index: src/crc32.m ================================================================== --- src/crc32.m +++ src/crc32.m @@ -19,11 +19,11 @@ #import "crc32.h" #define CRC32_MAGIC 0xEDB88320 uint32_t -of_crc32(uint32_t crc, unsigned char *bytes, size_t length) +of_crc32(uint32_t crc, const unsigned char *bytes, size_t length) { for (size_t i = 0; i < length; i++) { crc ^= bytes[i]; for (uint8_t j = 0; j < 8; j++)