Index: src/OFLHAArchive.h ================================================================== --- src/OFLHAArchive.h +++ src/OFLHAArchive.h @@ -31,10 +31,11 @@ */ @interface OFLHAArchive: OFObject { OF_KINDOF(OFStream *) _stream; of_string_encoding_t _encoding; + OFLHAArchiveEntry *_Nullable _lastEntry; OF_KINDOF(OFStream *) _Nullable _lastReturnedStream; } /*! * @brief The encoding to use for the archive. Defaults to ISO 8859-1. Index: src/OFLHAArchive.m ================================================================== --- src/OFLHAArchive.m +++ src/OFLHAArchive.m @@ -28,14 +28,14 @@ #import "OFString.h" #import "OFInvalidArgumentException.h" #import "OFNotImplementedException.h" #import "OFNotOpenException.h" +#import "OFTruncatedDataException.h" @interface OFLHAArchive_FileReadStream: OFStream { - OFLHAArchiveEntry *_entry; OF_KINDOF(OFStream *) _stream; uint32_t _toRead; bool _atEndOfStream; } @@ -120,52 +120,73 @@ [super dealloc]; } - (OFLHAArchiveEntry *)nextEntry { - OFLHAArchiveEntry *entry; - uint8_t headerSize; + char header[21]; + size_t headerLen; + + [_lastEntry release]; + _lastEntry = nil; [_lastReturnedStream of_skip]; [_lastReturnedStream close]; [_lastReturnedStream release]; _lastReturnedStream = nil; - if ([_stream isAtEndOfStream]) - return nil; - - if ([_stream readIntoBuffer: &headerSize - length: 1] == 0) - return nil; - - if (headerSize == 0) - return nil; - - entry = [[[OFLHAArchiveEntry alloc] - of_initWithHeaderSize: headerSize - stream: _stream - encoding: _encoding] autorelease]; + for (headerLen = 0; headerLen < 21;) { + if ([_stream isAtEndOfStream]) { + if (headerLen == 0) + return nil; + + if (headerLen == 1 && header[0] == 0) + return nil; + + @throw [OFTruncatedDataException exception]; + } + + headerLen += [_stream readIntoBuffer: header + headerLen + length: 21 - headerLen]; + } + + _lastEntry = [[OFLHAArchiveEntry alloc] + of_initWithHeader: header + stream: _stream + encoding: _encoding]; _lastReturnedStream = [[OFLHAArchive_FileReadStream alloc] of_initWithStream: _stream - entry: entry]; + entry: _lastEntry]; - return entry; + return [[_lastEntry copy] autorelease]; } - (OFStream *)streamForReadingCurrentEntry { + OFString *method; + if (_lastReturnedStream == nil) @throw [OFInvalidArgumentException exception]; + + method = [_lastEntry method]; + + if (![method isEqual: @"-lh0-"] && ![method isEqual: @"-lhd-"] && + ![method isEqual: @"-lz4-"]) + @throw [OFNotImplementedException + exceptionWithSelector: _cmd + object: self]; return [[_lastReturnedStream retain] autorelease]; } - (void)close { if (_stream == nil) return; + + [_lastEntry release]; + _lastEntry = nil; [_lastReturnedStream close]; [_lastReturnedStream release]; _lastReturnedStream = nil; @@ -179,20 +200,17 @@ entry: (OFLHAArchiveEntry *)entry { self = [super init]; @try { - OFString *method = [entry method]; - - if (![method isEqual: @"-lh0-"] && ![method isEqual: @"-lhd-"]) - @throw [OFNotImplementedException - exceptionWithSelector: _cmd - object: self]; - - _entry = [entry copy]; _stream = [stream retain]; - _toRead = [entry uncompressedSize]; + /* + * Use the compressed size, as that is the number of bytes we + * need to skip for the next entry and is equal for + * uncompressed files, the only thing supported so far. + */ + _toRead = [entry compressedSize]; } @catch (id e) { [self release]; @throw e; } @@ -201,12 +219,10 @@ - (void)dealloc { [self close]; - [_entry release]; - [super dealloc]; } - (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length Index: src/OFLHAArchiveEntry+Private.h ================================================================== --- src/OFLHAArchiveEntry+Private.h +++ src/OFLHAArchiveEntry+Private.h @@ -18,12 +18,12 @@ #import "OFLHAArchive.h" OF_ASSUME_NONNULL_BEGIN @interface OFLHAArchiveEntry () -- (instancetype)of_initWithHeaderSize: (uint8_t)headerSize - stream: (OFStream *)stream - encoding: (of_string_encoding_t)encoding +- (instancetype)of_initWithHeader: (char [_Nonnull 21])header + stream: (OFStream *)stream + encoding: (of_string_encoding_t)encoding OF_METHOD_FAMILY(init); @end OF_ASSUME_NONNULL_END Index: src/OFLHAArchiveEntry.h ================================================================== --- src/OFLHAArchiveEntry.h +++ src/OFLHAArchiveEntry.h @@ -20,10 +20,11 @@ OF_ASSUME_NONNULL_BEGIN @class OFArray OF_GENERIC(ObjectType); @class OFData; @class OFDate; +@class OFMutableArray OF_GENERIC(ObjectType); @class OFNumber; @class OFString; /*! * @class OFLHAArchiveEntry OFLHAArchiveEntry.h ObjFW/OFLHAArchiveEntry.h @@ -44,11 +45,11 @@ uint8_t _operatingSystemIdentifier; OFString *_Nullable _fileComment; OFNumber *_Nullable _mode, *_Nullable _UID, *_Nullable _GID; OFString *_Nullable _owner, *_Nullable _group; OFDate *_Nullable _modificationDate; - OFArray OF_GENERIC(OFData *) *_extensions; + OFMutableArray OF_GENERIC(OFData *) *_extensions; } /*! * @brief The file name of the entry. */ Index: src/OFLHAArchiveEntry.m ================================================================== --- src/OFLHAArchiveEntry.m +++ src/OFLHAArchiveEntry.m @@ -28,10 +28,29 @@ #import "OFStream.h" #import "OFString.h" #import "OFInvalidFormatException.h" #import "OFUnsupportedVersionException.h" + +static OFDate * +parseMSDOSDate(uint32_t MSDOSDate) +{ + uint16_t year = ((MSDOSDate & 0xFE000000) >> 25) + 1980; + uint8_t month = (MSDOSDate & 0x1E00000) >> 21; + uint8_t day = (MSDOSDate & 0x1F); + uint8_t hour = (MSDOSDate & 0xF800) >> 11; + uint8_t minute = (MSDOSDate & 0x7E0) >> 5; + uint8_t second = (MSDOSDate & 0x1F) << 1; + OFString *dateString; + + dateString = [OFString + stringWithFormat: @"%04u-%02u-%02u %02u:%02u:%02u", + year, month, day, hour, minute, second]; + + return [OFDate dateWithLocalDateString: dateString + format: @"%Y-%m-%d %H:%M:%S"]; +} static void parseFileNameExtension(OFLHAArchiveEntry *entry, OFData *extension, of_string_encoding_t encoding) { @@ -214,10 +233,29 @@ return false; function(entry, extension, encoding); return true; } + +static void +readExtensions(OFLHAArchiveEntry *entry, OFStream *stream, + of_string_encoding_t encoding) +{ + uint16_t nextSize; + + while ((nextSize = [stream readLittleEndianInt16]) > 0) { + OFData *extension; + + if (nextSize < 2) + @throw [OFInvalidFormatException exception]; + + extension = [stream readDataWithCount: nextSize - 2]; + + if (!parseExtension(entry, extension, encoding)) + [entry->_extensions addObject: extension]; + } +} @implementation OFLHAArchiveEntry @synthesize method = _method, compressedSize = _compressedSize; @synthesize uncompressedSize = _uncompressedSize, date = _date; @synthesize level = _level, CRC16 = _CRC16; @@ -229,73 +267,74 @@ - (instancetype)init { OF_INVALID_INIT_METHOD } -- (instancetype)of_initWithHeaderSize: (uint8_t)headerSize - stream: (OFStream *)stream - encoding: (of_string_encoding_t)encoding +- (instancetype)of_initWithHeader: (char [21])header + stream: (OFStream *)stream + encoding: (of_string_encoding_t)encoding { self = [super init]; @try { - char header[20]; uint32_t date; - uint16_t nextSize; - OFMutableArray *extensions; - - if (headerSize < 21) - @throw [OFInvalidFormatException exception]; - - [stream readIntoBuffer: header - exactLength: 20]; _method = [[OFString alloc] - initWithCString: header + 1 + initWithCString: header + 2 encoding: OF_STRING_ENCODING_ASCII length: 5]; - memcpy(&_compressedSize, header + 6, 4); + memcpy(&_compressedSize, header + 7, 4); _compressedSize = OF_BSWAP32_IF_BE(_compressedSize); - memcpy(&_uncompressedSize, header + 10, 4); + memcpy(&_uncompressedSize, header + 11, 4); _uncompressedSize = OF_BSWAP32_IF_BE(_uncompressedSize); - memcpy(&date, header + 14, 4); + memcpy(&date, header + 15, 4); date = OF_BSWAP32_IF_BE(date); - _level = header[19]; + _level = header[20]; + + _extensions = [[OFMutableArray alloc] init]; + + switch (_level) { + case 0:; + void *pool = objc_autoreleasePoolPush(); + uint16_t fileNameLength = [stream readInt8]; + OFString *tmp; + + _date = [parseMSDOSDate(date) retain]; + + tmp = [stream readStringWithLength: fileNameLength + encoding: encoding]; + tmp = [tmp stringByReplacingOccurrencesOfString: @"\\" + withString: @"/"]; + _fileName = [tmp copy]; + + _CRC16 = [stream readLittleEndianInt16]; + + objc_autoreleasePoolPop(pool); + break; + case 2: + _date = [[OFDate alloc] + initWithTimeIntervalSince1970: date]; + + _CRC16 = [stream readLittleEndianInt16]; + _operatingSystemIdentifier = [stream readInt8]; + + readExtensions(self, stream, encoding); - if (_level != 2) { + break; + default:; OFString *version = [OFString stringWithFormat: @"%u", _level]; @throw [OFUnsupportedVersionException exceptionWithVersion: version]; } - _date = [[OFDate alloc] initWithTimeIntervalSince1970: date]; - - _CRC16 = [stream readLittleEndianInt16]; - _operatingSystemIdentifier = [stream readInt8]; - - extensions = [[OFMutableArray alloc] init]; - _extensions = extensions; - - while ((nextSize = [stream readLittleEndianInt16]) > 0) { - OFData *extension; - - if (nextSize < 2) - @throw [OFInvalidFormatException exception]; - - extension = [stream readDataWithCount: nextSize - 2]; - - if (!parseExtension(self, extension, encoding)) - [extensions addObject: extension]; - } - - [extensions makeImmutable]; + [_extensions makeImmutable]; } @catch (id e) { [self release]; @throw e; } Index: utils/ofzip/LHAArchive.m ================================================================== --- utils/ofzip/LHAArchive.m +++ utils/ofzip/LHAArchive.m @@ -68,11 +68,12 @@ @try { _archive = [[OFLHAArchive alloc] initWithStream: stream mode: mode]; - [_archive setEncoding: encoding]; + if (encoding != OF_STRING_ENCODING_AUTODETECT) + [_archive setEncoding: encoding]; } @catch (id e) { [self release]; @throw e; } @@ -99,11 +100,11 @@ OFString *date = [[entry date] localDateStringWithFormat: @"%Y-%m-%d %H:%M:%S"]; OFString *compressedSize = [OFString stringWithFormat: @"%" PRIu32, [entry compressedSize]]; OFString *uncompressedSize = [OFString stringWithFormat: - @"%" PRIu32, [entry compressedSize]]; + @"%" PRIu32, [entry uncompressedSize]]; OFString *CRC16 = [OFString stringWithFormat: @"%04" PRIX16, [entry CRC16]]; [of_stdout writeString: @"\t"]; [of_stdout writeLine: OF_LOCALIZED( Index: utils/ofzip/OFZIP.m ================================================================== --- utils/ofzip/OFZIP.m +++ utils/ofzip/OFZIP.m @@ -168,11 +168,11 @@ { 'x', @"extract", 0, NULL, NULL }, { '\0', nil, 0, NULL, NULL } }; OFOptionsParser *optionsParser; of_unichar_t option, mode = '\0'; - of_string_encoding_t encoding = OF_STRING_ENCODING_UTF_8; + of_string_encoding_t encoding = OF_STRING_ENCODING_AUTODETECT; OFArray OF_GENERIC(OFString *) *remainingArguments, *files; id archive; #ifdef OF_HAVE_SANDBOX OFSandbox *sandbox = [[OFSandbox alloc] init]; Index: utils/ofzip/TarArchive.m ================================================================== --- utils/ofzip/TarArchive.m +++ utils/ofzip/TarArchive.m @@ -68,11 +68,12 @@ @try { _archive = [[OFTarArchive alloc] initWithStream: stream mode: mode]; - [_archive setEncoding: encoding]; + if (encoding != OF_STRING_ENCODING_AUTODETECT) + [_archive setEncoding: encoding]; } @catch (id e) { [self release]; @throw e; }