ObjFW  OFLHAArchiveEntry.m at [6b42e44f99]

File src/OFLHAArchiveEntry.m artifact 2705c71e3d part of check-in 6b42e44f99


/*
 * Copyright (c) 2008, 2009, 2010, 2011, 2012, 2013, 2014, 2015, 2016, 2017,
 *               2018
 *   Jonathan Schleifer <js@heap.zone>
 *
 * 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"

#define OF_LHA_ARCHIVE_ENTRY_M

#import "OFLHAArchiveEntry.h"
#import "OFLHAArchiveEntry+Private.h"
#import "OFArray.h"
#import "OFData.h"
#import "OFDate.h"
#import "OFNumber.h"
#import "OFStream.h"
#import "OFString.h"

#import "OFInvalidFormatException.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"];
}

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

	entry->_fileName = [[OFString alloc]
	    initWithCString: (char *)[extension items] + 1
		   encoding: encoding
		     length: [extension count] - 1];
}

static void
parseDirectoryNameExtension(OFLHAArchiveEntry *entry, OFData *extension,
    of_string_encoding_t encoding)
{
	void *pool = objc_autoreleasePoolPush();
	OFMutableData *data = [[extension mutableCopy] autorelease];
	char *items = [data items];
	size_t count = [data count];
	OFMutableString *directoryName;

	for (size_t i = 1; i < count; i++)
		if (items[i] == '\xFF')
			items[i] = '/';

	directoryName = [OFMutableString stringWithCString: items + 1
						  encoding: encoding
						    length: count - 1];

	if (![directoryName hasSuffix: @"/"])
		[directoryName appendString: @"/"];

	[directoryName makeImmutable];

	[entry->_directoryName release];
	entry->_directoryName = nil;

	entry->_directoryName = [directoryName copy];

	objc_autoreleasePoolPop(pool);
}

static void
parseCommentExtension(OFLHAArchiveEntry *entry, OFData *extension,
    of_string_encoding_t encoding)
{
	[entry->_fileComment release];
	entry->_fileComment = nil;

	entry->_fileComment = [[OFString alloc]
	    initWithCString: (char *)[extension items] + 1
		   encoding: encoding
		     length: [extension count] - 1];
}

static void
parsePermissionsExtension(OFLHAArchiveEntry *entry, OFData *extension,
    of_string_encoding_t encoding)
{
	uint16_t mode;

	if ([extension count] != 3)
		@throw [OFInvalidFormatException exception];

	memcpy(&mode, (char *)[extension items] + 1, 2);
	mode = OF_BSWAP16_IF_BE(mode);

	[entry->_mode release];
	entry->_mode = nil;

	entry->_mode = [[OFNumber alloc] initWithUInt16: mode];
}

static void
parseGIDUIDExtension(OFLHAArchiveEntry *entry, OFData *extension,
    of_string_encoding_t encoding)
{
	uint16_t UID, GID;

	if ([extension count] != 5)
		@throw [OFInvalidFormatException exception];

	memcpy(&GID, (char *)[extension items] + 1, 2);
	GID = OF_BSWAP16_IF_BE(GID);

	memcpy(&UID, (char *)[extension items] + 3, 2);
	UID = OF_BSWAP16_IF_BE(UID);

	[entry->_GID release];
	entry->_GID = nil;

	[entry->_UID release];
	entry->_UID = nil;

	entry->_GID = [[OFNumber alloc] initWithUInt16: GID];
	entry->_UID = [[OFNumber alloc] initWithUInt16: UID];
}

static void
parseGroupExtension(OFLHAArchiveEntry *entry, OFData *extension,
    of_string_encoding_t encoding)
{
	[entry->_group release];
	entry->_group = nil;

	entry->_group = [[OFString alloc]
	    initWithCString: (char *)[extension items] + 1
		   encoding: encoding
		     length: [extension count] - 1];
}

static void
parseOwnerExtension(OFLHAArchiveEntry *entry, OFData *extension,
    of_string_encoding_t encoding)
{
	[entry->_owner release];
	entry->_owner = nil;

	entry->_owner = [[OFString alloc]
	    initWithCString: (char *)[extension items] + 1
		   encoding: encoding
		     length: [extension count] - 1];
}

static void
parseModificationDateExtension(OFLHAArchiveEntry *entry, OFData *extension,
    of_string_encoding_t encoding)
{
	uint32_t modificationDate;

	if ([extension count] != 5)
		@throw [OFInvalidFormatException exception];

	memcpy(&modificationDate, (char *)[extension items] + 1, 4);
	modificationDate = OF_BSWAP32_IF_BE(modificationDate);

	[entry->_modificationDate release];
	entry->_modificationDate = nil;

	entry->_modificationDate = [[OFDate alloc]
	    initWithTimeIntervalSince1970: modificationDate];
}

static bool
parseExtension(OFLHAArchiveEntry *entry, OFData *extension,
    of_string_encoding_t encoding, bool allowFileName)
{
	void (*function)(OFLHAArchiveEntry *, OFData *, of_string_encoding_t) =
	    NULL;

	switch (*(char *)[extension itemAtIndex: 0]) {
	case 0x01:
		if (allowFileName)
			function = parseFileNameExtension;
		break;
	case 0x02:
		function = parseDirectoryNameExtension;
		break;
	case 0x3F:
		function = parseCommentExtension;
		break;
	case 0x50:
		function = parsePermissionsExtension;
		break;
	case 0x51:
		function = parseGIDUIDExtension;
		break;
	case 0x52:
		function = parseGroupExtension;
		break;
	case 0x53:
		function = parseOwnerExtension;
		break;
	case 0x54:
		function = parseModificationDateExtension;
		break;
	}

	if (function == NULL)
		return false;

	function(entry, extension, encoding);
	return true;
}

static void
readExtensions(OFLHAArchiveEntry *entry, OFStream *stream,
    of_string_encoding_t encoding, bool allowFileName)
{
	uint16_t size;

	while ((size = [stream readLittleEndianInt16]) > 0) {
		OFData *extension;

		if (size < 2)
			@throw [OFInvalidFormatException exception];

		extension = [stream readDataWithCount: size - 2];

		if (!parseExtension(entry, extension, encoding, allowFileName))
			[entry->_extensions addObject: extension];

		if (entry->_headerLevel == 1) {
			if (entry->_compressedSize < size)
				@throw [OFInvalidFormatException exception];

			entry->_compressedSize -= size;
		}
	}
}

@implementation OFLHAArchiveEntry
+ (instancetype)entryWithFileName: (OFString *)fileName
{
	return [[[self alloc] initWithFileName: fileName] autorelease];
}

- (instancetype)init
{
	OF_INVALID_INIT_METHOD
}

- (instancetype)initWithFileName: (OFString *)fileName
{
	self = [super init];

	@try {
		_fileName = [fileName copy];
		_compressionMethod = @"-lh0-";
		_date = [[OFDate alloc] initWithTimeIntervalSince1970: 0];
	} @catch (id e) {
		[self release];
		@throw e;
	}

	return self;
}

- (instancetype)of_initWithHeader: (char [21])header
			   stream: (OFStream *)stream
			 encoding: (of_string_encoding_t)encoding
{
	self = [super init];

	@try {
		uint32_t date;

		_compressionMethod = [[OFString alloc]
		    initWithCString: header + 2
			   encoding: OF_STRING_ENCODING_ASCII
			     length: 5];

		memcpy(&_compressedSize, header + 7, 4);
		_compressedSize = OF_BSWAP32_IF_BE(_compressedSize);

		memcpy(&_uncompressedSize, header + 11, 4);
		_uncompressedSize = OF_BSWAP32_IF_BE(_uncompressedSize);

		memcpy(&date, header + 15, 4);
		date = OF_BSWAP32_IF_BE(date);

		_headerLevel = header[20];
		_extensions = [[OFMutableArray alloc] init];

		switch (_headerLevel) {
		case 0:
		case 1:;
			void *pool = objc_autoreleasePoolPush();
			uint8_t fileNameLength;
			OFString *tmp;

			_date = [parseMSDOSDate(date) retain];

			fileNameLength = [stream readInt8];
			tmp = [stream readStringWithLength: fileNameLength
						  encoding: encoding];
			tmp = [tmp stringByReplacingOccurrencesOfString: @"\\"
							     withString: @"/"];
			_fileName = [tmp copy];

			_CRC16 = [stream readLittleEndianInt16];

			if (_headerLevel == 1) {
				_operatingSystemIdentifier = [stream readInt8];

				readExtensions(self, stream, encoding, false);
			}

			objc_autoreleasePoolPop(pool);
			break;
		case 2:
			_date = [[OFDate alloc]
			    initWithTimeIntervalSince1970: date];

			_CRC16 = [stream readLittleEndianInt16];
			_operatingSystemIdentifier = [stream readInt8];

			readExtensions(self, stream, encoding, true);

			break;
		default:;
			OFString *version = [OFString
			    stringWithFormat: @"%u", _headerLevel];

			@throw [OFUnsupportedVersionException
			    exceptionWithVersion: version];
		}

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

	return self;
}

- (void)dealloc
{
	[_compressionMethod release];
	[_fileName release];
	[_directoryName release];
	[_date release];
	[_fileComment release];
	[_mode release];
	[_UID release];
	[_GID release];
	[_owner release];
	[_group release];
	[_extensions release];

	[super dealloc];
}

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

- (id)mutableCopy
{
	OFLHAArchiveEntry *copy = [[OFMutableLHAArchiveEntry alloc]
	    initWithFileName: _fileName];

	@try {
		[copy->_compressionMethod release];
		copy->_compressionMethod = nil;

		[copy->_date release];
		copy->_date = nil;

		copy->_directoryName = [_directoryName copy];
		copy->_compressionMethod = [_compressionMethod copy];
		copy->_compressedSize = _compressedSize;
		copy->_uncompressedSize = _uncompressedSize;
		copy->_date = [_date copy];
		copy->_headerLevel = _headerLevel;
		copy->_CRC16 = _CRC16;
		copy->_operatingSystemIdentifier = _operatingSystemIdentifier;
		copy->_fileComment = [_fileComment copy];
		copy->_mode = [_mode retain];
		copy->_UID = [_UID retain];
		copy->_GID = [_GID retain];
		copy->_owner = [_owner copy];
		copy->_group = [_group copy];
		copy->_modificationDate = [_modificationDate retain];
		copy->_extensions = [_extensions copy];
	} @catch (id e) {
		[copy release];
		@throw e;
	}

	return copy;
}

- (OFString *)fileName
{
	if (_directoryName == nil)
		return _fileName;

	return [_directoryName stringByAppendingString: _fileName];
}

- (OFString *)compressionMethod
{
	return _compressionMethod;
}

- (uint32_t)compressedSize
{
	return _compressedSize;
}

- (uint32_t)uncompressedSize
{
	return _uncompressedSize;
}

- (OFDate *)date
{
	return _date;
}

- (uint8_t)headerLevel
{
	return _headerLevel;
}

- (uint16_t)CRC16
{
	return _CRC16;
}

- (uint8_t)operatingSystemIdentifier
{
	return _operatingSystemIdentifier;
}

- (OFString *)fileComment
{
	return _fileComment;
}

- (OFNumber *)mode
{
	return _mode;
}

- (OFNumber *)UID
{
	return _UID;
}

- (OFNumber *)GID
{
	return _GID;
}

- (OFString *)owner
{
	return _owner;
}

- (OFString *)group
{
	return _group;
}

- (OFDate *)modificationDate
{
	return _modificationDate;
}

- (OFArray OF_GENERIC(OFData *) *)extensions
{
	return _extensions;
}

- (OFString *)description
{
	void *pool = objc_autoreleasePoolPush();
	OFString *mode = (_mode == nil
	    ? @"(nil)"
	    : [OFString stringWithFormat: @"%" PRIo16, [_mode uInt16Value]]);
	OFString *extensions = [[_extensions description]
	    stringByReplacingOccurrencesOfString: @"\n"
				      withString: @"\n\t"];
	OFString *ret = [OFString stringWithFormat:
	    @"<%@:\n"
	    @"\tFile name = %@\n"
	    @"\tCompression method = %@\n"
	    @"\tCompressed size = %" @PRIu32 "\n"
	    @"\tUncompressed size = %" @PRIu32 "\n"
	    @"\tDate = %@\n"
	    @"\tHeader level = %u\n"
	    @"\tCRC16 = %04" @PRIX16 @"\n"
	    @"\tOperating system identifier = %c\n"
	    @"\tComment = %@\n"
	    @"\tMode = %@\n"
	    @"\tUID = %@\n"
	    @"\tGID = %@\n"
	    @"\tOwner = %@\n"
	    @"\tGroup = %@\n"
	    @"\tModification date = %@\n"
	    @"\tExtensions: %@"
	    @">",
	    [self class], [self fileName], _compressionMethod, _compressedSize,
	    _uncompressedSize, _date, _headerLevel, _CRC16,
	    _operatingSystemIdentifier, _fileComment, mode, _UID, _GID, _owner,
	    _group, _modificationDate, extensions];

	[ret retain];

	objc_autoreleasePoolPop(pool);

	return [ret autorelease];
}
@end