ObjFW  Diff

Differences From Artifact [2bbedc5f4d]:

To Artifact [3d9576934f]:


9
10
11
12
13
14
15


16
17
18
19
20
21
22

23
24
25



26

27

28
29
30

31
32
33


34
35
36
37
38
39
40
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25



26
27
28
29
30

31
32


33



34
35
36
37
38
39
40
41
42







+
+







+
-
-
-
+
+
+

+
-
+

-
-
+
-
-
-
+
+







 *
 * Alternatively, it may be distributed under the terms of the GNU General
 * Public License, either version 2 or 3, which can be found in the file
 * LICENSE.GPLv2 or LICENSE.GPLv3 respectively included in the packaging of this
 * file.
 */

#define OF_ZIP_ARCHIVE_M

#include "config.h"

#include <errno.h>

#import "OFZIPArchive.h"
#import "OFZIPArchiveEntry.h"
#import "OFZIPArchiveEntry+Private.h"
#import "OFArchiveURIHandler.h"
#import "OFCRC32.h"
#import "OFData.h"
#import "OFArray.h"
#import "OFArray.h"
#import "OFCRC32.h"
#import "OFData.h"
#import "OFDictionary.h"
#import "OFInflate64Stream.h"
#import "OFStream.h"
#import "OFInflateStream.h"
#import "OFSeekableStream.h"
#ifdef OF_HAVE_FILES
# import "OFFile.h"
#import "OFStream.h"
#endif
#import "OFInflateStream.h"
#import "OFInflate64Stream.h"
#import "OFURI.h"
#import "OFURIHandler.h"

#import "OFChecksumMismatchException.h"
#import "OFInvalidArgumentException.h"
#import "OFInvalidFormatException.h"
#import "OFNotImplementedException.h"
#import "OFNotOpenException.h"
#import "OFOpenItemFailedException.h"
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
58
59
60
61
62
63
64

65
66
67
68
69
70
71







-







	modeAppend
};

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

OF_DIRECT_MEMBERS
@interface OFZIPArchiveLocalFileHeader: OFObject
{
@public
79
80
81
82
83
84
85

86
87
88

89
90
91
92
93
94



95
96
97
98
99

100
101
102
103

104
105
106

107
108


109
110
111
112
113
114
115
80
81
82
83
84
85
86
87
88
89

90
91
92
93
94


95
96
97
98
99
100
101
102
103
104
105
106

107
108
109
110
111


112
113
114
115
116
117
118
119
120







+


-
+




-
-
+
+
+





+



-
+



+
-
-
+
+







- (instancetype)initWithStream: (OFStream *)stream;
- (bool)matchesEntry: (OFZIPArchiveEntry *)entry;
@end

OF_DIRECT_MEMBERS
@interface OFZIPArchiveFileReadStream: OFStream
{
	OFZIPArchive *_archive;
	OFStream *_stream, *_decompressedStream;
	OFZIPArchiveEntry *_entry;
	uint64_t _toRead;
	unsigned long long _toRead;
	uint32_t _CRC32;
	bool _atEndOfStream;
}

- (instancetype)of_initWithStream: (OFStream *)stream
			    entry: (OFZIPArchiveEntry *)entry;
- (instancetype)of_initWithArchive: (OFZIPArchive *)archive
			    stream: (OFStream *)stream
			     entry: (OFZIPArchiveEntry *)entry;
@end

OF_DIRECT_MEMBERS
@interface OFZIPArchiveFileWriteStream: OFStream
{
	OFZIPArchive *_archive;
	OFStream *_stream;
	uint32_t _CRC32;
@public
	int64_t _bytesWritten;
	unsigned long long _bytesWritten;
	OFMutableZIPArchiveEntry *_entry;
}

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

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

140
141
142
143
144
145
146
147

148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168

169
170

171
172





173
174
175
176
177
178
179
145
146
147
148
149
150
151

152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171


172
173

174
175

176
177
178
179
180
181
182
183
184
185
186
187







-
+



















-
-
+

-
+

-
+
+
+
+
+







	*size -= 8;

	return field;
}

