Artifact 027838e133edd9e754c46fb3612b9676ec850cab59b07144d34c23fe56248613:
- File
src/OFLHAArchiveEntry.m
— part of check-in
[327e67021e]
at
2022-08-25 19:56:25
on branch trunk
— OFLHAArchiveEntry: Merge date and modificationDate
The date is meant to be the modification date and the extension is
supposed to override the modification date on old LHA versions where the
modification date is only stored in local time. (user: js, size: 18018) [annotate] [blame] [check-ins using]
/* * Copyright (c) 2008-2022 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" #include <string.h> #import "OFLHAArchiveEntry.h" #import "OFLHAArchiveEntry+Private.h" #import "OFArray.h" #import "OFCRC16.h" #import "OFData.h" #import "OFDate.h" #import "OFNumber.h" #import "OFStream.h" #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; entry->_fileName = [[OFString alloc] initWithCString: (char *)extension.items + 1 encoding: encoding length: [extension count] - 1]; } static void parseDirectoryNameExtension(OFLHAArchiveEntry *entry, OFData *extension, OFStringEncoding encoding) { void *pool = objc_autoreleasePoolPush(); OFMutableData *data = [[extension mutableCopy] autorelease]; char *items = data.mutableItems; 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, OFStringEncoding 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, OFStringEncoding encoding) { uint16_t mode; if (extension.count != 3) @throw [OFInvalidFormatException exception]; memcpy(&mode, (char *)extension.items + 1, 2); mode = OFFromLittleEndian16(mode); [entry->_mode release]; entry->_mode = nil; entry->_mode = [[OFNumber alloc] initWithUnsignedShort: mode]; } static void parseGIDUIDExtension(OFLHAArchiveEntry *entry, OFData *extension, OFStringEncoding encoding) { uint16_t UID, GID; if (extension.count != 5) @throw [OFInvalidFormatException exception]; memcpy(&GID, (char *)extension.items + 1, 2); GID = OFFromLittleEndian16(GID); memcpy(&UID, (char *)extension.items + 3, 2); UID = OFFromLittleEndian16(UID); [entry->_GID release]; entry->_GID = nil; [entry->_UID release]; entry->_UID = nil; entry->_GID = [[OFNumber alloc] initWithUnsignedShort: GID]; entry->_UID = [[OFNumber alloc] initWithUnsignedShort: UID]; } static void parseGroupExtension(OFLHAArchiveEntry *entry, OFData *extension, OFStringEncoding 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, OFStringEncoding 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, OFStringEncoding encoding) { uint32_t modificationDate; if (extension.count != 5) @throw [OFInvalidFormatException exception]; memcpy(&modificationDate, (char *)extension.items + 1, 4); modificationDate = OFFromLittleEndian32(modificationDate); [entry->_modificationDate release]; entry->_modificationDate = nil; entry->_modificationDate = [[OFDate alloc] initWithTimeIntervalSince1970: modificationDate]; } static bool parseExtension(OFLHAArchiveEntry *entry, OFData *extension, OFStringEncoding encoding, bool allowFileName) { void (*function)(OFLHAArchiveEntry *, OFData *, OFStringEncoding) = 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, OFStringEncoding 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; } } } static void getFileNameAndDirectoryName(OFLHAArchiveEntry *entry, OFStringEncoding encoding, const char **fileName, size_t *fileNameLength, const char **directoryName, size_t *directoryNameLength) { OFMutableData *data; char *cString; size_t length; size_t pos; /* * We use OFMutableData to have an autoreleased buffer that we can * return indirectly. */ data = [OFMutableData dataWithItems: [entry->_directoryName cStringWithEncoding: encoding] count: [entry->_directoryName cStringLengthWithEncoding: encoding]]; [data addItems: [entry->_fileName cStringWithEncoding: encoding] count: [entry->_fileName cStringLengthWithEncoding: encoding]]; cString = data.mutableItems; length = data.count; pos = 0; for (size_t i = 0; i < length; i++) { if (cString[i] == '/' || cString[i] == '\\') { cString[i] = '\xFF'; pos = i + 1; } } *fileName = cString + pos; *fileNameLength = length - pos; *directoryName = cString; *directoryNameLength = pos; } - (instancetype)init { OF_INVALID_INIT_METHOD } - (instancetype)of_init { self = [super init]; @try { _compressionMethod = @"-lh0-"; _modificationDate = [[OFDate alloc] init]; } @catch (id e) { [self release]; @throw e; } return self; } - (instancetype)of_initWithHeader: (char [21])header stream: (OFStream *)stream encoding: (OFStringEncoding)encoding { self = [super init]; @try { uint32_t date; _compressionMethod = [[OFString alloc] initWithCString: header + 2 encoding: OFStringEncodingASCII length: 5]; memcpy(&_compressedSize, header + 7, 4); _compressedSize = OFFromLittleEndian32(_compressedSize); memcpy(&_uncompressedSize, header + 11, 4); _uncompressedSize = OFFromLittleEndian32(_uncompressedSize); memcpy(&date, header + 15, 4); date = OFFromLittleEndian32(date); _headerLevel = header[20]; _extensions = [[OFMutableArray alloc] init]; switch (_headerLevel) { case 0: case 1:; void *pool = objc_autoreleasePoolPush(); uint8_t fileNameLength; OFString *tmp; _modificationDate = [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: _modificationDate = [[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]; } if (_fileName == nil) @throw [OFInvalidFormatException exception]; [_extensions makeImmutable]; } @catch (id e) { [self release]; @throw e; } return self; } - (void)dealloc { [_compressionMethod release]; [_fileName release]; [_directoryName release]; [_modificationDate 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->_modificationDate release]; copy->_modificationDate = nil; copy->_directoryName = [_directoryName copy]; copy->_compressionMethod = [_compressionMethod copy]; copy->_compressedSize = _compressedSize; copy->_uncompressedSize = _uncompressedSize; copy->_modificationDate = [_modificationDate 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->_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 *)modificationDate { return _modificationDate; } - (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; } - (OFArray OF_GENERIC(OFData *) *)extensions { return _extensions; } - (void)of_writeToStream: (OFStream *)stream encoding: (OFStringEncoding)encoding { void *pool = objc_autoreleasePoolPush(); OFMutableData *data = [OFMutableData dataWithCapacity: 24]; const char *fileName, *directoryName; size_t fileNameLength, directoryNameLength; uint16_t tmp16; uint32_t tmp32; size_t headerSize; if ([_compressionMethod cStringLengthWithEncoding: OFStringEncodingASCII] != 5) @throw [OFInvalidArgumentException exception]; getFileNameAndDirectoryName(self, encoding, &fileName, &fileNameLength, &directoryName, &directoryNameLength); if (fileNameLength > UINT16_MAX - 3 || directoryNameLength > UINT16_MAX - 3) @throw [OFOutOfRangeException exception]; /* Length. Filled in after we're done. */ [data increaseCountBy: 2]; [data addItems: [_compressionMethod cStringWithEncoding: OFStringEncodingASCII] count: 5]; tmp32 = OFToLittleEndian32(_compressedSize); [data addItems: &tmp32 count: sizeof(tmp32)]; tmp32 = OFToLittleEndian32(_uncompressedSize); [data addItems: &tmp32 count: sizeof(tmp32)]; tmp32 = OFToLittleEndian32( (uint32_t)_modificationDate.timeIntervalSince1970); [data addItems: &tmp32 count: sizeof(tmp32)]; /* Reserved */ [data increaseCountBy: 1]; /* Header level */ [data addItem: "\x02"]; /* CRC16 */ tmp16 = OFToLittleEndian16(_CRC16); [data addItems: &tmp16 count: sizeof(tmp16)]; /* Operating system identifier */ [data addItem: "U"]; /* Common header. Contains CRC16, which is written at the end. */ tmp16 = OFToLittleEndian16(5); [data addItems: &tmp16 count: sizeof(tmp16)]; [data addItem: "\x00"]; [data increaseCountBy: 2]; tmp16 = OFToLittleEndian16((uint16_t)fileNameLength + 3); [data addItems: &tmp16 count: sizeof(tmp16)]; [data addItem: "\x01"]; [data addItems: fileName count: fileNameLength]; if (directoryNameLength > 0) { tmp16 = OFToLittleEndian16((uint16_t)directoryNameLength + 3); [data addItems: &tmp16 count: sizeof(tmp16)]; [data addItem: "\x02"]; [data addItems: directoryName count: directoryNameLength]; } if (_fileComment != nil) { size_t fileCommentLength = [_fileComment cStringLengthWithEncoding: encoding]; if (fileCommentLength > UINT16_MAX - 3) @throw [OFOutOfRangeException exception]; tmp16 = OFToLittleEndian16((uint16_t)fileCommentLength + 3); [data addItems: &tmp16 count: sizeof(tmp16)]; [data addItem: "\x3F"]; [data addItems: [_fileComment cStringWithEncoding: encoding] count: fileCommentLength]; } if (_mode != nil) { tmp16 = OFToLittleEndian16(5); [data addItems: &tmp16 count: sizeof(tmp16)]; [data addItem: "\x50"]; tmp16 = OFToLittleEndian16(_mode.unsignedShortValue); [data addItems: &tmp16 count: sizeof(tmp16)]; } if (_UID != nil || _GID != nil) { if (_UID == nil || _GID == nil) @throw [OFInvalidArgumentException exception]; tmp16 = OFToLittleEndian16(7); [data addItems: &tmp16 count: sizeof(tmp16)]; [data addItem: "\x51"]; tmp16 = OFToLittleEndian16(_GID.unsignedShortValue); [data addItems: &tmp16 count: sizeof(tmp16)]; tmp16 = OFToLittleEndian16(_UID.unsignedShortValue); [data addItems: &tmp16 count: sizeof(tmp16)]; } if (_group != nil) { size_t groupLength = [_group cStringLengthWithEncoding: encoding]; if (groupLength > UINT16_MAX - 3) @throw [OFOutOfRangeException exception]; tmp16 = OFToLittleEndian16((uint16_t)groupLength + 3); [data addItems: &tmp16 count: sizeof(tmp16)]; [data addItem: "\x52"]; [data addItems: [_group cStringWithEncoding: encoding] count: groupLength]; } if (_owner != nil) { size_t ownerLength = [_owner cStringLengthWithEncoding: encoding]; if (ownerLength > UINT16_MAX - 3) @throw [OFOutOfRangeException exception]; tmp16 = OFToLittleEndian16((uint16_t)ownerLength + 3); [data addItems: &tmp16 count: sizeof(tmp16)]; [data addItem: "\x53"]; [data addItems: [_owner cStringWithEncoding: encoding] count: ownerLength]; } for (OFData *extension in _extensions) { size_t extensionLength = extension.count; if (extension.itemSize != 1) @throw [OFInvalidArgumentException exception]; if (extensionLength > UINT16_MAX - 2) @throw [OFOutOfRangeException exception]; tmp16 = OFToLittleEndian16((uint16_t)extensionLength + 2); [data addItems: &tmp16 count: sizeof(tmp16)]; [data addItems: extension.items count: extension.count]; } /* Zero-length extension to terminate */ [data increaseCountBy: 2]; headerSize = data.count; if (headerSize > UINT16_MAX) @throw [OFOutOfRangeException exception]; /* Now fill in the size and CRC16 for the entire header */ tmp16 = OFToLittleEndian16(headerSize); memcpy([data mutableItemAtIndex: 0], &tmp16, sizeof(tmp16)); tmp16 = OFCRC16(0, data.items, data.count); tmp16 = OFToLittleEndian16(tmp16); memcpy([data mutableItemAtIndex: 27], &tmp16, sizeof(tmp16)); [stream writeData: data]; objc_autoreleasePoolPop(pool); } - (OFString *)description { void *pool = objc_autoreleasePoolPush(); OFString *mode = (_mode == nil ? nil : [OFString stringWithFormat: @"%ho", _mode.unsignedShortValue]); 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" @"\tModification date = %@\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" @"\tExtensions: %@" @">", self.class, self.fileName, _compressionMethod, _compressedSize, _uncompressedSize, _modificationDate, _headerLevel, _CRC16, _operatingSystemIdentifier, _fileComment, mode, _UID, _GID, _owner, _group, extensions]; [ret retain]; objc_autoreleasePoolPop(pool); return [ret autorelease]; } @end