ObjFW  Check-in [9a95da71d5]

Overview
Comment:Add OFZooArchive

Only support for reading so far.

Compression methods 0 (uncompressed) and 2 (LZ77 + Huffman, apparently
identical to lh4/lh5) are supported.

Downloads: Tarball | ZIP archive | SQL archive
Timelines: family | ancestors | descendants | both | trunk
Files: files | file ages | folders
SHA3-256: 9a95da71d52f7396534a701e7c07b09d2599e1881f2461fe8672f6f0fc6f7e96
User & Date: js on 2024-02-28 00:24:42
Other Links: manifest | tags
Context
2024-02-28
00:41
Fix missing import check-in: 4f9e27d416 user: js tags: trunk
00:24
Add OFZooArchive check-in: 9a95da71d5 user: js tags: trunk
2024-02-27
21:06
Add missing imports when using --disable-sockets check-in: 2d14df1979 user: js tags: trunk
Changes

Modified src/Makefile from [914eea7d94] to [fc3f492094].

104
105
106
107
108
109
110


111
112
113
114
115
116
117
       OFXMLElement.m			\
       OFXMLElementBuilder.m		\
       OFXMLNode.m			\
       OFXMLParser.m			\
       OFXMLProcessingInstruction.m	\
       OFZIPArchive.m			\
       OFZIPArchiveEntry.m		\


       ${USE_SRCS_FILES}		\
       ${USE_SRCS_PLUGINS}		\
       ${USE_SRCS_SOCKETS}		\
       ${USE_SRCS_SUBPROCESSES}		\
       ${USE_SRCS_THREADS}		\
       ${USE_SRCS_WINDOWS}
SRCS_FILES = OFFile.m			\







>
>







104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
       OFXMLElement.m			\
       OFXMLElementBuilder.m		\
       OFXMLNode.m			\
       OFXMLParser.m			\
       OFXMLProcessingInstruction.m	\
       OFZIPArchive.m			\
       OFZIPArchiveEntry.m		\
       OFZooArchive.m			\
       OFZooArchiveEntry.m		\
       ${USE_SRCS_FILES}		\
       ${USE_SRCS_PLUGINS}		\
       ${USE_SRCS_SOCKETS}		\
       ${USE_SRCS_SUBPROCESSES}		\
       ${USE_SRCS_THREADS}		\
       ${USE_SRCS_WINDOWS}
SRCS_FILES = OFFile.m			\

Modified src/OFLHAArchive.m from [b00facb75e] to [97efb45aaf].

412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
	_CRC16 = OFCRC16(_CRC16, buffer, ret);

	if (_toRead == 0) {
		_atEndOfStream = true;

		if (_CRC16 != _entry.CRC16) {
			OFString *actualChecksum = [OFString stringWithFormat:
			    @"%04" PRIX16, _CRC16];
			OFString *expectedChecksum = [OFString stringWithFormat:
			    @"%04" PRIX16, _entry.CRC16];

			@throw [OFChecksumMismatchException
			    exceptionWithActualChecksum: actualChecksum
				       expectedChecksum: expectedChecksum];
		}
	}








|

|







412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
	_CRC16 = OFCRC16(_CRC16, buffer, ret);

	if (_toRead == 0) {
		_atEndOfStream = true;

		if (_CRC16 != _entry.CRC16) {
			OFString *actualChecksum = [OFString stringWithFormat:
			    @"%04" @PRIX16, _CRC16];
			OFString *expectedChecksum = [OFString stringWithFormat:
			    @"%04" @PRIX16, _entry.CRC16];

			@throw [OFChecksumMismatchException
			    exceptionWithActualChecksum: actualChecksum
				       expectedChecksum: expectedChecksum];
		}
	}

Modified src/OFLHAArchiveEntry.m from [49afc93b6c] to [13f8a765b6].

29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55

56
57
58
59
60
61
62
#import "OFString.h"

