ObjFW  Check-in [def4cbbba6]

Overview
Comment:OFZIPArchive: Support for writing ZIP64 archives
Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: def4cbbba67f874ec79351d63b5e90062e5c854d19bf239882785cf21ffe9203
User & Date: js on 2017-08-14 00:31:05
Other Links: manifest | tags
Context
2017-08-14
00:45
OFZIPArchive(Entry): A logic and a typo fix check-in: de6e2319ca user: js tags: trunk
00:31
OFZIPArchive: Support for writing ZIP64 archives check-in: def4cbbba6 user: js tags: trunk
2017-08-13
22:38
Fix -[OFData description] check-in: 484c7987d2 user: js tags: trunk
Changes

Modified src/OFZIPArchive.m from [c1fa8a1f8d] to [db5f1942c0].

43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
#import "OFOutOfRangeException.h"
#import "OFSeekFailedException.h"
#import "OFUnsupportedVersionException.h"

/*
 * FIXME: Current limitations:
 *  - Split archives are not supported.
 *  - Write support is missing.
 *  - Encrypted files cannot be read.
 *  - No support for writing ZIP64 files.
 */

@interface OFZIPArchive ()
- (void)of_readZIPInfo;
- (void)of_readEntries;
- (void)of_closeLastReturnedStream;
- (void)of_writeCentralDirectory;







<

<







43
44
45
46
47
48
49

50

51
52
53
54
55
56
57
#import "OFOutOfRangeException.h"
#import "OFSeekFailedException.h"
#import "OFUnsupportedVersionException.h"

/*
 * FIXME: Current limitations:
 *  - Split archives are not supported.

 *  - Encrypted files cannot be read.

 */

@interface OFZIPArchive ()
- (void)of_readZIPInfo;
- (void)of_readEntries;
- (void)of_closeLastReturnedStream;
- (void)of_writeCentralDirectory;
471
472
473
474
475
476
477

478
479
480
481
482
483
484
485
486
487
488
489
490

	return [[_lastReturnedStream retain] autorelease];
}

- (OFStream *)streamForWritingEntry: (OFZIPArchiveEntry *)entry_
{
	/* TODO: Avoid data descriptor when _stream is an OFSeekableStream */

	void *pool;
	OFMutableZIPArchiveEntry *entry;
	OFString *fileName;
	OFData *extraField;
	uint16_t fileNameLength, extraFieldLength;
	int64_t offsetAdd = 0;

	if (_mode != OF_ZIP_ARCHIVE_MODE_WRITE &&
	    _mode != OF_ZIP_ARCHIVE_MODE_APPEND)
		@throw [OFInvalidArgumentException exception];

	pool = objc_autoreleasePoolPush();
	entry = [[entry_ mutableCopy] autorelease];







>





<







469
470
471
472
473
474
475
476
477
478
479
480
481

482
483
484
485
486
487
488

	return [[_lastReturnedStream retain] autorelease];
}

- (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 (_mode != OF_ZIP_ARCHIVE_MODE_WRITE &&
	    _mode != OF_ZIP_ARCHIVE_MODE_APPEND)
		@throw [OFInvalidArgumentException exception];

	pool = objc_autoreleasePoolPush();
	entry = [[entry_ mutableCopy] autorelease];
498
499
500
501
502
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
563
564
565
566
567
568
569
570
571
572
573
574









575

576






577

578
579
580
581
582
583
584
585
586
587
588
589
590
591
	if ([entry compressionMethod] !=
	    OF_ZIP_ARCHIVE_ENTRY_COMPRESSION_METHOD_NONE)
		@throw [OFNotImplementedException exceptionWithSelector: _cmd
								 object: self];

	[self of_closeLastReturnedStream];





	if (_offset > UINT32_MAX)

		@throw [OFOutOfRangeException exception];

	[entry setVersionMadeBy: ([entry versionMadeBy] & 0xFF00) | 45];
	[entry setMinVersionNeeded: ([entry minVersionNeeded] & 0xFF00) | 45];
	[entry setCompressedSize: 0];
	[entry setUncompressedSize: 0];
	[entry setCRC32: 0];
	[entry setGeneralPurposeBitFlag:
	    [entry generalPurposeBitFlag] | (1 << 3) | (1 << 11)];
	[entry of_setLocalFileHeaderOffset: _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];
	[_stream writeLittleEndianInt32: 0];
	[_stream writeLittleEndianInt32: 0];


	offsetAdd += 4 + (5 * 2) + (3 * 4);

	fileName = [entry fileName];
	fileNameLength = [fileName UTF8StringLength];
	extraField = [entry extraField];
	extraFieldLength = [extraField count];

	[_stream writeLittleEndianInt16: fileNameLength];

	[_stream writeLittleEndianInt16: extraFieldLength];



	offsetAdd += 2 * 2;

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

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

	_offset += offsetAdd;

	_lastReturnedStream = [[OFZIPArchive_FileWriteStream alloc]
	     initWithStream: _stream
		      entry: entry];

	objc_autoreleasePoolPop(pool);

	return [[_lastReturnedStream retain] autorelease];
}