static void
seekOrThrowInvalidFormat(OFSeekableStream *stream,
    OFFileOffset offset, int whence)
    OFStreamOffset offset, OFSeekWhence whence)
{
	@try {
		[stream seekToOffset: offset whence: whence];
	} @catch (OFSeekFailedException *e) {
		if (e.errNo == EINVAL)
			@throw [OFInvalidFormatException exception];

		@throw e;
	}
}

@implementation OFZIPArchive
@synthesize archiveComment = _archiveComment;

+ (instancetype)archiveWithStream: (OFStream *)stream mode: (OFString *)mode
{
	return [[[self alloc] initWithStream: stream mode: mode] autorelease];
}

#ifdef OF_HAVE_FILES
+ (instancetype)archiveWithPath: (OFString *)path mode: (OFString *)mode
+ (instancetype)archiveWithURI: (OFURI *)URI mode: (OFString *)mode
{
	return [[[self alloc] initWithPath: path mode: mode] autorelease];
	return [[[self alloc] initWithURI: URI mode: mode] autorelease];
}
#endif

+ (OFURI *)URIForFilePath: (OFString *)path inArchiveWithURI: (OFURI *)URI
{
	return OFArchiveURIHandlerURIForFileInArchive(@"zip", path, URI);
}

- (instancetype)init
{
	OF_INVALID_INIT_METHOD
}

- (instancetype)initWithStream: (OFStream *)stream mode: (OFString *)mode
201
202
203
204
205
206
207
208

209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227

228

229

230

231
232
233



234
235
236
237







238
239
240


241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264

265
266
267
268
269

270
271
272
273
274
275
276
209
210
211
212
213
214
215

216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233


234
235
236

237
238
239



240
241
242




243
244
245
246
247
248
249



250
251
252
253
254

255
256
257
258
259
260
261
262
263
264

265
266
267
268
269
270
271
272

273
274
275
276
277

278
279
280
281
282
283
284
285







-
+

















-
-
+

+
-
+

+
-
-
-
+
+
+
-
-
-
-
+
+
+
+
+
+
+
-
-
-
+
+



-










-








-
+




-
+







			[self of_readZIPInfo];
			[self of_readEntries];
		}

		if (_mode == modeAppend) {
			_offset = _centralDirectoryOffset;
			seekOrThrowInvalidFormat((OFSeekableStream *)_stream,
			    (OFFileOffset)_offset, SEEK_SET);
			    (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.
		 */
		[_stream release];
		_stream = nil;

		[self release];
		@throw e;
	}

	return self;
}

#ifdef OF_HAVE_FILES
- (instancetype)initWithPath: (OFString *)path mode: (OFString *)mode
- (instancetype)initWithURI: (OFURI *)URI mode: (OFString *)mode
{
	void *pool = objc_autoreleasePoolPush();
	OFFile *file;
	OFStream *stream;

	@try {
	if ([mode isEqual: @"a"])
		file = [[OFFile alloc] initWithPath: path mode: @"r+"];
	else
		if ([mode isEqual: @"a"])
			stream = [OFURIHandler openItemAtURI: URI mode: @"r+"];
		else
		file = [[OFFile alloc] initWithPath: path mode: mode];

	@try {
		self = [self initWithStream: file mode: mode];
			stream = [OFURIHandler openItemAtURI: URI mode: mode];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	self = [self initWithStream: stream mode: mode];
	} @finally {
		[file release];
	}

	objc_autoreleasePoolPop(pool);

	return self;
}
#endif

- (void)dealloc
{
	if (_stream != nil)
		[self close];

	[_stream release];
	[_archiveComment release];
	[_entries release];
	[_pathToEntryMap release];
	[_lastReturnedStream release];

	[super dealloc];
}

- (void)of_readZIPInfo
{
	void *pool = objc_autoreleasePoolPush();
	uint16_t commentLength;
	OFFileOffset offset = -22;
	OFStreamOffset offset = -22;
	bool valid = false;

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

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

295
296
297
298
299
300
301
302

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
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
336







-
+













-
+



-
+







	    _centralDirectoryEntries == 0xFFFF ||
	    _centralDirectorySize == 0xFFFFFFFF ||
	    _centralDirectoryOffset == 0xFFFFFFFF) {
		int64_t offset64;
		uint64_t size;

		seekOrThrowInvalidFormat((OFSeekableStream *)_stream,
		    offset - 20, SEEK_END);
		    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 || (OFFileOffset)offset64 != offset64)
		if (offset64 < 0 || (OFStreamOffset)offset64 != offset64)
			@throw [OFOutOfRangeException exception];

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

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

		size = [_stream readLittleEndianInt64];
		if (size < 44)
			@throw [OFInvalidFormatException exception];
336
337
338
339
340
341
342
343

344
345
346
347
348
349
350
351
352
353
354
355
356

357
358
359
360

361
362
363
364
365
366
367
345
346
347
348
349
350
351

352
353
354
355
356
357
358
359
360
361
362
363
364

365
366
367
368

369
370
371
372
373
374
375
376







-
+












-
+



-
+







		_centralDirectoryEntriesInDisk =
		    [_stream readLittleEndianInt64];
		_centralDirectoryEntries = [_stream readLittleEndianInt64];
		_centralDirectorySize = [_stream readLittleEndianInt64];
		_centralDirectoryOffset = [_stream readLittleEndianInt64];

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

	objc_autoreleasePoolPop(pool);
}

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

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

	seekOrThrowInvalidFormat((OFSeekableStream *)_stream,
	    (OFFileOffset)_centralDirectoryOffset, SEEK_SET);
	    (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)
			@throw [OFInvalidFormatException exception];
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
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
477
478

