/*
* 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"
#import "OFTarArchiveEntry.h"
#import "OFTarArchiveEntry+Private.h"
#import "OFDate.h"
#import "OFNumber.h"
#import "OFStream.h"
#import "OFString.h"
#import "OFOutOfRangeException.h"
static OFString *
stringFromBuffer(const unsigned char *buffer, size_t length,
OFStringEncoding encoding)
{
for (size_t i = 0; i < length; i++)
if (buffer[i] == '\0')
length = i;
return [OFString stringWithCString: (const char *)buffer
encoding: encoding
length: length];
}
static void
stringToBuffer(unsigned char *buffer, OFString *string, size_t length,
OFStringEncoding encoding)
{
size_t cStringLength = [string cStringLengthWithEncoding: encoding];
if (cStringLength > length)
@throw [OFOutOfRangeException exception];
memcpy(buffer, [string cStringWithEncoding: encoding], cStringLength);
for (size_t i = cStringLength; i < length; i++)
buffer[i] = '\0';
}
static unsigned long long
octalValueFromBuffer(const unsigned char *buffer, size_t length,
unsigned long long max)
{
unsigned long long value = 0;
if (length == 0)
return 0;
if (buffer[0] == 0x80) {
for (size_t i = 1; i < length; i++)
value = (value << 8) | buffer[i];
} else
value = [stringFromBuffer(buffer, length,
OFStringEncodingASCII) unsignedLongLongValueWithBase: 8];
if (value > max)
@throw [OFOutOfRangeException exception];
return value;
}
@implementation OFTarArchiveEntry
- (instancetype)init
{
OF_INVALID_INIT_METHOD
}
- (instancetype)of_init
{
self = [super init];
@try {
_type = OFTarArchiveEntryTypeFile;
_POSIXPermissions =
[[OFNumber alloc] initWithUnsignedShort: 0644];
} @catch (id e) {
[self release];
@throw e;
}
return self;
}
- (instancetype)of_initWithHeader: (unsigned char [512])header
encoding: (OFStringEncoding)encoding
{
self = [super init];
@try {
void *pool = objc_autoreleasePoolPush();
OFString *targetFileName;
_fileName = [stringFromBuffer(header, 100, encoding) copy];
_POSIXPermissions = [[OFNumber alloc] initWithUnsignedLongLong:
octalValueFromBuffer(header + 100, 8, ULONG_MAX)];
_ownerAccountID = [[OFNumber alloc] initWithUnsignedLongLong:
octalValueFromBuffer(header + 108, 8, ULONG_MAX)];
_groupOwnerAccountID = [[OFNumber alloc]
initWithUnsignedLongLong:
octalValueFromBuffer(header + 116, 8, ULONG_MAX)];
_uncompressedSize = (unsigned long long)octalValueFromBuffer(
header + 124, 12, ULLONG_MAX);
_compressedSize =
_uncompressedSize + (512 - _uncompressedSize % 512);
_modificationDate = [[OFDate alloc]
initWithTimeIntervalSince1970:
(OFTimeInterval)octalValueFromBuffer(
header + 136, 12, ULLONG_MAX)];
_type = header[156];
targetFileName = stringFromBuffer(header + 157, 100, encoding);
if (targetFileName.length > 0)
_targetFileName = [targetFileName copy];
if (_type == '\0')
_type = OFTarArchiveEntryTypeFile;
if (memcmp(header + 257, "ustar\0" "00", 8) == 0) {
OFString *prefix;
_ownerAccountName =
[stringFromBuffer(header + 265, 32, encoding) copy];
_groupOwnerAccountName = [stringFromBuffer(header + 297,
32, encoding) copy];
_deviceMajor = (unsigned long)octalValueFromBuffer(
header + 329, 8, ULONG_MAX);
_deviceMinor = (unsigned long)octalValueFromBuffer(
header + 337, 8, ULONG_MAX);
prefix = stringFromBuffer(header + 345, 155, encoding);
if (prefix.length > 0) {
OFString *fileName = [OFString
stringWithFormat: @"%@/%@",
prefix, _fileName];
[_fileName release];
_fileName = [fileName copy];
}
}
objc_autoreleasePoolPop(pool);
} @catch (id e) {
[self release];
@throw e;
}
return self;
}
- (void)dealloc
{
[_fileName release];
[_POSIXPermissions release];
[_ownerAccountID release];
[_groupOwnerAccountID release];
[_modificationDate release];
[_targetFileName release];
[_ownerAccountName release];
[_groupOwnerAccountName release];
[super dealloc];
}
- (id)copy
{
return [self retain];
}
- (id)mutableCopy
{
OFTarArchiveEntry *copy = [[OFMutableTarArchiveEntry alloc]
initWithFileName: _fileName];
@try {
copy->_POSIXPermissions = [_POSIXPermissions retain];
copy->_ownerAccountID = [_ownerAccountID retain];
copy->_groupOwnerAccountID = [_groupOwnerAccountID retain];
copy->_compressedSize = _compressedSize;
copy->_uncompressedSize = _uncompressedSize;
copy->_modificationDate = [_modificationDate copy];
copy->_type = _type;
copy->_targetFileName = [_targetFileName copy];
copy->_ownerAccountName = [_ownerAccountName copy];
copy->_groupOwnerAccountName = [_groupOwnerAccountName copy];
copy->_deviceMajor = _deviceMajor;
copy->_deviceMinor = _deviceMinor;
} @catch (id e) {
[copy release];
@throw e;
}
return copy;
}
- (OFString *)fileName
{
return _fileName;
}
- (OFNumber *)POSIXPermissions
{
return _POSIXPermissions;
}
- (OFNumber *)ownerAccountID
{
return _ownerAccountID;
}
- (OFNumber *)groupOwnerAccountID
{
return _groupOwnerAccountID;
}
- (unsigned long long)compressedSize
{
return _compressedSize;
}
- (unsigned long long)uncompressedSize
{
return _uncompressedSize;
}
- (OFDate *)modificationDate
{
return _modificationDate;
}
- (OFTarArchiveEntryType)type
{
return _type;
}
- (OFString *)targetFileName
{
return _targetFileName;
}
- (OFString *)ownerAccountName
{
return _ownerAccountName;
}
- (OFString *)groupOwnerAccountName
{
return _groupOwnerAccountName;
}
- (unsigned long)deviceMajor
{
return _deviceMajor;
}
- (unsigned long)deviceMinor
{
return _deviceMinor;
}
- (OFString *)description
{
void *pool = objc_autoreleasePoolPush();
OFString *POSIXPermissions = nil, *ret;
if (_POSIXPermissions != nil)
POSIXPermissions = [OFString stringWithFormat: @"%ho",
_POSIXPermissions.unsignedShortValue];
ret = [OFString stringWithFormat: @"<%@:\n"
@"\tFile name = %@\n"
@"\tPOSIX permissions = %@\n"
@"\tOwner account ID = %@\n"
@"\tGroup owner account ID = %@\n"
@"\tCompressed size = %llu\n"
@"\tUncompressed size = %llu\n"
@"\tModification date = %@\n"
@"\tType = %u\n"
@"\tTarget file name = %@\n"
@"\tOwner account name = %@\n"
@"\tGroup owner account name = %@\n"
@"\tDevice major = %" PRIu32 @"\n"
@"\tDevice minor = %" PRIu32 @"\n"
@">",
self.class, _fileName, POSIXPermissions, _ownerAccountID,
_groupOwnerAccountID, _compressedSize, _uncompressedSize,
_modificationDate, _type, _targetFileName,
_ownerAccountName, _groupOwnerAccountName, _deviceMajor,
_deviceMinor];
[ret retain];
objc_autoreleasePoolPop(pool);
return [ret autorelease];
}
- (void)of_writeToStream: (OFStream *)stream
encoding: (OFStringEncoding)encoding
{
unsigned char buffer[512];
unsigned long long modificationDate;
uint16_t checksum = 0;
stringToBuffer(buffer, _fileName, 100, encoding);
stringToBuffer(buffer + 100,
[OFString stringWithFormat: @"%06o ",
_POSIXPermissions.unsignedShortValue], 8, OFStringEncodingASCII);
stringToBuffer(buffer + 108,
[OFString stringWithFormat: @"%06o ",
_ownerAccountID.unsignedShortValue], 8, OFStringEncodingASCII);
stringToBuffer(buffer + 116,
[OFString stringWithFormat: @"%06o ",
_groupOwnerAccountID.unsignedShortValue], 8, OFStringEncodingASCII);
stringToBuffer(buffer + 124,
[OFString stringWithFormat: @"%011llo ", _uncompressedSize], 12,
OFStringEncodingASCII);
modificationDate = _modificationDate.timeIntervalSince1970;
stringToBuffer(buffer + 136,
[OFString stringWithFormat: @"%011llo", modificationDate],
12, OFStringEncodingASCII);
/*
* During checksumming, the checksum field is expected to be set to 8
* spaces.
*/
memset(buffer + 148, ' ', 8);
buffer[156] = _type;
stringToBuffer(buffer + 157, _targetFileName, 100, encoding);
/* ustar */
memcpy(buffer + 257, "ustar\0" "00", 8);
stringToBuffer(buffer + 265, _ownerAccountName, 32, encoding);
stringToBuffer(buffer + 297, _groupOwnerAccountName, 32, encoding);
stringToBuffer(buffer + 329,
[OFString stringWithFormat: @"%06" PRIo32 " ", _deviceMajor], 8,
OFStringEncodingASCII);
stringToBuffer(buffer + 337,
[OFString stringWithFormat: @"%06" PRIo32 " ", _deviceMinor], 8,
OFStringEncodingASCII);
memset(buffer + 345, '\0', 155 + 12);
/* Fill in the checksum */
for (size_t i = 0; i < 500; i++)
checksum += buffer[i];
stringToBuffer(buffer + 148,
[OFString stringWithFormat: @"%06" PRIo16, checksum], 7,
OFStringEncodingASCII);
[stream writeBuffer: buffer length: sizeof(buffer)];
}
@end