- (void)of_writeCentralDirectory
{
	void *pool = objc_autoreleasePoolPush();

	if (_offset > UINT32_MAX)
		@throw [OFOutOfRangeException exception];

	_centralDirectoryEntries = 0;
	_centralDirectoryEntriesInDisk = 0;
	_centralDirectorySize = 0;
	_centralDirectoryOffset = _offset;

	for (OFZIPArchiveEntry *entry in _entries) {
		_centralDirectorySize += [entry of_writeToStream: _stream];
		_centralDirectoryEntries++;
		_centralDirectoryEntriesInDisk++;
	}










	if (_centralDirectorySize > UINT32_MAX)

		@throw [OFOutOfRangeException exception];








	[_stream writeLittleEndianInt32: 0x06054B50];
	[_stream writeLittleEndianInt16: _diskNumber];
	[_stream writeLittleEndianInt16: _centralDirectoryDisk];
	[_stream writeLittleEndianInt16: _centralDirectoryEntriesInDisk];
	[_stream writeLittleEndianInt16: _centralDirectoryEntries];
	[_stream writeLittleEndianInt32: (uint32_t)_centralDirectorySize];
	[_stream writeLittleEndianInt32: (uint32_t)_centralDirectoryOffset];
	[_stream writeLittleEndianInt16: [_archiveComment UTF8StringLength]];
	if (_archiveComment != nil)
		[_stream writeString: _archiveComment];

	objc_autoreleasePoolPop(pool);
}








>
>
>
>
|
>

















|
|
|
|
>
>
|

|
|
<
<

|
>
|
>
>
>
|

<


|



















<
<
<











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

>

|
|
|
|
|
|







496
497
498
499
500
501
502
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
563
564
565
566



567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
	if ([entry compressionMethod] !=
	    OF_ZIP_ARCHIVE_ENTRY_COMPRESSION_METHOD_NONE)
		@throw [OFNotImplementedException exceptionWithSelector: _cmd
								 object: self];

	[self of_closeLastReturnedStream];

	fileName = [entry fileName];
	fileNameLength = [fileName UTF8StringLength];
	extraField = [entry extraField];
	extraFieldLength = [extraField count];

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

	[entry setVersionMadeBy: ([entry versionMadeBy] & 0xFF00) | 45];
	[entry setMinVersionNeeded: ([entry minVersionNeeded] & 0xFF00) | 45];
	[entry setCompressedSize: 0];
	[entry setUncompressedSize: 0];
	[entry setCRC32: 0];
	[entry setGeneralPurposeBitFlag:
	    [entry generalPurposeBitFlag] | (1 << 3) | (1 << 11)];
	[entry of_setLocalFileHeaderOffset: _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 ZIP64 */
	[_stream writeLittleEndianInt32: 0xFFFFFFFF];
	[_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:
	    OF_ZIP_ARCHIVE_ENTRY_EXTRA_FIELD_ZIP64];
	[_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 = [[OFZIPArchive_FileWriteStream alloc]
	     initWithStream: _stream
		      entry: entry];

	objc_autoreleasePoolPop(pool);

	return [[_lastReturnedStream retain] autorelease];
}

- (void)of_writeCentralDirectory
{
	void *pool = objc_autoreleasePoolPush();




	_centralDirectoryEntries = 0;
	_centralDirectoryEntriesInDisk = 0;
	_centralDirectorySize = 0;
	_centralDirectoryOffset = _offset;

	for (OFZIPArchiveEntry *entry in _entries) {
		_centralDirectorySize += [entry of_writeToStream: _stream];
		_centralDirectoryEntries++;
		_centralDirectoryEntriesInDisk++;
	}

	/* ZIP64 end of central directory */
	[_stream writeLittleEndianInt32: 0x06064B50];
	[_stream writeLittleEndianInt64: 44];	/* Remaining size */
	[_stream writeLittleEndianInt16: 45];	/* Version made by */
	[_stream writeLittleEndianInt16: 45];	/* Version required */
	[_stream writeLittleEndianInt32: _diskNumber];
	[_stream writeLittleEndianInt32: _centralDirectoryDisk];
	[_stream writeLittleEndianInt64: _centralDirectoryEntriesInDisk];
	[_stream writeLittleEndianInt64: _centralDirectoryEntries];
	[_stream writeLittleEndianInt64: _centralDirectorySize];
	[_stream writeLittleEndianInt64: _centralDirectoryOffset];

	/* ZIP64 end of central directory locator */
	[_stream writeLittleEndianInt32: 0x07064B50];
	[_stream writeLittleEndianInt32: _diskNumber];
	[_stream writeLittleEndianInt64:
	    _centralDirectoryOffset + _centralDirectorySize];
	[_stream writeLittleEndianInt32: 1];	/* Total number of disks */

	/* End of central directory */
	[_stream writeLittleEndianInt32: 0x06054B50];
	[_stream writeLittleEndianInt16: 0xFFFF];	/* Disk number */
	[_stream writeLittleEndianInt16: 0xFFFF];	/* CD disk */
	[_stream writeLittleEndianInt16: 0xFFFF];	/* CD entries in disk */
	[_stream writeLittleEndianInt16: 0xFFFF];	/* CD entries */
	[_stream writeLittleEndianInt32: 0xFFFFFFFF];	/* CD size */
	[_stream writeLittleEndianInt32: 0xFFFFFFFF];	/* CD offset */
	[_stream writeLittleEndianInt16: [_archiveComment UTF8StringLength]];
	if (_archiveComment != nil)
		[_stream writeString: _archiveComment];

	objc_autoreleasePoolPop(pool);
}

833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863


864

	_bytesWritten += (int64_t)length;
	_CRC32 = of_crc32(_CRC32, buffer, length);
}