479
480
481
482
483
484
485
403
404
405
406
407
408
409






























410
411
412
413
414
415
416
417
418
419
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







-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-


















+
+
+
+
+
-
+


-
+



-
+















+
+
-
-
-
+
+
+
+

-
-
-
+







	old = _archiveComment;
	_archiveComment = [comment copy];
	[old release];

	objc_autoreleasePoolPop(pool);
}

- (void)of_closeLastReturnedStream
{
	@try {
		[_lastReturnedStream close];
	} @catch (OFNotOpenException *e) {
		/* Might have already been closed by the user - that's fine. */
	}

	if ((_mode == modeWrite || _mode == modeAppend) &&
	    [_lastReturnedStream isKindOfClass:
	    [OFZIPArchiveFileWriteStream class]]) {
		OFZIPArchiveFileWriteStream *stream =
		    (OFZIPArchiveFileWriteStream *)_lastReturnedStream;

		if (INT64_MAX - _offset < stream->_bytesWritten)
			@throw [OFOutOfRangeException exception];

		_offset += stream->_bytesWritten;

		if (stream->_entry != nil) {
			[_entries addObject: stream->_entry];
			[_pathToEntryMap setObject: stream->_entry
					    forKey: [stream->_entry fileName]];
		}
	}

	[_lastReturnedStream release];
	_lastReturnedStream = nil;
}

- (OFStream *)streamForReadingFile: (OFString *)path
{
	void *pool = objc_autoreleasePoolPush();
	OFZIPArchiveEntry *entry;
	OFZIPArchiveLocalFileHeader *localFileHeader;
	int64_t offset64;

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

	if (_mode != modeRead)
		@throw [OFInvalidArgumentException exception];

	if ((entry = [_pathToEntryMap objectForKey: path]) == nil)
		@throw [OFOpenItemFailedException exceptionWithPath: path
							       mode: @"r"
							      errNo: ENOENT];

	@try {
		[_lastReturnedStream close];
	} @catch (OFNotOpenException *e) {
		/* Might have already been closed by the user - that's fine. */
	}
	[self of_closeLastReturnedStream];
	_lastReturnedStream = nil;

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

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

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

	if ((localFileHeader->_minVersionNeeded & 0xFF) > 45) {
		OFString *version = [OFString stringWithFormat: @"%u.%u",
		    (localFileHeader->_minVersionNeeded & 0xFF) / 10,
		    (localFileHeader->_minVersionNeeded & 0xFF) % 10];

		@throw [OFUnsupportedVersionException
		    exceptionWithVersion: version];
	}

	objc_autoreleasePoolPop(pool);

	_lastReturnedStream = [[OFZIPArchiveFileReadStream alloc]
	     of_initWithStream: _stream
			 entry: entry];
	_lastReturnedStream = [[[OFZIPArchiveFileReadStream alloc]
	    of_initWithArchive: self
			stream: _stream
			 entry: entry] autorelease];

	objc_autoreleasePoolPop(pool);

	return [[_lastReturnedStream retain] autorelease];
	return _lastReturnedStream;
}

