@@ -11,28 +11,30 @@ * Public License, either version 2 or 3, which can be found in the file * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this * file. */ +#define OF_ZIP_ARCHIVE_M + #include "config.h" #include #import "OFZIPArchive.h" #import "OFZIPArchiveEntry.h" #import "OFZIPArchiveEntry+Private.h" +#import "OFArchiveURIHandler.h" +#import "OFArray.h" #import "OFCRC32.h" #import "OFData.h" -#import "OFArray.h" #import "OFDictionary.h" -#import "OFStream.h" +#import "OFInflate64Stream.h" +#import "OFInflateStream.h" #import "OFSeekableStream.h" -#ifdef OF_HAVE_FILES -# import "OFFile.h" -#endif -#import "OFInflateStream.h" -#import "OFInflate64Stream.h" +#import "OFStream.h" +#import "OFURI.h" +#import "OFURIHandler.h" #import "OFChecksumMismatchException.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" #import "OFNotImplementedException.h" @@ -58,11 +60,10 @@ OF_DIRECT_MEMBERS @interface OFZIPArchive () - (void)of_readZIPInfo; - (void)of_readEntries; -- (void)of_closeLastReturnedStream; - (void)of_writeCentralDirectory; @end OF_DIRECT_MEMBERS @interface OFZIPArchiveLocalFileHeader: OFObject @@ -81,33 +82,37 @@ @end OF_DIRECT_MEMBERS @interface OFZIPArchiveFileReadStream: OFStream { + OFZIPArchive *_archive; OFStream *_stream, *_decompressedStream; OFZIPArchiveEntry *_entry; - uint64_t _toRead; + unsigned long long _toRead; uint32_t _CRC32; bool _atEndOfStream; } -- (instancetype)of_initWithStream: (OFStream *)stream - entry: (OFZIPArchiveEntry *)entry; +- (instancetype)of_initWithArchive: (OFZIPArchive *)archive + stream: (OFStream *)stream + entry: (OFZIPArchiveEntry *)entry; @end OF_DIRECT_MEMBERS @interface OFZIPArchiveFileWriteStream: OFStream { + OFZIPArchive *_archive; OFStream *_stream; uint32_t _CRC32; @public - int64_t _bytesWritten; + unsigned long long _bytesWritten; OFMutableZIPArchiveEntry *_entry; } -- (instancetype)initWithStream: (OFStream *)stream - entry: (OFMutableZIPArchiveEntry *)entry; +- (instancetype)of_initWithArchive: (OFZIPArchive *)archive + stream: (OFStream *)stream + entry: (OFMutableZIPArchiveEntry *)entry; @end uint32_t OFZIPArchiveReadField32(const uint8_t **data, uint16_t *size) { @@ -142,11 +147,11 @@ return field; } static void seekOrThrowInvalidFormat(OFSeekableStream *stream, - OFFileOffset offset, int whence) + OFStreamOffset offset, OFSeekWhence whence) { @try { [stream seekToOffset: offset whence: whence]; } @catch (OFSeekFailedException *e) { if (e.errNo == EINVAL) @@ -162,16 +167,19 @@ + (instancetype)archiveWithStream: (OFStream *)stream mode: (OFString *)mode { return [[[self alloc] initWithStream: stream mode: mode] autorelease]; } -#ifdef OF_HAVE_FILES -+ (instancetype)archiveWithPath: (OFString *)path mode: (OFString *)mode ++ (instancetype)archiveWithURI: (OFURI *)URI mode: (OFString *)mode { - return [[[self alloc] initWithPath: path mode: mode] autorelease]; + return [[[self alloc] initWithURI: URI mode: mode] autorelease]; } -#endif + ++ (OFURI *)URIForFilePath: (OFString *)path inArchiveWithURI: (OFURI *)URI +{ + return OFArchiveURIHandlerURIForFileInArchive(@"zip", path, URI); +} - (instancetype)init { OF_INVALID_INIT_METHOD } @@ -203,11 +211,11 @@ } if (_mode == modeAppend) { _offset = _centralDirectoryOffset; seekOrThrowInvalidFormat((OFSeekableStream *)_stream, - (OFFileOffset)_offset, SEEK_SET); + (OFStreamOffset)_offset, OFSeekSet); } } @catch (id e) { /* * If we are in write or append mode, we do not want -[close] * to write anything to it on error - after all, it might not @@ -221,29 +229,31 @@ } return self; } -#ifdef OF_HAVE_FILES -- (instancetype)initWithPath: (OFString *)path mode: (OFString *)mode -{ - OFFile *file; - - if ([mode isEqual: @"a"]) - file = [[OFFile alloc] initWithPath: path mode: @"r+"]; - else - file = [[OFFile alloc] initWithPath: path mode: mode]; +- (instancetype)initWithURI: (OFURI *)URI mode: (OFString *)mode +{ + void *pool = objc_autoreleasePoolPush(); + OFStream *stream; @try { - self = [self initWithStream: file mode: mode]; - } @finally { - [file release]; + if ([mode isEqual: @"a"]) + stream = [OFURIHandler openItemAtURI: URI mode: @"r+"]; + else + stream = [OFURIHandler openItemAtURI: URI mode: mode]; + } @catch (id e) { + [self release]; + @throw e; } + + self = [self initWithStream: stream mode: mode]; + + objc_autoreleasePoolPop(pool); return self; } -#endif - (void)dealloc { if (_stream != nil) [self close]; @@ -250,25 +260,24 @@ [_stream release]; [_archiveComment release]; [_entries release]; [_pathToEntryMap release]; - [_lastReturnedStream release]; [super dealloc]; } - (void)of_readZIPInfo { void *pool = objc_autoreleasePoolPush(); uint16_t commentLength; - OFFileOffset offset = -22; + OFStreamOffset offset = -22; bool valid = false; do { seekOrThrowInvalidFormat((OFSeekableStream *)_stream, - offset, SEEK_END); + offset, OFSeekEnd); if ([_stream readLittleEndianInt32] == 0x06054B50) { valid = true; break; } @@ -297,11 +306,11 @@ _centralDirectoryOffset == 0xFFFFFFFF) { int64_t offset64; uint64_t size; seekOrThrowInvalidFormat((OFSeekableStream *)_stream, - offset - 20, SEEK_END); + offset - 20, OFSeekEnd); if ([_stream readLittleEndianInt32] != 0x07064B50) { objc_autoreleasePoolPop(pool); return; } @@ -311,15 +320,15 @@ * central directory record. */ [_stream readLittleEndianInt32]; offset64 = [_stream readLittleEndianInt64]; - if (offset64 < 0 || (OFFileOffset)offset64 != offset64) + if (offset64 < 0 || (OFStreamOffset)offset64 != offset64) @throw [OFOutOfRangeException exception]; seekOrThrowInvalidFormat((OFSeekableStream *)_stream, - (OFFileOffset)offset64, SEEK_SET); + (OFStreamOffset)offset64, OFSeekSet); if ([_stream readLittleEndianInt32] != 0x06064B50) @throw [OFInvalidFormatException exception]; size = [_stream readLittleEndianInt64]; @@ -338,11 +347,11 @@ _centralDirectoryEntries = [_stream readLittleEndianInt64]; _centralDirectorySize = [_stream readLittleEndianInt64]; _centralDirectoryOffset = [_stream readLittleEndianInt64]; if (_centralDirectoryOffset < 0 || - (OFFileOffset)_centralDirectoryOffset != + (OFStreamOffset)_centralDirectoryOffset != _centralDirectoryOffset) @throw [OFOutOfRangeException exception]; } objc_autoreleasePoolPop(pool); @@ -351,15 +360,15 @@ - (void)of_readEntries { void *pool = objc_autoreleasePoolPush(); if (_centralDirectoryOffset < 0 || - (OFFileOffset)_centralDirectoryOffset != _centralDirectoryOffset) + (OFStreamOffset)_centralDirectoryOffset != _centralDirectoryOffset) @throw [OFOutOfRangeException exception]; seekOrThrowInvalidFormat((OFSeekableStream *)_stream, - (OFFileOffset)_centralDirectoryOffset, SEEK_SET); + (OFStreamOffset)_centralDirectoryOffset, OFSeekSet); for (size_t i = 0; i < _centralDirectoryEntries; i++) { OFZIPArchiveEntry *entry = [[[OFZIPArchiveEntry alloc] of_initWithStream: _stream] autorelease]; @@ -396,40 +405,10 @@ [old release]; objc_autoreleasePoolPop(pool); } -- (void)of_closeLastReturnedStream -{ - @try { - [_lastReturnedStream close]; - } @catch (OFNotOpenException *e) { - /* Might have already been closed by the user - that's fine. */ - } - - if ((_mode == modeWrite || _mode == modeAppend) && - [_lastReturnedStream isKindOfClass: - [OFZIPArchiveFileWriteStream class]]) { - OFZIPArchiveFileWriteStream *stream = - (OFZIPArchiveFileWriteStream *)_lastReturnedStream; - - if (INT64_MAX - _offset < stream->_bytesWritten) - @throw [OFOutOfRangeException exception]; - - _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; OFZIPArchiveLocalFileHeader *localFileHeader; @@ -444,18 +423,23 @@ if ((entry = [_pathToEntryMap objectForKey: path]) == nil) @throw [OFOpenItemFailedException exceptionWithPath: path mode: @"r" errNo: ENOENT]; - [self of_closeLastReturnedStream]; + @try { + [_lastReturnedStream close]; + } @catch (OFNotOpenException *e) { + /* Might have already been closed by the user - that's fine. */ + } + _lastReturnedStream = nil; offset64 = entry.of_localFileHeaderOffset; - if (offset64 < 0 || (OFFileOffset)offset64 != offset64) + if (offset64 < 0 || (OFStreamOffset)offset64 != offset64) @throw [OFOutOfRangeException exception]; seekOrThrowInvalidFormat((OFSeekableStream *)_stream, - (OFFileOffset)offset64, SEEK_SET); + (OFStreamOffset)offset64, OFSeekSet); localFileHeader = [[[OFZIPArchiveLocalFileHeader alloc] initWithStream: _stream] autorelease]; if (![localFileHeader matchesEntry: entry]) @throw [OFInvalidFormatException exception]; @@ -467,17 +451,18 @@ @throw [OFUnsupportedVersionException exceptionWithVersion: version]; } - _lastReturnedStream = [[OFZIPArchiveFileReadStream alloc] - of_initWithStream: _stream - entry: entry]; - objc_autoreleasePoolPop(pool); - return [[_lastReturnedStream retain] autorelease]; + _lastReturnedStream = [[[OFZIPArchiveFileReadStream alloc] + of_initWithArchive: self + stream: _stream + entry: entry] autorelease]; + + return _lastReturnedStream; } - (OFStream *)streamForWritingEntry: (OFZIPArchiveEntry *)entry_ { /* TODO: Avoid data descriptor when _stream is an OFSeekableStream */ @@ -505,11 +490,16 @@ if (entry.compressionMethod != OFZIPArchiveEntryCompressionMethodNone) @throw [OFNotImplementedException exceptionWithSelector: _cmd object: self]; - [self of_closeLastReturnedStream]; + @try { + [_lastReturnedStream close]; + } @catch (OFNotOpenException *e) { + /* Might have already been closed by the user - that's fine. */ + } + _lastReturnedStream = nil; fileName = entry.fileName; fileNameLength = fileName.UTF8StringLength; extraField = entry.extraField; extraFieldLength = extraField.count; @@ -558,16 +548,17 @@ @throw [OFOutOfRangeException exception]; _offset += offsetAdd; _lastReturnedStream = [[OFZIPArchiveFileWriteStream alloc] - initWithStream: _stream - entry: entry]; + of_initWithArchive: self + stream: _stream + entry: entry]; objc_autoreleasePoolPop(pool); - return [[_lastReturnedStream retain] autorelease]; + return [_lastReturnedStream autorelease]; } - (void)of_writeCentralDirectory { void *pool = objc_autoreleasePoolPush(); @@ -620,11 +611,16 @@ - (void)close { if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; - [self of_closeLastReturnedStream]; + @try { + [_lastReturnedStream close]; + } @catch (OFNotOpenException *e) { + /* Might have already been closed by the user - that's fine. */ + } + _lastReturnedStream = nil; if (_mode == modeWrite || _mode == modeAppend) [self of_writeCentralDirectory]; [_stream release]; @@ -672,11 +668,11 @@ if (ZIP64Index != OFNotFound) { const uint8_t *ZIP64 = [extraField itemAtIndex: ZIP64Index]; OFRange range = - OFRangeMake(ZIP64Index - 4, ZIP64Size + 4); + OFMakeRange(ZIP64Index - 4, ZIP64Size + 4); if (_uncompressedSize == 0xFFFFFFFF) _uncompressedSize = OFZIPArchiveReadField64( &ZIP64, &ZIP64Size); if (_compressedSize == 0xFFFFFFFF) @@ -730,16 +726,18 @@ return true; } @end @implementation OFZIPArchiveFileReadStream -- (instancetype)of_initWithStream: (OFStream *)stream - entry: (OFZIPArchiveEntry *)entry +- (instancetype)of_initWithArchive: (OFZIPArchive *)archive + stream: (OFStream *)stream + entry: (OFZIPArchiveEntry *)entry { self = [super init]; @try { + _archive = [archive retain]; _stream = [stream retain]; switch (entry.compressionMethod) { case OFZIPArchiveEntryCompressionMethodNone: _decompressedStream = [stream retain]; @@ -774,10 +772,15 @@ if (_stream != nil || _decompressedStream != nil) [self close]; [_entry release]; + if (_archive->_lastReturnedStream == self) + _archive->_lastReturnedStream = nil; + + [_archive release]; + [super dealloc]; } - (bool)lowlevelIsAtEndOfStream { @@ -803,11 +806,11 @@ #if SIZE_MAX >= UINT64_MAX if (length > UINT64_MAX) @throw [OFOutOfRangeException exception]; #endif - if ((uint64_t)length > _toRead) + if (length > _toRead) length = (size_t)_toRead; ret = [_decompressedStream readIntoBuffer: buffer length: length]; _toRead -= ret; @@ -857,15 +860,17 @@ [super close]; } @end @implementation OFZIPArchiveFileWriteStream -- (instancetype)initWithStream: (OFStream *)stream - entry: (OFMutableZIPArchiveEntry *)entry +- (instancetype)of_initWithArchive: (OFZIPArchive *)archive + stream: (OFStream *)stream + entry: (OFMutableZIPArchiveEntry *)entry { self = [super init]; + _archive = [archive retain]; _stream = [stream retain]; _entry = [entry retain]; _CRC32 = ~0; return self; @@ -876,10 +881,15 @@ if (_stream != nil) [self close]; [_entry release]; + if (_archive->_lastReturnedStream == self) + _archive->_lastReturnedStream = nil; + + [_archive release]; + [super dealloc]; } - (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length { @@ -886,28 +896,28 @@ #if SIZE_MAX >= INT64_MAX if (length > INT64_MAX) @throw [OFOutOfRangeException exception]; #endif - if (INT64_MAX - _bytesWritten < (int64_t)length) + if (ULLONG_MAX - _bytesWritten < length) @throw [OFOutOfRangeException exception]; @try { [_stream writeBuffer: buffer length: length]; } @catch (OFWriteFailedException *e) { OFEnsure(e.bytesWritten <= length); - _bytesWritten += (int64_t)e.bytesWritten; + _bytesWritten += (unsigned long long)e.bytesWritten; _CRC32 = OFCRC32(_CRC32, buffer, e.bytesWritten); if (e.errNo == EWOULDBLOCK || e.errNo == EAGAIN) return e.bytesWritten; @throw e; } - _bytesWritten += (int64_t)length; + _bytesWritten += (unsigned long long)length; _CRC32 = OFCRC32(_CRC32, buffer, length); return length; } @@ -914,14 +924,17 @@ - (void)close { if (_stream == nil) @throw [OFNotOpenException exceptionWithObject: self]; + if (_bytesWritten > UINT64_MAX) + @throw [OFOutOfRangeException exception]; + [_stream writeLittleEndianInt32: 0x08074B50]; [_stream writeLittleEndianInt32: _CRC32]; - [_stream writeLittleEndianInt64: _bytesWritten]; - [_stream writeLittleEndianInt64: _bytesWritten]; + [_stream writeLittleEndianInt64: (uint64_t)_bytesWritten]; + [_stream writeLittleEndianInt64: (uint64_t)_bytesWritten]; [_stream release]; _stream = nil; _entry.CRC32 = ~_CRC32; @@ -928,9 +941,17 @@ _entry.compressedSize = _bytesWritten; _entry.uncompressedSize = _bytesWritten; [_entry makeImmutable]; _bytesWritten += (2 * 4 + 2 * 8); + + [_archive->_entries addObject: _entry]; + [_archive->_pathToEntryMap setObject: _entry forKey: _entry.fileName]; + + if (ULLONG_MAX - _archive->_offset < _bytesWritten) + @throw [OFOutOfRangeException exception]; + + _archive->_offset += _bytesWritten; [super close]; } @end