Index: src/OFZooArchive.m ================================================================== --- src/OFZooArchive.m +++ src/OFZooArchive.m @@ -186,19 +186,10 @@ _currentEntry = [[OFZooArchiveEntry alloc] of_initWithStream: _stream encoding: _encoding]; - if (_currentEntry->_nextHeaderOffset == 0) { - /* - * End of archive is marked by a header that has the next - * header's offset set to 0. - */ - [_currentEntry release]; - _currentEntry = nil; - } - return _currentEntry; } - (OFStream *)streamForReadingCurrentEntry { Index: src/OFZooArchiveEntry+Private.h ================================================================== --- src/OFZooArchiveEntry+Private.h +++ src/OFZooArchiveEntry+Private.h @@ -16,11 +16,11 @@ #import "OFZooArchive.h" OF_ASSUME_NONNULL_BEGIN @interface OFZooArchiveEntry () -- (instancetype)of_initWithStream: (OF_KINDOF(OFStream *))stream - encoding: (OFStringEncoding)encoding +- (nullable instancetype)of_initWithStream: (OF_KINDOF(OFStream *))stream + encoding: (OFStringEncoding)encoding OF_METHOD_FAMILY(init) OF_DIRECT; @end OF_ASSUME_NONNULL_END Index: src/OFZooArchiveEntry.h ================================================================== --- src/OFZooArchiveEntry.h +++ src/OFZooArchiveEntry.h @@ -27,11 +27,11 @@ * * @brief A class which represents an entry in an Zoo archive. */ @interface OFZooArchiveEntry: OFObject { - uint8_t _compressionMethod; + uint8_t _headerType, _compressionMethod; #ifdef OF_ZOO_ARCHIVE_M @public #endif unsigned long long _nextHeaderOffset, _dataOffset; @protected @@ -42,13 +42,19 @@ bool _deleted; OFString *_Nullable _fileComment; OFString *_fileName, *_Nullable _directoryName; OFNumber *_Nullable _POSIXPermissions; int8_t _timeZone; + uint16_t _operatingSystemIdentifier; OF_RESERVE_IVARS(OFZooArchiveEntry, 4) } +/** + * @brief The header type of the entry. + */ +@property (readonly, nonatomic) uint8_t headerType; + /** * @brief The compression method of the entry. */ @property (readonly, nonatomic) uint8_t compressionMethod; @@ -68,10 +74,15 @@ /** * @brief Whether the file was deleted. */ @property (readonly, nonatomic, getter=isDeleted) bool deleted; +/** + * @brief The operating system identifier of the file. + */ +@property (readonly, nonatomic) uint16_t operatingSystemIdentifier; + /** * @brief The time zone in which the file was stored, as an offset in hours * from UTC (as a float). */ @property OF_NULLABLE_PROPERTY (readonly, nonatomic) OFNumber *timeZone; Index: src/OFZooArchiveEntry.m ================================================================== --- src/OFZooArchiveEntry.m +++ src/OFZooArchiveEntry.m @@ -22,17 +22,20 @@ #import "OFSeekableStream.h" #import "OFStream.h" #import "OFString.h" #import "OFInvalidFormatException.h" +#import "OFUnsupportedVersionException.h" @implementation OFZooArchiveEntry -@synthesize compressionMethod = _compressionMethod, CRC16 = _CRC16; -@synthesize uncompressedSize = _uncompressedSize; +@synthesize headerType = _headerType, compressionMethod = _compressionMethod; +@synthesize CRC16 = _CRC16, uncompressedSize = _uncompressedSize; @synthesize compressedSize = _compressedSize; @synthesize minVersionNeeded = _minVersionNeeded, deleted = _deleted; -@synthesize fileComment = _fileComment, POSIXPermissions = _POSIXPermissions; +@synthesize fileComment = _fileComment; +@synthesize operatingSystemIdentifier = _operatingSystemIdentifier; +@synthesize POSIXPermissions = _POSIXPermissions; - (instancetype)init { OF_INVALID_INIT_METHOD } @@ -49,34 +52,44 @@ uint16_t commentLength; if ([stream readLittleEndianInt32] != 0xFDC4A7DC) @throw [OFInvalidFormatException exception]; - /* Type seems to be always 2 */ - if ([stream readInt8] != 2) - @throw [OFInvalidFormatException exception]; + if ((_headerType = [stream readInt8]) > 2) + @throw [OFUnsupportedVersionException + exceptionWithVersion: [OFString + stringWithFormat: @"%u", _headerType]]; _compressionMethod = [stream readInt8]; _nextHeaderOffset = [stream readLittleEndianInt32]; + + if (_nextHeaderOffset == 0) { + [self release]; + return nil; + } + _dataOffset = [stream readLittleEndianInt32]; _lastModifiedFileDate = [stream readLittleEndianInt16]; _lastModifiedFileTime = [stream readLittleEndianInt16]; _CRC16 = [stream readLittleEndianInt16]; _uncompressedSize = [stream readLittleEndianInt32]; _compressedSize = [stream readLittleEndianInt32]; _minVersionNeeded = [stream readBigEndianInt16]; _deleted = [stream readInt8]; - /* Unknown. Most likely padding to get to 2 byte alignment? */ + /* + * File structure, whatever that is meant to be. Seems to + * always be 0. + */ [stream readInt8]; commentOffset = [stream readLittleEndianInt32]; commentLength = [stream readLittleEndianInt16]; [stream readIntoBuffer: fileNameBuffer exactLength: 13]; if (fileNameBuffer[12] != '\0') fileNameBuffer[12] = '\0'; - if ((_minVersionNeeded >> 8) == 2) { + if (_headerType >= 2) { uint16_t extraLength = [stream readLittleEndianInt16]; uint8_t fileNameLength, directoryNameLength; if (extraLength < 10) @throw [OFInvalidFormatException exception]; @@ -113,12 +126,12 @@ encoding: encoding] copy]; extraLength -= directoryNameLength; } if (extraLength >= 2) { - /* System ID */ - [stream readLittleEndianInt16]; + _operatingSystemIdentifier = + [stream readLittleEndianInt16]; extraLength -= 2; } if (extraLength >= 3) { uint8_t attributes[3]; Index: utils/ofarc/LHAArchive.m ================================================================== --- utils/ofarc/LHAArchive.m +++ utils/ofarc/LHAArchive.m @@ -238,11 +238,11 @@ [OFStdOut writeString: @"\t"]; [OFStdOut writeLine: OF_LOCALIZED( @"list_osid", @"Operating system identifier: " - "%[osid]", + @"%[osid]", @"osid", OSID)]; } } if (app->_outputLevel >= 3) { Index: utils/ofarc/ZooArchive.m ================================================================== --- utils/ofarc/ZooArchive.m +++ utils/ofarc/ZooArchive.m @@ -135,15 +135,16 @@ OFString *compressedSize = [OFString stringWithFormat: @"%llu", entry.compressedSize]; OFString *uncompressedSize = [OFString stringWithFormat: @"%llu", entry.uncompressedSize]; OFString *compressionMethod = [OFString - stringWithFormat: @"%u", entry.compressionMethod]; + stringWithFormat: @"%" PRIu8, + entry.compressionMethod]; OFString *CRC16 = [OFString stringWithFormat: @"%04" PRIX16, entry.CRC16]; OFString *deleted = [OFString stringWithFormat: - @"%u", entry.deleted]; + @"%" PRIu8, entry.deleted]; [OFStdOut writeString: @"\t"]; [OFStdOut writeLine: OF_LOCALIZED( @"list_compressed_size", @"[" @@ -224,19 +225,41 @@ } if (app->_outputLevel >= 2) { uint16_t minVersionNeeded = entry.minVersionNeeded; OFString *minVersionNeededString = [OFString - stringWithFormat: @"%u.%u", + stringWithFormat: @"%" PRIu8 @".%" PRIu8, minVersionNeeded >> 8, minVersionNeeded & 0xFF]; + OFString *headerType = [OFString + stringWithFormat: @"%" PRIu8, + entry.headerType]; [OFStdOut writeString: @"\t"]; [OFStdOut writeLine: OF_LOCALIZED( @"list_min_version_needed", @"Minimum version needed: %[version]", @"version", minVersionNeededString)]; + + [OFStdOut writeString: @"\t"]; + [OFStdOut writeLine: OF_LOCALIZED( + @"list_header_type", + @"Header type: %[type]", + @"type", headerType)]; + + if (entry.headerType >= 2) { + OFString *OSID = + [OFString stringWithFormat: @"%u", + entry.operatingSystemIdentifier]; + + [OFStdOut writeString: @"\t"]; + [OFStdOut writeLine: OF_LOCALIZED( + @"list_osid", + @"Operating system identifier: " + @"%[osid]", + @"osid", OSID)]; + } if (entry.POSIXPermissions != nil) { OFString *permissionsString = [OFString stringWithFormat: @"%llo", entry.POSIXPermissions Index: utils/ofarc/localization/de.json ================================================================== --- utils/ofarc/localization/de.json +++ utils/ofarc/localization/de.json @@ -91,10 +91,11 @@ list_owner_account_id: "Besitzerkontennummer: %[id]", list_group_owner_account_id: "Gruppenbesitzerkontennummer: %[id]", list_owner_account_name: "Besitzerkontenname: %[name]", list_group_owner_account_name: "Gruppenbesitzerkontenname: %[name]", list_header_level: "Header-Level: %[level]", + list_header_type: "Header-Typ: %[type]", list_modification_date: "Änderungsdatum: %[date]", list_type_normal: "Typ: Normale Datei", list_type_hardlink: "Typ: Harter Link", list_type_symlink: "Typ: Symbolischer Link", list_link_target: "Zieldateiname: %[target]",