- (OFStream *)streamForWritingEntry: (OFZIPArchiveEntry *)entry_
{
	/* TODO: Avoid data descriptor when _stream is an OFSeekableStream */
	int64_t offsetAdd = 0;
	void *pool;
503
504
505
506
507
508
509





510

511
512
513
514
515
516
517
488
489
490
491
492
493
494
495
496
497
498
499

500
501
502
503
504
505
506
507







+
+
+
+
+
-
+







				 mode: @"w"
				errNo: EEXIST];

	if (entry.compressionMethod != OFZIPArchiveEntryCompressionMethodNone)
		@throw [OFNotImplementedException exceptionWithSelector: _cmd
								 object: self];

	@try {
		[_lastReturnedStream close];
	} @catch (OFNotOpenException *e) {
		/* Might have already been closed by the user - that's fine. */
	}
	[self of_closeLastReturnedStream];
	_lastReturnedStream = nil;

	fileName = entry.fileName;
	fileNameLength = fileName.UTF8StringLength;
	extraField = entry.extraField;
	extraFieldLength = extraField.count;

	if (UINT16_MAX - extraFieldLength < 20)
556
557
558
559
560
561
562

563
564


565
566
567
568

569
570
571
572
573
574
575
546
547
548
549
550
551
552
553


554
555
556
557
558

559
560
561
562
563
564
565
566







+
-
-
+
+



-
+








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

	_offset += offsetAdd;

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

	objc_autoreleasePoolPop(pool);

	return [[_lastReturnedStream retain] autorelease];
	return [_lastReturnedStream autorelease];
}

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

	_centralDirectoryEntries = 0;
618
619
620
621
622
623
624





625

626
627
628
629
630
631
632
609
610
611
612
613
614
615
616
617
618
619
620

621
622
623
624
625
626
627
628







+
+
+
+
+
-
+







}

- (void)close
{
	if (_stream == nil)
		@throw [OFNotOpenException exceptionWithObject: self];

	@try {
		[_lastReturnedStream close];
	} @catch (OFNotOpenException *e) {
		/* Might have already been closed by the user - that's fine. */
	}
	[self of_closeLastReturnedStream];
	_lastReturnedStream = nil;

	if (_mode == modeWrite || _mode == modeAppend)
		[self of_writeCentralDirectory];

	[_stream release];
	_stream = nil;
}
670
671
672
673
674
675
676
677

678
679
680
681
682
683
684
666
667
668
669
670
671
672

673
674
675
676
677
678
679
680







-
+







		ZIP64Index = OFZIPArchiveEntryExtraFieldFind(extraField,
		    OFZIPArchiveEntryExtraFieldTagZIP64, &ZIP64Size);

		if (ZIP64Index != OFNotFound) {
			const uint8_t *ZIP64 =
			    [extraField itemAtIndex: ZIP64Index];
			OFRange range =
			    OFRangeMake(ZIP64Index - 4, ZIP64Size + 4);
			    OFMakeRange(ZIP64Index - 4, ZIP64Size + 4);

			if (_uncompressedSize == 0xFFFFFFFF)
				_uncompressedSize = OFZIPArchiveReadField64(
				    &ZIP64, &ZIP64Size);
			if (_compressedSize == 0xFFFFFFFF)
				_compressedSize = OFZIPArchiveReadField64(
				    &ZIP64, &ZIP64Size);
728
729
730
731
732
733
734
735
736



737
738
739
740

741
742
743
744
745
746
747
724
725
726
727
728
729
730


731
732
733
734
735
736
737
738
739
740
741
742
743
744
745







-
-
+
+
+




+







		return false;

	return true;
}
@end

