ObjFW  Check-in [89a51dab69]

Overview
Comment:OFZIPArchive: Only use data descriptor if needed
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 89a51dab69204ff1a922b31a564b2a401e8c17f55c188521337336bf63cb1a9b
User & Date: js on 2023-03-18 22:43:20
Other Links: manifest | tags
Context
2023-03-26
13:11
Don't install OFEmbeddedIRIHandler.h check-in: e78254fca5 user: js tags: trunk
2023-03-18
22:43
OFZIPArchive: Only use data descriptor if needed check-in: 89a51dab69 user: js tags: trunk
2023-02-19
17:40
Make GCC happy again check-in: d377e8819e user: js tags: trunk
Changes

Modified src/OFZIPArchive.h from [3c9f652c27] to [82b11667ec].

28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 * @class OFZIPArchive OFZIPArchive.h ObjFW/OFZIPArchive.h
 *
 * @brief A class for accessing and manipulating ZIP files.
 */
OF_SUBCLASSING_RESTRICTED
@interface OFZIPArchive: OFObject
{
	OFStream *_stream;
#ifdef OF_ZIP_ARCHIVE_M
@public
#endif
	int64_t _offset;
@protected
	uint_least8_t _mode;
	uint32_t _diskNumber, _centralDirectoryDisk;







|







28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
 * @class OFZIPArchive OFZIPArchive.h ObjFW/OFZIPArchive.h
 *
 * @brief A class for accessing and manipulating ZIP files.
 */
OF_SUBCLASSING_RESTRICTED
@interface OFZIPArchive: OFObject
{
	OF_KINDOF(OFStream *) _stream;
#ifdef OF_ZIP_ARCHIVE_M
@public
#endif
	int64_t _offset;
@protected
	uint_least8_t _mode;
	uint32_t _diskNumber, _centralDirectoryDisk;

Modified src/OFZIPArchive.m from [b9f9c8b9a8] to [1186bd8318].

97
98
99
100
101
102
103
104
105

106
107
108
109
110
111
112
113


114
115
116
117
118
119
120
			     entry: (OFZIPArchiveEntry *)entry;
@end

OF_DIRECT_MEMBERS
@interface OFZIPArchiveFileWriteStream: OFStream
{
	OFZIPArchive *_archive;
	OFStream *_stream;
	uint32_t _CRC32;

@public
	unsigned long long _bytesWritten;
	OFMutableZIPArchiveEntry *_entry;
}

- (instancetype)of_initWithArchive: (OFZIPArchive *)archive
			    stream: (OFStream *)stream
			     entry: (OFMutableZIPArchiveEntry *)entry;


@end

uint32_t
OFZIPArchiveReadField32(const uint8_t **data, uint16_t *size)
{
	uint32_t field = 0;








|

>







|
>
>







97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
			     entry: (OFZIPArchiveEntry *)entry;
@end

OF_DIRECT_MEMBERS
@interface OFZIPArchiveFileWriteStream: OFStream
{
	OFZIPArchive *_archive;
	OF_KINDOF(OFStream *) _stream;
	uint32_t _CRC32;
	OFStreamOffset _CRC32Offset, _size64Offset;
@public
	unsigned long long _bytesWritten;
	OFMutableZIPArchiveEntry *_entry;
}

- (instancetype)of_initWithArchive: (OFZIPArchive *)archive
			    stream: (OFStream *)stream
			     entry: (OFMutableZIPArchiveEntry *)entry
		       CRC32Offset: (OFStreamOffset)CRC32Offset
		      size64Offset: (OFStreamOffset)size64Offset;
@end

uint32_t
OFZIPArchiveReadField32(const uint8_t **data, uint16_t *size)
{
	uint32_t field = 0;

208
209
210
211
212
213
214
215
216
217
218
219
220
221
222

			[self of_readZIPInfo];
			[self of_readEntries];
		}

		if (_mode == modeAppend) {
			_offset = _centralDirectoryOffset;
			seekOrThrowInvalidFormat((OFSeekableStream *)_stream,
			    (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
		 * be a ZIP file which we would destroy otherwise.







|







211
212
213
214
215
216
217
218
219
220
221
222
223
224
225

			[self of_readZIPInfo];
			[self of_readEntries];
		}

		if (_mode == modeAppend) {
			_offset = _centralDirectoryOffset;
			seekOrThrowInvalidFormat(_stream,
			    (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
		 * be a ZIP file which we would destroy otherwise.
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
{
	void *pool = objc_autoreleasePoolPush();
	uint16_t commentLength;
	OFStreamOffset offset = -22;
	bool valid = false;

	do {
		seekOrThrowInvalidFormat((OFSeekableStream *)_stream,
		    offset, OFSeekEnd);

		if ([_stream readLittleEndianInt32] == 0x06054B50) {
			valid = true;
			break;
		}
	} while (--offset >= -65557);








<
|







273
274
275
276
277
278
279

280
281
282
283
284
285
286
287
{
	void *pool = objc_autoreleasePoolPush();
	uint16_t commentLength;
	OFStreamOffset offset = -22;
	bool valid = false;

	do {

		seekOrThrowInvalidFormat(_stream, offset, OFSeekEnd);

		if ([_stream readLittleEndianInt32] == 0x06054B50) {
			valid = true;
			break;
		}
	} while (--offset >= -65557);

303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
	    _centralDirectoryEntriesInDisk == 0xFFFF ||
	    _centralDirectoryEntries == 0xFFFF ||
	    _centralDirectorySize == 0xFFFFFFFF ||
	    _centralDirectoryOffset == 0xFFFFFFFF) {
		int64_t offset64;
		uint64_t size;

		seekOrThrowInvalidFormat((OFSeekableStream *)_stream,
		    offset - 20, OFSeekEnd);

		if ([_stream readLittleEndianInt32] != 0x07064B50) {
			objc_autoreleasePoolPop(pool);
			return;
		}

		/*
		 * FIXME: Handle number of the disk containing ZIP64 end of
		 * central directory record.
		 */
		[_stream readLittleEndianInt32];
		offset64 = [_stream readLittleEndianInt64];

		if (offset64 < 0 || (OFStreamOffset)offset64 != offset64)
			@throw [OFOutOfRangeException exception];

		seekOrThrowInvalidFormat((OFSeekableStream *)_stream,
		    (OFStreamOffset)offset64, OFSeekSet);

		if ([_stream readLittleEndianInt32] != 0x06064B50)
			@throw [OFInvalidFormatException exception];

		size = [_stream readLittleEndianInt64];
		if (size < 44)







<
|
















|







305
306
307
308
309
310
311

312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
	    _centralDirectoryEntriesInDisk == 0xFFFF ||
	    _centralDirectoryEntries == 0xFFFF ||
	    _centralDirectorySize == 0xFFFFFFFF ||
	    _centralDirectoryOffset == 0xFFFFFFFF) {
		int64_t offset64;
		uint64_t size;


		seekOrThrowInvalidFormat(_stream, offset - 20, OFSeekEnd);

		if ([_stream readLittleEndianInt32] != 0x07064B50) {
			objc_autoreleasePoolPop(pool);
			return;
		}

		/*
		 * FIXME: Handle number of the disk containing ZIP64 end of
		 * central directory record.
		 */
		[_stream readLittleEndianInt32];
		offset64 = [_stream readLittleEndianInt64];

		if (offset64 < 0 || (OFStreamOffset)offset64 != offset64)
			@throw [OFOutOfRangeException exception];

		seekOrThrowInvalidFormat(_stream,
		    (OFStreamOffset)offset64, OFSeekSet);

		if ([_stream readLittleEndianInt32] != 0x06064B50)
			@throw [OFInvalidFormatException exception];

		size = [_stream readLittleEndianInt64];
		if (size < 44)
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
{
	void *pool = objc_autoreleasePoolPush();

	if (_centralDirectoryOffset < 0 ||
	    (OFStreamOffset)_centralDirectoryOffset != _centralDirectoryOffset)
		@throw [OFOutOfRangeException exception];

	seekOrThrowInvalidFormat((OFSeekableStream *)_stream,
	    (OFStreamOffset)_centralDirectoryOffset, OFSeekSet);

	for (size_t i = 0; i < _centralDirectoryEntries; i++) {
		OFZIPArchiveEntry *entry = [[[OFZIPArchiveEntry alloc]
		    of_initWithStream: _stream] autorelease];

		if ([_pathToEntryMap objectForKey: entry.fileName] != nil)







|







362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
{
	void *pool = objc_autoreleasePoolPush();

	if (_centralDirectoryOffset < 0 ||
	    (OFStreamOffset)_centralDirectoryOffset != _centralDirectoryOffset)
		@throw [OFOutOfRangeException exception];

	seekOrThrowInvalidFormat(_stream,
	    (OFStreamOffset)_centralDirectoryOffset, OFSeekSet);

	for (size_t i = 0; i < _centralDirectoryEntries; i++) {
		OFZIPArchiveEntry *entry = [[[OFZIPArchiveEntry alloc]
		    of_initWithStream: _stream] autorelease];

		if ([_pathToEntryMap objectForKey: entry.fileName] != nil)
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
	}
	_lastReturnedStream = nil;

	offset64 = entry.of_localFileHeaderOffset;
	if (offset64 < 0 || (OFStreamOffset)offset64 != offset64)
		@throw [OFOutOfRangeException exception];

	seekOrThrowInvalidFormat((OFSeekableStream *)_stream,
	    (OFStreamOffset)offset64, OFSeekSet);
	localFileHeader = [[[OFZIPArchiveLocalFileHeader alloc]
	    initWithStream: _stream] autorelease];

	if (![localFileHeader matchesEntry: entry])
		@throw [OFInvalidFormatException exception];

	if ((localFileHeader->_minVersionNeeded & 0xFF) > 45) {







<
|







433
434
435
436
437
438
439

440
441
442
443
444
445
446
447
	}
	_lastReturnedStream = nil;

	offset64 = entry.of_localFileHeaderOffset;
	if (offset64 < 0 || (OFStreamOffset)offset64 != offset64)
		@throw [OFOutOfRangeException exception];


	seekOrThrowInvalidFormat(_stream, (OFStreamOffset)offset64, OFSeekSet);
	localFileHeader = [[[OFZIPArchiveLocalFileHeader alloc]
	    initWithStream: _stream] autorelease];

	if (![localFileHeader matchesEntry: entry])
		@throw [OFInvalidFormatException exception];

	if ((localFileHeader->_minVersionNeeded & 0xFF) > 45) {
461
462
463
464
465
466
467
468
469
470
471
472


473
474
475
476
477
478
479
			 entry: entry] autorelease];

	return _lastReturnedStream;
}

- (OFStream *)streamForWritingEntry: (OFZIPArchiveEntry *)entry_
{
	/* TODO: Avoid data descriptor when _stream is an OFSeekableStream */
	int64_t offsetAdd = 0;
	void *pool;
	OFMutableZIPArchiveEntry *entry;
	OFString *fileName;


	OFData *extraField;
	uint16_t fileNameLength, extraFieldLength;

	if (_stream == nil)
		@throw [OFNotOpenException exceptionWithObject: self];

	if (_mode != modeWrite && _mode != modeAppend)







<




>
>







461
462
463
464
465
466
467

468
469
470
471
472
473
474
475
476
477
478
479
480
			 entry: entry] autorelease];

	return _lastReturnedStream;
}

- (OFStream *)streamForWritingEntry: (OFZIPArchiveEntry *)entry_
{

	int64_t offsetAdd = 0;
	void *pool;
	OFMutableZIPArchiveEntry *entry;
	OFString *fileName;
	bool seekable;
	OFStreamOffset CRC32Offset = 0, size64Offset = 0;
	OFData *extraField;
	uint16_t fileNameLength, extraFieldLength;

	if (_stream == nil)
		@throw [OFNotOpenException exceptionWithObject: self];

	if (_mode != modeWrite && _mode != modeAppend)
503
504
505
506
507
508
509


510
511
512
513
514
515
516
517
518
519
520
521
522
523
524


525
526
527
528
529
530
531
532
533
534
535
536
537
538


539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555


556
557
558
559
560
561
562
	fileNameLength = fileName.UTF8StringLength;
	extraField = entry.extraField;
	extraFieldLength = extraField.count;

	if (UINT16_MAX - extraFieldLength < 20)
		@throw [OFOutOfRangeException exception];



	entry.versionMadeBy = (entry.versionMadeBy & 0xFF00) | 45;
	entry.minVersionNeeded = (entry.minVersionNeeded & 0xFF00) | 45;
	entry.compressedSize = 0;
	entry.uncompressedSize = 0;
	entry.CRC32 = 0;
	entry.generalPurposeBitFlag |= (1u << 3) | (1u << 11);
	entry.of_localFileHeaderOffset = _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];
	/* We use ZIP64 */
	[_stream writeLittleEndianInt32: 0xFFFFFFFF];
	[_stream writeLittleEndianInt32: 0xFFFFFFFF];
	[_stream writeLittleEndianInt16: fileNameLength];
	[_stream writeLittleEndianInt16: extraFieldLength + 20];
	offsetAdd += 4 + (5 * 2) + (3 * 4) + (2 * 2);

	[_stream writeString: fileName];
	offsetAdd += fileNameLength;

	[_stream writeLittleEndianInt16: OFZIPArchiveEntryExtraFieldTagZIP64];
	[_stream writeLittleEndianInt16: 16];
	/* We use the data descriptor */


	[_stream writeLittleEndianInt64: 0];
	[_stream writeLittleEndianInt64: 0];
	offsetAdd += (2 * 2) + (2 * 8);

	if (extraField != nil)
		[_stream writeData: extraField];
	offsetAdd += extraFieldLength;

	if (INT64_MAX - _offset < offsetAdd)
		@throw [OFOutOfRangeException exception];

	_offset += offsetAdd;

	_lastReturnedStream = [[OFZIPArchiveFileWriteStream alloc]
	    of_initWithArchive: self
			stream: _stream
			 entry: entry];



	objc_autoreleasePoolPop(pool);

	return [_lastReturnedStream autorelease];
}

- (void)of_writeCentralDirectory







>
>





|








|
>
>













|
>
>
















|
>
>







504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
	fileNameLength = fileName.UTF8StringLength;
	extraField = entry.extraField;
	extraFieldLength = extraField.count;

	if (UINT16_MAX - extraFieldLength < 20)
		@throw [OFOutOfRangeException exception];

	seekable = [_stream isKindOfClass: [OFSeekableStream class]];

	entry.versionMadeBy = (entry.versionMadeBy & 0xFF00) | 45;
	entry.minVersionNeeded = (entry.minVersionNeeded & 0xFF00) | 45;
	entry.compressedSize = 0;
	entry.uncompressedSize = 0;
	entry.CRC32 = 0;
	entry.generalPurposeBitFlag |= (seekable ? 0 : (1u << 3)) | (1u << 11);
	entry.of_localFileHeaderOffset = _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];
	/* Written later or data descriptor used instead */
	if (seekable)
		CRC32Offset = [_stream seekToOffset: 0 whence: OFSeekCurrent];
	[_stream writeLittleEndianInt32: 0];
	/* We use ZIP64 */
	[_stream writeLittleEndianInt32: 0xFFFFFFFF];
	[_stream writeLittleEndianInt32: 0xFFFFFFFF];
	[_stream writeLittleEndianInt16: fileNameLength];
	[_stream writeLittleEndianInt16: extraFieldLength + 20];
	offsetAdd += 4 + (5 * 2) + (3 * 4) + (2 * 2);

	[_stream writeString: fileName];
	offsetAdd += fileNameLength;

	[_stream writeLittleEndianInt16: OFZIPArchiveEntryExtraFieldTagZIP64];
	[_stream writeLittleEndianInt16: 16];
	/* Written later or data descriptor used instead */
	if (seekable)
		size64Offset = [_stream seekToOffset: 0 whence: OFSeekCurrent];
	[_stream writeLittleEndianInt64: 0];
	[_stream writeLittleEndianInt64: 0];
	offsetAdd += (2 * 2) + (2 * 8);

	if (extraField != nil)
		[_stream writeData: extraField];
	offsetAdd += extraFieldLength;

	if (INT64_MAX - _offset < offsetAdd)
		@throw [OFOutOfRangeException exception];

	_offset += offsetAdd;

	_lastReturnedStream = [[OFZIPArchiveFileWriteStream alloc]
	    of_initWithArchive: self
			stream: _stream
			 entry: entry
		   CRC32Offset: CRC32Offset
		  size64Offset: size64Offset];

	objc_autoreleasePoolPop(pool);

	return [_lastReturnedStream autorelease];
}

- (void)of_writeCentralDirectory
861
862
863
864
865
866
867


868
869
870
871
872
873
874


875
876
877
878
879
880
881
}
@end

@implementation OFZIPArchiveFileWriteStream
- (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;
}

- (void)dealloc
{
	if (_stream != nil)







>
>







>
>







870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
}
@end

@implementation OFZIPArchiveFileWriteStream
- (instancetype)of_initWithArchive: (OFZIPArchive *)archive
			    stream: (OFStream *)stream
			     entry: (OFMutableZIPArchiveEntry *)entry
		       CRC32Offset: (OFStreamOffset)CRC32Offset
		      size64Offset: (OFStreamOffset)size64Offset
{
	self = [super init];

	_archive = [archive retain];
	_stream = [stream retain];
	_entry = [entry retain];
	_CRC32 = ~0;
	_CRC32Offset = CRC32Offset;
	_size64Offset = size64Offset;

	return self;
}

- (void)dealloc
{
	if (_stream != nil)
919
920
921
922
923
924
925


926
927
928
929
930
931














932
933
934
935

936
937
938
939
940
941
942
943
944

945
946
947
948
949
950
951
952
953
954
955
956
957
	_CRC32 = OFCRC32(_CRC32, buffer, length);

	return length;
}

- (void)close
{


	if (_stream == nil)
		@throw [OFNotOpenException exceptionWithObject: self];

	if (_bytesWritten > UINT64_MAX)
		@throw [OFOutOfRangeException exception];















	[_stream writeLittleEndianInt32: 0x08074B50];
	[_stream writeLittleEndianInt32: _CRC32];
	[_stream writeLittleEndianInt64: (uint64_t)_bytesWritten];
	[_stream writeLittleEndianInt64: (uint64_t)_bytesWritten];


	[_stream release];
	_stream = nil;

	_entry.CRC32 = ~_CRC32;
	_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







>
>






>
>
>
>
>
>
>
>
>
>
>
>
>
>
|
|
|
|
>









>
|












932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
	_CRC32 = OFCRC32(_CRC32, buffer, length);

	return length;
}

- (void)close
{
	bool seekable;

	if (_stream == nil)
		@throw [OFNotOpenException exceptionWithObject: self];

	if (_bytesWritten > UINT64_MAX)
		@throw [OFOutOfRangeException exception];

	seekable = [_stream isKindOfClass: [OFSeekableStream class]];

	if (seekable) {
		OFStreamOffset offset = [_stream seekToOffset: 0
						       whence: OFSeekCurrent];

		[_stream seekToOffset: _CRC32Offset whence: OFSeekSet];
		[_stream writeLittleEndianInt32: ~_CRC32];
		[_stream seekToOffset: _size64Offset whence: OFSeekSet];
		[_stream writeLittleEndianInt64: (uint64_t)_bytesWritten];
		[_stream writeLittleEndianInt64: (uint64_t)_bytesWritten];

		[_stream seekToOffset: offset whence: OFSeekSet];
	} else {
		[_stream writeLittleEndianInt32: 0x08074B50];
		[_stream writeLittleEndianInt32: ~_CRC32];
		[_stream writeLittleEndianInt64: (uint64_t)_bytesWritten];
		[_stream writeLittleEndianInt64: (uint64_t)_bytesWritten];
	}

	[_stream release];
	_stream = nil;

	_entry.CRC32 = ~_CRC32;
	_entry.compressedSize = _bytesWritten;
	_entry.uncompressedSize = _bytesWritten;
	[_entry makeImmutable];

	if (!seekable)
		_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