@@ -1,7 +1,7 @@ /* - * Copyright (c) 2008-2023 Jonathan Schleifer + * Copyright (c) 2008-2024 Jonathan Schleifer * * All rights reserved. * * This file is part of ObjFW. It may be distributed under the terms of the * Q Public License 1.0, which can be found in the file LICENSE.QPL included in @@ -22,10 +22,11 @@ #import "OFArray.h" #import "OFCRC16.h" #import "OFData.h" #import "OFDate.h" #import "OFNumber.h" +#import "OFSeekableStream.h" #import "OFStream.h" #import "OFString.h" #import "OFInvalidArgumentException.h" #import "OFInvalidFormatException.h" @@ -333,19 +334,10 @@ self = [super init]; @try { uint32_t date; - _compressionMethod = [[OFString alloc] - initWithCString: header + 2 - encoding: OFStringEncodingASCII - length: 5]; - - if (_compressedSize > UINT32_MAX || - _uncompressedSize > UINT32_MAX) - @throw [OFOutOfRangeException exception]; - memcpy(&_compressedSize, header + 7, 4); _compressedSize = OFFromLittleEndian32((uint32_t)_compressedSize); memcpy(&_uncompressedSize, header + 11, 4); @@ -360,13 +352,17 @@ switch (_headerLevel) { case 0: case 1:; void *pool = objc_autoreleasePoolPush(); + uint8_t extendedAreaSize; uint8_t fileNameLength; OFString *tmp; + if (header[0] < (21 - 2) + 1 + 2) + @throw [OFInvalidFormatException exception]; + _modificationDate = [parseMSDOSDate(date) retain]; fileNameLength = [stream readInt8]; tmp = [stream readStringWithLength: fileNameLength encoding: encoding]; @@ -374,16 +370,45 @@ withString: @"/"]; _fileName = [tmp copy]; _CRC16 = [stream readLittleEndianInt16]; + extendedAreaSize = + header[0] - (21 - 2) - 1 - fileNameLength - 2; + if (_headerLevel == 1) { + if (extendedAreaSize < 3) + @throw [OFInvalidFormatException + exception]; + _operatingSystemIdentifier = [stream readInt8]; - readExtensions(self, stream, encoding, false); + /* + * 1 for the operating system identifier, 2 + * because we don't want to skip the size of + * the next extended header. + */ + extendedAreaSize -= 1 + 2; + } + + /* Skip extended area. */ + if ([stream isKindOfClass: [OFSeekableStream class]]) + [(OFSeekableStream *)stream + seekToOffset: extendedAreaSize + whence: OFSeekCurrent]; + else { + char buffer[256]; + + while (extendedAreaSize > 0) + extendedAreaSize -= [stream + readIntoBuffer: buffer + length: extendedAreaSize]; } + if (_headerLevel == 1) + readExtensions(self, stream, encoding, false); + objc_autoreleasePoolPop(pool); break; case 2: _modificationDate = [[OFDate alloc] initWithTimeIntervalSince1970: date]; @@ -402,10 +427,15 @@ exceptionWithVersion: version]; } if (_fileName == nil) @throw [OFInvalidFormatException exception]; + + _compressionMethod = [[OFString alloc] + initWithCString: header + 2 + encoding: OFStringEncodingASCII + length: 5]; [_extensions makeImmutable]; } @catch (id e) { [self release]; @throw e; @@ -705,10 +735,18 @@ } /* Zero-length extension to terminate */ [data increaseCountBy: 2]; + /* + * Some implementations only check the first byte to see if the end of + * the archive has been reached, which is 0 for every multiple of 256. + * Add one byte of padding to avoid this. + */ + if ((data.count & 0xFF) == 0) + [data increaseCountBy: 1]; + headerSize = data.count; if (headerSize > UINT16_MAX) @throw [OFOutOfRangeException exception];