- (void)close
{
	uint32_t bytesWritten;

	if (_stream == nil)
		return;

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

	bytesWritten = (uint32_t)_bytesWritten;

	[_stream writeLittleEndianInt32: 0x08074B50];
	[_stream writeLittleEndianInt32: _CRC32];
	[_stream writeLittleEndianInt32: bytesWritten];
	[_stream writeLittleEndianInt32: bytesWritten];
	_bytesWritten += (4 * 4);

	[_stream release];
	_stream = nil;

	[_entry setCRC32: ~_CRC32];
	[_entry setCompressedSize: bytesWritten];
	[_entry setUncompressedSize: bytesWritten];
	[_entry makeImmutable];
}


@end







<
<



<
<
<
<
<


|
|
<





|
|

|
>
>

853
854
855
856
857
858
859


860
861
862





863
864
865
866

867
868
869
870
871
872
873
874
875
876
877
878

	_bytesWritten += (int64_t)length;
	_CRC32 = of_crc32(_CRC32, buffer, length);
}

- (void)close
{


	if (_stream == nil)
		return;






	[_stream writeLittleEndianInt32: 0x08074B50];
	[_stream writeLittleEndianInt32: _CRC32];
	[_stream writeLittleEndianInt64: _bytesWritten];
	[_stream writeLittleEndianInt64: _bytesWritten];


	[_stream release];
	_stream = nil;

	[_entry setCRC32: ~_CRC32];
	[_entry setCompressedSize: _bytesWritten];
	[_entry setUncompressedSize: _bytesWritten];
	[_entry makeImmutable];

	_bytesWritten += (2 * 4 + 2 * 8);
}
@end

Modified src/OFZIPArchiveEntry.m from [1983c233dd] to [fef61c95a7].

420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457










458
459


460
461
462
463
464
465
466
467
468
469
470
	objc_autoreleasePoolPop(pool);

	return [ret autorelease];
}