@implementation OFZIPArchiveFileReadStream
- (instancetype)of_initWithStream: (OFStream *)stream
			    entry: (OFZIPArchiveEntry *)entry
- (instancetype)of_initWithArchive: (OFZIPArchive *)archive
			    stream: (OFStream *)stream
			     entry: (OFZIPArchiveEntry *)entry
{
	self = [super init];

	@try {
		_archive = [archive retain];
		_stream = [stream retain];

		switch (entry.compressionMethod) {
		case OFZIPArchiveEntryCompressionMethodNone:
			_decompressedStream = [stream retain];
			break;
		case OFZIPArchiveEntryCompressionMethodDeflate:
772
773
774
775
776
777
778





779
780
781
782
783
784
785
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788







+
+
+
+
+







- (void)dealloc
{
	if (_stream != nil || _decompressedStream != nil)
		[self close];

	[_entry release];

	if (_archive->_lastReturnedStream == self)
		_archive->_lastReturnedStream = nil;

	[_archive release];

	[super dealloc];
}

- (bool)lowlevelIsAtEndOfStream
{
	if (_stream == nil)
		@throw [OFNotOpenException exceptionWithObject: self];
801
802
803
804
805
806
807
808

809
810
811
812
813
814
815
804
805
806
807
808
809
810

811
812
813
814
815
816
817
818







-
+







		@throw [OFTruncatedDataException exception];

#if SIZE_MAX >= UINT64_MAX
	if (length > UINT64_MAX)
		@throw [OFOutOfRangeException exception];
#endif

	if ((uint64_t)length > _toRead)
	if (length > _toRead)
		length = (size_t)_toRead;

	ret = [_decompressedStream readIntoBuffer: buffer length: length];

	_toRead -= ret;
	_CRC32 = OFCRC32(_CRC32, buffer, ret);

855
856
857
858
859
860
861

862
863


864
865
866

867
868
869
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
895
896
897
898
899

900
901
902
903
904
905
906
907
908

909
910
911
912
913
914
915
916
917
918



919
920
921
922


923
924
925
926
927
928
929
930
931
932








933
934
935
936
858
859
860
861
862
863
864
865


866
867
868
869
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
895
896
897
898
899
900

901
902
903
904
905
906
907
908

909
910
911
912
913
914
915
916
917

918
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







+
-
-
+
+



+














+
+
+
+
+










-
+







-
+








-
+










+
+
+


-
-
+
+










+
+
+
+
+
+
+
+




	_decompressedStream = nil;

	[super close];
}
@end

@implementation OFZIPArchiveFileWriteStream
- (instancetype)of_initWithArchive: (OFZIPArchive *)archive
- (instancetype)initWithStream: (OFStream *)stream
			 entry: (OFMutableZIPArchiveEntry *)entry
			    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)
		[self close];

	[_entry release];

	if (_archive->_lastReturnedStream == self)
		_archive->_lastReturnedStream = nil;

	[_archive release];

	[super dealloc];
}

- (size_t)lowlevelWriteBuffer: (const void *)buffer length: (size_t)length
{
#if SIZE_MAX >= INT64_MAX
	if (length > INT64_MAX)
		@throw [OFOutOfRangeException exception];
#endif

	if (INT64_MAX - _bytesWritten < (int64_t)length)
	if (ULLONG_MAX - _bytesWritten < length)
		@throw [OFOutOfRangeException exception];

	@try {
		[_stream writeBuffer: buffer length: length];
	} @catch (OFWriteFailedException *e) {
		OFEnsure(e.bytesWritten <= length);

		_bytesWritten += (int64_t)e.bytesWritten;
		_bytesWritten += (unsigned long long)e.bytesWritten;
		_CRC32 = OFCRC32(_CRC32, buffer, e.bytesWritten);

		if (e.errNo == EWOULDBLOCK || e.errNo == EAGAIN)
			return e.bytesWritten;

		@throw e;
	}

	_bytesWritten += (int64_t)length;
	_bytesWritten += (unsigned long long)length;
	_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: _bytesWritten];
	[_stream writeLittleEndianInt64: _bytesWritten];
	[_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