#import "OFInvalidArgumentException.h"
#import "OFInvalidFormatException.h"
#import "OFOutOfRangeException.h"
#import "OFUnsupportedVersionException.h"

@implementation OFLHAArchiveEntry
static OFDate *
parseMSDOSDate(uint32_t MSDOSDate)
{
	uint16_t year = ((MSDOSDate & 0xFE000000) >> 25) + 1980;
	uint8_t month = (MSDOSDate & 0x1E00000) >> 21;
	uint8_t day = (MSDOSDate & 0x1F);
	uint8_t hour = (MSDOSDate & 0xF800) >> 11;
	uint8_t minute = (MSDOSDate & 0x7E0) >> 5;
	uint8_t second = (MSDOSDate & 0x1F) << 1;
	OFString *dateString;

	dateString = [OFString
	    stringWithFormat: @"%04u-%02u-%02u %02u:%02u:%02u",
			      year, month, day, hour, minute, second];

	return [OFDate dateWithLocalDateString: dateString
					format: @"%Y-%m-%d %H:%M:%S"];
}


static void
parseFileNameExtension(OFLHAArchiveEntry *entry, OFData *extension,
    OFStringEncoding encoding)
{
	[entry->_fileName release];
	entry->_fileName = nil;








<



















>







29
30
31
32
33
34
35

36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
#import "OFString.h"

#import "OFInvalidArgumentException.h"
#import "OFInvalidFormatException.h"
#import "OFOutOfRangeException.h"
#import "OFUnsupportedVersionException.h"


static OFDate *
parseMSDOSDate(uint32_t MSDOSDate)
{
	uint16_t year = ((MSDOSDate & 0xFE000000) >> 25) + 1980;
	uint8_t month = (MSDOSDate & 0x1E00000) >> 21;
	uint8_t day = (MSDOSDate & 0x1F);
	uint8_t hour = (MSDOSDate & 0xF800) >> 11;
	uint8_t minute = (MSDOSDate & 0x7E0) >> 5;
	uint8_t second = (MSDOSDate & 0x1F) << 1;
	OFString *dateString;

	dateString = [OFString
	    stringWithFormat: @"%04u-%02u-%02u %02u:%02u:%02u",
			      year, month, day, hour, minute, second];

	return [OFDate dateWithLocalDateString: dateString
					format: @"%Y-%m-%d %H:%M:%S"];
}

@implementation OFLHAArchiveEntry
static void
parseFileNameExtension(OFLHAArchiveEntry *entry, OFData *extension,
    OFStringEncoding encoding)
{
	[entry->_fileName release];
	entry->_fileName = nil;

Modified src/OFZIPArchiveEntry.m from [1e5ec904f3] to [d0352dc341].

424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
	OFString *compressionMethod =
	    OFZIPArchiveEntryCompressionMethodName(_compressionMethod);
	OFString *ret = [OFString stringWithFormat:
	    @"<%@:\n"
	    @"\tFile name = %@\n"
	    @"\tFile comment = %@\n"
	    @"\tGeneral purpose bit flag = %u\n"
	    @"\tCompressed size = %" @PRIu64 "\n"
	    @"\tUncompressed size = %" @PRIu64 "\n"
	    @"\tCompression method = %@\n"
	    @"\tModification date = %@\n"
	    @"\tCRC32 = %08" @PRIX32 @"\n"
	    @"\tExtra field = %@\n"
	    @">",
	    self.class, _fileName, _fileComment, _generalPurposeBitFlag,
	    _compressedSize, _uncompressedSize, compressionMethod,







|
|







424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
	OFString *compressionMethod =
	    OFZIPArchiveEntryCompressionMethodName(_compressionMethod);
	OFString *ret = [OFString stringWithFormat:
	    @"<%@:\n"
	    @"\tFile name = %@\n"
	    @"\tFile comment = %@\n"
	    @"\tGeneral purpose bit flag = %u\n"
	    @"\tCompressed size = %llu\n"
	    @"\tUncompressed size = %llu\n"
	    @"\tCompression method = %@\n"
	    @"\tModification date = %@\n"
	    @"\tCRC32 = %08" @PRIX32 @"\n"
	    @"\tExtra field = %@\n"
	    @">",
	    self.class, _fileName, _fileComment, _generalPurposeBitFlag,
	    _compressedSize, _uncompressedSize, compressionMethod,

Added src/OFZooArchive.h version [966424ef25].







































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * 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
 * the packaging of this file.
 *
 * 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.
 */

#import "OFObject.h"
#import "OFString.h"
#import "OFZooArchiveEntry.h"

OF_ASSUME_NONNULL_BEGIN

@class OFStream;

/**
 * @class OFZooArchive OFZooArchive.h ObjFW/OFZooArchive.h
 *
 * @brief A class for accessing and manipulating Zoo files.
 */
OF_SUBCLASSING_RESTRICTED
@interface OFZooArchive: OFObject
{
	OF_KINDOF(OFStream *) _stream;
	uint_least8_t _mode;
	OFStringEncoding _encoding;
	OFZooArchiveEntry *_Nullable _currentEntry;
#ifdef OF_ZOO_ARCHIVE_M
@public
#endif
	OFStream *_Nullable _lastReturnedStream;
}

/**
 * @brief The encoding to use for the archive. Defaults to UTF-8.
 */
@property (nonatomic) OFStringEncoding encoding;

/**
 * @brief Creates a new OFZooArchive object with the specified stream.
 *
 * @param stream A stream from which the Zoo archive will be read.
 *		 For read mode, this needs to be an OFSeekableStream.
 * @param mode The mode for the Zoo file. The only valid mode is "r" for
 *	       reading.
 * @return A new, autoreleased OFZooArchive
 */
+ (instancetype)archiveWithStream: (OFStream *)stream mode: (OFString *)mode;

/**
 * @brief Creates a new OFZooArchive object with the specified file.
 *
 * @param IRI The IRI to the Zoo file
 * @param mode The mode for the Zoo file. The only valid mode is "r" for
 *	       reading.
 * @return A new, autoreleased OFZooArchive
 */
+ (instancetype)archiveWithIRI: (OFIRI *)IRI mode: (OFString *)mode;

- (instancetype)init OF_UNAVAILABLE;

/**
 * @brief Initializes an already allocated OFZooArchive object with the
 *	  specified stream.
 *
 * @param stream A stream from which the Zoo archive will be read.
 *		 For read mode, this needs to be an OFSeekableStream.
 * @param mode The mode for the Zoo file. The only valid mode is "r" for
 *	       reading.
 * @return An initialized OFZooArchive
 */
- (instancetype)initWithStream: (OFStream *)stream
			  mode: (OFString *)mode OF_DESIGNATED_INITIALIZER;

/**
 * @brief Initializes an already allocated OFZooArchive object with the
 *	  specified file.
 *
 * @param IRI The IRI to the Zoo file
 * @param mode The mode for the Zoo file. The only valid mode is "r" for
 *	       reading.
 * @return An initialized OFZooArchive
 */
- (instancetype)initWithIRI: (OFIRI *)IRI mode: (OFString *)mode;

/**
 * @brief Returns the next entry from the Zoo archive or `nil` if all entries
 *	  have been read.
 *
 * @note This is only available in read mode.
 *
 * @warning Calling @ref nextEntry will invalidate all streams returned by
 *	    @ref streamForReadingCurrentEntry! Reading from an invalidated
 *	    stream will throw an @ref OFReadFailedException!
 *
 * @return The next entry from the Zoo archive or `nil` if all entries have
 *	   been read
 * @throw OFInvalidFormatException The archive's format is invalid
 * @throw OFTruncatedDataException The archive was truncated
 */
- (nullable OFZooArchiveEntry *)nextEntry;

/**
 * @brief Returns a stream for reading the current entry.
 *
 * @note This is only available in read mode.
 *
 * @note The returned stream conforms to @ref OFReadyForReadingObserving if the
 *	 underlying stream does so, too.
 *
 * @return A stream for reading the current entry
 */
- (OFStream *)streamForReadingCurrentEntry;

/**
 * @brief Closes the OFZooArchive.
 *
 * @throw OFNotOpenException The archive is not open
 */
- (void)close;
@end

OF_ASSUME_NONNULL_END

Added src/OFZooArchive.m version [47da796afe].













































































































































































































































































































































































































































































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
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
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
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
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
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
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * 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
 * the packaging of this file.
 *
 * 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_ZOO_ARCHIVE_M

#include "config.h"

#import "OFZooArchive.h"
#import "OFZooArchiveEntry.h"
#import "OFZooArchiveEntry+Private.h"
#import "OFCRC16.h"
#import "OFIRI.h"
#import "OFIRIHandler.h"
#import "OFLHADecompressingStream.h"
#import "OFSeekableStream.h"
#import "OFStream.h"
#import "OFString.h"

#import "OFChecksumMismatchException.h"
#import "OFInvalidArgumentException.h"
#import "OFInvalidFormatException.h"
#import "OFNotOpenException.h"
#import "OFTruncatedDataException.h"
#import "OFUnsupportedVersionException.h"

enum {
	modeRead
};

OF_DIRECT_MEMBERS
@interface OFZooArchive ()
- (void)of_readArchiveHeader;
@end

OF_DIRECT_MEMBERS
@interface OFZooArchiveFileReadStream: OFStream <OFReadyForReadingObserving>
{
	OFZooArchive *_archive;
	OF_KINDOF(OFStream *) _stream;
	OFStream *_decompressedStream;
	OFZooArchiveEntry *_entry;
	unsigned long long _toRead;
	uint16_t _CRC16;
	bool _atEndOfStream;
}

- (instancetype)of_initWithArchive: (OFZooArchive *)archive
			    stream: (OFStream *)stream
			     entry: (OFZooArchiveEntry *)entry;
@end

@implementation OFZooArchive
@synthesize encoding = _encoding;

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

+ (instancetype)archiveWithIRI: (OFIRI *)IRI mode: (OFString *)mode
{
	return [[[self alloc] initWithIRI: IRI mode: mode] autorelease];
}

- (instancetype)init
{
	OF_INVALID_INIT_METHOD
}

- (instancetype)initWithStream: (OFStream *)stream mode: (OFString *)mode
{
	self = [super init];

	@try {
		if ([mode isEqual: @"r"])
			_mode = modeRead;
		else
			@throw [OFInvalidArgumentException exception];

		_stream = [stream retain];
		_encoding = OFStringEncodingUTF8;

		if (_mode == modeRead) {
			if (![stream isKindOfClass: [OFSeekableStream class]])
				@throw [OFInvalidArgumentException exception];

			[self of_readArchiveHeader];
		}
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (instancetype)initWithIRI: (OFIRI *)IRI mode: (OFString *)mode
{
	void *pool = objc_autoreleasePoolPush();
	OFStream *stream;

	@try {
		if ([mode isEqual: @"a"])
			stream = [OFIRIHandler openItemAtIRI: IRI mode: @"r+"];
		else
			stream = [OFIRIHandler openItemAtIRI: IRI mode: mode];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	self = [self initWithStream: stream mode: mode];

	objc_autoreleasePoolPop(pool);

	return self;
}

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

	[_currentEntry release];

	[super dealloc];
}

- (void)of_readArchiveHeader
{
	char headerText[20];
	uint32_t firstFileOffset;

	[_stream readIntoBuffer: headerText exactLength: 20];

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

	firstFileOffset = [_stream readLittleEndianInt32];

	if ([_stream readLittleEndianInt32] != ~(uint32_t)(firstFileOffset - 1))
		@throw [OFInvalidFormatException exception];

	/* Version */
	[_stream readBigEndianInt16];

	[_stream seekToOffset: firstFileOffset whence: OFSeekSet];
}

- (OFZooArchiveEntry *)nextEntry
{
	if (_mode != modeRead)
		@throw [OFInvalidArgumentException exception];

	if (_currentEntry != nil)
		[_stream seekToOffset: _currentEntry->_nextHeaderOffset
			       whence: OFSeekSet];

	[_currentEntry release];
	_currentEntry = nil;

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

	_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
{
	if (_mode != modeRead)
		@throw [OFInvalidArgumentException exception];

	if (_currentEntry == nil)
		@throw [OFInvalidArgumentException exception];

	_lastReturnedStream = [[[OFZooArchiveFileReadStream alloc]
	    of_initWithArchive: self
			stream: _stream
			 entry: _currentEntry] autorelease];

	return _lastReturnedStream;
}

- (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. */
	}

	_lastReturnedStream = nil;

	[_stream release];
	_stream = nil;
}
@end

@implementation OFZooArchiveFileReadStream
- (instancetype)of_initWithArchive: (OFZooArchive *)archive
			    stream: (OFStream *)stream
			     entry: (OFZooArchiveEntry *)entry
{
	self = [super init];

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

		switch (entry.compressionMethod) {
		case 0:
			_decompressedStream = [stream retain];
			break;
		case 2:
			_decompressedStream = [[OFLHADecompressingStream alloc]
			    of_initWithStream: stream
				 distanceBits: 4
			       dictionaryBits: 14];
			break;
		default:
			@throw [OFUnsupportedVersionException
			    exceptionWithVersion: [OFString
			    stringWithFormat: @"%u", entry.compressionMethod]];
		}

		_entry = [entry copy];
		_toRead = entry.uncompressedSize;

		[_stream seekToOffset: entry->_dataOffset whence: OFSeekSet];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (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];

	return _atEndOfStream;
}

- (size_t)lowlevelReadIntoBuffer: (void *)buffer length: (size_t)length
{
	size_t ret;

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

	if (_atEndOfStream)
		return 0;

	if ([_stream isAtEndOfStream] &&
	    !_decompressedStream.hasDataInReadBuffer)
		@throw [OFTruncatedDataException exception];

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

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

	_toRead -= ret;
	_CRC16 = OFCRC16(_CRC16, buffer, ret);

	if (_toRead == 0) {
		_atEndOfStream = true;

		if (_CRC16 != _entry.CRC16) {
			OFString *actualChecksum = [OFString stringWithFormat:
			    @"%04" @PRIX16, _CRC16];
			OFString *expectedChecksum = [OFString stringWithFormat:
			    @"%04" @PRIX16, _entry.CRC16];

			@throw [OFChecksumMismatchException
			    exceptionWithActualChecksum: actualChecksum
				       expectedChecksum: expectedChecksum];
		}
	}

	return ret;
}

- (bool)hasDataInReadBuffer
{
	return (super.hasDataInReadBuffer ||
	    _decompressedStream.hasDataInReadBuffer);
}

- (int)fileDescriptorForReading
{
	return ((id <OFReadyForReadingObserving>)_decompressedStream)
	    .fileDescriptorForReading;
}

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

	[_stream release];
	_stream = nil;

	[_decompressedStream release];
	_decompressedStream = nil;

	[super close];
}
@end

Added src/OFZooArchiveEntry+Private.h version [fe9f3783e3].





















































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * 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
 * the packaging of this file.
 *
 * 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.
 */

#import "OFZooArchive.h"

OF_ASSUME_NONNULL_BEGIN

@interface OFZooArchiveEntry ()
- (instancetype)of_initWithStream: (OF_KINDOF(OFStream *))stream
			 encoding: (OFStringEncoding)encoding
    OF_METHOD_FAMILY(init) OF_DIRECT;
@end

OF_ASSUME_NONNULL_END

Added src/OFZooArchiveEntry.h version [117b15d3c1].

































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * 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
 * the packaging of this file.
 *
 * 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.
 */

#import "OFObject.h"
#import "OFArchiveEntry.h"

OF_ASSUME_NONNULL_BEGIN

@class OFDate;
@class OFString;

/**
 * @class OFZooArchiveEntry OFZooArchiveEntry.h ObjFW/OFZooArchiveEntry.h
 *
 * @brief A class which represents an entry in an Zoo archive.
 */
@interface OFZooArchiveEntry: OFObject <OFArchiveEntry, OFCopying>
{
	uint8_t _compressionMethod;
#ifdef OF_ZOO_ARCHIVE_M
@public
#endif
	unsigned long long _nextHeaderOffset, _dataOffset;
@protected
	uint16_t _lastModifiedFileDate, _lastModifiedFileTime;
	uint16_t _CRC16;
	unsigned long long _uncompressedSize, _compressedSize;
	bool _deleted;
	OFString *_Nullable _fileComment;
	OFString *_fileName;
	OF_RESERVE_IVARS(OFZooArchiveEntry, 4)
}

/**
 * @brief The compression method of the entry.
 */
@property (readonly, nonatomic) uint8_t compressionMethod;

/**
 * @brief The CRC16 of the file.
 */
@property (readonly, nonatomic) uint16_t CRC16;

/**
 * @brief Whether the file was deleted.
 */
@property (readonly, nonatomic, getter=isDeleted) bool deleted;

- (instancetype)init OF_UNAVAILABLE;
@end

OF_ASSUME_NONNULL_END

Added src/OFZooArchiveEntry.m version [01c49327d9].













































































































































































































































































































>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
>
1
2
3
4
5
6
7
8
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
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
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
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
/*
 * Copyright (c) 2008-2024 Jonathan Schleifer <js@nil.im>
 *
 * 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
 * the packaging of this file.
 *
 * 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.
 */

#include "config.h"

#import "OFZooArchiveEntry.h"
#import "OFZooArchiveEntry+Private.h"
#import "OFDate.h"
#import "OFSeekableStream.h"
#import "OFStream.h"
#import "OFString.h"

#import "OFInvalidFormatException.h"

@implementation OFZooArchiveEntry
@synthesize compressionMethod = _compressionMethod, CRC16 = _CRC16;
@synthesize uncompressedSize = _uncompressedSize;
@synthesize compressedSize = _compressedSize, deleted = _deleted;
@synthesize fileComment = _fileComment, fileName = _fileName;

- (instancetype)init
{
	OF_INVALID_INIT_METHOD
}

- (instancetype)of_initWithStream: (OF_KINDOF(OFStream *))stream
			 encoding: (OFStringEncoding)encoding
{
	self = [super init];

	@try {
		void *pool = objc_autoreleasePoolPush();
		uint32_t commentOffset;
		uint16_t commentLength;

		if ([stream readLittleEndianInt32] != 0xFDC4A7DC)
			@throw [OFInvalidFormatException exception];

		/* Type seems to be always 2 */
		if ([stream readInt8] != 2)
			@throw [OFInvalidFormatException exception];

		_compressionMethod = [stream readInt8];
		_nextHeaderOffset = [stream readLittleEndianInt32];
		_dataOffset = [stream readLittleEndianInt32];
		_lastModifiedFileDate = [stream readLittleEndianInt16];
		_lastModifiedFileTime = [stream readLittleEndianInt16];
		_CRC16 = [stream readLittleEndianInt16];
		_uncompressedSize = [stream readLittleEndianInt32];
		_compressedSize = [stream readLittleEndianInt32];
		/* Version */
		[stream readBigEndianInt16];
		_deleted = [stream readInt8];
		/* Unknown. Most likely padding to get to 2 byte alignment? */
		[stream readInt8];
		commentOffset = [stream readLittleEndianInt32];
		commentLength = [stream readLittleEndianInt16];

		_fileName = [[stream readLineWithEncoding: encoding] retain];

		if (commentOffset != 0) {
			[stream seekToOffset: commentOffset whence: OFSeekSet];
			_fileComment = [[stream
			    readStringWithLength: commentLength
					encoding: encoding] retain];
		}

		objc_autoreleasePoolPop(pool);
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (void)dealloc
{
	[_fileComment release];
	[_fileName release];

	[super dealloc];
}

- (id)copy
{
	return [self retain];
}

- (OFDate *)modificationDate
{
	void *pool = objc_autoreleasePoolPush();
	uint16_t year = ((_lastModifiedFileDate & 0xFE00) >> 9) + 1980;
	uint8_t month = (_lastModifiedFileDate & 0x1E0) >> 5;
	uint8_t day = (_lastModifiedFileDate & 0x1F);
	uint8_t hour = (_lastModifiedFileTime & 0xF800) >> 11;
	uint8_t minute = (_lastModifiedFileTime & 0x7E0) >> 5;
	uint8_t second = (_lastModifiedFileTime & 0x1F) << 1;
	OFDate *date;
	OFString *dateString;

	dateString = [OFString
	    stringWithFormat: @"%04u-%02u-%02u %02u:%02u:%02u",
			      year, month, day, hour, minute, second];

	date = [[OFDate alloc] initWithLocalDateString: dateString
						format: @"%Y-%m-%d %H:%M:%S"];

	objc_autoreleasePoolPop(pool);

	return [date autorelease];
}

- (OFString *)description
{
	void *pool = objc_autoreleasePoolPush();
	OFString *ret = [OFString stringWithFormat:
	    @"<%@: \n"
	    @"\tFile name = %@\n"
	    @"\tFile comment = %@\n"
	    @"\tCompressed size = %llu\n"
	    @"\tUncompressed size = %llu\n"
	    @"\tCompression method = %u\n"
	    @"\tModification date = %@\n"
	    @"\tCRC16 = %04" @PRIX16 @"\n"
	    @"\tDeleted = %u\n"
	    @">",
	    self.class, _fileName, _fileComment, _compressedSize,
	    _uncompressedSize, _compressionMethod, self.modificationDate,
	    _CRC16, _deleted];

	[ret retain];

	objc_autoreleasePoolPop(pool);

	return [ret autorelease];
}
@end

Modified src/ObjFW.h from [62977e609b] to [c01c2395b8].

62
63
64
65
66
67
68


69
70
71
72
73
74
75
#import "OFGZIPStream.h"
#import "OFLHAArchive.h"
#import "OFLHAArchiveEntry.h"
#import "OFTarArchive.h"
#import "OFTarArchiveEntry.h"
#import "OFZIPArchive.h"
#import "OFZIPArchiveEntry.h"


#import "OFFileManager.h"
#ifdef OF_HAVE_FILES
# import "OFFile.h"
#endif
#import "OFINIFile.h"
#import "OFSettings.h"
#ifdef OF_HAVE_SOCKETS







>
>







62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
#import "OFGZIPStream.h"
#import "OFLHAArchive.h"
#import "OFLHAArchiveEntry.h"
#import "OFTarArchive.h"
#import "OFTarArchiveEntry.h"
#import "OFZIPArchive.h"
#import "OFZIPArchiveEntry.h"
#import "OFZooArchive.h"
#import "OFZooArchiveEntry.h"
#import "OFFileManager.h"
#ifdef OF_HAVE_FILES
# import "OFFile.h"
#endif
#import "OFINIFile.h"
#import "OFSettings.h"
#ifdef OF_HAVE_SOCKETS