- (uint64_t)of_writeToStream: (OFStream *)stream
{
	void *pool;
	uint64_t size = 0;

	if (_compressedSize > UINT32_MAX ||
	    _uncompressedSize > UINT32_MAX ||
	    _localFileHeaderOffset > UINT32_MAX)
		@throw [OFOutOfRangeException exception];

	pool = objc_autoreleasePoolPush();

	[stream writeLittleEndianInt32: 0x02014B50];
	[stream writeLittleEndianInt16: _versionMadeBy];
	[stream writeLittleEndianInt16: _minVersionNeeded];
	[stream writeLittleEndianInt16: _generalPurposeBitFlag];
	[stream writeLittleEndianInt16: _compressionMethod];
	[stream writeLittleEndianInt16: _lastModifiedFileTime];
	[stream writeLittleEndianInt16: _lastModifiedFileDate];
	[stream writeLittleEndianInt32: _CRC32];
	[stream writeLittleEndianInt32: (uint32_t)_compressedSize];
	[stream writeLittleEndianInt32: (uint32_t)_uncompressedSize];
	[stream writeLittleEndianInt16: (uint16_t)[_fileName UTF8StringLength]];
	[stream writeLittleEndianInt16: (uint16_t)[_extraField count]];
	[stream writeLittleEndianInt16:
	    (uint16_t)[_fileComment UTF8StringLength]];
	[stream writeLittleEndianInt16: _startDiskNumber];
	[stream writeLittleEndianInt16: _internalAttributes];
	[stream writeLittleEndianInt32: _versionSpecificAttributes];
	[stream writeLittleEndianInt32: (uint32_t)_localFileHeaderOffset];
	size += (4 + (6 * 2) + (3 * 4) + (5 * 2) + (2 * 4));

	[stream writeString: _fileName];










	if (_extraField != nil)
		[stream writeData: _extraField];


	if (_fileComment != nil)
		[stream writeString: _fileComment];
	size += (uint64_t)[_fileName UTF8StringLength] +
	    (uint64_t)[_extraField count] +
	    (uint64_t)[_fileComment UTF8StringLength];

	objc_autoreleasePoolPop(pool);

	return size;
}
@end







|


|
<
<


<
<








|
|

|


|


|



>
>
>
>
>
>
>
>
>
>


>
>


<
<
|






420
421
422
423
424
425
426
427
428
429
430


431
432


433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469


470
471
472
473
474
475
476
	objc_autoreleasePoolPop(pool);

	return [ret autorelease];
}

- (uint64_t)of_writeToStream: (OFStream *)stream
{
	void *pool = objc_autoreleasePoolPush();
	uint64_t size = 0;

	if (UINT16_MAX - [_extraField count] < 32)


		@throw [OFOutOfRangeException exception];



	[stream writeLittleEndianInt32: 0x02014B50];
	[stream writeLittleEndianInt16: _versionMadeBy];
	[stream writeLittleEndianInt16: _minVersionNeeded];
	[stream writeLittleEndianInt16: _generalPurposeBitFlag];
	[stream writeLittleEndianInt16: _compressionMethod];
	[stream writeLittleEndianInt16: _lastModifiedFileTime];
	[stream writeLittleEndianInt16: _lastModifiedFileDate];
	[stream writeLittleEndianInt32: _CRC32];
	[stream writeLittleEndianInt32: 0xFFFFFFFF];
	[stream writeLittleEndianInt32: 0xFFFFFFFF];
	[stream writeLittleEndianInt16: (uint16_t)[_fileName UTF8StringLength]];
	[stream writeLittleEndianInt16: (uint16_t)[_extraField count] + 32];
	[stream writeLittleEndianInt16:
	    (uint16_t)[_fileComment UTF8StringLength]];
	[stream writeLittleEndianInt16: 0xFFFF];
	[stream writeLittleEndianInt16: _internalAttributes];
	[stream writeLittleEndianInt32: _versionSpecificAttributes];
	[stream writeLittleEndianInt32: 0xFFFFFFFF];
	size += (4 + (6 * 2) + (3 * 4) + (5 * 2) + (2 * 4));

	[stream writeString: _fileName];
	size += (uint64_t)[_fileName UTF8StringLength];

	[stream writeLittleEndianInt16: OF_ZIP_ARCHIVE_ENTRY_EXTRA_FIELD_ZIP64];
	[stream writeLittleEndianInt16: 28];
	[stream writeLittleEndianInt64: _uncompressedSize];
	[stream writeLittleEndianInt64: _compressedSize];
	[stream writeLittleEndianInt64: _localFileHeaderOffset];
	[stream writeLittleEndianInt32: _startDiskNumber];
	size += (2 * 2) + (3 * 8) + 4;

	if (_extraField != nil)
		[stream writeData: _extraField];
	size += (uint64_t)[_extraField count];

	if (_fileComment != nil)
		[stream writeString: _fileComment];


	size += (uint64_t)[_fileComment UTF8StringLength];

	objc_autoreleasePoolPop(pool);

	return size;
}
@end